Commit graph

86 commits

Author SHA1 Message Date
Erik
b282c69f28 docs(plan): Indoor portal-based cell tracking implementation plan
11-task plan: data wiring (PortalInfo + extend CellPhysics + extend
CacheCellStruct) → CellTransit port (FindTransitCellsSphere +
AddAllOutsideCells + FindCellList) → ResolveCellId integration (rename +
plumb sphereRadius + delete AABB containment) → BuildingPhysics for
outdoor→indoor → capture + docs.

Task 0 verifies DatReaderWriter exposes CellStruct.CellBSP and
LandBlockInfo.Buildings before any code touches them. The CellBSP
property name is the one known unknown.

Spec: docs/superpowers/specs/2026-05-19-indoor-portal-cell-tracking-design.md

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 16:40:03 +02:00
Erik
48f0b26f62 docs(spec): Indoor portal-based cell tracking design
Brainstormed spec for the follow-up to Cluster A: port retail's portal-graph
cell traversal to replace Phase D's AABB containment shortcut. Closes
ISSUES.md #87 and the remaining wall-collision parts of #84 + #85 — indoor
walking with walls that block from inside, walking through doors that
updates CellId.

Scope: all three transition types (indoor↔indoor, indoor↔outdoor,
outdoor→indoor). AABB containment deleted entirely; portal traversal is the
only path.

Key data references: docs/research/acclient_indoor_transitions_pseudocode.md
(2026-04-13) has the entire algorithm already documented from ACE source
cross-referenced against the retail header. BSPQuery.PointInsideCellBsp is
already wired (just unused).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 16:32:21 +02:00
Erik
18a2e28875 docs(plan): Indoor walking Phase 1 — BSP cluster implementation plan
14-task plan covering the diagnostic-driven phase: probe + capture +
three fix commits + docs. Tasks 1-6 land the [indoor-bsp] probe in
one feature commit. Task 7 is the user-run capture gate. Tasks 8-11
do post-capture diagnosis + fix for #84 and #85 (with a route-δ
escape hatch if #85's fix turns out to be a large cross-cell port).
Tasks 12-13 ship the WorldPicker cell-BSP occlusion fix for #86
(no capture dependency — pinned by code-reading). Task 14 closes
out ISSUES.md + roadmap + ships the post-phase handoff doc.

Spec: docs/superpowers/specs/2026-05-19-indoor-walking-phase1-bsp-cluster-design.md

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 14:15:50 +02:00
Erik
59a4e3f6cf docs(spec): Indoor walking Phase 1 — BSP cluster design
Brainstormed spec for the next indoor follow-up phase: surfacing root
causes for ISSUES.md #84 (blocked by air) + #85 (pass through walls
outside→in) + #86 (click selection penetrates walls). Diagnostic-first
single capture pass; one [indoor-bsp] probe in FindEnvCollisions, then
surgical fixes (one commit per issue). Mirrors the indoor cell rendering
Phase 1+2 pattern that landed earlier today.

#86's root cause is already pinned by code reading (WorldPicker has no
cell-BSP test) — its fix is structural and doesn't need capture data.

#78 (outdoor stabs through floor) is in the same handoff cluster but
defers to a separate phase — different code path (render visibility).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 14:07:01 +02:00
Erik
e9cc9cb228 plan: Phase 2 indoor cell rendering fix
Six tasks:
1. Add exception-surfacing ContinueWith in WbMeshAdapter.IncrementRefCount
   for EnvCell ids when ProbeIndoorUploadEnabled is on. Logs
   [indoor-upload] FAILED + [indoor-upload] NULL_RESULT.
