acdream/docs/plans/2026-07-26-modern-runtime-slice-k.md

17 KiB
Raw Blame History

Modern runtime Slice K — Linux headless and multi-session host

Status: K0K2 COMPLETE; K3 AUTOMATED CHECKPOINT COMPLETE, CONNECTED GATE ACTIVE 2026-07-27 Parent: 2026-07-24-modern-runtime-architecture.md, Slice K Authorization: the user approved Slices FL 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:

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

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

Automated checkpoint complete at 12b500d3, 9569dadb, and b6547ff3. 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 remaining K3 work is the visible-terminal Windows connected gate plus the matching Linux/WSL production gate. 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

  • 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 K0K4 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:

git revert aada8a37c1e933f9f0a3f41dc4f05615b01023bc

K1 exact rollback:

git revert f8cb840fb15b27f61afa5c33cad6c4dba8584949

K2 exact rollback, newest first:

git revert 38e83640d907ee3e0819f3b38ca117ca60914d6b
git revert 7e8acb74dd20931d5ba88430fd100cd074a32e4d

K3 automated-checkpoint rollback, newest first:

git revert b6547ff38c375328417855200f41c8af6436f246
git revert 9569dadb578b299b24e30aec85eee86ec0c2b3fb
git revert 12b500d38362e8aae0a23a4f9712b86367006f76