Commit graph

9 commits

Author SHA1 Message Date
Erik
aba6c9ac7f docs(phys L.2g): slice 1 shipped handoff + B.4 gap discovery + plan-of-record
L.2g slice 1 is CODE-COMPLETE: parser + registry mutator + WorldSession
dispatcher + GameWindow subscriber (4 commits: 2459f28, d538915,
536a608, 108e386). Build clean, 6 new tests pass, baseline-stable
across the full suite. Per-commit + final integration code reviews
all approved.

Visual verification deferred: while running the Holtburg-doorway test,
Phase B.4's outbound Use handler turned out to be unwired. The wire
builders (InteractRequests.BuildUse), classes (SelectionState,
WorldPicker), input-action enums, and keybindings all exist — but
GameWindow.OnInputAction has no case for SelectDblLeft, so the click
silently does nothing. The inbound L.2g chain we just landed can't
fire until something sends an outbound Use.

This commit captures the handoff + reframes next-session work:

  * docs/research/2026-05-12-l2g-slice1-shipped-handoff.md (NEW)
    Full evidence: 4 shipped commits, end-to-end code flow, B.4
    discovery explanation, 4 minor + 1 Important review notes
    (the Important one is a test-coverage gap that the B.4b visual
    test will settle automatically), reproducibility recipe,
    next-session pick.

  * CLAUDE.md
    "Currently in Phase L.2" paragraph: L.2g slice 1 code shipped;
    visual test deferred to B.4b. Next-phase-candidates list:
    L.2g slice 1 (now done) replaced with the B.4b candidate
    pointing at the slice scope.

  * docs/plans/2026-04-29-movement-collision-conformance.md
    L.2g section gains a "Current shipped slice (2026-05-12):" table
    listing the 4 commits.

  * docs/plans/2026-05-12-milestones.md
    M1 phase-list updated: L.2g slice 1 (code) shipped; B.4 renamed
    "B.4 / B.4b" with the gap-discovery note + B.4b shape.

  * docs/ISSUES.md
    New issue #57 (HIGH) for the B.4 interaction-handler gap.
    Promoted to Phase B.4b; will close as
    DONE (promoted to Phase B.4b) when B.4b's design spec lands.

  * Memory file project_interaction_pipeline.md (in personal
    memory dir, not in this commit) updated to reflect reality.

Next session: Phase B.4b (~30-50 LOC, 1-2 subagent dispatches,
~30 min). Subscribe SelectDblLeft -> WorldPicker.Pick ->
InteractRequests.BuildUse -> _liveSession.SendGameMessage. Same
Holtburg-doorway visual test verifies both L.2g slice 1 and B.4b
in one pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 23:17:05 +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
34b7f1faa1 docs(phys L.2d): slice 1 + 1.5 shipped handoff + 3rd plan-of-record reframe
L.2d as scoped is essentially closed at the Holtburg site. The slice-1.5
trace settled the question: the "I can't walk through doorways" symptom
is a closed Door entity (Setup 0x020019FF named "Door") at the building
threshold, not a building-BSP-collision issue. Building BSP is healthy.

The two prior framings turned out wrong:
- L.2a handoff (2026-05-12): "per-cell walkability missing" — based on
  hit attribution pointing at the building, missed the Door cylinder
  also colliding per tick.
- L.2d slice 1 spec (2026-05-13 morning): "BSP shape fidelity, three
  hypotheses X/Y/Z" — ruled out by the trace once the probe labeling
  bug was fixed in slice 1.5.

Handoff doc captures full evidence, side findings (building double-
registration latent bug, missing PhysicsState in entity-source log),
and a candidates list for the next-session ordering discussion.

