acdream/docs/plans/2026-04-13-rendering-rebuild.md
Erik 157ed9d974 fix(movement): jump works locally (airborne velocity preserved)
Two fixes for jump physics:
- Skip ground-snap when velocity Z > 0 (prevents immediate re-landing
  at high framerates where per-frame Z delta < 0.05 snap threshold)
- Guard apply_current_movement velocity write behind OnWalkable check
  (prevents MotionInterpreter.DoMotion from zeroing jump velocity on
  every frame while airborne)
- Guard PlayerMovementController velocity replacement behind OnWalkable
  (preserves momentum during airborne flight)

Jump works locally but server packet not yet sent (BUG-002).
Facing direction mismatch logged as BUG-003.
RunRate not verified as BUG-004.

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

3.5 KiB
Raw Blame History

Rendering Rebuild from ACME — COMPLETE

Port ACME's rendering pipeline to acdream. Each step produces a visually testable result. The animation system stays unchanged (ACME has none — ours is ported from the decompiled client).

Status: All 5 steps shipped 2026-04-13.

Step 1: Port StaticObject shader + instanced rendering

The biggest performance win. Replace per-entity DrawElements with instanced rendering using a shared instance VBO.

From ACME:

  • StaticObject.vert: aInstanceMatrix (mat4 at locations 3-6, divisor=1)
  • StaticObject.frag: sampler2DArray + alpha cutout
  • Instance buffer pattern: single float[] upload per frame

Changes:

  • New shader: mesh_instanced.vert/frag (from ACME StaticObject.vert/frag)
  • Rewrite StaticMeshRenderer to use instance buffer pattern
  • Group entities by (GfxObjId, textureAtlas) → one DrawElementsInstanced per group
  • Per-GfxObj TextureAtlasManager (grow-on-demand, starts at 32 slots)
  • ushort indices (not uint32) for objects

Test: Same visual output, fewer draw calls (check perf overlay).

Step 2: Port Landscape shader + terrain chunks

Replace per-landblock terrain draws with chunk batching.

From ACME:

  • Landscape.vert/frag: 8 packed uvec4 attributes, sampler2DArray terrain + alpha
  • TerrainChunk: N×N landblocks baked into one VBO/IBO
  • TerrainGPUResourceManager: buffer creation + partial updates

Changes:

  • New shader: terrain_acme.vert/frag (from ACME Landscape.vert/frag)
  • New TerrainChunkRenderer (replaces TerrainRenderer)
  • LandblockMesh.Build outputs VertexLandscape-compatible structs
  • Single DrawElements per chunk (multiple landblocks)

Test: Same terrain appearance, one draw call per chunk.

Step 3: Port AdjustPlanes lighting

Replace guessed sun direction with decompiled retail values.

From decompiled:

  • FUN_00532440 (AdjustPlanes): face-normal accumulation + per-vertex lighting
  • DAT constants: sun direction, ambient, diffuse

Changes:

  • LandblockMesh: face-normal accumulation (replaces central differences)
  • Shader uniforms: xLightDirection, xAmbient from decompiled constants
  • Static objects: same lighting model

Test: Side-by-side with retail client shows matching lighting.

Step 4: Port EnvCell portal visibility

Render only visible interior cells.

From ACME:

  • EnvCellManager: portal visibility BFS from camera cell
  • Portal occluder pass: depth-only draw of portal polygons
  • Conditional depth clear when camera is inside a cell

Changes:

  • New CellVisibility system (BFS through CellPortals)
  • Portal occluder depth pass before EnvCell geometry
  • Conditional depth clear in render order

Test: Enter a building — only visible rooms render.

Step 5: Wire animation into instanced pipeline

The AnimationSequencer outputs per-part transforms. These need to flow into the instance buffer alongside static transforms.

Changes:

  • Animated entities write their per-part instance matrices into the shared instance buffer every frame (same buffer as static objects)
  • AnimationSequencer.Advance(dt) is called BEFORE the instance buffer upload so the latest frame's transforms are included

Test: NPCs breathe, player walks — all through the instanced pipeline.

Render Order (target)

1. Terrain (one DrawElements per chunk, PolygonOffset on)
2. Conditional depth clear (if camera inside EnvCell)
3. EnvCell geometry (DrawElementsInstanced, portal visibility culled)
4. Static objects opaque (DrawElementsInstanced, alpha cutout)
5. Static objects translucent (DrawElementsInstanced, blend on)
6. Particles (additive blend — future)