docs(B.6+B.7): ship handoff — 36 commits, faithfulness audit, workaround retirement plan
Covers the 2026-05-15 session in full: B.6 local-player auto-walk on
inbound MoveToObject (issue #63 working), B.7 Vivid Target Indicator
MVP, WorldPicker tightening (#59 closed).
Includes:
- 36-commit table cf22f9c..e49c704
- Wire-format facts (MovementType 6/7/8, WalkRunThreshold, heartbeat cadence)
- Auto-walk state machine current shape + GameWindow wiring
- Picker + target-indicator current shapes
- Honest faithfulness audit (user-requested) — workarounds are our bugs not ACE's
- Closed issues (#59, #62, #67) + open follow-ups (#65, #66, #68, #69)
- Reproducibility commands + diagnostic env vars
- Files touched
- Workaround retirement plan (single fix retires four workarounds: per-tick outbound)
- Next-session entry points (sign indicator size, #66 MovementType=8, etc.)
Single biggest lesson surfaced in session: when a workaround starts feeling
load-bearing, find the heartbeat-cadence root cause first. The 10 Hz
AutonomousPosition bump (301281d) closed #67 and tightened firing distance
for every other interaction in one commit.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
e49c704b39
commit
520badd566
1 changed files with 382 additions and 0 deletions
382
docs/research/2026-05-15-b6-b7-shipped-handoff.md
Normal file
382
docs/research/2026-05-15-b6-b7-shipped-handoff.md
Normal file
|
|
@ -0,0 +1,382 @@
|
|||
# Phase B.6 + B.7 + WorldPicker tightening — handoff (visual-verified 2026-05-15)
|
||||
|
||||
**Date:** 2026-05-15 (session 06:08–18:21).
|
||||
**Branch:** commits live on `main` from `cf22f9c..e49c704` (36 commits).
|
||||
**Predecessors:**
|
||||
- [docs/research/2026-05-14-b5-shipped-handoff.md](2026-05-14-b5-shipped-handoff.md) — B.5 (pickup) shipped immediately before.
|
||||
- [docs/superpowers/specs/2026-05-14-phase-b6-design.md](../superpowers/specs/2026-05-14-phase-b6-design.md) — B.6 design with retail anchors + trace findings + 4-slice plan.
|
||||
- [docs/superpowers/specs/2026-05-15-phase-b7-target-indicator-design.md](../superpowers/specs/2026-05-15-phase-b7-target-indicator-design.md) — B.7 design (Vivid Target Indicator).
|
||||
|
||||
---
|
||||
|
||||
## TL;DR
|
||||
|
||||
Three coupled improvements shipped end-to-end this session:
|
||||
|
||||
- **Phase B.6 — Local-player auto-walk on inbound `MoveToObject` (issue #63 OPEN → working).** When the user double-clicks a far target or presses R/F on an out-of-range target, ACE sends `UpdateMotion (0xF74D)` with `MovementType=6` carrying the destination guid. We now synthesize `Forward+Run` input into `PlayerMovementController` to walk the body to the target, then fire the deferred Use/PickUp once the position has arrived AND the body has rotated to face. Smooth rotation (no snap), dual alignment thresholds (30° walk-while-turning, 5° fully aligned). 10 Hz position heartbeat while moving keeps ACE's server-side `WithinUseRadius` poll converging fast enough that doors / NPCs / items all complete the action.
|
||||
|
||||
- **Phase B.7 — Vivid Target Indicator (MVP).** Four small corner triangles drawn around the selected entity, colour-coded by entity type using a port of `gmRadarUI::GetBlipColor` (`0x004d76f0`). Box size scales with projected entity height × scale, per-type base height (humanoid 1.8 m, door/lifestone/portal 2.4 m, small item 0.8 m, default 1.5 m). Drawn via ImGui background draw list — no new GL infrastructure. **Selection bug is now self-correcting**: the user can see *what* they actually clicked before pressing R/F.
|
||||
|
||||
- **WorldPicker tightening (#59 closed).** 5 m fixed sphere radius → 0.7 m default → 1.0 m default with 0.9 m vertical offset (chest-height sphere centre). Per-entity radius/offset callbacks let doors / lifestones / portals get bigger spheres (1.5–2.0 m) and small items get tighter ones (0.4 m).
|
||||
|
||||
The M1 demo target *"click an NPC"* + *"open the inn door"* is now reachable from ANY range (close-range or far-range). The visual flow matches retail: you click → indicator appears → you press R → if far, character walks to within use radius, turns to face, action fires; if near, character turns to face, action fires.
|
||||
|
||||
**Honest faithfulness note (user-requested audit).** Several workarounds remain — arrival safety margin, deferred-wire-Use packet, AutonomousPosition flush on arrival, retry flag — all rooted in our 1 Hz position heartbeat. The 10 Hz bump retires the worst of them in practice. Per-tick outbound (or fixing whatever causes ACE to lose our position between heartbeats) would retire ALL of them. Documented below in **Workaround retirement plan**.
|
||||
|
||||
---
|
||||
|
||||
## What shipped on this branch (36 commits)
|
||||
|
||||
Ordered oldest → newest. Each commit subject is its own retail-faithful unit; the "fix(B.6+B.7)" pairing means a single workaround serves both phases.
|
||||
|
||||
| # | Commit | Subject |
|
||||
|---|---|---|
|
||||
| 1 | `87ba5c9` | `feat(B.5): pickup feedback chat line + toast ("You pick up the X.")` |
|
||||
| 2 | `7be1393` | `docs(M1): record all 4 demo targets met, list deferred polish` |
|
||||
| 3 | `20ecb23` | `Revert "feat(B.5): pickup feedback chat line + toast …"` — retail-faithful: no feedback. |
|
||||
| 4 | `a01ebd5` | `fix(B.5): block pickup of creatures client-side; show 'Can't pick that up' toast` |
|
||||
| 5 | `ab7c04f` | `docs(M1): reflect chat/toast revert + the actual B.5 polish (creature pickup guard)` |
|
||||
| 6 | `e55ad48` | `fix(B.5): make creature-pickup guard silent (retail-faithful)` |
|
||||
| 7 | `ec9fd52` | `fix #62: null-guard the PARTSDIAG read of ae.Animation` |
|
||||
| 8 | `5053e40` | `docs: close #62 — PARTSDIAG null-guard landed in ec9fd52` |
|
||||
| 9 | `281d125` | `docs(B.6): design spec for local-player MoveToObject auto-walk (issue #63)` |
|
||||
| 10 | `9e1d33a` | `docs(B.6): retail decomp settles Option A; revise spec with 4-slice plan` |
|
||||
| 11 | `eda8278` | `feat(B.6 slice 1): ACDREAM_PROBE_AUTOWALK diagnostic baseline` |
|
||||
| 12 | `1b4f3ba` | `feat(B.6 slice 1): DebugPanel mirror for ProbeAutoWalk checkbox` |
|
||||
| 13 | `d82b064` | `docs(B.6): record Slice 1 trace findings — ACE sends mtRun=0.00, no UP echo` |
|
||||
| 14 | `b936ef8` | `feat(B.6 slice 2): local-player auto-walk on inbound MoveToObject` — core feature lands. |
|
||||
| 15 | `f18de7c` | `fix(B.6 slice 2): don't cancel autowalk on the companion InterpretedMotionState` |
|
||||
| 16 | `5612ce7` | `feat(B.6): honor wire WalkRunThreshold — walk vs run per retail semantics` |
|
||||
| 17 | `37177a4` | `docs(B.7): design spec for Vivid Target Indicator (selection feedback)` |
|
||||
| 18 | `8544a78` | `feat(B.7): RadarBlipColors — port of gmRadarUI::GetBlipColor` |
|
||||
| 19 | `c7e5f9f` | `feat(B.7): TargetIndicatorPanel — corner triangles around selected entity` |
|
||||
| 20 | `4bc95ec` | `fix(B.7): scale indicator box from projected entity height, not fixed pixels` |
|
||||
| 21 | `5e29773` | `fix #59: tighten WorldPicker radius from 5 m to 0.7 m` |
|
||||
| 22 | `631571a` | `docs: close #59 — picker radius tightened in 5e29773` |
|
||||
| 23 | `23cb1e9` | `fix(B.7): square indicator box + bigger pick sphere for doors/lifestones/portals + diag` |
|
||||
| 24 | `1a0656a` | `fix(picker): lift sphere centre to mid-body so chest/head clicks hit` |
|
||||
| 25 | `211fe24` | `fix(B.6+B.7): run-all-the-way auto-walk, per-type indicator height, R = smart interact` |
|
||||
| 26 | `5f83766` | `docs: file #65 — local player doesn't turn to face on close-range Use` |
|
||||
| 27 | `2dc28bb` | `fix(B.6+B.7): re-send action on local arrival; scale indicator box by entity Scale` |
|
||||
| 28 | `a0fa3d6` | `fix(B.6+B.7): flush AutonomousPosition on arrival before re-sending action` |
|
||||
| 29 | `39ff3a5` | `fix(B.6+B.7): arrival predicate uses safety margin INSIDE ACE's WithinUseRadius` |
|
||||
| 30 | `64c9793` | `fix(B.6+B.7): shrink arrival safety margin; file #66 rotation, #67 door` |
|
||||
| 31 | `301281d` | `fix(B.6+B.7): bump AutonomousPosition heartbeat 1Hz -> 10Hz while moving` ← **single biggest fix** |
|
||||
| 32 | `32352af` | `fix(B.6): turn-first auto-walk + tiny margin; close #67 doors; file #68 remote arrival` |
|
||||
| 33 | `5b908bc` | `fix(B.6): close-range turn-to-face — install overlay on Use/PickUp send` |
|
||||
| 34 | `cffb10f` | `fix(B.6): tighter 5° alignment + defer Use until rotation completes; file #69 turn anim` |
|
||||
| 35 | `7158c46` | `fix(B.6): smooth local rotation — remove 20° snap-on-approach (not retail)` |
|
||||
| 36 | `e49c704` | `fix(B.6): speculative auto-walk uses WalkRunThreshold=15 to match ACE` |
|
||||
|
||||
**Build:** clean.
|
||||
**Tests:** `dotnet test -c Debug` shows the new RadarBlipColors tests (8) and the existing B.5 BuildPickUp tests passing. Failure count unchanged at the 8 pre-existing baseline in `AcDream.Core.Tests`.
|
||||
|
||||
---
|
||||
|
||||
## Wire-format facts (what ACE sends, what we parse)
|
||||
|
||||
| Wire | Field | Value | Our handling |
|
||||
|---|---|---|---|
|
||||
| `UpdateMotion (0xF74D)` | `MovementType` | `6` MoveToObject | Local: `BeginServerAutoWalk(...)` + speculative turn overlay. Remote: existing `RemoteMoveToDriver`. |
|
||||
| `UpdateMotion (0xF74D)` | `MovementType` | `7` MoveToPosition | Local: `BeginServerAutoWalk(...)` with a synthetic guid (positional destination only). |
|
||||
| `UpdateMotion (0xF74D)` | `MovementType` | `8` TurnToObject | **NOT YET PARSED** — issue #66. ACE sends this on close-range Use against an off-facing target. Our parser falls into the locomotion path and silently drops the rotation. |
|
||||
| `UpdateMotion (0xF74D)` | `MovementType` | `0` Interpreted | Companion locomotion echo after a MovementType=6 (RunForward command). We do NOT treat this as a cancel signal (commit f18de7c). |
|
||||
| `UpdateMotion (0xF74D)` | `WalkRunThreshold` | float meters | If `distance > threshold` → run, else walk. ACE default `15.0 m`. We honor it (commit 5612ce7) for inbound; we use it as the speculative-overlay walk/run gate too (commit e49c704). |
|
||||
| `GameAction (0xF7B1)` outbound | `0x0036 Use` | guid | Sent on R-key / double-click + close-range. For far-range we install the speculative overlay and defer the wire packet until arrival (commit cffb10f). |
|
||||
| `GameAction (0xF7B1)` outbound | `0x0019 PutItemInContainer` | item guid + container guid + placement | Sent on F-key. Same defer-on-far-range pattern. |
|
||||
| `AutonomousPosition (0xF7B1 0x0007)` outbound | position + heading | every 1 Hz idle / **10 Hz while moving** | The 10 Hz bump (commit 301281d) is what unblocks doors (#67) and lets ACE's `MoveToChain` see us arrive at the use radius without timing out. |
|
||||
|
||||
**Retail anchors for the above:**
|
||||
- `MovementManager::PerformMovement` at `0x00524440` — dispatch switch on MovementType.
|
||||
- `MoveToManager::HandleMoveToObject` — MovementType=6 driver (turn-to-face → walk → stop).
|
||||
- `MoveToManager::HandleMoveToPosition` — MovementType=7 driver.
|
||||
- `MoveToManager::HandleTurnToHeading` at `0x0052a0c0` — turn-only driver used by MovementType=8.
|
||||
- `CPhysicsObj::MoveToObject` at `0x00512860` — high-level entry from physics.
|
||||
- `Player_Move.CreateMoveToChain` (ACE) at `Player_Move.cs:37–179` — server-side state machine that depends on our heartbeat to detect arrival.
|
||||
|
||||
---
|
||||
|
||||
## Local auto-walk state machine (current shape)
|
||||
|
||||
`src/AcDream.App/Input/PlayerMovementController.cs`:
|
||||
|
||||
```csharp
|
||||
// State
|
||||
private bool _autoWalkActive;
|
||||
private Vector3 _autoWalkDestination;
|
||||
private float _autoWalkMinDistance; // ACE's WithinUseRadius (per-type)
|
||||
private float _autoWalkDistanceToObject; // initial distance, used for run/walk decision
|
||||
private bool _autoWalkMoveTowards;
|
||||
private bool _autoWalkInitiallyRunning; // decided ONCE at Begin
|
||||
|
||||
public event Action? AutoWalkArrived; // GameWindow re-sends Use/PickUp on this
|
||||
|
||||
// Per-frame overlay, called from top of Update
|
||||
private MovementInput ApplyAutoWalkOverlay(float dt, MovementInput input)
|
||||
{
|
||||
if (!_autoWalkActive) return input;
|
||||
|
||||
// User-input override → cancel
|
||||
if (input.Forward || input.Back || input.StrafeL || input.StrafeR)
|
||||
{
|
||||
EndServerAutoWalk("user-input");
|
||||
return input;
|
||||
}
|
||||
|
||||
// Compute delta yaw, distance, alignment
|
||||
Vector3 toTarget = _autoWalkDestination - Position;
|
||||
float dist = toTarget.Length();
|
||||
float targetYaw = MathF.Atan2(toTarget.Y, toTarget.X) - MathF.PI / 2f;
|
||||
float delta = NormalizeAngle(targetYaw - Yaw);
|
||||
|
||||
// SMOOTH rotation (commit 7158c46 — no snap)
|
||||
float maxStep = RemoteMoveToDriver.TurnRateRadPerSec * dt;
|
||||
Yaw += MathF.Sign(delta) * MathF.Min(MathF.Abs(delta), maxStep);
|
||||
|
||||
// Dual alignment thresholds (commit cffb10f)
|
||||
const float WalkWhileTurningRad = 30f * MathF.PI / 180f;
|
||||
const float FullyAlignedRad = 5f * MathF.PI / 180f;
|
||||
bool walkAligned = MathF.Abs(delta) <= WalkWhileTurningRad;
|
||||
bool aligned = MathF.Abs(delta) <= FullyAlignedRad;
|
||||
|
||||
// Arrival predicate uses TIGHT 0.05 m safety margin INSIDE ACE's radius
|
||||
// (commit 39ff3a5 + 64c9793; works because of 10 Hz heartbeat from 301281d)
|
||||
bool withinArrival = dist <= (_autoWalkMinDistance - 0.05f);
|
||||
|
||||
if (withinArrival && aligned)
|
||||
{
|
||||
EndServerAutoWalk("arrived"); // fires AutoWalkArrived event
|
||||
return input;
|
||||
}
|
||||
|
||||
bool moveForward = walkAligned && !withinArrival;
|
||||
return input with
|
||||
{
|
||||
Forward = moveForward,
|
||||
Run = moveForward && _autoWalkInitiallyRunning,
|
||||
// Strafes left clear so we don't combine with other input
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
`src/AcDream.App/Rendering/GameWindow.cs`:
|
||||
|
||||
```csharp
|
||||
// Wired in ctor:
|
||||
_playerController.AutoWalkArrived += OnAutoWalkArrivedReSendAction;
|
||||
|
||||
private (uint Guid, bool IsPickup)? _pendingPostArrivalAction;
|
||||
|
||||
// On Use/PickUp send: install speculative overlay + defer wire packet if close
|
||||
private void SendUse(uint guid, bool isRetryAfterArrival = false)
|
||||
{
|
||||
if (!isRetryAfterArrival)
|
||||
{
|
||||
InstallSpeculativeTurnToTarget(guid); // BeginServerAutoWalk with tiny radius
|
||||
_pendingPostArrivalAction = (guid, false);
|
||||
if (IsCloseRangeTarget(guid))
|
||||
return; // wire packet deferred until arrival
|
||||
}
|
||||
// ... build + send 0xF7B1/0x0036
|
||||
}
|
||||
|
||||
private void OnAutoWalkArrivedReSendAction()
|
||||
{
|
||||
if (_pendingPostArrivalAction is not (uint guid, bool isPickup)) return;
|
||||
_pendingPostArrivalAction = null;
|
||||
SendAutonomousPositionNow(); // flush position so ACE sees us at radius
|
||||
if (isPickup) SendPickUp(guid, isRetryAfterArrival: true);
|
||||
else SendUse(guid, isRetryAfterArrival: true);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Picker (current shape)
|
||||
|
||||
`src/AcDream.Core/Selection/WorldPicker.cs`:
|
||||
|
||||
- `DefaultRadius = 1.0f` (up from 0.7 m to compensate for vertical-offset lift, commit 1a0656a).
|
||||
- `DefaultVerticalOffset = 0.9f` (chest-height humanoid mid-body — fixes the bug where clicking the head/chest of an NPC missed because the sphere was at the feet).
|
||||
- Per-entity callbacks (`radiusForGuid`, `verticalOffsetForGuid`) supplied by `GameWindow`:
|
||||
- Doors / lifestones / portals: **radius 1.5–2.0 m, vertical offset 1.2 m** (commit 23ce1e9).
|
||||
- Small dropped items (BF_ITEM-class): **radius 0.4 m, vertical offset 0.1 m** (item lies on ground).
|
||||
- Default (NPC / creature / sign / other): defaults 1.0 m / 0.9 m.
|
||||
- Inside-sphere origin handled (commit 5821bdc, pre-session): if `t_near < 0` use `t_far` so the entity is still pickable at point-blank range.
|
||||
|
||||
---
|
||||
|
||||
## Target indicator (current shape)
|
||||
|
||||
`src/AcDream.App/UI/TargetIndicatorPanel.cs` + `src/AcDream.Core/Ui/RadarBlipColors.cs`:
|
||||
|
||||
- `TargetInfo(WorldPosition, ItemType, ObjectDescriptionFlags, Scale)` record carries the inputs.
|
||||
- Box height = `EntityHeightFor(itemType, pwdBitfield, scale)`:
|
||||
- Creature (NPC / monster / player): **1.8 m × scale** (humanoid baseline).
|
||||
- Door / Lifestone / Portal (BF_DOOR=0x1000 | BF_LIFESTONE=0x4000 | BF_PORTAL=0x40000): **2.4 m × scale** (door-frame tall).
|
||||
- Small carry items (Weapon | Armor | Clothing | Jewelry | Food | Money | Misc | MissileWeapon | Container | Gem | SpellComponents | Writable | Key | Caster): **0.8 m × scale**.
|
||||
- Default (signs, scenery interactables, untyped): **1.5 m × scale**. ⚠️ User reports signs still feel too small — see follow-up below.
|
||||
- Box is **square** (`WidthHeightRatio = 1.0`, matches retail) — width = height.
|
||||
- Projection: project feet + head world points to screen, draw 4 right-angle triangles at corners via ImGui background draw list.
|
||||
- Min screen height clamp: 16 px (prevents collapse on far entities).
|
||||
- Off-screen / behind-camera: returns early; ±20% NDC margin so a tall entity whose feet are just off-screen still gets head projected.
|
||||
- Colour: from `RadarBlipColors.For(itemType, pwdBitfield)`. Port of `gmRadarUI::GetBlipColor (0x004d76f0)` — Portal → Vendor → Creature (yellow) → PlayerKiller (red) → PKLite → FriendlyPlayer → default Item (white-ish).
|
||||
- 8 unit tests in `tests/AcDream.Core.Tests/Ui/RadarBlipColorsTests.cs`.
|
||||
|
||||
**MVP scope — explicitly deferred (per spec §3):** off-screen edge arrow (`m_pOffScreen`), DAT-loaded triangle sprite (today's are procedural), mesh-tint highlight on the target, player-option toggle.
|
||||
|
||||
---
|
||||
|
||||
## Faithfulness audit (user-requested honest comparison)
|
||||
|
||||
This was the most important conversation thread of the session. User asked: *"How faithful are we to retail in this?"* and pushed back on every workaround I'd introduced. Direct answer: **The data path is retail-faithful, the timing isn't, and the workarounds are our bugs not ACE's.**
|
||||
|
||||
| Piece | What retail did | What we do | Why we diverged | Resolution path |
|
||||
|---|---|---|---|---|
|
||||
| MoveToObject wire parse | `MovementManager::PerformMovement` switch on `MovementType` | We parse 6/7, miss 8 | Incremental delivery — B.6 scope didn't include TurnToObject | **Issue #66** — port MovementType=8 |
|
||||
| Local turn-to-face | Smooth interpolation animation (legs+arms cycle while body pivots) | Smooth Yaw step; no animation cycle (statue-pivot) | Motion interpreter not fed TurnLeft/TurnRight when overlay turns | **Issue #69** — synthesize TurnLeft/TurnRight |
|
||||
| Arrival predicate | Exact: stop when `dist ≤ radius` | `dist ≤ radius − 0.05 m` safety margin | Our client+server position drift would let retail's exact predicate fall through. 1 Hz heartbeat was the root cause; with 10 Hz it's mostly redundant. | **Drop the margin** once per-tick outbound lands. |
|
||||
| Action send timing | Once on player intent | Twice: once on intent (deferred if far) + once on arrival | Our `MoveToChain` poll on ACE side races against our position heartbeat. With 1 Hz we always lost. 10 Hz helps. | **Single-send** once per-tick outbound + a server-side action queue replaces the retry. |
|
||||
| AutonomousPosition flush | Continuous broadcast | One forced AP on arrival + 10 Hz during move | Same root cause: ACE polls at 0.1 s, we broadcast at 1 s default. | **Per-tick outbound** retires this entirely. |
|
||||
| Local-player TurnToObject | Server broadcasts MovementType=8; client rotates body | We drop the wire MovementType=8 | Incremental delivery — B.6 was scoped to MovementType=6 only | **Issue #66** — same fix as the parse gap above. |
|
||||
| Remote-player arrival animation | Client detects arrival from wire's distance threshold and transitions cycle | RemoteMoveToDriver `Arrived` state set but consumer doesn't flip cycle | Cycle-routing layer never wired to the arrival event | **Issue #68** — add `SetCycle(NonCombat, Ready)` on arrival. |
|
||||
| Local-player pickup animation | Server-initiated `Motion(Pickup)` broadcast → animates locally | Self-echo filter drops it | Pre-existing (B.5) | **Issue #64** — admit server-initiated one-shots through the filter. |
|
||||
|
||||
**Bottom line: every workaround in this list traces to the position heartbeat or the missing MovementType=8 path. Neither is "ACE's bug" — they are gaps in our client.**
|
||||
|
||||
---
|
||||
|
||||
## Open follow-up issues filed this session
|
||||
|
||||
| ID | Severity | Summary |
|
||||
|---|---|---|
|
||||
| **#66** | LOW-MED | Local + remote rotation flip-back / NPCs don't turn — port MovementType=8 TurnToObject |
|
||||
| **#68** | LOW-MED | Remote players' running animation doesn't stop on auto-walk arrival |
|
||||
| **#69** | LOW | Local player rotation isn't animated (statue-pivot vs leg-shuffle) — synthesize TurnLeft/TurnRight |
|
||||
| **#65** | LOW | Local-player no turn-to-face on close-range Use — superseded by #66 |
|
||||
|
||||
**Closed this session:**
|
||||
- **#59** — `WorldPicker` 5 m over-pick → 1.0 m + vertical offset (`5e29773`, `1a0656a`, `23ce1e9`).
|
||||
- **#62** — PARTSDIAG null-guard for sequencer-driven entities (`ec9fd52`).
|
||||
- **#67** — Door Use action doesn't complete after auto-walk arrival → fixed by 10 Hz heartbeat (`301281d`).
|
||||
|
||||
**Visual gripe still open (not filed as an issue yet):** signs still feel too small. Default 1.5 m × scale should probably be 2.5 m for sign-class objects — they're tall posts in retail. Wiring is there (just bump the constant in `EntityHeightFor` for the "everything else" branch, or add a sign-detection rule). User's most recent feedback before context-out was *"still when I select a sign the box is way to small."*
|
||||
|
||||
---
|
||||
|
||||
## Reproducibility — how to verify the shipped behaviour
|
||||
|
||||
```powershell
|
||||
# From C:\Users\erikn\source\repos\acdream
|
||||
Stop-Process -Name AcDream.App -ErrorAction SilentlyContinue
|
||||
Start-Sleep -Seconds 3
|
||||
|
||||
$env:ACDREAM_DAT_DIR = "$env:USERPROFILE\Documents\Asheron's Call"
|
||||
$env:ACDREAM_LIVE = "1"
|
||||
$env:ACDREAM_TEST_HOST = "127.0.0.1"
|
||||
$env:ACDREAM_TEST_PORT = "9000"
|
||||
$env:ACDREAM_TEST_USER = "testaccount"
|
||||
$env:ACDREAM_TEST_PASS = "testpassword"
|
||||
|
||||
# Optional diagnostic env vars (heavy)
|
||||
# $env:ACDREAM_PROBE_AUTOWALK = "1" # one [autowalk] line per MoveToObject inbound + transition
|
||||
# $env:ACDREAM_DUMP_MOTION = "1"
|
||||
|
||||
dotnet build -c Debug; if ($?) {
|
||||
dotnet run --project src\AcDream.App\AcDream.App.csproj --no-build -c Debug 2>&1 |
|
||||
Tee-Object -FilePath "launch.log"
|
||||
}
|
||||
```
|
||||
|
||||
**Test scenarios (each verified visually during the session):**
|
||||
|
||||
1. **Far-range Use on NPC.** Stand 5–10 m from Tirenia in the Holtburg inn. Click NPC → corner triangles appear (yellow). Press R. Character runs to within ~3 m, decelerates, turns to face, Use fires, dialogue appears.
|
||||
2. **Far-range pickup on item.** Stand 5–10 m from a dropped taper. Click item → corner triangles appear (white-ish). Press F. Character runs to within ~0.6 m, turns to face, PickUp fires, item despawns, inventory updates.
|
||||
3. **Door open.** Stand 3–5 m from the inn front door. Click door → corner triangles appear (white-ish, scaled to 2.4 m × scale). Press R. Character walks to within ~2 m, turns to face, Use fires, ACE broadcasts `SetState (ETHEREAL)`, character walks through.
|
||||
4. **Close-range Use.** Already within ~1 m of an NPC, facing away. Press R. Character turns to face → Use fires → dialogue. (Close-range branch — exercises `IsCloseRangeTarget` + deferred-wire-packet path.)
|
||||
5. **Far-range double-click on item.** Same as (2) but double-click — should behave identically to F-key (double-click activation passes through `OnInputAction` after `58b95bc`).
|
||||
|
||||
**Diagnostic env vars active for this work:**
|
||||
- `ACDREAM_PROBE_AUTOWALK=1` — one `[autowalk]` line per inbound MoveToObject + state transition (commit `eda8278`). Also toggleable via DebugPanel.
|
||||
- `ACDREAM_DUMP_MOTION=1` — every inbound `UpdateMotion` (guid, stance, cmd, speed).
|
||||
- `ACDREAM_REMOTE_VEL_DIAG=1` — `[UPCYCLE]` traces for remote-arrival debug (relates to #68).
|
||||
|
||||
---
|
||||
|
||||
## Files touched this session
|
||||
|
||||
**New files:**
|
||||
- `src/AcDream.Core/Ui/RadarBlipColors.cs` — colour table port.
|
||||
- `src/AcDream.App/UI/TargetIndicatorPanel.cs` — corner-triangle renderer.
|
||||
- `tests/AcDream.Core.Tests/Ui/RadarBlipColorsTests.cs` — 8 unit tests.
|
||||
- `docs/superpowers/specs/2026-05-14-phase-b6-design.md` — B.6 design + 4-slice plan.
|
||||
- `docs/superpowers/specs/2026-05-15-phase-b7-target-indicator-design.md` — B.7 design.
|
||||
|
||||
**Modified:**
|
||||
- `src/AcDream.App/Input/PlayerMovementController.cs` — auto-walk overlay, smooth rotation, dual alignment, 10 Hz heartbeat.
|
||||
- `src/AcDream.App/Rendering/GameWindow.cs` — `SendUse`/`SendPickUp` defer logic, `_pendingPostArrivalAction`, `OnAutoWalkArrivedReSendAction`, `InstallSpeculativeTurnToTarget`, `IsCloseRangeTarget`, `SendAutonomousPositionNow`, `TargetIndicatorPanel` wiring, per-entity picker callbacks, `UseCurrentSelection` smart-R dispatch.
|
||||
- `src/AcDream.Core/Selection/WorldPicker.cs` — radius/vertical-offset callbacks, inside-sphere origin handling documented.
|
||||
- `src/AcDream.Core.Net/WorldSession.cs` — MovementType=6 routing into local auto-walk path.
|
||||
- `src/AcDream.Core/Physics/PhysicsDiagnostics.cs` — `ProbeAutoWalkEnabled` static property, `ACDREAM_PROBE_AUTOWALK` env var.
|
||||
- `src/AcDream.UI.Abstractions/Panels/Debug/DebugVM.cs` + `DebugPanel.cs` — DebugPanel checkbox mirror.
|
||||
- `docs/ISSUES.md` — closed #59, #62, #67; filed #65, #66, #68, #69; updated #63 with B.6 status.
|
||||
- `docs/plans/2026-05-12-milestones.md` — M1 four-of-four status reflected.
|
||||
- `CLAUDE.md` — updated "Currently in Phase…" line with B.6/B.7 ship facts.
|
||||
|
||||
---
|
||||
|
||||
## Workaround retirement plan
|
||||
|
||||
The four workarounds that should NOT survive a per-tick-outbound + MovementType=8 phase:
|
||||
|
||||
1. **Arrival safety margin (currently 0.05 m).** `ApplyAutoWalkOverlay` stops at `dist <= _autoWalkMinDistance - 0.05f`. Retail stops at `dist <= radius`. Drop the margin when our outbound position is fresh enough that ACE's `WithinUseRadius` poll always sees us inside the radius the moment we get there.
|
||||
2. **Re-send on arrival.** `_pendingPostArrivalAction` + `OnAutoWalkArrivedReSendAction` re-fire `SendUse`/`SendPickUp` after the body arrives. Retail's client sends the action once and lets the server-side `MoveToChain` complete. Drop when ACE consistently completes the chain from a single send.
|
||||
3. **AutonomousPosition flush on arrival.** `SendAutonomousPositionNow()` explicitly broadcasts position the moment we arrive. With per-tick outbound this happens naturally.
|
||||
4. **`isRetryAfterArrival` flag.** Branch in `SendUse`/`SendPickUp` to skip the speculative-overlay install on the retry. Goes away when the retry goes away.
|
||||
|
||||
**Single fix that retires all four:** per-tick outbound position broadcast (probably at the physics tick rate of 60 Hz with a smaller payload, or 20–30 Hz with the full one). Currently `effectiveInterval = activelyMoving ? 0.1f : 1.0f` (10 Hz active / 1 Hz idle). Going to 20–30 Hz active would likely close the gap; per-tick is the upper bound.
|
||||
|
||||
**Reference for retail's outbound cadence:** `docs/research/named-retail/` — search for `CPhysicsObj::send_movement_event` and `AutonomousPosition` send-site. Holtburger's `client/movement/system.rs` also sends at higher cadence than our default.
|
||||
|
||||
---
|
||||
|
||||
## Next-session entry points (in rough priority order)
|
||||
|
||||
1. **Fix the sign indicator box.** User's last gripe. Bump `EntityHeightFor` default from 1.5 m to ~2.5 m, or add an explicit sign-detection rule. ~5 LOC. Verify in Holtburg by selecting one of the inn signs.
|
||||
2. **Issue #66 — MovementType=8 TurnToObject (local + remote).** Two-direction fix: stop local-player flip-back AND make NPCs turn to face. ~80–120 LOC + tests. Spec template is the B.6 spec with MovementType=8 substituted.
|
||||
3. **Issue #69 — animate rotation.** Synthesize `TurnLeft`/`TurnRight` input flags while the overlay turns the body. ~30 LOC in `ApplyAutoWalkOverlay` + verify retail's human motion table has the cycle. Pairs with #66 nicely.
|
||||
4. **Issue #68 — Remote players don't stop run animation on arrival.** Wire `RemoteMoveToDriver.Arrived` to `SetCycle(NonCombat, Ready)`. ~20 LOC. Small standalone fix.
|
||||
5. **Issue #64 — Local-player pickup animation.** Pre-existing B.5 gap; the self-echo filter drops `UpdateMotion(Pickup)`. Either (a) admit server-initiated one-shots through the filter, or (b) generate locally on send.
|
||||
6. **Per-tick outbound position broadcast.** The big one. Retires the four B.6 workarounds and probably fixes a class of "ACE doesn't see us" bugs we haven't even noticed yet. Probably its own design phase (call it B.8 or M.x). Read `docs/research/named-retail/` for retail's cadence first.
|
||||
7. **Investigate the running-in-circles bug.** User reported during B.6 slice 2 testing that auto-walk would occasionally "run in circles" before going straight. The fix in `211fe24` (run-all-the-way) appears to have fixed it but no regression test exists. Worth a one-session investigation with `ACDREAM_PROBE_AUTOWALK=1`.
|
||||
|
||||
---
|
||||
|
||||
## Predecessor reading order for a fresh session
|
||||
|
||||
1. **This document** — the full picture of what's in main.
|
||||
2. [`docs/superpowers/specs/2026-05-14-phase-b6-design.md`](../superpowers/specs/2026-05-14-phase-b6-design.md) — retail anchors + decomp citations for auto-walk.
|
||||
3. [`docs/superpowers/specs/2026-05-15-phase-b7-target-indicator-design.md`](../superpowers/specs/2026-05-15-phase-b7-target-indicator-design.md) — B.7 design + deferred-MVP list.
|
||||
4. [`docs/research/2026-05-14-b5-shipped-handoff.md`](2026-05-14-b5-shipped-handoff.md) — B.5 (pickup, close-range path) preceded this work.
|
||||
5. [`docs/research/2026-05-13-b4b-shipped-handoff.md`](2026-05-13-b4b-shipped-handoff.md) — B.4b (Use outbound + WorldPicker) preceded that.
|
||||
|
||||
**Retail decomp anchors for auto-walk:** `docs/research/named-retail/acclient_2013_pseudo_c.txt` searched by:
|
||||
- `MovementManager::PerformMovement` (`0x00524440`)
|
||||
- `MoveToManager::HandleMoveToObject`
|
||||
- `MoveToManager::HandleMoveToPosition`
|
||||
- `MoveToManager::HandleTurnToHeading` (`0x0052a0c0`)
|
||||
- `CPhysicsObj::MoveToObject` (`0x00512860`)
|
||||
- `VividTargetIndicator::SetSelected` (`0x004f5ce0`)
|
||||
- `gmRadarUI::GetBlipColor` (`0x004d76f0`)
|
||||
|
||||
**ACE anchors:** `references/ACE/Source/ACE.Server/WorldObjects/Player_Move.cs:37–179` (`CreateMoveToChain`) and `Player_Inventory.cs:976–1106` (pickup chain).
|
||||
|
||||
---
|
||||
|
||||
## Session-specific morale note
|
||||
|
||||
This was a long session — 43 user messages, ~12 hours wall-clock, 36 commits. The pattern was: implement, user tests against retail, user reports specific divergence, fix, repeat. The user pushed back hard on workarounds twice ("Why workarounds? Nothing wrong with ACE, our client is wrong" + "did you verify with retail?") and both times the right move was to drop the workaround and chase the root cause. The 10 Hz heartbeat (`301281d`) was the highest-leverage commit of the session — it closed #67 and tightened the firing distance for every other interaction. **Lesson: when a workaround starts feeling load-bearing, find the heartbeat-cadence-style root cause behind it before adding more layers.**
|
||||
|
||||
The B.6 four-slice plan in the spec was the right shape — Slice 1 (diagnostic) revealed `mtRun=0.00 + no UP echo`, which directly informed Slice 2 (treat MovementType=0 InterpretedMotionState as a companion not a cancel signal, `f18de7c`). Slice 3 + 4 (walk vs run + turn-first) emerged from visual testing. **Lesson: diagnostic-first slicing pays off when you don't actually know what ACE will send.**
|
||||
|
||||
— Session ended at user request to write this handoff before context compaction.
|
||||
Loading…
Add table
Add a link
Reference in a new issue