Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
4.9 KiB
title, type, permalink
| title | type | permalink |
|---|---|---|
| Exploding_Barrels_Build_Spec | note | gamevault/03-design/exploding-barrels-build-spec |
Exploding Barrels — Build Spec (Phase 1.5 hazard v1, design-review-hardened)
Review: wf_2cb10454-fdf (3 lenses, 23 agents, 13 findings CONFIRMED / 3 refuted; the first run lost 8
verifiers to a session limit and was resumed from cache — the workflow-agent-quota-failures-look-clean
protocol held). This spec is the fold; it supersedes the pre-review draft.
The core mechanism: the FUSE (one fix for both HIGHs)
On pop the barrel does NOT die. The server zeroes the barrel's REPLICATED BlightClutter.Remaining and adds
a server-only BarrelFuse { uint ExplodeTick } (TickUtil.NonZero(now + Tuning.BarrelFuseTicks≈36)). The
client therefore sees Remaining==0 on a still-alive barrel across multiple snapshots → an unambiguous
"about to blow" cue, and the boom-vs-puff choice at despawn keys on the CACHED Remaining<=0 — a room-
teardown despawn carries Remaining>0 → no false booms (HIGH #1: despawn-inference is cause-blind;
players stand in torn-down rooms through RouteSelect — RunDirectorSystem.cs:247-251, WorldFeedbackSystem
prune :96-119). Detonation is the AUTHORING moment → DamageEvent.SourceTick = TickUtil.NonZero(detonation tick) honors the authored-tick contract dash i-frames negate against (HIGH #2; DamageEvent.cs:20-24).
The fuse also: dissolves the melee self-hit complaint (pop → step out), creates the bait loop (lure enemies
onto a lit barrel), and — because the fuse lives ON the RoomTag'd barrel — room teardown/Staging sweep
clean fused barrels for free (the orphaned-ExplosionEvent lifecycle findings dissolve).
Server
- Seeding (RoomFieldSystem clutter loop):
bc.Variant = crng.NextFloat() < 0.25f ? (byte)3 : (byte)(i % 3)(seeded rng, deterministic; variants 0-2 stay cosmetic). - Pop sites (BOTH: ResourceHarvestSystem projectile sweep; MeleeComboSystem isServer-gated harvest block):
gate on
isClutter && Variant==3INSIDE the destroyed-bitset guard (nodes share the packed arrays with Variant slot 0 — confirmed LOW). On rem<=0 for an explosive: mark destroyed[i] (unhittable this tick),SetComponent(BlightClutter{Remaining=0,...})+ecb.AddComponent(BarrelFuse{ExplodeTick})— NO destroy. Both target snapshots SKIP entries withRemaining <= 0(a fused barrel is not hittable → no double-pop). HazardExplosionSystem(new, ProjectM.Server, plainSimulationSystemGroup, NO ordering attributes, NO lifecycle gating — a fused barrel must always drain (confirmed LOW: never RequireForUpdate-gate it);TryGetSingleton<NetworkTime>invalid → skip tick, fuse persists): query(BarrelFuse, LocalTransform); whenExplodeTickelapses (NetworkTick.IsNewerThan semantics): radius scan (XZ,Tuning.BarrelExplodeRadius ≈3.25) — players via the BossAISystem:62-70 gather VERBATIM (Health>0 && RegionTag==Expedition; confirmed: dead players persist at death pos for DelayTicks) + enemies (EnemyTag && Health>0, excludes Dying corpses) →AppendToBuffer(DamageEvent{ Amount=Tuning.BarrelExplodeDamage≈26, SourceNetworkId=-1, SourceTick= TickUtil.NonZero(now) })→ecb.DestroyEntity(barrel)(single destroy site). SourceNetworkId = -1 (environment convention — confirmed: a player id would score+consume Charger whiff-punish windows via HealthApplyDamageSystem:102, the sole reader).- No chain reactions v1 (clutter has Remaining, not Health). Damage numbers as
Tuningconsts v1; promote to TuningConfig knobs when the knob-table extension is worth it (confirmed-LOW tension, deferred deliberately — noted here per ask-before-deferring-during-implementation: the overlay knob table is a separate mechanical slice).
Client (all new work — confirmed: NO existing per-variant path; Variant is read by zero client systems)
- WorldFeedbackSystem: add
Variantto the Cache struct; at prune,IsClutter && Variant==3 && Remaining<=0→ BIG boom (burst +DynamicLightSystem.RequestFlashlarge orange + low boom SFX); else the existing small puff. Teardown despawns (Remaining>0) keep the puff. - DynamicLightSystem: Variant==3 barrels get a red glow light (node-glow pattern); when the cached
Remaining<=0(fuse lit) the glow strobes hard — the flee cue. (Body tint via URPMaterialPropertyBaseColor on LEG children = polish, deferred.)
Tests
- ResourceHarvestSystemTests: explosive pop → BarrelFuse added, Remaining==0, entity ALIVE; fused barrel not re-hittable.
- New HazardExplosionSystemTests: elapsed fuse → DamageEvent(Amount, SourceNetworkId=-1) on in-radius living enemy AND living player, dead player skipped, out-of-radius skipped, barrel destroyed once; un-elapsed fuse → nothing.
Wire/bake classification
ZERO new [GhostField]s / ghost prefabs / RPCs; BarrelFuse is server-only; Variant==3 rides the existing
replicated byte. No new ordering edges. Re-bake: none (code + seeded-value only).