Docs: Phase 1.7 boon-overhaul build spec + DR-047 + session log + roadmap tick + CLAUDE.md 7-type-query gotcha

Boon overhaul CODE-COMPLETE (mechanic-changers): design forks locked,
pre-code review (11 findings folded), post-impl review (incomplete on a
session limit — raised findings self-verified, 2 real bugs fixed).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-12 18:54:06 -07:00
parent 6dcd8a243d
commit 972e0d5b4f
5 changed files with 314 additions and 6 deletions
@@ -0,0 +1,55 @@
---
title: DR-047 — Phase 1.7 Boon Overhaul (mechanic-changers)
date: 2026-07-12
tags: [decision, coop-hades, phase-1.7, boons, netcode]
permalink: gamevault/decisions/dr-047-phase17-boon-overhaul
---
# DR-047 — Phase 1.7 Boon Overhaul: mechanic-changers
**Status:** shipped (code-complete, 488 EditMode green + Play smoke server==client). Supersedes the flat-stat-only boon
table from [[DR-044_Expedition_Redesign_Shipped_Demo_Polish]]. Full build detail: [[Boon_Overhaul_Build_Spec]]; session:
[[2026-07-12_Phase17_Boon_Overhaul]].
## Decision
Boons become **8 mechanic-changers + 4 flat-stat** (operator forks: ability-swap DEFERRED to Phase 3; synergy = tags for
dedup/dominated-protection/light-bias, NO synergy bonuses). Mechanic-changers ride a NEW per-player replicated
`BoonEffects` component — NOT the flat `StatModifier` pipeline, which cannot express behavior changes.
## Invariants (these outlive the current table/tuning)
1. **`BoonEffects` = `[GhostComponent(SendToOwnerType.SendToOwner)]` + `[GhostField] byte Pierce,Fork,Chain,Flags`,
baked INERT on the player, zeroed on the Returning edge.** Rollback-correctness comes from the GhostFields, NOT the
send type (owner is the sole predictor; needed so the owner predict-spawns the right Fork/Pierce projectiles).
A pick MUTATES it (non-structural, baked-present) — never AddComponent.
2. **Per-projectile pierce/chain state lives on a SEPARATE server-only `ProjectileEffectState`, never on `Projectile`.**
Adding ANY field to the ghost `Projectile` changes its StableTypeHash→serializer hash = a projectile ghost re-bake;
a separate non-ghost component keeps it frozen (the `KnockbackState` precedent).
3. **Pierce/chain re-hit guard = a per-projectile hit-set excluded DURING target selection** (not post-filtered), and
**exactly ONE `ecb.DestroyEntity` per projectile per tick** across the hit / survive+range / no-hit branches.
4. **Fork = extra predicted projectiles at a spread**, each with a UNIQUE deterministic spawnId
`(netId<<16)|((fireCount&0xFFF)<<4)|(forkIndex&0xF)`. **forkIndex is 4 bits → Fork MUST be capped ≤15** (shipped cap 8);
an uncapped fork wraps forkIndex → spawnId collision → classification mis-match. (Open risk: fireCount is 12 bits →
wraps at 4096 shots/run.)
5. **On-kill boons (Siphon/Frenzy) live in a SEPARATE `KillRewardSystem`, not in `HealthApplyDamageSystem`** — healing the
killer needs RW `Health` which would alias that system's `RefRW<Health>` victim query. The kill edge stamps
`Dying.KillerNetId` (last player-sourced hit, captured BEFORE `dmg.Clear()`) + a `Rewarded` idempotency latch.
6. **A run-scoped timed buff (Frenzy) is `Upsert`-ed (refresh, never stack) on a FIXED SourceId pinned to the TOP of the
boon band** (`BoonSourceIdBase+Span-1`, unreachable by the bottom-up pick counter) and stripped from BOTH the
`StatModifier` AND `TimedModifier` buffers on Returning (the range-strip only covers StatModifier).
7. **`BoonMath.PickBoons` takes the owner's `BoonEffects`** for dedup (skip owned non-stacking flags) + dominated-offer
protection (no two same-`Family` in a deal) + light ×1.5 build-bias — integer-hash only, drawn ONCE per RoomEpoch (the
latch), so feeding owned-state stays deterministic per (seed,room,player,owned-at-draw).
8. **Projectile boons (pierce/fork/chain) are Ranger-only (ClassMask=2)** — the Warrior's Fire is a cone, not a projectile.
9. **`SystemAPI.Query` caps at 7 type args** → reading `BoonEffects` in the already-7-wide `AbilityFireSystem`/`MeleeComboSystem`
player loops goes through a `ComponentLookup`, not an 8th query type.
## Deferred (open)
Ability-swap boons (→ Phase 3, with a `PlayerClass` GhostField if the client class-derivation coupling matters); a
client build-display HUD for active boons; DashTrail as a swept segment (v1 uses a per-tick radius — tunnel-safe at the
current dash step); fireCount 12-bit spawnId budget.
## Process note
Post-impl review (`wf_d2a0a673-af5`) had **4 verify agents die on a session limit** — an INCOMPLETE review masquerading as
near-clean. The raised-but-unverified findings were self-verified against the code; two were real bugs (fork cap, dash
overflow) the Build Spec §5 open-risks had predicted. Reinforces [[the failures-list check]] + the value of writing
open-risks into the spec so a truncated review still has a checklist.