Files
arena/environments/tera_spatial
kartiandClaude Opus 5 162d67a83c tera-spatial: an NDJSON replay bridge to the TypeScript spatial environments
Tera's four (now five) spatial environments already implement a Gym-style
contract with checksums, train/dev splits and a replay gate — in TypeScript,
unreachable from `uv run eval`. This bridges them rather than porting them: a
resident NDJSON worker over stdio drives the vendored TS closure, and no reward
arithmetic exists in Python anywhere in the package, including the denominator.

Correctness is replay, not assertion: every scenario x seed must round-trip
through Python and replay to an identical FNV-1a-64 checksum. 32/32 do, and 52
tamper attempts are rejected, 24 of them re-sealed with Tera's own checksum so
it is replay() catching divergence rather than the seal.

Lives in arena rather than tera because tera has no Python toolchain. That
overturns the earlier plan's claim that this workstream shares no files.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-21 15:55:12 -07:00
..

tera-spatial

Arena's bridge to Tera's four renderer-independent spatial environments.

The environments themselves live in the tera repository, in TypeScript, as tera.arena/v1. They are not reimplemented here and they never will be. This package vendors their import closure, runs it in a resident node worker, and transports results. The rule the whole thing is built around:

Never recompute in Python a number that came out of TypeScript.

Rewards, per-step state checksums, scenario materialisation and the four baseline returns that every reward is normalised against are all computed once, inside Tera, and only ever carried across the pipe.

What is here

Path What it is
tera_spatial/worker.mjs the resident NDJSON worker: reset, step, snapshot, restore, trace, replay, oracle, checksum
tera_spatial/bridge.py TeraWorker / TeraEpisode — transport, and nothing else
tera_spatial/vendor/tera/ the 23-file, 283 KB import closure of src/arena/index.ts
tera_spatial/hashes.py SHA-256 of all 23, generated; the worker refuses to start if the tree has drifted
tera_spatial/closure.py the import walk both the sync script and the gate use
scripts/sync_tera.py reproduces the vendoring from a tera checkout
tests/test_replay.py the replay gate

The tasksets are not here yet. This is the bridge and its correctness proof.

The gate

uv run python -m unittest discover -s environments/tera_spatial/tests -v

32 episodes — 4 environments x 4 public scenarios x 2 seeds — each driven from Python one JSON step at a time, then replayed in a fresh TypeScript environment. All 32 must reach an identical FNV-1a-64 checksum. Then seven forgeries per environment, each tried twice: raw, where the envelope checksum catches it, and re-sealed with a checksum Tera itself recomputed, where only replay() re-running the simulator can. All must be rejected.

node >= 22.18 is required — the vendored sources are raw .ts and are type-stripped, not compiled. Set TERA_NODE to point at a specific binary.

Re-vendoring

uv run python scripts/sync_tera.py --tera ~/repos/gitea/tera   # copy + regenerate hashes
uv run python scripts/sync_tera.py --check                     # CI: is the tree clean?

The closure is walked, not declared, so a new import in tera travels with it. A bare specifier is a hard failure: the wheel has no node_modules and a simulator that needs one is not a simulator that ships.