Plan-of-record L.2d sub-direction paragraph updated to match: "watch-
and-wait" mode, no more slices until a new shape-fidelity bug is
observed at a different site. Door-state handling becomes its own
sub-phase, scope deferred to project-ordering discussion.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 19:46:45 +02:00
Erik
66dc23e087 feat(phys L.2d slice 1): BSP-hit diagnostic probe + plan-of-record correction
Adds ACDREAM_PROBE_BUILDING — a read-only per-shadow-entry probe that
captures full BSP collision evidence whenever TransitionTypes.FindObjCollisions
attributes a hit (via the existing L.2a slice 3 chain). One multi-line
[resolve-bldg] entry per attributed hit: partIdx, hasPhys, bspR vs
vAabbR, world-space entOrigin_lb, and the actual hit polygon's vertices
in both object-local and world space.

Paired with a one-time [entity-source] line at every ShadowObjects.Register
call site in GameWindow so entityId from a probe line is greppable to its
WorldEntity source within a single log file.

Plumbing: BSPQuery writes the resolved hit polygon to a new
PhysicsDiagnostics.LastBspHitPoly side-channel at the 5 SetCollisionNormal
sites in Paths 5/6 + CollideWithPt. TransitionTypes clears that field
before each shadow-entry dispatch and reads it back at the L.2a slice 3
attribution site to emit the probe line.

Spec component 4 originally described an out ResolvedPolygon? parameter
on BSPQuery.FindCollisions; the static side-channel achieves the same
observable behavior without plumbing through BSPQuery's recursive private
methods. Deviation noted in PhysicsDiagnostics.LastBspHitPoly's XML doc.

Reframes the plan-of-record's L.2d sub-direction paragraph: the 2026-05-12
handoff proposed porting CBuildingObj + per-cell walkability, but ACE
BuildingObj.cs:39-52 + named-retail acclient_2013_pseudo_c.txt:701260
show find_building_collisions is one BSP test on Parts[0]. Per-cell
walkability belongs to L.2e, not L.2d. L.2d slice 1 is the diagnostic;
slice 2 is the actual fix scoped from slice 1's evidence (one of three
hypotheses: wrong BSP loaded / over-registered parts / BSPQuery flaw).

Tests: 2 synthetic unit tests in PhysicsDiagnosticsTests.cs pin the
static API contract that the BSPQuery → side-channel → TransitionTypes
emission chain depends on. The multi-line line format itself is verified
by acceptance criterion 2 (live Holtburg-doorway capture) — covering it
here would require a heavy PhysicsEngine + Transition fixture for a
diagnostic-only emission.

Verified: dotnet build green; the 2 new tests pass; the 8 pre-existing
test failures listed in the L.2a handoff (MotionInterpreter GetMaxSpeed_*,
PositionManager.ComputeOffset_BothActive_Combined,
PlayerMovementController.Update_ForwardInput_*, Dispatcher.W_held_*,
BSPStepUpTests.{D4,C3}) remain failing — none introduced by this slice.

Spec: docs/superpowers/specs/2026-05-13-l2d-cbuildingobj-collision-design.md
Conformance anchors:
- acclient_2013_pseudo_c.txt:701260 (CBuildingObj::find_building_collisions)
- acclient_2013_pseudo_c.txt:323725 (BSPTREE::find_collisions)
- ACE references/ACE/Source/ACE.Server/Physics/Common/BuildingObj.cs:39-52

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 19:14:34 +02:00
Erik
eb401e8cac docs(phys L.2a): handoff + next-session prompt + CLAUDE.md / plan-of-record updates
Documents the L.2a-slice-1/2/3 ship for the next session and updates
the project's source-of-truth docs to reflect current state.

New files:
- docs/research/2026-05-12-l2a-shipped-l2d-handoff.md — full cold-start
  handoff: what shipped, three findings (L.2e cell-id format gap, L.2c
  wall-slide working, L.2d sub-direction = CBuildingObj port), branch
  state, diagnostic surface inventory, files changed, open concerns,
  cold-start checklist, reproduction recipe.
- docs/research/2026-05-12-l2d-next-session-prompt.md — terse copy-paste
  prompt for the next Claude Code session.

Modified:
- docs/plans/2026-04-29-movement-collision-conformance.md — added "Current
  sub-direction" note under L.2d capturing the evidence-driven call:
  building-mesh fidelity issue, not door-state-toggle.
