Commit graph

58 commits

Author SHA1 Message Date
Erik
adf626367e docs: comprehensive architecture plan for acdream
The single most important document in the project. Defines:

Architecture: 6-layer stack (Platform → Renderer → Network → World →
Game Objects → Plugin API). The code is modern C#; the behavior
matches the retail client exactly.

GameEntity: the unified entity class that replaces the current
scattered state (WorldEntity + AnimatedEntity + guid dicts + player
controller). Every world object is a GameEntity with PhysicsBody +
AnimationSequencer + CellTracker + MotionInterpreter + AppearanceState.

Per-frame update order: Network → Streaming → Input → Entity tick
(motion → physics → collision → cell → animation) → Render → Plugin.

Execution plan (R1-R8):
  R1: GameEntity refactor (unify scattered state)
  R2: Thin GameWindow (extract to proper systems)
  R3: CellBSP + wall collision (indoor transitions)
  R4: Complete animation state machine
  R5: Lighting from decompiled AdjustPlanes
  R6: Server compliance (authoritative Z, keepalive)
  R7: Interaction (doors, NPCs, chat, inventory)
  R8: Plugin API completion (Lua macros)

Also updates CLAUDE.md to establish the architect role and reference
the architecture doc as the single source of truth.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 14:23:50 +02:00
Erik
4782532c4b research: indoor transition pseudocode from ACE + decompiled analysis
Sprint 2 research for indoor transitions. Documents:
- ACE EnvCell.find_transit_cells: sphere-plane + BSP containment
- ACE SortCell/BuildingObj.check_building_transit: outdoor→indoor
- PortalSide semantics and portal polygon plane testing
- Gap analysis: acdream needs CellBSP, BldPortal list, VisibleCells

Key finding: full accuracy requires CellBSP (physics BSP from dat)
for sphere_intersects_cell. Current PortalPlane.IsCrossing is a valid
approximation. ACME's AABB PointInCell is an intermediate option.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 13:56:16 +02:00
Erik
8402aee703 research: full animation pseudocode from decompiled acclient.exe
Complete pseudocode translation of the retail AC client's animation
system, extracted from chunk_00520000.c. Covers:

- Sequence::update_internal (1021 bytes, the core frame advance loop)
- Sequence::advance_to_next_animation (node transitions)
- Sequence::append_animation (queue management)
- MotionTableManager::PerformMovement (1878 bytes, full state machine)
- AddAnimationsToSequence (transition link → sequence nodes)
- GetStartFramePosition / GetEndFramePosition (reverse playback support)
- AdjustNodeSpeed (negative speed = swapped start/end frames)

Key findings:
- framePosition is a 64-bit DOUBLE, not float
- Negative speedScale swaps startFrame↔endFrame at the node level
- update_internal handles both forward and reverse in one loop
- Frame triggers fire at every integer boundary crossing
- The keyframe slerp lives in the renderer, not the sequencer

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 12:43:44 +02:00
Erik
67b51a3e6f fix(anim): implement adjust_motion — TurnLeft/SideStepLeft play backward
ROOT CAUSE FIX for missing left-side animations.

The AC client's MotionTable has NO cycles for TurnLeft (0x000E),
SideStepLeft (0x0010), or WalkBackward (0x0006). The real client
calls adjust_motion() which remaps these to their right-side
equivalents with NEGATIVE speed before looking up the cycle. Then
multiply_framerate() swaps LowFrame↔HighFrame so the animation
plays backward.

Source: ACE MotionInterp.cs:394-428, decompiled FUN_005267E0.

Changes:
- AnimationSequencer.SetCycle: adds adjust_motion block that remaps
  left→right with speed *= -1 (TurnLeft, SideStepLeft) or
  speed *= -0.65 (WalkBackward = BackwardsFactor)
- LoadAnimNode: when framerate < 0, swaps Low↔High (matching the
  decompiled multiply_framerate)
