DEV Community

Cover image for I rewrite the game while it is still running
Atomi J.D.
Atomi J.D.

Posted on Originally published at jdbasic.org

I rewrite the game while it is still running

I typed three sentences into a chat window. The ship came back red with 100 lives, then the HUD moved, then the bullets turned into a rolling rainbow. Same wave of enemies on screen for all of it. Seventy-three seconds, and the VM never restarted.

On screen:

  1. Stellar Drift is running. It is a nineteen-hundred-line jdBasic program, a vector-style arcade shooter. Score is at 2840 with three lives left and the ship still teal-mint.
  2. I press F6. The game freezes on a yellow STOPPED banner. The VM's worker thread parks itself at the next safe opcode. Nothing gets unwound, so the score and the sprite positions and the music thread are all still sitting there.

The assistant in the video is Claude Code, Anthropic's terminal CLI,
wired to jdBasic over MCP. Any MCP client works: Cursor, Cline, Continue,
Zed, Windsurf.

Tweak 1, pure state change. I type "turn the ship red and give me
100 lives"
. Claude fires one MCP tool call:

jdb_eval(code: "g_palette{\"player\"} = [255, 80, 80] : lives = 100")
Enter fullscreen mode Exit fullscreen mode

Two assignments against the live VM, no source edit. Resume, and the ship is
red with the lives counter reading 100. The enemies never left the screen.

Tweak 2, hot recompile. I type "move the shield bar 20 pixels up".
This one is a source edit. Claude finds the routine and changes exactly
one line:

 SUB draw_shield_bar()
     DIM bw AS INTEGER = 200
     DIM bxs AS INTEGER = 16
-    DIM bys AS INTEGER = SCR_H - 24
+    DIM bys AS INTEGER = SCR_H - 44
Enter fullscreen mode Exit fullscreen mode

Then jdb_recompile. The function body swaps into the live VM, module
state survives untouched. Resume, and the bar sits 20px higher with
everything else where it was.

Tweak 3, another recompile. I type "let me shoot rainbow bullets".
Claude rewrites the bullet-colour logic, jdb_recompile again. Same wave
still running, and only the bullets look different.

One state poke and two live recompiles, and the VM never went away.

Underneath that sit about seven hundred lines of plumbing.

What it is, in five lines

  • jdBasic is a small modern BASIC with APL-style array math.
  • It runs as an interpreter and compiles to a native EXE via LLVM 18.
  • One binary also speaks MCP (jdBasic.exe --mcp).
  • The MCP server keeps one persistent VM across every tool call.
  • It supports STOP -> EDIT -> RECOMPILE -> RESUME of a running script.

Why this is different from "AI runs your code"

Most code-exec MCPs fork a fresh process per call, so there is no state
to inspect and no way to iterate on a long-running game loop. You restart
the world for every tweak.

jdBasic stays on. The same VM owns your variables, the SDL window, 600
sprites and the music thread. Pause parks it at the next safe point.
Recompile swaps function bodies in without touching module state, and
resume picks up at the exact opcode it stopped at.

The tools:

Tool What it does
jdb_load load a .jdb file; runs to first STOP
jdb_stop / jdb_resume pause / continue the loaded script
jdb_recompile re-parse source; swap FUNC bodies; keep state
jdb_eval run a snippet against the live VM
jdb_doc substring lookup in doc/languages.md

jdb_doc greps the language reference before the model invents a
builtin that does not exist. The reference ships with the binary, so
there is no version guessing.

What a longer session looks like

Straight out of the log, all against the same running VM:

  • "Move the stats block to the bottom-right and where is my pink HUD?" -> Claude moved the stats block, repainted the HUD. jdb_recompile.
  • "A hit should count if any shot in the 5-way salvo connects." -> rule change in check_collisions(). jdb_recompile.
  • "The bullets are invisible now, but they still hit." -> Claude read the renderer, found a DRAWCOLOR call out of order, fixed it.
  • "Make the boss extra hard and give it its own boss music." -> new music track via SOUND.NOTE, plus a phase-switch in the enemy AI.
  • "The music needs to get more epic, and a boss-phase switch so I know the boss has arrived." -> filter cutoff jumps, second drum voice kicks in, screen tint shifts red.

Forty-five minutes and about a dozen iterations, all in one session. The
combo counter at the end was the one from the beginning.

The trick under the hood

Three things have to be true at once for the loop to work:

  1. A safe pause point. The VM checks a flag every N opcodes. When jdb_stop flips the flag, the worker stashes its frame stack and returns from run_code without unwinding any user state.
  2. Hot-swap, not reload. jdb_recompile parses the new source, then walks the live VM's function table and overwrites the bytecode in place. Module-level DIMs are intentionally not re-executed, they would clobber the world.
  3. Resume from the same opcode. jdb_resume clears the pause flag and the worker thread reads the next instruction. The pop / push stack is byte-identical to where it left off.

Total weight: about 700 lines of C++ across the VM, MCP server, and
worker. The hard part was not any one of those three; the hard part
was making them survive together across hundreds of recompile cycles
without leaking VM handles or double-freeing a sprite texture.

VB6 could do this

I had a VB6 phase. Stop the program, edit the code, carry on. Edit and
Continue. Coding on the open heart. Microsoft dropped it and everyone went
back to edit-build-run.

jdBasic was always meant to be a small dirty language that still does it.
MCP only changes who types.

The game side is one IMPORT and one branch in the loop:

IF CLAUDE_LIVE.pause_pressed() = 1 THEN
    draw_paused_frame() : SCREENFLIP
    STOP                              ' Claude takes over via MCP
    CLAUDE_LIVE.refocus()             ' focus snaps back to the game
    DO
        draw_paused_frame() : SCREENFLIP : SLEEP 30
    LOOP UNTIL CLAUDE_LIVE.unpause_held() = 1
ENDIF
Enter fullscreen mode Exit fullscreen mode

claude_live.jdb is 154 lines. Any jdBasic program can import it.

Try it

Just play with the language, free, no AI required:

Pair-code with Claude Code (the setup in the video) or any other
MCP client
, Cursor, Cline, Continue, Zed, Windsurf:

  • Same binary
  • One config block. For Claude Code, drop it in .mcp.json (or run claude mcp add jdbasic jdBasic.exe --mcp); other clients take the same mcpServers entry:
{
  "mcpServers": {
    "jdbasic": {
      "command": "jdBasic.exe",
      "args": ["--mcp"]
    }
  }
}
Enter fullscreen mode Exit fullscreen mode
  • Restart the client, ask it to "Let us code on space shooter"

Then press F6 in the game window when you want to take over.

GitHub: https://github.com/AtomiJD/jdBasic.
Discord: https://discord.gg/KknuCtm68.

Why I think this matters

Every AI coding tool aims at the same target: Node, React, Kubernetes,
Rust. That is where the jobs are.

The other direction is a small language with one canonical reference the
model can grep and a runtime built for the live-tweak loop. A model is
better at a language it can hold entirely in context, and the loop stops
feeling like writing code.


If you build something with jdBasic, drop a clip in #showcase on
Discord. We will boost.

Top comments (0)