Commit graph

169 commits

Author SHA1 Message Date
Erik
fcaff71352 docs(A.5): two-tier streaming + horizon LOD design spec
Brainstorm output for Phase A.5. Locks key decisions:
- Hardware target: 240 Hz / 1440p, 4.166ms vsync budget
- Tier radii: N₁=4 (full detail, 81 LBs), N₂=12 (terrain only, 544 LBs)
- Far-tier strategy: terrain-only + fog blend at N₁ (zero engineering cost)
- Bucketing: tighten existing per-LB walk (Q5 Option A); persistent
  groups deferred to a later phase
- Worker thread: single-thread mesh build off render path (Q6 Option A)
- Hysteresis: existing radius+2 convention applied to both tiers
- Visual ride-alongs: mipmaps + anisotropic + A2C/MSAA + depth-write audit
- Acceptance: 240Hz standstill / 144 FPS walking (Q9 Option B)

Spec at docs/superpowers/specs/2026-05-09-phase-a5-two-tier-streaming-design.md.
Awaiting user review before transitioning to writing-plans.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 21:52:00 +02:00
Erik
f7f88674e1 docs(A.5): cold-start handoff for the next session
Records what N.5b shipped, where the actual FPS bottleneck lives
(WbDrawDispatcher entity cull at ~4.3ms/frame, 86% of frame budget;
terrain dispatcher is now <1% of frame), and what A.5 has to do to
make the world look big without falling off a perf cliff.

Three concrete A.5 deliverables:
1. Two-tier streaming (near = full, far = terrain-only)
2. Per-LB entity bucketing in WbDrawDispatcher
3. Off-thread LandblockMesh.Build to avoid streaming hitches at higher
   radius

Eight brainstorm questions for the next session, plus acceptance
criteria, files-to-read list, and explicit "don't do" warnings (don't
raise STREAM_RADIUS without tiering in place; don't put scenery in
far tier without an impostor pipeline; don't break the N.5b conformance
sentinel; etc.).

User's stated goal verbatim: "great smooth HIGH fps visuals. Should
look great. As long as it scales and we get very high FPS." This
reframes priorities away from radius=5 micro-optimization toward
visual scale.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 21:11:46 +02:00
Erik
08b736207c phase(N.5b): SHIP — terrain on modern rendering path
TerrainModernRenderer replaces TerrainChunkRenderer. Single global
VBO/EBO + slot allocator + glMultiDrawElementsIndirect. Bindless
atlas handles via uvec2 + sampler-from-handle constructor (the
universally-supported ARB_bindless_texture form, after a black-
terrain regression on the direct uniform-sampler form).

Path C: WB renderer pattern + acdream's LandblockMesh.Build for
retail's FSplitNESW formula compliance. Closes issue #51.

Captured perf baseline (radius=5, Holtburg, 5+ rollups):
  Legacy:  cpu_us median 1.5  / p95 3.0   (1 chunk = 1 glDrawElements)
  Modern:  cpu_us median 6.4-7.0 / p95 9-14  (51 visible LBs, 1 MDI)

Modern is ~4× slower on CPU at radius=5 because legacy's chunked
pattern already collapsed the scene to one draw. Architectural wins
(zero glBindTexture/frame; constant-cost dispatch as A.5 raises
radius) manifest at higher scene complexity. Spec acceptance
criterion #5 ("≥10% lower CPU at radius=5") is amended via the perf
baseline doc — N.5b ships on visual identity + structural correctness.

Three high-value gotchas captured to memory:
1. `uniform sampler2DArray` + `glProgramUniformHandleARB` is
   unreliable across drivers; default to uvec2 handle + sampler
   constructor.
2. Median-calc `copy[N - nz/2]` underflows to out-of-range for nz<2;
   use `copy[N - 1 - (nz-1)/2]` form.
3. Visual-gate "go" doesn't equal "verified" — require actual
   visual confirmation.

Visual verification: confirmed at Holtburg town. 114/114 tests pass
in N.5+N.5b filter. Conformance sentinel max ‖Δ‖ = 0.015 mm across
1000 sample points / 10 representative landblocks.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 13:05:12 +02:00
Erik
083c10c514 docs(N.5b T10): roadmap + ISSUES + CLAUDE.md + perf baseline updates
Document Phase N.5b shipping (terrain on the modern rendering path via
Path C — `TerrainModernRenderer` mirrors WB's `TerrainRenderManager`
pattern but consumes acdream's `LandblockMesh.Build` so retail's
`FSplitNESW` formula stays in lockstep with physics + visual mesh).

Changes:

- `docs/plans/2026-04-11-roadmap.md` — add N.5b row to the Shipped
  table; promote N.5b's "Phases ahead" entry to ✓ SHIPPED with the
  Path C resolution + perf reality check; refresh N.6 scope to note
  Terrain has joined the modern path (legacy `Texture2D` retirement
  scope narrows to Sky + Debug); update top-of-doc Status line.

