Skip to content

Architecture

Screeps Lab is intended to provide a controlled private Screeps laboratory for testing colony code and related packages. It separates orchestration policy from Screeps game behavior: Screeps Lab decides what experiment to run and how to judge it, while the subject code remains responsible for in-game decisions.

Component Boundaries

Private Screeps Server

The private server is the simulated Screeps world. Screeps Lab owns the server lifecycle through an internal adapter in packages/server; application code should depend on that adapter contract rather than calling a specific server implementation directly.

The current implementation uses the official screeps npm package and its launcher behind the adapter. The adapter initializes ignored runtime state in .screeps-lab/official-server/, binds the game and administrative CLI interfaces to loopback, reports the actual game and CLI ports returned by the launcher, controls pause/resume through official storage, emits normalized server observations, advances exactly one serialized tick at a time, provisions local subject players, installs subject modules, observes subject memory, waits for the Loki database to persist each observed tick, and stops all launched processes. Scenario and suite execution may opt into a bounded private-server minimum tick duration. The adapter regenerates an official supported local mod and seeds the matching persisted tickRate on every startup or reset; omission restores the normal 1000 ms cadence. The world still starts paused, and bounded acceleration retains authoritative boundary checks, persistence checkpoints, final persistence, and serialized simulation ownership. Requested duration is a minimum cadence, not a guarantee that processing will complete within that time.

Application code should not reach into launcher internals, storage internals, or official-server implementation details. Those details belong behind the packages/server adapter.

Server Observation Layer

The server adapter owns event production for the official server. It normalizes three serializable event categories:

  • lifecycle: meaningful adapter-established states such as starting, ready, paused, running, stopping, stopped, and failed.
  • output: reconstructed stdout/stderr lines with info, warn, or error level classification while preserving the raw message text.
  • tick: authoritative game-time values observed through the same storage source used by controlled ticking.

Subscribers attach to a scoped observation stream on the server instance and receive plain data objects. They can unsubscribe cleanly, wait for a matching event, or wait specifically for a later tick. Subscriber failures are recorded on the stream and do not prevent other subscribers from receiving events.

Observation has separate consumers. CLI presentation renders events for a live operator. Artifact persistence writes observations.jsonl and a readable server.log. Tests can subscribe directly or wait for event predicates. None of those consumers depend directly on child-process objects, Node streams, or official launcher internals.

Tick observations are synthesized in 0.2.1 by polling the official storage game-time value inside the adapter. If several ticks pass between polls, the adapter reports the latest authoritative tick; it does not invent intermediate events. Polling timers and listeners are scoped to the wait operation and are cleaned up on match, timeout, shutdown, or failure.

Interactive Play Controller

npm run play is an operator workflow for keeping the private world alive as a local playground while preserving Screeps Lab ownership of the simulation clock. The play controller never hands the official server an uncontrolled free-running loop. Instead, continuous play repeatedly invokes the same controlled primitive:

  1. Ensure the world is paused.
  2. Observe the current game tick.
  3. Resume the official server.
  4. Wait until one tick advances.
  5. Pause immediately.
  6. Wait until that tick is persisted.

The configured tick interval is a minimum start-to-start period. If a tick plus persistence takes longer than the configured interval, the next tick starts only after the previous one is complete and persisted. max means no additional delay after persistence. The controller uses one asynchronous loop rather than a queueing interval timer, so tick executions do not overlap.

Manual N stepping uses the same primitive and leaves the session paused. Normal shutdown preserves runtime state; only the explicit --reset option deletes .screeps-lab/official-server/.

Creative Mode

Creative Mode is the 0.2.3 operator-authority slice for a local private world. It is explicitly enabled with npm run play -- --creative; without that flag, play mode accepts creative status and creative help but refuses mutating commands.

The CLI owns only command parsing, terminal presentation, and the artifacts/play/latest/creative-commands.jsonl command log. The authoritative API is runCreativeCommand on the server adapter. That adapter owns local player selection, room targeting, coordinate validation, terrain and collision checks, object construction, energy-store capacity rules, controller updates, official private-server storage writes, runtime restart publication, and persistence readback.

Creative Mode intentionally exposes typed operations instead of arbitrary JavaScript evaluation or a database shell. The first supported mutation set can create creeps, create selected structures and construction sites, remove explicitly targeted objects, set/fill/drain energy, set an owned controller level, and record a private-runtime checkpoint note. Checkpoints rely on the official private-server runtime state and are not the same artifact contract as the MMO-neutral snapshot importer.

Creative mutations are serialized with controlled ticks in the play session. If the world is running, the session pauses around the mutation and resumes after the adapter reports success or failure. Creative Mode targets only the local official private server managed by Screeps Lab and has no path to the public Screeps MMO.

Automated World Bootstrap

