445 lines
19 KiB
Markdown
445 lines
19 KiB
Markdown
# Modern runtime Slice K — Linux headless and multi-session host
|
||
|
||
**Status:** K0–K3 COMPLETE; K4 ACTIVE 2026-07-27
|
||
**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 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.
|
||
|
||
That entry gate passed at `a9a822f2`: 395 Runtime tests, 3,731 App tests /
|
||
3 skips, 8,696 complete-solution tests / 5 skips, exact-binary connected
|
||
lifecycle/reconnect, and canonical nine-stop routes all pass.
|
||
|
||
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.
|
||
|
||
The user additionally requires an explicit `-user`/`-password` (and long-form
|
||
alias) launch override for local single-session automation. It is accepted only
|
||
for `run` with exactly one configured session and intentionally carries the
|
||
ordinary process-list exposure of command-line arguments. Configuration-based
|
||
multi-session production continues to use the providers above. `ToString`,
|
||
validation errors, logs, checkpoints, crash reports, and metrics never echo the
|
||
password, and the session wraps the value in the same disposable credential
|
||
owner used by provider-based launch.
|
||
|
||
## 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
|
||
|
||
**Complete at `aada8a37`.** The dedicated `AcDream.Headless` executable and
|
||
test project reference only Runtime, strict no-connect configuration validation
|
||
runs on Windows and Linux, dependency/loaded-assembly guards forbid graphical
|
||
and audio backends, and the Windows/Ubuntu workflow builds/tests only the
|
||
portable closure. Local Ubuntu 24.04 gates pass 600 Core.Net, 119 Content, 395
|
||
Runtime, and 12 Headless tests. The exact Windows Release solution passes
|
||
8,708 tests / 5 intentional skips.
|
||
|
||
- 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
|
||
|
||
**Complete at `f8cb840f`.** The strict versioned configuration, XDG/Windows
|
||
path owner, redacted environment/stdin/owner-only-file credentials, typed
|
||
direct Runtime command route, canonical presentation-free inbound entity and
|
||
portal route, production single-session host, deterministic exit codes,
|
||
Ctrl+C/SIGTERM cancellation, and retryable teardown transaction are live.
|
||
The explicit `lifecycle-smoke` policy proves connect, enter, inbound state,
|
||
chat, lifestone recall, portal completion/LoginComplete, graceful stop,
|
||
reconnect, second-generation materialization, and terminal convergence.
|
||
|
||
Windows and Linux/WSL both passed that sequence against the same local ACE
|
||
server. ACE retains its account/session index for roughly two seconds after
|
||
confirming CharacterLogOff, so K1 gives the retiring production connection a
|
||
one-time 2.5-second quiescence suffix before replacement. This is not a
|
||
steady-state scheduler sleep; K2 replaces it with an absolute-deadline
|
||
scheduler transition.
|
||
|
||
- 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
|
||
|
||
**Complete at `7e8acb74` plus `38e83640`.** One monotonic
|
||
absolute-deadline scheduler advances every configured session in stable order,
|
||
uses bounded elapsed-time catch-up, and represents reconnect quiescence as a
|
||
deadline. Graphical and headless hosts now use the same Runtime local-player
|
||
frame controller, action owners, typed movement/selection/interaction/combat/
|
||
magic/state commands, hostile-target query, and ordered lifecycle/entity/
|
||
inventory/chat/movement/portal/combat/command event stream. The deterministic
|
||
gate proves MoveToState/AutonomousPosition order and elapsed cadence, stale
|
||
generation rejection, and 100,000 idle dispatch probes with zero turns and
|
||
zero managed allocation. Runtime passes 411 tests, Headless 31, App 3,732/3
|
||
skips, and the complete Release solution 8,744/5 skips. Evidence:
|
||
`docs/research/2026-07-27-slice-k2-deterministic-bot-parity.md`.
|
||
|
||
- 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
|
||
|
||
**Complete at `12b500d3`, `9569dadb`, `b6547ff3`, and `3f340125`.**
|
||
The process opens the exact DAT/pak/magic/terrain sources once, shares a
|
||
bounded immutable flat-collision cache, and gives each session its own
|
||
Runtime/physics/cache/movement/collision-neighborhood graph. The direct
|
||
accepted-state route now hydrates local-player movement and terrain, EnvCell,
|
||
building, stab, and procedural-scenery collision without loading presentation.
|
||
The 1/5/10-session same-GUID/portal/reconnect gate and policy-fault quarantine
|
||
pass. Runtime passes 412 tests, Headless 42, App 3,722/3 skips, and the complete
|
||
Release solution 8,758/5 skips.
|
||
|
||
The connected closeout adds explicit portal-route and long-lived observer
|
||
policies as consumers of the same `IGameRuntimeView` /
|
||
`IGameRuntimeCommands` seam used by graphical automation. It also aligns the
|
||
direct host with retail position routing: ordinary accepted position echoes no
|
||
longer re-run teleport placement or stop local motion, `ForcePosition` uses
|
||
`BlipPlayer` semantics plus an immediate position acknowledgement, and a real
|
||
teleport performs exactly one destination placement. High-volume collision
|
||
snap diagnostics are now explicitly opt-in.
|
||
|
||
The visible Windows production terminal passed connect, collision hydration,
|
||
lifestone recall, graceful reconnect, second-generation materialization, and
|
||
terminal teardown. The native Linux/WSL executable then passed direct CLI
|
||
launch, exact Aerlinthe/Caul/Rynthid/lifestone routing, external `teleallto`
|
||
portal generations, and a retail-client observer gate. The user saw the
|
||
headless character turn and run smoothly without rubberbanding; three accepted
|
||
external portal generations completed before ACE confirmed graceful logout.
|
||
The final Release gate passes 412 Runtime tests, 47 Headless tests, 3,722 App
|
||
tests / 3 skips, and 8,764 complete-solution tests / 5 skips. Evidence:
|
||
`docs/research/2026-07-27-slice-k3-shared-content-isolation-checkpoint.md`.
|
||
|
||
- 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
|
||
|
||
K4 telemetry checkpoint `cb512fd0` is complete. One process-level
|
||
absolute-deadline sampler now reports CPU, private/resident/managed memory,
|
||
GC, threads, handles, Linux descriptors/sockets, session/ownership debt,
|
||
shared-content leases, scheduler wakeups, and deadline lateness without adding
|
||
per-session workers or timers. The first native Linux two-account run exposed
|
||
and corrected both pre-connect deadline arming and a sub-millisecond
|
||
`Task.Delay` busy loop. Windows and native Ubuntu Headless tests pass 56/56;
|
||
the complete Release suite passes 8,773 tests / 5 skips. The user requested
|
||
that the active two-client observer process remain running, so the exact fixed-
|
||
binary connected baseline and numeric ceilings remain next. Evidence:
|
||
`docs/research/2026-07-27-slice-k4-resource-telemetry-checkpoint.md`.
|
||
|
||
- 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 <sha>` after each complete focused/Release/Linux/connected
|
||
gate. No later sub-slice begins while the prior exact rollback is absent from
|
||
this plan.
|
||
|
||
K0 exact rollback:
|
||
|
||
```text
|
||
git revert aada8a37c1e933f9f0a3f41dc4f05615b01023bc
|
||
```
|
||
|
||
K1 exact rollback:
|
||
|
||
```text
|
||
git revert f8cb840fb15b27f61afa5c33cad6c4dba8584949
|
||
```
|
||
|
||
K2 exact rollback, newest first:
|
||
|
||
```text
|
||
git revert 38e83640d907ee3e0819f3b38ca117ca60914d6b
|
||
git revert 7e8acb74dd20931d5ba88430fd100cd074a32e4d
|
||
```
|
||
|
||
K3 rollback, newest first:
|
||
|
||
```text
|
||
git revert 3f3401257c83bdd9f557eb577ba08cf2e5ec9a06
|
||
git revert b6547ff38c375328417855200f41c8af6436f246
|
||
git revert 9569dadb578b299b24e30aec85eee86ec0c2b3fb
|
||
git revert 12b500d38362e8aae0a23a4f9712b86367006f76
|
||
```
|