- CLAUDE.md — updated "Currently between phases" → "Currently in Phase
  L.2"; added L.2a shipped paragraph; added ACDREAM_PROBE_RESOLVE /
  ACDREAM_PROBE_CELL to Diagnostic env vars; prepended L.2d brainstorm
  to next-phase candidates list.

No code changes in this commit.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 18:15:16 +02:00
Erik
ebef82034e feat(phys L.2a slice 1): resolver + cell-transit probes (PhysicsDiagnostics)
New static `AcDream.Core.Physics.PhysicsDiagnostics` holds two
runtime-toggleable flags initialized from env vars:

- ACDREAM_PROBE_RESOLVE=1 — emit one [resolve] line per
  PhysicsEngine.ResolveWithTransition call: input/target/output
  position+cell, ok-vs-partial, grounded-in, contact-plane status,
  wall normal if hit, walkable-polygon valid, moving entity id.
- ACDREAM_PROBE_CELL=1 — emit one [cell-transit] line per
  PlayerMovementController.CellId change: old → new cell, current
  world position, reason tag (resolver / teleport).

Both also exposed as runtime-toggleable checkboxes in the DebugPanel
"Diagnostics" section. Unlike the existing four Dump-* checkboxes
(which only mirror sticky-at-startup env vars), the two new ones
forward directly to PhysicsDiagnostics — toggling on/off takes
effect on the next physics resolve, no relaunch.

Why now: L.2's plan-of-record (docs/plans/2026-04-29-movement-collision-
conformance.md) explicitly says "Land L.2a diagnostics first. Do not
make another physics change blind." This slice closes the most-load-
bearing gap in L.2a — a general-purpose probe on the resolver outcome
and a cell-transit log — so that later L.2b/c/d/e physics changes can
be evidence-driven instead of guessed. Foundation for the indoor /
dungeon walking trajectory (G.3 unblock).

Pure additive: when both flags are off (default), the probes collapse
to a single static-bool read per resolve, zero log cost. PlayerMovement
Controller's two CellId-mutation sites are now routed through a
private UpdateCellId(reason) helper for diag chokepoint.

Build green, 1032/1040 unit tests pass. The 8 failing tests are
pre-existing on the branch base (verified by stash-and-rerun);
none touch resolver or cell-transit code; all fail identically with
this slice stashed. Investigation deferred to a follow-up.

