docs(hygiene): reconcile CLAUDE.md + roadmap; triage 16 → 12 open issues

CLAUDE.md:
- "Currently in flight" (was NONE) → C.1.5b with pickup-doc link
- Added C.1.5a + N.6 slice 1 + post-A.5 polish ship paragraphs
- "Next planned phase" (was N.6) → ranked candidate list
- Collapsed Tier 1 + 4 historical phase paragraphs into
  roadmap-table pointer (signal density +; line count ~net zero)
- Fixed broken project_ui_architecture.md Memory-crib reference

Roadmap header: dated 2026-05-10 → 2026-05-11 with "since the last
update" delta. New Phase C.1.5 sliced sub-piece in Phases ahead.

ISSUES.md triage:
- Closed #37 (humanoid coat — resolved by #47 GfxObjDegradeResolver),
  #49 + #50 (scenery placement — accepted WB-vs-retail divergence).
- Promoted #36 (sky-PES dispatch) to Phase C.1.5c; #2/#28/#29
  auto-close when that phase ships.
- Downgraded #39 (Run↔Walk cycle) to LOW + VERIFY-PENDING.
- Cross-referenced #41 + #46 to Phase L.2 motion conformance.
- Anti-coupling note on #4 (NOT in C.1.5c cluster).
- Chore tag on #3 (single-commit clock-drift fix).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Erik 2026-05-11 23:34:01 +02:00
parent ad261539d6
commit 55dc861724
3 changed files with 159 additions and 72 deletions

128
CLAUDE.md
View file

