Commit graph

9 commits

Author SHA1 Message Date
Erik
6f05c298cf fix(app+core): Phase B.3 — streaming follows player, AC jump physics
Three fixes for user-reported movement bugs:

1. Character disappears far from spawn: streaming observer now computed
   from _playerController.Position when player mode is active, instead
   of _lastLivePlayerLandblockId which only updates from server echoes
   (never for autonomous moves). The 5x5 streaming window now follows
   the player as they walk.

2. Jump physics from ACE: JumpImpulse=5.0 and GravityAccel=9.8
   matching AC's formula: velocity_z = sqrt(height * 19.6) where
   height = BurdenMod * (JumpSkill / (JumpSkill + 1300) * 22.2 + 0.05)
   For a new char (skill=100, burden=50%): height≈1.31, vz≈5.07.

3. Gravity reduced from 20 to 9.8 (AC's F_GRAVITY constant).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 21:06:42 +02:00
Erik
192e066182 fix(app+core): Phase B.3 — player cull-exempt, jump height, slope Z
Three user-reported movement fixes:

1. Player disappears when facing away: StaticMeshRenderer now accepts
   an alwaysVisibleEntityId. When a culled landblock contains the
   player entity, it is still drawn. Prevents the frustum culler from
   hiding the player character when they walk far from their spawn
   landblock.

2. Jump too high: JumpImpulse reduced from 10.0 to 3.5 (placeholder;
   retail scales by Jump skill value from the server).

3. Slope Z alignment: replaced the frame-delta slope bias with a
   foot-forward sampling approach — sample terrain Z at 1 unit ahead
   in the walk direction and use max(center, foot) as the ground Z.
   Handles multi-grade slopes where the terrain rises faster than a
   single-point sample tracks.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 19:24:50 +02:00
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