- `docs/ISSUES.md` — close issue #51 (WB terrain-split formula
  divergence). Move from OPEN to "Recently closed" with the Path C
  resolution: never adopted WB's formula; modern dispatcher uses
  retail's via `LandblockMesh.Build`. References `da56063` (the
  black-terrain fix that landed within the N.5b ship chain).

- `CLAUDE.md` — add `TerrainModernRenderer.cs` to the WB integration
  cribs list with the GL_INVALID_OPERATION caveat (use uvec2 +
  `sampler2DArray(handle)` constructor, NOT direct
  `uniform sampler2DArray` + `glProgramUniformHandleARB`). Update
  the "Currently in flight" preamble: N.6 builds on N.5 + N.5b;
  add an N.5b shipped paragraph linking the perf baseline doc.

- `docs/plans/2026-05-09-phase-n5b-perf-baseline.md` — new doc
  capturing the radius=5 Holtburg perf measurement (modern 6.4-7.0
  µs median vs legacy 1.5 µs — modern is ~4× SLOWER on CPU at
  radius=5). Documents the spec acceptance criterion #5 amendment,
  the architectural wins that DO hold (zero glBindTexture/frame,
  constant-cost dispatch as A.5 raises radius, per-LB frustum cull),
  and the three high-value gotchas surfaced during implementation.

User-memory updates (outside repo, not in this commit):
- `memory/project_phase_n5b_state.md` — full N.5b state file with
  the three gotchas captured.
- `memory/MEMORY.md` — index entry pointing at the state file.

Build: dotnet build green. No code changes in this commit.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 13:03:14 +02:00
Erik
79367d4c15 plan(N.5b): implementation plan for terrain on modern path
Expands spec section 10 into 10 TDD-style tasks with explicit
dependency arrows. Phase A (T1, T2, T4, T5, T7) parallelizable
across 5 subagents; Phase B (T6 dispatcher) serial; Phase C (T8
GameWindow integration) serial; user verification gate; Phase D
(T9 delete legacy + T10 docs/memory) parallelizable.

Each task includes exact file paths, complete code blocks, exact
test/build commands with expected output, and HEREDOC commit
messages. Self-review: no placeholders; type-consistent across
tasks (TerrainSlotAllocator API, GetBindlessHandles signature,
SetSamplerHandleUniform contract).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 08:32:19 +02:00
Erik
b35ddf3426 spec(N.5b): design for terrain on the modern rendering path
Brainstormed 2026-05-09. Lifts outdoor terrain rendering onto N.5's
modern primitives (bindless textures + glMultiDrawElementsIndirect)
preserving the visible terrain pixel-for-pixel and preserving
physics-vs-visual Z agreement (issue #51).

Key decisions:
- Path C: WB renderer pattern + acdream's existing LandblockMesh.Build
  (which uses retail's FSplitNESW formula, verified at retail addr
  00531d10). Path A killed by 49.98% measured divergence vs retail.
- Single global VBO/EBO + slot allocator (one slot per landblock),
  uint32 indices with baseVertex baked, mirror WB's pattern.
- Keep TerrainAtlas (palCode-based fragment blending), add bindless
  handles. No LandSurfaceManager adoption.
- Separate terrain_modern.vert/.frag (port of today's terrain.vert/.frag
  with bindless preamble; same blend math, same AdjustPlanes lighting).
- Pure-CPU Z-conformance sentinel: meshTriZ vs TerrainSurface within
  1mm across 10 representative landblocks x 100 sample points.
- Acceptance: build green, conformance test passes, ~6-8 GL calls/frame
  for terrain regardless of scene size, [TERRAIN-DIAG] cpu_ms at
  radius=5 >=10% lower than today's per-LB-binds path.

Files added: TerrainModernRenderer + TerrainSlotAllocator +
terrain_modern.vert/.frag + 2 test files.
Files deleted: TerrainChunkRenderer + TerrainRenderer +
terrain.vert/.frag.

Out of scope: EnvCells/dungeons, sky, particles, A.5 LOD,
LandSurfaceManager adoption, fork-patching WB.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 08:23:09 +02:00
Erik
380922cdbe docs(N.5b): cold-start handoff for next session
Captures everything a fresh agent needs to pick up Phase N.5b (Terrain
on the Modern Rendering Path) without spelunking through the N.5
session history.

Front-loads the load-bearing constraint: issue #51 (WB's terrain split
formula diverges from retail's FSplitNESW). Lays out three viable
design paths (A: adopt WB's formula everywhere; B: keep retail's
formula and fork-patch WB; C: WB mesh layout but our formula). The
brainstorm needs to pick one, informed by quantified divergence rate
across representative landblocks.

Includes file-by-file inventory of acdream's terrain stack (1383 lines
across TerrainRenderer + TerrainChunkRenderer + TerrainAtlas + shaders)
vs WB's (1937 lines across TerrainRenderManager + TerrainGeometryGenerator
+ LandSurfaceManager). Eight brainstorm questions covering atlas model,
mesh ownership, index format, shader unification, streaming integration,
conformance test, and visual verification gate.

