Original spec placed WorldPicker in src/AcDream.App/Rendering/ and the
test in tests/AcDream.App.Tests/, but AcDream.App.Tests doesn't exist
as a project. Moved to AcDream.Core.Selection where it conceptually
belongs (no App-layer deps; only WorldEntity + System.Numerics) and
where the existing AcDream.Core.Tests project can hold the tests.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Phase B.4b closes the M1-blocker discovered during the L.2g slice 1
visual test: the input dispatcher fires SelectDblLeft on click but
GameWindow.OnInputAction has no case for any Select* / UseSelected
action, so clicks silently die.
Spec creates the minimum new structure to close the gap:
- New static helper WorldPicker (BuildRay + Pick over WorldEntities)
- Rename _selectedTargetGuid -> _selectedGuid on GameWindow (unifies
combat + interaction selection per retail's single-target model)
- Three switch cases (SelectLeft, SelectDblLeft, UseSelected)
Two further L.2g handoff inaccuracies surfaced during exploration:
WorldPicker and SelectionState do NOT exist in src/ (handoff and
ISSUES #57 both claimed they did). BuildPickUp also doesn't exist;
only BuildUse / BuildUseWithTarget / BuildTeleToLifestone are present.
Spec accounts for the actual state and defers BuildPickUp + SelectionState
class extraction.
Visual verification scenario reuses the L.2g slice 1 reproducibility
recipe: one Holtburg inn doorway log captures both L.2g + B.4b.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
L.2g slice 1 is CODE-COMPLETE: parser + registry mutator + WorldSession
dispatcher + GameWindow subscriber (4 commits: 2459f28, d538915,
536a608, 108e386). Build clean, 6 new tests pass, baseline-stable
across the full suite. Per-commit + final integration code reviews
all approved.
Visual verification deferred: while running the Holtburg-doorway test,
Phase B.4's outbound Use handler turned out to be unwired. The wire
builders (InteractRequests.BuildUse), classes (SelectionState,
WorldPicker), input-action enums, and keybindings all exist — but
GameWindow.OnInputAction has no case for SelectDblLeft, so the click
silently does nothing. The inbound L.2g chain we just landed can't
fire until something sends an outbound Use.
This commit captures the handoff + reframes next-session work:
* docs/research/2026-05-12-l2g-slice1-shipped-handoff.md (NEW)
Full evidence: 4 shipped commits, end-to-end code flow, B.4
discovery explanation, 4 minor + 1 Important review notes
(the Important one is a test-coverage gap that the B.4b visual
test will settle automatically), reproducibility recipe,
next-session pick.
* CLAUDE.md
"Currently in Phase L.2" paragraph: L.2g slice 1 code shipped;
visual test deferred to B.4b. Next-phase-candidates list:
L.2g slice 1 (now done) replaced with the B.4b candidate
pointing at the slice scope.
* docs/plans/2026-04-29-movement-collision-conformance.md
L.2g section gains a "Current shipped slice (2026-05-12):" table
listing the 4 commits.
* docs/plans/2026-05-12-milestones.md
M1 phase-list updated: L.2g slice 1 (code) shipped; B.4 renamed
"B.4 / B.4b" with the gap-discovery note + B.4b shape.
* docs/ISSUES.md
New issue #57 (HIGH) for the B.4 interaction-handler gap.
Promoted to Phase B.4b; will close as
DONE (promoted to Phase B.4b) when B.4b's design spec lands.
* Memory file project_interaction_pipeline.md (in personal
memory dir, not in this commit) updated to reflect reality.
Next session: Phase B.4b (~30-50 LOC, 1-2 subagent dispatches,
~30 min). Subscribe SelectDblLeft -> WorldPicker.Pick ->
InteractRequests.BuildUse -> _liveSession.SendGameMessage. Same
Holtburg-doorway visual test verifies both L.2g slice 1 and B.4b
in one pass.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
L.2d slice 1.5 ship identified the Holtburg doorway blocker as a closed
Door entity (Setup 0x020019FF) whose PhysicsState.Ethereal bit flips
when the player Uses the door. The L.2d shape-fidelity work doesn't
cover this — the door's collision shape is correct; what's missing is
honoring the *runtime* state change.
L.2g is the new sub-phase that handles it. Scope is narrow:
* Parse inbound GameMessageSetState (0xF74B).
* Plumb the new PhysicsState value into ShadowObjectRegistry's
cached per-entity state so the existing CollisionExemption.IsExempt
already-in-place short-circuit sees up-to-date bits.
* Verify the Holtburg inn-door scenario: walk in blocked, Use door,
walk through, auto-close blocks again after 30s.
* Confirm UpdateMotion (NonCombat, On/Off) drives non-creature
entities (door swing animation).
Why a new L.2 sub-letter (and not B.4 or Door-special-case): the wire
mechanism (SetState flipping Ethereal) is also how ACE handles activated
traps, opened chests, spell projectiles becoming ethereal. Generic
infrastructure with doors as the verification scenario; lane is the
informal sixth "dynamic state."
Roadmap state:
* L.2 plan-of-record adds the L.2g section after L.2f.
* Milestones doc M1 phase list extended `a-f` -> `a-g`.
* CLAUDE.md status pointer + "next phase candidates" list updated to
name L.2g slice 1 implementation as the natural next step.
Risk: low. Wire-byte width has a hex-dump fallback path in slice 1
(holtburger says 12 bytes, ACE writes 16, capture settles it). ETHEREAL
plumbing already exists; we feed it new data. No resolver changes.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Introduce a morale + scope-management layer above the roadmap. Seven
milestones (M0-M7) from "Connect & explore" to "v1.0", each defined by a
concrete playable scenario and ~6-10 weeks of work. Each milestone hit
triggers a recorded demo video + a freeze list of phases that go
off-limits until M7's polish pass.
M0 ("Connect & explore") is declared done retroactively — ~25 shipped
phases through 2026-05-12 (terrain, network, audio, particles, chat,
input, streaming, WB rendering migration, sky/lighting, particle system,
combat/spell/item data layers) are frozen. Currently working toward M1
("Walkable + clickable world"): L.2 collision + B.4 interaction, ~4-6
weeks.
CLAUDE.md gains a "Milestone discipline" section above the existing
"Roadmap discipline" — sets the two-altitude orientation (milestones
above, phases below), names M1 as the current target, and codifies the
four motivation rules: (1) one active milestone at a time, (2) frozen
phases off-limits, (3) recorded demo video per landing, (4) state both
altitudes at session start.
Addresses the "everything is half-built" feeling caused by per-phase
vertical-slice ships never adding up to a milestone-level "done" event.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
L.2d as scoped is essentially closed at the Holtburg site. The slice-1.5
trace settled the question: the "I can't walk through doorways" symptom
is a closed Door entity (Setup 0x020019FF named "Door") at the building
threshold, not a building-BSP-collision issue. Building BSP is healthy.
The two prior framings turned out wrong:
- L.2a handoff (2026-05-12): "per-cell walkability missing" — based on
hit attribution pointing at the building, missed the Door cylinder
also colliding per tick.
- L.2d slice 1 spec (2026-05-13 morning): "BSP shape fidelity, three
hypotheses X/Y/Z" — ruled out by the trace once the probe labeling
bug was fixed in slice 1.5.
Handoff doc captures full evidence, side findings (building double-
registration latent bug, missing PhysicsState in entity-source log),
and a candidates list for the next-session ordering discussion.
Plan-of-record L.2d sub-direction paragraph updated to match: "watch-
and-wait" mode, no more slices until a new shape-fidelity bug is
observed at a different site. Door-state handling becomes its own
sub-phase, scope deferred to project-ordering discussion.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds ACDREAM_PROBE_BUILDING — a read-only per-shadow-entry probe that
captures full BSP collision evidence whenever TransitionTypes.FindObjCollisions
attributes a hit (via the existing L.2a slice 3 chain). One multi-line
[resolve-bldg] entry per attributed hit: partIdx, hasPhys, bspR vs
vAabbR, world-space entOrigin_lb, and the actual hit polygon's vertices
in both object-local and world space.
Paired with a one-time [entity-source] line at every ShadowObjects.Register
call site in GameWindow so entityId from a probe line is greppable to its
WorldEntity source within a single log file.
Plumbing: BSPQuery writes the resolved hit polygon to a new
PhysicsDiagnostics.LastBspHitPoly side-channel at the 5 SetCollisionNormal
sites in Paths 5/6 + CollideWithPt. TransitionTypes clears that field
before each shadow-entry dispatch and reads it back at the L.2a slice 3
attribution site to emit the probe line.
Spec component 4 originally described an out ResolvedPolygon? parameter
on BSPQuery.FindCollisions; the static side-channel achieves the same
observable behavior without plumbing through BSPQuery's recursive private
methods. Deviation noted in PhysicsDiagnostics.LastBspHitPoly's XML doc.
Reframes the plan-of-record's L.2d sub-direction paragraph: the 2026-05-12
handoff proposed porting CBuildingObj + per-cell walkability, but ACE
BuildingObj.cs:39-52 + named-retail acclient_2013_pseudo_c.txt:701260
show find_building_collisions is one BSP test on Parts[0]. Per-cell
walkability belongs to L.2e, not L.2d. L.2d slice 1 is the diagnostic;
slice 2 is the actual fix scoped from slice 1's evidence (one of three
hypotheses: wrong BSP loaded / over-registered parts / BSPQuery flaw).
Tests: 2 synthetic unit tests in PhysicsDiagnosticsTests.cs pin the
static API contract that the BSPQuery → side-channel → TransitionTypes
emission chain depends on. The multi-line line format itself is verified
by acceptance criterion 2 (live Holtburg-doorway capture) — covering it
here would require a heavy PhysicsEngine + Transition fixture for a
diagnostic-only emission.
Verified: dotnet build green; the 2 new tests pass; the 8 pre-existing
test failures listed in the L.2a handoff (MotionInterpreter GetMaxSpeed_*,
PositionManager.ComputeOffset_BothActive_Combined,
PlayerMovementController.Update_ForwardInput_*, Dispatcher.W_held_*,
BSPStepUpTests.{D4,C3}) remain failing — none introduced by this slice.
Spec: docs/superpowers/specs/2026-05-13-l2d-cbuildingobj-collision-design.md
Conformance anchors:
- acclient_2013_pseudo_c.txt:701260 (CBuildingObj::find_building_collisions)
- acclient_2013_pseudo_c.txt:323725 (BSPTREE::find_collisions)
- ACE references/ACE/Source/ACE.Server/Physics/Common/BuildingObj.cs:39-52
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Reframes L.2d direction based on ACE BuildingObj.cs:39-52 + named-retail
acclient_2013_pseudo_c.txt:701260: retail's find_building_collisions is
one BSP test on PartArray.Parts[0]. No per-cell walkability. Per-cell
work (find_cell_list, point_in_cell, sphere/box_intersects_cell) is
L.2e territory.
Slice 1 is now a read-only BSP-hit diagnostic that captures full
collision evidence per L.2a [resolve] hit=yes line. Distinguishes 3
hypotheses (wrong BSP loaded / over-registered parts / BSPQuery flaw)
before any behavior change. Slice 2 is the actual fix, scoped from
slice 1's evidence.
Authors: brainstorm session 2026-05-13 (cold-start from L.2a slice
1+2+3 evidence). Predecessor handoff at
docs/research/2026-05-12-l2a-shipped-l2d-handoff.md.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Documents the L.2a-slice-1/2/3 ship for the next session and updates
the project's source-of-truth docs to reflect current state.
New files:
- docs/research/2026-05-12-l2a-shipped-l2d-handoff.md — full cold-start
handoff: what shipped, three findings (L.2e cell-id format gap, L.2c
wall-slide working, L.2d sub-direction = CBuildingObj port), branch
state, diagnostic surface inventory, files changed, open concerns,
cold-start checklist, reproduction recipe.
- docs/research/2026-05-12-l2d-next-session-prompt.md — terse copy-paste
prompt for the next Claude Code session.
Modified:
- docs/plans/2026-04-29-movement-collision-conformance.md — added "Current
sub-direction" note under L.2d capturing the evidence-driven call:
building-mesh fidelity issue, not door-state-toggle.
- CLAUDE.md — updated "Currently between phases" → "Currently in Phase
L.2"; added L.2a shipped paragraph; added ACDREAM_PROBE_RESOLVE /
ACDREAM_PROBE_CELL to Diagnostic env vars; prepended L.2d brainstorm
to next-phase candidates list.
No code changes in this commit.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
New static `AcDream.Core.Physics.PhysicsDiagnostics` holds two
runtime-toggleable flags initialized from env vars:
- ACDREAM_PROBE_RESOLVE=1 — emit one [resolve] line per
PhysicsEngine.ResolveWithTransition call: input/target/output
position+cell, ok-vs-partial, grounded-in, contact-plane status,
wall normal if hit, walkable-polygon valid, moving entity id.
- ACDREAM_PROBE_CELL=1 — emit one [cell-transit] line per
PlayerMovementController.CellId change: old → new cell, current
world position, reason tag (resolver / teleport).
Both also exposed as runtime-toggleable checkboxes in the DebugPanel
"Diagnostics" section. Unlike the existing four Dump-* checkboxes
(which only mirror sticky-at-startup env vars), the two new ones
forward directly to PhysicsDiagnostics — toggling on/off takes
effect on the next physics resolve, no relaunch.
Why now: L.2's plan-of-record (docs/plans/2026-04-29-movement-collision-
conformance.md) explicitly says "Land L.2a diagnostics first. Do not
make another physics change blind." This slice closes the most-load-
bearing gap in L.2a — a general-purpose probe on the resolver outcome
and a cell-transit log — so that later L.2b/c/d/e physics changes can
be evidence-driven instead of guessed. Foundation for the indoor /
dungeon walking trajectory (G.3 unblock).
Pure additive: when both flags are off (default), the probes collapse
to a single static-bool read per resolve, zero log cost. PlayerMovement
Controller's two CellId-mutation sites are now routed through a
private UpdateCellId(reason) helper for diag chokepoint.
Build green, 1032/1040 unit tests pass. The 8 failing tests are
pre-existing on the branch base (verified by stash-and-rerun);
none touch resolver or cell-transit code; all fail identically with
this slice stashed. Investigation deferred to a follow-up.
Refs: docs/plans/2026-04-29-movement-collision-conformance.md (L.2a
shipped-slice note added in same commit).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Roadmap:
- Status header now reads "Between phases" with C.1.5b in the recent-wins
bullet list.
- New SHIPPED row in the table (C.1.5b) summarising both slices, the
reality discovery about EnvCell statics, and the 4 visual-verified sites.
- The "IN FLIGHT — C.1.5b" sub-bullet under Phase C flipped to SHIPPED.
ISSUES.md:
- #56 removed from Active.
- #56 added to Recently closed with full commit chain (1e3c33b spec+plan,
f3bc15e SetupPartTransforms helper, 11521f4 ParticleHookSink part-transform,
5ca5827 activator refactor, 8735c39 GpuWorldState fire-sites), resolution
notes, and the visual-verification record.
CLAUDE.md:
- "Currently in flight" pointer replaced by a "Currently between phases"
marker + a C.1.5b shipped paragraph that's parallel to the existing
C.1.5a entry.
- "After C.1.5b" → "Next phase candidates".
Visual verification 2026-05-12: portal swirl matches retail extent + lateral
spread (no ground-burial); Holtburg Inn fireplace flames; cottage chimney
smoke; spell-cast particles on +Acdream — all match retail.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two-slice phase:
- Slice A: ParticleHookSink applies CreateParticleHook.PartIndex via
SetupPartTransforms.Compute(setup.PlacementFrames). Closes#56.
- Slice B: drop EntityScriptActivator's ServerGuid==0 guard so
dat-hydrated EnvCell statics + exterior stabs fire DefaultScript.
Key reality discovery folded into the spec §3: EnvCell.StaticObjects
are already WorldEntities (via GameWindow.BuildInteriorEntitiesForStreaming),
so no synthetic-ID scheme + no new walker class needed — the handoff's
§4 Q1/Q2 options are mooted by entity.Id being collision-free.
Doc-drift fixes from C.1.5a folded into §8.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Self-contained handoff doc for the C.1.5b session. §1 is a copy-paste
startup prompt for a fresh Claude Code session; §2+ is detailed
context (commits shipped in C.1.5a, decision space for the #56 fix
including precompute-per-spawn vs render-thread-side-table options,
EnvCell synthetic-id scheme, walker-class placement options, file
pointers, verification plan).
Slice will resolve issue #56 first (per-part transform handling for
static entities) before adding the EnvCell static-object DefaultScript
walker, per the C.1.5a final reviewer's recommendation that resolving
#56 unblocks slice 2's visual delight gate (the multi-emitter
collapse symptom affects portals, chimneys, and fireplaces alike).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Visual verification at the Holtburg Town network portal passed for the
slice's mechanism: 10-hook portal script fires end-to-end with correct
color, persistence, orientation, and multi-emitter dispatch. After the
334f0c6 rotation-seed fix, the swirl is oriented correctly along the
portal's facing instead of world-NS.
Known limitation surfaced during verification and filed as issue #56:
ParticleHookSink ignores CreateParticleHook.PartIndex, so all 10 of the
portal's emitters collapse to the entity root position + identity-rotated
offset, producing a compressed and partly-ground-buried swirl instead of
the multi-tier shape retail renders. Mechanism is correct; per-part
transform handling is the next vfx-pipeline concern (will affect every
multi-emitter PES — slice 2 chimneys/fireplaces in particular).
Documentation changes:
- docs/ISSUES.md: new #56 entry with the captured entity guids
(0x7A9B405B / 0x7A9B4080), script ids (0x3300126D / 0x3300067A),
symptom data, root-cause hypothesis, file pointers, and acceptance
criterion. Notes the blocks-slice-2 relationship explicitly.
- docs/superpowers/specs/2026-05-12-phase-c1.5a-portals-design.md §9:
new limitation #1 documenting the verified PartIndex collapse symptom.
- docs/plans/2026-04-11-roadmap.md: new "C.1.5a" row in the shipped
table referencing the spec, plan, and #56 caveat.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Plan: docs/superpowers/plans/2026-05-12-phase-c1.5a-portals.md. Four
tasks, TDD-style, each task is a single commit boundary:
1. EntityScriptActivator class + three xUnit tests
2. Wire into GpuWorldState (new optional ctor param + two ?. calls)
3. Construct in GameWindow with resolver lambda
4. Visual verification at Holtburg Town network portal + roadmap update
Spec amendments correct an inaccuracy in the 2026-05-12 commit
(06d7fbd): the activator's call sites live in GpuWorldState
(AppendLiveEntity / RemoveEntityByServerGuid), not directly in
GameWindow as the original spec described. Also fixes the test file
path: tests/AcDream.Core.Tests/... not AcDream.App.Tests/... per the
existing test-project convention. No design changes — same activator,
same trigger condition, same lifecycle ordering.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Slice 1 of Phase C.1.5: fire Setup.DefaultScript through the already-
shipped PhysicsScriptRunner on server-spawned WorldEntity create, so
portals (and any other entity with a DefaultScript) emit their retail-
faithful persistent particle effects at spawn time. Reuses the C.1
runner-sink-system-renderer chain end-to-end; one new ~50-line class
(EntityScriptActivator) plus a two-line wiring in GameWindow.
Slice 2 (C.1.5b) will cover EnvCell.StaticObjects + animation-hook
verification; spec landed separately after slice 1 verification passes.
Acceptance: visual confirmation at the Holtburg Town network portal,
side-by-side with retail.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Final code review of slice 1 flagged one Important issue (the spec's
"zero cost when off" claim for the surface-dump path is technically
violated — _uploadMetadata always writes one dict entry per upload
regardless of env var) plus minor doc/consistency gaps. Applied:
1. Spec §5 "Cost when off": dropped the "Zero" claim; replaced with
"Negligible — one Dictionary write per upload (~30-50 KB at Holtburg)
plus a hash-table write per upload. Expensive work (file I/O,
histogram construction) is still env-gated." This matches reality.
2. Baseline doc §5: rewrote from "Raw logs (scratch, can be deleted)"
referencing files that were never preserved in this worktree, to
"Reproducing the measurements" with the actual PowerShell launch
commands. Honest about the raw logs not being kept; the captured
medians in section 2 are the canonical record.
3. New issue #55 filed in docs/ISSUES.md — static-entity slow path
reports ~1.45M meshMissing/5s at r4 standstill, drops to ~0 when
walking. LOW severity (no visible regression), hypothesis points
at a "permanently-missing entity gets re-classified every frame"
pattern that Tier 1 cache doesn't cover.
4. Roadmap shipped table: renamed "N.6.1" row to "N.6 slice 1" to
match every other artifact's naming. Search-discoverability fix.
None of these change the slice's conclusion or next-phase
recommendation (C.1.5 first, then reduced-scope slice 2).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Code-quality review on commit 13abf96 flagged 3 Important issues in
the baseline document plus 2 minor roadmap consistency gaps. Applied
all of them:
1. The "CPU scales superlinearly with N₁" claim was imprecise because
CPU growth (4.0×) is actually sublinear vs near-LB count (7.7×).
Clarified: CPU grows more than linearly with radius N₁ but
sublinearly with visible-LB count; frustum cull discards most far
LBs early. The outer per-LB walk still scales with N₁, which is
what Tier 2's persistent groups address.
2. The "40-50% memory footprint reduction from atlas packing" estimate
was asserted without derivation and likely too optimistic given all
surfaces are already power-of-two and same-format (RGBA8). Replaced
with a more honest bound: "low-MB to ~10 MB absolute saving" with
explicit per-array metadata overhead reasoning. Conclusion is
unchanged — atlas adoption still isn't justified given GPU
under-utilization.
3. The "spec §6 threshold for atlas is >30%" citation pointed at text
that doesn't exist in the spec. Replaced with "A conventional
rule-of-thumb" so a future reader doesn't chase a phantom citation.
Plus roadmap consistency:
M1: The N.6 slice 1 bullet now uses the canonical "✓ SHIPPED — Title.
Shipped YYYY-MM-DD." prefix that every other shipped phase uses.
M2: Added N.6.1 row to the shipped table at the top of the roadmap
(lines ~55-66) so the at-a-glance shipped list is complete.
None of these change the conclusion or the next-phase recommendation
(C.1.5 first, then reduced N.6 slice 2). The fixes improve doc accuracy
and future-readability.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Capture authoritative CPU+GPU dispatch numbers at Holtburg with the
gpu_us diagnostic now working (commit 25cb147). Three radii (4/8/12)
x two motion modes (standstill/walking) + a surface-format histogram
from ACDREAM_DUMP_SURFACES=1.
Adds env-gated one-shot dump path (TextureCache.TickSurfaceHistogramDumpIfEnabled,
called from GameWindow.OnRender) that fires once after both (a) frame
600 of the session AND (b) the upload-metadata dict reaches 100 entries
-- the cache-size gate prevents the dump from firing during pre-world
GUI ticks where OnRender spins at high rates but no scenery has streamed.
Output writes to %LOCALAPPDATA%\acdream\n6-surfaces.txt with a try/catch
around the I/O so disk-full / permission errors don't crash mid-measurement.
Baseline document at docs/plans/2026-05-11-phase-n6-perf-baseline.md
documents:
- CPU dominates GPU by 30-50x at every radius (strongly CPU-bound)
- GPU wildly under-utilized (max gpu_us p95 ~600us vs 16,600us frame budget)
- CPU scales superlinearly with N1 (Tier 1 cache wins on inner loop but
not outer LB walk)
- Surface atlas opportunity high (59% of textures in top-3 triples) but
win is memory-only since GPU isn't bottlenecked
Recommendation: C.1.5 (PES emitter wiring) next, then a reduced-scope
N.6 slice 2 (drop atlas + persistent-mapped buffers -- not justified by
the GPU under-utilization observed).
Roadmap entry amended to split N.6 into slice 1 (shipped) and slice 2
(planned, reduced scope, deferred until after C.1.5).
Spec: docs/superpowers/specs/2026-05-11-phase-n6-slice1-design.md.
Plan: docs/superpowers/plans/2026-05-11-phase-n6-slice1.md (Task 4).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Step-by-step plan for the two-commit slice: fix WbDrawDispatcher's
gpu_us double-buffering bug (ring-of-3 query slots, read-before-overwrite,
vendor-neutral) then capture the radius=12 baseline at Holtburg with
the now-working diagnostic. Includes exact old_string/new_string Edit
patterns for every code change, PowerShell launch + measurement
procedure for the manual baseline, baseline doc template with explicit
fill-in slots, and a per-criterion acceptance checklist.
Output companion to docs/superpowers/specs/2026-05-11-phase-n6-slice1-design.md
(commit 05d590c).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Brainstormed design for the first slice of Phase N.6 (perf polish).
Slice 1 ships two commits: (1) fix the GPU timing query double-buffering
in WbDrawDispatcher (cross-vendor ring of 3, read-before-overwrite),
(2) add an env-gated surface-format histogram dump + capture the
radius=12 perf baseline at Holtburg. Slice 2 (TextureCache cleanup +
shader migration + optional persistent-mapped buffers) is deferred
until after C.1.5 (PES emitter wiring), with the next-phase decision
to be made on the baseline numbers slice 1 produces.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Post-A.5 polish Priority 3. EntityClassificationCache keyed by
(entityId, landblockHint) tuple. Entity dispatcher cpu_us median ~1.2 ms,
p95 ~1.5 ms — ~66% reduction vs pre-Tier-1 baseline. Closes the
post-A.5 polish phase entirely (#52, #54, #53 all closed).
See docs/ISSUES.md #53 closure + memory/project_tier1_cache.md for the
24-commit chain, 4 bug-fix iterations, and the per-tuple-vs-per-entity
recurring trap pattern documented for future cache work.
EntityClassificationCache keyed by (entityId, landblockHint) tuple lands
per spec docs/superpowers/specs/2026-05-10-issue-53-tier1-cache-design.md
+ plan docs/superpowers/plans/2026-05-10-issue-53-tier1-cache.md.
Perf result (horizon-safe preset + High quality, AMD Radeon RX 9070 XT
@ 1440p): entity dispatcher cpu_us median ~1200 us, p95 ~1500 us. Down
from ~3500m / ~4000p95 pre-Tier-1. ~66% / ~63% reduction. Well under
the A.5 spec budget (median <= 2.0 ms, p95 <= 2.5 ms). No BUDGET_OVER
flag across 30s+ standstill captures.
Visual gate cleared after 4 bug-fix iterations:
- 71d0edc: namespace stab Ids globally (cross-LB id collision)
- 95ebbf3: key cache by (entityId, landblockHint) tuple (defensive)
- c55acdc: skip cache populate when classification is incomplete
- f928e66: incomplete-entity flag must persist across same-entity tuples
User-confirmed visually via +Acdream test character: NPCs animate,
multi-part static buildings render fully (no airborne geometry, no
Z-fighting, no missing parts, no wrong textures), Nullified Statue of
a Drudge on top of the Foundry renders all parts, trees outside
Holtburg render with branches present.
Closes the post-A.5 polish phase. Issues #52, #54, #53 all closed.
Tests: 1711 passing, 8 pre-existing physics/input failures unchanged.
N.5b sentinel: 112/112 throughout.
Memory: ~/.claude/projects/.../memory/project_tier1_cache.md +
feedback_cache_per_tuple_pattern.md capture the audit-gap and per-tuple-
vs-per-entity recurring trap for future cache work.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Captures Phase M (Network Stack Conformance) as a fully-formed phase
ready to be picked up later. Three deliverables:
1. Design spec at docs/superpowers/specs/2026-05-10-phase-m-network-stack-design.md
(~700 lines, 8 sections):
- Bar C completeness target ("wireable on demand"): every wire opcode
a 2013 EoR retail client receives or sends gets a parser/builder +
golden-vector test + typed event in the new layered stack.
- Three-layer architecture: INetTransport / IReliableSession /
IGameProtocol, with WorldSession as a thin behavior consumer.
Concrete C# interface signatures, sub-component decomposition.
- Worktree-branch big-bang migration on claude/phase-m-network-stack;
weekly rebase cadence; single --no-ff merge ships the phase.
- Per-sub-phase entry/exit gates, conformance test plan (golden vectors
+ live capture replay + live ACE smoke), 10-row risk register, scope-
cut order if calendar compresses.
- Cost: 256 hours / ~6.4 weeks single-developer; 4-6 weeks calendar
with subagent parallelization on M.1 + M.6.
2. Opcode coverage matrix at docs/research/2026-05-10-phase-m-opcode-matrix.md
(~284 rows across 5 sections):
- Section 1: 22 transport flags (14 implemented).
- Section 2: 12 optional-header fields (10 partial).
- Section 3: 51 top-level GameMessages (21 implemented).
- Section 4: 103 GameEvent sub-opcodes inside 0xF7B0 (27 parsed,
26 wired).
- Section 5: 96 GameAction sub-opcodes inside 0xF7B1 (24 built,
8 with live callers).
- Roll-up: ~34% complete by raw opcode count. Biggest single
unblocking step is wiring the 16 dead builders in section 5
(Phase B.4 surface — Use / UseWithTarget / Allegiance / Inventory
/ Social / Cast / Appraise).
- Sources cited per row: holtburger (629695a), ACE, named retail
decomp, acdream current state.
- Produced by 4 parallel research agents (one per class). Spot-check
pass owed before M.1 closes.
3. Roadmap update: Phase M section trimmed to summary + status + pointer
to the spec; the previously-tracked M.0 Tier 1 quick-wins are folded
into M.3 / M.4 / M.6 per the spec; M.1 retained as the matrix
construction sub-lane with status note.
Why this shape: the user goal is a complete, layered, testable network
stack that can be wired in as gameplay phases need it — independent of
whether each opcode is yet hooked to game state. The matrix is the
source of truth for "done"; the spec is the architecture the matrix
implements against; the roadmap is the index that points at both.
Decisions captured during the design discussion (in case they need
revisiting):
- Bar C ("wireable on demand") chosen over Bar A (holtburger parity)
or Bar B (named-retail completeness).
- Three layers (INetTransport / IReliableSession / IGameProtocol)
chosen over holtburger's two-layer split.
- Big-bang on a feature branch (worktree) chosen over strangler
pattern; preserves live-ACE testing on main throughout the phase.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Holtburger reference fast-forwarded from 88b19bd to 629695a (+237 commits).
Four parallel research agents produced a parity-first-pass between
holtburger's network stack and acdream's src/AcDream.Core.Net/.
Why captured now: study surfaced six small, high-confidence "Tier 1" fixes
that can ship before the bigger M.1-M.8 layer extraction. Most likely fix
for the longstanding "remote retail observer sees us not perfect" bug
(MoveToState wire-format mismatches). Two transport gaps (no EchoResponse
reply, eager port-switch) match recent holtburger fixes (403bc98, 99974cc).
One latent bug worth a 5-min check (ISAAC search-mode for out-of-order
ENCRYPTED_CHECKSUM packets).
Captured as Phase M.0 in the roadmap so the work survives the session and
can be picked up later. Existing M.1-M.8 lift unchanged; M.1 marked as
partially started since the research note is the parity-map deliverable
in draft form.
Files:
- docs/research/2026-05-10-holtburger-network-stack-study.md (new) — full
study with ranked port candidates, recent commits worth knowing, and
acdream-vs-holtburger file map.
- docs/plans/2026-04-11-roadmap.md — Phase M Plan-of-record updated with
2026-05-10 pointer; M.0 sub-lane added before M.1; M.1 status note.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The audit at docs/research/2026-05-10-tier1-mutation-audit.md enumerates
every entity.MeshRefs write site (5 STATIC at hydration, 1 DYNAMIC at
GameWindow.cs:7580 inside TickAnimations) and verifies that all 7
Position/Rotation write sites only touch entities in _animatedEntities.
Establishes the load-bearing invariant: an entity's renderer state is
stable from spawn to despawn iff entity.Id is NOT in _animatedEntities.
The spec at docs/superpowers/specs/2026-05-10-issue-53-tier1-cache-design.md
locks in the design from brainstorming on 2026-05-10:
- Static-only cache + DEBUG cross-check (option c) — catches future
regressions of the prior bug class without paying perf cost in Release
- Separate EntityClassificationCache class injected into WbDrawDispatcher
- Cache the rest pose, not the full model matrix (Position/Rotation read
live each frame so Release stays correct even if the invariant breaks)
- Pre-flatten Setup multi-parts at populate time (the bulk of the win)
- 15 new tests covering all invalidation paths + DEBUG cross-check +
Setup pre-flatten + lifecycle pin
Closes the audit + design steps of the post-A.5 polish Priority 3 work.
Implementation plan owned by superpowers:writing-plans next.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
After the post-A.5 lifestone (#52) + JobKind plumbing (#54) work shipped,
only Priority 3 (Tier 1 entity-classification cache retry, ISSUE #53)
remains. This handoff captures the audit insights gathered during the
#52 investigation that the original post-A.5 handoff didn't have:
- MeshRef is a `readonly record struct` — its fields can NOT be mutated
in place. The actual per-frame mutation for animated entities is the
entire MeshRefs LIST replacement at GameWindow.cs:7474-7553. This
reframes the cache design.
- _animatedEntities dict at GameWindow.cs:160 is the source of truth
for which entities go through the per-frame rebuild path.
- Static entity = entity.Id NOT in _animatedEntities. Its MeshRefs is
the same instance from spawn until rare events (ObjDesc / palette
swap / part hide / scale apply).
- Recommended cache approach: static-only with explicit invalidation
hooks on the network/spawn-time write sites enumerated in the doc.
Doc covers: where main is, what shipped this session, why the first
Tier 1 attempt failed, the pre-started audit, cache design options,
acceptance criteria, files to read, workflow for the next session, and
things-to-NOT-do.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Move ISSUE #54 to Recently closed referencing commit `bf31e59`. Drop
#54 from CLAUDE.md "Currently in flight" — only #53 (Tier 1 retry)
remains open in the post-A.5 polish phase.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Move ISSUE #52 from Active to Recently closed with full root-cause writeup
referencing commit `e40159f`. Strip lifestone reference from CLAUDE.md
"Currently in flight"; remaining post-A.5 polish scope is #53 (Tier 1
retry) + #54 (JobKind plumbing).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Captures the three deferred items from A.5 ship:
- ISSUE #52: lifestone visual missing (1-3 hours, fast win)
- ISSUE #54: JobKind plumbing through BuildLandblockForStreaming
(~30 min - 1 hour, worker-thread efficiency cleanup)
- ISSUE #53: Tier 1 entity-classification cache retry (~5-7 days,
biggest perf win remaining; needs animation-mutation audit before
designing to avoid the freeze-pose bug from the first attempt)
Doc covers: A.5 final state + 3 high-value gotchas, files to read,
per-priority detail with effort estimates and acceptance criteria,
what NOT to do, the first-30-minute workflow, and the full A.5
commit chain for reference.
Phase is sized ~1 week if all three priorities land. The audit
step on Tier 1 is the highest-leverage investment.
Tier 2 + Tier 3 (static/dynamic split + GPU compute culling) are
explicitly out-of-scope for this phase — separate multi-week phases
per docs/plans/2026-05-10-perf-tiers-2-3-roadmap.md.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sub-phase under existing F.5 (Core panels) capturing the immediate
follow-up to ISSUES.md #13: now that PlayerDescriptionParser surfaces
the full trailer (Inventory / Equipped / Shortcuts / HotbarSpells /
DesiredComps / Options1+2 / SpellbookFilters) and GameEventWiring
populates ItemRepository at login, F.5a wires that data into minimal
ImGui dev panels under ACDREAM_DEVTOOLS=1 so it's observable in-game.
Establishes the binding pattern (AcDream.UI.Abstractions ViewModels →
ImGui renderer) that the eventual D.2b retail-skinned F.5 panels
reuse. Spec to brainstorm before code.
Captured 2026-05-10 during Phase A.5 polish discussion. User asked why
the 9070 XT @ 1440p doesn't hit Unreal-level FPS for an old game like
AC. Answer: architectural — we rebuild the entire draw plan from
scratch every frame instead of caching pre-baked static-world data.
Tier 1 (entity-classification cache) lands as A.5 polish (separate
commit). Tiers 2 + 3 documented here for future scheduling:
- Tier 2 — Static/dynamic split with persistent groups
~2-week phase. Static entities (~95% of world) get permanent GPU-
resident matrix slots, populated at spawn, dirty-tracked for delta
upload. Per-frame CPU cost for static = LB-cull + dirty-flag check
only. Estimated entity dispatcher: 3.5ms → 0.5-1ms median.
400-600 FPS at standstill, radius=12.
- Tier 3 — GPU-side culling (compute pre-pass)
~1-month phase. Per-instance frustum cull moves to GPU compute
shader. Compute writes draw-indirect buffer; rasterizer reads it.
Estimated CPU dispatcher: ~0.05ms (essentially free).
600-1000+ FPS at standstill, radius=12.
Doc captures effort estimates, sub-decisions, risks, mitigations, and
scheduling triggers for each tier. Also notes the architectural
ceiling (~800-1500 FPS for a C# + GL client; reaching native engine
performance requires becoming a different engine).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Code-quality review followup on Task 2 (becbde6) — addresses I1 (the
forward-looking concern that Tasks 3-9's inner-catch will leave partial
lists visible to callers with no signal) and M1 (silent inner catch).
Changes:
- Parsed gains a trailing `bool TrailerTruncated` field. Both
construction sites pass `false` by default; the trailer try/catch
flips a local `trailerTruncated` to `true` on FormatException and
feeds it into the final return.
- Inner catch logs `pos`/`payload.Length`/exception message under
ACDREAM_DUMP_VITALS=1, mirroring the outer catch's diagnostic
pattern.
- Task 2 test strengthened to assert defaults on Options2 /
SpellbookFilters / HotbarSpells / DesiredComps / GameplayOptions /
Equipped + TrailerTruncated=false (M2 followup — gives Tasks 3-9
a regression guard if they consume into the wrong field).
- New test `TryParse_TrailerAbsent_LessThan8BytesAfterEnchantments_*`
documents the contract that <8 bytes after enchantments means the
trailer is absent (not truncated): TrailerTruncated stays false,
upstream attribute data survives.
- Plan updated in lockstep so Tasks 3-11 implementers see the
`trailerTruncated` local and the new return-arg position.
271/271 AcDream.Core.Net.Tests pass.
Code review nit-fix on top of d3b58c9 — addresses two issues from the
quality review of Task 1:
I1 (Important): the record struct `Shortcut` was a homograph with the
flag member `CharacterOptionDataFlag.Shortcut`. Both names live inside
`PlayerDescriptionParser`'s scope. Rename to `ShortcutEntry` aligns
with `InventoryEntry`/`EquippedEntry` and removes the trap before
Task 3's walker references both names in the same method body.
M2 (Minor): `EquippedEntry` had no holtburger source citation; added
one referencing events.rs:180-190. Also expanded `InventoryEntry`'s
comment with the strict reader's validation reference.
Plan doc updated in lockstep so Task 3+ implementers see the new name.
8/8 PlayerDescriptionParser tests still pass.
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>
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>
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>
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>