Docs: exploding-barrels Build Spec + session log Part J + roadmap tick

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-10 19:42:41 -07:00
parent cd607ae156
commit d8e724e40a
3 changed files with 96 additions and 8 deletions
@@ -0,0 +1,71 @@
---
title: Exploding_Barrels_Build_Spec
type: note
permalink: 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==3` INSIDE 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 with `Remaining <= 0` (a fused barrel is not hittable → no double-pop).
- **`HazardExplosionSystem`** (new, ProjectM.Server, plain `SimulationSystemGroup`, 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)`;
when `ExplodeTick` elapses (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 `Tuning` consts 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 `Variant` to the Cache struct; at prune, `IsClutter && Variant==3 &&
Remaining<=0` → BIG boom (burst + `DynamicLightSystem.RequestFlash` large 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).