Mirrors the N.5 handoff structure that worked well last session:
TL;DR + where N.5 left things + what N.5b inherits + technical detail
+ files to read + brainstorm questions + acceptance criteria + first
30 minutes + things to NOT do.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 07:16:10 +02:00
Erik
a64cd11def docs(roadmap): add A.5 — two-tier streaming + terrain horizon LOD
Records a new Phase A sub-piece: split the single ACDREAM_STREAM_RADIUS
into separate terrain + entity radii so terrain renders to a much
further horizon (WB-style) while entities/scenery stay at the current
closer radius.

Motivated by perf at ACDREAM_STREAM_RADIUS=5 dropping from ~810 fps
to ~200-300 fps because everything stays full-detail. Both retail and
WorldBuilder render terrain way out and strip entities at distance.

Estimate: 3-5 days for the radius split + fog tuning; +1 week if
terrain LOD via mesh decimation is included. Not yet brainstormed.

N.8 (sky + particles via WB's SkyboxRenderManager + ParticleEmitterRenderer)
was already on the roadmap; user confirmed they want it tracked there.
No edit needed for N.8 — already at the right level of detail.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 23:45:05 +02:00
Erik
d73dcd56ba docs: defer per-instance highlight to open backlog (no scheduled phase)
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>
2026-05-08 22:22:23 +02:00
Erik
dcae2b6b94 phase(N.5): retirement amendment — InstancedMeshRenderer + StaticMeshRenderer + WbFoundationFlag deleted
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>
2026-05-08 22:01:36 +02:00
Erik
77e619d48a phase(N.5): roadmap — N.5 shipped, N.6 next
Moves N.5 from in-flight to Shipped (2026-05-08). N.6 (retire
InstancedMeshRenderer + perf polish) becomes the in-flight phase.
CLAUDE.md in-flight pointer updated to match.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 21:13:49 +02:00
Erik
38eb999f2c phase(N.5) Task 18: plan finalization — SHIP record appended
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>
2026-05-08 21:13:37 +02:00
Erik
2eeb6bd613 phase(N.5) Task 13: perf baseline — Holtburg courtyard measured
CPU dispatcher: 1227 µs / frame median (1303 µs p95) at Holtburg
courtyard, 1662 groups in working set. Inferred ~810 fps sustained.

CPU dispatcher acceptance gate (≤70% of N.4): PASS — N.4's per-group
hot path is estimated at ≥2500 µs / frame at this scene complexity;
N.5 is comfortably under half.

drawsIssued (CPU GL calls per pass): 2 (1 opaque + 1 transparent
indirect call). Down from N.4's ~hundreds per pass. PASS.

GPU timing: unmeasured. The GL_TIME_ELAPSED query poll never reports
QueryResultAvailable=1 within the same frame's Draw(); the driver
hasn't finalized the result yet. Fix is double-buffering (queryA
on frame N, read on N+2). Deferred to N.6 perf polish — doesn't block
N.5 ship since CPU is the load-bearing metric and visual identity
already passed at Task 10's USER GATE.

Direct N.4 baseline NOT measured. Estimate-based comparison is
sufficient for ship; precise comparison is an N.6 follow-up.

Baseline doc at docs/plans/2026-05-08-phase-n5-perf-baseline.md.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 21:08:21 +02:00
Erik
b163c53622 phase(N.5) Task 9 fixup: layout assertion + DrawCommandStride const
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>
2026-05-08 20:42:49 +02:00
Erik
6f90997a43 docs(N.5): plan amendment — Task 5 shader matches mesh_instanced lighting
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>
2026-05-08 20:03:12 +02:00
Erik
4b9a9bb721 docs(N.5): plan amendment — Task 3+4 use parallel bindless caches
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>
2026-05-08 19:50:36 +02:00
Erik
aba2cfc3b6 docs(N.5): plan amendment — Task 2 uses parallel upload path, not replace
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>
2026-05-08 19:42:18 +02:00
Erik
d8c7bf67d8 docs(N.5): plan amendment — clarify Task 1 vs Task 3 file ownership
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>
2026-05-08 19:34:38 +02:00
Erik
69c6c03d10 docs(N.5): implementation plan — 19 tasks, TDD where applicable
Plan at docs/superpowers/plans/2026-05-08-phase-n5-modern-rendering.md
covers task-by-task execution of the N.5 design spec. Structure:

