docs(render): V11 closeout — register, architecture, code structure, issues
Retires the GL framing from the documents that described a two-backend,
two-UI-stack client, and files what the deletion left behind.
Divergence register:
* AD-46 (anisotropic tap pattern in dense alpha scenery) is REFRAMED rather
than retired. Its substance survives -- distant foliage may read denser
than retail's -- but it was measured GL-vs-Vulkan, and with GL gone it is
a Vulkan-vs-retail question against the D3D oracle it already cited. The
measurement is kept as the evidence that the residual is a driver tap
pattern; the row now records that it is no longer falsifiable by
self-differential, which is a real loss the deletion causes.
* AD-47 and AD-48 are NEW, and the campaign's own risk register scheduled
them here: MSAA sample positions (measured at 8.83% of the frame at 4x,
which is why every strict gate runs MSAA off -- and therefore why a
regression confined to the multisample path would not be caught) and
present pacing (#235 is the live instance).
* AD-17's justification moves from a GL clip-plane citation to Vulkan's
maxClipDistances floor, which is the same 8, so the divergence is
unchanged and only its authority moves.
* AP-92 keeps IUiViewportRenderer.TextureIsBottomUp rather than folding it
flat, because it is what let the origin question be answered by data.
Architecture and code structure: the layer diagram, the frame order, the
residency vocabulary and the reference table all said OpenGL. The UI section
said two stacks. Rule 3's rationale is rewritten around what actually
happened -- ImGui was deleted and not one panel, ViewModel or command had to
change, because none of them had ever imported ImGuiNET. That is the rule
paying for itself, so it is recorded as evidence rather than removed as
obsolete.
Issues: #258 files the dev-panel host as a decision rather than an accident,
and #255 is REOPENED. Its TaskCreationOptions.LongRunning fix asks the
scheduler for a thread but does not promise two callbacks overlap; under nine
concurrent test assemblies it still failed 2 of 5 whole-suite runs. The
earlier evidence tested a narrower pool, not a contended one. The fix it
needs is a rendezvous inside the read stub -- not a weakened assertion.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
5852bdb877
commit
c265b52d4b
5 changed files with 143 additions and 62 deletions
|
|
@ -97,9 +97,49 @@ Copy this block when adding a new issue:
|
|||
|
||||
---
|
||||
|
||||
## #258 — Developer panels have no host after V11 deleted ImGui
|
||||
|
||||
**Status:** OPEN
|
||||
**Severity:** MEDIUM (developer capability regression; no player-facing effect)
|
||||
**Filed:** 2026-07-29
|
||||
**Component:** developer tooling / retained UI
|
||||
|
||||
**Description:** Campaign V slice V11 deleted `src/AcDream.UI.ImGui/` and the
|
||||
UI Studio tree, because ImGui was a GL-only frontend and porting it to Vulkan
|
||||
was never in the campaign's scope. The consequence, recorded here so it is a
|
||||
decision rather than an accident: **`ACDREAM_DEVTOOLS=1` no longer produces any
|
||||
developer UI.** The variable survives — it still selects Vulkan's debug-utils
|
||||
instance extensions, and the client logs one line saying so — but the panel
|
||||
overlay, the menu bar, the debug panel and the UI Studio previewer are gone.
|
||||
|
||||
What was lost: the `IPanel`/`IPanelRenderer` overlay and every panel written
|
||||
against it, the `ui-studio` CLI verb (layout preview, `--layout`, `--dump`,
|
||||
`--mockup`, `--screenshot`), and the ImGui-hosted Settings/Diagnostics
|
||||
surfaces. `AcDream.UI.Abstractions` itself **survives intact** — the panel
|
||||
contract, ViewModels and commands were always backend-agnostic, which is the
|
||||
whole reason Code Structure Rule 3 exists. Only the ImGui *backend* went.
|
||||
|
||||
**What this issue is for:** deciding what replaces it. The retained
|
||||
`UiHost`/`UiRoot` tree is already a working, Vulkan-capable retained-UI engine
|
||||
with its own layout, input routing and rendering — so the cheapest credible
|
||||
answer is to host the developer panels as retained-UI windows rather than
|
||||
reintroduce an immediate-mode dependency. That keeps one UI stack instead of
|
||||
two, which is a simplification the campaign paid for.
|
||||
|
||||
**Not urgent, and deliberately unscheduled.** Nothing in M4's remaining work
|
||||
needs a dev overlay, and the diagnostic env-var family
|
||||
(`ACDREAM_PROBE_*`, `ACDREAM_DUMP_*`) still works without one. Sequence this
|
||||
when a debugging task actually wants a panel, not before.
|
||||
|
||||
**Files:** `src/AcDream.UI.Abstractions/**` (the surviving contract),
|
||||
`src/AcDream.App/UI/**` (the retained engine that would host it). The deleted
|
||||
tree is recoverable from git history at `844cf092^`.
|
||||
|
||||
---
|
||||
|
||||
## #255 — Two RetailDatLoader concurrency tests measured the thread pool, not the loader
|
||||
|
||||
**Status:** DONE — 2026-07-28; the pair now runs on dedicated threads
|
||||
**Status:** REOPENED 2026-07-29 — the `LongRunning` fix is a hint, not a guarantee
|
||||
**Severity:** LOW (test infrastructure only; no production defect)
|
||||
**Filed:** 2026-07-28
|
||||
**Component:** tests / xUnit parallelism, content loaders
|
||||
|
|
@ -134,6 +174,32 @@ afterwards.
|
|||
|
||||
**Files:** `tests/AcDream.Content.Tests/Vfx/RetailDatLoaderTests.cs`.
|
||||
|
||||
### Reopened 2026-07-29 by the V11 gate
|
||||
|
||||
`AnimationCache_CoalescesSameDidAndAllowsUnrelatedReadsInParallel` failed again
|
||||
on Windows, at `RetailDatLoaderTests.cs:311` (`Assert.True(portal.MaxConcurrentReads >= 2)`),
|
||||
in **2 of 5 complete-solution Release runs** on an otherwise green V11 tree —
|
||||
and passed 124/124 in isolation and in the other 3 whole-suite runs. V11 does
|
||||
not touch the subject: its only change under `src/AcDream.Content/` is
|
||||
`UploadFormats.cs`, which is pixel-format enum documentation.
|
||||
|
||||
**Why the fix did not hold.** `TaskCreationOptions.LongRunning` *asks* the
|
||||
scheduler for a dedicated thread; it does not promise the two callbacks overlap
|
||||
in time. With nine test assemblies running concurrently, the first 40 ms
|
||||
`Thread.Sleep` read can finish before the second callback is scheduled at all,
|
||||
so `MaxConcurrentReads` never reaches 2 — the same failure mode #255 originally
|
||||
described, merely made rarer. The evidence in the section above (10/10 under a
|
||||
two-CPU pin) tested a *narrower* pool, not a *contended* one, which is why it
|
||||
looked closed.
|
||||
|
||||
**What a real fix needs:** the two callbacks must be synchronised against each
|
||||
other rather than against the scheduler — e.g. a `Barrier` or two-party
|
||||
`SemaphoreSlim` rendezvous *inside* the read stub, so each read cannot complete
|
||||
until both have entered. That makes the assertion measure the loader's
|
||||
coalescing, which is what it is for, and makes it independent of how many
|
||||
threads the host happens to offer. **Do not weaken the assertion or add a
|
||||
retry** — the assertion is correct; the harness around it is what is wrong.
|
||||
|
||||
---
|
||||
|
||||
## #254 — Logout confirmation wait overran its timeout on a starved thread pool
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ A modern C# .NET 10 Asheron's Call client that:
|
|||
- **Behaves identically to the retail client** — same physics, same
|
||||
animations, same terrain, same collision, same network protocol
|
||||
- **Looks identical to the retail client** — same meshes, same textures,
|
||||
same lighting, same blending, rendered via modern Silk.NET OpenGL
|
||||
same lighting, same blending, rendered via modern Silk.NET Vulkan
|
||||
- **Adds a plugin API** the retail client never had — native C# plugins
|
||||
+ Lua macro scripting for player automation
|
||||
- **Is NOT a 1:1 C++ port** — uses modern C# patterns (composition over
|
||||
|
|
@ -64,7 +64,7 @@ well-defined interfaces that the retail client never had.
|
|||
│ ► wire-format identical to retail │
|
||||
├──────────────────────────────────────────────────────────────┤
|
||||
│ LAYER 1: Renderer │
|
||||
│ Silk.NET OpenGL 4.3 core profile │
|
||||
│ Silk.NET Vulkan 1.3 (the only backend since Campaign V V11) │
|
||||
│ TerrainModernRenderer, WbDrawDispatcher, EnvCellRenderer │
|
||||
│ Shaders (terrain blending, mesh lighting, translucency) │
|
||||
│ ► completely different from retail (D3D7), same visual │
|
||||
|
|
@ -79,28 +79,35 @@ well-defined interfaces that the retail client never had.
|
|||
### UI Architecture (two coexisting presentation stacks)
|
||||
|
||||
The 2026-04-24 design began with a swappable renderer abstraction. D.2b proved
|
||||
that retail fidelity needs a retained LayoutDesc/DAT tree, while ImGui remains
|
||||
valuable as permanent devtools. The current architecture therefore has two
|
||||
presentation stacks over shared state, ViewModels, events, and commands. Full
|
||||
history and the corrected contract live in
|
||||
that retail fidelity needs a retained LayoutDesc/DAT tree, and for most of the
|
||||
project's life ImGui coexisted beside it as permanent devtools. **Campaign V
|
||||
slice V11 ended that (2026-07-29):** ImGui was a GL-only frontend, the campaign
|
||||
did not port it, and deleting OpenGL deleted it. There is now **one** presentation
|
||||
stack. Full history and the corrected contract live in
|
||||
`docs/plans/2026-04-24-ui-framework.md`.
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ DEVELOPER UI │
|
||||
│ IPanel/IPanelRenderer → permanent ImGui devtools │
|
||||
│ GAMEPLAY UI │
|
||||
│ GAMEPLAY UI (the only stack) │
|
||||
│ LayoutDesc/DAT → UiRoot retained widgets + controllers │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ SHARED CONTRACTS │
|
||||
│ ViewModels, commands, input actions, state/event services │
|
||||
│ ► one model and mutation path, two presentation projections│
|
||||
│ ► one model and mutation path, one presentation projection │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ Game state + events (unchanged) │
|
||||
│ IGameState / IEvents / WorldSession — UI only reads │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
`AcDream.UI.Abstractions` — the `IPanel`/`IPanelRenderer` contract, the
|
||||
ViewModels and the commands — **survives intact**. It was always
|
||||
backend-agnostic, which is exactly what Code Structure Rule 3 was written to
|
||||
protect, and it is what a future developer-panel host would bind to. Only the
|
||||
ImGui *backend* was deleted. `ACDREAM_DEVTOOLS=1` still selects Vulkan's
|
||||
debug-utils extensions and now logs that the developer UI is gone; replacing it
|
||||
is issue **#258**, deliberately unscheduled.
|
||||
|
||||
`AcDream.UI.Abstractions` owns backend-neutral ViewModels, commands, input,
|
||||
and the `IPanel`/`IPanelRenderer` devtools contract. `AcDream.App/UI` owns the
|
||||
retained gameplay tree, LayoutDesc importer, window runtime, and panel
|
||||
|
|
@ -112,8 +119,8 @@ retail client command (`ExecuteClientCommandCmd`), an ACE-owned command
|
|||
handlers and controllers translate those intents to `WorldSession`; panels
|
||||
never inspect or construct wire messages.
|
||||
Plugins register retained gameplay markup through the BCL-only
|
||||
`AcDream.Plugin.Abstractions.IUiRegistry`; they do not import App or ImGui
|
||||
assemblies. Core `SelectionState` is the sole selected-object owner for world,
|
||||
`AcDream.Plugin.Abstractions.IUiRegistry`; they do not import App or
|
||||
presentation assemblies. Core `SelectionState` is the sole selected-object owner for world,
|
||||
radar, inventory, paperdoll, toolbar, use/examine consumers, and plugins;
|
||||
`IPluginHost.Selection` exposes that same state and retail-style old/new callback.
|
||||
Temporary pointer modes are separate App orchestration in `InteractionState` and
|
||||
|
|
@ -131,11 +138,11 @@ Every retained gameplay window has a typed `RetailWindowHandle` and at most one
|
|||
`IRetainedPanelController` lifecycle owner. Multi-controller windows use
|
||||
`RetainedPanelControllerGroup`; the manager disposes controllers exactly once in
|
||||
reverse ownership order. `UiHost` removes Silk device subscriptions first, then
|
||||
disposes the manager/controllers, then its GL renderer. `GameWindow.OnClosing`
|
||||
disposes the manager/controllers, then its renderer. `GameWindow.OnClosing`
|
||||
tears this runtime down before session and game-state sources.
|
||||
|
||||
`RetailUiRuntime` is the production composition boundary. `GameWindow` creates
|
||||
the GL/DAT resolvers and supplies focused state/action binding records in one
|
||||
the device/DAT resolvers and supplies focused state/action binding records in one
|
||||
mount call; the runtime owns all LayoutDesc imports, controller construction,
|
||||
window registration, plugin mounts, cursor feedback, layout persistence, and the
|
||||
retained tick/draw/restore/dispose paths. Panel-specific construction must not
|
||||
|
|
@ -166,7 +173,7 @@ parallel window-lifecycle map.
|
|||
|
||||
```
|
||||
src/
|
||||
AcDream.Core/ Layer 2-4: no GL, no Silk.NET, pure logic
|
||||
AcDream.Core/ Layer 2-4: no Vulkan, no Silk.NET, pure logic
|
||||
Physics/
|
||||
PhysicsBody.cs -> body state / integration foundation (done)
|
||||
CollisionPrimitives.cs -> retail primitive helpers (partial, active)
|
||||
|
|
@ -247,7 +254,7 @@ src/
|
|||
RuntimeGenerationReset.cs -> one retryable canonical-generation reset
|
||||
-> Slice J complete; graphical and no-window hosts share one GameRuntime
|
||||
-> may reference Core, Core.Net, Content, and Plugin.Abstractions only
|
||||
-> must never reference App, UI, Silk.NET, OpenAL, Arch, or ImGui
|
||||
-> must never reference App, UI, Silk.NET, OpenAL, or Arch
|
||||
|
||||
AcDream.Headless/ Linux/Windows no-window production host
|
||||
Program.cs -> CLI entry only
|
||||
|
|
@ -275,10 +282,10 @@ src/
|
|||
GraphicalWindowBackendSelection.cs -> immutable GLFW 3.4
|
||||
Win32/X11/Wayland selection and
|
||||
packaged-native preference
|
||||
GraphicalCapabilityRecord.cs -> pre-render platform/GL/audio/input
|
||||
capability report and startup guard
|
||||
GraphicalGlFunctionProbe.cs -> active modern-GL shader/MDI/SSBO/
|
||||
timer/FBO/persistent-buffer validation
|
||||
Gpu/Vk/VulkanCapabilityRecord.cs -> pre-render platform/device/audio/
|
||||
input capability report + startup gate
|
||||
Gpu/Vk/VulkanBringUpHost.cs -> active device/descriptor/pipeline/
|
||||
offscreen-readback validation
|
||||
GraphicalLegacyConfigurationMigrator.cs -> no-overwrite Windows
|
||||
LocalAppData migration
|
||||
Physics/
|
||||
|
|
@ -646,7 +653,7 @@ centralizing physical storage. `ResidencyManager` owns generation-safe logical
|
|||
asset identity, owner tokens, leases, immutable budgets, and aggregate
|
||||
accounting. Specialized render/content owners continue to own their mesh
|
||||
ranges, texture arrays, staging payloads, decoded animations/audio, and
|
||||
retained shared-alpha scratch. The manager never sees a GL name: policy runs as
|
||||
retained shared-alpha scratch. The manager never sees a device handle: policy runs as
|
||||
a single writer during update/resource maintenance, while logical eviction and
|
||||
fence-delayed physical release remain render-thread owner operations. Prepared
|
||||
package address space is reported separately from committed CPU bytes, and
|
||||
|
|
@ -732,13 +739,13 @@ every region visited:
|
|||
128 MiB respectively, with an 896 MiB physical ceiling that includes an
|
||||
in-progress migration and its retired predecessor.
|
||||
|
||||
OpenGL deletion and range/slot reuse are not synonymous with logical release.
|
||||
Vulkan object destruction and range/slot reuse are not synonymous with logical release.
|
||||
`GpuFrameFlightController` fences three frames in flight. Mesh-buffer stores,
|
||||
texture handles, atlas layers, terrain slots, and landblock render records enter
|
||||
retirement only after they are no longer publishable, and their physical ids are
|
||||
recycled only after the corresponding fence signals. Shutdown follows the same
|
||||
dependency order and remains retryable: UI/controllers and render registrations
|
||||
withdraw first, then owner leases and caches, then GL backing stores. This keeps
|
||||
withdraw first, then owner leases and caches, then device backing stores. This keeps
|
||||
drivers from reading freed memory without adding a portal-specific purge or a
|
||||
visual-distance reduction.
|
||||
|
||||
|
|
@ -770,9 +777,9 @@ the exact retail `Client::UseTime` order. See the completed Slice 6 ledger and
|
|||
`GameWindow.OnRender` likewise owns only one handoff to the shipped
|
||||
`RenderFrameOrchestrator`. Its frozen graph begins per-resource frames and
|
||||
render-thread uploads; draws world/PView and its two shared-alpha scopes;
|
||||
renders portal and paperdoll private viewports, retained gameplay UI, ImGui
|
||||
renders portal and paperdoll private viewports, retained gameplay UI,
|
||||
devtools, and screenshots; then closes the GPU-flight transaction in
|
||||
`finally`. Both UI stacks coexist in the same frame.
|
||||
`finally`. There is one UI stack: the retained tree.
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -849,7 +856,7 @@ exact reference identity and retry position, rejects stale generations, and
|
|||
executes every class through the same meter. The canonical reveal generation
|
||||
supplies one exact destination cell/radius reservation; the meter protects its
|
||||
configured share across time, completion admissions, retained CPU bytes,
|
||||
entity operations, requested GPU bytes, and GL-retirement admissions. A
|
||||
entity operations, requested GPU bytes, and device-retirement admissions. A
|
||||
completion retains the reveal generation that classified it, so a stale
|
||||
completion or teardown cannot consume or clear a replacement generation's
|
||||
reservation. Priority changes order and reserved share, never the global
|
||||
|
|
@ -866,7 +873,7 @@ readiness continue. Canonical live records remain retained until physical
|
|||
teardown converges, so quiescence neither reconstructs server identity nor
|
||||
invents Hidden/UnHide transitions. A shared-origin recenter atomically removes
|
||||
the complete old spatial generation and captures one exact receipt per
|
||||
landblock before the origin changes. Expensive script, physics, render, and GL
|
||||
landblock before the origin changes. Expensive script, physics, render, and GPU
|
||||
release then advances only from `StreamingController.Tick` through stable
|
||||
per-owner entity/stage cursors on the frame's one meter. Publication remains
|
||||
fenced only by an older receipt for the same canonical landblock; the active
|
||||
|
|
@ -944,7 +951,7 @@ rejects a stable checkpoint while any host acknowledgement remains.
|
|||
`FrameScreenshotController` performs the requested default-framebuffer readback
|
||||
after world and retained UI draw on the render thread. The external connected
|
||||
gate launches normal Release processes and observes these artifacts, but never
|
||||
mutates streaming, physics, GL, or entity state from a worker thread.
|
||||
mutates streaming, physics, GPU, or entity state from a worker thread.
|
||||
|
||||
### Teleport cell identity at the streaming boundary
|
||||
|
||||
|
|
@ -1077,7 +1084,7 @@ Current movement/collision ownership:
|
|||
fallback/transfer. `RuntimeSettingsController` is the sole settings
|
||||
persistence/current-state owner, supplies the immutable pre-window snapshot,
|
||||
applies startup pacing/display/FOV/audio once, and later borrows complete
|
||||
runtime targets without replay. Checked GL construction/state boundaries
|
||||
runtime targets without replay. Checked GPU construction/state boundaries
|
||||
retain exact cleanup obligations across failure. The executable nine-phase
|
||||
production pipeline and terminal session start are complete. Checkpoint J
|
||||
moves the exact shutdown dependency graph into `GameWindowLifetime`, whose
|
||||
|
|
@ -1127,7 +1134,7 @@ For acdream-specific code (renderer, plugin API, streaming):
|
|||
| Physics/collision | `docs/research/named-retail/` | ACE Physics/ + older decompiled chunks |
|
||||
| Animation | `docs/research/named-retail/` + ACE Animation/ | — |
|
||||
| Terrain | ACME ClientReference.cs | named retail / older decompiled chunks |
|
||||
| Rendering | WorldBuilder (Silk.NET) | ACViewer |
|
||||
| Rendering | WorldBuilder (Silk.NET; its GL calls now read as RHI intent) | ACViewer |
|
||||
| Protocol | holtburger | AC2D |
|
||||
| Server behavior | ACE | — |
|
||||
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ from WB vs port ourselves).
|
|||
|
||||
## 1. The structural problem we're solving
|
||||
|
||||
The layered architecture works: `AcDream.Core` is GL-free, the network
|
||||
The layered architecture works: `AcDream.Core` is backend-free, the network
|
||||
layer is wire-compatible, the UI has a stable contract, plugins load.
|
||||
The structural debt is concentrated in **one file**:
|
||||
|
||||
|
|
@ -42,7 +42,7 @@ after Slice 8 checkpoint K 1,622 lines / canonical soak shell
|
|||
|
||||
At the campaign baseline, `GameWindow` was the single object that:
|
||||
|
||||
- Owns the GL context, the window, input, and shaders.
|
||||
- Owns the Vulkan device, the window, input, and shaders.
|
||||
- Reads ~40 different environment variables across its lifetime.
|
||||
- Composes the shipped `LiveSessionController`/host/router boundary; it no
|
||||
longer owns a parallel session, command bus, subscription lifetime, connect,
|
||||
|
|
@ -90,34 +90,40 @@ you find yourself adding a 200-line method to `GameWindow`, stop and
|
|||
extract.
|
||||
|
||||
**Exemption:** Trivial wiring that *must* stay in `GameWindow` because
|
||||
it touches GL state during `OnLoad` is acceptable, but should still
|
||||
it touches device state during `OnLoad` is acceptable, but should still
|
||||
delegate to a collaborator for the substance.
|
||||
|
||||
### Rule 2: `AcDream.Core` must not depend on window / GL / backend projects
|
||||
### Rule 2: `AcDream.Core` must not depend on window / backend projects
|
||||
|
||||
**Why:** Core is the GL-free, testable layer. The moment Core imports
|
||||
a GL or windowing namespace, we've lost the ability to test it without
|
||||
a graphics context, and the layer split becomes fiction.
|
||||
**Why:** Core is the backend-free, testable layer. The moment Core imports
|
||||
a graphics or windowing namespace, we've lost the ability to test it without
|
||||
a device, and the layer split becomes fiction.
|
||||
|
||||
**How to apply:** Phase O removed both external WorldBuilder/backend project
|
||||
references. The only currently allowed seams are the GL-free helpers owned in
|
||||
references. The only currently allowed seams are the backend-free helpers owned in
|
||||
our tree under `src/AcDream.Core/Rendering/Wb/`: `TerrainUtils`,
|
||||
`TerrainEntry`, `RegionInfo`, `SceneryHelpers`, and `TextureHelpers`.
|
||||
`ObjectMeshManager` and every GL resource owner remain in App. If Core needs a
|
||||
`ObjectMeshManager` and every GPU resource owner remain in App. If Core needs a
|
||||
new capability, define a narrow Core interface and implement it in App; adding
|
||||
a new project reference requires an inventory-doc update explaining why.
|
||||
|
||||
### Rule 3: UI panels target `AcDream.UI.Abstractions` only
|
||||
|
||||
**Why:** This is the one rule that keeps D.2b (the future retail-look
|
||||
backend) viable. Every panel that imports `ImGuiNET` directly is a panel
|
||||
we'd have to rewrite when the backend swaps.
|
||||
**Why:** The rule was written to keep D.2b (the retail-look backend)
|
||||
viable while ImGui was still the developer stack, and **it paid off**: when
|
||||
Campaign V slice V11 deleted ImGui on 2026-07-29, not one panel contract,
|
||||
ViewModel or command had to change, because none of them had ever imported
|
||||
`ImGuiNET`. `AcDream.UI.Abstractions` survived a backend deletion intact.
|
||||
|
||||
**How to apply:** A panel's `using` block must mention
|
||||
`AcDream.UI.Abstractions.*` and nothing from `AcDream.UI.ImGui`. The
|
||||
panel writes against `IPanelRenderer`. The `ImGuiPanelRenderer`
|
||||
translates those calls to ImGui at runtime. Plugin-facing UI follows the
|
||||
same rule.
|
||||
`AcDream.UI.Abstractions.*` and nothing from any backend assembly. The panel
|
||||
writes against `IPanelRenderer`; a renderer implementation translates those
|
||||
calls at runtime. Plugin-facing UI follows the same rule.
|
||||
|
||||
**Status:** there is currently no `IPanelRenderer` implementation in the tree —
|
||||
the ImGui one went with V11 and the replacement is issue **#258**. The contract
|
||||
is kept rather than deleted precisely because this rule proved its worth; a new
|
||||
host binds to it without touching a single panel.
|
||||
|
||||
### Rule 4: Startup env vars enter through `RuntimeOptions`
|
||||
|
||||
|
|
@ -173,7 +179,7 @@ Today:
|
|||
|
||||
`tests/AcDream.App.Tests/` now exists and owns App-layer controller, streaming,
|
||||
render-resource lifetime, retained-UI, and `RuntimeOptions` tests. New App tests
|
||||
belong there; do not place GL-free Core behavior in that project merely because
|
||||
belong there; do not place backend-free Core behavior in that project merely because
|
||||
App currently wires it.
|
||||
|
||||
---
|
||||
|
|
@ -228,7 +234,7 @@ src/AcDream.App/
|
|||
├── Program.cs # parse args + env → RuntimeOptions, build GameWindow
|
||||
├── RuntimeOptions.cs # typed startup options (Rule 4)
|
||||
├── Rendering/
|
||||
│ ├── GameWindow.cs # thin: GL/window lifecycle + delegates per-frame to RenderFrameOrchestrator
|
||||
│ ├── GameWindow.cs # thin: device/window lifecycle + delegates per-frame to RenderFrameOrchestrator
|
||||
│ ├── RenderFrameOrchestrator.cs # GPU-flight boundary + typed world/private/UI render phases
|
||||
│ ├── LiveEntityAnimationScheduler.cs # shipped: ordinary live-object update workset
|
||||
│ ├── LiveEntityAnimationPresenter.cs # final part-pose/mesh/effect composition after scheduler output
|
||||
|
|
@ -284,10 +290,10 @@ src/AcDream.App/
|
|||
|
||||
What `GameWindow` keeps:
|
||||
|
||||
- `IWindow` / `GL` / `IInputContext` lifecycle (constructor + `OnLoad` +
|
||||
- `IWindow` / device / `IInputContext` lifecycle (constructor + `OnLoad` +
|
||||
`Run` + `OnClosing`).
|
||||
- `RuntimeOptions` reference (the typed startup config).
|
||||
- GL resource construction and top-level collaborator composition. Construction
|
||||
- GPU resource construction and top-level collaborator composition. Construction
|
||||
is allowed here; feature algorithms and mutable subsystem state are not.
|
||||
- One field per top-level collaborator (`_liveSessionController`,
|
||||
`_liveEntityRuntime`, `_selectionInteraction`, `_streamingPresentation`,
|
||||
|
|
@ -480,7 +486,7 @@ useful ordering seam, but its ownership status is **partial**.
|
|||
| Landblock presentation | **Complete** | `LandblockBuildFactory` owns the captured-origin DAT transaction; concrete render/physics/DAT-static publishers and `LandblockPresentationPipeline` own typed-meter publication and exact retryable retirement. `StreamingController` owns stable destination/control/unload/Near/Far queues and destination reservation. CPU mesh-cache restaging requires an exact live owner. `StreamingOriginRecenterCoordinator` serializes old-window retirement with teleport/session origin lifetimes. `GameWindow` retains construction and one pipeline field only (`c79d0a49`, closeout `4a205a3e`; Slice E closeout `91e82c3c`). |
|
||||
| Render-frame orchestration | **Complete** | `RenderFrameOrchestrator` owns the GPU-flight, resource, world/PView/shared-alpha, private-presentation, diagnostics, screenshot, and recovery graph. `GameWindow.OnRender` takes one logical window-size snapshot and performs one immutable handoff (`9d7df1bf`). |
|
||||
| Unified `GameEntity` | **Slice J complete** | Canonical identity, retained-object lifetime, direct views, ordered entity/object deltas, gameplay state, action/combat/magic/movement/physics/remote/projectile simulation, world environment, reveal/transit truth, and host acknowledgement share failure-safe Runtime owners while exact graphical sidecars stay in App. One `GameRuntime` composes the graph. `RuntimeGenerationReset` is the sole retryable canonical-generation reset for graphical and no-window hosts; the deterministic direct host proves lifecycle, commands, portal, reconnect, GUID reuse, fault recovery, isolation, and terminal convergence without presentation assemblies (`a9a822f2`). |
|
||||
| Headless host | **Slice K complete** | `AcDream.Headless` references only Runtime and loads no App/UI/GL/window/audio assembly. Portable paths/config/credentials, deterministic command/event scheduling, shared immutable content, exact per-session identity, failure quarantine, reconnect, resource telemetry, 1/5/10/30-session isolation, two-hour simulated endurance, and the exact two-account native-Linux connected soak pass through `776482da`. |
|
||||
| Headless host | **Slice K complete** | `AcDream.Headless` references only Runtime and loads no App/UI/graphics/window/audio assembly. Portable paths/config/credentials, deterministic command/event scheduling, shared immutable content, exact per-session identity, failure quarantine, reconnect, resource telemetry, 1/5/10/30-session isolation, two-hour simulated endurance, and the exact two-account native-Linux connected soak pass through `776482da`. |
|
||||
|
||||
### 4.3 Revised extraction sequence
|
||||
|
||||
|
|
@ -687,7 +693,7 @@ Detailed execution ledger:
|
|||
(complete).
|
||||
|
||||
Move the complete draw graph and its reusable frame-local scratch state into a
|
||||
GL-owning App collaborator. Preserve the exact modern pipeline order, clip
|
||||
GPU-owning App collaborator. Preserve the exact modern pipeline order, clip
|
||||
routing, PView flood, landscape/opaque/shared-alpha flush boundaries,
|
||||
particles, debug draw, paperdoll, retained UI, and frame fences. Do not pass a
|
||||
hundred individual delegates or let the orchestrator reach back into
|
||||
|
|
@ -715,7 +721,7 @@ residency without weakening leak detection.
|
|||
Detailed execution ledger:
|
||||
[`docs/plans/2026-07-22-gamewindow-slice-8-composition-lifecycle.md`](../plans/2026-07-22-gamewindow-slice-8-composition-lifecycle.md).
|
||||
|
||||
Keep GL/window construction in `GameWindow.OnLoad`, but group creation into
|
||||
Keep device/window construction in `GameWindow.OnLoad`, but group creation into
|
||||
small composition functions and delete feature state left behind by prior
|
||||
slices. `OnClosing` delegates to the existing retryable shutdown transaction.
|
||||
Silk callbacks become narrow calls into the input, update, render, resize,
|
||||
|
|
@ -745,14 +751,14 @@ Gameplay draft fields when combat preferences change. `GameWindow` is now 3,663
|
|||
raw lines / 162 fields / 37 methods at G. H adds sole lifetime roots for the
|
||||
terrain atlas and dedicated sky shader, one retained Host/runtime lease, an
|
||||
atomic update/render frame-root slot, and a prepare-aware portal fallback and
|
||||
transfer slot. GL construction and state mutations now use checked commit
|
||||
transfer slot. GPU construction and state mutations now use checked commit
|
||||
boundaries with exact retry ownership for failed names, bindless residency,
|
||||
and texture-binding restoration. `GameWindow` is 3,689 raw lines / 162 fields /
|
||||
37 methods at H. Checkpoint I.1–I.5 now provide the executable nine-phase
|
||||
oracle, platform/host/content/settings phases, and the production world/render
|
||||
phase. `WorldRenderCompositionPhase` owns Region/environment, the mandatory
|
||||
modern-renderer foundation, immutable terrain-worker inputs, and WB/texture/
|
||||
sampler construction; every Phase-4 GL constructor has retryable prefix
|
||||
sampler construction; every Phase-4 GPU constructor has retryable prefix
|
||||
ownership. `GameWindow` is 3,522 raw lines after I.5. I.6 moves interaction,
|
||||
retained UI, live presentation, and landblock publication into ordered
|
||||
composition phases. Typed exact-owner sources bridge later session,
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ exactly this register's scope.
|
|||
|
||||
**Kinds.**
|
||||
- **Intentional architecture** — deliberate design choices we stand behind; retiring them would be a redesign, not a fix.
|
||||
- **Adaptation** — required by a real structural difference (async streaming vs synchronous load, ACE vs retail server semantics, GL vs D3D). Correct *given the difference*; each carries an equivalence argument.
|
||||
- **Adaptation** — required by a real structural difference (async streaming vs synchronous load, ACE vs retail server semantics, Vulkan vs D3D). Correct *given the difference*; each carries an equivalence argument.
|
||||
- **Documented approximation** — we know retail's mechanism and chose a cheaper/safer stand-in with a recorded justification.
|
||||
- **Temporary stopgap** — known-incomplete; explicitly awaiting a port/phase. These are scheduled debt.
|
||||
- **Unclear** — the recorded justification is missing, contradictory, or never argued. These are the most dangerous rows and head the retire list.
|
||||
|
|
@ -62,11 +62,13 @@ accepted-divergence entries (#96, #49, #50).
|
|||
|
||||
---
|
||||
|
||||
## 2. Adaptation (AD) — 39 rows (AD-11 retired 2026-07-23 — exact low-bit ItemUses predicate; AD-31 retired 2026-07-15 — the DAT-authored portal-space viewport replaces the black transit cover)
|
||||
## 2. Adaptation (AD) — 41 rows (AD-47 and AD-48 filed 2026-07-29 at Campaign V slice V11 — the MSAA sample-position and present-pacing rows the campaign's risk register scheduled for the GL deletion; AD-11 retired 2026-07-23 — exact low-bit ItemUses predicate; AD-31 retired 2026-07-15 — the DAT-authored portal-space viewport replaces the black transit cover)
|
||||
|
||||
| # | Divergence | Where (file:line) | Why it is safe / justified | Risk if assumption breaks | Retail oracle |
|
||||
|---|---|---|---|---|---|
|
||||
| AD-46 | **LIVE as of Campaign V slice V10 (cutover committed, user sign-off pending); previously Vulkan-arm-only and dormant.** Dense alpha-blended distant scenery (the treeline) resolves differently on the Vulkan backend than on GL, by about 15% of the pixels in the band, even though both arms now request identical sampler state — trilinear, clamp-and-repeat, and the device's maximum anisotropy. What remains is the anisotropic TAP PATTERN, which both the GL and Vulkan specifications leave implementation-defined, and AMD's two drivers do not agree. | `src/AcDream.App/Rendering/Wb/WorldTextureArray.cs` (`RhiWorldTextureArray.WorldArrayAnisotropy`); measured in plan §5.5.19 | Not assumed — narrowed by measurement, on an offline capture with no session, no entities and both clocks pinned. Anisotropy 1 → 41,509 differing pixels in the tree band; anisotropy 16 (GL's value, and retail's `m_D3DCaps.MaxAnisotropy`) → 22,266, and the rest of the frame falls to 497 px of 563,200, i.e. 8.8e-04, inside the campaign's 0.001 threshold. The residual is not a sub-pixel shift (an integer shift search finds none), not a sharpness change (high-frequency energy matches within 5%), and not depth precision (forcing Vulkan's window-depth range to GL's compressed [0.5, 1] moved it by 3%). Monotone improvement toward GL's own anisotropy with no knob left is what makes it a driver property rather than a bug. | If it is NOT the tap pattern, some other Vulkan sampler or alpha-path difference is hiding behind this row, and it will present after cutover as distant foliage that shimmers or reads denser than retail's. The class is confined to alpha-blended dense overlap: opaque terrain, roofs, walls, water, statics, the character and the whole retained UI are inside threshold. | `RenderDeviceD3D::SetDefaultD3DStates @ 0x005a3800`, whose `SetSamplerState(stage, 0xA /* D3DSAMP_MAXANISOTROPY */, m_D3DCaps.MaxAnisotropy)` at `0x005a4230` is the value both arms now request |
|
||||
| AD-46 | **LIVE. Reframed at Campaign V slice V11 (2026-07-29), when GL was deleted and the comparison that discovered this row ceased to exist.** Dense alpha-blended distant scenery (the treeline) may read slightly denser than retail's, because the anisotropic TAP PATTERN is implementation-defined and acdream's Vulkan driver does not tap identically to retail's D3D9 one. Both request the same sampler state — trilinear, clamp-and-repeat, the device's maximum anisotropy. **What changed at V11 is only the left-hand side of the comparison**: this was measured GL-vs-Vulkan (~15% of the pixels in the band), and it is now a Vulkan-vs-retail question against the D3D oracle in the last column. The measurement below is retained as the evidence that the residual is a tap pattern and not a bug, even though one of its two arms no longer exists. | `src/AcDream.App/Rendering/Wb/WorldTextureArray.cs` (`RhiWorldTextureArray.WorldArrayAnisotropy`); measured in plan §5.5.19, reframed §5.5.24 | Not assumed — narrowed by measurement while both backends still existed, on an offline capture with no session, no entities and both clocks pinned. Anisotropy 1 → 41,509 differing pixels in the tree band; anisotropy 16 (GL's value, and retail's `m_D3DCaps.MaxAnisotropy`) → 22,266, and the rest of the frame fell to 497 px of 563,200, i.e. 8.8e-04, inside the campaign's 0.001 threshold. The residual was not a sub-pixel shift (an integer shift search found none), not a sharpness change (high-frequency energy matched within 5%), and not depth precision (forcing Vulkan's window-depth range to GL's compressed [0.5, 1] moved it by 3%). Monotone improvement toward GL's own anisotropy with no knob left is what made it a driver property rather than a bug. | Distant foliage shimmers or reads denser than retail's. The class is confined to alpha-blended dense overlap: opaque terrain, roofs, walls, water, statics, the character and the whole retained UI are inside threshold. **Now unfalsifiable by self-differential** — with GL gone, the only way to retire this row is a side-by-side against the retail client, not against another acdream backend. | `RenderDeviceD3D::SetDefaultD3DStates @ 0x005a3800`, whose `SetSamplerState(stage, 0xA /* D3DSAMP_MAXANISOTROPY */, m_D3DCaps.MaxAnisotropy)` at `0x005a4230` is the value acdream requests |
|
||||
| AD-47 | **Filed at Campaign V slice V11 (2026-07-29); the campaign's risk register scheduled this row here.** Multisample resolve sample POSITIONS are unspecified by both the Vulkan and D3D9 specifications, so acdream's MSAA-on silhouette edges do not match retail's pixel-for-pixel even at the same sample count. acdream's strict pixel gates therefore run with MSAA forced OFF on every arm, and MSAA-on gets only a relaxed visual smoke. | `src/AcDream.App/RuntimeOptions.cs` (`ACDREAM_MSAA_SAMPLES`); forced to 0 in `tools/run-offline-pixel-gate.ps1` | Measured, not assumed: plan §5.5.16 compared two backends at 4x and found **8.83% of the frame differing — 81,359 px of 921,600 — essentially all of it hugging foliage and silhouette edges**, which is ninety-fold over the 0.001 gate threshold. That is two implementations' sample patterns, not a renderer divergence, which is why forcing MSAA off is what makes the remaining difference attributable rather than a threshold relaxation. | Edge quality on thin geometry (fence rails, foliage, distant railings) differs from retail at the sub-pixel level whenever MSAA is on, which is the ordinary player configuration. Because the gates run MSAA off, **a real regression confined to the multisample path would not be caught by them** — that is the actual exposure this row records. | D3D9 `D3DRS_MULTISAMPLEANTIALIAS` / `D3DMULTISAMPLE_TYPE` as set by `RenderDeviceD3D::SetDefaultD3DStates @ 0x005a3800`; retail's sample pattern is the driver's, exactly as ours is |
|
||||
| AD-48 | **Filed at Campaign V slice V11 (2026-07-29).** Presentation is paced by the Vulkan swapchain present mode (FIFO, i.e. VSync) or by a refresh-rate software pacer when uncapped, rather than by retail's D3D9 `Present` with its own frame-rate limiter. Frame delivery cadence, and therefore input-to-photon latency, is a property of our present path rather than a port of retail's. | `src/AcDream.App/RuntimeOptions.cs:98-100`; `src/AcDream.App/Rendering/Gpu/Vk/VulkanSwapchain.cs` | Retail's limiter and ours both bound the frame rate to the display; the simulation is fixed-step and clock-driven, so gameplay timing does not ride on presentation cadence. The uncapped path exists for measurement and is not the shipping default. | A pacing mismatch shows up as judder or input latency that differs from retail's feel without any visual difference in a captured frame — invisible to every pixel gate by construction. Issue **#235** (the capped/RDP jump-presentation cadence alias) is the known live instance of this class. | D3D9 `IDirect3DDevice9::Present`; retail's frame limiter in `RenderDeviceD3D` |
|
||||
| AD-38 | Outgoing teleport viewports retire when retail's quantized animation level exceeds the last captured visible level 1022 (index 96), suppressing levels 1023/1024 up to 20.2 ms before retail's literal `elapsed >= 1.0` state edge. Incoming fades retain the exact timer. | `src/AcDream.Core/World/TeleportAnimSequencer.cs` (`OutgoingViewportReachedTerminalProjection`) | An uncapped 2000 FPS pass can publish the finite tunnel at levels 1023/1024 even though the paired 2013 retail capture switches viewports after 1022. The table-level cutover preserves the captured visible viewport ordering without throttling the application. | Exit sound, viewport replacement, and logout tunnel entry can occur at most two easing-table quanta (about 20.2 ms) earlier than retail's logical timer. | `UIGlobals::GetAnimLevel @ 0x004EE540`; `gmSmartBoxUI::UseTime @ 0x004D6E30`; paired retail/acdream captures documented in `docs/research/2026-07-15-retail-portal-space-pseudocode.md` |
|
||||
| AD-1 | Lost-cell machinery replaced by recoverable outdoor demote (**#107** safety net) + outdoor-restore `max(terrainZ, z)` under-terrain lift; retail goes `GotoLostCell` | `src/AcDream.Core/Physics/PhysicsEngine.cs:553` (+ :808) | acdream has no lost-cell state machine; outdoor landcell is the recoverable equivalent; the #107 auto-entry hold should make the demote branch unreachable | Gap in the hold → player committed to outdoor terrain inside/under a building (fake-grounded spawn, fall-through); a legit below-heightmap server restore is silently lifted — upward warp vs server | `GotoLostCell` pc:283418; `SetPositionInternal` 0x00515bd0, pc:283892-283945 |
|
||||
| AD-2 | Async readiness gates replace retail's synchronous destination cell load. **#229 refinement (2026-07-20):** login and F751 portal-space exit now share `WorldRevealReadinessBarrier`, so neither path can expose the normal viewport until the same render-publication, composite-texture, and collision domains converge. A hydratable indoor claim requires its owning Near-tier static/EnvCell mesh set, destination composites, and exact EnvCell physics (`IsSpawnCellReady`); an outdoor claim requires those render domains plus terrain/collision residency for the required Near ring. Hard-recenter generations and tier-aware completion application prevent stale overlapping loads/unloads or Far/Near jobs from opening or erasing the gate; mesh upload remains separate from balanced landblock ownership. Claims beyond NumCells still take the loud unhydratable-placement path. `RuntimeWorldTransitState` owns the shared reveal generation, accepted readiness, transit correlation, and exact generation/cell-scoped host-acknowledgement suffix. `WorldRevealCoordinator` is a graphical adapter holding only App resource receipts; normalized Runtime checkpoints observe ownership without defining another readiness path. **Slice E3 refinement (2026-07-24):** the same generation now publishes an immediate `WorldGenerationQuiescence` edge: old-world drawing/spatial queries, simulation/effect clocks, reconciliation, targeting, and 3-D audio stop while retained physical teardown advances through metered cursors and destination network/UI/streaming/readiness remain live. **Slice E4 refinement (2026-07-24):** accepted render/physics/static publication may span update frames through retained exact cursors, but reveal still consumes only the completed spatial/render-ready generation; building and EnvCell snapshots remain invisible until complete and the final spatial identity swap stays observer-atomic. **Slice E5 refinement (2026-07-24):** the reveal generation owns one exact destination reservation across every typed budget dimension. Stale completion cannot consume or clear its replacement, and hydratable incomplete content is never force-revealed; portal transit retains the DAT tunnel and centered retail wait cue until readiness converges. The hold→materialize→regain-control lifecycle remains owned by `TeleportAnimSequencer`. | `src/AcDream.Runtime/World/RuntimeWorldTransitState.cs`; `src/AcDream.App/Streaming/WorldRevealCoordinator.cs`; `src/AcDream.App/Streaming/WorldGenerationQuiescence.cs`; `src/AcDream.App/Streaming/WorldRevealReadinessBarrier.cs`; `src/AcDream.App/Streaming/StreamingOriginRecenterCoordinator.cs`; `src/AcDream.App/Streaming/LandblockPresentationPipeline.cs`; `src/AcDream.App/Streaming/StreamingController.cs`; `src/AcDream.App/Rendering/PortalTunnelPresentation.cs`; `src/AcDream.App/UI/PortalWaitNoticeController.cs`; `src/AcDream.App/Streaming/GpuWorldState.cs` (`IsRenderReady`); `src/AcDream.App/Rendering/Wb/LandblockSpawnAdapter.cs`; `src/AcDream.Core/Physics/PhysicsEngine.cs` (`IsSpawnCellReady`, `IsNeighborhoodTerrainResident`) | This is the asynchronous equivalent of retail leaving `SmartBox::position_update_complete` false while `CellManager::blocking_for_cells` is set: neither initial login nor portal arrival may reveal or continue simulating an old/partial collision world, a terrain-only Far shell, or a published-but-not-drawable GPU landblock. Indoor does not require a terrain heightmap, only the owning render landblock and exact EnvCell. | Gate opens early → grey/untextured first login or portal reveal, free-fall, wrong-cell rooting, missing scenery, or a still-active old generation; predicate never satisfies (streamer/DAT/upload failure) → login remains behind the world render gate, while portal transit remains in the authored tunnel and presents the centered wait cue after five seconds. | `SmartBox::UseTime` 0x00455410; `gmSmartBoxUI::UseTime` 0x004D6E30; `gmSmartBoxUI::EndTeleportAnimation` 0x004D65A0 |
|
||||
|
|
@ -80,7 +82,7 @@ accepted-divergence entries (#96, #49, #50).
|
|||
| AD-13 | 1-second dedup window for identical system chat messages (retail has none) | `src/AcDream.Core/Chat/ChatLog.cs:29` | ACE dual-sends the same system text (0xF7E0 + 0x02EB) for back-compat; without dedup every line doubled (Phase J compromise) | Two genuinely distinct but textually identical system messages within 1 s collapse to one line where retail shows both | ACE dual-send 0xF7E0 + 0x02EB |
|
||||
| AD-15 | `IsEnv` masks low-16 of the cell id (`(Id & 0xFFFF) >= 0x100`) where retail tests the full id | `src/AcDream.Core/World/Cells/ObjCell.cs:25` | Every real prefixed EnvCell id has low-16 ≥ 0x100 and every outdoor cell ≤ 0x40 — identical answers for all real dat ids, works for both bare and prefixed forms | None for real dat data; a hypothetical convention-violating id would route to the wrong (BSP vs terrain) point-in-cell logic | `CObjCell::GetVisible` pc:308215 |
|
||||
| AD-16 | Building-flood gate is a CPU frustum test on each building's `PortalBounds` AABB; retail floods exactly when the shell draws and an aperture survives (no bounds constant anywhere) | `src/AcDream.App/Rendering/WorldRenderFrameBuilder.cs` (`RuntimeWorldFrameBuildingSource.Gather`) | Documented as the tight equivalent of the shell viewconeCheck for flood purposes (the FPS fix the Chebyshev≤1 hack approximated); per-portal admission still goes through BuildFromExterior's screen clip; missing-bounds buildings always flood (safe over-include) | A too-small/stale PortalBounds AABB means the interior never floods — doorway shows a hole/black aperture from outside (inverse of the vanishing-staircase class) | `DrawBuilding` 0x0059f2a0; `BSPPORTAL::portal_draw_portals_only` 0x53d870 |
|
||||
| AD-17 | ≤8 GPU `gl_ClipDistance` half-planes per view region, degrading to a union-AABB scissor (over-include) on multi-polygon / >8-edge views; particles always scissor; scissor slices disable per-object viewcone culling. Retail CPU-clips against the exact portal polygon | `src/AcDream.App/Rendering/ClipPlaneSet.cs:23` | GL guarantees only 8 simultaneous clip planes; invariant documented: over-inclusion is safe, under-inclusion is the bug class | Fallback on complex multi-aperture views draws terrain/sky/particles/objects outside the true aperture but inside its AABB — background/interior bleed strips at doorways (the **#130** family) | `ACRender::polyClipFinish` decomp:702749; PView portal_view slices |
|
||||
| AD-17 | ≤8 GPU `gl_ClipDistance` half-planes per view region, degrading to a union-AABB scissor (over-include) on multi-polygon / >8-edge views; particles always scissor; scissor slices disable per-object viewcone culling. Retail CPU-clips against the exact portal polygon | `src/AcDream.App/Rendering/ClipPlaneSet.cs:23` | Vulkan's `VkPhysicalDeviceLimits::maxClipDistances` floor is 8, the same number GL guaranteed, so the V11 backend change does not move this limit; invariant documented: over-inclusion is safe, under-inclusion is the bug class | Fallback on complex multi-aperture views draws terrain/sky/particles/objects outside the true aperture but inside its AABB — background/interior bleed strips at doorways (the **#130** family) | `ACRender::polyClipFinish` decomp:702749; PView portal_view slices |
|
||||
| AD-18 | Aperture far-Z punch is two-pass stencil-gated with an invented mark bias: 0.0005 NDC capped to a 0.5 m EYE-SPACE span (`MarkBiasNdc`); retail's single DEPTHTEST_ALWAYS punch is safe only under painter's far→near order we don't have | `src/AcDream.App/Rendering/PortalDepthMaskRenderer.cs:149` | **#117** (2026-06-11): the unconditional punch erased nearer occluders, painting interiors through them; the two-pass form is the z-buffered equivalent of retail's ordering safety. **#129** (2026-06-12): the constant-NDC bias spanned ~190 m of eye depth at a landblock (non-linear depth) → distant occluders punched; the eye-space cap bounds the reach (`Issue129PunchBiasTests`). DO-NOT-RETRY: punch must stay depth-gated (ISSUES #108) | Door-plane-hugging geometry beyond the 0.5 m cap re-occludes the aperture (a **#108**-class regression at >10 m viewing range); an occluder within the cap in front of a distant aperture still punches through | `D3DPolyRender::DrawPortalPolyInternal` 0x0059bc90 (maxZ1=7 / maxZ2=6) |
|
||||
| AD-19 | Under outdoor roots, ALL dynamics draw in one z-buffered final pass; retail draws objects painter-ordered per landcell inside the landscape pass (interior roots route per **#118**) | `src/AcDream.App/Rendering/RetailPViewRenderer.cs:126` | The dynamics-drawn-LAST invariant is what makes the aperture depth punch safe (first BR-2 attempt punched after dynamics and erased the player, reverted `88be519`); z-buffer substitutes for painter's order on opaque geometry | Punch/seal correctness hinges on an ordering invariant — any pass added after DrawDynamicsLast, or alpha content needing painter order, gets erased inside apertures or composites wrong | `LScape::draw` → `DrawBlock` 0x005a17c0 → DrawSortCell pc:430124; `PView::DrawCells` 0x005a4840 |
|
||||
| AD-20 | Camera sweep fallback seeds the eye's `AdjustPosition` from the PLAYER's cell; retail re-seats at the sought eye's own tracked cell (rest of function is a verbatim `update_viewer` port) | `src/AcDream.App/Rendering/PhysicsCameraCollisionProbe.cs:97` | acdream's camera doesn't track the sought-eye's cell separately; the eye is near the player so the player-cell stab list is assumed to cover it | An eye outside the player cell's stab-list coverage (boundary corners, cross-landblock pull-back) seats in the wrong cell — and the viewer cell roots the whole render: one-frame wrong root (flap-class flash) | `SmartBox::update_viewer` 0x00453ce0, pc:92878-92883 |
|
||||
|
|
@ -193,7 +195,7 @@ AP-94..AP-112 for the confirmed retail-UI completion gaps.
|
|||
| AP-87 | **NPC MoveOrTeleport placement adds a 4 m body-to-target snap + a no-Sequencer snap** beyond retail's <96 m-unconditional interpolate (remote-creature de-overlap #184, 2026-07-07): retail `CPhysicsObj::MoveOrTeleport` (0x00516330) hard-places only on the teleport-timestamp / cell==0 branch or the ≥96 m far-snap, and InterpolateTo-queues every near correction; acdream ADDS two snap conditions — `|Body.Position − worldPos| > 4 m` (a large correction / an unplaced first-UP body) and `!willBeDrTicked` (no Sequencer to consume the queue). Without them an unplaced body (origin / spawn seed) would enqueue, the InterpolationManager's 100 m far-blip would fire, and the per-tick sweep would run over a huge distance in a cell not containing the body → garbage resolved pos → the reverted attempt's INVISIBLE monster. `firstUp` (`LastServerPosTime<=0`) is a belt hint only — the 4 m guard is the load-bearing backstop | `src/AcDream.App/Physics/LiveEntityNetworkUpdateController.cs` (NPC MoveOrTeleport routing, `BodySnapThresholdNpc`/`willBeDrTickedNpc`) | acdream's catch-up+sweep needs the body already near the target (a valid nearby cell) for the per-frame sweep to be small; the 4 m snap keeps it there, and retail's own large-correction path (the 100 m far-blip) is upstream of it. The de-overlap sweep also uses the fixed human sphere (R 0.48 / H 1.835) for the mover regardless of creature size, so large packed creatures de-overlap at human radii — inherits **TS-46** | A grounded remote that legitimately lags >4 m from its server pos snaps (a small pop) where retail would slide; a no-Sequencer server-moved entity hard-snaps every UP (no DR smoothing). Both are rare | `CPhysicsObj::MoveOrTeleport` 0x00516330 (near-interpolate <96 m; teleport/cell-0 snap; far-snap ≥96 m); `InterpolationManager` 100 m `AutonomyBlipDistance` (the retail large-correction path) |
|
||||
| AP-89 | **TransparentPartHook fade multiplies the SAMPLED TEXTURE alpha, not a separate material alpha channel** (#188, 2026-07-08 — the fading-wall secret-passage doors, e.g. "Pedestal Weak Spot"): retail's `CPhysicsPart::SetTranslucency` (0x0050e670) → `CMaterial::SetTranslucencySimple` (0x005396f0) REPLACES the D3D9 material's 4 alpha channels wholesale (`Ambient.a = Diffuse.a = Specular.a = Emissive.a = 1 − translucency`) — a per-material alpha that composes with, but is conceptually separate from, the surface's own sampled texture alpha. acdream's `mesh_modern.frag` has no material-alpha concept at all; the port multiplies the runtime fade's opacity multiplier directly against the already-sampled `color.a` (`FragColor = vec4(rgb, color.a * vOpacityMultiplier)`) | `src/AcDream.App/Rendering/Shaders/mesh_modern.frag` (final `FragColor` line); `src/AcDream.App/Rendering/Wb/WbDrawDispatcher.cs` (`ClassifyBatches` `opacityMultiplier` param, `InstanceGroup.Opacities`); `src/AcDream.Core/Rendering/TranslucencyFadeManager.cs` | Observably identical to retail for any surface whose base texture alpha is 1.0 everywhere — the Pedestal Weak Spot's stone-wall texture, and the overwhelming majority of AC surfaces, since `color.a * 1.0 == color.a` and the fade multiplier alone then drives the ramp exactly as `1 − translucency` would | A hypothetical object that is BOTH already alpha-keyed/blended from its own texture (stained glass, a flame surface) AND plays a TransparentPartHook fade simultaneously would compound the two alphas (texture-alpha × fade-multiplier) instead of the fade cleanly replacing/overriding the surface's own alpha as retail's material-replace does — such an object would fade darker / more-transparent than retail, not just at retail's rate | `CPhysicsPart::SetTranslucency` 0x0050e670; `CMaterial::SetTranslucencySimple` 0x005396f0 (`alpha = 1 − translucency`, applied to all 4 D3D9 material alpha channels) |
|
||||
| AP-90 | **Radar fellowship/allegiance relationship state is modeled but not yet delivered at runtime.** `RetailRadar.GetBlipShape` and `RadarBlipColors.For` implement retail's leader/member/allegiance precedence, and `RadarSnapshotProvider` exposes a `relationshipFor(guid)` seam, but acdream does not yet maintain live fellowship membership and its `AllegianceTree` is not wired into GameWindow. PK/PKLite relationship shapes do work from PWD flags. | `src/AcDream.App/UI/Layout/RadarSnapshotProvider.cs`; `src/AcDream.Core/Ui/RetailRadar.cs`; `src/AcDream.Core/Ui/RadarBlipColors.cs` | Preserve the exact model/seam now and avoid inventing membership from names or chat; connect it when the social game-event state is ported | Fellowship members render their ordinary player color/shape instead of bright-green leader/member triangles; allegiance members render an ordinary plus instead of a hollow box | `gmRadarUI::GetBlipColor` 0x004D76F0; `gmRadarUI::GetBlipShape` 0x004D7B60 |
|
||||
| AP-92 | Private creature viewports (paperdoll and examination) render through isolated `IGpuRenderTarget`s and blit into `UiViewport`; retail renders each `CreatureMode` directly and advances a cloned `CPhysicsObj`, while examination currently refreshes its clone from the live target's animated mesh pose. **V6l narrowing (2026-07-28):** the target is backend-neutral on both arms and the blit's V origin is no longer assumed — `IUiViewportRenderer.TextureIsBottomUp` derives it from the backend that made the texture, so a GL framebuffer's bottom-left origin and a Vulkan image's top-left one both composite upright. | `src/AcDream.App/Rendering/PrivateEntityViewportRenderer.cs`; `src/AcDream.App/Rendering/PaperdollFramePresenter.cs`; `src/AcDream.App/Rendering/CreatureAppraisalPresentation.cs`; `src/AcDream.App/UI/UiViewport.cs` | GL RTT is the modern backend equivalent; shared live pose data provides animation without registering a second gameplay entity or duplicating the world sequencer | Alpha, lighting, state isolation, or an assessment-time cloned motion diverging later from the live target can differ from direct retail CreatureMode presentation. The origin half of this risk is closed on both backends; a THIRD backend would have to answer `TextureIsBottomUp` for itself | `CPhysicsObj::makeObject(CPhysicsObj const*) @ 0x005144B0`; `gmPaperDollUI::PostInit @ 0x004A5360`; `BasicCreatureExamineUI::Init @ 0x004AB9C0`; `UIElement_Viewport::SetCamera`; retail `CreatureMode::Render` |
|
||||
| AP-92 | Private creature viewports (paperdoll and examination) render through isolated `IGpuRenderTarget`s and blit into `UiViewport`; retail renders each `CreatureMode` directly and advances a cloned `CPhysicsObj`, while examination currently refreshes its clone from the live target's animated mesh pose. **V6l narrowing (2026-07-28), V11 update (2026-07-29):** the target is backend-neutral and the blit's V origin is not assumed — `IUiViewportRenderer.TextureIsBottomUp` derives it from the backend that made the texture. With GL deleted the only answer in the tree is Vulkan's top-left origin, but the seam is kept rather than folded flat, because it costs one property and it is what let the origin question be answered by data instead of by assumption. | `src/AcDream.App/Rendering/PrivateEntityViewportRenderer.cs`; `src/AcDream.App/Rendering/PaperdollFramePresenter.cs`; `src/AcDream.App/Rendering/CreatureAppraisalPresentation.cs`; `src/AcDream.App/UI/UiViewport.cs` | Vulkan render-to-texture is the modern backend equivalent; shared live pose data provides animation without registering a second gameplay entity or duplicating the world sequencer | Alpha, lighting, state isolation, or an assessment-time cloned motion diverging later from the live target can differ from direct retail CreatureMode presentation. The origin half of this risk is closed; a FUTURE backend would have to answer `TextureIsBottomUp` for itself | `CPhysicsObj::makeObject(CPhysicsObj const*) @ 0x005144B0`; `gmPaperDollUI::PostInit @ 0x004A5360`; `BasicCreatureExamineUI::Init @ 0x004AB9C0`; `UIElement_Viewport::SetCamera`; retail `CreatureMode::Render` |
|
||||
| AP-93 | Paperdoll does not port `UpdateForRace`; all characters use the cdb-confirmed default held pose `0x030003C0` and default presentation | `src/AcDream.App/Rendering/DollEntityBuilder.cs`; `RetailPaperdollPoseApplicator` in `src/AcDream.App/Rendering/PaperdollFramePresenter.cs` | Exact for the tested Horan character; unresolved for other heritage/gender/body combinations | Non-default races can use the wrong pose, heading, camera framing, or presentation asset | `gmPaperDollUI::UpdateForRace @ 0x004A3ED0` |
|
||||
| AP-94 | Imported Type-12 text defaults to interactive/selectable behavior; display labels can focus/capture/drag, and vitals synthesize duplicate runtime labels instead of binding `0x100000EB/ED/EF` | `src/AcDream.App/UI/UiText.cs`; `src/AcDream.App/UI/Layout/VitalsController.cs` | Historical widget-generalization default; Wave 1 ports explicit Display/Selectable/Editable roles | Invisible/static text steals input and duplicate labels drift from DAT geometry | `UIElement_Text` property handlers; `gmVitalsUI::PostInit @ 0x004BFCE0` |
|
||||
| ~~AP-95~~ | **RETIRED 2026-07-11** — `UiButton` owns retail hover/pressed/released-outside, disabled, selected/toggle, missing-state fallback, hot-click, and distinct press/release controller callbacks. | `src/AcDream.App/UI/UiButton.cs`; `UiButtonStateMachine.cs` | — | — | `UIElement_Button::UpdateState_ @ 0x00471CF0`; mouse handlers `0x00471FF0..0x004721F0` |
|
||||
|
|
|
|||
|
|
@ -111,7 +111,7 @@ internal static class GpuBindingModel
|
|||
|
||||
// ---- set 2: the global texture table ----
|
||||
|
||||
/// <summary>Set index of the sampled-texture descriptor array (Vulkan) / logical table (GL).</summary>
|
||||
/// <summary>Set index of the sampled-texture descriptor array.</summary>
|
||||
public const uint TextureTableSet = 2;
|
||||
|
||||
/// <summary>Binding of the descriptor array within <see cref="TextureTableSet"/>.</summary>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue