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