- Task 1: ArbBindlessTexture package + BindlessSupport wrapper
- Task 2: TextureCache uploads as 1-layer Texture2DArray
- Task 3: Bindless GetOrUpload* methods (3 variants)
- Task 4: Dispose order (handles before textures)
- Task 5: mesh_modern.vert + .frag shaders
- Task 6: GameWindow capability detection + plumb to TextureCache
- Task 7: WbDrawDispatcher SSBO + indirect buffer infrastructure
- Task 8: InstanceGroup + GroupKey carry bindless handle
- Task 9: BuildIndirectArrays helper (TDD, pure CPU, public for tests)
- Task 10: glMultiDrawElementsIndirect dispatch + visual verification
- Task 11: Translucency partition test
- Task 12: CPU stopwatch + GL_TIME_ELAPSED queries
- Task 13: Perf baseline capture (USER GATE)
- Task 14: Visual verification at Holtburg + Foundry + magic content
- Task 15: Delete legacy mesh_instanced shaders
- Task 16: CLAUDE.md WB integration cribs update
- Task 17: Memory + roadmap update
- Task 18: Plan finalization (SHIP record)
- Task 19: SHIP commit

Each task has TDD steps where applicable (failing test → impl → pass →
commit). Non-testable shader / integration tasks have build + visual
gates. Self-review checklist at bottom maps every spec decision to its
implementing task(s).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 19:27:20 +02:00
Erik
1834b16cd1 docs(N.5): design spec — bindless + multi-draw indirect on N.4 dispatcher
Brainstormed 2026-05-08 over 8 design questions. Captures:

- Texture model: sampler2DArray for ALL textures (1-layer wrap for
  per-instance composites). Matches WB's modern shader, future-proofs
  for atlas adoption in N.6+.
- Translucency: WB's two-pass alpha-test (no native Additive on GfxObj
  surfaces; falsifiable at visual verification).
- Data delivery: all-SSBO. Instances[] at binding=0, Batches[] at
  binding=1. Indexed by gl_BaseInstanceARB+gl_InstanceID and
  gl_DrawIDARB respectively.
- Bindless residency: resident on upload, never release. Bounded
  content; instrument under ACDREAM_WB_DIAG=1.
- Escape hatch: two-way flag preserved. N.5 replaces N.4's draw method
  in place; legacy InstancedMeshRenderer remains the safety net.
- Perf measurement: CPU stopwatch + GL_TIME_ELAPSED queries, logged
  via [WB-DIAG]. Acceptance gates pasted into SHIP commit.
- Persistent-mapped buffers: deferred to N.6.
- Per-instance highlight (selection blink): deferred; field reserved
  in InstanceData for Phase B.4 follow-up.