2. Capture procedure: user walks Holtburg with the probe on; analyze log.
3. Write cause report documenting the captured exception type(s).
4. Apply targeted fix (4a/4b/4c/4d sub-shapes for the 4 most-likely causes
   — choice driven by Task 2's data). Or 4d: re-design if cause is none
   of the above.
5. Verification: re-capture confirms completed lines, user visually
   confirms floor in Holtburg Inn.
6. Roadmap update.

Tasks 2 and 5 are user-driven (must walk the client). Tasks 1, 3, 4, 6
can be subagent-dispatched.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 12:20:57 +02:00
Erik
251763b2c4 docs(spec): Phase 2 indoor cell rendering fix design
Three components:
1. WbMeshAdapter wraps the PrepareMeshDataAsync task with a continuation
   that surfaces faulted-task exceptions + null-result cases for EnvCell
   IDs only (gated by ProbeIndoorUploadEnabled). Two new log shapes:
   [indoor-upload] FAILED  cellId=0x... exception=<TypeName>: <Message>
                            stack=[<top 3 frames>]
   [indoor-upload] NULL_RESULT cellId=0x...
2. Capture procedure: re-launch at Holtburg with the probe on, grep for
   FAILED/NULL_RESULT lines, get definitive per-cell cause for the 26
   missing-completion cells from Phase 1's capture.
3. Targeted fix: code change matching whichever exception type / null
   pattern dominates. Fix shape is data-driven — see the contingency
   table in the spec.

WB's catch at ObjectMeshManager.cs:589 already calls _logger.LogError,
but WbMeshAdapter constructs the manager with NullLogger.Instance, so
the log is dropped. Our continuation surfaces the same data scoped to
EnvCells only (avoids the thousands of GfxObj/Setup log lines a real
logger would emit during landblock streaming).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 12:18:04 +02:00
Erik
1fc6c0fd69 plan: Phase 1 indoor cell rendering diagnostics
Eight bite-sized tasks:
1. RenderingDiagnostics static class (mirrors PhysicsDiagnostics pattern)
2. Unit tests (cascade + IsEnvCellId rows)
3. DebugVM mirror properties
4. DebugPanel "Indoor rendering" checkbox group
5. WbMeshAdapter [indoor-upload] probes (requested + completed via pending set)
6. WbDrawDispatcher [indoor-walk] + [indoor-cull] probes
7. WbDrawDispatcher [indoor-lookup] + [indoor-xform] probes
8. Build + visual capture + match captured data to hypothesis H1-H6

Plan ends with research note documenting captured data + hypothesis,
which becomes the input to Phase 2's spec.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 11:13:53 +02:00
Erik
e798cb7898 docs(spec): expand probe design with concrete line formats + code sites
User feedback: "add the probes you need. Better info, better code."
Original spec had a single ACDREAM_PROBE_INDOOR=1 with vague "log
lookup results" guidance. Replaced with five individually-toggleable
probes, each with:

- Specific env var name + DebugPanel checkbox name.
- Concrete log-line format.
- Exact code site to instrument.
- The hypothesis it disambiguates.

Probe set:
- ACDREAM_PROBE_INDOOR_WALK   — dispatcher entity walk per cell
- ACDREAM_PROBE_INDOOR_LOOKUP — render-data lookup hit/miss + SetupParts
- ACDREAM_PROBE_INDOOR_UPLOAD — WB upload result (requested + completed)
- ACDREAM_PROBE_INDOOR_XFORM  — composed world transform for cell geom
- ACDREAM_PROBE_INDOOR_CULL   — visibility/frustum filter decisions

Plus ACDREAM_PROBE_INDOOR_ALL master toggle.

Implementation outline added: new RenderingDiagnostics static class
(mirrors L.2a's PhysicsDiagnostics pattern), DebugPanel subsection,
edits to WbDrawDispatcher + WbMeshAdapter.

Acceptance criteria refreshed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 11:07:54 +02:00
Erik
f6e9c58932 docs(spec): indoor cell rendering fix — Phase 1 diagnostics
Initial brainstorm assumed N.5 retirement broke EnvCell rendering by
leaving _pendingCellMeshes unconsumed. Pivoted mid-brainstorm:

- WB's PrepareMeshData routes EnvCell dat-record types to
  PrepareEnvCellMeshData (ObjectMeshManager.cs:557) which produces an
  IsSetup=true ObjectMeshData with the floor mesh as EnvCellGeometry.
- WbDrawDispatcher correctly handles IsSetup=true (line 607-621) by
  iterating SetupParts and drawing each.
- DefaultDatReaderWriter loads region cell dats; ResolveId resolves
  envCellId correctly.
- LandblockSpawnAdapter calls IncrementRefCount on every entity's
  GfxObjId, including envCellId for cell entities. ServerGuid==0 passes
  the atlas-tier filter.

Chain is structurally intact. The bug is somewhere subtler.

Spec pivots to a diagnostics-first phase: ACDREAM_PROBE_INDOOR=1
captures per-frame cell-entity walk + render-data lookup + SetupParts
traversal + composed-transform values. Six hypotheses (WB silently
returns null, empty batches, cull bug, double-spawn, transform
double-apply, dispatcher MeshRefs mismatch) match six concrete fix
shapes. Phase 2 design follows the probe data.

This is more honest than the original "build a new upload path"
design, which would have hidden the actual bug.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 11:02:05 +02:00
Erik
73dee43d14 docs(camera): impl plan — retail-faithful chase camera with dev-tools toggle
8 tasks: CameraDiagnostics static (Task 1) → RetailChaseCamera math
primitives (Task 2) → Update() integration (Task 3) → CameraController
dual-camera (Task 4) → InputAction + DebugVM mirrors (Task 5) →
DebugPanel section (Task 6) → GameWindow wiring (Task 7) → build +
test + visual handoff (Task 8). Each task is TDD-shaped with exact
code in every step. PlayerTranslucency is computed + tested but
applying to the player mesh is explicitly deferred (Q1 escape clause
in the spec).

For docs/superpowers/specs/2026-05-18-retail-chase-camera-design.md.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 19:24:34 +02:00
Erik
fc819a4814 docs(camera): design — retail-faithful chase camera with dev-tools toggle
Spec for porting retail's CameraManager + CameraSet behavior to a new
RetailChaseCamera class, controlled by a CameraDiagnostics toggle so the
user can A/B against legacy ChaseCamera. Covers six retail behaviors:
exponential damping (stiffness*dt*10), 5-frame velocity-averaged slope
alignment, mouse low-pass (0.25s window), held-key offset integration,
auto-fade <0.45m, and independent translation/rotation stiffness rates.

Brainstormed end-to-end before any code change.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 19:05:35 +02:00
Erik
d640ed74e1 feat(retail): Phase B.6 — server-driven auto-walk done right
Closes #63, #69, #74, #75. Replaces the chain of Commit-B workarounds
that compensated for ACE's MoveToChain getting cancelled by a leaked
user-MoveToState packet during inbound auto-walk. The fix is
architectural — auto-walk drives the body directly from the
server-supplied path data, no player-input synthesis, no spurious
wire-packet transitions, no grace-period band-aid.

Architectural change (closes #75):
  PlayerMovementController.ApplyAutoWalkOverlay → DriveServerAutoWalk.
  - Steps Yaw toward target at retail-faithful turn rates.
  - Computes desired forward velocity from path runRate.
  - Calls _motion.DoMotion(WalkForward, speed) directly for the
    motion-interpreter state (drives animation cycle).
  - Sets _body.set_local_velocity directly when grounded.
  - Returns true to gate the user-input motion + velocity section
    in Update so user-input flow doesn't overwrite auto-walk
    velocity or motion state.
  Mirrors retail's MovementManager::PerformMovement case 6 (decomp
  0x00524440) which never touches the user-input pipeline during
  server-controlled auto-walk.

Wire-layer guard at GameWindow.cs:6419 retained as a SEMANTIC
statement (`if (result.MotionStateChanged && !IsServerAutoWalking)`):
user-MoveToState packets are for user-driven motion intent. During
server-controlled auto-walk, the motion-state transitions caused by
the animation override (RunForward / WalkForward / TurnLeft /
TurnRight cycles) must not leak as user-cancellation packets. This
is NOT the deleted 500ms grace-period band-aid; it's the wire-layer
expressing the user-vs-server motion split.

Animation plumbed for auto-walk phases (closes #69):
  - Moving forward → WalkForward (speed=1.0) / RunForward (speed=runRate)
  - Turn-first phase → TurnLeft / TurnRight (sign of yawStep)
  - Aligned-but-pre-step / arrival → no override (idle)
Driven via _autoWalkMovingForwardThisFrame + _autoWalkTurnDirectionThisFrame
fields set in DriveServerAutoWalk and read in the MovementResult
construction at the bottom of Update. UpdatePlayerAnimation picks up
the localAnimCmd as the highest-priority animation source.

Walk/run threshold = 1.0m, retail-observed. ACE's wire-default of
15.0f is too generous; ACE's own physics layer uses 1.0f at
MovementParameters.cs:50 (with the 15.0f line commented out) and
Creature.cs:312 notes "default 15 distance seems too far". The
formula matches retail's MovementParameters::get_command at decomp
0x0052aa00: running = (initialDist - distance_to_object) >=
threshold, evaluated ONCE at chain start and held for the rest of
the auto-walk (matches retail "runs all the way / walks all the way"
behaviour). Wire-supplied threshold is ignored.

Pickup gate (IsPickupableTarget) now uses BF_STUCK
(acclient.h:6435, bit 0x4) to discriminate immovable scenery from
real pickup items that share a Misc ItemType. Sign (pwd=0x14 with
BF_STUCK) → blocked; spell component (pwd=0x10, no BF_STUCK) →
allowed. ACE's PutItemInContainer (Player_Inventory.cs:831-836)
responds with WeenieError.Stuck (0x29) on stuck items so the gate
prevents wasted wire packets + a UX dead-end.

R-key dispatch by target type. UseCurrentSelection's top-level
IsUseableTarget gate was wrong (blocked USEABLE_NO=1 items that
ARE pickupable). Reordered:
  1. Creature → SendUse
  2. Pickupable → SendPickUp
  3. Useable → SendUse
  4. Otherwise → "cannot be used" toast
Each handler keeps its own gate. Matches retail's per-action
server-side validation.

AP cadence revert (closes #74). With the MoveToChain race fixed,
the per-frame "send while moving" cadence is no longer load-bearing.
Reverted to retail's two-branch ShouldSendPositionEvent gate
(acclient_2013_pseudo_c.txt:700233-700285):
  Interval NOT elapsed (< 1 sec): send if cell or contact-plane changed.
  Interval elapsed (>= 1 sec):    send if cell or position frame changed.
Adds _lastSentContactPlane field + ApproxPlaneEqual helper +
PlayerMovementController.ContactPlane public accessor. Extended
NotePositionSent(Vector3, uint, Plane, float) — both outbound sites
(MoveToState + AP) pass _playerController.ContactPlane.
Effective rates: 0 Hz idle, ~1 Hz smooth motion, per-event on
cell/plane changes, 0 Hz airborne.

CLAUDE.md updated with no-workarounds rule (commit `da126f9` on
the worktree branch). Saved as feedback memory at
memory/feedback_no_workarounds.md.

Tests: build green; Core.Net 294/294; Core 1073/1081 (baseline,
8 pre-existing Physics failures unchanged). Visual-verified
end-to-end on 2026-05-16 for far/near Use + PickUp on NPCs,
doors, items, spell components, signs (correctly blocked), corpses,
turn-first animation, run/walk thresholds, idle quiet, smooth-
motion 1Hz.

Spec: docs/superpowers/specs/2026-05-16-phase-b6-suppress-movetostate-during-inbound-autowalk-design.md
Plan: docs/superpowers/plans/2026-05-16-phase-b6-suppress-movetostate-during-inbound-autowalk.md

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 16:14:44 +02:00
Erik
b5da17db76 feat(retail): Commit B — retail-faithful AP cadence + screen-rect picker
Retires divergences flagged in the 2026-05-16 faithfulness audit:

1. AP cadence. Replaces the 1 Hz idle / 10 Hz active flat heartbeat
   with a diff-driven model gated on `Contact && OnWalkable`
   (acclient_2013_pseudo_c.txt:700327 SendPositionEvent). Sends on
   position or cell change while grounded on walkable, plus a 1 sec
   heartbeat; suppressed entirely airborne. PlayerMovementController
   exposes `NotePositionSent(pos, cellId, now)` which GameWindow stamps
   after each AutonomousPosition / MoveToState send — mirrors retail's
   shared `last_sent_position_time` between SendPositionEvent
   (0x006b4770) and SendMovementEvent (0x006b4680). Known divergence
   from retail: ours is per-frame-while-moving, retail's effective rate
   is ~1 Hz during smooth motion (cell/plane checks). Filed as #74,
   blocked by #63 — when #63 lands we revert to retail's narrower gate.

2. Workaround retirement. Removes TinyMargin (0.05 m inside arrival)
   and the AP-flush before re-send (`SendAutonomousPositionNow`). The
   diff-driven cadence makes both obsolete. Close-range turn-first
   deferred Use is kept (it IS retail — ACE Player_Move.cs:66-87
   mirrors retail's CreateMoveToChain pre-callback rotation), renamed
   `OnAutoWalkArrivedSendDeferredAction` to clarify it's a FIRST send.
   `isRetryAfterArrival` parameter dropped.

3. Far-range Use/PickUp retry. Restored — was load-bearing, not the
   "redundant cleanup" the Group 2 audit thought. Issue #63 means ACE
   drops the first Use as too-far without re-polling on subsequent APs;
   the arrival re-send is what makes far-range Use complete. Logs
   include `(queued for arrival re-send pending #63)` to make this
   explicit. Removes when #63 closes.

4. Screen-rect picker. New `AcDream.Core.Selection.ScreenProjection`
   helper shared by `WorldPicker` and `TargetIndicatorPanel`. The
   `Setup.SelectionSphere` projects to a screen-space square (retail
   anchor `SmartBox::GetObjectBoundingBox` 0x00452e20); picker
   hit-tests the mouse pixel against the same rect the indicator draws,
   inflated by 8 px (`TriangleSize`). Guarantees what-you-see is
   what-you-click — including rect corners that were dead zones under
   the old ray-sphere picker. Per-type radius (1.0/1.6/2.0 m) and
   vertical-offset (0.2/0.9/1.0/1.5 m) heuristic lambdas retired;
   `IsTallSceneryGuid` deleted; `EntityHeightFor` trimmed to 1.5 m × scale
   defensive default. No defensive sphere synth — entities without a
   baked `SelectionSphere` are skipped, matching retail's
   `GfxObjUnderSelectionRay` (0x0054c740).

5. Rotation rate run multiplier (Commit A precursor). `TurnRateFor(running)`
   helper applies retail's `run_turn_factor = 1.5f` (PDB-named
   0x007c8914) under HoldKey.Run, matching `apply_run_to_command` at
   0x00527be0 (line 305098). Effective: walking ≈ 90°/s, running ≈ 135°/s.
   Keyboard A/D + ApplyAutoWalkOverlay both use it.

6. Useability gate (Commit A precursor). `IsUseableTarget` corrected to
   `useability != 0` per `ItemUses::IsUseable` at 256455 — ANY non-zero
   passes (USEABLE_NO=1, USEABLE_CONTAINED=8, etc.), not just the
   USEABLE_REMOTE bit. Cross-checked against 4 call sites in retail
   (ItemHolder::UseObject 0x00588a80, DetermineUseResult 0x402697,
   UsingItem 0x367638, disable-button-state 0x198826). Added
   `ProbeUseabilityFallbackEnabled` diagnostic
   (`ACDREAM_PROBE_USEABILITY_FALLBACK=1`) to measure how often the
   creature/BF_DOOR fallback fires for ACE-seed-DB entities with
   null useability.

CLAUDE.md updated with the graceful-shutdown rule for relaunch:
Stop-Process bypasses the logout packet, leaving ACE's session marked
logged-in for ~3+ min. CloseMainWindow() sends WM_CLOSE so the
shutdown hook runs and the logout packet reaches ACE.

Tests: +3 ScreenProjectionTests + 6 WorldPickerRectOverloadTests = +9.
Core.Net 294/294 pass; Core 1073/1081 (8 pre-existing Physics failures
unchanged). Visual-verified 2026-05-16: rotation rate, useability,
screen-rect click area, double-click + R-key + F-key Use/PickUp at
short and long range — dialogue/door/pickup fire on arrival.

Filed follow-ups #70 (triangle apex/size DAT sprite), #71 (picker
Stage B polygon refine), #72 (cdb omega.z probe), #73 (retail-message
sweep pattern), #74 (per-frame AP chattier than retail — blocked by
#63). Old ray-sphere `WorldPicker.Pick(origin, direction, ...)`
overload kept for back-compat; no callers in acdream proper.

Plan: docs/superpowers/plans/2026-05-16-retail-faithfulness-fixes.md

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 13:56:08 +02:00
Erik
e0d5d271f3 fix(retail): rotation rate, useability gate, retail toast strings
Two retail divergences fixed from the 2026-05-16 faithfulness audit
(Commit A of the plan at docs/superpowers/plans/2026-05-16-retail-faithfulness-fixes.md).

1. Rotation rate ignored HoldKey.Run. Retail's CMotionInterp::
   apply_run_to_command (decomp 0x00527be0 line 305098) multiplies
   turn_speed by run_turn_factor (1.5, PDB-named symbol at 0x007c8914)
   when input is TurnRight/TurnLeft under HoldKey.Run. Effective
   running rotation is 50% faster (~135°/s vs walking ~90°/s).
   Our keyboard A/D and ApplyAutoWalkOverlay used a fixed walking
   rate.

   New: RemoteMoveToDriver.TurnRateFor(running) helper. Keyboard
   path passes input.Run; auto-walk overlay passes
   _autoWalkInitiallyRunning. The walking-rate base
   (BaseTurnRateRadPerSec = π/2) is unchanged; TurnRateRadPerSec
   constant is preserved as the walking-rate alias for callers
   that don't have run/walk state (NPC remotes).

2. IsUseableTarget gated on `useability & USEABLE_REMOTE (0x20)`,
   which was stricter than retail. Per ItemUses::IsUseable
   (acclient_2013_pseudo_c.txt:256455) cross-referenced with 4
   call sites, retail's IsUseable() semantic is `_useability != 0`.
   But visually retail's USEABLE_NO (1) entities don't approach
   either, because ACE never broadcasts MovementType=6 for them.
   Our client installs a speculative auto-walk BEFORE the server
   responds, so we'd visibly approach + face signs before the
   wire packet was rejected.

   Pragmatic fix: block USEABLE_UNDEF (0) AND USEABLE_NO (1) in
   IsUseableTarget — slightly stricter than retail's
   IsUseable but matches retail's user-visible behaviour
   ("R on sign does nothing"). Documented in the doc-comment so
   a future implementer knows the gap.

3. New IsPickupableTarget gate for F-key path — requires
   USEABLE_REMOTE (0x20) bit. Null-useability fallback for
   BF_CORPSE + small-item ItemTypes (preserves M1 ground-item
   pickup flow when ACE seed DB doesn't publish useability).

4. R-key (UseCurrentSelection) upfront gate now ALWAYS uses
   IsUseableTarget. R is conceptually "use" with smart-routing
   to pickup as a downstream optimization. F-key (SendPickUp)
   uses IsPickupableTarget directly.

5. Retail toast strings on block, centralised in new
   src/AcDream.Core/Ui/RetailMessages.cs:
   - "The X cannot be used" (data 0x007e2a70, sprintf 0x00588ea4)
     fires on UseCurrentSelection / SendUse gate block.
   - "The X can't be picked up!" (sprintf 0x00587353) fires on
     SendPickUp non-pickupable block.
   - "You cannot pick up creatures!" (data 0x007e22b4) fires on
     SendPickUp creature block (was previously silent).
   - Plus 4 inactive retail strings ready for future call sites:
     CannotBeUsedWith (two-target Use), CannotBePickedUp (formal
     pickup variant), CannotBeUsedWhileOnHook_HooksOff +
     CannotBeUsedWhileOnHook_NotOwner (housing). All cite their
     retail data addresses + runtime sprintf addresses.

6. ProbeUseabilityFallbackEnabled diagnostic (env var
   ACDREAM_PROBE_USEABILITY_FALLBACK=1) logs every time the
   null-useability fallback fires. Settles whether the
   fallback for creature + BF_DOOR/LIFESTONE/PORTAL/CORPSE
   entries in ACE's seed DB without useability is hot code
   or theoretical defense.

Test coverage:
- +3 RemoteMoveToDriverTests cover TurnRateFor walking/running/back-compat.
- +7 RetailMessagesTests cover each retail string with retail anchor.
- +1 CreateObjectTests TryParse_WeenieFlagsUsable_ReadsUseableNoValue
  pins parser correctness for USEABLE_NO=1.
- 294/294 Core.Net pass; 24/24 new+touched Core tests pass.
- Pre-existing baseline of 8 Physics test failures unchanged
  (BSPStepUp + MotionInterpreter regression noise from prior
  sessions; out of scope here).

Deferred to a separate session per user direction:
- Click area = indicator-rect retail fidelity. Retail's picker
  uses per-part CGfxObj.drawing_sphere + polygon refine
  (0x0054c740); ours uses single Setup.SelectionSphere ray-
  intersect. The rect corners are dead zones today. Three fix
  options analyzed: screen-space rectangle hit-test, sqrt(2)
  sphere inflation, polygon refine Stage B.

Plan: docs/superpowers/plans/2026-05-16-retail-faithfulness-fixes.md

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 12:17:54 +02:00
Erik
37177a418e docs(B.7): design spec for Vivid Target Indicator (selection feedback)
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.
2026-05-15 06:46:55 +02:00
Erik
d82b0648b5 docs(B.6): record Slice 1 trace findings — ACE sends mtRun=0.00, no UP echo
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.
2026-05-14 18:45:17 +02:00
Erik
9e1d33a5f7 docs(B.6): retail decomp settles Option A; revise spec with 4-slice plan
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.
2026-05-14 17:56:35 +02:00
Erik
281d125e9b docs(B.6): design spec for local-player MoveToObject auto-walk (issue #63)
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.
2026-05-14 17:42:16 +02:00
Erik
5c24f6cafe docs(B.5): implementation plan from writing-plans skill 2026-05-14 15:10:23 +02:00
Erik
6ae38f7c6c docs(B.4c): implementation plan — 4 tasks, door spawn-time sequencer + UM diagnostic
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>
2026-05-14 06:50:51 +02:00
Erik
b4f131e5c6 docs(B.4c): design spec for door swing animation
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>
2026-05-14 06:26:57 +02:00
Erik
179e441d11 docs(B.4b): implementation plan — 6 tasks, TDD picker + handler wiring
Task-by-task plan with full code in every step, no placeholders.

Tasks 1+2: WorldPicker.BuildRay + WorldPicker.Pick (TDD: tests first,
8 xUnit Facts total).
Task 3: rename _selectedTargetGuid -> _selectedGuid (mechanical).
Task 4: add 3 OnInputAction switch cases + 3 private helpers
(PickAndStoreSelection, UseCurrentSelection, SendUse).
Task 5: Holtburg inn doorway visual test (user-performed).
Task 6: ship handoff + close #57 + roadmap/CLAUDE.md/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-b4b-design.md.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 17:37:09 +02:00
Erik
ffa404d236 docs(B.4b): correct file paths — WorldPicker lives in AcDream.Core.Selection
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>
2026-05-13 17:31:49 +02:00
Erik
4a1c594887 docs(B.4b): design spec for outbound Use handler wiring
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>
2026-05-13 17:28:18 +02:00
Erik
869677bc88 docs(phys L.2g): slice 1 implementation plan
Step-by-step TDD plan for L.2g slice 1 — dynamic PhysicsState toggling.
Six commits' worth of work + a user-driven visual test at the Holtburg
inn doorway + a ship handoff commit.

Task 1: SetState (0xF74B) wire parser + xUnit tests (TDD).
Task 2: ShadowObjectRegistry.UpdatePhysicsState mutator + tests (TDD).
Task 3: WorldSession dispatcher branch + StateUpdated event.
Task 4: GameWindow subscribes, routes to registry, smoke-tests launch.
Task 5: One-shot hex-dump probe to settle holtburger 12-byte vs ACE
        16-byte payload claim before declaring slice 1 done.
Task 6: Slice 0.5 freebie — extend [entity-source] log with state +
        flags (the handoff's 'slice 1.6' suggestion).
Task 7: User-driven visual verification at Holtburg inn doorway.
Task 8: Ship handoff + CLAUDE.md / plan-of-record updates.

Risk surface: all changes are additive. No resolver edits. No broadphase
edits. No retail-port semantics changes. Per-task revert is safe.

Plan: docs/superpowers/plans/2026-05-12-phase-l2g-slice1.md
Spec: docs/superpowers/specs/2026-05-12-l2g-dynamic-physicsstate-design.md

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 21:52:31 +02:00
Erik
2c10dd4d67 docs(phys L.2g): design spec for dynamic PhysicsState toggling (doors)
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>
2026-05-12 21:00:36 +02:00
Erik
92cd7238ff docs(phys L.2d): design spec for slice 1 BSP-hit diagnostic + L.2d reframe
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>
2026-05-12 19:01:44 +02:00
Erik
1e3c33b4db docs(vfx #C.1.5b): design + plan for issue #56 + EnvCell DefaultScript
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>
2026-05-11 23:51:44 +02:00
Erik
9009318656 docs(vfx #C.1.5a): ship Phase C.1.5a + file issue #56 for per-part collapse
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>
2026-05-11 16:13:12 +02:00
Erik
ed5335b81e docs(vfx #C.1.5a): implementation plan + spec wiring-location fixes
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>
2026-05-11 13:36:18 +02:00
Erik
06d7fbd5ef docs(vfx): Phase C.1.5a — portal PES wiring design spec
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>
2026-05-11 13:28:55 +02:00
Erik
41981c4d74 docs(perf #N6.1): apply final-review fixes — spec, baseline doc, issue #55
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>
2026-05-11 12:51:10 +02:00
Erik
a4931eeaa2 docs(perf): Phase N.6 slice 1 — implementation plan
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>
2026-05-11 11:12:26 +02:00
Erik
05d590cd54 docs(perf): Phase N.6 slice 1 — spec for gpu_us fix + radius=12 baseline
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>
2026-05-11 11:03:44 +02:00
Erik
175ad14f8b Merge branch 'claude/friendly-varahamihira-7b8664' — Tier 1 entity-classification cache (closes #53)
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.
2026-05-11 00:11:42 +02:00
Erik
c7021d8645 docs(phase-m): sharpen Phase M into design spec + opcode coverage matrix
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>
2026-05-10 19:22:49 +02:00
Erik
2f8a574b92 docs(post-A.5 #53): Tier 1 cache — implementation plan (writing-plans)
17-task TDD-style plan for the Tier 1 entity-classification cache, sized
~5-7 days. Phases:

  Phase 1 (Tasks 1-5):  Cache foundation — extract GroupKey, build the
                        cache class with TryGet/Populate/InvalidateEntity/
                        InvalidateLandblock, and 11 pure-CPU tests.
  Phase 2 (Tasks 6-10): Dispatcher integration — plumb landblockId
                        through the walk scratch, inject the cache,
                        wire ClassifyBatches collector + cache-miss
                        populate + cache-hit fast path. +2 integration
                        tests.
  Phase 3 (Tasks 11-12): Invalidation hooks — wire InvalidateEntity from
                        RemoveLiveEntityByServerGuid + InvalidateLandblock
                        from GpuWorldState.RemoveEntitiesFromLandblock
                        via callback (W3b per spec §5.3).
  Phase 4 (Task 13):    DEBUG cross-check — assert membership predicate
                        + DebugCrossCheck method + 2 unit tests via
                        TraceListener capture.
  Phase 5 (Tasks 14-16): Verification — full suite + sentinel + visual
                        gate (user) + perf gate (user, ≤2.0 ms median).
  Phase 6 (Task 17):    Ship — ISSUES + CLAUDE.md + memory + final commit.

Plan resolves spec §11 open implementation choices: W3b for LB invalidation,
GroupKey at namespace internal, ResolveLandblockHint plumbed via walk
scratch, _populateScratch as a field.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 17:06:42 +02:00
Erik
4abb838729 docs(post-A.5 #53): Tier 1 retry — mutation audit + cache design spec
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>
2026-05-10 16:50:26 +02:00
Erik
d3d78fa14f Merge branch 'claude/hopeful-darwin-ae8b87' — Phase A.5 SHIP + Quality Preset system
Phase A.5 — Two-tier Streaming + Horizon LOD shipped.

Headline: 2.3 km terrain horizon (radius=4 near + 12 far) with off-thread
mesh build, fog blend at N₁, mipmaps + 16x AF, MSAA 4x + A2C foliage,
depth-write audit, BUDGET_OVER diag, Quality Preset system (Low/Medium/
High/Ultra) with env-var overrides + F11 mid-session re-apply.

~999 tests pass, 8 pre-existing physics/input failures unchanged.

Two structural-to-A.5 bug fixes shipped post-T26:
- Bug A (9217fd9): far-tier worker strips entities (T13/T16 had only
  wired the controller side; far-tier was loading full entity layers,
  ~71K entities instead of ~10K, 5x perf regression).
- Bug B (0ad8c99): WalkEntities scratch list reused across frames
  (was 480 KB / frame allocation).

Tier 1 entity-classification cache attempted as polish (3639a6f),
reverted (9b49009) — broke animation by caching mutable per-frame
state. Retry deferred to post-A.5 polish phase (ISSUE #53).

Deferred to post-A.5 polish:
- Tier 1 retry with animation-mutation audit (ISSUE #53)
- Lifestone missing visual (ISSUE #52)
- JobKind plumbing through BuildLandblockForStreaming (ISSUE #54)
- Tier 2 (static/dynamic split) + Tier 3 (GPU compute cull) —
  separate multi-week phases. Roadmap at
  docs/plans/2026-05-10-perf-tiers-2-3-roadmap.md.

SHIP commit: 9245db5.
2026-05-10 10:09:03 +02:00
Erik
a28a5b7583 docs(A.5 T27): spec + plan amendments for T22.5 + ship
Spec (2026-05-09-phase-a5-two-tier-streaming-design.md):
- §2 acceptance metrics reshaped from absolute 240 FPS to
  refresh-rate-relative + per-preset (95th-pct ≤ 1000ms/refresh
  standstill; ≤ 1.5× walking) to match the Quality Preset reality.
- New §4.10 Quality Preset System (T22.5): enum Low/Medium/High/Ultra,
  QualitySettings schema, canonical preset values table, env-var
  override table, wiring notes (GameWindow.OnLoad + ReapplyQualityPreset),
  MSAA mid-session unsupported caveat, file list, test count (12).
- New §11 What was deferred: 8 items (Tier 1 cache, lifestone, JobKind
  plumbing, Tier 2/3, ToEntries alloc, InvalidateEntity wiring, High
  preset retest). Former §11 References renumbered to §12.

Plan (2026-05-09-phase-a5-two-tier-streaming.md):
- New Task 22.5 section inserted between T22 and T23: full inline spec
  with schema, preset table, env-var list, wiring steps, acceptance
  criteria, deferred items, commit SHAs. Includes file-name corrections
  (SettingsState → DisplaySettings, DisplayTab → SettingsPanel).
- Self-review cross-check table: new §4.10 row pointing at T22.5.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 10:06:26 +02:00
Erik
9a0dfe03da refactor(net): #13 Parsed.TrailerTruncated + diag logging
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.
2026-05-10 08:26:08 +02:00
Erik
65870349a8 refactor(net): #13 rename Shortcut → ShortcutEntry, expand doc citations
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.
2026-05-10 08:16:01 +02:00
Erik
b373523f98 docs(A.5): two-tier streaming + horizon LOD implementation plan
28-task TDD-first implementation plan for Phase A.5. Maps each spec
section to concrete bite-sized tasks with failing-test → minimal-impl
→ commit cycles. Self-review at plan footer cross-checks coverage,
type consistency, placeholders.

Plan structure:
- T1-T6: StreamingRegion two-tier + 5-list TwoTierDiff with hysteresis
- T7: LandblockStreamResult.Loaded.Tier + MeshData; Promoted variant
- T8: WorldEntity AABB cache + dirty flag
- T9-T12: off-thread mesh build (ConcurrentDictionary + DatLock + worker activation + DI)
- T13-T16: StreamingController two-tier + GpuWorldState two-tier ops + GameWindow wiring
- T17-T18: WbDrawDispatcher bucketing tightening (Change #1 + Change #2)
- T19-T21: visual quality (mipmaps + A2C + depth-write lock-in)
- T22: fog params from N₁/N₂ + env-var multipliers
- T23: BUDGET_OVER flag in DIAG output
- T24-T26: perf baseline (before/after) + visual user gate
- T27-T28: roadmap/ISSUES/CLAUDE.md updates + memory + SHIP commit

Plan at docs/superpowers/plans/2026-05-09-phase-a5-two-tier-streaming.md.
Spec at docs/superpowers/specs/2026-05-09-phase-a5-two-tier-streaming-design.md (fcaff71).

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 08:23:09 +02:00
Erik
d73dcd56ba docs: defer per-instance highlight to open backlog (no scheduled phase)
Reframe the selection-blink follow-up so it doesn't suggest near-term
work. Was listed in N.5 ship record as "Phase B.4 follow-up adds the
field" — now phrased as open backlog with the hook reserved in
mesh_modern.vert's InstanceData comment for whoever eventually picks
it up.

The shader hook itself is unchanged — change is purely doc wording in
the plan SHIP record + CLAUDE.md WB integration cribs.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 22:22:23 +02:00
Erik
dcae2b6b94 phase(N.5): retirement amendment — InstancedMeshRenderer + StaticMeshRenderer + WbFoundationFlag deleted
Final cross-cutting review of N.5 found that Task 15's deletion of
mesh_instanced.vert/.frag left InstancedMeshRenderer orphaned —
ACDREAM_USE_WB_FOUNDATION=0 silently rendered terrain+sky only with
no entities. The SHIP commit's "[x] ACDREAM_USE_WB_FOUNDATION=0 still
works" claim was inaccurate.

Resolution: formal retirement of the legacy renderer path within N.5
instead of deferring to N.6.

Deleted:
- src/AcDream.App/Rendering/InstancedMeshRenderer.cs
- src/AcDream.App/Rendering/StaticMeshRenderer.cs
- src/AcDream.App/Rendering/Wb/WbFoundationFlag.cs

GameWindow simplified — capability detection is unconditional, missing
bindless throws NotSupportedException with a clear message at startup.
WbDrawDispatcher + mesh_modern shader load are mandatory after init.
No escape hatch.

GpuWorldState simplified — WbFoundationFlag.IsEnabled guards on
AddLandblock/RemoveLandblock removed; adapter calls are unconditional
when the adapter is non-null.

PendingSpawnIntegrationTests updated — WbFoundationFlag.ForTestsOnly_ForceEnable
static ctor removed (flag is gone; adapter calls are unconditional).

The ApplyLoadedTerrain physics-data loop was also simplified: the
EnsureUploaded sub-loop that fed InstancedMeshRenderer is gone;
_pendingCellMeshes is now explicitly cleared to prevent unbounded
accumulation (the worker thread still populates it, but WB handles
EnvCell geometry through its own pipeline).

Spec §2 Decision 5 + §10 Out-of-Scope updated. Plan ship-amendment
section added. Roadmap updated (N.5 ships with retirement; N.6 scope
narrowed to perf-only). CLAUDE.md "WB integration cribs" updated.
Perf baseline doc updated. WbDrawDispatcher class summary docstring
corrected to describe the as-shipped SSBO + multi-draw-indirect path.
ISSUES.md #51 updated (terrain not in N.5 scope; deferred to N.7).

Bindless support is now a hard requirement. Modern desktop GPUs
universally expose GL_ARB_bindless_texture + GL_ARB_shader_draw_parameters;
if a user hits the NotSupportedException, that's a real bug report
worth investigating, not a silent fallback.

Build: 0 errors, 0 warnings. Tests: 71/71 (Wb+MatrixComposition+TextureCacheBindless filter).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 22:01:36 +02:00
Erik
38eb999f2c phase(N.5) Task 18: plan finalization — SHIP record appended
Records the as-shipped state: acceptance gate verdicts, plan amendments
captured during execution, code-review adjustments per task, out-of-scope
N.6 follow-ups, and a complete files-changed summary.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 21:13:37 +02:00