docs: reconcile project status and navigation

Add a canonical documentation map, modernize the public README, and align milestone, roadmap, architecture, issue, divergence, and session guidance with the July 20 baseline. Correct the far-teleport residual to issue #153, close visually accepted indicator and terrain-tiling work, record the remaining detail-overlay and build-warning debt, and deprecate the duplicate legacy bug ledger.

Co-authored-by: OpenAI Codex <codex@openai.com>
This commit is contained in:
Erik 2026-07-20 13:00:41 +02:00
parent a755b764bf
commit 6c3bd4ce4b
11 changed files with 429 additions and 155 deletions

View file

@ -15,10 +15,33 @@ What does NOT go here:
## Conventions
- Sequential integer IDs (`#1`, `#2`, …). Commits that close an issue reference the ID in the message (e.g. `fix #3: periodic TimeSync parsing`).
- `Status` is `OPEN`, `IN-PROGRESS`, or `DONE`. DONE items move to the **Recently closed** section at the bottom with closed-date + commit SHA.
- `Status` is `OPEN`, `IN-PROGRESS`, or `DONE`. The status inside each issue is
authoritative. This long-lived file still has completed items interleaved
with open history; physical placement under an older heading is not status.
- New DONE items should move to **Recently closed** when practical. Do not do a
mechanical move of old blocks merely to tidy the file; preserve their
research context until a deliberate archive split.
- Every session: scan OPEN issues at start; promote/close anything we touched during the session before ending.
- Promoting to a Phase: mark as `DONE (promoted to Phase X)` + commit SHA where the Phase entry landed.
## Current queue — 2026-07-20
- **Next visual gate:** R6 locomotion/collision/projectile/teleport comparison
after the complete-root-Frame/object-workset cutover.
- **M3 closing gate:** `#218`, final two-client portal-out/materialization and
indoor observer comparison.
- **Separate rendering gate:** `#225`, lifestone/particle alpha ordering. Its
connected performance, lifetime, and unattended portal routes pass.
- **Carried behavior debt:** `#153` far-teleport unstreamed-edge arrival and
`#116` slide response. TS-50/TS-51 are tracked in the divergence register.
- **Deferred visual fidelity:** `#226` retail landscape detail overlay.
- **Build hygiene:** `#228` records 17 clean-Release test-project warnings;
production compilation and all tests pass.
The [documentation map](README.md) defines how this tactical ledger relates to
the milestone, roadmap, architecture, divergence register, research, and
memory.
## Template
Copy this block when adding a new issue:
@ -44,6 +67,80 @@ Copy this block when adding a new issue:
---
## #228 — Clean Release build emits 17 test-project warnings
**Status:** OPEN
**Severity:** LOW
**Filed:** 2026-07-20
**Component:** tests / build hygiene
**Description:** A fresh `dotnet build AcDream.slnx -c Release --no-restore`
succeeds with zero errors but emits 17 warnings from
`tests/AcDream.Core.Tests`. Earlier current-state prose claimed “zero warnings”
after an incremental gate in which those projects did not recompile.
**Root cause / status:** The warnings are existing nullable-flow (`CS8600`,
`CS8602`, `CS8625`), never-assigned test fixture fields (`CS0649`), and xUnit
analyzer findings (`xUnit1025`, `xUnit1031`, `xUnit2013`, `xUnit2017`). One
duplicate `InlineData` also causes xUnit to skip a duplicate test-case ID during
discovery; the intended unique cases still pass. This is test hygiene, not a
production runtime failure.
**Files:** `tests/AcDream.Core.Tests/Conformance/DatConcurrencyStressTests.cs`;
`tests/AcDream.Core.Tests/Physics/Motion/RemoteChaseEndToEndHarnessTests.cs`;
`tests/AcDream.Core.Tests/Physics/CellGraphMembershipTests.cs`;
`tests/AcDream.Core.Tests/Physics/DoorSetupGfxObjInspectionTests.cs`;
`tests/AcDream.Core.Tests/Physics/MotionInterpreterDoMotionFamilyTests.cs`;
`tests/AcDream.Core.Tests/Rendering/Wb/AcSurfaceMetadataTableTests.cs`;
`tests/AcDream.Core.Tests/Physics/MotionInterpreterTests.cs`;
`tests/AcDream.Core.Tests/Physics/CellArrayTests.cs`;
`tests/AcDream.Core.Tests/Streaming/StreamingControllerTwoTierTests.cs`.
**Acceptance:** A non-incremental Release solution build reports zero warnings
and zero errors without suppressing analyzers or weakening nullable checking;
the full 6,452-pass / 5-skip suite remains green.
---
## #226 — Retail landscape detail-texture overlay is not rendered
**Status:** OPEN — deferred visual fidelity; the user-visible tiling regression
in #155 is fixed
**Severity:** LOW
**Filed:** 2026-07-20
**Component:** rendering / terrain material
**Description:** Retail can overlay a high-frequency landscape detail texture,
faded by viewer distance and gated by the Environment Detail Textures setting.
acdream now repeats every base/overlay/road surface at its authored
`TerrainTex.TexTiling`, which fixed the stretched/blurry symptom in #155, but
does not yet render this separate optional detail layer.
**Root cause / status:** The earlier #155 investigation conflated two retail
mechanisms. `bb5acab9` ported the behavior that produced the observed mismatch:
`TexMerge::CopyAndTile`/`Merge` pass each source's authored base tiling into the
terrain composition. The still-missing detail pass is a distinct
`LScape::GenerateDetailSurfaces`/`ACRender::landPolyDraw` path. The first
experimental detail-array implementation sampled the wrong neutral/data
contract and was reverted rather than shipping a darkened ground. TS-52 records
the current divergence.
**Files:** `src/AcDream.App/Rendering/TerrainAtlas.cs`;
`src/AcDream.App/Rendering/TerrainModernRenderer.cs`;
`src/AcDream.App/Rendering/Shaders/terrain_modern.frag`.
**Research:** `docs/research/2026-07-13-retail-terrain-texture-tiling-pseudocode.md`
covers the now-shipped base contract. The detail symbols cited above must be
distilled into a dedicated pseudocode note as the first #226 implementation
step; the reverted experiment remains available in git history.
**Acceptance:** With retail Environment Detail Textures enabled, close ground
shows the same high-frequency detail and distance fade without changing base
color/brightness. Disabling it produces the already-accepted authored base
tiling.
---
## #225 — Scene particles overpaint translucent world objects
**Status:** IN-PROGRESS — implementation/reviews and connected stress gate pass; final visual gate pending
@ -172,6 +269,16 @@ The run was performed over RDP, but software pacing was explicitly disabled and
`pace` remained zero, so these are engine-throughput measurements rather than
remote-display refresh measurements. The lifestone/particle visual gate remains.
The 2026-07-20 unattended R6 rebaseline repeated the seven-destination route
without an interactive desktop and exercised semantic turn, forward, charged
jump, and combat input through the production dispatcher. All seven portal
materializations, outbound movement gates, same-location memory/update checks,
fatal-log scan, and graceful WM_CLOSE teardown passed. Caul update p95 was
1.2 ms on the first visit and 1.1 ms on return; Sawato was 0.2/0.3 ms. This is
additional lifetime/correctness evidence, not a replacement for the translucent
lifestone visual check. See
`docs/research/2026-07-20-connected-r6-soak.md`.
**Files:** `src/AcDream.App/Rendering/RetailAlphaQueue.cs`;
`src/AcDream.App/Rendering/Wb/WbDrawDispatcher.cs`;
`src/AcDream.App/Rendering/ParticleRenderer.cs`;
@ -210,7 +317,7 @@ degrade FPS, and revisiting an expired region restores its objects.
## #224 — Gameplay indicator bar only implemented effect icons
**Status:** IN-PROGRESS
**Status:** DONE — 2026-07-17, user visually confirmed after detail-panel polish
**Severity:** MEDIUM
**Filed:** 2026-07-17
**Component:** retained UI / gameplay indicators / session telemetry
@ -249,6 +356,13 @@ selected spell name/description now reflows to the authored width and its own
`0x10000127` scrollbar works independently of the upper effect-list scrollbar.
Vitae recovery XP also uses retail's grouped integer presentation.
**Resolution:** `52c529be`, `a96767ba`, `d1d60310`, `16c21e29`, and
`82789eea` ported the strip, shared pages, retail Vitae/Character Information,
button state, description wrapping, independent lower scrolling, and grouped
XP. The user accepted the resulting pages. Transport packet-loss averaging and
Mini Game gameplay remain explicitly tracked by AP-110 rather than keeping this
visible UI issue open.
**Files:** `src/AcDream.App/UI/Layout/IndicatorBarController.cs`;
`src/AcDream.App/UI/Layout/DatWidgetFactory.cs`;
`src/AcDream.Core.Net/WorldSession.cs`; `src/AcDream.App/UI/RetailUiRuntime.cs`.
@ -2618,42 +2732,42 @@ BCL-only.
**2026-07-09 triage:** investigated, verdict STILL_OPEN — a fix for this exact bug (`9444a328`, normalizing the panel root to (0,0) for off-screen FBO captures) exists but only on unmerged branches (`codex/mockup-stage` / `claude/peaceful-visvesvaraya-e0a196`); `StudioWindow.cs` on this branch still has no position-normalization code and the mechanism is unfixed here.
## #155 — Outdoor terrain textures look stretched + blurry vs retail (missing landscape DETAIL-texture overlay)
## #155 — Outdoor terrain textures looked stretched and blurry vs retail
**Status:** OPEN — **root cause VERIFIED (oracle-first); fix attempt implemented + REVERTED** (rendered the ground black). Ready for a focused fresh implementation pass.
**Severity:** MEDIUM (visible — outdoor ground reads soft/stretched everywhere vs retail's crisp ground; user side-by-side screenshots)
**Status:** DONE — 2026-07-13, `bb5acab9`, user visually confirmed
**Severity:** MEDIUM
**Filed:** 2026-06-25
**Component:** render — terrain texturing (WB-derived modern path: `TerrainAtlas` / `TerrainModernRenderer` / `terrain_modern.frag`)
**Component:** rendering / terrain texture composition
**Description (user-observed, 2026-06-25):** Outdoor terrain textures look stretched and less detailed than retail — a cobblestone road's stones look too big/blurry, grass is softer. Side-by-side: retail crisp, acdream smeared.
**Description:** Cobblestone, roads, and grass appeared enlarged and soft even
though the high-resolution source surfaces, mipmaps, and anisotropic filtering
were active.
**Root cause (VERIFIED — decomp + dat, not guessed):** acdream renders one base texture tiled **once per 24 m landcell** (`terrain_modern.frag:49` `const float TILE = 1.0;`, `vBaseUV` 0→1 per cell) and **ignores the dat's detail-texture fields entirely**. Retail overlays a high-frequency **landscape detail texture** on the base ground; that overlay is what keeps retail's ground crisp up close. Filtering is NOT the problem (the ground array already has mipmaps + `LinearMipmapLinear` + 16× anisotropy, `TerrainAtlas.cs:187-193`); resolution is NOT the problem (terrain is 512² in both Portal and HighRes).
**Corrected root cause / resolution:** The first investigation attributed the
whole symptom to retail's separate detail-texture overlay and incorrectly
claimed the base `TerrainTex.TexTiling` field did not exist. The later
named-retail/DAT audit disproved that claim. Retail
`TexMerge::CopyAndTile @ 0x00503580` and `TexMerge::Merge @ 0x005038C0` pass
each base, overlay, and road surface's authored `TexTiling` into composition.
acdream had hard-coded one repeat per 24-metre landcell. `bb5acab9` carries the
field through `TerrainAtlas`, uploads a layer-indexed table, and applies it in
the modern shader while leaving cell-scale alpha masks unchanged. The user
confirmed the outdoor textures now match the expected scale.
**Verified retail mechanism (decomp `docs/research/named-retail/acclient_2013_pseudo_c.txt`):**
- Dat fields on `DatReaderWriter.Types.TerrainTex` (confirmed via reflection on Chorizite.DatReaderWriter 2.1.7): `DetailTextureId` (`QualifiedDataId<SurfaceTexture>`, `.DataId`) + `DetailTexTiling` (`uint`). acdream currently reads neither (only `TextureId`).
- `ACRender::landPolyDraw` 0x006b6525 (lines 702299-702300): **detail UV = `curr_detail_tiling × baseUV`** (the detail texture tiles `detail_tiling×` relative to the base).
- `landPolyDraw` 0x006b6450-0x006b649c (lines 702263-702287): **distance fade** — detail alpha is FULL at camera depth `< 10`, lerps to 0 by depth `50`, gone beyond. (Detail only sharpens up close.)
- 0x0059cab4 / lines 425099-425112: blend = **`TEXOP_MODULATE`** (modern equivalent: MODULATE2X `base*detail*2`, neutral at gray 0.5).
- `TexMerge::GetDetailTiling` 0x00503a10 (line 263852); `LScape::GenerateDetailSurfaces`/`SetDetailTexturing` 0x00506b40 (line 268389). Gated by the `EnvironmentDetailTextures` graphics preference (`Render::m_RenderPrefs.EnvironmentDetailTextures`).
- **FAITHFULNESS NOTE:** retail uses a **single landscape detail-tiling SCALAR**`GetDetailTiling(region, 0)` reads `TerrainDesc[0].DetailTexTiling` (category 0 = landscape; default **2.0** @0x0081eca8), stored in global `Render::curr_detail_tiling` and applied to ALL terrain UVs. It is **NOT per-terrain-type**. Prefer the single scalar.
The optional high-frequency Environment Detail Textures pass is a different
retail mechanism. It remains deferred under #226/TS-52 and does not keep this
fixed user-visible regression open.
**⚠ DO-NOT-RETRY:** the first investigation agent **FABRICATED a base `TexTiling` field** — it does **NOT** exist on `TerrainTex` (only `DetailTexTiling`). Do not re-introduce a "read `TexTiling`, multiply the base UV" fix; the base tiling is not a per-type dat field. (Verify the actual base landcell UV scale in the decomp before touching `TILE`.)
**Files:** `src/AcDream.App/Rendering/TerrainAtlas.cs`;
`src/AcDream.App/Rendering/TerrainModernRenderer.cs`;
`src/AcDream.App/Rendering/TerrainTextureTilingTable.cs`;
`src/AcDream.App/Rendering/Shaders/terrain_modern.frag`.
**Fix attempt (implemented + REVERTED, this session):** built a parallel detail `GL_TEXTURE_2D_ARRAY` (per type from `DetailTextureId`) + `uDetailTiling[]` + the shader block `rgb *= mix(vec3(1.0), detail*2.0, fade)` with the 10→50 depth fade. It **compiled, linked, ran with no GL error** — but the ground rendered **black/dark up close**: the detail array sampled near-zero, so MODULATE2X collapsed `rgb` to 0. **The bindless handles were verified NOT swapped** (renderer correctly binds `(terrain, alpha, detail)`). The bug is in the detail-texture **DATA**: the no-detail gray fill (`NoDetailGray=128`) didn't land as neutral, OR `DetailTextureId` decodes dark/black, OR AC's detail textures are not 0.5-centered (so MODULATE2X darkens). Reverted via `git checkout` — terrain back to the working (blurry) state.
**Research:** `docs/research/2026-07-13-retail-terrain-texture-tiling-pseudocode.md`.
**NEXT (debugging steps for the fresh pass):**
1. **Dump the detail array content** — are the layers ~0.5 gray or ~0 black? (Throwaway: decode each `DetailTextureId` + print mean brightness.) This tells you gray-fill vs decode vs texture-content.
2. If the textures decode dark, AC detail textures may not be gray-centered → use plain MODULATE or re-center, not MODULATE2X. Confirm the actual blend visually.
3. Confirm `DetailTextureId``SurfaceTexture``RenderSurface` resolves (do the detail textures even exist for these terrain types? many may be 0 → must fall to the gray no-op).
4. Use retail's **single landscape scalar** (`TerrainDesc[0].DetailTexTiling`, default 2.0), not per-type (faithfulness note above).
5. Gate behind an `EnvironmentDetailTextures` equivalent.
6. **Secondary (separate, unconfirmed):** is the base `TILE=1.0` (once per cell) correct vs retail? Verify the landcell base UV scale in the decomp during the fix — it may also contribute to "stretched."
**Files (the reverted attempt, for reference):** `TerrainAtlas.cs` (detail array build + gray fill + `DetailTiling`), `terrain_modern.frag` (uDetail sampler + MODULATE2X + fade), `TerrainModernRenderer.cs` (bind detail handle + upload `uDetailTiling[]`).
**Milestone note:** freeze-override render-polish (off the M1.5 critical path); resume when prioritized. Detail-overlay is a real retail feature acdream lacks — register an AP row when it ships (or note it as a known divergence until then).
**Acceptance:** outdoor ground (cobblestone road + grass) reads crisp up close matching retail, detail fades cleanly past ~50 m, no overall color/brightness shift (modulate neutral); user side-by-side vs retail.
**Acceptance:** Authored grass, road, base, and overlay textures repeat at
retail scale without reducing source resolution or changing blend masks —
passed in the connected visual gate.
---
@ -2699,9 +2813,18 @@ BCL-only.
---
## #153Portals only work once per session (can't run in, run out, re-enter)
## #153Far teleport onto an unstreamed landblock edge can run away
**Status:** 🟡 PARTIALLY FIXED — REOPENED 2026-06-21 (a NARROWER residual). The cell-relative carried-anchor (Option B, Slices 13+7, `438bb68``403a338`) **fixes the cascade for a STREAMED-terrain arrival** (live-verified: `+Je` ran through ~10 far-town landblocks across 2 sessions, zero march). BUT a third live session found the cascade **RECURS when a teleport arrives onto a NOT-YET-STREAMED landblock NEAR AN EDGE.** Evidence (`launch5.log`): arrival `[snap] claim=0xC98C0028 pos=(113.666,190.259,22.010) branch=NO-LANDBLOCK (lbs=0) -> verbatim` (local Y=190.3, **1.7 m from the 192 boundary**) → cell marches `0xC98C(lbY 0x8C) → 0xC9FE(lbY 0xFE)` +2/tick while Z free-falls 22→19; the outbound wire then sends the marched cell `C9FE0031` with compensating garbage `localY=21684` → ACE `MOVEMENT SPEED` / `failed transition` → player stuck. Contrast same session: hub `0x00070133` VALIDATED→fine; far town `0x977B000C` arrived unstreamed but **mid-block** (Y=73.8) → Z free-fall only, NO march. **ROOT (hypothesis, NOT yet apparatus-confirmed):** outdoor teleport places immediately but the destination streams a few ticks later (`streaming: dungeon EXIT-expand -> (201,140)` logs just after placement); during that gap the resolve runs against an empty world, and at an edge the player crosses into the unstreamed neighbour where the carried anchor (which computes to `(0,0,0)` at the recentered center) gives no protection. Same root as the Z free-fall (`#135`/`#138` placed-but-unstreamed gap). **DO NOT guess-patch** (original #145 burned 5 attempts) — needs a capture + an anchor/guard diagnostic at the crossing FIRST. Likely fix shape: a streaming-gap HOLD (freeze the per-tick resolve — no march, no fall — until the player's landblock loads; the async equivalent of retail's synchronous load), which would fix both symptoms. Suites still green; `TeleportFarTownRunawayTests` covers the STREAMED case only. [Prior "fixed" banner was premature — 2 clean sessions wasn't enough for a streaming-timing bug.]
**Status:** IN-PROGRESS — the original repeat-portal failure and streamed-arrival
cascade are fixed; a narrower unstreamed-arrival-near-edge residual remains.
The cell-relative carried anchor (Option B, Slices 13+7,
`438bb68``403a338`) passed roughly ten streamed far-town transitions. The
recorded residual arrives within about 2 metres of a 192-metre landblock edge
before the neighbour is resident, then advances membership through unstreamed
cells while producing an inconsistent outbound cell/local-position pair. Keep
the capture-first/DO-NOT-RETRY constraints and detailed verified mechanism
below; this is the issue formerly misreferenced in living docs as
#145-residual.” Issue #145 is an unrelated completed UI Z-order bug.
**Status (historical):** REOPENED 2026-06-21 — RESIDUAL: a teleport to a FAR town triggers a per-frame **resolver runaway** (the 2026-06-20 source-drop fix holds for Holtburg↔dungeon, but far-town destinations regress). #138 (objects come back) is confirmed SEPARATE + fixed. Root cause under research (retail-decomp oracle workflow).
**Severity:** HIGH (blocks far-town portal travel; breaks collision after the teleport — user, 2026-06-21)
@ -3060,7 +3183,7 @@ Both are the **entity-lifecycle/render path across a teleport**, NOT the streami
**Acceptance:** portal out of the 0x0007 dungeon → full outdoor world streams (trees/scenery present), **server objects (doors/NPCs/portals) render**, **own avatar renders across repeated round-trips**, collision works, position tracks (no avatar-vs-camera desync).
**2026-07-10 gate — PASSED, #138 CLOSED:** the user confirmed the full dungeon round-trip end-to-end — ENTER + NAVIGATE (dungeon streams, renders, collides, navigable, doors work) AND the EXIT (portal OUT to the outdoor world, world streams, collision holds, position tracks). This completes the #138 acceptance and lands M1.5. **Separate follow-up still open:** `#145-residual` (the far-town teleport-OUT arrival cascade) is a narrow far-teleport streaming edge case tracked on its own; it is NOT a #138 or M1.5 blocker and stays open for capture-harness-first work if it recurs.
**2026-07-10 gate — PASSED, #138 CLOSED:** the user confirmed the full dungeon round-trip end-to-end — ENTER + NAVIGATE (dungeon streams, renders, collides, navigable, doors work) AND the EXIT (portal OUT to the outdoor world, world streams, collision holds, position tracks). This completes the #138 acceptance and lands M1.5. **Separate follow-up still open:** #153 (the far-town teleport-OUT arrival cascade) is a narrow far-teleport streaming edge case tracked on its own; it is NOT a #138 or M1.5 blocker and stays open for capture-harness-first work if it recurs.
---
@ -7468,7 +7591,7 @@ App 741+2skip / UI 425 / Net 385 green.
---
## #170 — Remote creature chase+attack renders wrong vs retail (glide, run not sustained)
## Historical closing summary for #170 — Remote creature chase+attack
**Status:** DONE (2026-07-04, user visual gate passed — "as close to retail now as
I can see"). Three-fix arc: `427332ac` deleted the per-frame
@ -8712,10 +8835,10 @@ The mechanism named as the suspect in #68 (`RemoteMoveToDriver`'s arrival handli
---
## #87 — Drop WB fork patch by switching to PrepareEnvCellGeomMeshDataAsync
## #227 — [DONE] Drop WB fork patch by switching to PrepareEnvCellGeomMeshDataAsync
**Status:** DONE (2026-07-09, investigated this session).
Superseded by Phase O (2026-05-21, dropped the `WorldBuilder.Shared`/`Chorizite.OpenGLSDLBackend` project references entirely — there's no more forked submodule to patch/revert) and Phase A8 (2026-05-28, shipped `EnvCellRenderer.cs` which already calls the narrow `PrepareEnvCellGeomMeshDataAsync(geomId, environmentId, cellStructure, surfaces)` entry point at `EnvCellRenderer.cs:355`, using the bit-32-tagged synthetic geom id the issue proposed). `ObjectMeshManager.cs`'s bounds/type dispatch now branches on resolution type before calling `TryGet<T>`, so the blind `TryGet<Setup>` bug is structurally gone. `EnvCellRenderer` remains the live production cell-rendering path under Phase U as of commit `6aabe0b5` (Jul 6 2026). Note: a second, unrelated, already-DONE issue also uses the heading `## #87` elsewhere in this file (a pre-existing numbering collision, left untouched).
Superseded by Phase O (2026-05-21, dropped the `WorldBuilder.Shared`/`Chorizite.OpenGLSDLBackend` project references entirely — there's no more forked submodule to patch/revert) and Phase A8 (2026-05-28, shipped `EnvCellRenderer.cs` which already calls the narrow `PrepareEnvCellGeomMeshDataAsync(geomId, environmentId, cellStructure, surfaces)` entry point at `EnvCellRenderer.cs:355`, using the bit-32-tagged synthetic geom id the issue proposed). `ObjectMeshManager.cs`'s bounds/type dispatch now branches on resolution type before calling `TryGet<T>`, so the blind `TryGet<Setup>` bug is structurally gone. `EnvCellRenderer` remains the live production cell-rendering path under Phase U as of commit `6aabe0b5` (Jul 6 2026). This historical item was renumbered from a duplicate `#87` heading to `#227` during the 2026-07-20 documentation audit; the original indoor-cell issue retains #87.
---