Settings
A settings screen with nothing behind it. Move a fader, press SAVE, close the tab — and the value is still there when you come back, kept in this origin's own private filesystem by the same code that writes a config file on a desktop.
controls
The panel is the demo, so it is never dismissed.
Click the canvas first — a canvas has to hold focus to receive keys, and letting go of it pauses the loop.
- ↑↓ pick a row
- ←→ move the fader or RENDER SCALE under it, or step FRAME CAP, ANISOTROPY or an effect switch — they stop at the ends
- ENTER step one of those round the end, or press SAVE or RESET
- Pause and resume Esc
- Fullscreen F11
- Debug overlay F3
- Debug console `
- Pause, fullscreen and the debug overlay keys — unless a control for them is listed above
- Debug console, and its on-screen keyboard the CONSOLE button, once you have touched the canvas
The F3 panel has a settings file section: the file as it stands, one row per key, in the file's own tables — so a key none of these rows owns is still there to read.
SAVE says where it went. RESET puts every bus back to 100%, takes the frame cap off, puts the anisotropy back to the engine's own and the render scale back to the whole frame, switches every effect on, and leaves the file alone until you save it — a reset you did not mean to keep is one reload away from being undone.
why a screen with no game under it
Because a setting that does not survive a restart is not a setting.
The engine has had a layered settings stack for a long time: an on-disk TOML file under the player's own config directory, layers that override each other in a defined order, and readers for every key the renderer and the mixer understand. What it had never had was an application writing one. The only writer was a command line, so the code that keeps a player's choices had been exercised by everything except a player.
This sample is that player. The six faders are the audio buses — master, music, effects, interface, voice and ambience — and each is a real key in the file, written the moment you move it and read back the next time the page opens.
They are not linear. A fader is a square law, so the middle of the groove is a quarter of the amplitude rather than half of it, which is roughly how a halving sounds. The percentage beside each row is the gain that reaches the file, not the position of the handle.
And you can hear them. Three of the six buses carry something: a looping
tone on music, a tick once a second on effects, and a click on the interface
bus every time a fader passes a detent. They are told apart by behaviour
rather than by timbre, so the bus you are pulling down is the one you can
identify by ear — and master takes all three with it. Voice and ambience
carry nothing and their rows say (silent), because a control
that does nothing is worse than one that admits it.
QUALITY, FRAME CAP, ANISOTROPY, ANTIALIASING, RENDER SCALE and the six
effect switches are the [engine.video] keys on the screen and
the only rows here that do not apply as you change them: the loop takes its
frame ceiling once, when it is built, and a renderer takes its anisotropy,
its antialiasing, its scale and its effect set when it opens over a scene —
and this screen draws none. QUALITY sits above the rest and writes a whole
tier at once, so the rows below it move together and then say
custom the moment you disagree with one of them. So each row
says (next start) until a reload has come up on the value you
chose — which is the same honesty the silent buses get, applied to time
instead of to sound. FRAME CAP goes one further: a ceiling above the rate
this build asked for reads held to the rate it actually runs
at, since a settings file may only hold a game down and never lift it.
what is actually running
The native sample, with the storage swapped underneath it.
Start-up is polled across several frames, because
requestDevice is a promise and the main thread cannot block on
it. The frame loop is driven by requestAnimationFrame out here
rather than by a loop inside the engine. And the clock is the
browser's, because wasm32-unknown-unknown has no
Instant.
The fourth difference is this demo's subject. A browser tab has no config
directory, so the same call that resolves
~/.config/options/ on Linux resolves the Origin Private File
System here, and the save is queued rather than written — the page drains
the queue every frame. Where no store is installed at all, the screen says
so instead of pretending: a settings screen that silently forgets is the
worst version of this bug.
What is not here yet is the rest of the video half — display mode, resolution, present mode — and the requested-versus-resolved display that makes a device's downward-only clamp visible instead of confusing. Those are written down in the repository's backlog rather than implied by an empty row.
console.log, so log lines are queued
inside wasm and drained by the page each frame. crcbl.audio(),
crcbl.saves() and crcbl.logLevel(4) are there too.