# 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)
```
