Skip to content

Drive the real simulation headlessly from a Colab notebook - #48

Closed
charlieijk wants to merge 3 commits into
mainfrom
feat/colab-notebooks
Closed

Drive the real simulation headlessly from a Colab notebook#48
charlieijk wants to merge 3 commits into
mainfrom
feat/colab-notebooks

Conversation

@charlieijk

@charlieijk charlieijk commented Aug 22, 2026

Copy link
Copy Markdown
Owner

Strata's authoritative simulation is a stdlib-only Go package, so it runs anywhere a Go toolchain does — including a headless Colab VM. This adds the harness and the notebook that exploit that.

What cannot run on Colab, stated plainly

  • The browser expedition game (web/, Vite + three.js) needs WebGL, a canvas and a display. Not present on a headless Ubuntu runtime.
  • The Wails desktop host (cmd/strata-desktop/) needs native WebKit/GTK and a windowing system. It is a separate Go module precisely so the server and simulation never drag that toolchain in.

Out of scope here; their gates stay npm --prefix web test and make desktop. The world manifest and the .glb/.ktx2/.ogg assets are authored presentation data, not simulation inputs — the manifest contributes world dimensions and nothing else to these runs.

cmd/strata-simtrace

A thin driver: construct a sim.Simulation, call Step() N times, write one CSV or JSONL record per tick straight off sim.Snapshot. It decides nothing itself.

  • Optional per-tick sha256 over the whole snapshot, mirroring the replay contract's authoritativeSnapshotHash. Agreement therefore covers every agent position, energy, credit and cargo value plus the pressure/scar overlays — not just summary metrics.
  • Rejects seed 0, which sim.Reset reads as a wall-clock seed, so an unrepeatable run cannot masquerade as evidence.
  • Rejects an unknown preset name, which the engine would otherwise answer silently as focus-agent.
  • Floats are written at full precision ('g', 17), so a value read in Python is the value the engine computed.

Tests cover the guards, the CSV/JSONL shapes, the hash stride, byte-identical repeat runs, the prefix property, and float round-tripping. Each was mutation-checked: breaking the stride check, the seed-0 guard, the preset guard, or float precision fails the corresponding test.

notebooks/01_simulation_dynamics.ipynb

Follows the fight-lab / Orbit notebook convention. Reads the Go version out of go.mod (toolchain pin asserted exactly, never hardcoded), clones via the GITHUB_TOKEN Colab secret and strips the token from the remote, then asserts no credential remains.

Determinism, measured

check result
seed 101, two processes, 2500 ticks byte-identical (sha256:e10be14e…)
all 2500 per-tick snapshot hashes equal
1000-tick run is a prefix of the 2500-tick run holds
seed 101 vs 202, ticks differing 2500 / 2500, first difference on tick 1
hash collisions 0
max |Δ alive| between the two seeds 24 agents (mean 3.87)

2500 ticks is chosen to run past the population crash below, so the proof covers the most numerically violent stretch rather than a quiet opening.

The two levels disagree, which is the interesting part: every tick's state differs between seeds, yet populations stay within a couple of dozen agents. The seed decides which agent stands where; it barely decides how many the world carries.

Dynamics: ecosystem preset, 10 seeds × 12000 ticks (600 s simulated)

Seeds 101, 202, 303, 404, 505, 606, 707, 808, 909, 1010.

  • Overshoot → crash → plateau. 1000 agents peak at 1004–1025, fall through half the starting count at tick 1672 ± 12 (range 1652–1688), then hold 241–288 agents (mean 263.0, CV 6.1%).
  • No extinction in any seed, and the plateau is a real equilibrium, not a slow slide: fitted tail slope averages +0.09 agents per 1000 ticks (largest |slope| 2.31), and inside the tail window the ten seeds run 1198 births against 1181 deaths — a 1.4% imbalance. The cumulative counters do not show this, because they are dominated by the crash; differencing them across the window is what makes it a measurement rather than an assertion.
  • The crash timing is nearly seed-independent — carrying capacity is set by the resource configuration, not by the draw.
  • Energy per survivor grows without bound: mean energy climbs from ~85 to 2202–2787. Nothing caps it, and reproduction is gated on crowding, contest and stress as well as energy, so survivors accumulate far past the reproduction threshold without converting it into offspring. Worth knowing before anyone tunes the preset.

Limits (in the notebook, not just here)

Browser and desktop surfaces out of scope; assets are presentation data; the expedition/game path is never entered, so terrain, hazard and cargo modifiers are inert in these traces; 10 seeds is a small sample; one preset at shipped parameters; determinism proved within one toolchain and architecture only; 600 s is inside MaxAge (900 s) so nothing here says what happens once the founding cohort ages out.

