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>
10 KiB
date, type, tags, permalink
| date | type | tags | permalink | ||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 2026-06-02 | session |
|
gamevault/07-sessions/2026/2026-06-02-gamefeel-identity |
Session 2026-06-02 — "First Blood": game-feel & identity slice
Goal
Before more systems work, make the project feel like a game — in feel, look, and play — and give it an identity. Diagnosis: M1–M5 are systems-complete but identity-less — pillars are mechanical (no fiction), and there is no feedback layer (grep confirmed zero audio/particles/VFX/shake/UI), the only enemy is an inert dummy, and there's no death/respawn or HUD. Operator chose: identity = sci-fi frontier colony, first slice = the full game-feel pass ("First Blood"), proceed plan→approve→build. Locked in DR-009_GameFeel_Identity_FirstBlood; fiction in Identity.
Process
- Explore + propose (read-only): diagnosed the look/feel/play gap against Pillars + the M1–M5 vault, surfaced the "mechanics without a fiction or a feedback layer" core, and proposed a spine (Identity) + 3 axes (Feel/Look/Play) with a recommended first slice. Operator picked the fiction + full slice via a 3-question gate.
- context7 WAS reachable this session (vs DR-008_M5_HomeBase_BaseLayer_Storage). A 4-agent ctx7 verification workflow (Netcode / Entities-enableable / URP / DOTS→GameObject presentation) returned high-confidence verified API shapes + a key correction: an interpolated enemy ghost must be moved server-only in the plain
SimulationSystemGroup, NOT in the predicted loop, andDeadis cleanest derived from replicated Health (no replicated enabled bit). - Implementation (sequential via MCP
create_script/script_apply_edits— single editor, domain-reload-ordered; NOT a parallel swarm) in 5 stages, each compiled + console-checked + (where applicable) EditMode-tested + runtime-validated before the next. - Adversarial review workflow over the diff (12 agents: 4 dimensions → per-finding adversarial verification). It verified the architecture correct — the Husk is a properly interpolated/ownerless ghost (
HasOwner=0,DefaultGhostMode=Interpolated), the derivedDeadis rollback-safe (no GhostField), the local player's Health is never client-predicted, and presentation randomness is outside the sim — every netcode-desync suspicion came back not-a-bug. 3 confirmed low-severity findings, all the same root: theuintcooldown "0 = ready" sentinel can collide at tick wraparound (RespawnMathalready guarded it; the three cooldown writers + the HUD didn't). Fixed via a sharedTickUtil.NonZeroguard at the three writes (EnemyAISystem/EnemySpawnSystem/AbilityFireSystem) and a wrap-safeNetworkTickcompare in the HUD cooldown bar.
Done
Stage 1 — Husk enemy (ProjectM.Simulation/Combat, ProjectM.Server/Combat, ProjectM.Authoring/Combat): EnemyTag/EnemyStats/EnemyAttackCooldown/EnemySpawner + pure EnemyAIMath (SeekVelocity/InAttackRange/RingPosition); EnemyAISystem (server-only, plain SimulationSystemGroup [UpdateBefore(PredictedSimulationSystemGroup)] — seeks nearest living player, writes LocalTransform, appends DamageEvent on a NetworkTick-gated strike); EnemySpawnSystem (persistent server spawner, sustains MaxAlive on a deterministic ring around BaseAnchor); HealthApplyDamageSystem +EnemyTag death; EnemyAuthoring + EnemySpawnerAuthoring. Enemy.prefab duplicated from UpgradePickup.prefab (interpolated ownerless ghost) → EnemyAuthoring; EnemySpawner GameObject added to Gameplay.unity.
Stage 2 — Player death/respawn (ProjectM.Simulation/Player, ProjectM.Server/Combat): Dead (enableable, derived from Health) + RespawnState + pure RespawnMath; PlayerDeathStateSystem (both worlds, predicted, before movement/aim/fire — sets Dead + zeroes velocity); PlayerRespawnSystem (server-only, after the predicted group — schedule on death, refill+reposition on due). PlayerControlSystem/PlayerAimSystem/AbilityFireSystem gained .WithDisabled<Dead>(); PlayerAuthoring bakes Dead (disabled) + RespawnState.
Stage 3 — Combat juice (ProjectM.Client/Presentation): CombatFeedbackSystem (client-only managed SystemBase, PresentationSystemGroup) — edge-detects replicated Health → pooled billboard TextMesh damage numbers + runtime ParticleSystem hit/death/muzzle bursts + procedural AudioClip SFX + camera shake; Husk death via despawn-prune; local-fire muzzle via AbilityCooldown edge. PrototypeCameraRig +AddShake (presentation-only decayed shake).
Stage 4 — HUD (ProjectM.Client/Presentation): HudSystem (code-built uGUI overlay — RawImage health + cooldown bars over Texture2D.whiteTexture, legacy Text threat count + DOWNED/RESPAWNING overlay) driven from the local player ghost. ProjectM.Client.asmdef +UnityEngine.UI.
Stage 5 — Look (assets/editor): URP global Volume (Bloom/ACES Tonemapping/ColorAdjustments/Vignette) + dark flat ambient + exp fog + cool key light + URP HDR color grading; emissive re-tint (teal crew, cyan projectiles, orange Husks via new M_Husk, dark-metal walls/ground). Husk balance tuned (dmg 8→5, strike 36→48 ticks, speed 3.5→3.0).
Docs: Identity (the fiction) + linked from Pillars/Home; DR-009_GameFeel_Identity_FirstBlood.
Validation
- EditMode 74/74 green (+12 vs M5's 62 — 7
EnemyAIMath, 5RespawnMath). Console clean of code/Burst/ghost errors after every stage. - Runtime (single in-editor client, 6.4.7): Husks spawn (6) + replicate server↔client (6==6), chase from the ring to the player, deal contact damage (player → 0 HP). Death→respawn loop verified by polling:
Health=0/Dead=True/RespawnTickscheduled → recovered at base full-HP/Dead=False. HUD renders (health bar, "HUSKS N", DOWNED overlay). Look confirmed via screenshots: dark metal world + cyan crew + orange Husks + bloom (after fixing theM_Dummy-as-wall-material orange-walls bug). - Adversarial review (12 agents): architecture verified correct; 3 confirmed low-severity findings (the
uintcooldown 0-sentinel wraparound), all fixed (TickUtil.NonZero+ HUDNetworkTickcompare). EditMode 74/74 after fixes, console clean.
Diagnosis notes (for future me)
- Interpolated ghost ≠ predicted. Move an ownerless interpolated enemy ghost server-only in plain
SimulationSystemGroup([WorldSystemFilter(ServerSimulation)]), NOT inPredictedSimulationSystemGroup.[UpdateAfter(PredictedSimulationSystemGroup)](NOT[UpdateBefore]— the predicted group is OrderFirst, so UpdateBefore is silently ignored; corrected in the deepening pass) so a contactDamageEventdrains the following tick (~16ms, fine for melee). StockLocalTransformreplication carries its position — no hand-written[GhostField]. - Derive enableable state, don't replicate it, when it's a pure function of already-replicated data.
Dead = Health<=0derived every predicted tick in both worlds is rollback-correct and dodges the[GhostEnabledBit]surface — same idiom asEffectiveCharacterStats. To toggle a disabled enableable component you must still visit the entity:.WithPresent<Dead>()(write) vs.WithDisabled<Dead>()(alive-only run).Simulate(also enableable) ANDs independently — keep.WithAll<Simulate>(). - A Baker can bake an enableable component DISABLED (
AddComponent<Dead>(e); SetComponentEnabled<Dead>(e, false);) → instances spawn alive with zero first-tick work (instantiated entities inherit the prefab's enabled state). - Presentation belongs in a client-only managed
SystemBaseinPresentationSystemGroup(once per frame, no rollback double-fire). Read ECS state viaSystemAPI.QueryINSIDEOnUpdate(registers job deps) +EntityManager.CompleteDependencyBeforeRO<T>()— NOT from a MonoBehaviourLateUpdate(the job-safety exception the camera rig hit).Entityis a stable client dict key for a ghost's lifetime; prune the cache each frame (a pruned enemy = a kill → death VFX at its last position; neverDestroyEntitya ghost from the client —GhostDespawnSystemowns that). - Netcode-safe "hit-stop" = a camera punch, never
Time.timeScale(which would corrupt the deterministic sim). - Code-built uGUI with no assets:
RawImageoverTexture2D.whiteTexture(anchor-driven fill, no sprite) + legacyTextwithResources.GetBuiltinResource<Font>("LegacyRuntime.ttf"). ProceduralAudioClip.Create+ runtimeParticleSystem(Sprites/Default material, HDR start color to bloom) keep the slice asset-free. - Watch shared-material bleed when re-tinting: the walls glowed orange because
M_Dummydoubled as the wall material — gave Husks their ownM_Huskinstead. Editing a prefab asset's component in code needsPrefabUtility.LoadPrefabContents→ modify →SaveAsPrefabAsset(NOTSavePrefabAsset, which rejects the contents root) →UnloadPrefabContents. - ACES tonemapping needs the URP asset's color grading mode = HDR (
m_ColorGradingMode = 1), else HDR bloom/emission won't tonemap right. - Editor
is_focusedmatters again: play-enter domain reload + boot took ~10–15s while unfocused; entity inspection viaexecute_codehad to be retried until worlds booted. (Application.runInBackgroundkeeps Play ticking but Edit mode still throttles unfocused.)
Open / deferred
- Generalize
AbilityFireSystemauto-target fromTrainingDummyTagtoEnemyTag(Husks are hittable via raw-aim swept hit; only the aim-assist snap is missing). - Respawn safety (brief invuln / Husk push-back) so respawn isn't instant re-death.
- Real assets (audio, stylized crew/structure models, biomech Husks); SSAO renderer feature.
- Enemy variety + a wave/threat director (ranged Husk, brute, broodmaker).
- Multi-client validation of remote-player death VFX + per-client HUD.
Next
Either deepen the loop (enemy variety + wave director + respawn safety + auto-target on Husks) to make the combat sustain, or resume the milestone track at M6 — server-authoritative grid build placement (DR-008_M5_HomeBase_BaseLayer_Storage). The presentation + server-AI patterns from this slice are the reusable foundation for the former.