@ -147,9 +147,10 @@ time using dat assets); ImGui persists forever as the
`ACDREAM_DEVTOOLS=1` overlay. **All plugin-facing UI targets
`AcDream.UI.Abstractions` — never import a backend namespace from a
panel.** Full design: `docs/plans/2026-04-24-ui-framework.md`.
Memory cribs: `memory/project_ui_architecture.md` (architecture),
`memory/project_chat_pipeline.md` (chat pipeline as of Phase I),
`memory/project_input_pipeline.md` (input pipeline as of Phase K).
Memory cribs: `memory/project_chat_pipeline.md` (chat pipeline as of
Phase I), `memory/project_input_pipeline.md` (input pipeline as of
Phase K). UI architecture full design at
[`docs/plans/2026-04-24-ui-framework.md`](docs/plans/2026-04-24-ui-framework.md).
**Input pipeline:** `src/AcDream.UI.Abstractions/Input/` (action enum,
`KeyChord`, `KeyBindings`, multicast `InputDispatcher` with scope
@ -525,68 +526,75 @@ acdream's plan lives in two files committed to the repo:
acceptance criteria. Do not drift from the spec without explicit user
approval.
**Currently in flight: NONE — Post-A.5 polish phase COMPLETE 2026-05-11.**
All three post-A.5 issues closed: #52 (lifestone, `e40159f`), #54 (JobKind, `bf31e59`),
#53 (Tier 1 entity cache, `f928e66`). Phase A.5 + post-A.5 polish together comprise
the streaming + rendering perf foundation for the project.
**Currently in flight: Phase C.1.5b — issue #56 (per-part transforms for
multi-emitter PES) + EnvCell static-object `DefaultScript` walker.**
Pickup at [`docs/plans/2026-05-12-phase-c1.5b-handoff.md`](docs/plans/2026-05-12-phase-c1.5b-handoff.md).
#56 first per the C.1.5a final reviewer — every multi-emitter PES on a
multi-part entity (portals, chimneys, fireplaces) currently collapses
its emitters to entity root because `ParticleHookSink` ignores
`CreateParticleHook.PartIndex`. The mechanism is correct end-to-end;
the part-relative transform application is the gap.
**Tier 1 entity-classification cache (#53) shipped 2026-05-11.** New
`EntityClassificationCache` keyed by `(entityId, landblockHint)` tuple at
`src/AcDream.App/Rendering/Wb/EntityClassificationCache.cs`; the dispatcher's static-
entity slow path populates flat `CachedBatch[]` (one entry per (partIdx, batchIdx)
with part-relative `RestPose` + resolved `BindlessTextureHandle`), and the cache-hit
fast path skips classification entirely on subsequent frames. Animated entities
(`_animatedEntities`) bypass the cache. Invalidation fires on live-entity despawn
(`RemoveLiveEntityByServerGuid`) and LB demote/unload (`RemoveEntitiesFromLandblock`).
Entity dispatcher cpu_us **median ~1200 µs / p95 ~1500 µs** at horizon-safe preset
on AMD Radeon RX 9070 XT @ 1440p — down from ~3500m / ~4000p95 pre-Tier-1
(~66% / ~63% reduction). Well under the A.5 spec budget (median ≤ 2.0 ms, p95 ≤ 2.5 ms).
The implementation required 4 bug-fix iterations after the spec landed (stab Id
namespacing → cache tuple-key → drudge incomplete-classification → mid-list null-
renderData); see `docs/ISSUES.md` #53 closure entry for the lessons.
**Phase C.1.5a (portal PES wiring) shipped 2026-05-11** (merge `88bda12`).
Server-spawned `WorldEntity` entities fire their `Setup.DefaultScript`
through `PhysicsScriptRunner` on enter-world via the new ~70-line
`EntityScriptActivator` ([src/AcDream.App/Rendering/Vfx/EntityScriptActivator.cs](src/AcDream.App/Rendering/Vfx/EntityScriptActivator.cs)).
Visual-verified at the Holtburg Town network portal: 10-hook portal
script fires end-to-end with correct color, persistence, orientation,
multi-emitter dispatch. Known visual gap filed as #56 (per-part
transform handling); resolution is slice A of C.1.5b. Plan archived at
[`docs/superpowers/plans/2026-05-12-phase-c1.5a-portals.md`](docs/superpowers/plans/2026-05-12-phase-c1.5a-portals.md).
**Next planned phase: N.6 (perf polish) — see `docs/plans/2026-04-11-roadmap.md`.**
Alternative escalation path: Tier 2 (static/dynamic split with persistent groups,
~2 weeks) or Tier 3 (GPU compute culling, ~1 month) per
`docs/plans/2026-05-10-perf-tiers-2-3-roadmap.md`. With the Tier 1 dispatcher at
~1.2 ms, the project comfortably hits 200-400 FPS at radius=12 standstill;
escalation makes sense only if user wants 500+ FPS sustained.
**Phase N.6 slice 1 (gpu_us fix + radius=12 perf baseline) shipped
2026-05-11** (merge `9b447d4`). Fixed `gpu_us` double-buffering in
`WbDrawDispatcher` (ring-of-3 query slots, read-before-overwrite,
vendor-neutral). Captured authoritative perf baseline at Holtburg radii
4 / 8 / 12. **Conclusion: CPU dominates GPU by 3050× at every radius**;
GPU sits at 3.6% of frame budget; per-LB walk is the next bottleneck.
Baseline-doc recommendation: do C.1.5 next, then a reduced-scope slice 2
(atlas + persistent-mapped buffers dropped from slice-2 scope). Baseline
at [`docs/plans/2026-05-11-phase-n6-perf-baseline.md`](docs/plans/2026-05-11-phase-n6-perf-baseline.md).
Plan archived at [`docs/superpowers/plans/2026-05-11-phase-n6-slice1.md`](docs/superpowers/plans/2026-05-11-phase-n6-slice1.md).
Issue #55 filed (static-entity slow path reports ~1.45M `meshMissing`
per 5s at r4 standstill — diagnostic, not a visible regression).
**Phase A.5 (Two-tier Streaming + Horizon LOD) shipped 2026-05-10.**
N₁=4 near-tier (81 LBs, full detail) + N₂=12 far-tier (544 LBs, terrain only); fog
horizon; QualityPreset system (Low/Medium/High/Ultra) with env-var overrides; F11
mid-session reapply. Two post-ship-prep bugs fixed: Bug A (far-tier worker was loading
full entity layer — ~71K entities, ~5x perf regression vs spec), Bug B (WalkEntities
per-frame list alloc — ~480 KB/frame GC pressure). Tier 1 entity cache reverted (animation
regression; see #53). Plan archived at
[`docs/superpowers/plans/2026-05-09-phase-a5-two-tier-streaming.md`](docs/superpowers/plans/2026-05-09-phase-a5-two-tier-streaming.md).
**Post-A.5 polish phase complete 2026-05-11.** All three post-A.5
issues closed: #52 (lifestone, `e40159f`), #54 (JobKind, `bf31e59`),
#53 (Tier 1 entity cache, `f928e66`). Phase A.5 + post-A.5 polish
together comprise the streaming + rendering perf foundation for the
project.
**Phase N.5b (Terrain on Modern Rendering Path) shipped 2026-05-09.**
`TerrainModernRenderer` mirrors WB's `TerrainRenderManager` pattern
(single global VBO/EBO + slot allocator + bindless atlas +
`glMultiDrawElementsIndirect`) but consumes `LandblockMesh.Build` so
retail's `FSplitNESW` formula is preserved (Path C; closes ISSUE #51).
Path A (substitute WB's `CalculateSplitDirection`) killed by 49.98%
divergence vs retail in
[`tests/AcDream.Core.Tests/Terrain/SplitFormulaDivergenceTest.cs`](tests/AcDream.Core.Tests/Terrain/SplitFormulaDivergenceTest.cs).
At radius=5 in Holtburg modern is ~4× SLOWER on CPU than the legacy
chunked path was; architectural wins manifest at higher radius. Honest
perf baseline at
[`docs/plans/2026-05-09-phase-n5b-perf-baseline.md`](docs/plans/2026-05-09-phase-n5b-perf-baseline.md).
Plan archived at
[`docs/superpowers/plans/2026-05-09-phase-n5b-terrain-modern.md`](docs/superpowers/plans/2026-05-09-phase-n5b-terrain-modern.md).
**After C.1.5b, candidate next phases (in rough preference order):**
- **Triage the chronic open-issue list** in `docs/ISSUES.md`#2 (lightning),
#4 (sky horizon-glow), #28 (aurora), #29 (cloud thinness), #37 (humanoid
coat), #50 (stray tree), #41 (remote-motion blips) have been open since
April/early-May and keep getting deferred. Either link each to a future
phase or downgrade. ~1 hour, surfaces what's chronic vs. linked-to-a-phase.
- **More Phase C visual-fidelity work** (C.2 dynamic point lights, C.3
palette tuning, C.4 double-sided translucent polys) closing the
"world reads as old / broken vs. retail" backlog.
- **N.6 slice 2** at reduced scope (atlas opportunities only — persistent-
mapped buffers and other slice-2 items dropped per slice-1 baseline doc).
- **Perf tiers 2/3** (`docs/plans/2026-05-10-perf-tiers-2-3-roadmap.md`)
only if user wants sustained 500+ FPS. With Tier 1 dispatcher at ~1.2 ms
the project comfortably hits 200-400 FPS at radius=12 standstill;
escalation is optional from here.
**Phase N.5 (Modern Rendering Path) shipped + amended 2026-05-08.** `WbDrawDispatcher`
on bindless textures + `glMultiDrawElementsIndirect`. CPU dispatcher 1.23ms/frame
at Holtburg (~810 fps). **Ship amendment:** `InstancedMeshRenderer`,
`StaticMeshRenderer`, `WbFoundationFlag` deleted in same phase — modern path is
mandatory; missing bindless throws at startup. Plan archived at
[`docs/superpowers/plans/2026-05-08-phase-n5-modern-rendering.md`](docs/superpowers/plans/2026-05-08-phase-n5-modern-rendering.md).
**Phase N.4 (Rendering Pipeline Foundation) shipped 2026-05-08.** WB's
`ObjectMeshManager` is integrated and is the production rendering path
(mandatory as of N.5 ship amendment). Plan archived at
[`docs/superpowers/plans/2026-05-08-phase-n4-rendering-foundation.md`](docs/superpowers/plans/2026-05-08-phase-n4-rendering-foundation.md).
**Earlier rendering + streaming arc (2026-05-08 → 2026-05-10).**
Phases **N.4 → N.5 → N.5b → A.5** shipped the modern rendering
pipeline + two-tier streaming foundation: WB `ObjectMeshManager` as
production mesh path (N.4); bindless + `glMultiDrawElementsIndirect`
for entities (N.5, ~12-15 GL calls/frame) and terrain via Path C
preserving retail's `FSplitNESW` formula (N.5b, closes #51); two-tier
streaming N₁=4 / N₂=12 + QualityPreset system (A.5). Modern path is
mandatory as of N.5 ship amendment — `InstancedMeshRenderer`,
`StaticMeshRenderer`, `WbFoundationFlag` all deleted; missing bindless
throws at startup. Detail + decomp anchors + plan archives in roadmap
shipped-table rows 6366 at `docs/plans/2026-04-11-roadmap.md`.
Engineering gotchas (bindless Dispose order, texture target lock-in,
`uvec2` sampler-handle pattern, WB-vs-retail formula divergence)
documented inline at the relevant call sites and in
`feedback_wb_migration_*.md` memory entries.
**Rules:**

View file

@ -111,13 +111,25 @@ settle at any radius/motion combination, not stay at ~1.45M/5s indefinitely at s
---
## #50 — Road-edge tree at 0xA9B1 visible in acdream but not retail
## #50 [DONE 2026-05-11 · accepted WB divergence] Road-edge tree at 0xA9B1 visible in acdream but not retail
**Status:** OPEN
**Status:** DONE
**Closed:** 2026-05-11
**Severity:** LOW (cosmetic; one spawned tree near the road in Holtburg)
**Filed:** 2026-05-08
**Component:** scenery placement / Phase N (WorldBuilder rendering migration)
**Resolution:** Same disposition as #49 — accepted as WB-upstream
divergence from retail. The earlier fix attempt (`e279c46`, ACME-style
per-vertex road check) successfully removed this specific tree but
over-suppressed scenery elsewhere; revert at `677a726` stood. Without
a coherent port of ACME's full per-vertex filter set, piecemeal
patching is net-negative. Left as a documented WB divergence.
---
**Original investigation (kept for reference):**
**Description:** With `ACDREAM_USE_WB_SCENERY=1` (default since commit `b84ecbd`),
a tree at landblock 0xA9B1 around `(lx=85.08, ly=190.97)` appears in acdream but
neither retail nor ACME WorldBuilder render it. Upstream Chorizite/WorldBuilder
@ -155,13 +167,33 @@ other Phase N work catches a similar issue and a coherent fix becomes obvious.
---
## #49 — Scenery (X, Y) placement drifts from retail at some landblocks
## #49 [DONE 2026-05-11 · accepted WB divergence] Scenery (X, Y) placement drifts from retail at some landblocks
**Status:** OPEN
**Severity:** MEDIUM (visible misplacement; species-specific or per-cell, not a global offset)
**Status:** DONE
**Closed:** 2026-05-11
**Severity:** LOW (minor cosmetic placement difference)
**Filed:** 2026-05-06
**Component:** scenery placement / `SceneryGenerator`
**Resolution:** Accepted as WB-upstream divergence from retail. Since
the N.1 phase (WorldBuilder-backed scenery, see roadmap), acdream
defers scenery placement math to the WB fork; retail and WB diverge
slightly here on some landblocks. Piecemeal patching against WB
upstream would create a maintenance burden disproportionate to the
visible impact (a handful of trees positioned a few meters off across
the world). Left as-is; revisit only if WB upstream patches the
divergence or if a coherent ACME-style filter port (see issue body
below) becomes worthwhile.
The original investigation plan (cdb trace of retail's
`CLandBlock::get_land_scenes` for diff against acdream's
`SceneryGenerator` output) is preserved below for historical
reference if anyone picks this up.
---
**Original investigation (kept for reference):**
**Description:** While verifying the `#48` Z fix at Holtburg
landblock `0xA9B30001`, the user spotted a scenery tree placed at
the **wrong (X, Y)** in acdream relative to retail at the same
@ -362,8 +394,8 @@ regression on the species that already render correctly.
## #39 — Run↔Walk cycle transition not visible on observed player remotes (acdream-as-observer)
**Status:** OPEN
**Severity:** MEDIUM (visible animation desync; not a correctness/wire bug)
**Status:** OPEN — VERIFY-PENDING (cases #1/#2/#4/#5 user-verified working 2026-05-06; cases #3/#6/#7 unverified in live test)
**Severity:** LOW (most cases now visibly correct after the 2026-05-06 fix sequence; remaining unverified cases are direction-flip — believed to work via direct UM but not explicitly exercised)
**Filed:** 2026-05-03
**Component:** physics / motion / animation
@ -678,6 +710,7 @@ the local terrain normal, not the actor's facing.
**Severity:** LOW (within retail's own DesiredDistance / MinDistance tolerances; visible only on close inspection)
**Filed:** 2026-05-05
**Component:** physics / motion / animation (per-tick remote prediction)
**Phase:** L.2 (Movement & Collision Conformance) — inbound-motion fidelity sub-piece. Blocked on cdb-trace of `CSequence::velocity` for Humanoid running cycle, then porting `add_motion @ 0x005224b0`'s `style_speed × MotionData.velocity` chain.
**Description:** With the L.3 M3 path live (queue catch-up + animation
root motion fallback), observed player remotes chase server position
@ -926,13 +959,35 @@ collision fixes.)
still resolve correctly)
- Observer view from a parallel retail client unchanged
## #37 — Humanoid coat doesn't extend up to neck (visible "skin stub" between hair and coat)
## #37 [DONE 2026-05-11 · resolved by `0bd9b96`] Humanoid coat doesn't extend up to neck (visible "skin stub" between hair and coat)
**Status:** OPEN
**Status:** DONE
**Closed:** 2026-05-11
**Commit:** `0bd9b96` (the #47 humanoid degrade-resolver fix, 2026-05-06)
**Severity:** LOW (cosmetic; doesn't affect gameplay)
**Filed:** 2026-05-01
**Component:** rendering / clothing / textures
**Resolution:** Closed by the same mesh-fidelity work that resolved #47.
The `GfxObjDegradeResolver` (commit `0bd9b96`, 2026-05-06) swapped
humanoid parts to their higher-detail `Degrade[0].Id` meshes (e.g.
upper arm `0x01000055 → 0x01001795`, lower arm `0x01000056 → 0x0100178F`).
The higher-detail meshes include the coat-collar polygons that the
low-detail meshes were missing — which is what was exposing the
skin-toned palette indices in the upper-coat region. With the
correct mesh resolution, those polygons cover the previously-visible
"skin stub". User confirmed visually 2026-05-11.
The original 2026-05-01/2026-05-04 investigation work (palette range
analysis, SubPalette overlay tracing) is preserved below for
historical reference; it was a correct read of *what* was rendering,
but the root cause was the missing collar polygons, not the palette
gap.
---
**Original investigation (kept for reference):**
**Description:** Every humanoid character (player + NPCs) wearing a coat
shows a visible skin-colored region at the top of the coat where retail
shows continuous coat fabric. From the back view: hair → skin stub →
@ -1276,13 +1331,29 @@ in under 5 minutes by following the CLAUDE.md workflow.
---
## #36 — Sky-PES dispatch port (consolidates #2 / #28 / #29 visual gaps)
## #36 [DONE 2026-05-11 · promoted to Phase C.1.5c] Sky-PES dispatch port (consolidates #2 / #28 / #29 visual gaps)
**Status:** OPEN
**Status:** DONE (promoted to Phase C.1.5c)
**Closed:** 2026-05-11
**Promoted to:** Phase C.1.5c (Sky-PES dispatch chain) — see roadmap `docs/plans/2026-04-11-roadmap.md`
**Severity:** MEDIUM (aesthetic feature-parity, but addresses a cluster of bugs)
**Filed:** 2026-04-30
**Component:** sky / weather / particles
**Resolution:** Promoted to a roadmap phase (C.1.5c) — the work is
multi-commit (decomp dive + persistent-emitter creation + PES timeline
driver + PES script execution + live-trace verification) and warrants
a named phase rather than living forever as an "open issue." The
decomp anchors, live-trace evidence (24,576-frame `GameSky::Draw`
trace), and 6-step implementation outline in the body below remain
the authoritative implementation reference; the roadmap phase entry
is the schedule/scope tracker. **Issues #2 (lightning), #28 (aurora),
and #29 (cloud thinness) auto-close when C.1.5c ships.**
---
**Original investigation (kept as implementation reference):**
**Description:** Three open sky bugs (#2 lightning, #28 aurora, #29 cloud
density) all trace back to the same missing infrastructure: retail's
sky-PES (Particle Effect Script) dispatch chain. We have it now from a
@ -1427,6 +1498,7 @@ one live creature case no longer use the single-cylinder fallback.
**Severity:** MEDIUM
**Filed:** 2026-04-25
**Component:** net / sky
**Chore tag:** Single-commit fix — well-scoped ~10-line wiring. `WorldTimeService.SyncFromServer(double)` already exists; just needs `WorldSession` to detect header-flag `0x1000000` and call it. Pickup at any opportunistic session.
**Description:** Our `WorldTimeService.DayFraction` syncs with the server once at login via `ConnectRequest + TimeSync`, then advances from the local wall-clock. Retail receives periodic `TimeSync` refreshes (header flag `0x1000000`) carrying a fresh `PortalYearTicks double` and re-anchors its clock. Without those, acdream's keyframe state drifts from retail's over 10+ minutes — observed during the 2026-04-24 sky-color debug sessions where retail was at DayFraction 0.976 while acdream was at 0.634.
@ -1456,6 +1528,8 @@ one live creature case no longer use the single-cylinder fallback.
**Root cause / status:** Three competing hypotheses, none pinned down: (a) retail uses a **different** fog range for sky than terrain; (b) retail applies fog with an **elevation-angle** weighting rather than linear distance; (c) retail's sky meshes **don't participate** in the global fog and the "horizon glow" comes from a different atmospheric-scatter path. Need to identify retail's actual sky-fog behaviour before re-enabling with correct parameters.
**Not in the Phase C.1.5c (Sky-PES) cluster.** Unlike #2/#28/#29 — all PES-driven sky visuals consolidated under the C.1.5c phase via former issue #36 — this is a fragment-shader fog-mix problem. Addressing C.1.5c will NOT resolve #4, and #4 should NOT be bundled into Phase C.1.5c scope. The fix likely needs its own decomp dive into retail's sky-fog math + shader work.
**Files:**
- `src/AcDream.App/Rendering/Shaders/sky.frag` — line ~55, `rgb = mix(uFogColor.rgb, rgb, vFogFactor)` currently commented out
- `src/AcDream.App/Rendering/Shaders/sky.vert` — lines 109-114, `vFogFactor` computation
@ -1686,6 +1760,7 @@ retail show matching silhouette and shape definition.
**Severity:** MEDIUM (degrades external perception of acdream-driven characters)
**Filed:** 2026-05-06
**Component:** net / motion (acdream's outbound path: `PlayerMovementController``MoveToState` (0xF61C) / `AutonomousPosition` heartbeat → ACE → retail observer)
**Phase:** L.2 (Movement & Collision Conformance) — outbound-motion fidelity sub-piece. Counterpart to #41 (which is the inbound side); both are L.2 conformance work. If outbound fidelity grows into multi-commit work, consider carving "L.2e — Outbound motion fidelity" as a named sub-piece on the roadmap.
**Description:** When viewing acdream's local +Acdream character through a parallel retail acclient.exe, the retail observer sees the character's movement as visibly blippy and laggy — position appears to step in discrete jumps rather than translating smoothly. The local acdream view of the same character looks fine, and acdream observing a retail-driven character (after #39 / #45) also looks fine. The degradation is specifically on the **outbound** side: what acdream sends to ACE for relay to other clients.

View file

@ -1,6 +1,6 @@
# acdream — strategic roadmap
**Status:** Living document. Updated 2026-05-10 for Phase A.5 shipping (two-tier streaming N₁=4/N₂=12 + QualityPreset system + Bug A/B fixes; closes the two-tier streaming spec). Post-A.5 polish (Tier 1 retry + lifestone fix + JobKind plumbing) is now the in-flight work.
**Status:** Living document. Updated 2026-05-11. **In flight: Phase C.1.5b** (issue #56 per-part transforms for multi-emitter PES + EnvCell static-object `DefaultScript` walker); pickup at [`docs/plans/2026-05-12-phase-c1.5b-handoff.md`](2026-05-12-phase-c1.5b-handoff.md). **Since the last header update:** post-A.5 polish completed (#52 lifestone, #54 JobKind, #53 Tier 1 cache); N.6 slice 1 shipped (gpu_us fix + radius=12 perf baseline, conclusion CPU dominates GPU 3050×); C.1.5a shipped (portal PES wiring; surfaced #56 for C.1.5b to resolve).
**Purpose:** One source of truth for where the project is and where it's going. Every observed defect or missing feature has a named phase that owns it; when something looks wrong in-game, look here to find the phase that'll address it. Implementation details live in per-phase specs under `docs/superpowers/specs/`, not in this file.
---
@ -122,6 +122,10 @@ Plus polish that doesn't get its own phase number:
**Sub-pieces:**
- **✓ SHIPPED — C.1 — VFX / particle system + sky-pass refinements.** Retail-faithful `ParticleEmitterInfo` runtime + 13-type motion integrator port + `PhysicsScript` runner + instanced billboard renderer with material-derived blend + global back-to-front sort + AttachLocal live-parent follow. Sky-pass refinements: Translucent+ClipMap alpha-blend, raw-Additive fog-skip, per-keyframe SkyObjectReplace divide-by-100, sampler-object wrap selection (ported from WorldBuilder), gated post-scene Z-offset. Sky-PES disabled by default — named-retail decomp proves `GameSky` drops `pes_id`. **Portal swirls, chimney smoke, fireplace flames** still need a Phase C.1.5 follow-up to wire entity-attached emitters to retail effect IDs (the data layer is ready; only the wiring is deferred). Lands as merge `feat(vfx): Phase C.1 — PES particle renderer + post-review fixes` (`ec1bbb4`) + `refactor(sky): replace per-frame wrap-mode mutation with persistent samplers` (`3d21c13`).
- **C.1.5 — entity-attached PES wiring (sliced).** Three sub-slices wiring `PhysicsScript` / `DefaultScript` dispatch to the entity lifecycle so portals, chimneys, fireplaces, and sky effects animate per retail:
- **✓ SHIPPED — C.1.5a (portals)** — 2026-05-11 (merge `88bda12`). `EntityScriptActivator` fires `Setup.DefaultScript` on every server-spawned `WorldEntity` via `PhysicsScriptRunner`. Visual-verified at Holtburg Town network portal. Surfaced known limitation as issue #56 (per-part transform handling) — addressed in C.1.5b. Plan archived at [`docs/superpowers/plans/2026-05-12-phase-c1.5a-portals.md`](../superpowers/plans/2026-05-12-phase-c1.5a-portals.md).
- **IN FLIGHT — C.1.5b (per-part transforms + EnvCell static walker).** Fix issue #56 first (every multi-emitter PES on a multi-part entity — portals, chimneys, fireplaces — currently collapses to entity root because `ParticleHookSink` ignores `CreateParticleHook.PartIndex`), then add EnvCell static-object `DefaultScript` walker for chimney smoke + fireplace flames. Pickup at [`docs/plans/2026-05-12-phase-c1.5b-handoff.md`](2026-05-12-phase-c1.5b-handoff.md).
- **PLANNED — C.1.5c (sky-PES dispatch chain).** Promoted from former issue #36 (2026-05-11 triage). Ports retail's persistent-emitter creation on celestial / sky objects + the PES timeline driver (`CallPESHook::Execute``CPhysicsObj::CallPES``create_particle_emitter`) that drives them ~150×/min. Decomp anchors + live-trace evidence + 6-step impl outline in closed issue [#36](../ISSUES.md#36). **Closes #2 (lightning), #28 (aurora), #29 (cloud thinness) when shipped.** Does NOT close #4 (sky horizon-glow fog) — that's shader work, not PES.
- **C.2 — Dynamic point lights.** Fireplaces and lamps need local lighting; small upgrade to the mesh shader to accumulate N (e.g., 4) nearest point lights per draw. Uniform-buffer or UBO-friendly layout.
- **C.3 — Palette range tuning.** Small per-range offset/length tweaks to match retail skin/hair/eye colors. Mostly diff and verify work, no architecture change.
- **C.4 — Double-sided translucent polys.** Edge case left by Phase 9.2: neg-side translucent polys are culled because cull is always BACK. Fix by tracking per-sub-mesh `CullMode` and flipping GL state per draw (or drawing twice with opposite cull). Minor.