Nintendo DS games in TypeScript.

Preview them in the browser, then build a real .nds ROM. Both run the same compiled ES5 script.

Insert a cartridge

    Starting up

    Ticks/s
    0
    Quads
    0/1500
    Draw
    0µs

      Click the console to play with your keyboard.

      Change the code. Reboot the console.

      This is the inserted cartridge's real source. Edits compile in your browser under the same ES5 rules as the command-line build, then run on the console above.

      examples/hinge-breakout/main.ts

      One script, two targets.

      The TypeScript compiler runs on your computer. What reaches the console is plain ES5 text, run by your browser on this page or by the Duktape interpreter inside the ROM.

      1. main.ts

        Your game: one global TypeScript script.

      2. tsc

        Type-checks against ES5 and the SDK. Imports, Map and Promise are rejected here.

      3. game.js

        0 bytes of ES5 for the cartridge in the console.

      In this browser

      1. Canvas 2D paints both screens, Web Audio plays the tone.
      2. Colors are cut to RGB15 and text uses the same 3×5 font.
      3. The top screen keeps the DS limit of 1500 quads a frame.

      On a Nintendo DS

      1. script_gen.h embeds the same bytes as a C array.
      2. Duktape 2.7 runs them in a 512 KiB heap on the ARM9.
      3. libnds draws the top screen with 3D quads, the bottom as a bitmap.
      4. BlocksDS links a real .nds ROM inside Docker.

      melonDS 0.9.3 libretro core

      Measured on an emulated DS.

      59.9ticks per second

      The Playground cartridge's observed game rate in the emulator, against a target of 60.

      82KiBJavaScript heap

      Shown by the ROM's own HUD, out of a 512 KiB budget.

      0512 KiB

      13.29msof a 16.7 ms frame

      update 0.91 msdraw 12.38 ms

      660.5Hzjump tone

      Measured from captured audio. The script asked for 660 Hz.

      3frames from tap to orb

      A tap on the touchscreen relocates the orb within three emulated frames.

      Change one TypeScript number. Keep every native byte.

      A second ROM changed only the movement multiplier from 2 to 3. Holding right for 30 ticks moved the orb 90 pixels instead of 60, and every native source hash stayed the same.

      These figures describe the Playground cartridge in an emulator. Physical hardware has not been verified yet, and the other three cartridges have not been measured in an emulator.

      The whole API fits on one screen.

      Every game gets one global object, nds. Its full declarations live in sdk/index.d.ts.

      What v0.1 does not do yet.

      nds-ts is a small 2D engine prototype. These are the edges you will meet first.

      Graphics

      • No hardware sprites, tilemaps or PNG import yet.
      • One built-in 16×16 sprite: the orb.
      • A frame past 1500 top-screen quads stops the game.

      Code

      • One global script per game, with no imports.
      • ES5 at runtime: no Map, Set or Promise.
      • Numbers use software floating point on the DS.

      Runtime

      • 512 KiB JavaScript heap, headers included.
      • Watchdogs stop a boot after 500 ms and a callback after 100 ms.
      • A frame that misses VBlank slows the game down.

      Sound and storage

      • One square-wave voice.
      • No save data, music sequencing or networking.
      • Not tested on physical hardware so far.

      Build your first ROM.

      You need Node.js 22.12 or newer. ROM builds also need Docker, which runs a pinned BlocksDS image with networking turned off.

      1. Install

        Clone the repository and install the locked dependencies.

      2. Preview

        Opens the browser preview at port 5174.

      3. Build the ROM

        Writes build/nds-ts-demo.nds for an emulator or flashcart.

      4. Make it yours

        Point the same build at your own script.

      Terminal
      $ git clone https://github.com/jiwonMe/nds-ts.git
      $ cd nds-ts
      $ npm ci
      $ npm run dev
      $ npm run build
      $ npm run build -- examples/hinge-breakout/main.ts