Automated world bootstrap is the 0.2.5 deterministic room fixture slice. The operator command is npm run world:bootstrap; the CLI owns argument parsing, operator rendering, run directories, and summary serialization. The server adapter owns the official-private-server-specific work: lifecycle control, pause/serialization, private player provisioning, room and terrain records, controller ownership and RCL, deterministic spawn placement, source and mineral seed objects, active-room and map-view metadata, runtime restart publication, controlled tick advancement, persistence waiting, and authoritative re-read verification.

The command is non-destructive unless --reset is supplied. In preserving mode, compatible player, room, controller, terrain, and spawn state is reused. Material differences fail before silent replacement, including ownership conflicts, controller RCL mismatches, duplicate spawn names, spawn relocation requests, malformed terrain, and occupied requested spawn tiles. In reset mode, Screeps Lab starts a clean private-server runtime through the same server lifecycle and clears seeded room state before creating the requested fixture.

When --spawn-x and --spawn-y are omitted, the adapter chooses a valid tile deterministically near room center. Placement rejects walls, room edges, occupied tiles, controller/source/mineral tiles, and other incompatible objects. Bootstrap verification is not based on write-call success; it re-reads the room, controller, and spawn from authoritative storage after a controlled tick.

Subject Configuration and Workspace

Subject configuration describes what Screeps Lab intends to run independently from the private-server implementation. The implemented 0.1.2 subject type is a synthetic smoke subject that writes a deterministic Memory.screepsLab marker. Release 0.2.4 adds an external Screeps repository adapter in @screeps-lab/subjects.

The external adapter owns canonical path resolution, subject configuration, Git provenance inspection, explicit local build execution, generated module collection, validation, deterministic manifest generation, and execution-wrapper injection. It returns a normalized prepared subject containing a module map and metadata; CLI and server layers do not reach back into subject-specific build layout details.

@screeps-lab/server owns official private-server details: player provisioning, complete branch replacement, activation, runtime restart publication, controlled tick advancement, persistence waits, and Memory observation. The CLI orchestrates the sequence:

resolve -> inspect -> build -> collect -> validate -> manifest -> install -> activate -> controlled tick -> verify

All build and module validation happens before any private-server mutation. The server adapter replaces the complete module map on the selected private-server branch and attempts to restore the previous branch record if the installation write fails. It does not automatically roll back merely because subject code throws during its first tick; that state is reported as entry reached with a subject-loop error.

Execution verification is generic and Lab-owned. Screeps Lab appends a wrapper to the configured entry module, preserving the module graph and relative requires. The wrapper writes Memory.screepsLab.externalSubject with the deployment ID, subject ID, revision, first/last execution ticks, and loop error state, then delegates to the subject's original loop.

Snapshot Import

The snapshot importer is split across two boundaries. @screeps-lab/snapshots owns the Lab-side v1 contract, JSON parsing, structural and semantic validation, checksum verification, compatibility normalization, import planning, and raw Memory object-ID rewriting. Schema v1 supports an additive optional terrainRooms catalog. When it is absent, the snapshot package derives a one-room effective catalog from the primary room name and room.terrain. @screeps-lab/server owns official private-server storage mutation.

The CLI workflows are:

  1. sandbox:inspect --snapshot <path> reads and validates a snapshot artifact without starting the server or mutating world state.
  2. sandbox:import --snapshot <path> --reset starts the official server with a clean runtime, validates the artifact before mutation, reconstructs the primary room and every effective terrain record, creates or reuses a private player for the source username, rewrites primary-room raw Memory references from public MMO object IDs to private IDs, verifies authoritative terrain, objects, ownership, and publication metadata, writes import artifacts, and stops the server.

Only the primary room contributes controllers, ownership, sources, minerals, structures, construction sites, object-ID remapping, and Memory. Auxiliary catalog entries create the minimal room and terrain records required by the official runtime and are included in published terrain data so Game.map.getRoomTerrain(roomName) can load them before the first subject tick. They are not active, accessible, map-visible, owned, or populated merely because terrain is present. Terrain-only room records are excluded from automatic roomsForceUpdate activation with a non-expiring force-update deadline.

The first importer requires --reset for every actual import. This preserves the repository safety convention: absence of --reset is never interpreted as permission to overwrite existing private-world data.

Overlord

Overlord is the working title for the orchestration component. It will coordinate scenario setup, subject deployment, observation, assertions, agent delegation, session state, and artifact collection.

Scenario Runner

The future scenario runner will execute scenario lifecycles, enforce limits, poll state, evaluate assertions, and produce reports. It is a planned capability, not an implemented one.

Conceptual Flow

flowchart TD
    Operator[Operator] --> Overlord[Overlord orchestration]
    Overlord --> Agents[Coding or testing agents]
    Overlord --> Subject[Subject workspace]
    Overlord --> Server[Private Screeps server]
    Overlord --> Runner[Scenario runner]
    Server --> Observations[World observations]
    Runner --> Observations
    Observations --> Overlord
    Overlord --> Artifacts[Reports, logs, telemetry, screenshots, replays]

