-
Notifications
You must be signed in to change notification settings - Fork 0
Architecture
The whole design follows from one decision: the expensive thinking happens offline, and the thing that runs during play does arithmetic.
| Program | When it runs | Language | What it does |
|---|---|---|---|
| The compiler | Offline, once per scenario | Python | Turns public grid data into a scenario pack. All the modelling lives here. |
| The optimizer | Offline, once per scenario | Python, MILP via HiGHS | Solves the pack for its exact optimal cost, which becomes the gold line. |
| The engine | Every turn of every episode | TypeScript | Applies fixed rules to three numbers. No modelling, no solving, no randomness. |
The compiler may fit curves, derive price slopes, take percentiles of an annual series, and build a scarcity adder. The engine may not. By the time a pack reaches a player it is a list of numbers, and resolving a turn is addition, multiplication, and a handful of caps.
That split is why the same engine runs in a browser, on a worker at the edge, and inside a test on your laptop, and produces identical results in all three.
public grid data
│
▼
┌───────────┐ pack.json ┌───────────┐
│ compiler │ ─────────────► │ the pack │ (data: prices, campus, events, scoring)
└───────────┘ └─────┬─────┘
│
┌────────────────┴────────────────┐
▼ ▼
┌────────────┐ ┌────────────┐
│ optimizer │ optimal cost │ engine │ turn resolutions
│ (offline) │ ─────────────────► │ (runtime) │
└────────────┘ the gold line └────────────┘
│
▼
episode log
A scenario is one JSON file, valid against a published schema. It carries the day's clock, the price series, the campus and its assets, the events, the scoring declaration, and its own provenance. Nothing in it executes.
The library grows without touching the platform, a contributor needs a text editor rather than a development environment, and a thing that is only data can be checked completely before it is trusted.
See The Scenario Pack.
Between the player and the platform sits a documented interface: what may be observed, what may be done, how a turn resolves, what an episode looks like.
The user interface is a client of that contract with no privileges. It cannot see anything an agent could not see, and it cannot do anything an agent could not do. Phase two is a swap, not a rewrite.
At the venue, an episode's state is not kept as a mutable row that the client updates. Actions are appended to a log, and the current state is reconstructed by folding those actions through the engine from the beginning.
Two consequences matter. A client cannot assert a state it did not earn, because its assertions are not what the server reads. And the episode log is not a report about what happened; it is the thing that happened. Replay is not a feature bolted on afterward. It is the primary mechanism.
Same pack, same action sequence, same resolutions, same score. Always.
The runtime contains no randomness at all. A forecast that misleads you is data compiled with a documented error model, not a die roll.
The strongest form of this is enforced by a test that ships with the code:
score(replay(episode_log)) === stored_score
Any change to the engine has to keep that true, and has to keep packs that use no optional features resolving bit-identically to the previous version. That constraint is why engine changes are careful and why this repository does not let you edit the engine locally.
Three independent version numbers, all pinned into an episode when it is created:
-
pack_versionfor the scenario file -
engine_versionfor the resolution rules -
score_versionfor the scoring function
An episode created today is immune to a pack edited tomorrow. A breaking change
to the observation or action shape bumps the schema from v0 to v1 and gets a
new section in the contract, never a silent edit.
Field names crossing the wire are snake_case, and the JSON Schemas are
normative. Where this documentation and a schema disagree, the schema is right.
The venue is a single Cloudflare Worker serving a React application, with D1 for episodes and R2 for packs. None of that is needed to build a scenario, so none of it is here.
Exedra ships the compiler, the optimizer, the engine, the scoring function, the schema, the acceptance gate, two packs, and a local runner that keeps an episode in memory. Same rules, no infrastructure.
See What Exedra Withholds for the full boundary, and Hydration for how the shared parts get here.
Exedra
Why
How it is built
What you write
Boundaries
For reviewers