DOM ENGINEV0.21.3FIZLET

03 GUIDE · PORTING

Move a game
onto the kernel.

Five games have moved onto Fizlet's kernel so far. A port keeps gameplay: same numbers, same controls, and the page comes back byte for byte. What changes is underneath. This guide is the order that worked, smallest change first, each step tested before the next.

The order

  • Measure first

    Bundle the game as it ships today, minified and gzipped, and write the number down. Every port so far got bigger in bytes; you want to know by how much, and what each byte bought.

  • Hand the loop to the kernel

    Replace the game's own requestAnimationFrame with one engine. Its update becomes step (fixed dt), its drawing render, and every layout read moves to read.

  • Write through the ledger

    Every style, class, attribute, text node or element the game puts on the page goes through engine.ledger. Then destroy() gives the page back, and a browser test can compare the DOM with a snapshot.

  • Seed it

    Swap Math.random in the step for engine.random. Now a run replays from its seed, and a replay test can pin the gameplay before you touch the rules.

  • State into typed arrays

    Move positions, health and timers into typed arrays made once. An allocation test (young-generation GCs over many busy steps) tells you when the step stops allocating.

  • Swap in bricks

    Only where one does the same job: @fzlt/core/collide for a spatial hash, @fzlt/core/steer for movers, @fzlt/core/rules for waves, drops and score, @fzlt/core/board-page for the page as terrain, @fzlt/core/arcade for the whole shell of a page arcade game.

  • Measure again

    Bundle and gzip the port. Say the delta, and say what it bought.

STEP 2 · game.ts
// Before: game.loop() called requestAnimationFrame, Math.random and el.style itself.
const e = createEngine({ root: host, seed });
e.add({
  read: () => game.measure(),                        // layout reads move here
  step: (e, dt) => game.update(dt, e.random),         // fixed dt, the seeded random
  render: (e) => game.draw((el, prop, v) => e.ledger.style(el, prop, v)), // writes through the ledger
});
e.start(); // one shared rAF; e.destroy() gives the page back

What the ports cost

Every game got bigger in gzip bytes. Most of the growth is the shared Board UI kit the arcade games now bake in; the rest buys steps that never allocate, runs that replay from a seed and one shell for three games. Moving the games onto a 4 KB kernel has every number.

Questions

Do I have to port everything at once?

No. Each step above ships on its own and keeps the game playable. The kernel runs fine with one system that calls the old update and draw.

Will the port change how the game plays?

It shouldn't. Write a replay test from a seed before you start (inputs in, final state out) and keep it green through every step.