Retail-anchored design for the missing visual feedback on selection:
four corner triangles + radar-blip colour coding around the selected
entity, drawn via ImGui in screen space.
Retail evidence (named decomp):
* VividTargetIndicator::SetSelected at 0x004f5ce0
* gmRadarUI::GetBlipColor at 0x004d76f0 (Portal / Vendor / Creature /
Player / PK / PKLite / Default colours from pwd._bitfield bits +
IsCreature/IsPlayer/IsPK predicates we already parse)
* VividTargetIndicator::CopyImage at 0x004f5dd0 (tints a source
bitmap by RGBA)
MVP scope:
1. RadarBlipColors helper (Core, with unit tests)
2. TargetIndicatorPanel (App, ImGui draw via background draw list)
3. Wire to existing _selectedGuid from B.4b
4. ~200 LOC + tests
Deferred to follow-ups: off-screen edge arrow, DAT-loaded sprite (MVP
draws procedurally), mesh-tint highlight, player-option toggle, server
selection-relay.
Pairs with #59 (WorldPicker over-pick): the indicator makes the
mis-pick visible, so the user can clear + reselect even before the
underlying picker is tightened.
Captured a live ACDREAM_PROBE_AUTOWALK trace double-clicking +Je from
~3.5m. Findings folded into the spec's State at design freeze section:
1. Wire parser is correct (matches ACE MoveToObject.Write +
MoveToParameters.Write byte-for-byte).
2. ACE sends mtRun=0.00. Not a parser bug — that's the wire value.
Retail's apply_run_to_command (0x00527BE0) fell back to the
player's own rate; our Slice 2 needs the same fallback chain.
3. Player position never changed during the entire trace — current
behavior is pure no-op on the inbound MoveToObject (literally
ignored, as our code at OnLiveMotionUpdated:3289 suggests).
4. ACE does NOT broadcast UpdatePosition for the local player during
auto-walk. Definitively kills Option C — nothing to blend with.
Local body must drive itself.
The trace validates the spec's Option A path. Slice 2 implementation
can proceed without further wire-format guessing.
Grounded the design in named-retail evidence. MovementManager::Perform
Movement at 0x00524440 case 6 (decomp lines 300628-300648) shows the
retail client's local-side dispatcher for inbound MoveToObject:
unpacks the wire, sets motion_interpreter->my_run_rate, calls
CPhysicsObj::MoveToObject on the LOCAL player's physics body. Same
code path retail used for every creature chasing the player.
Conclusion: Option A (run a local driver against the player's body)
is retail-faithful. Option C (server-position-blend) is a non-retail
shortcut and is now eliminated from consideration.
Re-scoped the spec into 4 slices:
1. ACDREAM_PROBE_AUTOWALK diagnostic baseline (~30 LOC)
2. PlayerMovementController.BeginServerAutoWalk + reuse of
RemoteMoveToDriver against the local player's body (~100 LOC)
3. Animation cycle selection during auto-walk (~20 LOC)
4. Local pickup-animation echo (closes#64, ~10 LOC)
Total ~160 LOC, no new files. All existing acdream infrastructure
(RemoteMoveToDriver, ServerControlledLocomotion, MotionState.MoveTo
Path parsing) is reused; the work is wiring it for _playerServerGuid
in addition to remote guids.
Captures the wire-format facts that are already parsed (MotionState.
IsServerControlledMoveTo + MoveToPath fields), the two gating sites
that drop the inbound MoveToObject for the local player today, and a
three-option solution space (run remote-driver locally, visual tween,
server-position-authoritative blend).
Recommendation: Option C first (smallest blast radius, single-commit
hotfix if ACE's UpdatePosition broadcast cadence is adequate); promote
to Option A only if the trace shows server broadcasts are too sparse
to render smoothly.
Explicitly does NOT implement yet. The 'walks then snaps back' visible
symptom is observed but the mechanism isn't characterized in detail —
the spec calls for a diagnostic-trace session first (ACDREAM_PROBE_
AUTOWALK env var, ~30 LOC) to capture exactly what ACE sends during a
failed auto-walk. The trace decides between Option C (sufficient
position broadcasts) and Option A (need to fill in per-tick locally).
#64 (local pickup animation) is flagged as likely-related — same
OnLiveMotionUpdated:3289 self-echo filter drops both. May fix in
the same B.6 work.
M1's demo scenario is mechanically complete:
1. Walk through Holtburg — met via L.2a/d/g
2. Open the inn door — met via B.4b + B.4c
3. Click an NPC — met via B.4b chain + chat handlers
(visually verified 2026-05-14 on Tirenia + Royal Guard)
4. Pick up an item — met via B.5 + 87ba5c9 feedback polish
What's left to formally land: record ≈30s demo video, pin still +
writeup, flip freeze list, point CLAUDE.md "currently working toward"
at M2. Per the milestone-discipline rules, milestone landing is a
user-driven event with an artifact; this commit only updates the
factual demo-target status.
Filed but explicitly deferred (don't block M1 recording): #61 (door
swing cycle-boundary flash), #62 (PARTSDIAG null-guard), #63
(server-initiated MoveToObject auto-walk — candidate Phase B.6),
#64 (local-player pickup animation).
Phase B.5 (ground-item pickup, close-range path) shipped and
visual-verified 2026-05-14 at Holtburg. M1 demo target 4/4 ("pick up
an item") met.
New ship-handoff doc captures the 5-commit history including the
post-visual-test PickupEvent (0xF74A) wire-handler fix that closes
the local-despawn gap.
Roadmap and CLAUDE.md updated to reflect the ship + the new follow-up
issues:
- #63 (MEDIUM) — server-initiated MoveToObject auto-walk not
honored; blocks double-click pickup + out-of-range F. Filed as
candidate Phase B.6. holtburger has the reference implementation.
- #64 (LOW) — local-player pickup animation does not render
(retail observers see it correctly). Likely a self-echo filter
dropping UpdateMotion(Pickup) on the local player.
Carry-overs from B.4c (#61 link-cycle flash, #62 PARTSDIAG null-guard)
unchanged.
Captures post-B.4c state, click-NPC investigation findings (chain
already wired via Tell/CommunicationTransientString/etc; verify
opportunistically during B.5 visual test), and B.5 scope decisions
made in chat before the user requested a session handoff:
- Trigger: F-key (SelectionPickUp action, already bound)
- Target: requires _selectedGuid (no pick-under-cursor fallback)
- Wire opcode 0x0019 (GameAction.PutItemInContainer)
- Payload: itemGuid + containerGuid + placement (12 bytes)
- Container = _playerServerGuid
- Three changes in two existing files (~50 LOC total)
Plus carry-overs from B.4c (#61 cycle-boundary flap, #62 PARTSDIAG
null-guard), the B.4b ID-translation gotcha pattern to watch for,
and the standard ACE session-race tip.
Branch `claude/phase-b5-pickup` (renamed from
`claude/investigate-npc-click`) is the workspace; the fresh session
should start there.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Opus final review of B.4c flagged that Task 4's handoff doc invented
implementation details that don't exist in the code:
1. IsDoorSpawn claimed to check "spawn.WeenieObj.WeenieType == 8 OR
IsDoorName(spawn.Name)" — the actual code is just IsDoorName(spawn.Name)
delegating to "name == "Door"". No WeenieType lookup exists.
2. A "_doorSequencers" per-door dict was referenced in three places — that
dict doesn't exist. The actual code reuses the existing
_animatedEntities[entity.Id] dict (same one that holds creatures + the
player), with Animation = null! per the existing pattern at line 7885.
3. The UM dispatch path was described as a new B.4c-added branch with
pseudocode — that's wrong. B.4c does NOT add a new dispatch path;
OnLiveMotionUpdated's existing TryGetValue against _animatedEntities
handles doors automatically once Task 1's spawn-time branch registers
them. The only UM-dispatch B.4c contribution is the [door-cycle]
diagnostic line, gated on IsDoorName.
Corrects sections "At world load (spawn time)", "When the door opens",
"Per-frame mesh rebuild", and "Door types covered" to reflect the actual
shipped code. cmd→motion mapping (cmd=0x000C → open, cmd=0x000B → close)
left as-is — it was correct.
No code change. Verified by re-reading GameWindow.cs IsDoorSpawn /
IsDoorName helpers, the Task 1 spawn-time branch body, and the
TickAnimations sequencer dispatch.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Task-by-task plan with full code in every step, no placeholders.
Task 1: IsDoorSpawn helper + Door registration branch (state-seeded
SetCycle from spawn PhysicsState ETHEREAL bit).
Task 2: [door-cycle] diagnostic in OnLiveMotionUpdated for greppable
verification.
Task 3: Holtburg inn doorway visual test (user-performed).
Task 4: ship handoff + close#58 + roadmap/CLAUDE/memory updates.
Self-review table at bottom maps every spec section to its task(s);
all covered. Companion to spec
docs/superpowers/specs/2026-05-13-phase-b4c-design.md.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Phase B.4c closes#58 (filed during B.4b ship). When a door's
state flips via ACE Door.ActOnUse, the L.2g chain handles the
SetState collision-bit flip but no UpdateMotion handler ever
animated the door visually. Investigation traced the gap to the
spawn-time registration gate at GameWindow.cs:2692 which requires
a multi-frame idle cycle — doors have no idle.
Design: door-specific spawn-time branch that bypasses the gate,
builds an AnimationSequencer, seeds it with Off (closed) or On
(open) cycle based on spawn PhysicsState. ACE Door.cs:43 sets the
same initial state. ~40 LOC in one file. Reuses the existing
AnimationSequencer + per-frame tick + WB renderer pipeline. No
changes downstream.
Discovered during self-review that the per-frame tick at
GameWindow.cs:7691-7697 unconditionally overwrites ae.Entity.MeshRefs
with sequencer-derived transforms; an empty sequencer would collapse
the door to origin. The state-seeded SetCycle at spawn keeps the
sequencer always producing valid frames. Also documented:
ae.Animation = null is safe because the tick's sequencer branch at
line 7497 never reads it (only the legacy slerp else branch does).
Diagnostic tags renamed from phase-named [B.4c] to durable
[door-anim] / [door-cycle] per Opus reviewer feedback on B.4b.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Final whole-branch code review (Opus) surfaced two Important post-merge
follow-ups + a one-word inaccuracy in the handoff doc:
- #59: tighten WorldPicker per-entity Setup.Radius (M1-deferred; the
ServerGuid==0 invariant is load-bearing and worth documenting before
L.2d's CBuildingObj port lands).
- #60: port retail's obstruction_ethereal downstream path so combat-HUD
contact reporting works for ethereal creatures (M2-combat).
- handoff: corrected "Added a _entitiesByServerGuid reverse-lookup" to
"Used the pre-existing _entitiesByServerGuid" — the dict has existed
since Phase 6.6/6.7; slice 1c used it, didn't add it.
Review verdict: branch ready to merge to main.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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>