A local AI game studio that ships playable games — not chat logs.
You describe a verb. DotLab scaffolds a real project, fills a vertical slice you can run, and keeps iterating while the game stays open. Models run on your machine. The quality bar is enforced by the host, not left to the model’s good intentions.
A cube on a plane is a fail. A ledge, a jump, and a flag is a start.
Three windows. That is the whole product.
- Studio (
./start) — projects, composer, Play / Verify / Deep, thelocal $0chip. - Play (
dotlab liveor the Play button) — the game fills the window. The agent writes files. You keep jumping. - OpenZoo — click the status chip. Wallet, wrap, quote, spend cap. Off until you turn it on.
Chrome shots below match the real UI. The hero is the quality bar the host is trying to hit.
Most AI coding tools optimize for looking busy: more files, more tokens, more confidence. Games punish that. A green capsule on a plane is not a game. A plaza with one hoop is not a race. A jump that never lands on a ledge is not a platformer.
DotLab is built around a different bet:
- The host owns the floor. Feel numbers, opposition counts, ledges, gates, doors, and death rules are applied by the studio — not invented in prose.
- The model works in a tight loop. It peeks at the project, patches one system at a time, and stops when the slice passes a real grade.
- Local is the default. Ollama runs the stack. Paid models exist only if you turn them on.
One prompt can still open a world. The difference is what “done” means: a project you can play, measure, and ship.
| Strength | What you feel |
|---|---|
| Playable first | Scaffold → npm run dev → controls work. No empty repo ceremony. |
| Host quality floor | Genre CONFIG, coyote, pit death, juice keys — applied by the host after the coder. The 30B does not own feel. |
| Grok as code | Pairing loop in bin/grok.py (open / complain / route / prefill). Not Grok weights — the decisions, as Python. |
| Look + craft + body kits | Place, feel, and figures are host modules. The model picks card ids. Verify fails a slice that skips them. |
| Engine law | Three.js vanilla, Vite, metres, Y-up. Not Unity. Not Z-up. |
| Play is the judge | An 8s bot grades canvas, input, restart <3s, stutter, screenshot slop. Verify seeing coyoteMs in a file is not enough. |
| Studio that builds | Director → Architect (max) → recursive coder → Critic (dense). Novelty goes in src/systems/*, not a dump into game.js. |
| Live iteration | The Play window stays open while files change. Five deaths on the same gap → the host shortens it. |
| Deterministic verify | verify grades structure and genre contracts without calling a model. P0 fail blocks “done”. |
| Skill routing | The agent asks the catalog what exists. Unknown tools do not invent themselves. |
| Worlds from a sentence | Regions, height field, instances — walkable Three.js you can keep editing. |
| $0 local path | Game-tuned models on Ollama. Cloud is opt-in, never ambient. |
| OpenZoo option | openzoo.fun paid floor: leCore in front, x402, no API key. Long trees cheaper than buying the model direct. |
| Ship path | Private GitHub repo in one command when you are ready. |
describe a verb → real project → studio build → play while it writes
↓
verify (files) + play-P0 (8s bot) → ship
- You write what the player does at t=8s.
- The host scaffolds a Vite project (not a chat attachment).
- Studio roles design the slice. Architect and critic stay off the 7B. The coder writes novelty under
src/systems/*. - Play stays open. Auto-update reloads the game. You jump the gap while gravity changes.
dotlab verify -p .grades the files.dotlab playtest -p .grades the bot run. Either P0 fail = not done.dotlab shippushes a private GitHub repo when you want it off the laptop.
Undo a bad agent step with dotlab kit undo -p DIR. Recap the session with dotlab kit recap -p DIR.
./start opens this. Ollama must be running.
| Control | What it does |
|---|---|
| New game | Scaffold a project into ~/dotLab/Projects. |
| Project list | Filter by engine. Click to focus. Meta line is engine · genre · last verify. |
| Play | Opens the Play window on this project (dotlab live). |
| Verify | Host grade. No model. P0 fail stays a fail. |
| Deep | File agent for systems (dialogue, weapons, AI) — not feel numbers. |
| More | Folder, terminal, zip, rename, keep-feel / tighter / juice. The game loop stays in the header. |
| ⌘K | Command palette. |
| local $0 / zoo · $0.01 | Status chip. Click it to open the OpenZoo yard. This is where money lives. |
| Ship | Private GitHub repo for the focused game. |
| Composer | Enter sends. “Make game” starts a slice from the verb. |
The composer is the front door. The header is the craft loop. Folder / Term / Zip stay under More so they do not compete with Play.
This window is for playing, not configuring models.
| Control | What it does |
|---|---|
| Play | Focus the game so keyboard and mouse go to the iframe. |
| Apply updates | Reload now if you turned auto-update off. |
| Auto-update | Default on. File writes from the studio land while you play. |
| AI log | Director / Coder / file events. The game stays on the left. |
| local / zoo pill | Who is coding. Read-only. There is no wallet modal here. |
| GitHub | Ship this game. |
Click the game to capture input. Updates from the studio apply live, or queue until you hit Apply. Five deaths in the play log and the host pulls the next ledge closer — no model. Manage OpenZoo in Studio — Play only shows the pill so you know whether the coder is local or paid.
- Web games — Three.js vanilla + Vite, metres, Y-up. Place is a look card. Figures are a body card. Feel is the craft kit. One toy per slice. The model writes the one novelty.
- Pixel games — Canvas2D bake path with nearest-filter feel
- Vintage — Game Boy ship bar, hard GBA ceiling
- Open worlds — prompt → regions → terrain → instances
- Shader lab — multipass GLSL, Shadertoy import
- Solana Seeker — same game loop + Mobile Wallet Adapter
Engine rule: the game is always a real project (Vite, files, a loop). Seeker adds wallet; it does not replace the game.
- macOS or Linux (Apple Silicon recommended)
- 32 GB RAM for the full model pair · 16 GB can run
--14bor--7b - Ollama running
- Node.js 18+
- Python 3.10+ (stdlib only in the studio)
- Disk: ~40 GB for
--dual, less for smaller profiles - Optional: GitHub CLI to ship
- Optional: Solana RPC only if you turn OpenZoo on
git clone https://github.com/AleisterMoltley/dotLab.git
cd dotLab
chmod +x install.sh start bin/*
./install.sh --dualinstall.sh pulls models, builds the dotlab tags from Modelfile, and puts dotlab on your PATH (gamemaster remains a CLI alias).
| Profile | Models | When |
|---|---|---|
--dual |
30B MoE + 32B dense + 14B flash (7B if no 14B) | Best quality |
--max |
30B MoE + 14B/7B flash | Strong coding, lighter critic |
--14b |
qwen2.5-coder:14b | 16–24 GB machines |
--7b |
qwen2.5-coder:7b | Laptops |
export PATH="$HOME/.local/bin:$PATH"
dotlab -h
python3 tests/run.py # cheap suite, no Ollama
./start # browser studio (Ollama must be running)Open Ollama.app before the first session. Games land in ~/dotLab/Projects.
# Walkable world from a sentence
dotlab scaffold world-game --name Wilds
cd Wilds
dotlab worlds generate -p . "coastal village, pine ridge, desert canyon"
npm install && npm run devWASD to walk · click to look · Space to jump.
# Or a genre slice you can tighten immediately
dotlab scaffold web-game --genre platformer --name Skyjump
cd Skyjump && npm i && npm run dev
dotlab -p . --agent "Add a flag at the last ledge and a restart on death"
dotlab studio build -p . "one fair first death and clearer juice" --live
dotlab verify -p .--live keeps the Play window open while the studio works. --playtest adds the 8s Play-P0 bot after the build.
- Compile the brief into genre, loop, feel, palette, look card, and opposition counts
- Scaffold a real project — Vite + Three vanilla, metres, Y-up. Immutable
src/look/,src/craft/,src/body/ - Studio roles design and structure the slice (director 8k, architect max, critic dense)
- Deep coder writes
src/systems/<novelty>.js. The host wires the import and owns CONFIG - Host floor injects missing feel keys, pit death, 1/1/1 slop out
- Verify grades files (
look_kit,craft_kit,body_kit,engine_law). P0 fails get a verify-anchored repair (not “add inventory”) - Play-P0 (with
--playtest) runs an 8s genre bot: canvas, input, restart, slop frames - Ship (optional) pushes a private GitHub repo
The host applies feel, locks, and events. The model proposes. Invalid ops do not crash the game. Council picks a pitch by host score (verb, t=8s, feel, kill list) — LLM only on a tie.
| Mode | Use when |
|---|---|
| plan | Design + architecture only |
| build | Full production loop with deep coder (default) |
| council | Three pitches, vote, optional build |
| parallel | Player / world / UI streams, then merge |
| review | Roast an existing project |
Use --flat on build only if you want a single-pass coder. Prefer the default.
OpenZoo (openzoo.fun)
OpenZoo is an optional paid model yard. It is not a zoo game, not a biome pack, and not “the cloud” in general. Local Ollama stays the default. You turn this on when a long tree is cheaper on their floor than buying the same body direct — or when you want a specific frontier model for one session.
Every floor model has leCore in front. The body is spilled and retrieved, so a long game tree costs about a tenth of buying that same body direct. Short pings with nothing to spill are marked up (today ×3). If their sidecar is down they still serve, billed at direct — never extra for their outage.
There is no API key. You do not call OpenRouter. You POST a normal OpenAI chat body to the official option:
https://openzoo.fun/api/v1/chat/completions
You get HTTP 402. Pick yUSDCx or wTOKENx, sign one Token-2022 transfer, retry the same URL with X-PAYMENT. That is what the stall on openzoo.fun does. That is what dotlab cloud on zoo does.
The 402 resource may name the floor backend (x402-tokens.fly.dev). Still retry on openzoo.fun. Do not default to the floor. Do not use lecore-front.fly.dev — memory is already in front.
sequenceDiagram
participant Studio
participant Zoo as openzoo.fun
participant Sol as Solana
Studio->>Zoo: POST /api/v1/chat/completions
Zoo-->>Studio: 402 + accepts[] (yUSDCx / wTOKENx)
Studio->>Sol: Token-2022 TransferChecked
Studio->>Zoo: same POST + X-PAYMENT
Zoo-->>Studio: 200 completion
Solana mainnet. Their facilitator pays SOL — you do not.
| Token | Role |
|---|---|
| yUSDCx | Wrapped USDC. This is what settles. |
| wTOKENx | Wrapped TOKEN (EVULoNF4DeMBN4dGiZiDfpiiTfNZgoCvXWWgaV3epump). |
| Raw USDC / raw TOKEN | Will not settle. Wrap first. |
Wrap at x402.accrue.fund/start or dotlab zoo wrap (needs a dust of SOL for the wrap tx only).
| You want | Do this |
|---|---|
| See that the floor is live | dotlab zoo ping or OpenZoo → Ping in the yard |
| Price a model | dotlab zoo quote --model x-ai/grok-4.6 |
| Get a deposit address | dotlab zoo wallet or Create / show wallet |
| Fund | Send USDC or TOKEN to that address, then Wrap |
| Route the studio through it | dotlab cloud on zoo or Use OpenZoo |
| Back to $0 local | dotlab cloud off or Use local Ollama |
| Session receipts | dotlab zoo spend |
| Search the floor | dotlab zoo models grok (hundreds of listings) |
Default floor model: x-ai/grok-4.6. Featured also: Gemini 2.5 Flash, Claude Sonnet 4, GPT-4o-mini.
Wallet files stay in config/zoo-wallet.json (gitignored). Export a backup from the yard before you fund it.
Session spend is capped at $0.50 by default (ZOO_SPEND_CAP). If a call cannot settle — empty wrap, cap hit, 402 error — the studio falls back to local Ollama instead of dying mid-build.
Their floor can fail-open: HTTP 200 with an empty wallet. That is not “paid and working”. dotlab zoo spend labels those receipts. The host’s can_pay check refuses to treat fail-open as funded.
When zoo is on, the host sends more project, not less. A tiny ping pays markup. A full tree is the cheap path. Read extra.pricing on the live 402 (counterfactual vs markup) — do not hardcode a multiple.
dotlab cloud on grok|claude|openai|gemini still exists (API keys via env or dotlab cloud set). OpenZoo is the key-less rail. They are not the same thing.
# Chat
dotlab "Third-person village: walk, talk, fair first death"
# Studio
dotlab studio plan -p DIR "brief"
dotlab studio build -p DIR "brief" --live
dotlab studio council -p DIR "brief" --build --live
dotlab studio review -p DIR "what is weak"
# Worlds
dotlab worlds generate -p DIR "biomes…"
dotlab worlds generate --offline -p DIR "snow village"
# Scaffolds
dotlab scaffold web-game --genre platformer --name Skyjump
dotlab scaffold world-game --name Wilds
dotlab scaffold pixel-game --name Grove
dotlab scaffold seeker-game --genre idle --name ClaimQuest
dotlab scaffold shader-lab --name NeonFrag
# Agent
dotlab -p ./Skyjump --agent "Add collectibles and a score HUD"
# Measure
dotlab live -p DIR
dotlab playtest -p DIR # 8s bot + Play-P0 report
dotlab verify -p DIR
dotlab skills route "juice the jump"
dotlab rlm -p DIR "deepen opposition and juice"
dotlab eval-briefs # host slice ship-rate (no Ollama)
# Memory & ship
dotlab prefs set like "tight jumps"
dotlab wiki add -p DIR "Gravity 28" --why "user said floaty"
dotlab github login
dotlab ship -p ./Wilds -m "vertical slice"
dotlab kit undo -p DIR
dotlab kit recap -p DIR
dotlab hands fit -p DIR --hang 0.55 --apex 1.8
dotlab hands mark -p DIR --kind flag --x 12 --y 2
dotlab share -p DIR
# Local speed / stronger flash
dotlab turbo warmup
dotlab intervene # bake flash FROM 14B when installed
dotlab update --modelfile
# Optional paid model (off until you opt in)
dotlab cloud on grok
dotlab --cloud claude "Tighten coyote time"
dotlab cloud off
# OpenZoo — official floor at openzoo.fun
dotlab zoo ping
dotlab zoo quote --model x-ai/grok-4.6
dotlab zoo wallet
dotlab zoo wrap # USDC→yUSDCx or TOKEN→wTOKENx
dotlab zoo spend # session receipts + cap
dotlab zoo models grok
dotlab cloud on zoo
dotlab --cloud zoo "Tighten coyote time"./start is the same CLI with a browser UI.
A cube on a plane is a fail.
A slice needs:
- a place you can read in one second
- a body with real feel (accel, friction, coyote — not
pos += speed) - a verb obvious by t=8s
- opposition that pushes back
- juice on every meaningful hit
- a fair first death and a restart under three seconds
Three.js. Vanilla. Vite. One unit is one metre. Y is up.
That is not a preference. Verify engine_law fails a new three-slice that pulls in React Three Fiber, skips Vite, or never sets metres / applyEngine. Pixel and Vintage stay 2D on purpose. Seeker is the same Three.js game plus a wallet button.
A local 30B can write a loop. It cannot reliably invent lighting, a camera that does not make you sick, juice on every hit, a readable figure, or enemies that telegraph and then commit. Those are host modules. The model picks card ids and writes the novelty. It does not rewrite the kit.
A real 1280×720 slice (Razor Pit), not a mock and not a phone crop. Place, bodies, cover, captain, pulse rifle — this is the host kit on Ollama.
src/look/ is vendored and immutable. One call: applyLook({ scene, renderer, camera, pal, spec }).
| Card | Reads as |
|---|---|
neon-night |
Wet street, magenta / cyan, one shader accent |
pine-ridge |
Cool hemi, dark ground, tree scatter |
dusk-coast |
Warm key, long fog, low sun |
desert-gold |
Hard sun, pale sand, sparse rocks |
rain-alley |
Tight fog, reflective ground, puddle sheen |
interior-warm |
Small room, practicals, no outdoor scatter |
Lights, fog = background, a landmark, InstancedMesh scatter, one shader. Not twenty-two individual boxes. Each loop also gets a room: pit ring (shoot), canyon (jump), tunnel (run), corridor (sneak), interior (talk), rails (race). Verify look_kit fails a new three-slice that skips applyLook or InstancedMesh.
src/body/ is immutable. makePlayer / makeEnemy / makeWeapon / makeCover + tickPose.
| Card | Reads as |
|---|---|
visor / runner |
Head, torso, visor — not a lone capsule |
drone / crawler / captain |
Three enemy bodies. Elite is a captain |
pulse |
Viewmodel with muzzle and sight |
crate |
Cover with an emissive lip |
Pose is procedural: breath, walk bob, windup lean, strike lunge. No mixer. Verify body_kit fails a slice that vendors player.js and still draws a raw capsule.
src/craft/ is the same idea for motion, combat, and AI. The slice calls these. It does not invent them.
| Module | Call | If the model writes this instead |
|---|---|---|
| Camera | fpsLook / springTo / chaseIdeal |
Parent the cam to the mesh → sick |
| Juice | punch(stack, 'hit'|'kill'|'shoot') |
Split sfx / shake / hitstop → silence |
| Tracers | makeTracerPool |
new Mesh per shot → hitch |
| Impacts | makeImpactPool |
Kill with no confirm |
| AI | tickBrain |
Strike that still tracks → unfair |
| Telegraph | makeMarkPool |
Windup with no floor ring |
| Ground | attachBlob |
Floating capsule |
| Recoil | kickRecoil / springRecoil |
Random HUD kick |
| Hurt | attachVignette |
Damage with no flash |
| Motion | spinY / squashLand / popOut |
Dead pickups, instant vanish |
| Scale | SCALE.eye / doorH / capsuleR |
Door 0.5 m, eye 3 m |
| Engine | applyEngine |
Z-up, Unity, no metres |
| Director | tickDirector |
Five enemies strike at once |
Strike is a lock (lockX / lockZ). Windup tracks; the commit does not. At most three attackers; the rest orbit.
One toy per slice (ricochet, dash-slash, sticky, time-gun). The model picks the id. The host wires it.
Verify craft_kit / body_kit / engine_law are P0 when those files are vendored. Studio / RLM keep the 30B off large rewrites of src/game.js (DOTLAB_NOVELTY_JAIL=1). Novelty lives in src/systems/.
Grok 4.6 weights cannot be copied into Ollama. What ships is the pairing loop as host Python: bin/grok.py.
The local 30B is a coder. The host is the pair.
| Call | What happens without a model |
|---|---|
| open | Verb, t=8s, look / body / toy, feel, kill list. Same instant compile as slice. |
| complain | “floaty” is gravity. “icy” is accel/friction. Not a new jump system. |
| route | patch / rebuild / refuse / LLM. Feel words never wait on Ollama. |
| pack + prefill | A short law plus the first assistant turn, so the 30B starts from a decision. |
dotlab grok open "neon pit shooter"
dotlab grok route "jump feels floaty"
dotlab grok whoamiEvery new slice writes .dotlab/grok.json. Chat, agent, and studio director inject that session as HOST SESSION (locked). Director JSON is seeded from it; if the model fails the schema, the host seed ships. Taste is numbers (camLag, telegraphSec, maxAttackers) — the 30B does not invent them.
Kernel moves (open / complain / refuse) are teacher traces and LoRA pairs. The local model can be trained on those decisions later; the host does not auto-train.
dotlab grok traces
dotlab grok harvest
dotlab lora harvest # export-kernel.jsonl for Unsloth / KilnFlash is skipped when the kernel already drafted valid director JSON. Max may sharpen pitch only. Slot-JSON is the only legal coder emit besides src/systems/*.
Want the actual frontier model for one tree? That is OpenZoo, opt-in, not the default.
The prompt shrinks. Play writes the floor.
| You do | Host does |
|---|---|
| Jump | Solves gravity / jumpForce from hang time and apex (dotlab hands fit) |
| Mark death / flag / place in Play | Spatial brief for the next studio pass |
| Keep / Tighter / Juice | Feel keyframes — dotlab hands timeline / restore |
| Play a green run | Saves a ghost. A later patch that can't clear that jump is Play-P0 ghost_broke |
| Share | dotlab share -p DIR → zip. Friend: npm i && npm run dev. Not a GitHub remote. |
The game talks in the Play HUD: “You broke my jump.” / “I didn’t know where to go.” / “One more run?” — not a critic essay.
After three look-alike slices the host stamps a constraint (four colors, one moving ledge, no dash). Same neon village twice is a fail.
Feel lives in CONFIG numbers. The host writes them after every coder pass.
After a coder pass the host re-vendors src/look, src/craft, src/body. If the 30B deleted applyLook / punch / makePlayer, the host restitches game.js from the template. Novelty jail is on by default: the model writes src/systems/*, not a new lighting rig.
dotlab verify -p DIR grades the tree without an LLM (look_kit, craft_kit, body_kit, engine_law, genre contracts). dotlab playtest -p DIR grades the running game: no canvas, no input, restart slower than 3s, stutter, or a near-black / green-capsule frame is a Play-P0 fail. Skill routing refuses tools the catalog cannot name.
dotLab/
AGENTS.md How to patch this repo
install.sh Models + PATH
Modelfile Game-tuned system prompt
start Browser studio launcher
bin/ CLI and host logic
knowledge/ Domain packs injected by route
lib/ Pixel, look, body, craft — immutable kits
templates/ Scaffolds and slices
tests/ Cheap suite (no Ollama)
chat/ live/ Browser UIs (Studio + Play)
playtest/ Headless 8s bot (Play-P0)
docs/readme/ GitHub shots (HTML mocks + PNGs)
Working on the repo: read AGENTS.md, then python3 tests/run.py. Regenerate chrome shots with node docs/readme/capture.mjs (needs Playwright from playtest/). The arena still is a real playtest frame (docs/readme/slice-arena.png).
| Tier | Default | Role |
|---|---|---|
| flash | 14B if installed and RAM ≥16 GB, else 7B | Drafts, short QA — never final game files |
| max | dotlab (Qwen3-Coder 30B MoE) |
Coding + architect |
| dense | 32B when present | Critic / hard repair |
ollama pull qwen2.5-coder:14b then dotlab intervene to bake the stronger flash. Do not swap in a 70B “because bigger”.
Editor endpoint: http://127.0.0.1:11434/v1 · key ollama · model dotlab. See config/README-editor.md.
MIT — LICENSE. Copyright 2026 AleisterMoltley.
Runtime: Ollama, Qwen coder weights, Three.js. Optional cloud APIs and the OpenZoo x402 floor are yours to enable.
Ship the game. Pay $0 unless you choose not to.