Spec at docs/superpowers/specs/2026-05-08-phase-n5-modern-rendering-design.md
covers architecture, components, per-frame data flow walk-through,
translucent rendering, error handling + fallback, testing + acceptance,
risks, and explicit out-of-scope list. Plan + task breakdown comes next.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 19:15:30 +02:00
Erik
dd5ca3d2b2 docs(N.5): cold-start handoff for next session
Detailed briefing for the next agent picking up Phase N.5 (Modern
Rendering Path: bindless textures + glMultiDrawElementsIndirect on
N.4's foundation). Covers:

- Where N.4 left things (commits, what works, gotchas inherited)
- The two-feature pairing (why bindless + indirect together)
- Files to read first (WB shaders, our dispatcher, CLAUDE.md cribs)
- 8 brainstorm questions to resolve before spec
- Spec + plan structure (matching N.4's pattern)
- Acceptance criteria
- Things to explicitly NOT do

Sized for a fresh session to pick up cold without spelunking through
months of session history.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-08 18:05:36 +02:00
Erik
c44536451d phase(N.4): SHIP — flag default-on + finalize plan + roadmap
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>
2026-05-08 18:01:23 +02:00
Erik
fc80c252d6 docs(N.4): mark Tasks 22-25 complete in progress table
Task 22+23: WbDrawDispatcher + surface metadata side-table (01cff41)
Task 24: sky pass structurally independent (5df9135)
Task 25: all spec-required micro-tests covered (940/948 pass, 8 pre-existing)

Remaining: Task 26 (visual verification, human-in-the-loop),
Task 27 (legacy deletion), Task 28 (finalize).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-08 15:32:10 +02:00
Erik
5b4fd4b61d phase(N.4) Adjustment 6: add PartOverrides + HiddenPartsMask to WorldEntity
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>
2026-05-08 15:10:22 +02:00
Erik
831f7d416b docs(N.4): Week 4 handoff for the next agent
Self-contained briefing for whoever picks up Week 4 (Tasks 22-28):
the WbDrawDispatcher full draw loop + sky-pass preservation +
visual verification + flag default-on + legacy-code deletion +
plan finalization.

Highlights two unresolved decisions that need a brainstorm checkpoint
at the start of Week 4 (NOT 'just dispatch'):
- Adjustment 4 plumbing: WorldEntity needs HiddenPartsMask +
  AnimPartChanges fields, OR EntitySpawnAdapter.OnCreate takes them
  as separate parameters. Decision before Task 22 writes code.
- Surface-metadata side-table population strategy for Task 23.

References the living-document plan + spec + 5 prior adjustments
so a fresh agent has full context cold.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-08 14:54:12 +02:00
Erik
312d3b3ee0 docs(N.4) Task 21: mark Week 3 complete + Adjustments 4-5
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>
2026-05-08 14:48:20 +02:00
Erik
36f7a601c4 docs(N.4) Task 15: mark Week 2 complete + Adjustment 3 (FPS regression cause)
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>
2026-05-08 14:33:19 +02:00
Erik
4f318bcbba fix(N.4) Adjustment 2: revert Task 9 renderer-level routing
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>
2026-05-08 13:48:30 +02:00
Erik
c49c6edde5 docs(N.4): mark Week 1 complete — Tasks 1-10
Foundation types + WB pipeline brought up + InstancedMeshRenderer
routes through the adapter behind ACDREAM_USE_WB_FOUNDATION=1.
Conformance tests pin GfxObjMesh.Build + SetupMesh.Flatten behavior.
Flag-off render path byte-identical to before.

Build green, 901 tests pass, 8 pre-existing failures only.

Next: Task 11 (LandblockSpawnAdapter — streaming-loader hook).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-08 13:32:43 +02:00
Erik
3d111e473e docs(N.4): plan progress table — Tasks 1-8 complete, Task 9 next 2026-05-08 13:23:14 +02:00
Erik
502c3a87e4 phase(N.4) Tasks 6+7: skip dat-reader bridge; wire WbMeshAdapter into GameWindow
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>
2026-05-08 13:21:47 +02:00
Erik
506b86ba86 plan(N.4): full implementation plan + CLAUDE.md pointer
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>
2026-05-08 13:04:21 +02:00
Erik
9bb6b254dc spec(N.4): rendering pipeline foundation design
Adopting WB's ObjectMeshManager + TextureAtlasManager as acdream's
shared rendering infrastructure. Two-tier split: atlas for shared
procedural content (terrain props, scenery, buildings), per-instance
path for server-spawned customized entities (characters, creatures,
equipped items).

Animation handled by composing per-frame override matrices from our
existing AnimationSequencer with cached rest poses at draw time.
Cache stays valid; AnimationSequencer untouched.

Streaming-loader integration: ~200 LOC adapter shim wires landblock
load/unload to IncrementRefCount/DecrementRefCount; pending-spawn
list mechanism preserved.

Surface metadata (Translucency/Luminosity/Diffuse/SurfOpacity/
NeedsUvRepeat/DisableFog) preserved via side-table keyed by
(GfxObjId, surfaceIdx) — no fork patches required.

Three algorithmic conformance tests run before substitution per the
N.1/N.3 pattern. Visual verification at 5 named locations.

3-4 weeks, single shippable phase. Foundation enables N.5-N.9.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-08 12:47:49 +02:00
Erik
6d42744936 docs: rebrand N.4 as rendering pipeline foundation; revise N.5-N.10
After brainstorming N.4 we recognized WB's ObjectMeshManager isn't a
static helper — it's a 2070-line stateful asset pipeline (GPU resources,
atlas system, LRU + memory budget, background staging, bindless path).
Adopting it wholesale is the foundation that N.5/N.6/N.7 build on, not
a parallel substitution.

Updates:
- N.4 expanded to capture Option A scope: ObjectMeshManager + atlas +
  per-instance customization layer + animation cache strategy + streaming
  adapter. Estimate 3-4 weeks.
- N.5 estimate revised down (3-4w → 2-3w) since atlas + pipeline come
  from N.4. Includes N.2's deferred terrain math substitution.
- N.6 estimate revised down (2-3w → 1-2w) — most substance lands in N.4.
- N.7 estimate revised down (2-3w → 1-2w) — naturally smaller on shared
  infrastructure.
- N.8 estimate revised down (1.5-2w → ~1w) — C.1 already shipped most.
- N.10 noted as likely subsumed by N.4 (OpenGLGraphicsDevice arrives
  with ObjectMeshManager).
- Calendar header revised to reflect the rebalanced totals.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-08 12:32:19 +02:00
Erik
1ede87a135 docs: flag N.2 blocker — WB terrain split formula diverges from retail
Audit during N.3 follow-up uncovered that WB's TerrainUtils
CalculateSplitDirection uses a different math expression than
retail's FSplitNESW (the AC2D-cited polynomial 0x0CCAC033 etc that
our visual terrain mesh and physics already share). Substituting
TerrainSurface.SampleZ with WB's GetHeight in isolation would
re-introduce the triangle-Z hover bug from earlier work — physics
and visual mesh would pick different diagonals on disputed cells.