Repository Boundaries

Screeps Lab is a monorepo for the lab itself. The primary Screeps colony repository is not part of this repository and must not be committed as a nested repository or submodule.

The lab may reference a subject through configuration, such as ../screeps, or through an ignored path under subjects/local/. SCREEPS_LAB_SUBJECT_PATH selects the local external subject when a CLI path is not supplied. Future adapters can add more subject-resolution strategies without changing this boundary.

Subject Isolation

Subject isolation keeps experiments reproducible and prevents the lab from accidentally taking ownership of colony code. A scenario should be able to say which subject revision it used, but Screeps Lab should not assume the internal layout of any one bot.

Adapters and interfaces should be preferred over direct assumptions about the primary Screeps repository.

Scenario Lifecycle

The target scenario lifecycle is:

  1. Resolve subject.
  2. Validate environment.
  3. Start or reset private server.
  4. Apply world fixture.
  5. Deploy subject code.
  6. Start scenario.
  7. Advance ticks.
  8. Observe state.
  9. Evaluate assertions.
  10. Stop on success, failure, or timeout.
  11. Collect artifacts.
  12. Clean up or preserve the run for inspection.

The smoke command implements a narrow deterministic version of steps 3, 7, 10, and 11. The subject smoke command implements a narrow deterministic version of steps 1, 3, 5, 7, 8, 10, and 11 by installing a synthetic subject and observing its memory marker. The play command implements an operator-controlled persistent version of steps 3, 7, 10, and 11. The sandbox import command implements a snapshot-backed version of step 4 for a clean reconstructed private world. The world bootstrap command implements a deterministic owned-room version of step 4 and verifies it through steps 7 and 8. The full scenario lifecycle remains a design target.

Suite Progress and Terminal Presentation

The suite package owns a versioned stream of small, serializable progress events. It emits suite phases, subject preparation provenance, manifest-ordered scenario queue/start/completion/skip state, forwarded scenario lifecycle and tick progress, and the terminal suite result. Scenario progress originates inside the scenario engine rather than from CLI polling. Observer failures are isolated from terminal classification, and each suite run preserves progress-events.jsonl.

The CLI reduces those events into operator state. Interactive TTYs may redraw a width-bounded panel; noninteractive environments receive stable line-oriented output. Rendering owns no orchestration and does not parse human logs or partial reports. The server adapter remains the only owner of official-server tick-rate configuration and speed changes. Configured minimum cadence, exact scenario tick ownership, and host-limited observed throughput are distinct values. The terminal panel does not implement the deferred web dashboard or parallel suite workers.

Artifact and Telemetry Flow

Generated output should flow into artifacts/ during local runs. Reports, logs, screenshots, replays, temporary server state, databases, and generated context exports are ignored by default unless intentionally committed as fixtures or examples.

Smoke artifacts are written to artifacts/smoke/latest/ and contain console output, structured observation JSON Lines, a human-readable observation log, startup notes, environment metadata, and a tick summary. World bootstrap artifacts are written to run-specific directories under artifacts/world-bootstrap/runs/, and artifacts/world-bootstrap/latest points at the newest run. They contain console output, structured observation JSON Lines, startup/server logs, environment metadata, and bootstrap-summary.json with requested state, observed state, created-vs-reused resources, reset intent, ticks, diagnostics, verification result, and failure reason when applicable. Subject smoke artifacts are written to run-specific directories under artifacts/subject-smoke/runs/, and artifacts/subject-smoke/latest points at the newest run. They contain console output, structured observation JSON Lines, a human-readable observation log, startup notes, environment metadata, and a subject summary with non-secret identity metadata, installation details, the controlled tick result, and the observed memory marker. Play artifacts are written separately to artifacts/play/latest/ and contain console output, structured observation JSON Lines, a human-readable observation log, startup notes, environment metadata, and a session summary with starting/ending tick, configured speed, reset flag, connection details, shutdown reason, fatal server output, and any error. Sandbox import artifacts are written to artifacts/sandbox-import/latest/ and contain console output, server logs, startup notes, environment metadata, and an import summary with the validated snapshot summary, sorted terrain room names, terrain and auxiliary counts, authoritative verification, and object ID remap counts. Routine summaries do not embed full terrain rows. Creative Mode command artifacts are written inside artifacts/play/latest/ as creative-commands.jsonl, with non-secret structured records for the validated command, tick, success or failure, affected object ID when available, and error message when applicable.

Telemetry should explain what happened during a run, not just whether a scenario passed. Future reports should preserve enough context to reproduce a failure.

Future Extension Points

Planned extension points include:

  • subject adapters for local paths, temporary clones, mounts, and Git sources
  • server adapters for different private-server launch strategies
  • scenario fixtures and assertion libraries
  • agent protocols for delegated coding and testing tasks
  • report writers and telemetry sinks
  • CI adapters and dashboard integrations