diff --git a/docs/plans/2026-07-24-modern-runtime-architecture.md b/docs/plans/2026-07-24-modern-runtime-architecture.md index fc85f854..d822cc6d 100644 --- a/docs/plans/2026-07-24-modern-runtime-architecture.md +++ b/docs/plans/2026-07-24-modern-runtime-architecture.md @@ -1061,6 +1061,10 @@ host-parity tests after each move. ### Slice K — Linux headless and multi-session host +**Prepared execution plan:** +[`2026-07-26-modern-runtime-slice-k.md`](2026-07-26-modern-runtime-slice-k.md). +Implementation begins after Slice J's no-window closeout. + **Purpose:** Deliver efficient automated bots. - Add Linux CI for Core, Core.Net, Content, Runtime, and Headless. diff --git a/docs/plans/2026-07-26-modern-runtime-slice-k.md b/docs/plans/2026-07-26-modern-runtime-slice-k.md new file mode 100644 index 00000000..64ff4add --- /dev/null +++ b/docs/plans/2026-07-26-modern-runtime-slice-k.md @@ -0,0 +1,331 @@ +# Modern runtime Slice K — Linux headless and multi-session host + +**Status:** PREPARED — BEGINS AFTER SLICE J CLOSEOUT +**Parent:** `2026-07-24-modern-runtime-architecture.md`, Slice K +**Authorization:** the user approved Slices F–L on 2026-07-24 +**Purpose:** run efficient automated clients on Linux without a display, GPU, +audio device, hidden graphical process, or duplicated gameplay model, then +prove 30 isolated sessions can share immutable content safely in one process. + +## 1. Entry contract + +Slice K starts only after J8 proves one direct `GameRuntime` can connect, enter +world, receive authoritative state, move, use, fight/cast, portal, stop, +reconnect, and converge without presentation assemblies. + +K adds a production host over that same root. It does not move gameplay out of +App again and does not create a bot-specific world model. + +Dependency direction: + +```text +AcDream.Headless ──► AcDream.Runtime + ├──► AcDream.Core.Net + ├──► AcDream.Core + ├──► AcDream.Content + └──► AcDream.Plugin.Abstractions + +AcDream.Headless -X► AcDream.App / AcDream.UI.* / Silk.NET / OpenAL / ImGui +``` + +The existing `AcDream.Cli` is asset/performance tooling with ImageSharp and DAT +inspection dependencies. It is not renamed or expanded into the bot process. +Create a dedicated `AcDream.Headless` executable and matching test project. + +## 2. Initial platform inventory + +The Runtime/Core.Net/Content closure is already mostly portable: + +- `NetClient` uses the Windows `SIO_UDP_CONNRESET` socket control code only + behind `OperatingSystem.IsWindows()`. +- retail CP1252 encoding is provided by .NET code pages and is not a Windows + OS dependency. +- Runtime has no App, Silk, OpenAL, Arch, ImGui, or native-window reference. +- `PakReader`, prepared collision, and immutable content contracts are + presentation-independent. + +Known work: + +- no ordinary Linux build/test workflow exists; +- no portable application-path/config owner exists; +- graphical `RuntimeOptions` is App-owned and cannot be reused; +- no production headless session host or typed bot command adapter exists; +- `WorldSession` uses wall-clock deadlines and a fixed `Thread.Sleep(200)` + during connect; scheduler/timing ownership needs an explicit audit; +- a few Runtime/Core diagnostics still read environment variables at call + sites; +- `RuntimeRemotePhysicsUpdater` reads `DateTime.UtcNow`; +- no multi-session mutable-static/source guard exists; +- each `RuntimePhysicsState` currently creates a production + `PhysicsDataCache`; shared immutable collision content versus per-session + mutable cache/scratch must be made explicit; +- no long-duration, 30-session process resource ledger exists. + +## 3. Fixed host model + +### Process owner + +One `HeadlessProcessHost` owns: + +- parsed immutable process options; +- portable paths; +- credential provider; +- shared read-only DAT/prepared-content/collision sources; +- one deterministic scheduler; +- a registry of session hosts keyed by configured bot identity; +- process cancellation and terminal diagnostics. + +### Session owner + +Each `HeadlessSessionHost` owns exactly: + +- one `GameRuntime`; +- one headless host lease; +- one `LiveSessionHost` borrowing `GameRuntime.Session`; +- one session command adapter implementing `IGameRuntimeCommands`; +- one event subscription; +- one bot policy/plugin instance; +- per-session clock deadlines, sequence state, query scratch, and cancellation; +- an exact retryable teardown cursor. + +Sessions never share: + +- `GameRuntime`, session/generation, local-player identity; +- entity directory, object table, inventory, character, communication, action, + movement, environment, transit, or event sequencer; +- physics engine, moving bodies, transition scratch, projectile workset, or + spatial queries; +- mutable bot/plugin state; +- credentials or logs containing credentials. + +Sessions may share only immutable, thread-safe content: + +- mapped `acdream.pak`; +- read-only DAT databases behind a proven synchronization/reader contract; +- immutable spell/skill/region tables; +- immutable flat collision payloads. + +Sharing a cache is permitted only when both its values and all bookkeeping +mutations are thread-safe and session-neutral. Otherwise each session gets its +own bounded cache over the shared immutable source. + +## 4. Scheduler and timing contract + +Use one monotonic process scheduler, not one busy loop or dedicated OS thread +per bot. + +- Each session publishes its next absolute deadline. +- The scheduler waits until the earliest deadline or cancellation/wakeup. +- Due sessions tick in stable configured order. +- A bounded catch-up loop preserves elapsed-time behavior without a tick-count + clock. +- One slow session cannot make another session skip generation validation or + accepted packet order. +- Network receive may remain asynchronous, but every accepted inbound callback + for a session is serialized through that session's Runtime turn. +- Bot policy runs only after accepted inbound and simulation work for that + turn, then emits typed commands synchronously. +- No `Thread.Sleep` appears in the steady scheduler. + +Wire cadence uses the instance clock and absolute elapsed deadlines. It never +derives AutonomousPosition or MoveToState emission from “every N scheduler +ticks.” The gate compares: + +- AutonomousPosition inter-packet intervals; +- MoveToState-on-change emission points; +- use/combat/cast request order; +- portal LoginComplete placement; + +against the graphical host under the same deterministic scenario and a +committed tolerance band. + +## 5. Configuration, paths, and credentials + +### Portable paths + +Introduce a BCL-only `HeadlessPathSet`: + +- Linux config: `$XDG_CONFIG_HOME/acdream`, else `~/.config/acdream`; +- Linux data: `$XDG_DATA_HOME/acdream`, else `~/.local/share/acdream`; +- Linux cache: `$XDG_CACHE_HOME/acdream`, else `~/.cache/acdream`; +- Windows equivalents use the appropriate `Environment.SpecialFolder`; +- explicit command-line paths override defaults; +- all paths use `Path` APIs and are normalized before use. + +### Configuration + +Use a versioned JSON document with process settings plus session descriptors. +Unknown properties are rejected by default. Each session has stable ID, +endpoint, character selector, bot policy/plugin ID, and credential reference. +Passwords never appear in the ordinary process config. + +### Credentials + +Support non-interactive providers suitable for local automation: + +- environment-variable reference; +- standard-input secret stream; +- credential file with an explicit user-only-permission check on Linux. + +Do not accept a plaintext password directly on the command line because process +lists expose it. `ToString`, validation errors, logs, checkpoints, crash +reports, and metrics contain only redacted credential IDs. Secrets are cleared +from retained buffers when the session is disposed where the BCL representation +allows it. + +## 6. Bot surface + +The public bot API is the existing stable seam: + +- `IGameRuntimeView`; +- `IGameRuntimeCommands`; +- `IRuntimeEventSource`; +- `RuntimeStateCheckpoint`; +- generation and teardown acknowledgements. + +Add a headless command adapter that maps those typed commands to the same +Runtime owners and `WorldSession` sends used by the graphical adapter. It may +provide host operations for target queries or decoded content lookups, but it +must not expose mutable owner internals or reproduce App's selection/render +world. + +Minimum bot commands/events: + +- movement and stance; +- selection and target cycling; +- use, appraisal, pickup, and looting; +- combat mode, attack, and spell cast; +- portal recalls; +- inventory/shortcut/spellbook/character/social mutations; +- chat and slash/server commands; +- lifecycle, entity, inventory, chat, movement, portal, combat, and command + outcomes. + +Commands are generation-gated, synchronous at the Runtime seam, and inert as +soon as session teardown begins. + +## 7. Execution + +### K0 — portability boundary and Linux CI + +- Add `AcDream.Headless` and `AcDream.Headless.Tests`. +- Add dependency-closure and loaded-assembly guards. +- Add a normal GitHub Actions matrix for Windows and Ubuntu. +- Build/test Core, Core.Net, Content, Runtime, Plugin.Abstractions, Headless, + and their tests on Ubuntu with no App restore/build requirement. +- Audit and correct portable socket, encoding-provider, file-map, path, + executable, and cancellation behavior. +- Promote surviving per-call Runtime diagnostics into instance/process + diagnostic options where necessary. + +Gate: Ubuntu restores, builds, tests, and runs `--help` plus a no-connect +configuration validation without a graphical/audio/native-window dependency. + +### K1 — portable config, credentials, and single-session CLI + +- Implement portable paths and versioned strict config. +- Implement redacted credential providers. +- Compose one `GameRuntime`, one `LiveSessionHost`, one command adapter, one + bot policy, and one teardown transaction. +- Support start, graceful stop, reconnect, Ctrl+C/SIGTERM, and deterministic + exit codes. +- Emit structured credential-safe lifecycle/resource diagnostics. + +Gate: one Windows and one Linux no-window client connect to local ACE, enter +world, receive state, issue a harmless command, portal, reconnect, and exit +cleanly without loading presentation assemblies. + +### K2 — deterministic scheduler and bot API parity + +- Add the absolute-deadline multi-session scheduler. +- Serialize accepted per-session work while allowing sessions to progress + independently. +- Implement the complete typed bot command/event adapter. +- Add deterministic graphical-versus-headless command, packet-order, and + packet-timing traces. +- Inject `TimeProvider`/monotonic clocks into remaining wall-clock Runtime + reads that affect session behavior. + +Gate: command/event traces match; AutonomousPosition cadence and +MoveToState-on-change fall within the committed tolerance; idle sessions do +not busy-loop. + +### K3 — shared immutable content and isolation + +- Introduce a process content owner with leases and exact last-user disposal. +- Share mapped pak/DAT/immutable tables and flat collision payloads. +- Retain per-session mutable physics/cache/scratch/state. +- Audit all mutable statics, thread-local caches, random sources, diagnostics, + and process-wide identifiers in the Headless dependency closure. +- Add 1/5/10-session connect, activity, portal, disconnect, reconnect, and + GUID-collision tests in one process. +- Add plugin/bot-policy fault isolation. + +Gate: equal server GUIDs in different sessions remain isolated; one failing +session/plugin cannot corrupt or stop another; shared content opens once and +disposes only after the final host acknowledgement. + +### K4 — 30-session stress and closeout + +- Measure a 1-session warm baseline, then 5, 10, and 30 sessions. +- Before the final run, commit numeric ceilings derived from K3 evidence for: + - incremental private bytes per established session; + - managed heap per session; + - idle CPU per session and whole process; + - handles, threads, timers, and sockets; + - scheduler wakeups and maximum lateness; + - reconnect/teardown debt. +- Run at least two hours with 30 local-server sessions, including recurring + movement, use, combat/cast, chat, portal, death/reconnect, and randomized + cancellation. +- Require stable plateaus over the final hour, no per-session creep, no + busy-loop CPU, no credential exposure, and terminal convergence for all 30. +- Update architecture, milestones, roadmap, issues/divergences, + AGENTS/CLAUDE, and durable memory. + +Gate: 30 sessions in one Linux process for at least two hours, within committed +numeric resource ceilings, with wire timing parity and clean teardown. Slice K +then closes. + +## 8. Adversarial matrix + +- missing XDG variables, unwritable home, relative paths, symlinked config, + and paths containing spaces/non-ASCII; +- invalid JSON version, duplicate session IDs, duplicate credential refs, and + unknown fields; +- password present in exception, log, metric label, process arguments, or + crash output; +- SIGINT/SIGTERM during connect, world entry, portal, combat, and teardown; +- DNS failure, IPv4-only/IPv6 answers, port exhaustion, socket reset, packet + loss, and delayed receive; +- one session floods inbound while another is idle; +- one bot policy blocks, throws, recursively commands, or retains a stale + generation; +- same server GUID and local IDs across different roots; +- shared content disposed while another session reads; +- mutable cache/scratch shared accidentally between sessions; +- scheduler clock jump, long pause, catch-up, cancellation wakeup, and + deadline tie; +- 30 simultaneous reconnects and staggered teardown; +- stale outbound route after reconnect; +- plugin load/unload failure; +- no renderer/audio/window allocation or assembly load. + +## 9. Non-goals + +- No Linux graphical window, input, audio, or packaging; those are Slice L. +- No GPU work. +- No hidden/minimized App process. +- No server-authoritative gameplay decisions on the client. +- No bot-only shortcuts around the Runtime command/event seam. +- No per-bot duplicate DAT/pak mapping unless evidence proves the shared reader + cannot be made safe. +- No service locator, global current-session singleton, or mutable static bot + registry. + +## 10. Commit and rollback discipline + +Land K0–K4 as separate buildable commits. Record the full production SHA and +exact `git revert ` after each complete focused/Release/Linux/connected +gate. No later sub-slice begins while the prior exact rollback is absent from +this plan.