Maschinenraum Handbook

Build the neon machine room,
then keep it running.

Everything you can author or change in Maschinenraum, from a room file to a drink's hangover, with the commands that prove you did not break it. The live panels write the files for you.

Start here

Build, run, measure

A Rust workspace: protocol, server, client (wasm), race, props, loadgen. The server serves the built client bundle.

everyday

Build and play

cd client && trunk build --release
cargo run -p server --release
# open http://localhost:8080/?room=lobby&nick=you

The first build compiles DuckDB's C++ and takes minutes. On Windows a running server.exe locks the binary: stop it before rebuilding or testing.

quality gates

Check everything

cargo xtask all             # lint test rooms client server golden smoke
cargo xtask linux all       # the same in a Debian container
cargo xtask bench           # benches + loadgen, history in .bench/
scripts/check.sh --quick    # lint, tests without the release suites, rooms
scripts/dev.sh              # stop the server, build both, run on 0.0.0.0:8080

cargo xtask runs each step by hand, the same on Linux and Windows; there is no CI server. golden checks that the wasm builds compute the hashes the native tests pin, and smoke drives a headless browser through joining, sound and a server restart. check.sh runs clippy with warnings as errors for the native crates and the wasm client (app.rs and gfx.rs only build for wasm), every test suite, and every room in rooms/ through roomcheck.

screenshots

Every look in every room

node scripts/looks.mjs --rooms lobby,bazaar --zoom 1
node .claude/skills/look-from-image/tools/snap.mjs \
     --room market --look Off-World --out shot.png

looks.mjs drives one headless Chrome through each room and each look and writes a labelled contact sheet per room to shots/looks/: the regression check for a look that only works where it was tuned. The server must be running.

performance

Benches and load

cargo bench -p server --bench room
cargo bench -p client --bench tessellate -- light_
cargo bench -p client --bench audio
node client/benches/wasm_audio.mjs client/dist
cargo run -p loadgen --release -- --rooms 10 --bots 50 --secs 20
cargo test -p client --release --lib machine -- --nocapture   # the disco machine: x real time
cargo test -p client --release --lib machine_wav -- --ignored  # eight bars to target/disco.wav
cargo test -p client --release --lib sid_wav -- --ignored      # the SID's march to target/sid.wav
environment

Server configuration

VariableDefaultWhat it sets
MR_BIND0.0.0.0:8080HTTP and WebSocket listen address
MR_RTC_BINDthe HTTP portUDP for the WebRTC data channels; off keeps everything on the WebSocket
MR_RTC_PUBLICunsetIP advertised to browsers for UDP (behind NAT, in a container)
MR_DBmaschinenraum.duckdbThe event log file
MR_ARCHIVEarchiveWhere history older than a day goes, as Parquet
MR_ROOMSroomsRoom files (<room>.ron, world.ron)
MR_STATICclient/distThe built client bundle
MR_DOCSdocs/htmlThese pages, served at /docs/
MR_ACCESS_KEYunset (open)The key needed to join; it buys an HttpOnly session cookie at POST /login
MR_BUILDER_KEYunset (everyone in may edit)The key needed to edit rooms, checked per connection
MR_SESSION_SECRET<MR_DB>.session-secretSigns the session cookies
MR_TRUSTED_PROXYunsetA proxy whose forwarded client address is believed
MR_TLS_CERT, MR_TLS_KEYunsetPEM files: the server speaks HTTPS itself (server/src/tls.rs)
MR_ICEnoneComma-separated STUN/TURN URLs voice links may use, served at /ice
RUST_LOGunsetLog filter, e.g. info,str0m=warn
rooms/<name>.ron

Rooms are files plus history

A room is its base file with every logged edit replayed on top. Change the file and restart: players' edits still apply, because they point at object ids. Hover a line to read about it.

hover a line

Each field of a room file, explained. Fields marked optional have a default.

rotation

Which way rot points

0 1 2 3
  • 0 = +x, 1 = +y
  • 2 = −x, 3 = −y
  • Stairs climb along it; seats and sofas put their back on that side; screens face it.
floors