Updates:
- ISSUE #51 documents the divergence with file references and the
  research that's needed when N.5 picks this up.
- Roadmap N.2 entry flags the dependency on N.5 and the reasoning
  ("not low-risk after all").

N.1's conformance proved slope-filtering equivalence (boolean
walkable verdict), not formula equivalence. The lesson is captured
in memory (feedback_wb_migration_formulas.md, not in-repo).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-08 12:05:04 +02:00
Erik
c189ec0c40 docs(N.3): visual verification passed — flip Live ✓
Walked Holtburg with the user; no texture regressions on terrain
blending, mesh textures, scenery clipmap edges, or building surfaces.
The deliberate A8 non-additive change (R=G=B=255,A=val) produced no
visible delta on entity textures. Phase N.3 is shipped end-to-end.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-08 11:42:53 +02:00
Erik
8d166afc62 docs(N.3): mark Phase N.3 shipped + commit implementation plan
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>
2026-05-08 11:37:52 +02:00
Erik
6010827b21 docs: roadmap N.0 shipped + realistic N.2-N.9 estimates + N.3 handoff
Roadmap updates after Phase N.1 ship:
- Marks N.0 (submodule + project refs setup) as ✓ SHIPPED with the
  c8782c9 commit reference
- Updates N.2-N.9 effort estimates with realistic post-N.1 numbers
  (originals were 1-2 days / 1 week / 2 weeks; realistic numbers
  factor in conformance-test discovery, ACME-vs-Chorizite delta
  hunts, and the visual-verification-then-revert cycle that ate
  most of N.1's calendar time)
- Adds a "Lessons from N.1" subsection so future N phases benefit
  from the rotation-bug-conformance-test pattern, the ACME divergence
  insight, and the "whackamole = stop" rule
- Updates total calendar estimate to 3-4 months / 10-12 engineering
  weeks for N.2-N.9 (was 2-3 months / 6-8 weeks)

New handoff doc at docs/research/2026-05-08-phase-n3-handoff.md
captures everything a fresh agent picking up N.3 (texture decoding)
needs: phase context, what to read first, suggested task decomposition,
watchouts (especially the ACME-divergence and conformance-test
lessons), and where to start.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 10:49:16 +02:00
Erik
ad8b931be7 docs: mark Phase N.1 shipped + file road-edge tree as known issue
Adds Phase N.1 to "Phases already shipped" table at top of roadmap,
updates the Phase N section to mark N.1 with checkmark SHIPPED status,
and files the known road-edge-tree cosmetic difference at landblock
0xA9B1 in ISSUES.md as issue #50 follow-up.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 10:38:01 +02:00
Erik
21425ffb22 plan(N.1): scenery via WorldBuilder helpers — implementation plan
Bite-sized TDD plan for Phase N.1. Eight tasks:
1. WbSceneryAdapter (LandBlock → TerrainEntry[] adapter)
2. ACDREAM_USE_WB_SCENERY feature flag scaffold
3. Per-helper conformance tests (Displace / OnRoad / GetNormalZ / RotateObj / ScaleObj)
4. Implement GenerateViaWb alternative path
5. Wire feature-flag dispatch in Generate()
6. Visual verification at landblock 0xA9B1 (manual)
7. Flip flag default-on
8. Delete legacy code paths + mark roadmap shipped

Each task has explicit code blocks, exact dotnet commands, expected
output, and a commit instruction. Conformance tests prove substitution
is behavior-preserving before the dispatch is wired in.

Spec: docs/superpowers/specs/2026-05-08-phase-n1-scenery-via-wb-helpers-design.md

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 09:05:53 +02:00
Erik
8a06fce7a5 spec(rendering): Phase N WorldBuilder migration design + N.1 scenery
Adds two design docs and a roadmap entry for the strategic shift
from "port retail rendering algorithms ourselves" to "depend on a
fork of Chorizite/WorldBuilder for rendering + dat-handling."

- docs/superpowers/specs/2026-05-08-phase-n-worldbuilder-migration-design.md
  — parent design: integration model (fork + submodule), 10 sub-phases
  (N.0 setup through N.10 GL consolidation), strangler-fig phasing
  with per-phase feature flags, retail-decomp boundary clarified for
  what WB does NOT cover (network, physics, animation, motion, UI,
  plugin, audio, chat).

- docs/superpowers/specs/2026-05-08-phase-n1-scenery-via-wb-helpers-design.md
  — N.1 detailed design: replace IsOnRoad / DisplaceObject /
  slope-normal calc / rotation / scale inside SceneryGenerator.Generate()
  with calls to WB's SceneryHelpers + TerrainUtils. Keep data flow,
  ScenerySpawn shape, and renderer integration. Add small LandBlock →
  TerrainEntry[] adapter. Feature flag ACDREAM_USE_WB_SCENERY=1.

- docs/plans/2026-04-11-roadmap.md — Phase N entry added between
  Phase M and Phase J. Lists all 10 sub-phases with effort estimates.

Fork already created at https://github.com/eriknihlen/WorldBuilder.
N.0 setup (replace references/WorldBuilder/ snapshot with submodule,
add project references, build green) is the next implementation step.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 08:47:23 +02:00
Erik
9b210be126 docs(architecture): WorldBuilder inventory + CLAUDE.md alignment
Saves the comprehensive inventory of what WorldBuilder provides
(terrain, scenery, static objects, EnvCells, portals, sky, particles,
texture decode, mesh extraction, visibility) vs what acdream still
ports from retail decomp (network, physics, animation, movement, UI,
plugin, audio, chat).

This is the load-bearing reference for the strategic shift from
"port retail algorithms ourselves" to "rely on WorldBuilder for
rendering + dat-handling, port only what WB doesn't cover."

Updates CLAUDE.md:
- Adds top-level instruction: read the inventory FIRST before
  re-porting anything in the 🟢 list
- Reframes references/WorldBuilder/ as acdream's rendering BASE,
  not just a reference repo
- Updates the "Reference hierarchy by domain" table to point
  rendering/dat questions at WorldBuilder, with retail decomp as
  cross-check

Subsequent commits will fork WorldBuilder and replace our terrain/
scenery/object rendering with calls into the fork.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 08:31:03 +02:00
Erik
c5412aa795 docs(research): #49 handoff — scenery (X, Y) drift investigation
Self-contained brief for a fresh agent: bug description with the
2026-05-06 screenshot evidence, what's confirmed (Z fix from #48 is
in, Stab placement is correct, dat content matches), five competing
hypotheses (LCG noise drift / cell-coord transposition / Align
reference / slope filter / SceneInfo lookup), and the cdb retail
trace as the gold-standard diagnostic to disambiguate. Same pattern
as the #48 handoff — paste the doc into a new session as the prompt.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 14:34:55 +02:00
Erik
ab1ba5e0e2 docs(issues): pin #48 SHA in ISSUES.md
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 14:30:37 +02:00
Erik
a4693954d8 fix(scenery): #48 unify scenery Z with physics-path triangle picker
Closes #48. Trees on sloped cells visibly hovered above the visible
terrain because GameWindow.SampleTerrainZ (the bilinear fallback used
during scenery hydration before physics registers a landblock) had
its diagonal arms swapped — used the SEtoNW triangle test on SWtoNE
cells and vice versa. The ACDREAM_DUMP_SCENERY_Z=1 diagnostic showed
every scenery line ran through the bilinear path (streaming race),
so on hilly terrain scenery was placed at a Z up to ~1.5 m off from
the visible mesh.

