2026
TL;DR
I built a scriptable 3D game engine for the Nintendo DS so you can write and run games directly on the console itself. Written in C using libnds, it compiles to a ~100KB .nds ROM that runs at 60 FPS. Features a touch-based code editor on the bottom screen and real-time 3D rendering on the top screen. Ships with a working 3D pong game as the default script.
I felt nostalgic for when I made my first games on an old TI-82 graphing calculator. So I tried bringing that whole experience to my Nintendo DS. A complete programming environment you can hold in your hands.
What you see is a scriptable game engine with a custom programming language featuring variables, loops, and conditionals. You write code using the bottom touchscreen, click play, and the game will execute in real-time on the top screen with full 3D rendering.
At a high level, the engine breaks down into three parts:
Uses the DS's 3D hardware to render colored cubes at 60 FPS. Each model has position (X, Y, Z), rotation angle, and color. The camera is fully controllable with position and yaw/pitch angles.
// DS 3D rendering code (C + libnds)
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
gluLookAt(camX, camY, camZ, // camera position
camX + lookX, camY + lookY, camZ + lookZ, // look target
0, 1, 0); // up vector Each model is drawn with a transform (position + Y-axis rotation), then the cube geometry: one color, six quads (24 vertices).
// Per-model draw calls (from main.c)
for (i = 0; i < MAX_MODELS; i++) {
if (!modelActive[i]) continue;
glPushMatrix();
glTranslatef(modelX[i], modelY[i], modelZ[i]);
glRotatef(modelAngle[i], 0, 1, 0);
drawCube(CUBE_COLORS[modelColorIndex[i]]);
drawWireframeCube();
glPopMatrix(1);
}
// Cube geometry: RGB15 color -> glColor3b, then 6 faces as GL_QUADS
glColor3b(r * 255/31, g * 255/31, b * 255/31);
glBegin(GL_QUADS);
/* +Z face */
glVertex3f(-1.0f, 1.0f, 1.0f);
glVertex3f( 1.0f, 1.0f, 1.0f);
glVertex3f( 1.0f, -1.0f, 1.0f);
glVertex3f(-1.0f, -1.0f, 1.0f);
/* -Z, +Y, -Y, +X, -X ... (24 vertices total) */
glEnd(); A touch-based code editor with a custom UI drawn pixel-by-pixel to a 256x192 bitmap. Features include:
// Software rendering to bottom screen u16 *subBuffer = (u16*)BG_BMP_RAM_SUB(0); // 256x192 framebuffer subBuffer[y * 256 + x] = RGB15(31, 31, 31); // white pixel
Executes one line of script per frame (~60 lines/sec). Scripts can use 26 variables (A-Z) plus 9 read-only registers for input (D-pad, buttons) and system state (elapsed time, camera direction).
// Script execution (simplified)
if (tokenEquals(script[scriptIP], "add")) {
int r = scriptReg[scriptIP]; // which register (A-Z)
registers[r] += getNumberParamValue(scriptIP, 0);
scriptIP++; // next line
} Scripts are built from tokens (commands) with numeric parameters. Each line executes instantly, with no parsing overhead, just a series of if-checks against token names.
Variables & Math
SET A 5 — set register A to 5ADD A 1 — add 1 to ASUBTRACT A 2 — subtract 2 from AMULTIPLY B -1 — multiply B by -1Control Flow
LOOP / END_LOOP — infinite loopIF_GT A 10 — if A > 10IF_LT A 0 — if A < 0IF_TRUE kA — if A button pressedEND_IF — close conditional3D Objects
MODEL 0 — create model at index 0POSITION 0 X Y Z — set positionANGLE 0 45 — set rotation angleNEXT_COLOR 0 — cycle colorCamera & Rendering
CAM_POS X Y Z — set camera positionCAM_ANGLE yaw pitch — set look directionBACKGROUND 2 — set bg color (0-3)BEEP — play 0.1s soundSLEEP 0.016 — pause (60 FPS = 0.016s/frame)LEFT, UP, RGT, DN: D-pad (1.0 when held, 0.0 when released)
KA, KB: A and B buttonsTIME: elapsed seconds since script startedLOOKX, LOOKZ: camera forward direction (normalized X and Z)
The engine ships with a playable pong game. Here's a simplified excerpt:
MODEL 0 ; create ball
MODEL 1 ; create paddle
CAM_POS 0 8 18 ; position camera
SET A 0 ; ball X position
SET B 1 ; ball velocity
SET C 0 ; paddle Z position
LOOP
ADD A B ; move ball
IF_GT A 10 ; hit right wall?
MULTIPLY B -1 ; reverse velocity
END_IF
IF_TRUE Up ; up button pressed?
ADD C -0.5 ; move paddle up
END_IF
POSITION 0 A 0 0 ; update ball position
POSITION 1 -13 0 C ; update paddle position
SLEEP 0.016 ; ~60 FPS
END_LOOP The full script includes collision detection, game-over logic, and beep sounds on miss, all done with simple register math and conditionals.
make in the project directory
program.nds (~100 KB ROM file)
You need a flashcart (e.g. R4, DSTT, Acekard) with a microSD card:
program.nds to the microSD card
Note: I got my R4 cart + SD card from a friend years ago, so I don't have detailed setup instructions for the cart itself. Most modern flashcarts just need you to copy their firmware to the SD root, then add ROMs in a folder.
You can test the DS game engine build directly below. The emulator loads ds-game-engine.nds. Loads a more basic pong game than the one in the video.
Nintendo DS emulator (Desmond). If the game doesn’t start, ensure JavaScript is enabled and the page has finished loading.
Compiled ROM (ds-game-engine.nds)
Feel free to ask or discuss in this Reddit thread