01Two doors into the same world
One is for playing it. The other is for adding to it. Both are open, and neither needs an account or a permission.
Door one — play it
- Open lamabroski.com on the phone. There is no install, no download and no account.
- Turn the phone to landscape and tap once. The island boots, the race director counts you in.
- Drive with your head: yaw steers, chin down is throttle, chin up brakes and reverses, ear-to-shoulder drifts. Tap the screen to re-centre.
- Not in the mood to race? Free drive drops the race and leaves the whole island to you.
On a desktop, WASD, the arrows, Space and R are a development fallback — they are deliberately disabled on a touch device. The car tops out near 295 km/h.
Door two — build a module
Players, developers, artists and AI agents all add to this game the same way: one file, one global, one script tag, one check. No bundler, no npm, no CDN, no build step — that is not nostalgia, it is what makes contributing possible. You can add to this world with a text editor and a browser.
The three-step contract
- Write
js/yourmodule.js— a classic script, noimport. - Expose exactly one global with
var(or assignwindow.MYMODyourself). A top-levelconstin a classic script is script-scoped and never becomes a window property — that exact mistake once made the whole effects layer of this game invisible. - Add
<script src="js/yourmodule.js"></script>beforejs/game.js, add the file toJS_FILESintools/autodeploy.sh, and writetools/check-yourmodule.mjs— one runnable check that fails loudly when your module breaks.
That is the whole integration. The engine calls whatever you implement and skips
the rest: init(scene, renderer, world, opts),
update(dt, camera, world), setQuality(q),
dispose() — every one optional.
02The module skeleton
Copy this into js/mymodule.js and it is a valid module. Taken
verbatim from docs/COMMUNITY.md — the same file the
in-game registry points at.
/* js/mymodule.js — a LAMA module.
* Author: your name License: CC0 / MIT / whatever you like
*/
var MYMOD = (function () {
'use strict';
var S = { scene: null, renderer: null, ready: false, quality: 2 };
/** Called once by the engine. Everything is optional — implement what you need. */
function init(scene, renderer, world, opts) {
S.scene = scene; S.renderer = renderer; S.quality = (opts && opts.quality) || 2;
S.ready = true;
}
/** Once per frame. dt is seconds; camera is the left eye (the right eye follows). */
function update(dt, camera, world) { if (!S.ready) return; }
/** Quality tier changed at runtime: 0 low, 1 medium, 2 high. */
function setQuality(q) { S.quality = q | 0; }
/** Called when the scene is torn down. Release GPU resources here. */
function dispose() { S.ready = false; }
return { init: init, update: update, setQuality: setQuality, dispose: dispose,
name: 'My Module', author: 'you', version: '1.0.0' };
})();
if (typeof window !== 'undefined') window.MYMOD = MYMOD; // makes the guard work
Then register it in index.html before js/game.js:
<script src="js/mymodule.js"></script>. The deploy loop picks the
file up automatically — add it to JS_FILES and it is live within
30 seconds, versioned, with an entry in the visible changelog.
03The world that already exists
One island: beaches, jungle, mountains and a volcano, a generated race track with barriers and a lap director, seven NPC drivers, a rear-view mirror render target, and a stereo split that renders two eyes plus the mirror.
Nine modules, nine authors
The upgrade work on this island was done through this contract: nine modules, each by a different author, each owning one file and touching nothing else. These are the exact names and claims the in-game registry shows.
js/sky.jssky, clouds, sun disc, aerial hazejs/water.jsGerstner waves, Fresnel, shoreline foamjs/terrain.jsground detail, procedural albedo + normaljs/vegetation.jssix instanced species, windjs/lighting.jssun shadows, contact shadowsjs/carmat.jscar paint, glass, brakes, tyresjs/particles.jsexhaust, per-wheel debrisjs/bloom.jsthreshold bloom, stereo-safejs/grade.jsfilmic tone curve + colour grade
They coexist because of one rule: one author, one file. When a module needs something from another it asks at runtime and degrades gracefully if the answer is no.
The community panel, in the game
js/registry.js is itself a module. It puts a modules badge in the
bottom-right corner of the game and binds Shift+M on a keyboard. Open it and
you get every module this build ships — the file, the global it exposes, and
whether it initialised. A module that failed to load is still listed, because an
absent module is information. It is the live list; the nine above are only the ones
that have a name in the contract.
It is the shortest honest answer to “who built this world?”
Live in 30 seconds, versioned, visible
tools/autodeploy.sh runs a cycle every 30 seconds: hash the shipped
sources, syntax-check every game script, upload, then verify that the bytes the
server serves equal the bytes on disk — per file. A script that does not parse
blocks the deploy; a build that fails verification leaves the previous one live.
A version is a content hash, so it moves when code moves and holds when nothing did. The changelog is a page you can look at, not a claim in a commit message.
04The rules that keep one world from becoming nine
These are not style preferences. Each one exists because breaking it broke the game.
- One author, one file. Never edit another module's file. Need a change there? Report it; the integrator applies it.
- Never assume another module loaded. Guard with
typeof X !== 'undefined'and do nothing if it is absent. A 404 script tag is silent. - No new dependencies. No npm, no CDN, no bundler. If it cannot be done with the standard library and three.js r128, it cannot be done here.
- No asset files. Generate procedurally — textures, shader maths, geometry. The game has to work with the network off and stay small enough for a phone.
- 60 fps in stereo is the budget. Every visual effect is paid twice. Declare a quality tier for anything expensive and gate it.
- No per-frame allocation. Pool your objects. A phone notices.
- Stereo and the mirror are sacred. Two eyes through scissor halves plus a mirror render target — a full-screen pass that ignores the viewport corrupts one eye.
- Never claim it works without measuring it. Software-rendered frames are a correctness gate, never a performance gate. Say what you could not verify.
05Straight talk
A landing page that oversells is a bug you ship to every visitor. So:
What is measured
Every claim in the repository is backed by a runnable check that exits non-zero when it breaks: the island and track generator (28 checks), car physics, the AI field and a full three-lap race (21), head-tilt mapping and its signs (42), the HTTPS dev server (13), the whole three.js game headless (40), and the effects layer.
Those checks have caught, among others: a Math.atan2 branch cut that
carved a visible seam through the coastline; an inverted steering sign that sent all
eight AI cars off the track in a straight line; barriers that were glue, turning
158 km/h into 10 in one second; and a desktop browser that exposes
DeviceOrientationEvent without ever firing it — “listenable”
is not “live”.
What this build is not yet
No pedestrians, no traffic and no multiplayer here. You are at the wheel of a car on a tropical island, not on four legs: the walkable llama protagonist, the living city and the shared streets are the work of the Babylon build, not of this one. The llama is the name on the door and the mark on the page; it is not yet a claim about this build's player.
About this page
No trackers, no cookies, no CDN, no web fonts, no analytics, and not one line of JavaScript. Every byte is served from this domain and inline, so the page renders instantly, offline, and with scripting turned off.
Contribution rule zero: a check that cannot fail is decoration. One runnable file, exiting non-zero, and a report that names what you could not verify.