Docs: DR-046 base<->expedition ties + session log; CLAUDE.md net-zero
DR-046 (portal-gated rooms, class-at-base via shared ClassSwapUtil, per-run prep spend, Health.Max GhostField, C3/C4/Spitter) + the session log. CLAUDE.md folds the batch into the core-loop bullet and corrects the now-wrong 'Health.Max unreplicated' line; paid net-zero (40921 B) by archiving the one-time Rukhanka-samples bullet + trims into the gotchas archive. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,73 @@
|
||||
---
|
||||
title: Base↔Expedition Ties — Portal Rooms, Class-at-Base, Per-Run Prep Spend (+ C3/C4/Spitter/Health.Max)
|
||||
date: 2026-07-04
|
||||
tags: [session, combat, demo, netcode, rpc, class, prep, portal, dots-dev, ultracode]
|
||||
permalink: gamevault/07-sessions/2026/2026-07-04-base-expedition-ties-portal-class-prep
|
||||
---
|
||||
|
||||
# Base↔Expedition Ties (batch 2 of the demo pass, /dots-dev, ultracode)
|
||||
|
||||
Operator brief (follow-up to the combat-feel pass): *"do C3 & C4 + the Health.Max GhostField; no time for resource
|
||||
collection in an expedition — after enemies clear, spawn a portal to interact with for the next room / choice UI; the
|
||||
ranged enemies are too hard (fast, instant-flee on approach) — are projectiles i-frame dodgeable?; let the player pick
|
||||
their class as part of the expedition / loadout from base resources & crafting; tie the base and expeditions together."*
|
||||
Decisions + full change list: [[DR-046_Base_Expedition_Ties_Portal_Class_Prep]]. (Batch 1 = [[2026-07-04_Combat_Demo_Feel_Pass]].)
|
||||
|
||||
## How it went
|
||||
|
||||
1. **Fork → operator** (AskUserQuestion): class/loadout = **per-run prep spend** (free class pick at base + a per-run buff
|
||||
panel spending base resources, wiped on return).
|
||||
2. **Pre-code adversarial review** (`wf_e0c9f916-ca1`, 3 lenses + critics, 13 confirmed). Drove the three key design
|
||||
moves: (a) the class swap needed a full **`ClassSwapUtil`** extraction — otherwise base-selection would drain Aether
|
||||
into the wrong class's meta + leave stale HP; (b) the portal needed the room **teardown moved** off the InRoom edge +
|
||||
the boss-branch/route-gate **relocated** into the new RoomExplore exit; (c) prep needed reset-per-run, solved elegantly
|
||||
as "purchased == the prep StatModifier band is present," lifetime == the band stripped on Returning (no extra buffer).
|
||||
3. **Serial MCP implementation** (~15 edits + 11 new files) in compile-checked batches: sim types (ClassSwapUtil,
|
||||
ClassSelectRequest, PrepCatalog, PrepPurchaseRequest, PortalInteractRequest, PortalCommand, RunLifecycle.RoomExplore,
|
||||
RunRuntime.ExploreGraceTick, Tuning, Health.Max) → server receivers (ClassSelectReceive, PrepPurchase, PortalInteract;
|
||||
RunDirectorSystem RoomExplore rework; CycleDirectorSpawn PortalCommand; DebugCommand dedup) → client senders
|
||||
(ClassSelectSend, PrepPurchaseSend, PortalInteractSend) → HudSystem (class row, prep panel, "E to LEAVE" portal
|
||||
prompt) → feel (CombatFeedback C3/C4, EnemyAISystem Spitter cornered-hold, EnemySpitter.prefab).
|
||||
4. **Verify**: clean compile; 456/456 EditMode (5 RunDirectorTraversalTests updated to the RoomExplore/portal contract via
|
||||
a `PortalAdvance` helper); live Play smoke — both worlds in-game with the 3 new RPCs (RpcCollection hash matched), all
|
||||
3 receivers created, PortalCommand on the director, GoalProgress.Target=2 server==client, zero Play exceptions.
|
||||
5. **Post-impl diff review** (`wf_f0e6cecc-8b0`, clean run, 0 agent failures): 9 candidates, **2 confirmed (the same
|
||||
issue from two lenses)** — the RoomExplore empty-abort guard excluded a boss clear, so a post-boss total wipe/leave
|
||||
idled the run ~30 s (the full ExploreGrace) before the win banked. Fixed (drop the boss condition → an empty expedition
|
||||
advances now). Folded 3 hygiene items (Temp-ECB `Dispose` in the 3 receivers; the orphaned `_lastConeFireTick`
|
||||
comment). Re-ran tests → still 456/456.
|
||||
|
||||
## Gotchas worth remembering
|
||||
|
||||
- **Extract the shared mutation before you add a second caller.** The class swap looked like "set PlayerClass + AbilityRef
|
||||
+ heal," but the load-bearing part is the **meta-band strip + per-class MetaTierState replay**; a second caller that
|
||||
skips it silently corrupts the Aether economy. `ClassSwapUtil` makes the dev tool and the base-selection RPC share one
|
||||
code path — the pre-code review flagged this as the #1 risk.
|
||||
- **"Purchased once per run" needs no bookkeeping if the effect IS the record.** Prep buffs live as StatModifiers in a
|
||||
disjoint SourceId band; the once-per-run gate is just "is a modifier with this row's SourceId already present?", and the
|
||||
lifetime is the band stripped on Returning. No per-player PrepPurchased buffer, no reset bug surface.
|
||||
- **When you insert a new FSM beat, re-audit which edge owns teardown.** Moving the loot window in meant room teardown had
|
||||
to move OFF the InRoom→RoomReward edge and the boss-branch/route-gate had to move INTO the new RoomExplore exit — else
|
||||
the room would be gone before you could loot it, or advance would double-fire. RunDirectorSystem must stay the *sole*
|
||||
RunInfo writer + teardown owner through the change.
|
||||
- **An empty-party abort guard must not exclude the terminal (boss) clear.** Gating the RoomExplore fast-exit on
|
||||
`LastTerminalCleared==0` meant a post-boss wipe waited out the full grace timer before banking the win — self-healing
|
||||
but a ~30 s dead-time on the single most important moment of the demo. The abort should fire for *any* empty expedition.
|
||||
- **Spitter projectiles were always dodgeable** — the fix for "too hard" was the *approach* behavior (a cornered-hold so
|
||||
you can close distance), not the projectile (i-frames already negate a DamageEvent by SourceTick). Diagnose the felt
|
||||
problem, not the assumed one.
|
||||
- **Health.Max as a `[GhostField]` is worth the one re-bake** when two display surfaces (boss bar + floating enemy bars)
|
||||
both need a server-mutated max; the client-side reconstruction from DR-045 only covered the boss and was fragile.
|
||||
|
||||
## Validation
|
||||
|
||||
Clean compile · 456/456 EditMode · live Play netcode smoke (3 new RPCs hash-matched, receivers up, PortalCommand present,
|
||||
Target=2 server==client, console clean) · two adversarial reviews (13-confirmed pre-code folded into the plan; post-impl
|
||||
clean bar the one RoomExplore dead-time + 3 hygiene items, all fixed). L3 fun-gate (portal loot-window pacing, prep buff
|
||||
feel, Spitter cornered-hold, class-at-base flow) is the operator's playtest.
|
||||
|
||||
## Next-session intent
|
||||
|
||||
Operator fun-gate: the portal loot window (does `ExploreGraceTicks` feel right vs. an instant advance?), the prep buff
|
||||
values, the Spitter cornered-hold, and the base→class→prep→launch flow. Live-tune the prep row values + PortalInteractRange.
|
||||
Then the standalone 2-instance LAN smoke (DR-044 §5, still open).
|
||||
Reference in New Issue
Block a user