- GameWindow.UpdatePlayerAnimation: passes original animCommand to
  SetCycle (sequencer handles remapping internally), keeps legacy
  fallback for non-sequencer entities

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 12:17:26 +02:00
Erik
50c0704ada research: complete acclient.exe function map — 70+ identified functions
Maps decompiled functions to their real AC class::method names by
cross-referencing against ACE's C# physics port. Covers:

- CPhysicsObj (13 functions): Euler integration, velocity, gravity,
  collision dispatch, friction, contact checking
- CMotionInterp (18 functions): full motion state machine including
  jump, movement, velocity computation, motion dispatch
- CLandBlockStruct (7 functions): terrain mesh construction, split
  direction, UVs, water depth, normal lighting
- CLandBlock (6 functions): landblock lifecycle, static object loading,
  cell management, neighbor expansion
- LandDefs (4 functions): coordinate system constants and transforms
- Collision/Transition (13 functions): sphere-polygon intersection,
  ray-plane tests, BSP traversal, walkable surface detection,
  slide/step sphere, main collision loop
- WeenieObject vtable (7 entries): confirmed from call site analysis

Also documents PhysicsObj struct layout (13 fields with offsets) and
MotionInterp struct layout (14 fields with offsets).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 23:46:22 +02:00
Erik
4d36756b91 research: full acclient.exe decompilation — 22,225 functions, 688K lines
Complete decompilation of the retail Asheron's Call client using
Ghidra 12.0.4 + pyghidra headless. 22,225 of 22,226 functions
successfully decompiled in 75 seconds.

Output: docs/research/decompiled/ (54 files, 688,567 lines of C)

Key findings already identified:
- CLandBlockStruct::ConstructPolygons at chunk_00530000.c:2270
  (split direction formula with 0x0CCAC033 constants)
- Motion command handlers at chunk_00510000.c (0x45000005 etc)
- Motion interpreter at chunk_00520000.c
- Portal space UI at chunk_004D0000.c and chunk_00560000.c

Next: identify CPhysicsObj, CMotionInterp, collision, and movement
functions by cross-referencing against ACE's C# port.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 23:25:51 +02:00
Erik
370c6e3133 research: decompile acclient.exe terrain/physics via Ghidra headless
Used Ghidra 12.0.4 + pyghidra to decompile 368 functions from the
retail AC client binary (acclient.exe, 4.7MB, 2016).

Output: docs/research/acclient_decompiled.c (13,560 lines)

Confirmed the decompiled code matches ACME's ClientReference.cs:
- ConstructPolygons split formula at ~0x00532610 with constants
  0x0CCAC033, 0x6C1AC587, -0x421BE3BD, -0x519B8F25
- Same 2.3283064e-10 float comparison for split direction

Regions decompiled:
- 0x530000-0x536000: CLandBlockStruct + terrain (85 functions)
- 0x536000-0x540000: nearby functions (168 functions)
- 0x5A9000-0x5AB000: LandDefs region (111 functions)

Tools: tools/decompile_acclient.py (pyghidra headless script)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 23:18:27 +02:00
Erik
5cd776914a docs: movement deep dive — AC2D + holtburger cross-reference
Exhaustive analysis of two working AC clients revealing three critical
findings that reshape acdream's movement system:

1. Server-authoritative Z: neither AC2D nor holtburger computes local
   terrain Z for the player. AC2D sends keys, receives position. Holtburger
   dead-reckons for smoothing but the server overrides.

2. Terrain split formula mismatch: AC2D and ACViewer's render path use
   0x0CCAC033-based FSplitNESW; WorldBuilder (our source) uses a different
   214614067-based physics formula. Our terrain mesh triangulation doesn't
   match the real AC client's, causing Z mismatches on slopes.

3. Movement deduplication: MoveToState sent once per state change, not per
   frame. AutonomousPosition heartbeat every 1 second.

Also adds AC2D to CLAUDE.md reference repos section.

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