Housekeeping

  • notebooks/results/ git-ignored — a committed measurement quietly becomes a stale claim the next time the preset is tuned. Saved results carry commit, dirty flag, toolchain, platform and timestamp.
  • notebooks/README.md adapted from the siblings: Colab open URL, multi-account/authuser warning, save-to-branch, clear-outputs, ground rules.
  • AGENTS.md layout table and CHANGELOG updated.

Verification

make gofmt-check go-vet go-test manifest-check determinism-check eval green; make go-cover at 82.6% against the 78% floor. Every notebook code cell was executed locally end to end — the numbers above are that run's output, not estimates.

Strata's authoritative simulation is a stdlib-only Go package, so it runs
anywhere a Go toolchain does -- including a headless Colab VM, where the
browser client and the Wails desktop host cannot run at all. This adds the
harness and the notebook that exploit that.

`cmd/strata-simtrace` advances `sim.Step()` a fixed number of times and
writes one CSV or JSONL record per tick straight off `sim.Snapshot`, with an
optional per-tick sha256 over the whole snapshot (mirroring the replay
contract's authoritative hash, so agreement covers every agent position,
energy and overlay cell rather than the summary metrics alone). It decides
nothing itself. It rejects seed 0, which `sim.Reset` reads as a wall-clock
seed, and an unknown preset name, which the engine would otherwise answer
silently as `focus-agent`.

`notebooks/01_simulation_dynamics.ipynb` installs the Go toolchain that
`go.mod` pins -- read from the file, asserted, never hardcoded -- clones via
a Colab secret and strips the token from the remote, then measures two
things on 10 seeds of the `ecosystem` preset:

- Determinism: a 2500-tick trace is byte-identical across two processes at
  seed 101 (sha256 e10be14e...), all 2500 per-tick snapshot hashes match,
  and a 1000-tick run is a prefix of the 2500-tick one. Against seed 202
  every one of the 2500 ticks differs, first difference on tick 1, no hash
  collisions -- while populations stay within 24 agents. The seed decides
  which agent stands where, not how many the world carries.
- Dynamics: 1000 agents overshoot to 1004-1025, crash through half the
  starting count at tick 1672 +/- 12, then hold a plateau of 241-288 agents
  (CV 6.1%) with a fitted tail slope under 0.1 agents per 1000 ticks and no
  extinction in any seed. Mean energy per survivor climbs from ~85 to
  2202-2787: nothing caps it, and reproduction is gated on crowding and
  stress as well as energy.

Results carry a provenance stamp and land in a git-ignored
`notebooks/results/`; a committed measurement quietly becomes a stale claim
the next time the preset is tuned.
@coderabbitai

coderabbitai Bot commented Aug 22, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@charlieijk, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 6 minutes

Limit details: You’ve used all 10 included reviews currently available.

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

Wait for the limit to reset, then comment @coderabbitai review or push new commits to the PR.

An organization admin can change what happens after included review limits in Billing.

How do review limits work?

CodeRabbit enforces per-developer PR review limits within each organization.

For paid Pro and Pro+ reviews, CodeRabbit uses a developer's included PR review attempts over the past 7 days to set the current hourly allowance. At typical activity levels, the full plan allowance applies. Higher sustained activity can lower the allowance until earlier attempts leave the 7-day window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: e4e0e2f2-dc7c-41b0-9dab-70ec5804feb9

📥 Commits

Reviewing files that changed from the base of the PR and between 2f53822 and 28852ce.

📒 Files selected for processing (7)
  • .gitignore
  • AGENTS.md
  • CHANGELOG.md
  • cmd/strata-simtrace/main.go
  • cmd/strata-simtrace/main_test.go
  • notebooks/01_simulation_dynamics.ipynb
  • notebooks/README.md

Comment @coderabbitai help to get the list of available commands.

The dynamics section claimed births roughly match deaths on the plateau, but
the only counters in the table were cumulative and therefore dominated by the
crash that precedes it -- 99-418 births against 858-1130 deaths says nothing
about whether the tail is held or slowly draining.

Difference the counters across the tail window instead. Across the ten seeds
the plateau runs 1198 births against 1181 deaths, a 1.4% imbalance, which is
what makes it an equilibrium rather than a slow decay.

Also corrects the prose on the fitted tail slope: it averages +0.09 agents per
1000 ticks, but one seed reaches 2.31, so "a fraction of an agent" was true of
the mean and not of the sample.
CI's golangci-lint run flagged the unchecked fmt.Fprintln (errcheck). The
metadata line carries the run's identity -- seed, preset, toolchain -- so a
silent failure to write it would leave a trace with no provenance attached,
which is exactly the failure the notebook's stamp exists to prevent. Return
the error.
@charlieijk charlieijk closed this Aug 23, 2026
@charlieijk
charlieijk deleted the feat/colab-notebooks branch August 26, 2026 01:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant