Reframe the selection-blink follow-up so it doesn't suggest near-term
work. Was listed in N.5 ship record as "Phase B.4 follow-up adds the
field" — now phrased as open backlog with the hook reserved in
mesh_modern.vert's InstanceData comment for whoever eventually picks
it up.
The shader hook itself is unchanged — change is purely doc wording in
the plan SHIP record + CLAUDE.md WB integration cribs.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Final cross-cutting review of N.5 found that Task 15's deletion of
mesh_instanced.vert/.frag left InstancedMeshRenderer orphaned —
ACDREAM_USE_WB_FOUNDATION=0 silently rendered terrain+sky only with
no entities. The SHIP commit's "[x] ACDREAM_USE_WB_FOUNDATION=0 still
works" claim was inaccurate.
Resolution: formal retirement of the legacy renderer path within N.5
instead of deferring to N.6.
Deleted:
- src/AcDream.App/Rendering/InstancedMeshRenderer.cs
- src/AcDream.App/Rendering/StaticMeshRenderer.cs
- src/AcDream.App/Rendering/Wb/WbFoundationFlag.cs
GameWindow simplified — capability detection is unconditional, missing
bindless throws NotSupportedException with a clear message at startup.
WbDrawDispatcher + mesh_modern shader load are mandatory after init.
No escape hatch.
GpuWorldState simplified — WbFoundationFlag.IsEnabled guards on
AddLandblock/RemoveLandblock removed; adapter calls are unconditional
when the adapter is non-null.
PendingSpawnIntegrationTests updated — WbFoundationFlag.ForTestsOnly_ForceEnable
static ctor removed (flag is gone; adapter calls are unconditional).
The ApplyLoadedTerrain physics-data loop was also simplified: the
EnsureUploaded sub-loop that fed InstancedMeshRenderer is gone;
_pendingCellMeshes is now explicitly cleared to prevent unbounded
accumulation (the worker thread still populates it, but WB handles
EnvCell geometry through its own pipeline).
Spec §2 Decision 5 + §10 Out-of-Scope updated. Plan ship-amendment
section added. Roadmap updated (N.5 ships with retirement; N.6 scope
narrowed to perf-only). CLAUDE.md "WB integration cribs" updated.
Perf baseline doc updated. WbDrawDispatcher class summary docstring
corrected to describe the as-shipped SSBO + multi-draw-indirect path.
ISSUES.md #51 updated (terrain not in N.5 scope; deferred to N.7).
Bindless support is now a hard requirement. Modern desktop GPUs
universally expose GL_ARB_bindless_texture + GL_ARB_shader_draw_parameters;
if a user hits the NotSupportedException, that's a real bug report
worth investigating, not a silent fallback.
Build: 0 errors, 0 warnings. Tests: 71/71 (Wb+MatrixComposition+TextureCacheBindless filter).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Records the as-shipped state: acceptance gate verdicts, plan amendments
captured during execution, code-review adjustments per task, out-of-scope
N.6 follow-ups, and a complete files-changed summary.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Code quality review caught:
- sizeofDEIC was a local; promoted to public const DrawCommandStride
so tests can reference it symbolically.
- BatchDataPublic layout invariant (size + field offsets) wasn't
asserted in tests. Added BatchDataPublic_LayoutMatchesPrivateBatchData
+ DrawCommandStride_MatchesStructSize tests to gate Task 10's
MemoryMarshal.Cast<BatchData, BatchDataPublic> safety.
- Plan doc updated: BatchDataPublic spec was Pack=4 (wrong — must
match private BatchData's Pack=8 for the cast to work). Implementation
was already correct; plan now matches.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Original Task 5 draft used hardcoded vec3 ambient/sun uniforms in
mesh_modern.frag. Reading actual mesh_instanced.frag revealed it uses
a SceneLighting UBO at binding=1 with 8 lights, fog params (start/end/
lightning/mode), fog color, camera/time, and per-channel clamp.
Revised: mesh_modern.frag preserves the full SceneLighting UBO +
accumulateLights + applyFog + lightning flash + per-channel clamp.
mesh_modern.vert adds vWorldPos output (consumed by accumulateLights
and applyFog). Visual identity to N.4's lighting model preserved.
Two-pass alpha-test (N.5 Decision 2) sits inside the same shader,
gated by uRenderPass instead of uTranslucencyKind.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Original Task 3 had Bindless* methods calling the legacy Texture2D
GetOrUpload* and converting the GL name to a bindless handle —
producing a sampler2D texture sampled via sampler2DArray (GLSL type
mismatch).
Revised: Task 3 introduces three parallel cache dictionaries
(_bindlessBySurfaceId / _bindlessByOverridden / _bindlessByPalette)
storing both the GL texture name and the resident handle. Bindless*
methods call DecodeFromDats + UploadRgba8AsLayer1Array directly with
their own caching; legacy three-cache structure mirrored exactly.
Task 4 (Dispose) updated to: (1) MakeNonResident on every bindless
handle FIRST, (2) DeleteTexture on every Texture2DArray name, (3)
DeleteTexture on every legacy Texture2D handle. Order matters per
ARB_bindless_texture spec.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Implementer caught that the original Task 2 (replace UploadRgba8 target
with Texture2DArray) would break four legacy consumers whose shaders
sample via sampler2D: WbDrawDispatcher (pre-rewrite path),
StaticMeshRenderer, InstancedMeshRenderer (legacy escape hatch),
ParticleRenderer.
Revised: Task 2 ADDS a parallel UploadRgba8AsLayer1Array. Existing
UploadRgba8 (Texture2D) stays for legacy callers. Task 3's Bindless*
methods will call the new array path with their own cache dictionaries.
Same surface may be uploaded twice during transition; bounded cost.
N.6 cleanup deletes the legacy path.
Task 3 will be amended at dispatch time to reflect parallel caches.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The TextureCacheBindlessTests.cs file is created in Task 3 (where it
gets meaningful test cases), not Task 1. Removed it from Task 1's
Files list and added an explicit note. Caught during Task 1 code review.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Phase N.4 (Rendering Pipeline Foundation) ships. WbFoundationFlag
flips to default-on (== "1" → != "0"). WB's ObjectMeshManager is
now acdream's production mesh pipeline; WbDrawDispatcher is the
production draw path. Legacy InstancedMeshRenderer is retained as
ACDREAM_USE_WB_FOUNDATION=0 escape hatch until N.6 retires it.
Visual verification at Holtburg passed:
- Scenery (trees / rocks / fences / buildings) renders correctly
- Characters connected with full close-detail geometry (Issue #47
preserved — GfxObjDegradeResolver path intact)
- FPS substantially improved by grouped instanced draws + per-entity
AABB cull + opaque front-to-back sort + palette-hash memoization
Three high-value WB API gotchas surfaced during Task 26 visual
verification and are now documented in CLAUDE.md "WB integration
cribs" + plan Adjustments 7-9 + memory project_phase_n4_state.md:
1. ObjectMeshManager.IncrementRefCount only bumps a counter — does
NOT trigger mesh loading. Call PrepareMeshDataAsync explicitly.
2. ObjectRenderBatch.SurfaceId is unset — read batch.Key.SurfaceId.
3. Modern rendering (GL 4.3 + bindless = every modern GPU) packs
every mesh into ONE global VAO/VBO/IBO. Use
glDrawElementsInstancedBaseVertex(BaseInstance) with FirstIndex +
BaseVertex from the batch, not naive DrawElementsInstanced.
Plan doc flipped to Final state. Roadmap N.4 → Live ✓; N.5 rebranded
from "Terrain rendering" to "Modern rendering path" (bindless +
multi-draw indirect on top of N.4's foundation; terrain rendering
moves to N.5b). CLAUDE.md "Currently in flight" pointer updated to
N.5. New memory file project_phase_n4_state.md preserves the three
WB gotchas for cross-session continuity.
n4-verify*.log added to .gitignore.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Resolves Adjustment 4 (Option A): WorldEntity now carries the server-
sent AnimPartChange data as PartOverrides and a HiddenPartsMask bitmask.
EntitySpawnAdapter.OnCreate populates AnimatedEntityState from these
fields at spawn time. GameWindow's CreateObject handler converts the
network-layer AnimPartChange records into lightweight PartOverride
structs.
This unblocks Task 22: the WbDrawDispatcher can now resolve per-part
GfxObj overrides and hidden-part suppression from entity state.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Week 3 ships: AnimatedEntityState (Tasks 16+18+19, commit ce72c57),
EntitySpawnAdapter routing server-spawned content through the existing
TextureCache.GetOrUploadWithPaletteOverride path (Task 17, commit
c02c307). 947 tests pass.
Adjustment 4: WorldEntity lacks HiddenPartsMask + AnimPartChanges
fields. Adapter scaffolding ships; AnimatedEntityState gets default
values (empty mask + empty override map). Plumbing deferred to Task 22
brainstorm — either add fields to WorldEntity or thread through a
separate parameter to EntitySpawnAdapter.OnCreate.
Adjustment 5: Task 20 (per-instance decode conformance) is structural.
Both old and new paths call the same TextureCache function — bytes
identical by construction. EntitySpawnAdapterTests already cover the
routing. No separate conformance test file needed.
Next: Task 22 (Week 4) — WbDrawDispatcher full draw loop. First task
that actually draws through WB and unlocks Adjustment 3's mitigation
(dual-pipeline cost resolves when legacy renderer can short-circuit
its upload for atlas-tier content).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Week 2 ships: LandblockSpawnAdapter routes atlas-tier GfxObjs to WB
ref counts (Task 11/12), pending-spawn list integration verified
(Task 14), WbMeshAdapter.Tick drains the pipeline queues per frame
(added per Adjustment 3, fixes a real memory leak).
Task 13 (memory budget verification) is deferred: stress-test
revealed the FPS drop with flag-on isn't the queue leak we thought
— it's the dual-pipeline cost (background workers + duplicate GL
upload + duplicate I/O + legacy renderer still doing the same atlas
work). The savings only materialize in Task 22 when the dispatcher
short-circuits the legacy upload for atlas-tier content. Plan
Adjustment 3 documents this; no fix needed before Week 4 since
default-off is byte-identical to pre-N.4.
Next: Task 16 (Week 3) — AnimatedEntityState + per-instance path.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Smoke test flag-on showed characters/NPCs disappearing along with
static scenery. Root cause: Task 9 routed all
InstancedMeshRenderer.EnsureUploaded calls through WB. But that
renderer is used for BOTH tiers in production — character per-part
spawn (line 2302, per-instance) AND streaming-loader spawns (lines
5137 + 5155, atlas).
The renderer is tier-blind by design. Tier-routing belongs at the
spawn-callback layer per the spec's data-flow section:
- LandblockSpawnAdapter (Task 11) calls IncrementRefCount per
unique GfxObj — atlas-tier only.
- EntitySpawnAdapter (Task 17) routes through per-instance path
via TextureCache.GetOrUploadWithPaletteOverride.
This commit removes the sentinel pattern + 4 sentinel-skip checks
from InstancedMeshRenderer. Kept the _wbMeshAdapter constructor
parameter (unused for now) so GameWindow's wire-up doesn't shift.
Kept all the real WB pipeline construction in WbMeshAdapter
(it's the substrate routing will use in Week 2).
Verified flag-on === flag-off post-revert.
Plan updated with Adjustment 2 explaining the discovery + correct
architectural placement for routing.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Task 6 (dat-reader bridge) obsoleted: WB ships DefaultDatReaderWriter
which takes a dat-directory path and constructs all four databases
(Portal/HighRes/Language + CellRegions) internally. We can use it
directly instead of bridging our DatCollection. Adjustment 1 noted
in the plan; full bring-up deferred to Task 9.
Task 7: GameWindow constructs WbMeshAdapter when
ACDREAM_USE_WB_FOUNDATION=1 is set; pairs with Dispose. Field is
null when flag is off, so no behavioral effect on default-off path.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
28-task plan covering 4 weeks of work organized as:
- Week 1 (Tasks 1-10): WB plumbing + atlas for static scenery + conformance
- Week 2 (Tasks 11-15): streaming integration + memory budget verification
- Week 3 (Tasks 16-21): per-instance customization + animation
- Week 4 (Tasks 22-28): full draw dispatcher + visual verification + ship
Living document — task checkboxes marked as commits land; adjustments
appended in-place rather than rewriting earlier tasks. Conformance
tests run before substitution per N.1/N.3 pattern. Behind
ACDREAM_USE_WB_FOUNDATION=1 feature flag during weeks 1-3.
CLAUDE.md updated with a "Currently in flight" pointer in the Roadmap
discipline section so future agents pick up the plan as authoritative
for rendering work.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Roadmap: N.3 row added to shipped table; sub-phase block updated from
ahead-estimate to shipped summary. Document header date bumped.
Plan: docs/superpowers/plans/2026-05-08-phase-n3-texture-decode-via-wb.md
captures the audit + per-format substitution strategy + A8 isAdditive
divergence resolution that drove this phase.
No ISSUES.md update — visual verification at Holtburg is the remaining
gate; if the A8 non-additive change produces a visible delta on entity
textures, an issue gets filed there.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
5-task TDD plan: TerrainSurface (outdoor heightmap Z + cell ID),
CellSurface (indoor floor polygon projection via barycentric interp),
PhysicsEngine (top-level resolver with step-height + cell transitions),
GameWindow integration (populate from streaming), and roadmap update.
~16 new unit tests with fake data. No dat files or rendering needed.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Review follow-up from commit 11df793. Three fixes:
1. Visible semantics: StreamingRegion.Visible now strictly describes the
current (2r+1)×(2r+1) window, not window + hysteresis retainees.
Added a parallel Resident property exposing the actual loaded set
(window + hysteresis buffer). This matters because StreamingController
(next task) reads these to decide what to render vs what to unload;
conflating them in one set would have forced awkward post-processing
downstream.
2. Doc/code disagreement: updated the RecenterTo and RegionDiff doc
comments from "radius + 1" to "radius + 2" to match the actual
implementation (which is what the tests require). Also updated the
plan doc so future readers don't hit the same contradiction.
3. Edge-clamping test coverage: added a single-axis edge test
(cx=0, cy=50 → 15 entries) and an ID-encoding test (radius=0 at
0x12,0x34 → 0x1234FFFE) so a swapped-shift bug in EncodeLandblockId
or an asymmetric off-by-one would fail a test instead of passing
silently.
9 tests green, full suite regressions-free.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
9-task TDD plan for the first chunk of Phase A (Foundation): the
streaming landblock loader. Covers StreamingRegion (pure data +
hysteresis diff), LandblockStreamer (background worker + channels),
GpuWorldState (render-thread entity registry), StreamingController
(glue), TerrainRenderer.RemoveLandblock, GameWindow wiring (env var,
camera/player center switch, Dispose), scenery + interior integration,
and the roadmap-shipped-table update.
Each task is a full TDD cycle: failing test, run, minimal impl, run,
commit. ~16 new unit tests land alongside the implementation.
Phase A.2 (frustum culling) and A.3 (net I/O thread) get their own
plans once A.1 ships — they're independent subsystems per the
brainstorming skill's decomposition guidance.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>