A local boarding simulator comparing fixed boarding orders with a Jev controller that selects the next passenger using boarding-pass scans and seat occupancy.
Node.js 22 or newer; no runtime dependencies or install step.
cp .env.example .env
# Set OPENROUTER_API_KEY in .env, or connect it in the page.
npm startOpen http://localhost:3001. Choose luggage scenario, cabin size, passenger seed, and playback speed, then start. You can compare the five fixed strategies without an API key by disabling Jev. Runs live in memory; restarting resets the experiment. The server binds to loopback only.
There are two observable events:
- Boarding pass scanned: that passenger's assigned row is assumed busy.
- Seat occupied: that passenger no longer contributes to the row's busy count.
If two scanned passengers share a row, seating one still leaves that row busy. A scanned passenger might actually still be walking; row occupancy is an estimate, not a location sensor. Jev receives the waiting passengers' assigned seats, scanned-but-not-seated passengers, aggregated busy rows, and occupied seats. It does not see true aisle positions, luggage durations, walking speed, or activity countdowns. The cabin animation shows the simulator's true state so you can inspect how well the estimate works.
Jev chooses a group of passengers for nonconflicting rows in one OpenRouter request. The native POST /api/alpha/decisions endpoint receives one typed choice question for each currently free row: choose one waiting passenger assigned there. The model is ~typesafe/jev-latest. It cannot select two passengers for the same row or select an already busy row. Selected rows are reserved until their passengers scan through the gate.
The dispatcher releases each chosen group rear-first, preventing a newly called front-row passenger from blocking the rest of that group. This ordering rule is application logic; Jev chooses the passengers. All strategies retain the same physical gate limit of one scan every two simulated seconds and the same aisle physics. Multiple passengers can walk and load luggage concurrently. Once the queue is empty, Jev can choose another group for free rows without waiting for everyone already onboard to sit down. If all rows are busy, the simulation advances until one frees up.
Only actual validated model choices are dispatched. There is no heuristic fallback and no invented model explanation. The feed distinguishes group calls from individual boarding scans. Confidence is per row choice; any meanConfidence is an arithmetic average, not the probability that the whole group is correct. API failures pause the comparison.
- Random boarding: shuffled passenger order.
- Front to back: ascending rows, randomized within each row.
- Back to front: descending rows, randomized within each row.
- Window → middle → aisle: window seats first, then middle, then aisle, shuffled within each group.
- Rear-first zones: three contiguous row groups, rear zone first, shuffled within each zone.
- Jev adaptive: nonconflicting groups chosen from scan/seat observations, then dispatched rear-first.
Every strategy receives the same seeded manifest: the same passengers, assigned seats, baggage stow durations, and walking speeds. The playback speed changes animation pace, not the model's physics. All simulation clocks freeze together while waiting for a Jev response. The displayed completion times therefore compare boarding strategy alone, excluding inference latency; actual model latency is reported separately in the feed. They are not predictions of real-world performance with network delays.
This is an illustrative model, not a calibrated airline operations study. There is one entrance, one aisle, six seats per row, and no overtaking. A passenger occupies one aisle cell while walking, stowing, or taking their seat. The gate admits at most one passenger every two simulated seconds, and only when the entrance cell is clear. Stowing blocks the passenger's row. Taking a seat costs two seconds plus four seconds for each already-seated passenger they must cross on the same side.
Luggage scenarios use synthetic seeded distributions: light (55% without overhead bags, otherwise 4–12 seconds), mixed (20% without, otherwise 8–28 seconds), and heavy (5% without, otherwise 16–52 seconds). Walking takes 0.7–1.3 seconds per row. Families, priority boarding, bin capacity, passengers ignoring instructions, multiple doors, and mobility assistance are not modeled. Jev receives no advantage from knowing these passengers' hidden durations.
Metrics include completion time, seated count, cumulative passenger-seconds blocked in the aisle, and seated-passenger crossings. Results depend on the seed and assumptions; Jev is not guaranteed to win.
GET /api/state: runs, settings, observed status, Jev activity, configuration status.POST /api/start:{ "scenario": "mixed", "rows": 12, "seed": 42, "speed": 20, "includeJev": true }.POST /api/pause:{ "running": false };trueresumes.POST /api/settings:{ "speed": 30 }or{ "apiKey": "..." }.POST /api/reset:{}resets without starting.
POST bodies must be JSON. Keys entered in the UI stay in server memory and are never returned to the browser or placed in browser storage. .env is ignored by Git. A comparison with Jev makes one billable API request per group decision; pausing/resetting during a request discards its result, but the provider may still charge for that request.
npm testThe pet experiment remains separate: Jevagotchi.