Commit graph

7 commits

Author SHA1 Message Date
Erik
dc0341e85a fix(core): Phase B.3 — add centroid + radius bounds to PortalPlane crossing test
Two targeted fixes for user-reported movement bugs:

1. Wall bounce: PortalPlane.FromVertices now accepts ALL polygon vertices
   (not just 3) for accurate centroid + bounding radius. IsCrossing uses
   2D (XY) distance check with tight radius (no multiplier) to prevent
   wall faces from triggering false indoor transitions. Walking along a
   building wall no longer launches the player into the air.

2. Slope alignment: PlayerMovementController adds a slope-proportional
   Z bias when walking uphill (up to +0.8 on steep slopes, grounded
   only). Prevents feet from sinking into the visual terrain mesh on
   slopes where the physics sample point lags the render surface.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 19:08:46 +02:00
Erik
41013ce3e3 fix(core+app): Phase B.3 — Setup.StepUpHeight + scenery road exclusion
Four targeted fixes for user-reported movement/visual bugs:

1. Player entity disappearing: GpuWorldState now supports persistent
   entities (MarkPersistent/DrainRescued). The player character survives
   landblock unloads and gets re-injected into the streaming window at
   the current center landblock.

2. Feet sinking into terrain: +0.15 Z bias in PlayerMovementController
   keeps the character model above terrain z-fighting edge cases.

3. Camera after portal teleport: ChaseCamera.Update now called
   immediately after teleport snap so the camera recenters on the new
   position instead of lingering at the pre-teleport location.

4. Scenery on roads: SceneryGenerator now checks road status at the
   final displaced position (not just the origin vertex), catching
   objects that drift from non-road vertices onto road cells.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 18:56:45 +02:00
Erik
ae06f9c0ff feat(net+app): Phase B.3 — portal-space state machine for teleports
PlayerTeleport (0xF751) is a standalone GameMessage (u16 sequence,
align-4). When received, WorldSession fires TeleportStarted(uint sequence).

GameWindow subscribes: OnTeleportStarted sets PlayerMovementController.State
= PortalSpace, freezing all WASD/physics input. OnLivePositionUpdated
detects arrival (different landblock or >100 unit jump on our character guid),
recenters the streaming origin, resolves physics for ground Z, snaps the
player entity + controller, returns State to InWorld, and sends
GameActionLoginComplete directly (matching holtburger's PlayerTeleport
handler: send_login_complete on every portal transition).

PlayerMovementController gains PlayerState enum + early-return guard: if
State == PortalSpace, Update() returns a zero-movement result immediately
so no MoveToState / AutonomousPosition messages are emitted during transit.

WorldSession gains ResetLoginComplete() for callers that need to re-arm
the latch (documented; not called by the teleport path since we send
LoginComplete directly rather than through the PlayerCreate latch).

Opcode source: holtburger/crates/holtburger-protocol/src/opcodes.rs:84
Wire layout: holtburger/crates/.../movement/messages/teleport.rs

Build: 0 errors. Tests: 283 passed, 0 failed.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-12 18:32:41 +02:00
Erik
8252523b8b feat(core): Phase B.3 — CellPortal-based indoor/outdoor transitions in PhysicsEngine
Replace the disabled if(false) outdoor→indoor branch with real portal-plane
crossing logic. LandblockPhysics now carries IReadOnlyList<PortalPlane> Portals
(populated at load time; GameWindow passes Array.Empty for now until Task 3).

Resolve logic:
- Outdoor player: tests all portals where TargetCellId==0xFFFF (outside-facing);
  crossing enters the portal's OwnerCellId.
- Indoor player: tests portals where OwnerCellId==currentCell; crossing to
  TargetCellId==0xFFFF exits to terrain, otherwise transitions room-to-room.
- Landblock boundary crossing: unchanged — candidatePos landblock lookup already
  picks the adjacent block's terrain naturally.

Tests: renamed disabled test → Resolve_OutdoorThroughPortal_TransitionsToIndoor;
added Resolve_IndoorThroughExitPortal_TransitionsToOutdoor and
Resolve_LandblockBoundary_PicksAdjacentTerrain. 274 tests green.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-12 18:22:55 +02:00
Erik
b341193cfe fix(app+core): Phase B.2 — increase step height + resolve initial Z from terrain
Two fixes for the "position never changes when walking" bug:

1. StepUpHeight was 1.0 units — too tight. The player started at
   Z=92.2 (ACE relocation from previous session) but terrain Z was
   ~94, so every movement attempt had a Z delta of 1.8 which
   exceeded the limit. Increased to 5.0 (forgiving for MVP; AC
   default for humans is ~2 from Setup.StepUpHeight).

2. Initial position now resolves through PhysicsEngine with a huge
   step height (100) to snap to the correct terrain Z regardless
   of where the server-sent Z currently is. With indoor transitions
   disabled, this always produces the outdoor terrain height.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 15:11:50 +02:00
Erik
97c17c5bc3 fix(app): Phase B.2 — use server position directly, fix yaw wrap + turn spam
Three more fixes from the diagnostic dump:

1. Initial position: PhysicsEngine.Resolve was mapping the player
   into an indoor EnvCell (foundry at Z=66) when they're standing
   on outdoor terrain at Z=93+. The cell-containment check was too
   aggressive for initial placement. Now uses the server-sent
   position directly — the server already gave us a valid position.

2. Yaw unbounded: mouse delta accumulated without wrapping, growing
   to 24+ radians. Now wraps to [-PI, PI] after every turn.

3. Turn command spam: MouseDeltaX > 0.5 threshold was too low for
   raw pixel deltas. Any mouse jitter triggered turnCmd flips every
   frame → stateChanged=True → MoveToState flood to the server.
   Mouse turning now only affects yaw directly; turn COMMANDS only
   come from A/D keyboard (matching retail client behavior where
   mouse-look doesn't generate a TurnRight/TurnLeft command).

265 tests still green.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 14:58:25 +02:00
Erik
d9cd2b0b1d feat(app): Phase B.2 — PlayerMovementController (input → physics → motion state)
Per-frame controller that reads MovementInput (WASD/ZX/Shift/mouse),
drives PhysicsEngine.Resolve for collision, and tracks motion state
changes for outbound server messages + animation switching. Walk
(~4 u/s) and run (~7 u/s) speeds match AC retail. Heartbeat timer
triggers AutonomousPosition every ~200ms while moving.

5 new tests covering idle, forward, run, turn, and state-change
detection.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 14:27:07 +02:00