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>
This commit is contained in:
2026-06-02 22:50:43 -07:00
parent dd0064c377
commit e362aaeb43
4830 changed files with 1293057 additions and 38 deletions
+9
View File
@@ -82,6 +82,15 @@ Root namespace: **`ProjectM`**. Code lives under `Assets/_Project/Scripts/` in f
- **`execute_code` runs as a method body** — **no `using` directives** (they parse as statements → "Identifier expected"); fully-qualify every type (`Unity.Entities.World`, `ProjectM.Simulation.BaseAnchor`, …). Also: world flags overlap a shared `Game` bit, so identify worlds by `world.Name == "ServerWorld"/"ClientWorld"` rather than `(Flags & GameServer)`.
- **An unfocused editor throttles Edit mode to near-idle** → MCP pings time out and the bridge looks hung (it still *queues* commands — `telemetry_ping` succeeds). `Application.runInBackground` only helps in **Play** mode. If it wedges, focus or restart the editor; don't pile `refresh_unity` calls onto a blocked main thread. Prefer `refresh_unity scope=scripts` for code-only changes (`scope=all force` is heavy and contributed to a mid-session hang).
### Build gotchas (learned — M5.5 game feel & identity, 2026-06-02)
- **Move an ownerless INTERPOLATED enemy ghost SERVER-ONLY in the plain `SimulationSystemGroup`, never in `PredictedSimulationSystemGroup`** (interpolated ghosts aren't predicted; the server has no rollback). Use `[UpdateAfter(PredictedSimulationSystemGroup)]`, **NOT `[UpdateBefore]`** — the predicted group is **OrderFirst** in `SimulationSystemGroup`, so `UpdateBefore`/`After` *it* is silently *ignored* (Unity logs "Ignoring invalid UpdateBefore… OrderFirst/OrderLast has higher precedence"). A plain-`SimulationSystemGroup` server system therefore always runs **after** the predicted group, so a contact `DamageEvent` it appends drains the **following** tick (~16ms, fine for melee). Stock `LocalTransform` replication carries position — **no hand-written `[GhostField]`**. Build the enemy ghost by **duplicating an existing interpolated ghost** (`UpgradePickup.prefab``Enemy.prefab`) so the ownerless/interpolated `GhostAuthoringComponent` comes free — the training dummy is **not** a ghost (server-only → invisible to clients). See [[DR-009_GameFeel_Identity_FirstBlood]].
- **Derive an enableable gate from already-replicated state instead of replicating it.** Player `Dead` = a LOCAL enableable derived every predicted tick from the replicated `Health<=0` (`PlayerDeathStateSystem`, runs in **both** worlds, before movement/aim/fire) — the same derive-don't-replicate idiom as `StatRecomputeSystem`/`EffectiveCharacterStats`; rollback-correct on server + owner-client with **no `[GhostEnabledBit]`**. To write the bit on a currently-disabled entity the query must visit it: `.WithPresent<Dead>()` (write) vs `.WithDisabled<Dead>()` (alive-only run); `.WithAll<Simulate>()` ANDs independently. **Bake the enableable DISABLED** (`AddComponent<T>(e); SetComponentEnabled<T>(e, false);` in the baker) so instances spawn in the off state (instantiated entities inherit the prefab's enabled state). Respawn TIMING is server-only (`SimulationSystemGroup`, after the predicted group).
- **All juice/HUD = client-only managed `SystemBase` in `PresentationSystemGroup`** (once per frame, no rollback double-fire) that OBSERVES replicated state — never mutates the sim. Read ECS via `SystemAPI.Query` inside `OnUpdate` + `EntityManager.CompleteDependencyBeforeRO<T>()` — NOT from a MonoBehaviour `LateUpdate` (that throws the job-safety exception the camera rig hit). `Entity` is 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 pos; **never `DestroyEntity` a ghost from the client**`GhostDespawnSystem` owns despawn). Netcode-safe "hit-stop" = a camera punch, **never `Time.timeScale`** (it would corrupt the deterministic sim).
- **Asset-free presentation:** procedural `AudioClip.Create` SFX; a runtime `ParticleSystem` pool (Sprites/Default material + HDR start color so bursts bloom); a code-built uGUI HUD (`RawImage` over `Texture2D.whiteTexture` for anchor-driven bars + legacy `Text` with `Resources.GetBuiltinResource<Font>("LegacyRuntime.ttf")`). To edit a prefab asset's component in code: `PrefabUtility.LoadPrefabContents` → modify → **`SaveAsPrefabAsset(root, path)`** → `UnloadPrefabContents` (`SavePrefabAsset` rejects the contents root — "Can't save a Prefab instance"). Watch **shared-material bleed** when re-tinting (`M_Dummy` doubled as the wall material → orange walls; Husks got their own `M_Husk`). **ACES tonemapping needs the URP asset color grading mode = HDR** (`m_ColorGradingMode = 1`).
- **The "0 = ready" raw-`uint` cooldown sentinel can collide at tick wraparound** — a computed `ServerTick + delay` can equal 0. Route every cooldown/spawn "next tick" write through **`TickUtil.NonZero(...)`** (coerce 0→1), and compare stored ticks with `new NetworkTick(raw).IsNewerThan(serverTick)` / `.TicksSince(...)`**never** raw `<` / subtraction — the HUD cooldown bar included. (Caught by the adversarial review; `RespawnMath` already guarded it.)
- **An unfocused editor stalls EditMode test INIT** ("tests did not start within timeout") and slows play-enter domain reloads — pass `run_tests(init_timeout=120000)` and retry; ask the operator to focus Unity for heavy build/test sessions (`Application.runInBackground` only helps in Play mode).
## Bootstrap & worlds
- `ProjectM.Simulation.GameBootstrap : ClientServerBootstrap` → overrides `Initialize`, sets `AutoConnectPort = 7979` (in-editor auto-connect over IPC; set in M1 — was 0), calls `CreateDefaultClientServerWorlds()`. Entering Play Mode creates separate `ServerWorld` (`WorldFlags.GameServer`) and `ClientWorld` (`WorldFlags.GameClient`) — verified.