Further Tests & Progress
This commit is contained in:
@@ -15,8 +15,8 @@ Multiplayer game on **Unity DOTS** (Entities) + **Netcode for Entities** (server
|
||||
- **Vision** → [[Pillars]] — design pillars & locked decisions · [[Identity]] — the fiction (sci-fi frontier colony)
|
||||
- **Game Design** → [[Systems_Index]] — per-system design docs
|
||||
- **Roadmap** → [[Milestones]] · [[Backlog]]
|
||||
- **Sessions** → `07_Sessions/2026/` — dated work logs (latest: [[2026-06-03_Pre_M6_Cleanup]])
|
||||
- **Decisions** → `07_Sessions/_Decisions/` — decision records DR-001 … DR-012 · [[DR-001_Netcode_Test_Harness]] · latest [[DR-012_Aim_Controls_Cursor_Gamepad]]
|
||||
- **Sessions** → `07_Sessions/2026/` — dated work logs (latest: [[2026-06-04_Polish_Backlog_Pass]])
|
||||
- **Decisions** → `07_Sessions/_Decisions/` — decision records DR-001 … DR-016 · [[DR-001_Netcode_Test_Harness]] · latest [[DR-016_Stage_G_Combat_Gameplay]]
|
||||
- **Meta** → [[Documentation_Protocol]] · [[Tags]]
|
||||
- **Templates** → [[Session_Log_Template]] · [[Decision_Record_Template]]
|
||||
|
||||
|
||||
@@ -10,11 +10,13 @@ permalink: gamevault/06-roadmap/backlog
|
||||
|
||||
Unordered pool of candidate work. Promote to a [[Milestones|milestone]] when committed.
|
||||
|
||||
> **2026-06-04 Polish & backlog-clear pass** ([[2026-06-04_Polish_Backlog_Pass]], [[DR-016_Stage_G_Combat_Gameplay]]) — delivered + validated (EditMode 86→127): hygiene/reconcile; **+system tests for every M6 server system**; centralized `Tuning.cs` consts; replicated **wave number** on the HUD; procedural **ambient + combat juice** (camera punch / FOV hit-stop, kill-shot fanfare, respawn shimmer, reticle lock-on tether); **ghost-prop reskin** (Storage/Turret/Node distinct materials) + **SSAO/ACES post verified**; **timed/removable modifiers**, **enemy knockback**, **Husk attack telegraph**; and the **aim-drift fix** (movement-based camera look-ahead). Remaining from the selected Stage-G slice: ranged **Spitter**, **multi-prefab abilities**, + small fold-ins (storage proximity-gate, pickup auto-grant, standalone-debug RPC). Then Stages H (controls/UI) + I (multi-client harness + operator live runs).
|
||||
|
||||
- [x] Upgrade Unity 6.4 → 6.6 — done (now `6000.6.0a6`). Entities/Collections/Graphics → 6.5.0; **Netcode → 6.6.0 and Physics → 6.5.0 also renumbered into the editor line** (not independent 1.x as [[DR-001_Netcode_Test_Harness]] assumed). See [[2026-05-30_M1_Player_Slice]].
|
||||
- [x] Define the core gameplay loop and the first predicted player ghost — delivered as M1 ([[2026-05-30_M1_Player_Slice]]).
|
||||
- [ ] **Re-validate the M1 play-tick on a stable Unity 6.x** — live runtime blocked on the 6.6 alpha ([[DR-002_Unity66_Alpha_Netcode_Transport]]); optionally reproduce with the `networked-cube` sample to file a bug.
|
||||
- [x] **Re-validate the M1 play-tick on a stable Unity 6.x** — moot/subsumed 2026-06-04: M1–M6 are all runtime-validated on the stable **6.4.7** line (the 6.6 alpha netcode bug never affected 6.4.7). The original "blocked on 6.6 alpha" framing ([[DR-002_Unity66_Alpha_Netcode_Transport]]) no longer applies.
|
||||
- [ ] Replace template `SampleScene` with a dedicated bootstrap scene + gameplay subscene.
|
||||
- [ ] Optional template cleanup: remove `com.unity.visualscripting`, `Assets/TutorialInfo/`, `Assets/Readme.asset` (delete each asset **with** its `.meta`).
|
||||
- [x] Optional template cleanup: remove `com.unity.visualscripting`, `Assets/TutorialInfo/`, `Assets/Readme.asset` — **done 2026-06-03** (pre-M6 cleanup; see the "2026-06-03 Visual & Controls Polish" section below + [[2026-06-03_Pre_M6_Cleanup]]). Duplicate of the now-checked item there.
|
||||
- [x] Decide **relay provider** before M4 — resolved: **Direct IP/LAN now, Unity Relay later** ([[DR-005_M4_Connection_Model_Direct_IP]], [[2026-06-01_M4_LAN_CoOp_And_Classification_Fix]]).
|
||||
- [x] Decide home-base **grid 2D vs 3D** before M6 — resolved 2026-06-02: **planar single-level `int2` grid**, CellSize 1.0, 32×32 plot (full 3D/stacked deferred). Locked in `BaseGridMath` — [[DR-008_M5_HomeBase_BaseLayer_Storage]].
|
||||
- [ ] Decide **production replication** (predicted vs server-only) before M7 (automation).
|
||||
@@ -24,12 +26,12 @@ Unordered pool of candidate work. Promote to a [[Milestones|milestone]] when com
|
||||
- [x] **M2 follow-up — player death/respawn** — done in **M5.5**: derived `Dead` enableable gate (from replicated Health) + server `PlayerRespawnSystem` (full HP + reposition to base after a delay). [[DR-009_GameFeel_Identity_FirstBlood]]
|
||||
- [ ] M2 polish — projectile/dummy visuals (primitive meshes/materials currently); optional predicted client-side auto-target if the soft server reconcile feels off.
|
||||
- [ ] **M3 follow-up — UI/icon/description pipeline** for abilities (managed lookup keyed by `AbilityId`, off the blob). Deferred from M3 ([[2026-05-31_M3_Data_Driven_Abilities]]).
|
||||
- [ ] **M3 follow-up — timed / removable modifiers** (expiry on `NetworkTick`, `ClearByType` via `StatModifier.SourceId`). M3 modifiers are permanent-once-granted.
|
||||
- [x] **M3 follow-up — timed / removable modifiers** — done 2026-06-04 ([[DR-016_Stage_G_Combat_Gameplay]]): a SEPARATE server-only `TimedModifier{SourceId,UntilTick}` buffer (keeps the replicated `StatModifier` layout byte-identical → no re-bake) + `TimedModifierExpirySystem` (expiry on `NetworkTick`) + `TimedModifierUtil.RemoveBySourceId` (clear-by-type). +4 EditMode tests.
|
||||
- [ ] **M3 follow-up — multi-prefab abilities** (a per-ability *different* projectile ghost) needs `ProjectileClassificationSystem` generalized beyond the single shared prefab.
|
||||
- [ ] **M3 follow-up — standalone-server debug modifier path** via `IRpcCommand` (current `DebugModifierInjectionSystem` is in-editor single-process only).
|
||||
- [x] **M3 follow-up — rate-limited turning** — done 2026-06-03 (pre-M6 cleanup): `PlayerAimSystem` now rotates `PlayerFacing` toward the aim target at `EffectiveCharacterStats.TurnRateRadiansPerSec` (authored 720°/s) instead of snapping; deterministic in the predicted loop (fixed-step `dt`, replays on rollback). [[2026-06-03_Pre_M6_Cleanup]].
|
||||
- [ ] **M3 polish — pickup visuals** (primitive sphere/default material currently); pickup auto-grant feel (continuous overlap).
|
||||
- [ ] **M5 follow-up — base/expedition subscene split + streaming (Option C)**: the persistent-space split the locked world design ultimately needs (`SceneSystem.LoadSceneAsync`/`UnloadScene`, per-world load on the listen-server, enter-expedition/return-to-base transition). Deferred to its own world-architecture milestone — M6/M7 only need the anchor + grid, now done ([[DR-008_M5_HomeBase_BaseLayer_Storage]]). The physics-in-prediction + base-layer slices of M5 are done ([[DR-006_M5_Physics_In_Prediction]], [[DR-008_M5_HomeBase_BaseLayer_Storage]]).
|
||||
- [ ] **M5 follow-up — base/expedition subscene split + streaming (Option C)** — **superseded 2026-06-03 by [[DR-013_M6_Aether_Cycle_Region_Split]]** (coordinate-region + per-connection `GhostRelevancy` delivered the split without `SceneSystem` streaming). Kept only as a note for a future larger-world milestone where true async streaming is wanted; do NOT build streaming now. Original framing: the persistent-space split the locked world design ultimately needs (`SceneSystem.LoadSceneAsync`/`UnloadScene`, per-world load on the listen-server, enter-expedition/return-to-base transition). Deferred to its own world-architecture milestone — M6/M7 only need the anchor + grid, now done ([[DR-008_M5_HomeBase_BaseLayer_Storage]]). The physics-in-prediction + base-layer slices of M5 are done ([[DR-006_M5_Physics_In_Prediction]], [[DR-008_M5_HomeBase_BaseLayer_Storage]]).
|
||||
- [ ] **M5 follow-up — shared-storage disk persistence** (host-only): runtime structures don't exist until M6, so nothing to save yet; add a thin per-record serialization slice (replayed through M6's placement path) after M6. `BaseAnchor`/`StorageEntry` are already flat/serialization-friendly.
|
||||
- [ ] **M5 follow-up — storage interaction polish**: proximity gate the deposit/withdraw (the container carries `HitRadius`); real item/UI model beyond the fixed test item; multi-writer ordering beyond first-come server apply.
|
||||
- [ ] **M5 follow-up — multi-client shared storage**: validate two clients see identical shared-storage buffer state (pairs with the deferred M5b multi-client interpolation + M4 two-build tests).
|
||||
|
||||
@@ -20,6 +20,7 @@ permalink: gamevault/06-roadmap/milestones
|
||||
| **— 2026-06-03 Visual & controls polish —** | Non-milestone polish layered on M5.5 (no mechanical rework): HDRP→URP art import + reusable converter; a cohesive **Synty** sci-fi colony world (cosmetic SampleScene GameObjects) + **GabrielAguiar** combat VFX; **KBM mouse-cursor aim + gamepad aim** with last-actuation device auto-switch (rides the existing `PlayerInput.Aim` ghost field). | ✅ Done 2026-06-03 — [[DR-010_Art_Import_URP_Conversion_Visual_Upgrade]], [[DR-011_Synty_World_VFX_Integration]], [[DR-012_Aim_Controls_Cursor_Gamepad]] |
|
||||
| **— 2026-06-03 Pre-M6 cleanup —** | Loose-ends pass before M6: vault roadmap reconcile, Unity-template + orphaned-material removal, rate-limited turning, console/runtime health gate. | ✅ Done 2026-06-03 — [[2026-06-03_Pre_M6_Cleanup]] |
|
||||
| **M6 — The Aether Cycle (core loop)** | Reframed from "grid build placement" into the first vertical slice of the **core game loop**: Expedition (gather) → Defend (wave) → Build/Charge (spend), persistent base + procedural sorties, escalating toward a goal. Build placement is now Stage 3 of this milestone. | 🚧 In progress 2026-06-03 — **Stages 0–4 done + runtime-validated** on 6.4.7 (M6 core loop systems complete): **base/expedition split via coordinate-region + `GhostRelevancy`** (player transit despawns/re-grants the other region's ghosts; server==client); a **server phase-director** (Expedition→Defend→Build→Expedition auto-cycle, cycle 1→2, Husk `WaveSystem` only in Defend, escalation 4→6); and **resources + harvest** — a **global CycleDirector ghost** carrying the replicated `CycleState` + a shared resource **ledger** (relevant in every region, unlike the base storage), a procedural **expedition field** (8 resource-node ghosts seeded per cycle, region-scoped), and a tunnel-safe **harvest** sweep depositing into the ledger; client **HUD** shows phase + resource counts. Supersedes DR-008's "split requires streaming" framing. **Stage 3** (generic automation-ready **structure model** + data-driven catalog + grid **build-placement** RPC with co-op-atomic commit + a hitscan **turret** that auto-defends + **ability tiers** via a bounded StatModifier) and **Stage 4 goal meter** are **done + validated** (turret placed/Ore-deducted/replicated; two same-tick requests → one build; turrets killed the wave; ability damage 20→30 bounded; goal increments per cycle). Disk-persistence **writer deferred to post-M7** (M7-additive surface — tick fields + frozen schema — baked now); the structure model is the M7 production-chain foundation. Playable walk-in-gate loop with build/spend, visible in the HUD. — [[DR-014_M6_Build_Structures_Automation_Foundation]] — [[DR-013_M6_Aether_Cycle_Region_Split]], [[2026-06-03_M6_Aether_Cycle_CoreLoop]] |
|
||||
| **— 2026-06-04 Polish & backlog-clear pass —** | Comprehensive *sequential* polish across hygiene/reconcile, system-level tests, HUD (TMP + replicated wave number), procedural audio + combat juice, ghost-prop reskin + post-processing, new gameplay content, controls/UX, and a validation-harness + operator handoff. Clears the open [[Backlog]]. | 🚧 In progress 2026-06-04 |
|
||||
| **M7 — Automation** | Self-running tick-based production chains (deterministic offline catch-up) | ⬜ |
|
||||
|
||||
Promote items from [[Backlog]] here when committed.
|
||||
@@ -0,0 +1,75 @@
|
||||
---
|
||||
date: 2026-06-04
|
||||
type: session
|
||||
tags: [session, polish, backlog, testing, hygiene, netcode, audio]
|
||||
---
|
||||
|
||||
# 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.cs`** created (`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_ProjectileRadius` and `AbilityUpgradeSystem` `UpgradeSourceId`/`TierStep`/`CostAmount` now derive from `Tuning.*` (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 WaveNumber` to `CycleState`; `CyclePhaseSystem` is the single writer (syncs it each tick from the server-only `WaveState`); `HudSystem` shows "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 server `WaveState`** → 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-only `SystemBase` in `PresentationSystemGroup`, 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`, mirrors `CombatFeedbackSystem.MakeClip`); never mutates the sim.
|
||||
- **Play-validated**: `~AmbientAudio` AudioSource `isPlaying=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 **`FeelConfig`** static (`Client/Presentation/FeelConfig.cs`) — ~22 live-pokeable knobs, reset on play-enter via `[RuntimeInitializeOnLoadMethod(SubsystemRegistration)]` (the `AimPresentation` precedent). 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` — NEVER `Time.timeScale`); (2) kill-shot fanfare (amplified Husk-death-on-prune burst/shake/SFX); (3) respawn shimmer (local Health 0→positive edge in `CombatFeedbackSystem`); (4) reticle lock-on tether in `AimReticleSystem` (client computes nearest living Husk itself — no replicated target exists — gamepad-gated `LineRenderer`).
|
||||
- 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 → new **`M_Storage`** (dark steel + cool-blue emissive), Turret → new **`M_Turret`** (green ally-tech, distinct from orange Husks), ResourceNode → new **`M_ResourceNode`** (amber/gold), UpgradePickup keeps its glow. Found `M_Env_Storage` was 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 + `isActive` on the active `PC_Renderer`; URP `colorGradingMode=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; `StatRecomputeSystem` unchanged) + `TimedModifierUtil.RemoveBySourceId` (clear-by-type). +4 tests.
|
||||
- **Enemy knockback ✅** — server-only `KnockbackState{Dir,Speed,UntilTick}` (no re-bake; Husk position already replicates), stamped by `ProjectileDamageSystem` on hit (backward-compatible via `TryGetSingleton<NetworkTime>` so existing tests pass), applied by `EnemyAISystem` as the SOLE position writer (recoil replaces seek + suppresses the strike). Tunable `Tuning.KnockbackSpeed`(8, 0=off) / `KnockbackDurationTicks`(8). +3 tests.
|
||||
- **Husk attack telegraph ✅ (re-bake)** — replicated `[GhostField] AttackWindup.WindUpUntilTick` on the Husk; `EnemyAISystem` restructured 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 in `CombatFeedbackSystem` (observer, warns on the wind-up-start edge). Tunable `Tuning.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:** `PrototypeCameraRig` now 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: `AimLeadDistance` 0 = 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_code` statics preserved.
|
||||
- **RegionRelevancySystem is integration/operator-validated, not unit-tested.** `Unity.NetCode.GhostRelevancy` has an **internal** constructor + `readonly` `GhostRelevancySet` → 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 via `unity_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-tick `LocalTransform` write 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) because `EnemyAttackCooldown`/`EnemyStats`/`AttackRange` are 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 interpolated `EnemyProjectile` ghost 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-Burst `ProjectileClassificationSystem` to 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).
|
||||
@@ -52,6 +52,12 @@ Operator feedback after the first pass: make KBM aiming feel more natural / worl
|
||||
- **Skipped** (with reasons): constant-screen-size scaling (fixed-distance cam), aim line (redundant with the ring), KBM enemy-magnetism (would undermine the intentional gamepad-only assist).
|
||||
- Validated: EditMode **86/86**, console clean; runtime (focused) KBM ring active at the cursor ground point, OS cursor hidden, `TargetFacing` published, no exceptions.
|
||||
|
||||
## Refinement 2 — movement-based camera look-ahead (aim-drift fix, 2026-06-04)
|
||||
|
||||
Operator feedback: holding the mouse cursor NEAR the player, the aim/reticle "swims" without moving the mouse when the character turns or the camera pans — feels inaccurate. **Root cause:** the look-ahead in Refinement 1 led the framed point toward `PlayerFacing` (the aim). The KBM reticle/aim is the LIVE cursor screen-ray re-projected onto the ground each frame, so turning to face a near-cursor moved the camera (aim → facing → look-ahead → camera pan), and a stationary mouse then re-projected to a DIFFERENT ground point → the aim direction drifted. Worst near the player (short lever arm → high angular sensitivity). Research (gamedeveloper.com dual-stick controls; Relic Hunters Zero) confirmed that coupling the camera to the crosshair/aim causes exactly this and is conditioned-out in shipped games.
|
||||
|
||||
**Fix (supersedes the facing-based look-ahead in Refinement 1):** the camera look-ahead now leads toward **MOVEMENT** (`PlayerInput.Move`), not aim/facing. `PrototypeCameraTargetSystem` publishes `PrototypeCameraRig.TargetMoveDir`; the rig leads toward it. A stationary aim no longer pans the camera → the reticle is rock-stable while turning/aiming; the camera still anticipates where the player is MOVING. `AimLeadDistance` (default 2.5, tunable, 0 = off) is unchanged in magnitude — only its direction source changed. EditMode **127/127**, console clean. Live "feels accurate now" = operator (focused Game view).
|
||||
|
||||
## Open / deferred
|
||||
|
||||
- The **real mouse-cursor path + live device auto-switch** need a **focused** Game view — the unfocused editor can't inject mouse position / device actuation (validated the replication, math, gate, and reticle headlessly via `DebugInputInjectionSystem` + forced scheme). Operator focused click-test pending.
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
---
|
||||
id: DR-016
|
||||
title: Stage G combat gameplay slice — timed modifiers, knockback, attack telegraph (+ deferred Spitter/multi-prefab)
|
||||
status: accepted
|
||||
date: 2026-06-04
|
||||
tags:
|
||||
- decision
|
||||
- gameplay
|
||||
- combat
|
||||
- netcode
|
||||
- enemies
|
||||
- modifiers
|
||||
permalink: gamevault/07-sessions/decisions/dr-016-stage-g-combat-gameplay
|
||||
---
|
||||
|
||||
# DR-016 — Stage G combat gameplay slice
|
||||
|
||||
## Context
|
||||
|
||||
The 2026-06-04 polish pass ([[2026-06-04_Polish_Backlog_Pass]]) reached a "new gameplay" stage after polish stages A–F. The operator selected a slice: ranged **Spitter** enemy, **knockback + attack telegraph**, and **timed/removable modifiers + multi-prefab abilities** (small fold-ins — storage proximity-gate, pickup auto-grant, standalone-debug RPC — riding along). Each is netcode-touching, so a **4-agent adversarial design-review Workflow** (netcode/determinism · reuse · test-plan → synthesis) ran first and produced per-feature specs (re-bake?, determinism, files, EditMode tests). This DR records the architecture the review locked and what shipped.
|
||||
|
||||
## Decision
|
||||
|
||||
- **Timed/removable modifiers — a SEPARATE server-only `TimedModifier{SourceId,UntilTick}` buffer, NOT a field on the replicated `StatModifier`.** Adding ANY member (even non-`[GhostField]`) to a `[GhostField]` buffer element regenerates its serializer/stride/hash = an effective re-bake; the separate buffer keeps `StatModifier` byte-identical. `TimedModifierExpirySystem` (server, plain `SimulationSystemGroup`) removes the matching `StatModifier` by `SourceId` when due (wrap-safe `NetworkTick`); the shortened `[GhostField]` buffer auto-replicates and `StatRecomputeSystem` reverts the effective stat unchanged. `TimedModifierUtil.RemoveBySourceId` is the clear-by-type helper. **No re-bake.**
|
||||
- **Enemy knockback — server-only `KnockbackState{Dir,Speed,UntilTick}`, applied INSIDE `EnemyAISystem`.** Husk position already replicates (stock `LocalTransform` variant), so knockback needs NO new `[GhostField]` (no re-bake). The one desync trap is two writers of the Husk's `Position`; `EnemyAISystem` is the sole writer, so knockback is blended there (recoil REPLACES seek + suppresses the strike for the window). `ProjectileDamageSystem` stamps `KnockbackState` on hit (it has the projectile heading; backward-compatible via `TryGetSingleton<NetworkTime>` so existing tests pass). Tuned by `Tuning.KnockbackSpeed`(8, 0=off) / `KnockbackDurationTicks`(8). **No re-bake.**
|
||||
- **Husk attack telegraph — replicated `[GhostField] AttackWindup.WindUpUntilTick` on the Husk (a re-bake) + a 2-phase strike.** The client has NONE of the timing inputs (`EnemyStats` / `EnemyAttackCooldown` are server-only), so the wind-up signal MUST be replicated. A `uint` tick (not a `[GhostEnabledBit]`) so the client cue can ramp/countdown and survive a missed snapshot. `EnemyAISystem` restructured: when first in-range + cooldown-ready it commits `WindUpUntilTick = now + Tuning.AttackWindupTicks` and damages nothing; strikes when the tick elapses; cancels on leave-range; a knocked Husk doesn't wind up. Client cue = observer in `CombatFeedbackSystem` (warns on the wind-up-start edge). Tuned by `Tuning.AttackWindupTicks`(18 ≈ 0.3s, 0/1 = instant). **Re-bake** (done on a focused editor).
|
||||
- **Deferred (still selected, not yet built):** ranged **Spitter** (two NEW ghost prefabs — `EnemySpitter` + an interpolated `EnemyProjectile` — + spit-fire/move/damage-vs-players systems; the enemy projectile must NOT reuse the player's predicted projectile or `ProjectileDamageSystem`'s faction-blind target query; the plain-group sweep must use a stored `LastStep`, not `DeltaTime`, per the DR-013 dt-trap) and **multi-prefab abilities** (generalize the non-Burst `ProjectileClassificationSystem` to a ghost-type SET; core correctness — no owner-client double-spawn — is Play-only, `NetCodeTestWorld` being internal in 1.13.2). Small fold-ins (storage proximity-gate, pickup auto-grant, standalone-debug RPC) also pending.
|
||||
|
||||
## Consequences
|
||||
|
||||
- **Validated (6.4.7):** EditMode **86 → 127** (timed-mods +4, knockback +3, telegraph +2; the M6 system tests from Stage B are the rest). Console clean throughout. The telegraph re-bake was **Play-validated server==client** (4 Husks both worlds, 2 winding up, identical `maxWindTick`; no "not a known Burst entry point" spam; no errors). Knockback's existing-test backward-compat confirmed.
|
||||
- **Reusable rulings:** never mutate a replicated buffer-element layout (use a separate server-only tracking buffer); server-only state that drives an already-replicated field needs no re-bake; a replicated signal is mandatory when the client lacks the timing inputs; keep ONE writer of an interpolated ghost's transform; `replace_method` (MCP `script_apply_edits`) does NOT handle `struct` ISystems — use line/anchor edits.
|
||||
- **Tunables** let the operator dial or disable each gameplay change (knockback speed 0 = off; windup ticks 0/1 = instant/legacy).
|
||||
|
||||
See [[2026-06-04_Polish_Backlog_Pass]]. Builds on [[DR-009_GameFeel_Identity_FirstBlood]] (Husk/feel), [[DR-013_M6_Aether_Cycle_Region_Split]] (dt-trap, region gating), [[DR-004_M3_DataDriven_Abilities_Modifiers]] (StatModifier).
|
||||
Reference in New Issue
Block a user