Files
Project-M/Docs/Vault/07_Sessions/2026/2026-06-02_GameFeel_Deepening.md
T
kronic e362aaeb43 Import art/VFX asset packs + game-feel systems; normalize texture extensions to lowercase for LFS
Add BefourStudios SciFi environment packs, Gabriel Aguiar VFX, and the
ShaderCrew Toon Shader embedded packages, plus combat/enemy/wave/death
gameplay systems and supporting vault docs/screenshots.

Rename 11 vendor textures from uppercase .PNG/.HDR to lowercase so the
case-sensitive Git LFS filters (*.png/*.hdr) match on case-sensitive
filesystems (Linux CI, case-sensitive macOS), not just locally where
core.ignorecase=true masks the gap. Each .meta moved with its asset so
GUID references are preserved. All ~1000 binaries tracked via LFS.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-02 22:50:43 -07:00

7.0 KiB

date, type, tags, permalink
date type tags permalink
2026-06-02 session
session
dots
netcode
enemy-ai
wave-director
game-feel
m5
gamevault/07-sessions/2026/2026-06-02-gamefeel-deepening

Session 2026-06-02 — Game-feel deepening (waves, variety, invuln, auto-target)

Goal

Continue the 2026-06-02_GameFeel_Identity slice's suggested next steps — turn the flat Husk swarm into a sustaining combat loop: (1) auto-target on Husks, (2) respawn safety (invulnerability), (3) enemy variety + a wave/threat director. Same constraints (server-authoritative, deterministic, ctx7-verified, adversarially reviewed). Decisions extend DR-009_GameFeel_Identity_FirstBlood.

Done

  • Auto-target on Husks: AbilityFireSystem soft auto-target candidate query .WithAll<TrainingDummyTag>().WithAny<TrainingDummyTag, EnemyTag>() — Husks are now aim-assist targets (they were already raw-aim hittable).
  • Respawn invulnerability: new replicated RespawnInvuln { [GhostField] uint UntilTick } + RespawnState.InvulnTicks. PlayerRespawnSystem (server) sets the window on recovery; HealthApplyDamageSystem (server, predicted group) nullifies player damage while active; HudSystem (client) shows a cyan health bar + "SHIELDED". Stops instant re-death into the swarm.
  • Wave/threat director: WaveDirector (baked config) + WaveEnemyPrefab (buffer pool) + WaveState (runtime singleton) + WaveSystem (server state machine): Lull → Spawning BaseCount + (wave-1)*CountPerWave Husks, one every SpawnIntervalTicks at a deterministic ring, round-robin over the prefab pool → wait for the field clear (no EnemyTag) → Lull(LullTicks). Replaced the flat EnemySpawner sustain (deleted EnemySpawner/EnemySpawnSystem/EnemySpawnerAuthoring). Tick gating uses TickUtil.NonZero + wrap-safe NetworkTick.
  • Husk variety: 3 interpolated-ghost variants spawned round-robin — Grunt (Enemy, 30 HP), Swarmer (EnemySwarmer, 15 HP, fast/weak, small), Brute (EnemyBrute, 80 HP, slow/tanky/hard-hitting, large) — each = different baked EnemyStats + its own emissive material (M_HuskSwarmer yellow, M_HuskBrute deep-red) + scale. Subscene EnemySpawner GameObject swapped to WaveDirector (wired to all three).
  • Cleanup (from runtime): EnemyAISystem [UpdateBefore(PredictedSimulationSystemGroup)][UpdateAfter(...)] — the predicted group is OrderFirst, so UpdateBefore was silently ignored (console warning); contact damage now intentionally drains the following tick (~16ms melee latency, accepted). Removed a spurious ParticleSystem.duration set (warning).

Validation

  • EditMode 74/74 green (no new pure-math helpers; wave logic is stateful/integration-validated at runtime). Console clean of compile errors.
  • Runtime (single in-editor client, 6.4.7):
    • Wave director: wave 1 spawned 4 Husks (BaseCount), replicated server↔client (4==4); cleared the field → Lullwave 2 escalated to 6 Husks (BaseCount + CountPerWave). Full spawn→clear→lull→bigger-wave cycle confirmed.
    • Variety: distinct baked maxHP 30 / 15 / 80 among live Husks (Grunt/Swarmer/Brute), round-robin from the pool; distinct materials + scales confirmed by screenshot (yellow Swarmers, orange Grunts, a large red Brute).
    • Invulnerability: RespawnInvuln.UntilTick set on the player's respawn (server-authoritative, replicated for the HUD cue).
  • Adversarial review (7 agents): verified the wave director, variety, invuln replication, and auto-target correct — the "wave deadlock", "escalation overflow", and "0-sentinel conflation" suspicions all came back not-a-bug (the empty-server stall self-heals on respawn + Husks teleport-seek so can't wall-stick; overflow needs wave ~1 billion; writes are TickUtil.NonZero-guarded). 1 confirmed low-severity finding: RespawnMath.IsDue used a raw now >= respawnTick compare — the lone wrap-safety holdout (the slice's tick writes were guarded by TickUtil.NonZero, but this read-side compare was missed). Fixed with a wrap-safe signed-delta (int)(now - respawnTick) >= 0 (kept pure-uint, so the 5 RespawnMath unit tests stay valid). EditMode 74/74 after the fix.

Diagnosis notes (for future me)

  • [UpdateBefore(PredictedSimulationSystemGroup)] is IGNORED — the predicted group is OrderFirst in SimulationSystemGroup, and OrderFirst/OrderLast outrank UpdateBefore/After (Unity logs "Ignoring invalid UpdateBefore… because OrderFirst/OrderLast has higher precedence"). A plain-SimulationSystemGroup server system therefore always runs after the predicted group; to read this tick's post-predicted state use [UpdateAfter(PredictedSimulationSystemGroup)] (and accept that anything it appends for the predicted group drains next tick). This corrects the "same-tick" claim in DR-009_GameFeel_Identity_FirstBloodEnemyAISystem contact damage is a 1-tick-late (~16ms, imperceptible for melee) event, NOT same-tick. The runtime warning caught what the first review missed.
  • Wave state machine — avoid the deadlock: only re-enter Lull when RemainingToSpawn==0 AND the field is clear (EnemyTag count 0). The headless (input-less) player can't kill Husks, so the field never clears and the cycle stalls in Spawning — that's a test-harness artifact, not a bug (a real player clears it; validated by killing Husks via execute_code → the director advanced to wave 2).
  • Editing a subscene authoring component in code: open the subscene additively, GameObject.GetComponent/DestroyImmediate the old authoring + AddComponent the new + set fields (incl. GameObject[] prefab arrays, which the MCP property setter handles awkwardly) → EditorSceneManager.MarkSceneDirty+SaveScene → close. AssetDatabase.DeleteAsset is blocked by execute_code safety checks — guard duplicate-creates with a LoadAssetAtPath != null check instead.
  • Server tick-batching warnings in-editor are the known perf artifact (multiple worlds in one process + presentation + earlier unfocused throttle), not a code bug — reliable server state (wave director, RPCs) is robust to it; only one-shot predicted InputEvents drop under batching.

Open / deferred

  • Ranged Husk (Spitter): a server-spawned enemy projectile subsystem for tactical (dodge) depth — the next variety step (melee-only today).
  • Wave number on the HUD: needs a replicated game-state ghost (WaveState is server-only); the live "HUSKS N" count conveys threat for now.
  • Boss / mini-boss + per-wave composition weighting (brutes later, swarms early) instead of flat round-robin.
  • Server performance: investigate the in-editor tick-batching under the wave load (Burst cache health, world count); validate in a real build.

Next

Add the ranged Spitter (enemy projectile) for combat depth, or resume the milestone track at M6 — server-authoritative grid build placement (DR-008_M5_HomeBase_BaseLayer_Storage). The wave director + variety pool make adding new enemy types data-cheap.