diff --git a/docs/plans/2026-04-11-roadmap.md b/docs/plans/2026-04-11-roadmap.md index 91f7100..9e66f68 100644 --- a/docs/plans/2026-04-11-roadmap.md +++ b/docs/plans/2026-04-11-roadmap.md @@ -498,6 +498,93 @@ before porting. --- +### Phase N — WorldBuilder Rendering Migration + +**Goal:** Stop re-porting AC-specific rendering / dat-handling +algorithms. Depend on a fork of `Chorizite/WorldBuilder` (MIT) for +terrain, scenery, static objects, EnvCells, portals, sky, particles, +texture decoding, mesh extraction, and visibility. Acdream keeps its +own network, physics, animation, motion, UI, plugin, audio, chat +layers (those aren't in WB). + +**Why now (2026-05-08):** the scenery edge-vertex bug at landblock +`0xA9B1` was the third subtle porting bug in a quarter (after the +triangle-Z bug and the hover-over-terrain bug). Even when our code +looked byte-identical to WB's, our output diverged. WB renders the +world correctly; the cost of "we re-port retail algorithms" is now +higher than "we depend on WB's tested port." + +**Design + inventory:** + +- `docs/architecture/worldbuilder-inventory.md` — full taxonomy of + what WB has and what we keep porting ourselves. +- `docs/superpowers/specs/2026-05-08-phase-n-worldbuilder-migration-design.md` — + parent design doc. +- `docs/superpowers/specs/2026-05-08-phase-n1-scenery-via-wb-helpers-design.md` — + N.1 detailed design. + +**Integration model:** fork at +`https://github.com/eriknihlen/WorldBuilder` (already created), git +submodule replacing `references/WorldBuilder/` snapshot, project +references in our solution. Long-lived `acdream` branch in the fork +for our deletions/additions; merge upstream `master` periodically. + +**Sub-phases (strangler-fig with feature flags):** + +- **N.0 — Setup.** Submodule + project references + build green. ~1-2 hrs. +- **N.1 — Scenery algorithm calls.** Replace `IsOnRoad` / + `DisplaceObject` / slope-normal calc / rotation / scale inside + `SceneryGenerator.Generate()` with calls to WB's `SceneryHelpers` + + `TerrainUtils`. Tiny adapter `LandBlock → TerrainEntry[]`. Keeps our + data flow + `ScenerySpawn` shape. Feature flag + `ACDREAM_USE_WB_SCENERY=1`. ~1-2 days. +- **N.2 — Terrain math helpers.** Refactor `TerrainSurface.SampleZ` / + `SampleNormal` / `SampleSurface` to call WB's `TerrainUtils.GetHeight` + / `GetNormal` internally. ~1-2 days. +- **N.3 — Texture decoding.** Replace `TextureCache` decode pipeline + with WB's `TextureHelpers`. ~2-3 days. +- **N.4 — Object meshing.** Replace `SetupMesh.cs` + `GfxObjMesh.cs` + with calls to WB's `ObjectMeshManager`. Character-appearance + behaviors (CreaturePalette / GfxObjRemapping / HiddenParts) remain + ours — ACME is the secondary oracle. ~1 week. +- **N.5 — Terrain rendering.** Replace `TerrainChunkRenderer` + + `TerrainAtlas` + `TerrainBlending` with WB's `TerrainRenderManager` + + `LandSurfaceManager` + `TerrainGeometryGenerator`. ~2 weeks. +- **N.6 — Static objects rendering.** Replace `StaticMeshRenderer` + + `InstancedMeshRenderer` with WB's `StaticObjectRenderManager`. + ~2 weeks. +- **N.7 — EnvCells / dungeons.** Replace EnvCell rendering with WB's + `EnvCellRenderManager` + `PortalRenderManager`. ~2 weeks. +- **N.8 — Sky + particles.** Replace sky rendering + particle pipeline + (#36 / C.1 work) with WB's `SkyboxRenderManager` + + `ParticleEmitterRenderer`. ~1 week. +- **N.9 — Visibility / culling.** Replace `CellVisibility` + + `FrustumCuller` with WB's `VisibilityManager`. ~3-5 days. +- **N.10 — GL infrastructure consolidation (optional).** Replace our + `Shader` / `TextureCache` / `SamplerCache` plumbing with WB's + `ManagedGL*` wrappers + `OpenGLGraphicsDevice`. ~1 week. + +**Estimated calendar:** 2-3 months. Engineering effort: 6-8 weeks. + +**Each sub-phase:** +- Ships behind `ACDREAM_USE_WB_=1` flag. +- Has its own conformance test (side-by-side against existing path). +- Visual verification before flag becomes default-on. +- Old code deleted after default-on lands cleanly. + +**N.2-N.10 detailed specs are NOT yet written** — each gets its own +brainstorm + spec when we reach it. + +**Acceptance:** +- All 10 sub-phases shipped, feature flags removed, old rendering code + paths deleted. +- Visual verification at Holtburg + Foundry statue + a representative + dungeon shows no regression vs Phase C.1. +- WB upstream merges into our `acdream` branch are clean (or have + documented conflict-resolution patterns). + +--- + ### Phase J — Long-tail (deferred / low-priority) Not detailed here; each gets its own brainstorm when it becomes relevant. diff --git a/docs/superpowers/specs/2026-05-08-phase-n-worldbuilder-migration-design.md b/docs/superpowers/specs/2026-05-08-phase-n-worldbuilder-migration-design.md new file mode 100644 index 0000000..33f2280 --- /dev/null +++ b/docs/superpowers/specs/2026-05-08-phase-n-worldbuilder-migration-design.md @@ -0,0 +1,223 @@ +# Phase N — WorldBuilder Rendering Migration: Design + +**Date:** 2026-05-08 +**Status:** Design complete, awaiting plan generation for N.1. + +## Goal + +Stop re-porting AC-specific rendering and dat-handling algorithms from +retail decomp. Instead, depend on a fork of WorldBuilder +(`github.com/Chorizite/WorldBuilder`, MIT) for terrain, scenery, static +objects, EnvCells, portals, sky, particles, texture decoding, mesh +extraction, and visibility / culling. Acdream keeps its own network, +physics, animation, motion, UI, plugin, audio, and chat layers — those +are not in WorldBuilder. + +## Why + +acdream has accumulated a recurring pattern of subtle porting bugs in +its own rendering algorithms (the latest: a tree near the road at +landblock `0xA9B1` that retail and WorldBuilder do not show but our +re-port did, despite the algorithm code looking byte-identical to +WorldBuilder's). The triangle-Z bug, the hover-over-terrain bug, and +the edge-vertex spawn bug are all in the same family: small porting +errors that survive surface-level review. + +WorldBuilder is verified by visual inspection to render the AC world +correctly. It uses the same Silk.NET + .NET stack we already target. +It is MIT-licensed. It has fewer subtle bugs because its developers +have run it against the entire client_cell + client_portal dat content +and fixed everything users have reported. + +The cost of "we re-port retail algorithms ourselves" is now higher than +the cost of "we depend on someone else's tested port and inherit their +fixes." Migrating the rendering+dat layer to WorldBuilder is the +right call. + +## Inventory reference + +The full taxonomy of "what WorldBuilder has, what we keep porting +ourselves" lives at +[`docs/architecture/worldbuilder-inventory.md`](../../architecture/worldbuilder-inventory.md). +Before re-implementing any rendering or dat-handling algorithm, **check +the inventory first**. CLAUDE.md is updated to enforce this. + +## Architecture + +### Integration model + +**Fork upstream WorldBuilder, depend on the fork via git submodule.** + +- Fork: `https://github.com/eriknihlen/WorldBuilder` (already created; + upstream: `Chorizite/WorldBuilder`). +- Long-lived branch in fork: `acdream`. Upstream `master` merges into + `acdream` periodically; our acdream-specific changes (delete editor + files, expose hooks for our scene state) live on `acdream`. +- The current read-only snapshot at `references/WorldBuilder/` is + **replaced** by a git submodule pointing at the fork's `acdream` + branch. Existing CLAUDE.md path references and research docs that + cite `references/WorldBuilder/...` keep working. +- Our solution adds two ``s: + - `references/WorldBuilder/Chorizite.OpenGLSDLBackend/Chorizite.OpenGLSDLBackend.csproj` + - `references/WorldBuilder/WorldBuilder.Shared/WorldBuilder.Shared.csproj` +- Transitive NuGet dependencies (`Chorizite.Core`, + `Chorizite.DatReaderWriter.Extensions`, `BCnEncoder.Net`, + `SixLabors.ImageSharp`, `Silk.NET.SDL`, `MP3Sharp`) flow through. +- Editor-only files in WorldBuilder (Modules/Landscape/{Tools, + Commands, Services, Migrations, Hubs}, LandscapeDocument, etc.) stay + in the fork's source tree but are simply not referenced by acdream. + They impose no runtime cost. We can prune later if upstream stays + well-organized. + +### Phasing — strangler fig, subsystem by subsystem + +Each sub-phase is independently shippable behind a feature flag +(`ACDREAM_USE_WB_=1`). After visual verification the flag becomes +default-on, then is removed and the old code is deleted. This gives us +a one-line revert if a phase regresses. + +| # | Sub-phase | Effort | Risk | +|---|---|---|---| +| **N.0** | Submodule + project references + build green | 1-2 hrs | Low | +| **N.1** | Scenery algorithm calls | 1-2 days | Low | +| **N.2** | Terrain math helpers | 1-2 days | Low | +| **N.3** | Texture decoding | 2-3 days | Medium | +| **N.4** | Object meshing (Setup/GfxObj) | 1 week | Medium | +| **N.5** | Terrain rendering (full pipeline) | 2 weeks | High | +| **N.6** | Static objects rendering | 2 weeks | High | +| **N.7** | EnvCells / dungeons | 2 weeks | High | +| **N.8** | Sky + particles | 1 week | Medium | +| **N.9** | Visibility / culling | 3-5 days | Medium | +| **N.10** | GL infrastructure consolidation (optional) | 1 week | Medium | + +Total estimated calendar: 2-3 months. Engineering effort: 6-8 weeks. + +### What WorldBuilder does NOT cover (keep porting from retail decomp) + +- Network protocol (UDP, ISAAC, ACE messages) — keep ours +- Physics: collision, BSP queries, sphere sweeps, walkable validation + — keep ours (partial), continue porting from retail decomp +- Animation: motion sequencer, cycle/non-cycle parts — keep ours +- Movement: WASD → MoveToState wire, remote-entity motion via + UpdateMotion + dead-reckoning — keep ours +- Game UI: chat, vitals, inventory, spell book — keep ours (ImGui + today, custom-toolkit later) +- Plugin API: IGameState, IEvents, IActions, IPacketPipeline, + IOverlay — keep ours (acdream-unique) +- Game events: combat, allegiance, spell casting — keep ours +- Audio (OpenAL pipeline) — keep ours +- TurbineChat + slash commands — keep ours +- Login + character selection flow — keep ours + +Per CLAUDE.md update, these still follow the +"grep named → decompile → verify → port" workflow against retail decomp +at `docs/research/named-retail/`. + +### Network reference posture + +`references/Chorizite.ACProtocol/` (separate Chorizite repo) remains +the Primary Oracle for protocol field order and packed-dword +conventions per CLAUDE.md's reference table. No fork needed there. We +will lean on it harder during future network-conformance phases (Phase +M is already on the roadmap for that). + +## Components + +### N.0 — Setup (must land before N.1) + +**Files / actions:** +- Remove `references/WorldBuilder/` from working tree (it's currently a + checked-in snapshot). Add it back as a submodule pointing at + `git@github.com:eriknihlen/WorldBuilder.git` tracking the `acdream` + branch (created off `master`). +- Add `` entries in + `src/AcDream.Core/AcDream.Core.csproj` and + `src/AcDream.App/AcDream.App.csproj` for the two WB projects. +- Update `.gitmodules` to reflect the new submodule. +- Verify `dotnet build` and `dotnet test` are green. +- Commit. + +**Done criteria:** +- `git submodule status` shows `references/WorldBuilder` at the fork's + `acdream` HEAD. +- Solution builds clean with no new warnings. +- Existing 870+ tests still pass. + +### N.1 — Scenery algorithm calls + +See companion design doc: +[`2026-05-08-phase-n1-scenery-via-wb-helpers-design.md`](2026-05-08-phase-n1-scenery-via-wb-helpers-design.md). + +Brief: replace the algorithm guts inside `SceneryGenerator.Generate()` +with calls to WB's `SceneryHelpers` (Displace, RotateObj, ScaleObj, +ObjAlign, CheckSlope) and `TerrainUtils` (OnRoad, GetNormal). Keep our +data flow, our `ScenerySpawn` shape, our renderer integration. Add a +small adapter `LandBlock → TerrainEntry[]`. + +### N.2-N.10 — separately brainstormed when we get there + +Each sub-phase will get its own brainstorm + spec when we reach it. +Estimating ahead is unreliable for the bigger phases (N.5, N.6, N.7); +we'll know more after N.1 ships and we have hands-on experience with +the WB integration. + +## Risks + +1. **Chorizite.Core dependency footprint.** Each render manager we + take pulls in `Chorizite.Core.Lib` and `Chorizite.Core.Render`. + Mitigation: take the NuGet dep, don't try to strip it. Risk is + mostly cosmetic (an extra package). + +2. **WB's data-flow is editor-shaped.** `LandscapeDocument`, + `LandscapeChunk`, etc. are editor concepts. Mitigation: write small + adapters that produce the editor-shaped data from our dat reads. + Phase N.1 is intentionally chosen to avoid this — we use only the + stateless helpers, not the full `SceneryRenderManager`. Larger + phases (N.5+) will need real adapter layers. + +3. **Upstream divergence.** WB's `master` will keep moving. Mitigation: + merge upstream `master` into our `acdream` branch periodically (at + minimum, before each new phase starts). Our acdream-specific + changes are isolated to deletions and additions on the `acdream` + branch, which merges cleanly with upstream most of the time. + +4. **Behaviors WB doesn't have.** WB is a dat editor; some + in-game-only behaviors (creature appearance via CreaturePalette / + GfxObjRemapping / HiddenParts) aren't in WB and we'll still need to + handle them ourselves at the integration boundary. Mitigation: + ACME's `StaticObjectManager.cs` covers these and is documented in + CLAUDE.md as the secondary oracle for character appearance. + +5. **Visual regression during migration.** Mitigation: feature flag + per phase. Visual verification at known-good locations (Holtburg, + Foundry statue, dungeon entrances) before flag becomes default-on. + +## Testing + +- **N.0:** existing 870+ tests stay green; `dotnet build` clean. +- **N.1:** new conformance test that runs both our `SceneryGenerator` + and a parallel call into WB's helpers against the same fixture data, + asserts identical spawn list. Visual verification at landblock + `0xA9B1` — the offending tree should be gone, Issue #49's missing + scenery should still be visible. +- **N.2-N.10:** each phase will define its own conformance and visual + verification criteria when brainstormed. + +## Documentation impact + +- [x] `docs/architecture/worldbuilder-inventory.md` — created. +- [x] `CLAUDE.md` — updated with new posture (top-level rule + reference + table + per-domain oracle hierarchy). +- [ ] `docs/plans/2026-04-11-roadmap.md` — add Phase N entry alongside + L, M, etc. (this happens in the same commit as the spec). +- [ ] `docs/architecture/acdream-architecture.md` — needs an + acknowledging note that the rendering layer is now WB-backed; can + follow in a later commit, not blocking. + +## Out of scope for this design + +- Phase N.2-N.10 detailed scope (each gets own brainstorm). +- Network conformance work (separate Phase M). +- Animation, physics, motion ports (continue against retail decomp, + not WB). +- UI, plugin, chat work (separate phases, not affected). diff --git a/docs/superpowers/specs/2026-05-08-phase-n1-scenery-via-wb-helpers-design.md b/docs/superpowers/specs/2026-05-08-phase-n1-scenery-via-wb-helpers-design.md new file mode 100644 index 0000000..6ec1b58 --- /dev/null +++ b/docs/superpowers/specs/2026-05-08-phase-n1-scenery-via-wb-helpers-design.md @@ -0,0 +1,191 @@ +# Phase N.1 — Scenery via WorldBuilder Helpers: Design + +**Date:** 2026-05-08 +**Parent design:** [`2026-05-08-phase-n-worldbuilder-migration-design.md`](2026-05-08-phase-n-worldbuilder-migration-design.md) +**Status:** Design complete, awaiting plan generation. + +## Goal + +Replace the algorithm guts of `SceneryGenerator.Generate()` with calls +to WorldBuilder's stateless `SceneryHelpers` and `TerrainUtils`. Keep +our data flow, our `ScenerySpawn` shape, and our renderer integration +unchanged. + +## Why scenery first + +1. **Active bug source.** Issues #48, #49 are scenery-related; the + investigation in this session uncovered another (the road-edge tree + at `0xA9B1`) we couldn't easily root-cause despite our code looking + identical to WB's. +2. **Smallest coherent slice.** Scenery placement uses only stateless + helpers from WB (Displace, OnRoad, GetNormal, CheckSlope, RotateObj, + ScaleObj). No need to take WB's `SceneryRenderManager`, no need for + editor-shaped data flow. +3. **Proves the integration pattern.** Phase N.0 wires up the + submodule + project references. N.1 uses them with a tiny surface + area. If something is wrong with the dependency model, we discover + it cheaply. + +## Architecture + +### What changes + +`src/AcDream.Core/World/SceneryGenerator.cs`: +- Remove our private `IsOnRoad(LandBlock, float, float)` helper. +- Remove our private `DisplaceObject(ObjectDesc, uint, uint, uint)` helper. +- Remove the `RoadHalfWidth` constant. +- Replace inline algorithm calls with WB equivalents (see table below). + +New file `src/AcDream.Core/World/WbSceneryAdapter.cs` (or similar +location — TBD during implementation): +- Helper `BuildTerrainEntries(LandBlock block) → TerrainEntry[]` + converting our `DatReaderWriter.DBObjs.LandBlock` (the dat type) into + the `TerrainEntry[]` shape WB's `TerrainUtils` expects (9×9 grid, + Type/Scenery/Road/Height fields per vertex). +- Helper for `RegionInfo` if needed (small wrapper over our + `Region` dat). + +### Algorithm-call substitution table + +| Today (ours) | Phase N.1 (WB) | +|---|---| +| `IsRoadVertex(raw)` (kept; small util) | unchanged — small predicate, no benefit to swap | +| `IsOnRoad(block, lx, ly)` | `TerrainUtils.OnRoad(new Vector3(lx, ly, 0), terrainEntries)` | +| `DisplaceObject(obj, gx, gy, j)` | `SceneryHelpers.Displace(obj, gx, gy, j)` | +| Slope normal: `TerrainSurface.SampleNormalZFromHeightmap(...)` | `TerrainUtils.GetNormal(region, terrainEntries, lbX, lbY, lbOffset).Z` | +| Slope check: `nz < obj.MinSlope \|\| nz > obj.MaxSlope` | `SceneryHelpers.CheckSlope(obj, normal.Z)` (returns bool) | +| Rotation logic (`AFrame::set_heading` reproduction) | `SceneryHelpers.RotateObj(obj, gx, gy, j, localPos)` (returns Quaternion) | +| Scale logic (LCG + Pow + clamp) | `SceneryHelpers.ScaleObj(obj, gx, gy, j)` (returns float) | + +### What does NOT change + +- The 9×9 vertex loop (`for (x = 0; x < 9; x++) for (y = 0; y < 9; y++)`). +- Scene selection hash. +- Frequency roll. +- `obj.WeenieObj != 0` skip (weenie entries are dynamic spawns). +- Bounds check `lx, ly ∈ [0, 192)`. +- Per-spawn building check using our `buildingCells` HashSet. +- `BaseLoc.Z` offset application. +- `ScenerySpawn` record shape returned to the renderer. +- `Generate()` method signature — same parameters, same return type. + +### What about `obj_within_block`? + +We attempted this during the bug investigation but it's too aggressive +when applied with the model's actual sorting sphere radius (rejects +trees that should be there). WB also doesn't apply it. The retail +behavior we couldn't reproduce stays unreproduced for now — we accept +that as a known minor cosmetic discrepancy and move on. The point of +N.1 is matching WB's behavior, not retail's. If WB and retail +disagree, that's a WB-upstream problem to file separately. + +## Components + +### Files modified + +- `src/AcDream.Core/World/SceneryGenerator.cs` — algorithm-call swap. +- `src/AcDream.Core/AcDream.Core.csproj` — already has WB project ref + from N.0. + +### Files added + +- `src/AcDream.Core/World/WbSceneryAdapter.cs` — `LandBlock → + TerrainEntry[]` and any other small adapters needed. +- `tests/AcDream.Core.Tests/World/SceneryGeneratorWbConformanceTests.cs` + — side-by-side test asserting our generator's output equals what + comes out when the same algorithms are called via WB directly. + +### Files deleted (eventually, after flag is on by default) + +- The deleted helpers in `SceneryGenerator.cs` mentioned above. + +### Feature flag + +Phase 1 of the rollout: `ACDREAM_USE_WB_SCENERY=1` (default off — old +path runs). When the env var is set, the new WB-backed path runs. + +Phase 2 (after visual verification at Holtburg / `0xA9B1`): flag +default-on. Old path can still be reached via +`ACDREAM_USE_WB_SCENERY=0`. + +Phase 3 (one or two sessions later, after no regressions): delete the +flag and the old code paths entirely. + +## Done criteria + +1. `dotnet build` green with no new warnings. +2. All existing tests pass (870+). +3. New conformance test passes: `SceneryGeneratorWbConformanceTests` + runs both code paths against fixture LandBlock data and asserts + identical spawn lists (same ObjectId, same LocalPosition within + 1e-4, same Rotation within 1e-4, same Scale within 1e-4). +4. Visual verification at landblock `0xA9B1` (Holtburg area): + - The offending tree near the road that retail/WB do not show is + **gone** in our render. + - Issue #49's previously missing scenery (the tree from the 9×9 + loop expansion) is **still visible**. + - No new visual regressions in surrounding landblocks during a + brief flight around Holtburg. +5. Issue #49 stays closed; no new issues filed. + +## Risks (Phase-N.1-specific) + +1. **`TerrainEntry` field semantics.** WB packs Type/Scenery/Road/ + Height into the `TerrainEntry` struct in a specific format. Getting + the adapter wrong means OnRoad / scenery selection produces + different results than ours. Mitigation: read + `WorldBuilder.Shared/Modules/Landscape/Models/TerrainEntry.cs` + carefully; cross-check against WB's `TerrainUtils.GetRoad` / + `GetTerrainEntryForCell` to confirm field encoding. +2. **`RegionInfo` dependencies.** WB's `TerrainUtils.GetNormal` takes + a `RegionInfo` parameter. We need to either build a minimal + `RegionInfo` from our `Region` dat or call WB's normal calc + differently. Mitigation: investigate during implementation; expect + this is a small wrapper. +3. **`obj.MaxScale / obj.MinScale` divide-by-zero.** Our code checks + `if (obj.MinScale == obj.MaxScale)` first; WB's `ScaleObj` does the + same per-line review of `references/WorldBuilder/Chorizite.OpenGLSDLBackend/Lib/SceneryHelpers.cs:42-51`. Should be a non-issue. +4. **Rotation quaternion convention.** Our rotation produces + `headingQuat * baseLoc.Orientation`. WB's `RotateObj` calls + `SetHeading` which does its own composition. Need to confirm the + resulting quaternion is the same convention our renderer expects. + Mitigation: the conformance test catches this if it's wrong. + +## Testing + +### Conformance test (new) + +`SceneryGeneratorWbConformanceTests`: +- Construct a synthetic `LandBlock` with known terrain data. +- Run `SceneryGenerator.Generate(...)` with `ACDREAM_USE_WB_SCENERY=0` + and again with `=1`. +- Assert spawn counts equal. +- Assert each spawn's ObjectId, LocalPosition (within 1e-4), Rotation + (within 1e-4 per component), Scale (within 1e-4) are equal. + +### Existing tests + +`SceneryGeneratorTests` covers: road-vertex predicate, edge-vertex +displacement bounds, interior-vertex displacement bounds. These tests +exercise our internal helpers (`IsRoadVertex`, `DisplaceObject`). +After N.1, the `DisplaceObject` test must be either deleted (if we +delete the helper) or replaced (if we keep `IsRoadVertex` as a small +predicate — it's only one bit-test). + +### Visual verification + +User runs the client against ACE locally: +- Navigate to landblock `0xA9B1` (Holtburg). Verify offending tree + near road is gone. +- Confirm Issue #49's tree is still visible. +- Fly around Holtburg, scan visible scenery for any obvious + regression. + +## Out of scope for N.1 + +- Replacing our `SceneryRenderManager` (we don't have one — we have + `SceneryGenerator` producing `ScenerySpawn[]` and the renderer + consuming it directly). N.1 only touches the generator. +- Replacing our terrain math helpers (that's N.2). +- Replacing the static-object renderer (that's N.6). +- Anything in N.2-N.10.