--- title: 2026-07-12 — Phase 1.7 Boon Overhaul (mechanic-changers) date: 2026-07-12 tags: [session, coop-hades, phase-1.7, boons, netcode, dots] permalink: gamevault/07-sessions/2026-07-12-phase17-boon-overhaul --- # Phase 1.7 — Boon Overhaul **Goal:** turn boons from flat-stat rows (the operator's "boring" verdict) into **mechanic-changers** so two runs feel different. The full `/dots-dev` spine ran (ground → forks → ctx7 → pre-code review → plan → serial impl → verify → post-impl review → docs). Build Spec: [[Boon_Overhaul_Build_Spec]]. Decision record: [[DR-047_Phase17_Boon_Overhaul_Mechanic_Changers]]. ## Forks locked (operator, this session) 1. **Ability-swap boons DEFERRED to Phase 3** (content-thin — only Ranger has alt abilities; the Warrior cone has none; couples to the client's `AbilityRef→class` derivation). Phase 1.7 = behavior-boons only. 2. **~8 mechanic-changers + ~4 stat boons** (keep a few "safe" econ/pacing picks). 3. **Synergy = tags for dedup + dominated-offer protection + light build-bias** (NO synergy bonuses in v1). ## What shipped **Architecture (netcode-correct, review-hardened):** - **`BoonEffects`** — new per-player `[GhostComponent(SendToOwnerType.SendToOwner)]` with `[GhostField] byte Pierce,Fork,Chain,Flags`. Baked INERT on the player (the `BoonOffer` idiom → **one deliberate player-ghost re-bake**); mutated on pick (non-structural); zeroed on the Returning edge. Rollback-correct via the GhostFields (owner is the sole predictor). - **`ProjectileEffectState`** — SERVER-ONLY (NOT a GhostField), baked inert on the projectile prefab → keeps the `Projectile` ghost hash FROZEN (the C1 correction; a field on `Projectile` itself WOULD change its StableTypeHash→serializer hash). Seeded server-side at spawn; a `FixedList64Bytes` re-hit set excluded DURING target selection. - **`DashTrailState`** — server-only per-player Blade-Dash dedup keyed to `DashState.StartTick` (no reliable clear edge). - **`BoonDefBlob` +`Kind`/`EffectKind`/`Family`** discriminators (config blob, not wire); the 12-row table redefined. - **`TimedModifierUtil.Upsert`** (refresh-never-stack) + a `TimedModifier`-buffer `RemoveBySourceId` overload. - **`Tuning.FrenzySourceId = 0x00B0FFFF`** — pinned to the TOP of the boon band (the bottom-up pick counter can't reach it; the Returning range-strip still clears it free). **Combat hooks:** pierce/chain in `ProjectileDamageSystem` (survive-and-continue, one destroy/tick preserved); fork = extra predicted projectiles at a spread in `AbilityFireSystem` (unique packed spawnId); knockback→pull negates `Dir` at the stamp (new `KnockbackUtil.Stamp(..., bool pull)`); Blade-Dash = new server `DashTrailDamageSystem`; Detonating Finisher inline in `MeleeComboSystem` (reads BoonEffects in the player loop, stashes IsFinisher/Detonate/Pull on `PendingCleave`); Siphon+Frenzy = new server `KillRewardSystem` off a `Dying.KillerNetId`+`Rewarded` latch stamped in `HealthApplyDamageSystem`. **Offers:** `BoonMath.PickBoons` gains the owner's `BoonEffects` — dedup (skip an owned non-stacking flag), dominated-offer protection (no two same-`Family` in a deal → kills the "-15% vs -25% cd" bug), light ×1.5 build-bias. Integer-hash only. **Table (12):** 1 Piercing·2 Split·3 Ricochet (Ranger, projectile) · 4 Detonating Finisher (Warrior) · 5 Blade Dash · 6 Gravity Pull · 7 Siphon · 8 Frenzy (both) · 9 Executioner · 10 Titan's Vigor · 11 Fleet Foot · 12 Berserker's Pace (both, stat). ## Reviews - **Pre-code (`wf_c4bdd60d-7b2`, 27 agents / 0 errors):** 23 raised → **11 confirmed**, all folded (C1 separate projectile component; C2 SendToOwner not All; C3–5 Frenzy top-of-band + Upsert + TimedModifier Returning-strip; C6 hit-set-excluded-in- selection; C7 dash dedup on StartTick; C8 inline finisher; C9 killer-capture-before-clear; C10 owned-state determinism doc). - **Post-impl (`wf_d2a0a673-af5`) — INCOMPLETE:** **4 verify agents died on a session limit** (the masquerade hazard — checked the failures list). Self-verified the raised findings against the code and fixed **2 real bugs the Build Spec §5 open-risks predicted** + 1 nit: (a) **Fork uncapped** → `shots = 1 + min(Fork,8)` (forkIndex is 4 spawnId bits, wrapped at 16); (b) **Blade-Dash hit-set overflow re-damaged** an unrecordable enemy every tick → break once `Hit` full; (c) the Slice-2 comment was displaced onto the BoonEffects lookup line (the `apply_text_edits` swallow gotcha) → relabelled. ## Verification - **L1** console clean vs baseline (only benign Server-Tick-Batching warnings), no Burst ICE. - **L2** **488/488 EditMode** (from 474; +13 new: pierce/chain, TimedModifierUtil Upsert, KillReward ×4, DashTrail ×2, offer dedup/owned-flag, a predicted-chain cycle guard) + **live Play smoke: `BoonEffects` server==owner-client** (set server Pierce3/Fork2/Chain1/Flags16 → replicated to the owning client), client connected (new GhostField didn't break the handshake → ghost hash consistent), world-creation exception-free (no ordering cycle). - **L3** N/A — behavioral slice, no new visual assets; the boon effects in-combat are the operator's playtest. ## Wire/bake churn `BoonEffects` = ONE player-ghost re-bake (front-loaded; both worlds bake identically → handshake OK). `ProjectileEffectState`/ `DashTrailState`/`Dying`/`BoonDefBlob` = no ghost-hash change. `StatModifier` + RPC collection UNCHANGED. ## Next-session intent **Phase 1.7 boon overhaul is CODE-COMPLETE + tests green + Play-smoke-verified.** NEXT = the operator's **feel/balance playtest** (the gate: "two consecutive runs feel different from boons alone") — tune knobs: Fork cap (8), DashTrail radius/damage (1.6/12), Frenzy duration/mult (240t/-0.30), Siphon heal (8), finisher detonation radius (3.5). Then **Phase 2 — Hub-ification** (mothball sieges, win moves fully expedition-side, base=hub; adversarial design-review first). Order stays 1.7 → 2 → 3 → 4/5 parallel. Also OPEN (deferred): ability-swap boons (Phase 3), a client build-display HUD for active boons. Related: [[Iteration_2026-07_CoopHades]] · [[DR-047_Phase17_Boon_Overhaul_Mechanic_Changers]] · [[Boon_Overhaul_Build_Spec]] · [[DR-044_Expedition_Redesign_Shipped_Demo_Polish]]