Asset Dump

This commit is contained in:
2026-06-03 13:46:13 -07:00
parent e362aaeb43
commit 9091388bc2
20821 changed files with 26544125 additions and 58 deletions
+35
View File
@@ -91,6 +91,41 @@ Root namespace: **`ProjectM`**. Code lives under `Assets/_Project/Scripts/` in f
- **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).
### Build gotchas (learned — art import / Synty packs → URP, 2026-06-03)
The imported store art (BefourStudios; future Synty) is **HDRP-authored** but we run **URP 17.4 + Entities Graphics** → source `M_*` materials render **magenta**. The reusable converter is `Assets/_Project/Scripts/Editor/EnvArtTools.cs` (menu `ProjectM/Art/1. Convert Curated Env Materials`); it outputs stock URP/Lit to `Assets/_Project/Materials/Env/`. See [[DR-010_Art_Import_URP_Conversion_Visual_Upgrade]].
- **Convert, don't switch pipelines.** Re-author to **stock URP/Lit** (`933532a4…`, the same shader our prototypes use → DOTS-instancing/Entities-Graphics compatible). FBX meshes + `T_*_B/_N/_ORM` textures are reusable as-is; the auto-generated `MI_*` URP stubs are blank. Switching the project to HDRP is NOT an option (breaks Entities Graphics).
- **A dark-lit screenshot MASKS material bugs — verify material *values*, not just the render.** `S_General` exposes a **float** `_BaseColorMultiply` and the real Color in `_AlbedoTint`. `HasProperty("_BaseColorMultiply")` is true but `GetColor()` on a float returns **(0,0,0)** (and logs a "doesn't have a color property" warning) → black albedo everywhere. **Always `shader.GetPropertyType(idx)`-guard before `GetColor`/`GetFloat`/`GetTexture`.**
- **Gate source emission on the `_Emissive` (0/1) flag AND a fixture name** — `S_General` carries a non-zero default `_EmissiveColor` even when off; reading it unconditionally makes crates/walls/domes glow (flat color can't reproduce the source emission mask).
- **`VolumeProfile.Add<T>()` does NOT persist the override** — on save the `components` list serializes `{fileID:0}` nulls (works in-session, gone after reload). Use `AssetDatabase.AddObjectToAsset(component, profile)` per effect, then `SaveAssets`; verify non-null refs on disk.
- **`LocalTransform.FromPosition()` resets Scale=1**, silently discarding a ghost prefab's authored scale (Scale is a replicated `[GhostField]` → consistent-but-wrong, not a desync). Server spawners must read the prefab's baked `LocalTransform` and override only Position (fixed in `UpgradePickupSpawnSystem`/`SharedStorageSpawnSystem`).
- **High metallic + no reflection probe + dark skybox = near-black.** Keep converted env metallic low (0.10.2); rely on albedo + direct light.
- **Static decor goes in the gameplay subscene** (Entities Graphics renders only baked/EG-spawned entities; a SampleScene MeshRenderer renders via classic URP). **Strip colliders** from cosmetic props (else they bake into the static PhysicsWorld the CC sweeps) and put **no GhostAuthoring** on scenery. Edit the subscene via `manage_scene load … additive` → place → `SaveScene``close_scene` (re-bakes on Play); the baked-entity view disappears while it's open additively — verify placement via `execute_code` over the scene roots, not the game view.
- **HUD-free beauty shot** = a **positioned `game_view` capture** (`view_position`/`view_rotation`) — direct camera rendering excludes Screen-Space-Overlay UI. `scene_view` rejects positioned capture.
- **VFX (GabrielAguiar) is now imported** (499 prefabs, ~94% Shuriken; 27 VFX-Graph). Wired into combat via [[DR-011_Synty_World_VFX_Integration]] — see the VFX gotchas below. VFX-Graph (hits/beams) packs still need separate URP setup if wanted.
### Build gotchas (learned — Synty world + GabrielAguiar VFX, 2026-06-03)
The world is now a cohesive **Synty** sci-fi colony + GabrielAguiar combat VFX. See [[DR-011_Synty_World_VFX_Integration]] / [[2026-06-03_Synty_World_And_VFX]].
- **Synty is URP-native — NO conversion** (unlike the HDRP BefourStudios art). The grounded world is built as **cosmetic classic-URP GameObjects in SampleScene** (`SyntyWorld` root), NOT the DOTS subscene — the custom `Synty/Generic_Basic` shader just renders, and you never have to verify its Entities-Graphics DOTS-instancing. Only the gameplay subscene needs Entities Graphics + the baked PhysicsWorld.
- **Cosmetic SampleScene colliders are inert to gameplay** — classic PhysX is separate from the DOTS PhysicsWorld (baked only from the subscene); the planar-pinned CC never sees them. So a cosmetic world needs no collider stripping for *gameplay*.
- **"Grounded" = surround + horizon, not a bigger plane** — a skyline ring of tall buildings + a planet/asteroid backdrop + a `Skybox/6 Sided` space skybox + light fog killed the floating-plane far better than extending the ground.
- **Swap a subscene object's VISUAL while keeping collision:** disable its MeshRenderer but keep the BoxCollider — the collider still bakes to the static `PhysicsCollider`, and a disabled renderer bakes no RenderMesh (invisible wall, collision intact). Used to retire the BefourStudios walls under the Synty world.
- **A GA "projectile" prefab is NOT a passive trail** — it ships a non-kinematic `Rigidbody` + collider + `ProjectileMoveScript` (self-propels, collides, spawns secondary muzzle/hit VFX). Any authored VFX dropped into a cosmetic slot must be **stripped to particles**: destroy `Rigidbody`/`Collider` and disable `Projectile`/`Move`-named MonoBehaviours BEFORE their `Start` runs (`CombatFeedbackSystem.StripCosmetic`). Verify a prefab's *components*, not its name.
- **VFX prefab → client SystemBase bridge:** a MonoBehaviour with a static `Instance` + prefab fields in the bootstrap scene (`VFXConfig`, mirrors `PrototypeCameraRig`) hands authored assets to the managed `CombatFeedbackSystem`; keep a procedural fallback so a null slot still runs. Derive VFX TTL from the prefab's longest ParticleSystem (not a blanket constant) and cap concurrent VFX to bound GC churn under swarms.
- **Projectile-follow VFX:** query `SystemAPI.Query<RefRO<LocalTransform>>().WithAll<Projectile>()` each presentation frame; dict-by-`Entity` spawn/reposition/prune (same idiom as the Health FX cache). The one-shot Fire `InputEvent` still drops under the **unfocused** editor, so fire-driven VFX (muzzle/trail/enemy-death) need a focused editor / real client to fire.
### Build gotchas (learned — aim controls: mouse cursor + gamepad, 2026-06-03)
The KBM/gamepad aim rework is [[DR-012_Aim_Controls_Cursor_Gamepad]] / [[2026-06-03_Aim_Controls_Cursor_Gamepad]].
- **Client-derived aim rides the EXISTING `PlayerInput.Aim` `[GhostField]` — no new netcode surface.** Mouse-cursor aim is computed client-side in `PlayerInputGatherSystem` (managed `SystemBase`, `GhostInputSystemGroup`, once/frame): `Mouse.current.position``Camera.main.ScreenPointToRay``AimMath.PlanarAimFromRay` (pure, Burst-safe, unit-tested) against the player's movement plane → write the player→cursor direction as `Aim`. Only the resulting direction crosses the wire; predicted/server systems are unchanged. Movement (`Move`) is already decoupled from facing (`Aim`/`PlayerFacing`), so **strafe-while-aiming is free** the moment `Aim` is the cursor. Don't add a mouse binding to the `Aim` action — the gather reads the device directly (no `.inputactions` edit → no wrapper regen).
- **Active input scheme = last-meaningful-actuation-wins, replicated as a `byte` (NOT an enum).** Detect KBM vs gamepad each frame (stick/trigger/button past a 0.04 lengthsq deadzone vs mouse-delta/click/movement-key; `InputDevice.lastUpdateTime` breaks ties; hold last when idle) and stream `PlayerInput.Scheme` (`InputSchemeId.KeyboardMouse=0/Gamepad=1`). It is a **byte** because it is compared inside the Burst-compiled `AbilityFireSystem` (the cross-assembly enum-in-Burst ICE hazard). The server gates the `AutoTarget` cone to `applied.InternalInput.Scheme == Gamepad` (read at the fire tick from the same `GetDataAtTick` lookup) → **precise mouse, gamepad-only assist**; mouse then predicts == server (fewer reconcile snaps).
- **A static presentation bridge must reset on play-enter.** `AimPresentation.Scheme` (mirrors `PrototypeCameraRig`/`VFXConfig` statics) needs `[UnityEngine.RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)]` to reset — statics survive **fast-enter-playmode** domain reloads, and a stale value flashes the wrong cursor/reticle for the first frames (caught by the adversarial review).
- **Cursor/reticle = client `PresentationSystemGroup` `SystemBase` (`AimReticleSystem`) that OBSERVES, never mutates.** A flat world-space ground ring (primitive quad, `Sprites/Default` with a null-guard fallback, procedural ring texture) is the aim indicator for BOTH schemes — KBM at the cursor's ground-projection point, gamepad a fixed distance ahead along replicated `PlayerFacing`. The hardware cursor is **hidden while aiming + focused** (`Application.isFocused`-gated) and restored on focus-loss / `OnDestroy`. A radial **dead-zone** (`AimMath.PlanarAimFromRay` `deadZoneRadius`) holds facing when the cursor is over the character. **The KBM ground point is re-raycast INSIDE `AimReticleSystem`** (PresentationSystemGroup runs after the follow-cam's LateUpdate), not latched from the gather (`GhostInputSystemGroup`, before the move) — latching there drifts the ring a frame behind the cursor under the moving camera. Optional camera **aim look-ahead** (`PrototypeCameraRig.AimLeadDistance`, tunable) leads the framed point toward `PlayerFacing` (not the live cursor projection, to avoid a feedback loop). Headless validation: drive `DebugInputInjectionSystem` (now stamps `Scheme`) + force `AimPresentation.Scheme`; the **real cursor / live device-switch needs a focused Game view** (the unfocused editor can't inject mouse position).
## 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.