Code review found the probe passed ObjectInfoState.None; retail's
SmartBox::update_viewer calls init_object(player, 0x5c) =
IsViewer|PathClipped|FreeRotate|PerfectClip (pseudo-C :92864). PathClipped makes
the sweep hard-stop at first contact (TransitionTypes.cs:811) instead of
edge-sliding around corners (which would re-trigger the A8.F camera-cell
instability); IsViewer lets the eye pass through creatures, colliding only with
world geometry. Resolves the spec's slide-vs-stop open question. Also reset
CollideCamera in the Defaults_AreRetailValues baseline test (review: maintenance
trap). Spec §5.1/§11.1 synced.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Bite-sized TDD plan for the swept-sphere camera collision: CollideCamera flag,
ICameraCollisionProbe + PhysicsCameraCollisionProbe (reuses ResolveWithTransition),
RetailChaseCamera slot-in, GameWindow wiring, Camera-menu toggle, visual
acceptance. Also refines the spec from planning findings: the InitPath +radius
sphere-center offset (ToSpherePath/FromSpherePath z-shift) and the deterministic
probe test scope (z-offset round-trip + cellId==0 guard; collision correctness
rides the existing sweep suite + visual).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Design for porting retail's stage-2 camera collision (SmartBox::update_viewer):
sweep a 0.3 m sphere from the head-pivot to the damped eye via the existing
ResolveWithTransition engine (collides both indoor cell walls and GfxObj
building shells, e.g. the cottage cellar per #98/#101), publish the stopped
position as the eye. Fixes the A8.F flap by keeping the eye out of walls so the
camera-cell + portal side-tests stay stable. Self-skip via LocalEntityId; gated
by CameraDiagnostics.CollideCamera (default ON). Corrects the prior
retail-chase-camera spec's "no camera collision" note.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The 2026-05-28 handoff's "uncommitted A8 batch" is stale: 5dc4140 landed
the batch after the handoff. Step 0 reduces to stripping the leftover
ACDREAM_A8_DIAG_* flags (still present in RuntimeOptions + GameWindow).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Faithful port of retail PView recursive portal-clip visibility
(ConstructView/ClipPortals/GetClip) to fix the residual A8 cellar flap.
Key finding: WB has no per-portal recursion — the flat-stencil algorithm
cannot express the fix; the recursion is retail-only. Builder ports as
GL-free CPU math producing a recursively-clipped OutsideView; enforcement
maps onto the existing A8 stencil pipeline. Builds on (does not supersede)
the A8 WB full-port baseline.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Lands the working A8 indoor-rendering and streaming fixes accumulated this
session. User has verified these visually to some degree (e.g. lifestone /
translucent meshes confirmed fine under the FrontFace flip; bridge / wall /
collision regressions confirmed fixed after travel); not every path has been
exhaustively gated. The cellar-flap defect remains OPEN and will be solved
the retail-faithful way via a dedicated brainstorm (see handoff docs).
Rendering core (reviewed, high confidence):
- EnvCellRenderer SSBO stride fix: upload packed Matrix4x4[] (64B) instead of
the 80B CPU InstanceData struct the shader never expected — fixes the
transform/texture "explosion" for any draw with >1 instance (cells that
dedupe to a shared cellGeomId). Real root cause.
- WB-style global FrontFace(CW) + per-batch CullMode carried through the MDI
layout (GroupKey + BuildIndirectArrays + DrawIndirectRange split into
same-cull runs with absolute uDrawIDOffset per run).
- EntitySet partitioning (IndoorPass / OutdoorScenery / LiveDynamic) +
WorldEntity.BuildingShellAnchorCellId so building shells scope to their
dat-derived building cell instead of rendering everywhere.
- RenderOutsideInAcdream (look into buildings from outside) +
CollectVisiblePortalBuildings frustum cull of portal bounds.
- Sky-when-inside-building + per-cell audit probe + GL-state probe.
Streaming / perf (test-covered; not independently code-reviewed this session):
- Near/far priority queues so near work wins over far; PromoteToNear carries
full landblock + mesh data; LandblockEntriesWithoutAnimatedIndex avoids
rebuilding the animated-lookup dict in the hot draw path. Fixes the
bridge-not-appearing / missing-walls / broken-collision-after-travel
regressions and improves post-transition FPS.
Tooling + docs:
- tools/A8CellAudit: offline dat cell/portal/building dumper (portals +
buildings modes) — reproduces the cellar-flap investigation with no launch.
- docs/research cellar-flap root-cause + option-2 handoff (the didInsideStencil
double-duty finding + the WB-recursive design decision + brainstorm prompt),
entity-taxonomy, replan, issue-78 visibility investigation.
Diagnostics retained on purpose: ACDREAM_A8_DIAG_* gates, portal_stencil.vert
provisional pos.w clamp, and the probe families are kept (env-var gated, zero
cost when off) because the pending option-2 cellar-flap brainstorm needs them.
Strip in the option-2 ship commit.
Indoor branch stays behind ACDREAM_A8_INDOOR_BRANCH=1 (default off = pre-A8
visual). Build green; App tests + Core (streaming/dispatcher/loader) tests pass.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replaces the four reverted RR7 variants from 2026-05-27 with a
verbatim port of WB VisibilityManager.RenderInsideOut.
Plan covers 10 tasks across 5 dependency waves:
- Wave 1 (tasks 1-4, 7): extract WbRenderPass, WbFrustum,
EnvCellSceneryInstance/EnvCellLandblock, EnvCellVisibilitySnapshot;
add IndoorCellStencilPipeline.RenderBuildingStencilMask
- Wave 2 (task 5): build EnvCellRenderer with inline RenderModernMDI
- Wave 3 (task 6): wire EnvCellRenderer into landblock streaming
- Wave 4 (task 8): port RenderInsideOutAcdream byte-for-byte
- Wave 5 (task 9): probe trail [envcells]/[stencil]/[draworder]/[buildings]
- Wave 6 (task 10): probe-gated visual verification launch
Process rules carved from RR7 saga:
- No visual gate without probe data first
- No partial WB ports (Steps 1-5 ship together)
- No conceptual adaptations
- Trust-but-verify after every subagent
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Both documents retained for historical reference. The new full-WB-port
design + plan (2026-05-26-phase-a8-wb-full-port-design.md + plan, ea60d1f +
651e7e2) replace them.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Brainstorm-approved design for the A8 R3.5 → restructure pivot. Replaces
the R3.5 v1+v2 frankenstein (terrain twice + depth-clear workaround) with
WB's RenderInsideOut order verbatim: skip initial sky+terrain when inside,
delete the depth-clear, add a stencil-gated sky step inside the indoor
branch so windows show real sky (closes R4 Issue B).
Unifies the two-flag asymmetry (cameraInsideCell lenient + cameraReallyInside
strict) into a single strict cameraInside flag via PointInCell. Grace
mechanism in CellVisibility stays alive for non-render consumers.
Six tasks ahead, in order:
RR0 — pre-restructure falsification spike (Issues A + C on main?)
RR1 — revert R3.5 v1+v2 (38d5374 + 2bfeafd)
RR2 — restructure render frame to WB-faithful order
RR3 — verify SkyRenderer doesn't toggle stencil state
RR4 — visual verification matrix (cottage/cellar/inn/dungeon + transitions)
RR5 — ship docs (close#78; file new follow-ups if pre-existing on main)
Next: superpowers:writing-plans to produce the per-task plan.
Note: the design references two predecessor docs that are currently
untracked in this worktree (entity-taxonomy + phase-a8-replan). Their
contents are read-stable on disk; committing them is a separate concern
(they belong to the prior session's work). The handoff doc this design
continues from is at f90fa2f.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Session-end documentation for the issue #100 ship and the visibility-
culling investigation handoff for the next session.
Three documents land together:
- docs/superpowers/plans/2026-05-25-issue-100-terrain-cutout.md
(the 3-task plan that drove this session's f48c74a / a64e6f2 /
84e3b72 — never committed by Tasks 1-2)
- docs/research/2026-05-25-issue-100-terrain-cutout-handoff.md
(the predecessor session's smoking-gun research that drove the
#100 fix — never committed by the prior session)
- docs/research/2026-05-25-issue-100-shipped-and-culling-handoff.md
(THIS session's handoff: what shipped, what visual-verification
surfaced, the issue family map for #78 + #95 + the new cellar-
stairs finding, root-cause hypothesis, retail anchors, WB
references, do-not-retry list, and pickup prompt for the next
session's investigation + plan + implementation)
Plus two updates to existing files:
- CLAUDE.md — adds a ship paragraph for #100 to the M1.5 progress
block. References the new handoff doc as the next-session pickup
point.
- docs/ISSUES.md #78 — broadens scope from "outdoor stabs visible
through floor" to "outdoor stabs + terrain mesh visible inside
EnvCells". Adds the 2026-05-25 cellar-stairs evidence (per user
direction: not filed as new issue; treated as evidence
reinforcing #78's hypothesis #2). Promotes hypothesis #2 to
"high confidence as of 2026-05-25" and adds the retail anchor
(acclient_2013_pseudo_c.txt:311397 CEnvCell::find_visible_child_cell).
Acceptance criteria broadened to include the cellar-stairs case.
Next session: pickup prompt at the bottom of the new handoff doc
drives a /investigate → writing-plans → subagent-driven-development
pass on indoor-cell visibility culling — the work that closes#78
+ cellar-stairs together, and possibly #95 if the infrastructure
overlaps.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Captures the brainstorm session 2026-05-24 evening after A6.P4 slice 1
(b49ed90) shipped without closing #99. Investigation surfaced the actual
root cause: doors register as a single 14cm × 20cm bounding-cylinder
approximation derived from Setup.Radius/Height fallback. Their real
collision-bearing geometry lives in per-part GfxObj BSPs (3 parts for
Setup 0x020019FF), including the threshold polygon spanning the doorway.
Retail-faithful design: every server-spawned entity registers N shadow
entries (one per CylSphere + one per Sphere + one per Part-with-BSP),
all sharing the same EntityId. UpdatePhysicsState propagates ETHEREAL
flips to all entries via the existing EntityId-iteration path. Unifies
the live-entity and landblock-static registration code paths under one
ShadowShapeBuilder.
Retail anchor: CObjCell::find_obj_collisions → CPhysicsObj::FindObjCollisions
→ CPartArray::FindObjCollisions → CPhysicsPart::find_obj_collisions →
CGfxObj::find_obj_collisions. One PhysicsObj per entity, parts iterated
internally for collision (acclient_2013_pseudo_c.txt:276776-275055).
Five-commit migration sequence; tests at three layers (builder unit tests,
registry behavior tests, live-capture regression pin). Approach A approved
by user 2026-05-24.
Spec stands on its own as M1.5 work; not formally assigned a phase letter
per CLAUDE.md's "don't invent phase numbers on the fly" rule.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Knowledge-preservation pass after the issue #98 cellar-up fix shipped
(`b3ce505`). Closes the saga's documentation loop and plans the next
phase.
Changes:
- docs/research/2026-05-23-a6-p3-issue98-comparison-harness-findings.md
Appended "Resolution 2026-05-24" section: v3 hypothesis falsified,
actual mechanism (head-bump cottage GfxObj floor poly from below)
confirmed, b3ce505 fix shipped, known door regression flagged.
Memory artifacts cross-referenced.
- docs/ISSUES.md
#98 moved to DONE with full resolution writeup + decomp anchors.
#99 filed: door regression at building thresholds (caused by
b3ce505's indoor-primary gate). Closes via A6.P4.
#100 filed: transparent rectangular patches around houses
(terrain rendering). Bisect found commit 35b37df introduced the
hiddenTerrainCells mechanism that collapses 24m outdoor cells
when buildings sit in them; cottage building only fills part of
its cell so the rest of the 24m cell shows the sky-bleeding gap.
Three fix-path options documented.
- docs/superpowers/specs/2026-05-24-phase-a6-p4-retail-shadow-architecture.md
Full A6.P4 design doc. Three-slice plan: (1) query-side portal
expansion to close#99 while preserving #98 fix, (2) port retail's
BuildShadowCellSet at registration time so per-cell semantics match
`CObjCell::find_cell_list`, (3) remove b3ce505 stopgap entirely.
Decomp anchors, file-by-file plan, risk inventory, open questions.
Memory entries written separately (out-of-tree at
~/.claude/projects/.../memory/):
- feedback_retail_per_cell_shadow_list.md
The architectural lesson: retail uses per-cell shadow_object_list
with portal-aware registration; our landblock-wide spatial
registry diverges at indoor/outdoor seams.
- feedback_apparatus_for_physics_bugs.md
The apparatus-first pattern that cracked the saga: live capture +
fixture dump + replay harness. Template for future physics bugs.
Quote rule: "when a physics bug is resisting and you catch
yourself about to ship 'fix attempt N+1 with no new evidence,'
STOP. Build the apparatus first."
- MEMORY.md index updated with both new entries.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds one log line per AdjustOffset call (gated by ACDREAM_PROBE_STEP_WALK)
naming the branch taken (no-cp / no-cp-slide / slide-degenerate /
slide-crease / into-plane / away-plane, optionally +safety-push) plus
zGain = output.Z - input.Z.
No math or control-flow changes — pure observability so the next capture
can disambiguate the three failure-mode hypotheses for the cellar-ramp
climb cap. Re-reading the existing capture (a6-issue98-negpoly-...log)
showed the sphere DOES climb 90.00 -> 92.79 (2.79 m gain), then caps,
contradicting the divergence comparison's "no altitude gain" framing.
The real question is what stops the climb at world Z ~= 92.79 with the
cottage floor still 1.21 m higher. Existing [step-walk] probes wrap
AdjustOffset; this new probe reveals which branch the projection takes.
Fix plan with the four-branch decision tree at
docs/superpowers/plans/2026-05-23-a6-p3-issue98-cellar-up-fix.md.
Test baseline maintained: 1167 + 8.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
15-task TDD plan covering the three pieces of A6.P1:
Phase A — Build the [push-back] acdream probe (Tasks 1-9):
toggle + 3 helpers + 3 emission sites in BSPQuery/Transition,
DebugVM mirror, CLAUDE.md env-var docs.
Phase B — Build the cdb infrastructure (Tasks 10-12):
7-BP cdb script, PowerShell runner, README.
Phase C — Execute 9 captures + findings stub (Tasks 13-15):
PDB-match verify, capture dir + findings stub, scenario captures.
API surface verified against current code: ResolvedPolygon has no
Id property (probe omits poly attribution; cross-ref via time-
adjacent [push-back-cell] line). CheckOtherCells locals are
sp.CheckCellId + cellId + result (verified at TransitionTypes.cs
lines 1418-1473). SpherePath has Collide/InsertType/WalkInterp,
ObjectInfo has State (verified).
Spec: docs/superpowers/specs/2026-05-21-phase-a6-indoor-physics-fidelity-design.md
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Brainstormed + approved 2026-05-21 for M1.5 milestone work. Designs
the cdb probe spike methodology (7 retail breakpoints + new
[push-back] probe) to capture retail's per-tick BSP collision
response state at 9 indoor scenarios (4 buildings + 5 dungeon sites)
and compare against acdream. Working hypothesis: BSPQuery.AdjustSphereToPlane
or its callers over-correct vs retail, producing the family of
indoor symptoms (walls walk through, ping-pong, vibration, multi-Z
falling) plus driving the existing #90 + TryFindIndoorWalkablePlane
workarounds. A6 ships in 4 slices: P1 probe spike, P2 analysis,
P3 surgical fixes, P4 workaround removal + acceptance.
Phase O (DatPath Unification) pre-empted M1.5 and shipped 2026-05-21;
A6 resumes from Phase O state. Phase O only touched rendering/dat
code; indoor physics design is unchanged.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Plan: 7 tasks decomposing spec T2..T9 with bite-sized TDD-style steps,
exact file paths, commit-message templates, and a T4 safety-check
branch (refactor in place if ObjectMeshManager._dats call sites <=20;
fall back to thin adapter otherwise).
Spec fix: §4.1 mesh-pipeline files now correctly placed under
src/AcDream.App/Rendering/Wb/ instead of Core (ObjectMeshManager uses
Silk.NET.OpenGL types from Managed* wrappers, and CLAUDE.md forbids
Core depending on GL). §4.2's layer split (TextureHelpers in Core,
rest in App) was already correct.
Plan task order: T2 (setup) -> T5 (Core helpers, lowest risk) ->
T3 (App GL infra) -> T4 (App mesh pipeline + dat-shim) -> T7 (drop
refs + cleanup) -> T8 (visual verification) -> T9 (ship). T5 moved
earlier than spec order to validate the namespace migration flow on
small-blast-radius files before the load-bearing T4.
Self-review: all 12 spec decisions (O-D1..O-D12) mapped to plan tasks;
placeholders intentional + explained (MIT license body fetched at T2
step 4; commit-message parameters filled at task close).
Spec: docs/superpowers/specs/2026-05-21-phase-o-dat-path-unification-design.md
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
O-T1 audit (REPORT-ONLY) maps acdream's transitive closure on WorldBuilder:
33 files / ~7.7K LOC across Chorizite.OpenGLSDLBackend (28 files) and
WorldBuilder.Shared (5 files). Verdict on O-Q1 (thread-model): SAFE —
adapters run render-thread only; no worker-thread access to WB code.
Spec amendments incorporated via brainstorm:
- O-D7: Refactor ObjectMeshManager to take DatCollection directly (not
via adapter). T4 safety check — fall back to thin adapter if call-site
count >20.
- O-D8: Drop LandSurfaceManager, EnvCellRenderManager, PortalRenderManager,
TerrainRenderManager from the extract list — audit confirmed not reachable
(we have our own ports or never used them).
- O-D9: Promote 3 internal types in Chorizite to public on extraction
(EmbeddedResourceReader, TextureFormatExtensions, BufferUsageExtensions).
- O-D10: Strip [MemoryPackable] from TerrainEntry (we don't serialize).
- O-D11: Namespace AcDream.Core.Rendering.Wb.* for extracted code.
- O-D12: Drop ResolveId + [indoor-upload] NULL_RESULT diagnostic block.
Task breakdown: T6 (EnvCell/portal) eliminated; T5 (stateless helpers)
shrinks to 0.5d; T4 (mesh + refactor) grows to 2.5d. Net effort estimate
holds at ~7.75d.
All originally-open spec questions are now closed (Q1/Q2/Q3/Q4) or
deferred to T3 with an explicit verify step (Q5: SixLabors.ImageSharp
reachability).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Phase O extracts the WB pieces we actually use (mesh pipeline, texture
decode, GL state, scenery, terrain blending, EnvCell/portal decode —
roughly 3-5K LOC) into src/AcDream.Core/Rendering/Wb/, swaps their
dat dependency from DefaultDatReaderWriter to our DatCollection, and
drops the WorldBuilder.Shared + Chorizite.OpenGLSDLBackend project
references. WB stays in references/ as a read reference, not as a
project dependency. MIT attribution in NOTICE.md.
Tagline: ONE thing touches the DATs.
Discipline: verbatim copy first, no "improvements" while extracting.
Refactors land in follow-up phases. Out of scope: re-porting from
retail decomp; perf optimization; API cleanup.
User direction 2026-05-21: pre-empts M1.5. M1.5 paused at its
2026-05-20 baseline; A6/A7 don't touch dat infrastructure so no
rework needed when it resumes.
Files:
- docs/superpowers/specs/2026-05-21-phase-o-dat-path-unification-design.md (new, full spec)
- docs/plans/2026-04-11-roadmap.md (Phase O block inserted before M1.5; M1.5 marked PAUSED)
- CLAUDE.md (Currently-working-toward line updated; M1.5 block marked paused)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Port retail's CTransition::check_other_cells (acclient_2013_pseudo_c.txt
:272717-272798) into Transition.FindEnvCollisions so the foot-sphere
sees walls in EVERY cell it overlaps, not just the one cell the player's
center is in. Closes the Holtburg inn vestibule wall walk-through
(cell 0xA9B40164 has only 4 polys; adjacent 0xA9B40157 has 23 walls
that are never queried today).
Architecture: new CellTransit.FindCellSet overload (preserves the
candidate HashSet that FindCellList currently discards), new private
Transition.CheckOtherCells method (direct port of the retail loop),
one wire-up in FindEnvCollisions between the existing primary-cell BSP
return and the synthesis fall-through. ~380 LOC total.
Out of scope: FindObjCollisions (already landblock-radius broadphased),
synthesis multi-cell search (A3's job), var_4c re-target (defer).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
ISSUES #83 Phase A1. Landblock stabs (entity.Id 0xC0XXYY00+n per
LandblockLoader.cs:55) were being registered with TWO collision
shadows: the correct per-part BSP at `entity.Id*256 + partIdx`, AND a
redundant mesh-AABB-fallback cylinder at `entity.Id`. The fallback
clamped to 1.5m radius, centered at the building's mesh origin,
producing user-reported "thin air" collisions inside cottages and
within 2m of building exteriors.
The fallback was originally designed for canopy-only-BSP procedural
scenery (0x80XXYY00+n) — trees whose BSP covers the canopy but not
the trunk. Landblock stabs have full BSP coverage and don't need it.
Probe evidence (launch-thinair capture):
- 0xC0A9B479 cylinder fallback (Holtburg cottage): 104 hits in a
short capture session, all inside the cottage main room
(cell=0xA9B4013F), ~2m from the building's mesh origin.
- 0xA9B47900 BSP (the actual cottage walls): 52 legitimate hits.
Fix: one new bool _isLandblockStab + one clause in the existing
mesh-AABB-fallback gate.
Spec: docs/superpowers/specs/2026-05-21-cylinder-fallback-dedup-design.md.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Three docs from the indoor walk-miss probe spike landed in commits
27c7284..a2e7a87:
- Spec: design of the [walk-miss] + [floor-polys] diagnostic emissions
with the H1/H2/H3 disambiguation matrix.
- Plan: 3-task TDD implementation plan (flag, aggregator, emissions).
- Findings: live-capture analysis showing H3 (walkable_hits_sphere /
adjust_sphere_to_plane synthesis rejection) is the dominant defect.
817 of 876 ground-contact misses (93%) cluster at dz~0.48 m, while
the 7 HITs all sit at dz~0.46 m — a 2 cm boundary between working
and broken that points at the sphere-overlap math, not the probe
distance. H1 (multi-cell iteration missing) is real but only 3%
of misses, secondary. H2 (probe distance) ruled out.
Next step: line-by-line decomp comparison of FindWalkableInternal /
walkable_hits_sphere / adjust_sphere_to_plane against retail at
acclient_2013_pseudo_c.txt:322032 / :323006 / :326793, then design
the fix in a follow-up session.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Six-task plan for Bug A slice (spec 2026-05-20):
1. Replace synthesis call site with return TransitionState.OK
2. Delete Transition.TryFindIndoorWalkablePlane method + constant
3. Delete IndoorWalkablePlaneTests.cs + TransitionTypesTests.cs
4. Run physics suite, confirm baseline holds
5. Single commit per spec
6. User visual verification (5 scenarios)
Net delta: ~-480 lines. BSPQuery.FindWalkableSphere + its 5 unit tests
retained as the underlying retail-faithful walkable-finder API.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Slice 2 of 2 in the indoor ContactPlane retention phase. Deletes
Transition.TryFindIndoorWalkablePlane + the per-frame synthesis call
+ outdoor-terrain fallthrough + 9 tests. Replaces with bare
return TransitionState.OK; matching retail's BSPTREE::find_collisions
OK path (acclient_2013_pseudo_c.txt:323938). ContactPlane is retained
via the per-tick seed at PhysicsEngine.ResolveWithTransition:583
(init_contact_plane equivalent) or refreshed by BSP Path 3 / Path 4.
Predecessor: de8ffde (Bug B, BSP world-origin fix).
Evidence: launch-cp-probe-postfix-v2.log shows 3150 MISS / 3154
indoor-walkable calls (99.87% miss rate) after Bug B, with user-visible
"stuck falling when brushing upper floor edge" symptom unchanged.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Five-task plan for Bug B slice (spec 2026-05-20). Tasks:
1. Regression test in BSPQueryTests.cs (BSPQuery API contract)
2. Apply Decompose + arg-pass at TransitionTypes.cs:1442
3. Run physics suite, confirm 8-failure baseline holds
4. Commit
5. User visual + probe-equivalence verification
Bug A explicitly deferred to a future slice.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Single-call-site defect in TransitionTypes.cs:1442 — the indoor cell
BSP query invokes BSPQuery.FindCollisions without passing the cell's
world rotation or world origin. Path 3 step-down + Path 4 land write
ContactPlanes with D ≈ 0 instead of the cell's world floor Z.
320 corrupt CP writes per Holtburg session per the [cp-write] probe
capture 2026-05-20.
Fix: decompose cellPhysics.WorldTransform once, pass rotation +
translation. Mirrors the existing correct pattern at :1808 (object
BSP via FindObjCollisions).
This is slice 1 of 2 for the indoor ContactPlane retention phase.
Slice 2 (Bug A — TryFindIndoorWalkablePlane removal) deferred
pending Bug B retest.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Brainstormed spec for resolving the cellar-descent / 2nd-floor /
invisible-obstacle indoor collision regressions reported post-Phase 2.
Root cause: Phase 2 commit eb0f772 introduced TryFindIndoorWalkablePlane
as a stop-gap walkable-plane synthesis when the indoor BSP returns OK.
Its body does a linear first-match XY scan over cellPhysics.Resolved with
no Z-proximity test, so multi-Z indoor geometry (cellars, 2nd floors,
balconies) collapses to wrong-floor selection. Walking UP stairs works
because step_up routes through DoStepDown → TransitionalInsert(5) →
BSPQuery.FindCollisions Path 3 (StepSphereDown) which already uses
FindWalkableInternal — the retail-faithful BSP walkable-finder. The
linear scan only fires in the OK-no-wall branch.
Fix: route TryFindIndoorWalkablePlane through the existing
FindWalkableInternal via a thin new BSPQuery.FindWalkableSphere wrapper.
Extends FindWalkableInternal's signature to expose the hit polyId
(dictionary key, since ResolvedPolygon doesn't carry its own id). Threads
the foot-sphere radius through TryFindIndoorWalkablePlane's signature
(was hardcoded to nothing — used the localFootCenter alone). Deletes
the now-dead PointInPolygonXY helper.
Awaiting user spec review before plan.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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.