Floors, voids and stairs

  • floors lists levels bottom up: a height and the areas that have floor, minus voids.
  • Stairs climb two tiles along rot and land one tile further, a floor up. The floor above needs a void over the ramp and floor at the landing.
  • raised lifts or sinks parts of a floor; steps up to MAX_STEP (0.5) are walked (FloorPlan::step_ok in the server's A*), taller edges need steps between.
  • protocol::FloorPlan is shared: server walking and client drawing read the same rasterised floors and heights.
ids

The id rule

Base-file ids must stay below 1 << 20 (1,048,576). Ids made in-game start there, so edits logged against them keep pointing at the right object when you rewrite the file. Never reuse a base id for something different.

world.ron

Connecting rooms

(
  rooms: [
    (name: "lobby",  origin: (0, 0)),
    (name: "street", origin: (24, 4)),
    (name: "geode",  origin: (40, 16)),
    (name: "central", origin: (64, 0)),
  ],
)

Rooms whose footprints touch on the world grid are neighbours: the room next door is blacked out but for its walls, doors, arches, windows and railings along the seam, and you walk through wherever both sides have floor. elevation: 120.0 puts a room up the tower, which keeps it from neighbouring the plaza. Rooms not in world.ron are reached by portals: to: "lobby", arrive: Some((11, 11, 0)).

styles

style: and what comes with it

StyleFeelAmbience
neonThe default clubmains hum, traffic, lamp buzz, sign crackle
corporateOfficesair conditioning, screen buzz
slumStreet, raintransformer, heavy traffic, crackle
templeCalm halldrone, candle chimes
industrialMachineryedgy hum, rumble, sparks
crystalThe geodewind, drone a fifth up, crystal chimes
prairieOak, Roman brick, green carpetsoft air, a faint drone, rare chimes
marketNight market: AC units, cables, hanging neoncrowd air, transformers, sign buzz
labMachine room: ducts, rack lights, checkersrack hum, low drone, screen buzz

outdoor: true adds the city skyline, sky and a short, damp reverb.

people

Residents and the bartender

  • residents: 22 fills the room with people who sit, stand about, wave and dance while someone is there.
  • The first presents on a Stage if there is one.
  • The second works the room's longest Bar if there is one, from the open floor behind it that is not beside a stool. Keep that floor connected: a board placed behind the bar splits it into pockets.
live billboards

Keyword billboards

#latencyRound trips between the players here, measured client to client
#serverThe server's CPU and memory, two minutes, smoothed
#clientsEvery player's main-thread load and memory
#departuresThe gates here and how many people are at the other end now
#skateThe room's best half pipe runs, one per nick, with the airs each landed
#transferThe room's transfer winners, most wins first, with the droid each wears
#scoreThe football score between the room's two goals
#cyclesLight-cycle winners, most matches won first
#breakoutThe best breakout games
#dizzyThe best Spindizzy runs: most gems, then quickest
#bomberBomberman champions
#commandoCommando high scores, one per nick
#koboldKobold's histograms for the machine floor's job: cycles, size, activity
#duelKobold's duel ladder for the machine floor's arena: Elo rating, wins, losses
#relayKobold's best relay shifts on the machine floor's line, one per crew
#showKobold's light-show set pieces: each nick's smallest solution per piece

A Billboard whose text is the keyword draws the diagram instead. Keep them on floor 0.

the city

Rooms that ship

  • On the world grid: lobby, street, atrium (Chinatown), geode, central (a gate to every room and the departures board), tower (the skyscraper's plaza: a glass lift corkscrewing up to the skybar at 120 and the roof at 300, a paternoster in its foot), skatepark (a half pipe: step on the pad, K at the top of each air), deck (Deck 1: Paradroid's transfer game, X claims, ↑ ↓ Space fire), stadium (football between two goals, #score), grid (light cycles for two, WASD or the arrows), court (breakout, one player or two taking turns, A/D and Space), spindizzy (a marble on a floating level, gems against the clock) and bomber (Bomberman for one to four, Space drops, E sets off remotes).
  • Behind portals: llm (the babbling model), lexicon (an 80-tile hall where every word said grows a tree, from the model room), prairie (a dining room), market (rainy night market), lab (machine room), bazaar (a market on three levels), disco (three stations, a DJ mixer, a bar), basement (Commando on a sunken sand table, from the lobby and the street) and boiler (Kobold, a coding game after EXAPUNKS with heists, rated duels, relay shifts and light shows on a rig of 32 lamps, down the basement's stairwell).
  • Arcade cabinets in several rooms play a vector Scramble with a high-score table each.
  • Specs for the image-made ones are in .claude/skills/room-from-image/examples/.
objects

Object kinds

KindKeyWhat it isBlocks
live instrument

Room sketcher

Pick a kind, click or drag on the grid to place, right-click or use Erase to remove, click a placed object again with the same kind to rotate it. The file updates as you draw.

A single-floor room

writes rooms/<name>.ron
in the browser

Editing a live room

Every edit is logged to DuckDB and replays on load. With MR_BUILDER_KEY set only people who logged in with it may edit; unset, everyone who got in may.

tools

Place, turn, erase

  • E toggles edit mode (placing walls).
  • In edit mode only: 1…9 pick the first nine kinds, 0 portal; Shift+digit the next six; Alt+digit the ten after. The kinds past those are placed from the bar at the top.
  • R rotates what you place, X erases, right-click removes.
  • Ctrl+Z takes back your own last edits, one at a time (32 deep).
  • Esc goes back to walking. ? shows every key.
chat commands

Text, portals, orders

/screen TEXTSets the nearest screen, sign or billboard
/board Title | line | lineSets the nearest board
/portal room [x y level]Points the nearest portal somewhere
/order 7 · /order vodkaOrders from the bar by number or name
/wave /dance /point /sitEmotes
/group name · /g textJoins a group (/group alone leaves it); says something to it, in whatever room each member is
/letter nick textLeaves a letter where you stand; only that nick can open it, by clicking it. It survives restarts
/plane nick textThrows a note to someone here; only they read it
/follow nick · /unfollowWalks after someone, across seams too; clicking a person offers it as well
/map · NThe world map with how many are in each room
/helpThe commands, in the chat
people

Talking and finding each other

  • Click a person for a menu: walk to, follow, wave, throw a note, leave a letter. The cursor's hint says what a click does.
  • Joining a room shows its last eight lines as fading bubbles where they were said. A room with quiet: true carries chat only five tiles and keeps no echoes.
  • Your nick in someone's line is marked, pings, and counts in the tab title while the tab is hidden.
  • The mic button turns on proximity voice: people near you hear you, quieter with distance and duller through walls. It needs https or localhost.
  • Click a screen, sign, billboard or board to read it up close; Esc goes back.
accounts

Your nick, kept

  • Give a password when you join and the nick is yours: from then on it needs that password, and nobody else can join as it.
  • Your outfit, group, display settings and where you stood go with it, to any browser, and survive a crash or restart.
  • ⚙ → Account changes the password or logs out. Without a password you join as a guest under any free nick.
  • The whole story, keys and cookies included: SECURITY.md.
⚙ access

Text size, contrast, motion

  • Text size scales names, chat and panels. High contrast puts them on solid backgrounds.
  • Reduce motion (on if your system asks for it) drops trails, glitches, the teleport shimmer and interface animation.
  • On a phone, buttons for what you are doing appear above the chat: trick and off on the half pipe, rows, fire, swap and give up in a transfer, close, unfollow.
durability

What survives a restart

  • Durable, replayed: placing, removing, rotating, screen text, portal targets, book box links.
  • Durable, read at load: letters until opened, the boards' results, the lexicon's words, Kobold programs and champions, Kobold duels (replayed for the ratings).
  • Kept across a crash (room_state): open doors, the DJ deck and its mixer, matches under way (settled on load), notices owed; accounts with their outfit, settings and last tile.
  • History only: chat, joins, leaves, drinks served.
  • Gone on restart: who sits where, guests' positions, props, a Commando run, the Kobold crew, the run on show, a live duel's clock, a relay shift under way and the show on the rig with its queue.
protocol::MENU

The bar's menu

One array in protocol/src/lib.rs. Each item has a name, a course (what it is served in), a glow colour and an Effect. Orders and props carry the index; the event log stores the name, so you may reorder freely.

add an item

One line

item("Heap Dump With Fries", Course::Food,
     [1.0, 0.6, 0.1],
     Effect { sway: -0.3, ..Effect::NONE }),

Bump the array length MENU: [MenuItem; N]. The DOM menu, /order, the prop's shape and colour all follow.

courses

What it comes in

Coffeea cup with a handleno shake
Bara glass with a strawshaken 2.5 s
Fooda plate, stacked ringsno shake
Softa glassno shake
effects

Effect fields

  • sway: drunk walk, swimming head, haze.
  • jitter: trembling, twitchy view, grain.
  • haze: blurred, dark edges.
  • secs: how long one dose takes to wear off.
  • Doses are 0..1 and add up per sip; negative ones sober. Helpers: booze(s), caffeine(s).
flow

An order, start to finish

  • Click a bar, sit at a stool beside it, or type /order.
  • Greeting → walk to a station → shake (bar drinks) → serve → slide along the bar top.
  • The customer picks it up within 2.5 tiles. Sips at 4 s, then every 15–25 s; three sips and it is gone.
  • Lines live in server/src/room/bartender.rs: GREETINGS, SERVES, TALK.
live instrument

Effect tuner

Pick an item, take sips, and watch the figure stagger with the same formulas the client uses (client/src/buzz.rs). Tune the dose and copy the line back into MENU.

SWAY
JITTER
HAZE
client/sounds/*.snd

Sound is text

One sound per file, synthesised (FM, subtractive or drum voice) into a delay and a reverb; nothing is sampled. Two sources are code instead: the disco's machine and the SID. A field with two values goes from the softest to the hardest play: v is the event's strength over ref. Times are milliseconds; pitches and times interpolate geometrically.

keys

Every key a .snd file knows

KeyValuesMeaning
new event

Adding a sound the game can play

  • In client/src/sfx/mod.rs: a new EV_* constant before EV_COUNT (bump it), its name in EV_NAME, the file in SFX_SRC (bump its length).
  • The file's event line uses that name.
  • Play it from app.rs with self.play(sfx::EV_…, key, v, spot, delay_ms); held sounds use Sound::hold/steer/release.
  • Run cargo test -p client --lib sfx, then re-pin VOICE_GOLDEN.
client/src/sfx/machine.rs

The disco's machine

  • The disco's drums, its acid bass and the DJ's mixer are synthesised in the engine, not played from files: a kick swept in pitch with a click, a two-mode snare, 808 hats (the open one choked), a 303 bass through a saturating ladder at twice the rate.
  • The mixer: a strip per channel (gain, three-band kill EQ, fader, send), the crossfader, the master, a limiter at 0.9; the send feeds the 80s chain: saturation, chorus, delay, reverb, each switched without a click.
  • Notes are EV_MACHINE events; knobs are tweaks of it (never dropped), set from Deck by set_machine.
  • cargo test -p client --release --lib machine checks voices, choke, clicks, aliasing, switching latency and cost.
client/src/sfx/sid.rs

A SID

  • After the C64's chip: three voices (pulse, saw, triangle with ring mod, noise), its ADSR and a resonant filter, and a 50 Hz player for the march.
  • Effects take voice 3, the most urgent winning. EV_SID commands it: key a CMD_* or FX_*, v a level.
  • Commando sounds through it.
bus.snd

The room's buses

delay_time      240
delay_feedback  0.32
delay_damp      2400
delay_spread    1.4
reverb_predelay 14
reverb_rt60     2300
reverb_damp     3600
reverb_width    1.0
master_gain     0.9

Each room overrides the reverb time from its size and style (Sabine), in app/ambience.rs.

ambience

Beds and details

  • beds(style): up to two held sounds on the ambience bus with their level, pitch and edge.
  • details(style): one-shots at lamps, signs or glowing things with a mean interval.
  • The ambience ducks about 8 dB under the room's own sounds; players set its level under Ambience.
live instrument

Patch sketcher

Shape a sound and copy the file. The preview is a rough Web Audio stand-in for the engine (no FM index sweep, no drive), good enough to judge length, pitch and brightness.

client/src/scene.wgsl · post.wgsl

Looks, light and the frame

Six shader looks share one scene. Every look in the menu (52 of them) is a profile built on one of them, and choosing it sets every display setting, as far as the device's Quality allows. U steps to the next, Shift+U back.

⚙ panel

Look, Quality, Fine-tune

  • Look: a complete preset. Nothing lingers from the look before; what a profile leaves out comes from Settings::DEFAULT.
  • Quality: Auto (Low on WebGL2 and phones, High on WebGPU), Low (no traced light, reflections, shafts or MSAA), Medium (traced light at most On, no shafts), High.
  • Fine-tune: the raw switches; the vertex (10), edge (11, Fine among them) and face (12, Stone and Metal among them) themes; line weights for detail, edges and silhouettes. A change marks the look tuned until you choose a look again. Saved as settings.v5.
client/looks/*.look

Profiles

look        Off-World
base        diorama
shafts      on        # glow mirror shadows trails too
lighting    ultra     # capped by Quality
tone_dark   #0a2a35
tone_light  #ff9a3c
ramp        #000000 #06151c #1d4a55 #d77a2e #ffe0a8
streak      0.8       # anamorphic flares
grain       0.35      # film: grain vignette letterbox
letterbox   2.39
text        2         # text mode: the frame as characters

Shipped: the six shader looks; Off-World, Nouveau, Blockwork, Sunset, Noir, Character Soup; ten that set every property (Blueprint to Chalkboard); thirty traditional ones, from Daylight and Kodachrome through Architect's Model and Museum to Wireframe, Hidden Line, Vector Display and Cinema. From the twelfth on, every profile sets every key (a test checks). Add a file to look::LOOK_SRC; the look-from-image skill drafts one from a picture.

looks

The six shader looks

DioramaDefault. Low-poly blocks in pools of coloured light, neon spilling onto surfaces.
LoungeSolid colour, soft edges.
AtmosphereGlass walls, haze, shading modes Flat, Gouraud, Phong, Glass.
Classic vectorPure beams, the original.
GraphicIllustrated colour blocks on black.
ComicInk and hatching on a navy night.
where to tweak

Shader entry points

  • diorama(), lounge(), atmo(): ranges of camera.look.x; graphic() is look ≥ 4, comic() 5.
  • Illustrated looks return early in fs_fill and take derivatives before any branch (Chrome rejects fwidth in non-uniform flow).
  • pose_of() holds the walk cycle and gestures; add a gesture there, in scene::anim and in gesture_len.
  • post.wgsl: bloom, tone mapping, vignette, the grade (graded), text mode (as_text), the anamorphic streak, and photo mode's and the look's film.
light

The light volume

  • Lamps, glowing objects and neon lines feed a 3D grid (light.rs), two cells per tile.
  • Per kind: height, reach and colour in emits(); neon spill in NEON_SPILL.
  • It covers the room you are in (light::Patch): rooms next door are blacked out, neither drawn nor lit (1.4 ms to bake a 24×24 room).
  • An edit rebakes only within 9 tiles of itself, bit-identical to a full bake.
  • Lighting tiers Off, On, Ultra add traced bounce light (WebGPU only).
camera

Views

  • Orbit: drag to turn, wheel to zoom, WASD or Shift-drag to pan, ←→ quarter turns.
  • V first person (automatic when you sit).
  • O photo mode: free flight, the six shader looks on 1–6, exposure, grain, letterbox, tilt-shift by clicking. Moving draws a lighter picture; held still it draws everything at its best, past Quality. All rooms of the world shows every room on the map at once. C waits for the light to settle and captures at 2×.
  • B boss mode: flat, top-down, silent, every text coded.
  • PgUp/PgDn show the floors above and below: in the bazaar, the decks and the rooftop.
hand-mirrored layouts

Change one side, change the other

RustWGSLSize or rule
gfx::CameraUniformCamera in scene.wgsl304 bytes: vol, beat, then world (x elevation, yzw line weights)
gfx::VERTEX_THEMES, EDGE_THEMES, FACE_THEMESV_*, E_*, F_*same order; look::THEME_MAX caps each
look::Grade::lanes()Grade in post.wgslten vec4; w lanes: text, text_bg, streak
gfx::GiParamsParams in gi.wgslfour vec4, the volume's corner last
scene::Linebeam vertex layout64 bytes
scene::Instanceinstance attributes80 bytes
gfx::Photo::lanes()Photo in post.wgsltwo vec4
scene::anim, scene::rigG_*, bones, HOLDINGby value
LIGHT_SCALE, MAX_LIGHTSsame names32 lights

Get one wrong and the geometry arrives scrambled with no error. cargo test -p client --test shaders validates both shaders and their WebGL2 translation, not the layouts.

props/ · server/src/room/

Things that move, people who live here

physics

Deterministic props

  • props::step runs on the server and every client from the same body.
  • Only + − × ÷, sqrt, min, max, abs: no sin, powf, mul_add or SIMD.
  • After any change, re-pin GOLDEN in props/tests/golden.rs and check the wasm build prints the same hash.
protocol/src/*.rs

Games are shared rules

  • Each game's rules live in protocol and run on the server and every client: skate, transfer, football, cycles, court, dizzy, bomber, commando, kobold, lift, paternoster. Change one and rebuild both sides together.
  • commando::Game is lockstep: the runner's browser, every onlooker's and the room step it from the same input bytes. Like props it may use only + − × ÷, sqrt, min, max, abs.
  • kobold is integer only, so a run is its job, its case and the programs. The server scores a submission's 100 cases off the room task; golden_scores pins the reference solutions. A duel is two games with the homes swapped: side 1 reads links through the arena's mirror, and the sides take turns going first within a cycle. Ratings are Elo per nick and arena, replayed from kobold_duel rows at load. A relay shift is its line, its seed and its swaps: clients play it behind the room and replay from the start when a swap comes late, and the room replays it whole to score it. A light show is its program, its seed and its beat period, the period taken from the disco's tempo on Directory::tempo. The room never steps a show, only browsers do; it checks a set piece with kobold::piece on its own task. Jobs and arenas are append-only.
  • The lexicon's fold is the server's: it refits the room's principal axes and sends them; clients only apply them.
balls

Tuning

Rolling resistanceROLL_DECEL 2.5 tiles/s²
Kick reachKICK_REACH 2 tiles
Kick cooldown24 ticks (0.4 s)
Balls per roomMAX_BALLS 4, one per Ball spot
holding

Grab, carry, throw

  • Click a glass to pick it up; Shift+click a ball.
  • Holding: Shift+click a point to throw, G to put down.
  • Drawn at the holder's right hand; the forearm is raised by anim::HOLDING in the idle lane.
residents

Behaviour knobs

  • NICKS and timing in room/residents.rs; they act every 0.5 s while a real player is in the room.
  • They use the same walk, sit and emote paths as players, so they cannot do anything a player cannot.
server/src/eventlog.rs

The event log is the contract

One DuckDB table, events, whose payload is JSON of eventlog::Event. Variants and fields are append-only: old rows must keep replaying.

kinds

What is logged

place remove rotate screen portal drop takereplayed onto the layout (from the latest snapshot)
chat join leave servedhistory, archived to Parquet after 24 h
skate transfer cycles court arcade dizzy bomber commando koboldresults, read at load for the boards; never archived
kobold_texteach nick's program for a job or arena; the latest is read at load. A line's station programs are rows under job 160 + 4 × line + station, the latest whoever wrote it
kobold_duela duel in an arena, both nicks and scores, live or a challenge; replayed in order at load for the ratings
kobold_championeach nick's champion for an arena; the latest counts
kobold_shifta relay shift on a line, its score and crew; read at load for the board, never archived
kobold_piecea light-show set piece a nick solved, and its size; read at load for the board, never archived
letter letter_openedletters lie there until opened
lexiconlines that grew the tree, replayed into it at load

ts_us is UTC microseconds. A failed write ends the process rather than diverge from disk.

query

Read it with DuckDB

SELECT payload::json->>'item' AS item, count(*)
FROM events WHERE kind = 'served'
GROUP BY 1 ORDER BY 2 DESC;

Open a copy, or the file with the server stopped.

feeds

Load and census

  • server/src/load.rs: a thread samples the process's CPU and memory each second into a watch, two minutes kept.
  • Directory::census: each room writes its headcount on join and leave.
  • A room passes either on only when one of its billboards asks (#server, #departures).
hot paths

Must not allocate

  • The move and chat broadcast path; tests/alloc.rs checks it, with and without a neighbour room observing.
  • A whole race; race/tests/alloc.rs.
  • Room-to-room messages only try_send; a blocking send between two full inboxes deadlocks.
scripts/ · deploy/

Ship it

One process, one DuckDB file, UDP for WebRTC. Never more than one replica.

scripts

Four commands

IMAGE=ghcr.io/you/maschinenraum scripts/image.sh --push
scripts/deploy.sh kustomize prod
scripts/deploy.sh helm --set rtc.publicIP=203.0.113.10
scripts/compose.sh up     # Caddy + TLS on this machine
scripts/lint.sh
udp

WebRTC needs a reachable port

  • Kubernetes: a hostPort on a pinned node (maschinenraum/rtc=true) with MR_RTC_PUBLIC set to its public IP.
  • Helm also offers rtc.mode=loadBalancer and off.
  • Without UDP everything still works over the WebSocket, with more race latency.
operations

Keep it alive

  • SIGTERM flushes the event log: give it 30 s.
  • Back up the DuckDB file with the server stopped.
  • Apply manifests server-side; client-side apply drops one of the two port-8080 entries.
live instrument

I changed…

Tick what you touched. You get the checks to run, in order, the invariants at stake and the docs to keep in step. Your ticks stay in this browser.

Run

Watch out

Update

in the game

Key map

Hover or tap a lit key.

Lit keys do something. Enter chats, / commands start with a slash.