Latent since ff325ab (2026-04-17 "feat(ui): debug overlay + refined
input controls" carrying along the upgrade). That commit reached for
WorldBuilder TerrainUtils.GetHeight as the secondary oracle and
re-derived the triangle-pair tests; the named-retail / ACE algorithm
in TerrainSurface.SampleZ (used by the physics path / player Z) was
always correct, so player feet stayed flush — the two paths just
disagreed and only scenery noticed.

Fix:
- TerrainSurface.InterpolateZInTriangle (private static) — single
  source of truth for the triangle pick + barycentric Z, sourced
  from FUN_00532a50 / ACE LandblockStruct.ConstructPolygons.
- TerrainSurface.SampleZFromHeightmap (public static) — heightmap-
  byte-array variant for the scenery hydration fallback. Both this
  and TerrainSurface.SampleZ (instance) now delegate to the same
  InterpolateZInTriangle.
- GameWindow.SampleTerrainZ — thin wrapper over the new static.
- TerrainSurfaceTests.SampleZFromHeightmap_AgreesWithInstance_AcrossWholeLandblock
  asserts both sampler paths agree at 1500 sample points across both
  diagonals, so future drift gets caught.

The ACDREAM_DUMP_SCENERY_Z=1 diagnostic in BuildSceneryEntitiesForStreaming
is kept committed (env-var gated, zero cost when off) — useful for
the related #49 scenery (X, Y) placement investigation filed in the
same commit.

Visual verified at Holtburg landblock 0xA9B30001 2026-05-06: the
formerly floating 32 m pines (setups 0x020002D3 / 0x020002D9) now
sit flush on the visible terrain mesh.

Test baseline: dotnet test reports the same 8 pre-existing motion /
BSP step-up failures as the handoff doc warned about — no new
failures introduced.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 14:30:25 +02:00
Erik
71b1622293 fix(camera): #38 render-interpolate player motion
Keep local physics authoritative at the retail 30 Hz MinQuantum, but expose a render-only position that lerps between completed physics ticks for the player mesh and chase-camera target. Network outbound continues to use the discrete physics position.

Also make the visually confirmed #47 humanoid close-detail DIDDegrade path default-on, with ACDREAM_RETAIL_CLOSE_DEGRADES=0 left as a diagnostic opt-out.

Verification: dotnet build AcDream.slnx -c Debug; focused #38 interpolation tests passed; visual confirmed smooth 2026-05-06. Full dotnet test AcDream.slnx -c Debug --no-build still has the known 8 AcDream.Core.Tests baseline failures.

Co-authored-by: Codex <codex@openai.com>
2026-05-06 17:53:34 +02:00
Erik
0d3b85dd69 docs(issues): file #48 — subset of tree species hover above terrain
A few specific scenery GfxObjs render with their trunk base ~0.5-1.5m
above the terrain mesh while the vast majority sit flush. Per-GfxObj-
id ⇒ deterministic across instances. Player Z snap is unaffected.
Side-by-side with retail confirmed the same species place flush there.

Filed with three competing hypotheses: per-GfxObj origin convention
(some tree meshes authored with origin at bbox-center vs trunk-base),
physics-vs-bilinear terrain Z mismatch on NE↔SW-cut cells, or the
same DIDDegrade close-detail story as #47 applied to scenery.

Detailed cut-and-paste handoff for the next agent at
docs/research/2026-05-06-issue-48-handoff.md — covers the diagnostic
dump that disambiguates the hypotheses with one log line per
offending tree.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 17:18:26 +02:00
Erik
50da2bb81d docs(research): #38 handoff prompt for next-session agent
Self-contained pickup brief for the chase-camera "30 fps" issue
introduced by L.5's 30 Hz physics-tick gate. Covers confirmed root
cause with file:line citations, recommended fix (render-time
interpolation between physics ticks — Fix-Your-Timestep pattern),
implementation sketch with edge cases, file pointers, test workflow,
and don't-break constraints (physics cadence + network outbound).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 17:03:43 +02:00
Erik
0bd9b9693b fix(rendering): #47 — walk DIDDegrade for retail close-detail meshes
Humanoid bodies (Setup 0x02000001 + heritage variants) rendered visibly
flat / bulky vs retail because we drew the base GfxObj id from Setup /
AnimPartChange directly. Retail's CPhysicsPart::LoadGfxObjArray
(0x0050DCF0) treats that base id as the entry point to a DIDDegrade
table; close/player rendering uses Degrades[0].Id, which is the
higher-detail mesh that carries bicep / deltoid / shoulder geometry.

ACViewer also has this bug — it was the key signal it isn't acdream-
specific. Both clients drew the LOD-3 base mesh (e.g. 14 verts / 17
polys for Aluvian Male upper arm 0x01000055), missing the close-
detail variant (0x01001795: 32 verts / 60 polys).

Adds GfxObjDegradeResolver that walks the table with safe fallbacks
at every step. Wired in GameWindow after AnimPartChange application
and before texture-change resolution so texture overrides match the
resolved mesh's surfaces. Gated by ACDREAM_RETAIL_CLOSE_DEGRADES=1
and scoped to humanoid setups (34 parts with >=8 null-sentinel
attachment slots) while the fix bakes — the change is harmless on
non-humanoid setups (resolver falls back to base when no degrade
table) but we hold the broader sweep until LOD distance plumbing
lands.

User confirmed visually 2026-05-06: bicep, deltoid, and back-muscle
definition match retail.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 16:46:23 +02:00
Erik
8d7cad5b14 docs(research): #47 handoff prompt for next-session agent
Self-contained pickup brief for the bulky-humanoid bug. Has:
- the bug + acceptance criterion
- everything ruled out this session (with evidence)
- starting facts confirmed via diagnostics
- 4 ranked hypotheses (per-vertex normals → ambient → MSAA → frame
  composition) with concrete tests for each
- diagnostic env vars + their output shapes
- the CLAUDE.md grep-named-first workflow
- files most likely to need edits
- live test workflow (env vars, expected entities in Holtburg)
- constraints (don't break drudges / scenery / +Acdream local view)

Designed to drop straight into a fresh agent's prompt window.
2026-05-06 11:34:25 +02:00