Refs: docs/plans/2026-04-29-movement-collision-conformance.md (L.2a
shipped-slice note added in same commit).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 17:19:05 +02:00
Erik
b1af56eb19 fix(physics): L.4 — steep airborne hits slide-tangent (interim, deviates from retail)
Phase L.4 closes the "stuck in falling animation on a steep roof" bug
the user reported on 2026-04-30 ("I jump up, I land on it. It should not
even let me land, should just slide with a falling animation"). After
this commit the body no longer sticks to a steep roof when jumping
into it — it slides along the slope while keeping the falling animation.

Two pieces:

1. BSPQuery Path 6 steep-poly slide
   When an airborne sphere hits a polygon whose world normal Z is below
   FloorZ (≈ 0.6642, slope > ~49°), the previous flow was:
   Path 6 SetCollide → Path 4 set_walkable → ContactPlane committed →
   body "lands" on the steep poly with Contact bit + falling animation.
   This left the player stuck mid-slope because OnWalkable was cleared
   but Contact stayed set.

   The new branch detects the steep normal in Path 6 BEFORE SetCollide
   is called. Instead of entering the landing path, it removes the
   into-wall component of the move (project onto the steep face), sets
   CollisionNormal + SlidingNormal, and returns Slid. Same shape as
   Path 5's step-up fallback and CylinderCollision. The resolver retries;
   the sphere is now outside the poly; FindCollisions returns OK;
   ValidateTransition commits the slid position. ContactPlane is never
   set, so the body stays airborne with falling animation.

2. PlayerMovementController L.3a-bounce carve-out + Inelastic stop
   Re-enables the velocity-reflection bounce when the contact normal is
   upward-facing but steeper than walkable (0 < N.Z < FloorZ). The base
   L.3a rule suppresses bounce on landing transitions to avoid micro-
   bounce on flat terrain; that suppression also stuck the player to
   too-steep roofs they shouldn't land on. This carve-out re-enables
   the reflection specifically for the steep upward case.

Also lands related L.2c precipice / edge-slide work that was in flight:

- TransitionTypes EdgeSlideAfterStepDownFailed: walkable-poly-steep
  cliff route + steep-ContactPlane cliff route ordering, so that
  CliffSlide fires when the stored walkable polygon itself is too
  steep (Path 4 had previously accepted it as a "landing" via the
  permissive LandingZ threshold).
- CliffSlide reference-normal selection: prefer LastWalkable, fall back
  to LastKnownContactPlane only when walkable, else use world-up. This
  prevents the cross(steepN, steepN) = 0 degenerate case that left the
  cliff slide as a no-op when both current and last-known were steep.
- Phase 2 / step-down branch / edge-slide branch / cliff-slide
  diagnostic helpers gated on ACDREAM_DUMP_EDGE_SLIDE / ACDREAM_DUMP_STEEP_ROOF.
- Two new airborne-mover regression tests in BSPStepUpTests +
  PhysicsEngineTests covering wall-slide and edge tangent motion.

DEVIATION FROM RETAIL — DOCUMENTED FOR FOLLOW-UP

The Path 6 steep slide is NOT what retail does. Retail's flow on the
same hit is:

  Path 6 SetCollide (no steep check) → Path 4 find_walkable returns
  nothing for steep → Phase 3 reset path: restore_check_pos +
  kill_velocity → return COLLIDED → validate_transition reverts CheckPos
  to CurPos and forces OK.

Net retail behavior: position reverts to pre-failed-move (typically
just below the roof in the common jump-up case), velocity zeroed,
gravity rebuilds Z next frame, body falls back down naturally with
the falling animation. The "freeze" framing I used earlier was wrong;
in the typical case retail just bounces the body off and lets gravity
take over.

Strict retail behavior would match the user's intent better in the
common case AND avoid the bounce-energy-accumulation we saw with the
slide-tangent approach (V grew to ~50 m/s in continuous-contact frames).
However, retail's behavior degenerates in the edge case of an overhead
landing onto a steep slope (body would freeze mid-air above the roof).

This commit ships the slide-tangent fix as an interim "much better"
state per user verification on 2026-04-30. Follow-up work to match
retail strictly: revert Path 6 steep-slide, audit Phase 3 reset to
ensure kill_velocity (matching OBJECTINFO::kill_velocity ->
CPhysicsObj::set_velocity({0,0,0}, 0)) actually fires, and re-test.

Refs:
  - acclient_2013_pseudo_c.txt:323784-323821 (Path 6 SetCollide)
  - acclient_2013_pseudo_c.txt:273191-273239 (Phase 3 reset path)
  - acclient_2013_pseudo_c.txt:272563-272596 (validate_transition revert)
  - acclient_2013_pseudo_c.txt:274467-274475 (kill_velocity)
  - acclient_2013_pseudo_c.txt:282699-282715 (handle_all_collisions bounce)

Tests: 833/833 green.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-30 13:22:07 +02:00
Erik
261322b48e fix(physics): #32 L.2c precipice edge-slide context
Port the first retail precipice-slide slice from named retail/ACE: terrain and BSP walkable hits now preserve polygon vertices, failed step-down edges back-probe to rediscover the walkable polygon, and edge-slide can run precipice/cliff slide instead of only hard-stopping.

Adds pseudocode anchors plus regression coverage for terrain polygon context and loaded-terrain boundary edge-slide.

Co-authored-by: Codex <codex@openai.com>
2026-04-30 08:04:37 +02:00
Erik
d4c3f947d2 docs(physics): Phase L.2 movement collision conformance plan
Formalize Phase L.2 as the active holistic movement/collision program, align the roadmap and architecture docs, file tactical physics follow-ups, and refresh collision memory away from rewrite-from-zero guidance.

Co-authored-by: OpenAI Codex <codex@openai.com>
2026-04-29 21:28:56 +02:00