Skip to content

Latest commit

 

History

137 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Strata

A deterministic expedition game built on an authoritative Go world simulation.

A layered agentic world simulator.

Start with the reviewer-ready walkthrough in DEMO.md.

Strata is a layered world simulator turned expedition game. The default route opens a three-contract campaign where one player-controlled scout must charge, mine, and survive regional failures before returning to a physical extraction site:

  • Verdant Recovery establishes the route through a western food collapse
  • Iron Scar Haul raises the ore quota during a haul-road outage
  • Stormbreak closes the campaign inside a cascading grid failure
  • Reserve Cell and Ore Sieve are available immediately; the faster Drift Rig unlocks after the first successful extraction
  • every contract offers two deterministic routes with distinct spawn points, extraction windows, hazards, collision obstacles, and payout bonuses; Verdant's two routes additionally use hashed 32×18 authoritative heightfields whose slopes and blocked cells change movement and energy in Go
  • Canopy Circuit's Canopy Mire and Spore Bloom overlap at the authored Living Causeway gate. Go replaces their ordinary combined traversal penalty with a content-hashed 82% pace / 3.4 energy-per-second interaction while both fields are occupied; HUD, minimap, and braided ground marks only present that result
  • the three rigs carry authored non-weapon abilities: Reserve Pulse, Sieve Overdrive, and Drift Stabilizer; their 16/20/16-unit holds retain expedition ore outside the ambient market, and cargo mass now trades pace and energy for a viable extraction payload
  • the isolated Go session is the sole progression authority and now issues the canonical strata.save-envelope.v1 at field-station and debrief safe points. The save contains the exact authoritative campaign profile, compiled-content SHA-256 identity, strata.campaign-state.v2 selections and Ironman flag, structured migration history, and replay provenance. Session-scoped run IDs make debrief application idempotent, browser autosaves rotate an opaque recovery copy, and Settings can export or import the engine save without an account
  • every terminal run opens a campaign debrief with the authoritative objective ledger, score, grade, payout, route/rig context, and newly earned unlocks; continuation is gated on the rotating autosave or an explicit manual recovery export. Replay export is the basis-bound session archive, verified by fresh authoritative command/fixed-step reconstruction; debrief review remains a bounded presentation history over that evidence

The same deterministic world remains available as a real-time survival sandbox:

  • agents move in continuous 2D space
  • food sources regenerate and deplete
  • ore fields create a second resource pressure and a simple credit economy
  • scarcity drives migration, clustering, deaths, reproduction, and wealth accumulation
  • named scenarios apply timed regional shocks and log world events
  • a browser viewer renders agents, food density, active pressure fields, and the event feed live

The campaign is built on a single-agent hero mode:

  • the world now starts with 1 focus agent
  • reproduction is disabled by default so the graphics stay isolated while you work on presentation
  • the Ecosystem preset restores a 1000 agent reproductive world for population-scale runs

The frontend now has two surfaces:

  • / serves the built Vite + TypeScript + Three.js expedition campaign
  • /classic serves the built TypeScript Canvas viewer from web/classic
  • /?showcase=1 preserves the guided cinematic simulator walkthrough

The cinematic viewer is backed by the versioned strata.world.v1 manifest. It describes an authored 8192×4608 fictional world on a 16×9 grid of 512-unit chunks, four regions, and twelve landmarks. Simulation and replay coordinates remain authoritative in Go; the renderer uses the manifest only for world presentation and streaming. The manifest is available at GET /api/world-manifest, and snapshots include its manifestVersion.

Quick Start

The bin/strata CLI wraps every command this repo runs. Put it on your PATH once with bin/strata link (symlinks into ~/.local/bin), then:

strata dev
strata test
strata lint
strata coverage
strata web dev
strata site dev
strata verify

strata help lists everything, including the web/site build and lint tasks, the desktop host gate, and the dependency audit. The development commands run the same invocations as CI — the canonical, CI-verified forms stay in AGENTS.md.

Run

The browser viewers are built artifacts and are not committed, so build them once before the first run:

npm --prefix web ci
npm --prefix web run build

Then start the server:

go run ./cmd/strata

Then open http://localhost:8080.

Flask-compatible host

