Architecture

Terrarium is a folder of plain JavaScript. There is no contract to deploy, no database, no server that holds anything. One process (the runner) owns one wallet; a static site reads what the process writes.

                +---------------------- the runner (one per organism) ----------------------+
 Pons escrow -> |  claim -> split -> tier -> hooks -> mind (optional) -> execute -> journal   | -> data/<id>/*.json
 Pons curve  <- |                                                    |                         |
 v4 pool     <- |                      buy and burn  <---------------+                         |
                +--------------------------------------------------------------------------+
                                                                                             |
 browser  <-  site/ (static)  <-  data/<id>/status.json, feed.json, series.json  <-----------+
                                    (localhost: the folder; elsewhere: the repository's `data` branch, pushed by the runner)
    |
    +-> Verify: JSON-RPC straight to the chain (no Terrarium server in between)

The engine (engine/)

FileRole
config.mjschain facts, the tier table, the limits, the defaults
battery.mjspure functions: level, tier, charge, drain, claim split. Also run by the browser
hooks.mjspure rules: the drip and the dip defender, the caps, the settings validation. Also run by the browser
mind.mjsthe optional model review; can only trim an order
ledger.mjsthe book and the journal, written atomically
organism.mjsthe beat: claim, reconcile, tier, hooks, mind, execute. Paper and live executors
pons.mjsclaim from the escrow; buy and burn on the curve or in the pool
v4.mjsthe Uniswap v4 pool of a graduated coin: key, price, liquidity, the swap
market.mjswhere the coin trades and at what price, and ETH in dollars
publish.mjsstatus.json, feed.json, series.json, organisms.json
launch.mjs, rescue.mjs, runner.mjslaunching a coin, the exit, the process

battery.mjs, hooks.mjs and config.mjs have no network and no key. The site serves only these three to the browser, so the lab runs exactly the code the organism runs.

The beat, as code

tick(market, now):
  claim()                          -> CLAIM, battery, treasury
  reconcile(treasury, wallet)      -> RISK if trimmed
  tier = tierFor(level(battery))   -> TIER / WAKE / SLEEP
  if dormant or not time yet: return
  if market.canBurn is false: log, return
  orders = hooks.evaluate(...)     -> HOOK lines for hooks that did not fire
  review = think(orders)           -> THINK, battery drain  (scale <= 1)
  for each order: burn()           -> BURN with tx, saved at once
  SAY

What each side trusts

SideTrustsDoes not trust
The runnerthe chain's answers, its own capsthe model's amounts, a thrown error as proof that nothing was sent
The modelnothing: it sees only the orders and a small contextit never touches a key or an address
The sitedata/<id>/*.json for displayit never needs them for the Verify page, which reads the chain
Verifythe chainthe organism's own numbers, which it compares with the chain

Failure model

The chaos soak test (tests/soak.mjs) exercises all four.