Merge branch 'claude/quirky-jepsen-fd60f1' — N.4 Week 4 handoff doc
This commit is contained in:
commit
16a36dda8f
1 changed files with 318 additions and 0 deletions
318
docs/research/2026-05-08-phase-n4-week4-handoff.md
Normal file
318
docs/research/2026-05-08-phase-n4-week4-handoff.md
Normal file
|
|
@ -0,0 +1,318 @@
|
|||
# Phase N.4 Week 4 handoff — full draw dispatcher + visual verification + ship
|
||||
|
||||
**Use this whole document as the prompt** when handing off to a fresh
|
||||
agent. Everything they need to pick up cold is below.
|
||||
|
||||
---
|
||||
|
||||
## Background you'll need
|
||||
|
||||
You're working in `acdream`, a from-scratch C# .NET 10 reimplementation
|
||||
of Asheron's Call's retail client. The project's house rule (in
|
||||
`CLAUDE.md`) is **the code is modern, the behavior is retail**.
|
||||
|
||||
acdream is in the middle of Phase N.4 — the rendering pipeline
|
||||
foundation migration to WorldBuilder's `ObjectMeshManager` +
|
||||
`TextureAtlasManager`. **Three of the four planned weeks have shipped
|
||||
this session (2026-05-08)**:
|
||||
|
||||
- Week 1 (commits up through `c49c6ed`): foundation types — feature
|
||||
flag, surface metadata side-table, mesh-extraction + setup-flatten
|
||||
conformance tests, `WbMeshAdapter` constructed against the real WB
|
||||
pipeline.
|
||||
- Week 2 (commits up through `36f7a60`): streaming integration —
|
||||
`LandblockSpawnAdapter` routes atlas-tier (procedural / `ServerGuid==0`)
|
||||
GfxObjs to WB's ref-count lifecycle. `WbMeshAdapter.Tick()` drains
|
||||
the WB pipeline's main-thread queues per frame (fixes a real memory
|
||||
leak).
|
||||
- Week 3 (commits up through `d30fcb2`): per-instance tier hookup —
|
||||
`AnimatedEntityState` holds per-server-spawned-entity overrides;
|
||||
`EntitySpawnAdapter` routes server-spawned entities through the
|
||||
existing `TextureCache.GetOrUploadWithPaletteOverride` decode path.
|
||||
|
||||
**Current state at `main`:** build green, **947 tests pass**, 8
|
||||
pre-existing failures only (unchanged from pre-N.4 main). Default-off
|
||||
behavior is byte-identical to pre-N.4 main; flag-on (`ACDREAM_USE_WB_FOUNDATION=1`)
|
||||
runs both rendering pipelines in parallel — WB silently prepares
|
||||
content, but nothing is yet drawn through it.
|
||||
|
||||
**Read first:**
|
||||
|
||||
- [docs/superpowers/plans/2026-05-08-phase-n4-rendering-foundation.md](../superpowers/plans/2026-05-08-phase-n4-rendering-foundation.md) —
|
||||
the **living-document** plan. Top of file has a Progress table
|
||||
showing Tasks 1-21 ✅ shipped with commit SHAs. Adjustments 1-5
|
||||
document architectural surprises caught during execution. **Read the
|
||||
Adjustments before writing any Task 22 code** — they explain why the
|
||||
current architecture is what it is.
|
||||
- [docs/superpowers/specs/2026-05-08-phase-n4-rendering-foundation-design.md](../superpowers/specs/2026-05-08-phase-n4-rendering-foundation-design.md) —
|
||||
the design spec. Architecture / two-tier split / animation handling /
|
||||
data-flow diagrams. Strategic source of truth for "how the pieces
|
||||
fit together."
|
||||
- [CLAUDE.md](../../CLAUDE.md) — project-wide rules. The "Currently in
|
||||
flight" section near the top points at the plan.
|
||||
|
||||
## What Week 4 is
|
||||
|
||||
Seven tasks (22-28). **Task 22 alone is the biggest single task in the
|
||||
entire 28-task plan** — it's the moment we flip from "WB is silently
|
||||
preparing content" to "WB is drawing content to your screen."
|
||||
|
||||
The remaining six tasks are smaller: surface-metadata side-table
|
||||
population, sky-pass preservation check, micro-tests round-out, visual
|
||||
verification at 5 named locations, flag default-on, delete legacy
|
||||
code, finalize plan + memory + ISSUES.
|
||||
|
||||
**Task 22 also unlocks the Adjustment 3 mitigation.** Right now
|
||||
flag-on has a real FPS regression because both rendering pipelines run
|
||||
in parallel (legacy renderer still does atlas-tier upload + draw,
|
||||
even though WB is also building atlas state). When Task 22 lands the
|
||||
dispatcher AND wires the legacy-renderer short-circuit for atlas-tier
|
||||
content, that double-work disappears.
|
||||
|
||||
## Two unresolved decisions before Task 22 starts
|
||||
|
||||
These need a brainstorm checkpoint at the start of Week 4, NOT a
|
||||
"just dispatch":
|
||||
|
||||
1. **Adjustment 4 plumbing.** `WorldEntity` doesn't carry
|
||||
`HiddenPartsMask` or `AnimPartChanges` — those live on the
|
||||
network-layer spawn record and don't make it to the render-side
|
||||
entity. Two options:
|
||||
- **A**: add `HiddenPartsMask` + `AnimPartChanges` fields to
|
||||
`WorldEntity`, populate at spawn time. Cleaner long-term; small
|
||||
network→render plumbing change.
|
||||
- **B**: thread them as separate parameters into
|
||||
`EntitySpawnAdapter.OnCreate(entity, hiddenMask, animPartChanges)`.
|
||||
Sidesteps the `WorldEntity` change but couples the spawn-handler
|
||||
to the adapter API.
|
||||
|
||||
Decide before writing Task 22 because the dispatcher reads from
|
||||
`AnimatedEntityState` which currently holds defaults (empty mask +
|
||||
empty override map). Without this resolved, hidden parts won't
|
||||
actually be hidden flag-on.
|
||||
|
||||
2. **Surface-metadata side-table population strategy** (Task 23). The
|
||||
spec proposes: when `WbMeshAdapter.IncrementRefCount(id)` is first
|
||||
called for a GfxObj, walk its sub-meshes via `GfxObjMesh.Build`,
|
||||
write each `(gfxObjId, surfaceIdx) → AcSurfaceMetadata` entry into
|
||||
the side-table. The `_metadataPopulated: HashSet<ulong>` field
|
||||
tracks which ids have been processed.
|
||||
|
||||
**But:** if the same GfxObj gets its ref count drop to zero and
|
||||
then re-incremented (LRU eviction + reload), do we re-populate?
|
||||
The metadata is invariant per-GfxObj (surface flags don't change
|
||||
with eviction), so probably no — the `HashSet` is fine. But
|
||||
verify before implementing.
|
||||
|
||||
## Watchouts (lessons from Weeks 1-3)
|
||||
|
||||
These are real, observed gotchas. Read each before going deeper.
|
||||
|
||||
- **The renderer is tier-blind by design (Adjustment 2).** Don't try
|
||||
to put routing decisions in `InstancedMeshRenderer` or any mesh
|
||||
uploader. Routing belongs at the **spawn-callback layer**:
|
||||
`LandblockSpawnAdapter` for atlas-tier, `EntitySpawnAdapter` for
|
||||
per-instance. Task 22's dispatcher reads from those adapters'
|
||||
per-entity state at draw time; it doesn't make tier decisions.
|
||||
|
||||
- **Flag-off must stay byte-identical to pre-N.4.** Every Task-22 code
|
||||
path must have a `WbFoundationFlag.IsEnabled` gate. Default-off path
|
||||
is what users see; we can't regress it.
|
||||
|
||||
- **WB's pipeline does work even when you're not draining its results.**
|
||||
Adjustment 3: `IncrementRefCount` triggers background mesh prep,
|
||||
texture decode, atlas allocation. `WbMeshAdapter.Tick()` already
|
||||
drains the upload queue per frame. The remaining FPS cost is
|
||||
pure dual-pipeline cost (legacy + WB doing the same upload work).
|
||||
Task 22's short-circuit fixes this.
|
||||
|
||||
- **`MeshRef.SurfaceOverrides`** is the per-surface texture-swap data
|
||||
carried by spawned entities. `GfxObjSubMesh.SurfaceId` is what gets
|
||||
swapped. Task 22's draw loop must consult both: the entity's
|
||||
`MeshRef.SurfaceOverrides` for explicit swaps, and otherwise the
|
||||
mesh's built-in `SurfaceId`.
|
||||
|
||||
- **Conformance tests catch divergences early.** Per N.1's rotation
|
||||
bug: write the conformance test BEFORE the substitution. The
|
||||
matrix-composition test (`(entityWorld) × (animation) × (restPose)`)
|
||||
is the load-bearing one for Task 22 — pin it before integrating.
|
||||
|
||||
- **`WbMeshAdapter.Tick()` is required.** It's already wired into
|
||||
`GameWindow.OnRender`. Task 22's dispatcher needs the upload queue
|
||||
drained BEFORE it tries to draw, so order in OnRender is:
|
||||
`_wbMeshAdapter?.Tick()` → `_wbDrawDispatcher?.Draw(...)` → other
|
||||
draw work.
|
||||
|
||||
- **Name retail decomp first; Phase N.4 doesn't change that rule.**
|
||||
Task 22's matrix composition uses standard graphics math — no AC-
|
||||
specific algorithms — so the "grep `named-retail/` first" workflow
|
||||
doesn't apply to the matrix code itself. But for any AC-specific
|
||||
question that surfaces during integration (e.g., "does retail
|
||||
render hidden parts as zero-alpha or skip them entirely?"), grep
|
||||
`docs/research/named-retail/acclient_2013_pseudo_c.txt` first.
|
||||
|
||||
## Acceptance criteria for Week 4
|
||||
|
||||
From the plan:
|
||||
|
||||
- [ ] All conformance tests pass (Tasks 3, 4, 20 — already shipped;
|
||||
verify still green after Task 22 lands).
|
||||
- [ ] All component micro-tests pass (Tasks 11, 17, 18, 19, 22 —
|
||||
Task 22 adds matrix-composition tests).
|
||||
- [ ] All existing tests still pass. 8 pre-existing failures don't
|
||||
count.
|
||||
- [ ] Build green throughout.
|
||||
- [ ] Visual verification at 5 named locations passes:
|
||||
1. Holtburg outdoor — terrain props, scenery, buildings, NPCs,
|
||||
characters all render correctly.
|
||||
2. Drudge Hideout (or comparable) — EnvCell + interior lighting +
|
||||
animated creatures.
|
||||
3. Foundry — heavy NPC traffic + customized appearances.
|
||||
4. A character with extreme palette overrides.
|
||||
5. Long roam (5+ minutes) — GPU memory stabilizes (LRU eviction
|
||||
fires).
|
||||
- [ ] Memory budget enforcement actually verified (Task 13 was
|
||||
deferred to here; Task 22 makes it testable because GL resources
|
||||
finally get allocated for LRU to evict).
|
||||
- [ ] Sky pass renders identically (load-bearing — sky's
|
||||
`Translucent+ClipMap` cloud sheet, raw-`Additive` fog skip,
|
||||
`Luminosity` keyframe handling all flow through the side-table
|
||||
via `AcSurfaceMetadata`).
|
||||
- [ ] Flag flipped to default-on at the end (Task 26).
|
||||
- [ ] Legacy code paths deleted (Task 27).
|
||||
- [ ] Roadmap + memory + ISSUES updated (Task 28).
|
||||
|
||||
## Tasks 22-28 — quick map
|
||||
|
||||
Full detail is in the plan. Brief here:
|
||||
|
||||
- **22 — `WbDrawDispatcher` full draw loop.** ~1-2 days. Atlas-tier
|
||||
+ per-instance-tier draw with matrix composition. Reads from
|
||||
`WbMeshAdapter.GetRenderData(id)` for atlas content; reads from
|
||||
`EntitySpawnAdapter.GetState(serverGuid)` for per-instance state;
|
||||
composes per-part `(entity × animation × rest-pose)` matrices;
|
||||
pushes uniforms; issues GL draws. **Also wires the legacy-
|
||||
renderer short-circuit** for atlas-tier content (the Adjustment 3
|
||||
fix).
|
||||
- **23 — Surface-metadata side-table population.** ~half day. Hook
|
||||
into `WbMeshAdapter.IncrementRefCount` so that on first registration
|
||||
of a GfxObj, the side-table gets populated with one
|
||||
`AcSurfaceMetadata` per surfaceIdx (using `GfxObjMesh.Build`'s
|
||||
metadata as the source of truth).
|
||||
- **24 — Sky-pass preservation check.** ~half day. Verify the sky
|
||||
pass's `NeedsUvRepeat` / `DisableFog` / `Luminosity` flow through
|
||||
the side-table to `SkyRenderer` correctly. Likely no code change;
|
||||
smoke-test sky rendering with flag on, weather/day-night cycle.
|
||||
- **25 — Component micro-tests round-out.** Audit existing tests
|
||||
against the spec's Testing section. Probably nothing to add since
|
||||
Tasks 11/17/18/19/22 already cover the listed micro-tests.
|
||||
- **26 — Visual verification + flag default-on.** Human-in-the-loop
|
||||
walk through the 5 named locations. If clean, flip
|
||||
`WbFoundationFlag.IsEnabled` from `== "1"` to `!= "0"` so flag-on
|
||||
becomes the default.
|
||||
- **27 — Delete legacy code paths.** Remove the now-unused legacy
|
||||
upload code in `StaticMeshRenderer` + `InstancedMeshRenderer`.
|
||||
N.6 fully replaces these files anyway.
|
||||
- **28 — Update roadmap + memory + ISSUES + finalize plan.** Mark
|
||||
N.4 shipped in the roadmap's Live ✓ table. File any cosmetic
|
||||
deltas as ISSUES. Add a memory note if a durable lesson emerged.
|
||||
Flip the plan's status header from "Living document — work in
|
||||
progress" to "Final state — phase shipped (merge `<sha>`)".
|
||||
|
||||
## Where to start
|
||||
|
||||
1. **Read the three "Read first" docs above end-to-end.** Especially
|
||||
the Adjustments section in the plan — those are the architectural
|
||||
constraints Task 22 must respect.
|
||||
2. **Decide Adjustment 4 plumbing** (option A vs B from above). This
|
||||
is a small brainstorm checkpoint, not a multi-question
|
||||
`superpowers:brainstorming` skill invocation. Document the choice
|
||||
inline in the plan as Adjustment 6.
|
||||
3. **Don't create a new worktree.** The existing branch
|
||||
`claude/quirky-jepsen-fd60f1` and worktree
|
||||
`.claude/worktrees/quirky-jepsen-fd60f1` are clean and ready.
|
||||
Submodule already initialized. Build green.
|
||||
4. **Use `superpowers:subagent-driven-development`** to execute Week 4
|
||||
task-by-task. Pattern from Weeks 1-3: dispatch one subagent per
|
||||
task (or batch of related tasks), use Sonnet for implementation,
|
||||
merge to main per logical chunk, update the plan's Progress table
|
||||
as commits land.
|
||||
5. **Pause for visual verification at Task 26.** This is a human-in-
|
||||
the-loop step — needs you to walk the 5 named locations.
|
||||
|
||||
## Open questions a fresh agent might hit
|
||||
|
||||
- **Q: Why did Adjustment 5 mark Task 20 (per-instance decode
|
||||
conformance) as "structural"?** Because both old and new paths call
|
||||
the same `TextureCache.GetOrUploadWithPaletteOverride` function. We
|
||||
preserved the decode logic exactly; the seam is at the call site,
|
||||
not at the algorithm. Byte-equality is automatic.
|
||||
|
||||
- **Q: Can I delete `InstancedMeshRenderer`?** Not in N.4. The plan
|
||||
marks it as "becomes a thin adapter in N.4, fully replaced in N.6."
|
||||
Task 27 deletes the legacy upload paths inside it but keeps the
|
||||
file as a draw-orchestration adapter until N.6.
|
||||
|
||||
- **Q: What's the memory budget check actually checking?** GPU memory
|
||||
stabilizes during long roam. WB's `_maxGpuMemory = 1 GB` triggers
|
||||
LRU eviction once the cache exceeds that. We verify by walking
|
||||
for 5+ minutes at radius 7 (49 landblocks visible at any time) and
|
||||
confirming GPU memory in the title bar plateaus rather than
|
||||
growing unboundedly.
|
||||
|
||||
- **Q: What happens if Task 22 takes longer than expected?** The
|
||||
living-document convention says document Adjustments inline. If
|
||||
Task 22 needs to split (e.g., atlas-tier draw lands first, per-
|
||||
instance tier in a follow-on commit), that's fine — just update
|
||||
the Progress table and add an Adjustment explaining the split.
|
||||
|
||||
## Useful greps and commands
|
||||
|
||||
- `dotnet build --verbosity quiet 2>&1 | tail -3` — quick build check.
|
||||
- `dotnet test --verbosity quiet 2>&1 | tail -3` — full test suite.
|
||||
- `git -C C:\Users\erikn\source\repos\acdream log --oneline -10` —
|
||||
recent main commits.
|
||||
- `grep -rn "WbFoundationFlag.IsEnabled" src/` — every place we gate
|
||||
on the flag (audit before flipping default-on in Task 26).
|
||||
- `grep -rn "_wbMeshAdapter\|_wbSpawnAdapter\|_wbEntitySpawnAdapter" src/` —
|
||||
every WB adapter wiring point.
|
||||
|
||||
## Smoke-test launch (PowerShell)
|
||||
|
||||
```powershell
|
||||
# Kill any stale processes first
|
||||
Get-Process -Name AcDream.App -ErrorAction SilentlyContinue | Stop-Process -Force
|
||||
Start-Sleep -Seconds 4
|
||||
|
||||
# Flag-on at radius 7 — Week 4 dev environment
|
||||
$env:ACDREAM_DAT_DIR = "$env:USERPROFILE\Documents\Asheron's Call"
|
||||
$env:ACDREAM_LIVE = "1"
|
||||
$env:ACDREAM_TEST_HOST = "127.0.0.1"
|
||||
$env:ACDREAM_TEST_PORT = "9000"
|
||||
$env:ACDREAM_TEST_USER = "testaccount"
|
||||
$env:ACDREAM_TEST_PASS = "testpassword"
|
||||
$env:ACDREAM_USE_WB_FOUNDATION = "1"
|
||||
$env:ACDREAM_STREAM_RADIUS = "7"
|
||||
dotnet run --project src\AcDream.App\AcDream.App.csproj --no-build -c Debug 2>&1 |
|
||||
Tee-Object -FilePath "n4-week4-smoke.log"
|
||||
```
|
||||
|
||||
(Drop the `ACDREAM_USE_WB_FOUNDATION` line for flag-off comparison.)
|
||||
|
||||
## Adjustments index — quick reference
|
||||
|
||||
For full text, see the plan document (each is a `### Adjustment N`
|
||||
subsection under Task 6's old position, in chronological order):
|
||||
|
||||
1. **`DefaultDatReaderWriter` discovery** (2026-05-08) — no
|
||||
dat-reader bridge needed; WB ships a usable concrete
|
||||
implementation.
|
||||
2. **Renderer is tier-blind** (2026-05-08) — routing belongs at
|
||||
spawn callbacks, not in the renderer.
|
||||
3. **FPS regression = dual-pipeline cost** (2026-05-08) — both
|
||||
pipelines run in parallel until Task 22's short-circuit lands.
|
||||
4. **`WorldEntity` lacks HiddenParts/AnimPartChange fields**
|
||||
(2026-05-08) — plumbing deferred; Task 22 needs to resolve
|
||||
(option A: add fields; option B: thread as separate args).
|
||||
5. **Task 20 is structural** (2026-05-08) — same function called
|
||||
both paths, byte-equality automatic, no test file needed.
|
||||
Loading…
Add table
Add a link
Reference in a new issue