Strata can also start behind Flask without replacing its authoritative Go simulation. The Flask process serves the Vite bundle, starts the Go server when needed, and reverse-proxies /api/* (including the event stream):

python3 -m venv .venv-flask
source .venv-flask/bin/activate
pip install -r requirements-flask.txt
npm --prefix web ci
npm --prefix web run build
python flask_app.py

Open http://127.0.0.1:5102. Set PORT for the Flask port or STRATA_BACKEND_URL to reuse an already-running Go backend. If port 8080 belongs to an incompatible older service, the local launcher safely starts the current backend on 58080 instead.

The server binds to loopback by default. Set STRATA_ADDR=127.0.0.1:8081 when another local process already owns the default port. Binding another interface is an explicit deployment choice.

Rebuild with npm --prefix web run build whenever you change anything under web/. Without a build, the Go server still serves the API but has no viewer assets to hand back.

Environment variables

Variable Consumer Default Purpose
STRATA_ADDR cmd/strata 127.0.0.1:8080 Listen address for the Go server. Loopback by default; binding another interface is an explicit deployment choice that changes the trust boundary below.
STRATA_TOKEN cmd/strata unset Shared bearer token that re-enables mutating requests when STRATA_ADDR is not loopback. Ignored on a loopback bind.
PORT flask_app.py 5102 Port for the Flask companion host.
FLASK_HOST flask_app.py 127.0.0.1 Bind host for Flask; loopback addresses only, anything else is refused.
STRATA_BACKEND_URL flask_app.py http://127.0.0.1:8080 Go backend that Flask proxies /api/* to; set it to reuse an already-running server.
STRATA_FALLBACK_BACKEND_URL flask_app.py http://127.0.0.1:58080 Where the launcher starts Strata when the preferred port is held by an incompatible service.
STRATA_STATIC_DIR flask_app.py web/dist Built viewer bundle that Flask serves.
VITE_STRATA_DESKTOP web/vite.config.ts unset true routes the web build into dist-desktop for the desktop embed pipeline.

Trust boundary

Summarized here; SECURITY.md is the full statement, including what the code already enforces and how to report a vulnerability.

Strata's HTTP API is a local, single-user control plane. It has no accounts, no roles, and no per-request authorization: any caller that can reach the port can drive the simulation. That is deliberate, and it is safe only because the server binds 127.0.0.1 by default, so the operating system limits callers to this machine.

Setting STRATA_ADDR to anything else publishes those endpoints to the network, so the server fails closed rather than trusting the new audience:

STRATA_ADDR STRATA_TOKEN Mutating requests (POST/PUT/PATCH/DELETE) Read-only requests
Loopback (default) ignored Served, unauthenticated Served
Non-loopback unset Refused with 403, and a warning is logged at startup Served
Non-loopback set Require Authorization: Bearer $STRATA_TOKEN, else 401 Served

The guard is enforced centrally by HTTP method, so a mutation route added later inherits it automatically. Read-only endpoints stay unauthenticated in every mode: the token narrows who can change the world, not who can watch it. If you need confidentiality as well, terminate TLS in front of Strata — it speaks plain HTTP and the bearer token is only as private as the transport carrying it.

Sessions and replay API

The primary web client creates an isolated simulation with POST /api/sessions and includes its sessionId on snapshot, control, operations, intervention, contract, replay, and SSE requests. Separate browser tabs therefore run separate authoritative expeditions. GET /api/sessions/{id} describes a live session and DELETE /api/sessions/{id} closes it; server-side expiry remains a fallback for abandoned clients. Every state endpoint requires a sessionId: requests without one are rejected with 400 missing sessionId rather than falling back to a shared world. Mutation requests require same-origin application/json; this keeps the local browser API unavailable to cross-origin form/fetch traffic while preserving CLI and native-adapter access.

Every authoritative snapshot carries an {epoch, sequence} cursor. The browser rejects duplicate, stale, or malformed snapshots across control responses and both live transports before presentation state can change. GET /api/campaign-profile?sessionId={id} reads the session-owned progression ledger; PUT replaces an exact-schema progression profile only while the expedition is idle or terminal. Restore rejects host-mode changes, unknown fields, unearned unlock closure, oversized input, and active-run replacement. GET /api/campaign-state?sessionId={id} returns the owned v2 field-station selection state; PUT atomically validates and replaces its complete mutable selection/route map and Ironman intent. Campaign-state updates are replay neutral, unavailable during active expeditions, and cannot disable a consequence-locked Ironman campaign. Progression hosts also reject start, restart, or reset commands that would erase an active expedition.

GET /api/replay?sessionId={id} returns a verified strata.replay-archive.v2. Its replay basis binds the exact game and engine versions, current GameContent schema and SHA-256, normalized deterministic Config SHA-256, and effective initial seed. Each successful command and each fixed Step call consumes one monotonic invocation ordinal in addition to epoch/tick, so paused steps, same-tick commands, speed changes, and resets have one reconstructable order. Command-record hashes bind the basis hash; checkpoint proofs bind the complete basis, cursor, command boundary, reason, and public state fingerprint. The archive final proof binds its basis, exact final cursor/state, command chain, checkpoint proof list, and truncation state.

Before HTTP or the desktop bridge returns an archive, VerifyReplayArchive rejects incompatible engine/content/config, noncanonical commands, broken chains or proofs, malformed or over-budget schedules, missing or divergent checkpoints, truncation, and final-state drift. It then instantiates a fresh simulation, reproduces every inferred fixed step and recorded command, and matches the complete periodic/epoch/terminal checkpoint schedule plus final snapshot. Checkpoints remain inspection evidence with resumable: false until private simulation and PRNG state have a versioned codec. Active expeditions checkpoint every 6,000 fixed steps (five minutes at 20 Hz), with 128 retained records; the conservative three-contract 0.25x campaign bound is 43 records. Command count/bytes and total reconstruction work are independently bounded; a cutoff advances monotonically when evidence is evicted, and a truncated archive deliberately cannot pass full reconstruction. Snapshot storage is deep-owned on capture and export. The paused expedition menu exports this canonical archive directly.

GET /api/save-envelope?sessionId={id} issues the session's canonical campaign save at the field station or debrief. PUT accepts the unchanged raw JSON body at the same safe points and returns the accepted canonical form. Imports are limited to 4 MiB and fail closed on duplicate or unknown keys, trailing JSON, incompatible engine/content identity, invalid owned payloads, migration-chain errors, or a campaign access-mode mismatch. A successful import atomically replaces the authoritative CampaignProfile, CampaignState, and session-owned migration history; it never partially restores any of them. Ironman imports must preserve monotonic consequences and prove the exact overlap of the bounded debrief ledger; once the entire retained overlap has expired, a direct leap is rejected until a cumulative chain anchor exists.

The additive strata.game.v1strata.game.v2 migration accepts only the exact pre-v2 compiled-content SHA-256 at a safe point, appends the structured content/add-expedition-cargo-v1 record, and returns the current canonical save. This unshipped v2 lands retained cargo and public route hazard/obstacle/interaction geometry as one atomic content version; no intermediate v2 hash is supported. Unknown hashes and active-run imports remain rejected atomically. Exact strata.campaign-state.v1 payloads migrate to v2 through the authored campaign-state-v1-to-v2 record; unknown, duplicate, or contradictory migration lineage fails closed.

V1 intentionally omits runCheckpoint, and save issue/import/export is blocked during an active expedition until private simulation and PRNG state have a versioned resumable codec. replayHead.checkpointHash is the latest retained v2 checkpoint proof, which transitively binds basis, invocation cursor, command boundary, reason, and state fingerprint. It records provenance, but does not restore a run and cannot locate its archive unless a caller persists that archive as a sidecar. An exact pre-cargo save may preserve its historical state-hash pointer during safe-point migration; the next engine reissue replaces it with the current v2 proof because the old envelope cannot recreate a missing legacy sidecar.

GET /api/game-content publishes strata.game.v2: each contract's retained cargo target, each rig's cargo capacity, immutable route hazards, obstacles and linked interactions, and each Verdant route's terrain schema, ID, and SHA-256 hash plus the quantized height lattice and boolean walkability mask once. The 20 Hz snapshot carries matching live terrain contact and authoritative active interaction state. Replay state now identifies strata.public-snapshot.v3; consumers must resolve the referenced terrain and route geometry from the matching content catalog.

Desktop bridge technical proof

internal/desktopbridge exposes the same authoritative session, command, snapshot, campaign-profile restore, campaign-state read/update, inspection, save, and replay contracts through a dependency-free binding surface. web/src/transport/desktop-simulation-transport.ts consumes those bindings and the versioned strata:simulation-snapshot event stream. The primary UI selects that adapter when a packaged host injects window.__STRATA_DESKTOP__; otherwise it retains the HTTP/SSE browser-demo path.

cmd/strata-desktop is the native host and pins Wails v3.0.0-beta.3. It is a separate Go module with its own go.mod/go.sum, which is what keeps the shipped server honest: the root strata module requires nothing outside the standard library, so go build ./cmd/strata never resolves the Wails runtime or its ~137-package transitive toolchain. CI enforces the boundary by building and testing the server with no desktop system libraries installed; the separate desktop job installs GTK/WebKit and runs make desktop. Because Go evaluates the internal rule on import paths, the module path stays strata/cmd/strata-desktop so the host can still import strata/internal/.... Production tasks generate typed bindings, build the desktop-mode Vite entry into web/dist-desktop, stage it for embedding, compile the host, and create an ad-hoc-signed macOS technical-proof bundle at artifacts/Strata.app. The browser demo remains separately buildable in web/dist, so desktop packaging cannot replace its HTTP entry with one that expects the Wails runtime. See docs/DESKTOP_BRIDGE.md for the binding/event contract, reproducible task sequence, and explicit release-work boundaries.

Public product contracts

The dependency-free public strata/contracts Go package defines strict, versioned SaveEnvelope, ReplayHead, and data-only ContentPackManifest formats. The save envelope and v2 campaign state are now wired through the simulation session, HTTP/SSE adapter, desktop bridge proof, and browser recovery slots. Its contentHash is the canonical SHA-256 of the current compiled GameContent, including authoritative terrain, cargo limits, and route interaction geometry. The same package provides exact-decimal canonical JSON, SemVer engine/dependency ranges, dependency-cycle checks, portable-path and payload-budget validation, and engine-owned schema validation. Content packs cannot declare scripts, plugins, native libraries, shaders, or WASM.

Content-pack activation remains foundation work rather than a claim that the hard-coded catalog has migrated. See docs/PRODUCT_CONTRACTS.md for the activation and validation sequence.

Controls

  • choose a contract, extraction route, and scout rig at the field station
  • WASD / arrow keys or a standard controller steer the expedition scout; analog input has a configurable dead zone and is quantized before it becomes an authoritative command
  • click the terrain to set a route target
  • Space / the controller south button activates the fitted rig ability
  • Escape / the controller menu button pauses or resumes, with keyboard focus contained in the pause menu and rapid pause edges coalesced
  • Abandon expedition in the pause menu uses a deliberate two-press confirmation and returns through the same authoritative, saved debrief flow
  • Settings is available from the field station and pause menu, with interface scaling, system-aware reduced motion, a color-safe palette, graphics presets, authored audio-cue captions, persistent conflict-free keyboard/controller rebinding, analog axis/dead-zone controls, rotating save recovery, and manual save export / safe-point import
  • Export replay in the pause menu writes the authoritative hash-chained session command/checkpoint archive rather than the renderer's interpolation buffer; the engine reconstructs and verifies it before HTTP or desktop export
  • win, failure, and abort each open the full-screen expedition debrief exactly once per run; Review run presents the terminal world and evidence deck, Return to debrief restores the outcome, and Continue to field station remains unavailable until campaign authority has accepted the outcome and a local autosave or manual recovery copy exists. Review mode cannot issue authoritative commands
  • complete charge and hold objectives, retain the required cargo in the rig, then physically bring that payload into the marked extraction site before the 15–20 minute contract window closes
  • Pause / Resume is also available in the inspector controls
  • 1x, 2x, 4x simulation speed
  • seed input plus Reset Seed / Load Preset
  • presets for Focus Agent, Competition Demo, and Ecosystem
  • scenario picker with Start Scenario / Clear Scenario
  • one-click demo buttons for Food Collapse and Bloom Shift
  • Autoplay Demo for a staged in-browser presentation
  • guided demo briefing with stage progress, live metric readouts, and mobile-safe presentation overlays
  • replay scrubber with event bookmarks and live/replay switching
  • presentation mode for a cleaner showcase surface
  • pilot takeover with click-to-move and WASD / arrow key steering
  • metrics for credits, ore reserves, and ore price
  • event feed showing phase transitions and scenario completion

For the cinematic or hands-free presentation launch, open:

The guided demo runs through baseline competition, a western food collapse, a migration corridor cue, an eastern bloom reset, and replay review. The sidebar briefing explains what to watch, while presentation mode keeps the same stage, progress, and metric readouts over the 3D viewport.

Test

The full local quality gate matches CI:

make test

It verifies Go formatting, Go tests, the deterministic 90+ simulator eval, and the Vite build for both the 3D and classic viewers.

Manifest and deterministic checks can be run independently:

make manifest-check determinism-check

These checks prove all 144 chunk coordinates are covered, landmark and LOD references validate, presentation presets retain the manifest dimensions, and same-seed snapshots remain byte-identical. Go replay tests additionally prove fresh full-schedule reconstruction and command/checkpoint/final-proof tamper rejection through the engine, HTTP, and desktop boundaries.

Run the simulator eval directly when you want the scored evidence:

go run ./cmd/strata-eval -min-score 90

Individual checks are still available:

go test ./...
npm --prefix web run build

Structure

  • cmd/strata: HTTP server and streaming endpoints
  • cmd/strata-eval: deterministic 90+ quality gate for simulator behavior and demo evidence
  • contracts: public dependency-free save, replay-head, canonical hashing, and data-only content-pack formats
  • internal/sim: deterministic simulation core
  • internal/sim/campaign.go: versioned contract, route, hazard, obstacle, loadout, and ability catalog
  • internal/sim/terrain.go: versioned quantized route terrain, public hashes, bilinear contact, swept walkability/grade collision, and reachability checks
  • internal/sim/campaign_profile.go: exact-schema, session-owned progression, unlock closure, bounded debrief history, and safe-point restore validation
  • internal/sim/session.go: isolated simulation-session ownership, lifecycle, cleanup, and serialized stepping/commands
  • internal/sim/command.go and internal/sim/replay.go: canonical command application plus basis-bound, hash-chained, full-schedule replay verification
  • internal/desktopbridge: Wails-neutral single-session binding and snapshot event proof with deterministic parity tests
  • internal/sim/world_manifest.go: authoritative world manifest and validation
  • web: Vite/TypeScript source and built assets for both browser viewers
  • web/src/campaign/campaign-progress.ts and web/src/campaign/campaign-authority.ts: rotating opaque engine-save recovery slots, separate campaign preferences, legacy one-way migration, and the read-only projection of Go campaign authority into field-station presentation state
  • web/src/campaign/campaign-authority-queue.ts and web/src/session/snapshot-cursor.ts: serialized authority synchronization and stale-snapshot rejection shared by the browser and desktop runtime paths
  • web/src/expedition/expedition-setup.ts: contract, route, rig, and campaign field-station board
  • web/src/expedition/expedition-debrief.ts, web/src/expedition/expedition-debrief-lifecycle.ts, and web/src/expedition/expedition-debrief-view.ts: pure authoritative outcome model, once-per-run review/sync lifecycle, and accessible responsive debrief surface
  • web/src/renderer/terrain-surface.ts: strict route/catalog/contact identity checks and presentation-only height/contact interpolation; it never decides traversal
  • web/src/input/input-context.ts: explicit campaign, gameplay, inspection, replay, pause, and modal input policy
  • web/src/input/input-bindings.ts and web/src/input/pilot-input.ts: device-local, versioned input profiles plus the single quantized/coalesced authoritative steering sink
  • web/src/input/camera-mode.ts: deterministic mapping from experience/input state to cinematic, chase, spectator, and inspection cameras
  • web/src/transport/simulation-transport.ts and web/src/transport/http-sse-transport.ts: transport-neutral simulation calls and the isolated HTTP/SSE adapter
  • web/src/transport/desktop-simulation-transport.ts and web/src/transport/runtime-simulation-transport.ts: validated desktop event/binding adapter plus automatic packaged-host/browser selection
  • web/src/hud/player-settings.ts: validated accessibility and presentation preferences stored separately from campaign progress as strata.settings.v1
  • web/classic: classic Canvas viewer HTML entry served at /classic after npm --prefix web run build
  • evals: MVP capability and regression checks
  • docs/CINEMATIC_ACCEPTANCE.md: browser, performance, and reduced-motion acceptance checklist
  • docs/DESKTOP_BRIDGE.md: versioned desktop binding/event contract and the pinned Wails packaging proof and remaining storefront release boundary
  • docs/PRODUCT_CONTRACTS.md: SaveEnvelope and data-only content-pack contract semantics and activation order
  • docs/WORLD_PIPELINE.md: source-art and runtime asset pipeline

GitHub Flow

  • work from short-lived branches off main
  • open pull requests back into main
  • let GitHub Actions run the full make test gate: Go formatting, Go tests, go run ./cmd/strata-eval -min-score 90, and the Vite build for both browser viewers
  • use the PR template and issue templates to keep changes reviewable

Notes

  • The 3D viewer combines validated authored GLB assets with generated terrain, simulation-driven agents, and procedural atmospheric effects.
  • The Go simulation remains authoritative; the frontend adapts live snapshots and replay frames without adding backend pose hints.
  • The server still uses server-sent events for snapshot streaming. That keeps the engine/viewer split intact across both viewers.

License

MIT — see LICENSE.

About

Authored survival-extraction simulation with deterministic expeditions, contract resolution, and campaign consequences

Topics

Resources

Contributing

Security policy

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages