12 KiB
date, type, tags, permalink
| date | type | tags | permalink | |||||||
|---|---|---|---|---|---|---|---|---|---|---|
| 2026-06-04 | session |
|
gamevault/07-sessions/2026/2026-06-04-polish-backlog-pass |
Session 2026-06-04 — Polish & Backlog-Clear Pass (Stages A–G)
Goal
Operator asked to clear the open backlog and do a comprehensive polish pass on everything, executed sequentially at full scope. A critical read (3 explore audits + direct verification) found the game systems-complete through M6, single-client runtime-validated, console clean, netcode well-built and convention-compliant. The plan (~/.claude/plans/melodic-yawning-hearth.md) sequences the work into 9 dependency-ordered stages A→I, each landing cleanly (compile → console → tests → runtime spot-check).
This log covers Stages A, B, C, D, and E(audio). Stages A–C were done on the unfocused editor; the operator then focused Unity, enabling the Burst-edit + ghost-re-bake + Play-mode validation for D (one new netcode surface, validated server==client in Play) and E(audio) (validated running in Play). The remaining Stage E feel tweaks + Stages F–I continue next.
Done
Stage A — Hygiene, reconcile, const-centralization
- Backlog reconciled (
06_Roadmap/Backlog.md): M1-revalidation → moot/subsumed (M1–M6 validated on stable 6.4.7); duplicate "template cleanup" checked off; M5 subscene-split/streaming → superseded by DR-013_M6_Aether_Cycle_Region_Split (region + GhostRelevancy; do not build streaming). - Milestones: added the 2026-06-04 Polish & backlog-clear pass row.
- Home MOC un-stale'd: latest session pointer + decisions range DR-001…DR-015.
Tuning.cscreated (Simulation/Tuning.cs) — central, Burst-safe home for the previously-buried balance consts.- Verified no stale "74" test-count claim in CLAUDE.md (audit misattribution; the "74/74" is the historical M5.5 Milestones row, left as-is).
Stage B — System-level EditMode tests for M6 systems
+31 cases / 9 files (86 → 117 green): StorageOpReceiveSystemTests, TurretFireSystemTests, ExpeditionGateSystemTests, CyclePhaseSystemTests, PlayerRespawnSystemTests, ResourceHarvestSystemTests (incl. N-projectile at-most-once destroy), WaveSystemTests, BuildPlaceSystemTests (incl. co-op same-cell atomicity), RegionTransitSystemTests. Plain-Entities pattern; immediate-ECB-playback so no separate ECB system.
Stage C — wire systems to Tuning (code)
ResourceHarvestSystem.k_ProjectileRadiusandAbilityUpgradeSystemUpgradeSourceId/TierStep/CostAmountnow derive fromTuning.*(value-identical const-from-const; comments preserved; not a query-set change → no Burst-binary hazard). 117/117 green.
Stage D — replicated wave number (the ONE new netcode surface) ✅ Play-validated
- Added
[GhostField] int WaveNumbertoCycleState;CyclePhaseSystemis the single writer (syncs it each tick from the server-onlyWaveState);HudSystemshows "WAVE N — M HUSKS" during Defend. - +1 EditMode test (118 green). Play-mode validated server==client: both worlds reported identical
CycleState(Wave 0 at rest, and Wave 7 after seeding the serverWaveState→ synced + replicated). No "not a known Burst entry point" spam after the forced CycleDirector re-bake (focused editor) and no errors — only the pre-existing in-editor "Server Tick Batching" warnings (a known backlog item, not a regression). - TMP HUD migration deferred — would add a TMP font-asset dependency, against the project's asset-free HUD convention. Kept legacy
Text; documented as optional future polish.
Stage E(audio) — procedural ambient + phase stingers ✅ Play-validated
- New
AmbientAudioSystem(Client/Presentation, client-onlySystemBaseinPresentationSystemGroup, observer-only): a low (vol 0.10) seamless-looping procedural drone (frequencies snapped to integer cycles/buffer so the loop has no click) + short procedural phase-change stingers (Expedition/Defend/Build, with a tenser "wave incoming" cue + a Defend volume swell). Asset-free (AudioClip.Create, mirrorsCombatFeedbackSystem.MakeClip); never mutates the sim. - Play-validated:
~AmbientAudioAudioSourceisPlaying=true, loop=true, vol=0.10, clip=176400 samples (4s), no runtime/job-safety errors.
Stage E(feel) — combat juice (4 client-only features) ✅ operator-approved
- New
FeelConfigstatic (Client/Presentation/FeelConfig.cs) — ~22 live-pokeable knobs, reset on play-enter via[RuntimeInitializeOnLoadMethod(SubsystemRegistration)](theAimPresentationprecedent). Client-presentation only; never read from Burst. - Implemented (observer-only, validated in Play): (1) hit camera-punch routed through
FeelConfig+ a netcode-safe FOV "hit-stop" kick (PrototypeCameraRig.PunchFov— NEVERTime.timeScale); (2) kill-shot fanfare (amplified Husk-death-on-prune burst/shake/SFX); (3) respawn shimmer (local Health 0→positive edge inCombatFeedbackSystem); (4) reticle lock-on tether inAimReticleSystem(client computes nearest living Husk itself — no replicated target exists — gamepad-gatedLineRenderer). - Defaults adversarially reviewed (3-critic + synthesis Workflow) and operator-approved live ("feels good") in a forced Defend wave (10 Husks, server==client). Console clean.
Stage F — ghost-prop reskin + post-processing ✅ (values verified)
- Prop reskin — distinct material assets (persist into Play, no shared-material bleed; assigned via
PrefabUtility.SaveAsPrefabAsset, the CLAUDE.md-safe prefab-asset edit) so props stop reading as identical "batteries": Storage → newM_Storage(dark steel + cool-blue emissive), Turret → newM_Turret(green ally-tech, distinct from orange Husks), ResourceNode → newM_ResourceNode(amber/gold), UpgradePickup keeps its glow. FoundM_Env_Storagewas a blank auto-stub (internal name "Universal Render Pipeline/Lit", white base) — replaced. Verified each prop's material/shader/base+emission values (not just a render). - Post-processing verified already complete: SSAO renderer feature present +
isActiveon the activePC_Renderer; URPcolorGradingMode=HighDynamicRange(correct for ACES);PostFX_DarkSciFi= Bloom + Tonemapping(ACES) + ColorAdjustments + Vignette, all active. Backlog "add SSAO" satisfied. - Deferred (diminishing returns, plan-noted): reflection probe, ORM-repack / deeper material fidelity, a proper turret MESH (battery mesh kept — distinct material is the 80/20; a real turret mesh needs a Synty asset + Entities-Graphics verification). Materials are assets → instantly re-tintable on operator request.
Stage G — new gameplay (in progress; design-reviewed) ✅ timed modifiers · knockback · telegraph
- Adversarial design review (4-agent Workflow: netcode/determinism · reuse · test-plan → synthesis) gave per-feature specs (re-bake?/determinism/files/tests). No-re-bake: timed modifiers, knockback, debug-RPC, storage-gate, pickup, multi-prefab. Re-bake: Spitter (new ghost types) + telegraph (one
[GhostField]on the Husk). - Timed/removable modifiers ✅ — server-only
TimedModifier{SourceId,UntilTick}buffer (StatModifier layout untouched → provably no re-bake) +TimedModifierExpirySystem(removes the matching StatModifier when due; replicates via the existing buffer;StatRecomputeSystemunchanged) +TimedModifierUtil.RemoveBySourceId(clear-by-type). +4 tests. - Enemy knockback ✅ — server-only
KnockbackState{Dir,Speed,UntilTick}(no re-bake; Husk position already replicates), stamped byProjectileDamageSystemon hit (backward-compatible viaTryGetSingleton<NetworkTime>so existing tests pass), applied byEnemyAISystemas the SOLE position writer (recoil replaces seek + suppresses the strike). TunableTuning.KnockbackSpeed(8, 0=off) /KnockbackDurationTicks(8). +3 tests. - Husk attack telegraph ✅ (re-bake) — replicated
[GhostField] AttackWindup.WindUpUntilTickon the Husk;EnemyAISystemrestructured to a 2-phase strike (commit wind-up when in-range + cooldown-ready → strike at expiry; cancel on leave-range; a knocked Husk doesn't wind up); client cue inCombatFeedbackSystem(observer, warns on the wind-up-start edge). TunableTuning.AttackWindupTicks(18 ≈ 0.3s, 0/1 = instant). +2 tests. Re-bake Play-validated: server==client (4 Husks, 2 winding up, identical maxWindTick), no Burst-cache spam, no errors. 127 EditMode green.
Aim-drift fix (operator request, end of session) ✅
- Symptom: holding the cursor near the player, the aim/reticle swims without mouse movement when the character turns / the camera pans — feels inaccurate. Cause: the camera look-ahead led toward
PlayerFacing(aim) → turning to face a near-cursor panned the camera → the live cursor screen-ray re-projected onto a different ground point → aim drifted (worst near the player, short lever arm). Fix:PrototypeCameraRignow leads toward MOVEMENT (PlayerInput.Move), not aim — a stationary aim no longer pans the camera; the cam still anticipates where you're going. Researched (gamedeveloper.com dual-stick; Relic Hunters Zero "ignore the crosshair unless not moving"). EditMode 127/127, console clean; "feels accurate now" = operator feel-test (tunable:AimLeadDistance0 = no lead). Recorded in DR-012_Aim_Controls_Cursor_Gamepad Refinement 2.
Decisions
-
Created DR-016_Stage_G_Combat_Gameplay (timed-modifier / knockback / telegraph architecture + the deferred Spitter / multi-prefab) and amended DR-012_Aim_Controls_Cursor_Gamepad (Refinement 2: movement-based camera look-ahead supersedes the facing-based look-ahead).
-
Debug systems kept
#if UNITY_EDITOR-gated in place (not moved to a new Editor asmdef). Already build-stripped; tightly coupled to their Client/Server worlds; a separate Editor asmdef risks DOTS system-discovery surprises for no functional gain.execute_codestatics preserved. -
RegionRelevancySystem is integration/operator-validated, not unit-tested.
Unity.NetCode.GhostRelevancyhas an internal constructor +readonlyGhostRelevancySet→ the singleton genuinely cannot be constructed from the test assembly. Relies on existing runtime validation (DR-013_M6_Aether_Cycle_Region_Split) + the Stage-I multi-client checklist. (Verified type shapes viaunity_reflect.)
Open / deferred
- Enemy knockback + Husk attack-telegraph → re-homed to Stage G (server/netcode, not client presentation): the 3-critic review proved both inherently touch the sim/netcode surface — knockback fights
EnemyAISystem's per-tickLocalTransformwrite on the interpolated ghost (needs a server knockback-state component + EditMode test); telegraph needs a NEW replicated wind-up signal ([GhostEnabledBit]/[GhostField]on the Husk → re-bake) becauseEnemyAttackCooldown/EnemyStats/AttackRangeare server-only. Each behind an adversarial review + test. - Stage C decor-LOD client-only split — verify deferred (wants the gameplay subscene open; tick-budget optimization, not correctness).
- Stages F–I: ghost-prop reskin + post-processing (F); new content — Spitter/boss/timed-modifiers/multi-prefab-abilities/storage-proximity/standalone-debug-RPC (G); controls — rebind + ability slots + ability UI (H); validation harness + operator-required live runs — two-build LAN co-op, live fire, standalone server perf (I).
Next
- Polish stages A–F are DONE + validated (118 EditMode green; the one new netcode surface —
CycleState.WaveNumber— proven server==client in Play; feel operator-approved; props reskinned + post verified). - Stage G done so far: timed modifiers, knockback, attack telegraph (all validated). Remaining: the ranged Spitter (the large one — new
EnemySpitter+ new interpolatedEnemyProjectileghost prefabs + spit-fire/move/damage-vs-players systems; re-bake = new ghost TYPES, not an existing-ghost serializer change) and multi-prefab abilities (generalize the non-BurstProjectileClassificationSystemto a ghost-type SET; core correctness — no owner-client double-spawn — is Play-only). Small fold-ins still open: standalone-server debug RPC, storage proximity-gate, pickup auto-grant (confirm intent). - Stage H (rebindable controls + ability slots + ability icon/UI) and Stage I (thin-client/MPPM harness + operator-required live runs: two-build LAN co-op, live fire, standalone server perf — the standing ~1.25–1.75 ticks/frame question).