refactor(app): extract typed RuntimeOptions for startup env vars (Step 1)
Lifts 13 startup-time environment variables out of GameWindow.cs into a
single typed AcDream.App.RuntimeOptions record read once in Program.cs.
Behavior-preservation only — no live behavior change, no visual change.
Verified end-to-end against ACE on 127.0.0.1:9000: full M1 demo loop
(walk Holtburg, click door, click NPC, portal entry) plus DEVTOOLS
ImGui panels load cleanly.
Why: GameWindow.cs is 10,304 LOC and scattered Environment.GetEnvironmentVariable
calls were one of the structural smells called out in the new
"Code Structure Rules" doc. Typed options is the safest cut to make
first because the substitution is mechanical and parsing semantics
get pinned by unit tests.
What lands:
- CLAUDE.md: removed stale R1→R8 execution-phases line, replaced with
pointers to the milestones doc + strategic roadmap (the actual
source of truth). Tightened the "check ALL FOUR references"
section to describe WB as the production rendering base, not
just a reference. New "Code Structure Rules" section (6 rules)
captures the discipline we're committing to.
- docs/architecture/acdream-architecture.md: removed dangling link
to the deleted memory/project_ui_architecture.md.
- docs/architecture/code-structure.md (NEW, 376 LOC): rationale for
the 6 rules + 6-step extraction sequence
(RuntimeOptions → LiveSessionController → LiveEntityRuntime →
SelectionInteractionController → RenderFrameOrchestrator →
GameEntity aggregation). This PR is Step 1.
- src/AcDream.App/RuntimeOptions.cs (NEW, 100 LOC): typed record
with FromEnvironment(string) factory and Parse(datDir, env)
overload for testability. Covers ACDREAM_LIVE, _TEST_HOST/PORT/
USER/PASS, _DEVTOOLS, _DUMP_MOVE_TRUTH, _NO_AUDIO,
_ENABLE_SKY_PES, _HIDE_PART, _RETAIL_CLOSE_DEGRADES,
_DUMP_SCENERY_Z, _STREAM_RADIUS.
- src/AcDream.App/Program.cs: builds RuntimeOptions once, passes
to GameWindow.
- src/AcDream.App/Rendering/GameWindow.cs: ctor takes RuntimeOptions;
7 startup-cached env-var fields become expression-bodied
properties or direct _options.X reads; TryStartLiveSession,
audio init, legacy stream-radius branch all route through
_options.
- tests/AcDream.App.Tests/ (NEW project, 10 unit tests + csproj):
pins parser semantics — default-off bools, the literal "0"
gate for RETAIL_CLOSE_DEGRADES, the >=0 guard for
STREAM_RADIUS, null-vs-empty for user/pass, exact-"1" check
for diagnostic flags. Registered in AcDream.slnx.
Out of scope (per code-structure.md §4):
- Per-call-site ACDREAM_DUMP_* / _REMOTE_VEL_DIAG diagnostic reads
sprinkled through GameWindow (~40 sites). Rule 5 in CLAUDE.md
commits us to migrating these opportunistically as larger
extractions land, not in a bulk pass.
- AcDream.Core's project-reference to Chorizite.OpenGLSDLBackend.
Only the stateless .Lib namespace is used; tightening the project
reference is documented as future work in code-structure.md §2.
Build: green.
Tests: AcDream.App.Tests 10/10 ✓, Core.Net.Tests 294/294 ✓,
UI.Abstractions.Tests 419/419 ✓,
AcDream.Core.Tests 1073/1081 (8 pre-existing failures verified
against pre-refactor baseline by stash-and-rerun).
Visual verification: full M1 demo loop against ACE +Acdream login
including DEVTOOLS panel host load.
Next: Step 2 — extract LiveSessionController per code-structure.md §4.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
2950cd5740
commit
eda936dc4d
9 changed files with 863 additions and 63 deletions
126
CLAUDE.md
126
CLAUDE.md
|
|
@ -126,12 +126,18 @@ ourselves".
|
|||
both radii and MSAA/anisotropic/A2C/completions-per-frame as a unit.
|
||||
Spec: `docs/superpowers/specs/2026-05-09-phase-a5-two-tier-streaming-design.md`.
|
||||
|
||||
**Execution phases:** R1→R8 in the architecture doc. Each phase has clear
|
||||
goals, test criteria, and builds on the previous. Don't skip phases.
|
||||
**Execution model:** the active source of truth is the **milestones doc**
|
||||
(`docs/plans/2026-05-12-milestones.md`) for "what are we building right
|
||||
now" and the **strategic roadmap** (`docs/plans/2026-04-11-roadmap.md`)
|
||||
for the per-phase ledger of what's shipped, what's in flight, and what
|
||||
comes next. **Ignore the old "R1→R8" sequence** — it was an early refactor
|
||||
sketch that no longer matches reality (see the "Roadmap Model" section in
|
||||
`docs/architecture/acdream-architecture.md`). Per-phase detailed specs
|
||||
live under `docs/superpowers/specs/`.
|
||||
|
||||
The codebase is organized by layer (see architecture doc). Current phase
|
||||
state lives in memory (`memory/project_*.md`), plans in `docs/plans/`,
|
||||
research in `docs/research/`.
|
||||
The codebase is organized by layer (see architecture doc + the **Code
|
||||
Structure Rules** section below). Plans live in `docs/plans/`,
|
||||
research in `docs/research/`, persistent project memory in `memory/`.
|
||||
|
||||
**UI strategy:** three-layer split — swappable backend (ImGui.NET +
|
||||
`Silk.NET.OpenGL.Extensions.ImGui` for Phase D.2a, custom retail-look
|
||||
|
|
@ -164,12 +170,74 @@ click-to-rebind. As of Phase K (2026-04-26), ALL keyboard / mouse
|
|||
input flows through the dispatcher — no IsKeyPressed polling outside
|
||||
the per-frame movement queries.
|
||||
|
||||
## Code Structure Rules
|
||||
|
||||
These are the structural rules the project commits to. They are
|
||||
**process rules** (where code goes, what depends on what), not style
|
||||
rules (formatting / naming). They exist to keep the layer split honest
|
||||
and to stop `GameWindow.cs` from continuing to grow into a 10k-line
|
||||
god object. The full rationale + the extraction sequence we're
|
||||
pursuing live in [`docs/architecture/code-structure.md`](docs/architecture/code-structure.md).
|
||||
|
||||
1. **No new substantial feature bodies in `GameWindow.cs`.** It is
|
||||
already over 10,000 lines and owns runtime wiring. New runtime
|
||||
work goes into a dedicated controller / sink / orchestrator class
|
||||
in `src/AcDream.App/` (or deeper in `AcDream.Core` when it's pure
|
||||
logic). Adding a handful of fields and a one-paragraph method to
|
||||
wire an extracted class in is fine; adding a new ~200-line feature
|
||||
directly is not. When in doubt, file a small follow-up extraction
|
||||
as part of the change.
|
||||
|
||||
2. **`AcDream.Core` must not depend on the window / GL / backend
|
||||
projects, except via documented interop seams.** The only
|
||||
currently-allowed seams are `WorldBuilder.Shared` (stateless helpers:
|
||||
`TerrainUtils`, `TerrainEntry`, `RegionInfo`) and
|
||||
`Chorizite.OpenGLSDLBackend.Lib` (stateless helpers only:
|
||||
`SceneryHelpers`, `TextureHelpers`). New Core code that needs a GL
|
||||
surface must define an interface in Core and let `AcDream.App`
|
||||
implement it — never the reverse. If you need to add a project
|
||||
reference to Core, the change must come with an inventory-doc
|
||||
update explaining why.
|
||||
|
||||
3. **UI panels target `AcDream.UI.Abstractions` only.** No panel may
|
||||
import `AcDream.UI.ImGui` or any backend namespace. ViewModels,
|
||||
commands, and the `IPanel` / `IPanelRenderer` contract are the
|
||||
surface; everything else is backend. This is what lets us swap
|
||||
D.2a (ImGui) for D.2b (retail-look) later without rewriting
|
||||
panels.
|
||||
|
||||
4. **Startup environment variables enter through a typed options
|
||||
object.** `AcDream.App.RuntimeOptions` is the single source of
|
||||
truth for startup configuration. `Program.cs` reads the
|
||||
environment once into `RuntimeOptions` and passes it into
|
||||
`GameWindow`. Don't sprinkle `Environment.GetEnvironmentVariable`
|
||||
reads through new code paths; add a field to `RuntimeOptions` and
|
||||
pipe it through.
|
||||
|
||||
5. **Runtime probes (diagnostic toggles) belong in diagnostic owner
|
||||
classes.** Today `AcDream.Core.Physics.PhysicsDiagnostics` owns the
|
||||
`ACDREAM_PROBE_*` family. The pattern: one static class per
|
||||
subsystem, exposing typed bool/int properties read from env vars
|
||||
once at startup (with optional runtime-toggleable counterparts for
|
||||
the DebugPanel). Per-call-site `Environment.GetEnvironmentVariable`
|
||||
reads in new code are a process smell — if a flag survives one
|
||||
phase, promote it to a diagnostic owner. The dozens of existing
|
||||
`ACDREAM_DUMP_*` reads scattered through `GameWindow` are tech
|
||||
debt; do not add more.
|
||||
|
||||
6. **Tests live in the project matching the layer under test.** Core
|
||||
tests in `tests/AcDream.Core.Tests/`, UI tests in
|
||||
`tests/AcDream.UI.Abstractions.Tests/`, network tests in
|
||||
`tests/AcDream.Core.Net.Tests/`. App-layer tests (RuntimeOptions
|
||||
parsing, etc.) belong in `tests/AcDream.App.Tests/`. When adding a
|
||||
new test project, register it in `AcDream.slnx`.
|
||||
|
||||
## How to operate
|
||||
|
||||
**You are the lead engineer AND architect on this project at all times.**
|
||||
You own the architecture (`docs/architecture/acdream-architecture.md`),
|
||||
the execution plan (phases R1–R8), the development workflow, and all
|
||||
technical decisions. Stop as little as possible. Drive work autonomously and continuously through full phases and
|
||||
the execution plan (milestones doc + strategic roadmap), the development
|
||||
workflow, and all technical decisions. Stop as little as possible. Drive work autonomously and continuously through full phases and
|
||||
across commit boundaries. Do not stop mid-phase for routine progress check-ins,
|
||||
permission asks on low-stakes design calls, or "should I continue?" confirmations.
|
||||
The user has repeatedly authorized direct-to-main commits, multi-commit sessions,
|
||||
|
|
@ -1095,13 +1163,19 @@ already-running ACE session via the handshake race.
|
|||
stop transitions, and keep their visual position tracked smoothly
|
||||
between the 5–10 Hz UpdatePosition bursts (dead-reckoning).
|
||||
|
||||
## Reference repos: check ALL FOUR, not just one
|
||||
## Reference repos: cross-check the relevant ones
|
||||
|
||||
When researching a protocol detail, dat format, rendering algorithm, or
|
||||
any "how does AC do X" question, **check all four of the vendored
|
||||
references in `references/`** before committing to an approach. Do not
|
||||
settle on the first hit and move on — cross-reference at least two of
|
||||
these, ideally all four:
|
||||
The `references/` tree holds **six** vendored projects (ACE, ACViewer,
|
||||
WorldBuilder, Chorizite.ACProtocol, holtburger, AC2D). They overlap in
|
||||
some areas and disagree in others. Before committing to an approach,
|
||||
**cross-reference at least two of them** for the domain you're working
|
||||
in — the per-domain hierarchy in the next section tells you which to
|
||||
read first. A single reference can be misleading; the intersection of
|
||||
the relevant references is almost always the truth. The user has
|
||||
repeatedly had to remind me about this when I narrowly searched one ref
|
||||
and missed obvious answers in another.
|
||||
|
||||
The six references:
|
||||
|
||||
- **`references/ACE/`** — ACEmulator server. Authority on the wire
|
||||
protocol (packet framing, ISAAC, game message opcodes, serialization
|
||||
|
|
@ -1113,17 +1187,21 @@ these, ideally all four:
|
|||
`ACViewer/Render/TextureCache.cs::IndexToColor` for the canonical
|
||||
subpalette overlay algorithm.
|
||||
- **`references/WorldBuilder/`** — **acdream's rendering + dat-handling
|
||||
BASE (not just a reference).** As of 2026-05-08 acdream is moving to
|
||||
fork WorldBuilder upstream and depend on the fork for terrain,
|
||||
scenery, static objects, EnvCells, portals, sky, particles, texture
|
||||
decoding, mesh extraction, visibility/culling. WorldBuilder is
|
||||
MIT-licensed, exact-stack match (Silk.NET + .NET), and verified to
|
||||
render the world correctly. **Before re-porting any rendering or
|
||||
dat-handling algorithm from retail decomp, check
|
||||
`docs/architecture/worldbuilder-inventory.md` first.** If WB has it,
|
||||
use WB's port. If WB doesn't have it (network, physics, animation,
|
||||
movement, UI, plugin, audio, chat), port from retail decomp as
|
||||
before.
|
||||
base.** WorldBuilder is not just a reference: as of Phase N.4 (shipped
|
||||
2026-05-08), `ObjectMeshManager` is the production mesh pipeline,
|
||||
`WbMeshAdapter` is the seam, and `WbDrawDispatcher` is the production
|
||||
draw path. The modern path (`N.5`) is **mandatory** — missing bindless
|
||||
throws at startup, there is no legacy fallback. **Before re-porting
|
||||
any rendering or dat-handling algorithm from retail decomp, read
|
||||
`docs/architecture/worldbuilder-inventory.md` first.** The inventory
|
||||
tells you what WB covers (terrain, scenery, static objects, EnvCells,
|
||||
portals, sky, particles, texture decoding, mesh extraction,
|
||||
visibility/culling) and what we still write ourselves (the 🔴 list:
|
||||
network, physics, animation, movement, UI, plugin, audio, chat).
|
||||
WorldBuilder is MIT-licensed and exact-stack with us (Silk.NET +
|
||||
.NET); the divergences we've documented (e.g. WB's terrain split
|
||||
formula vs retail's `FSplitNESW`) are called out in the inventory
|
||||
doc.
|
||||
- **`references/Chorizite.ACProtocol/`** — clean-room C# protocol
|
||||
library generated from a protocol XML description. Useful sanity check
|
||||
on field order, packed-dword conventions, type-prefix handling. The
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue