Build Log
The engine is being built in the open, and this log is where the experience gets passed on. Newest entries first.
Milestone 2 — The Manor (13 July 2026)
Shipped: flick-screen room transitions, patrolling guardians, collectables, conveyor belts, crumbling floors, ladders, inertia as a first-class physics switch — and The Manor, a four-room winnable game that uses all of it. The engine grew two modules (world.js, entities.js) and is still pure vanilla JS with zero dependencies.
A whole game is now data
This was the milestone's real test: could we build a complete game — rooms, enemies, hazards, goals — without writing any game-specific code? The Manor is one data file: four character grids, a legend, exit wiring, and entity coordinates.
belts: {
name: 'The Belt Room',
rows: [/* 25 strings of 40 characters */],
exits: { left: 'landing', down: 'engine' },
entities: [
{ type: 'patroller', x: 232, y: 164, mode: 'range', min: 224, max: 296, speed: 0.9 },
{ type: 'item', x: 120, y: 121 },
],
},
That's a room. The World class does the rest: builds the tilemap on entry, spawns the entities, watches the edges. Walk off an edge that has an exit wired and you flick to the neighbouring room — walking, falling and climbing all trigger it, which is how the ladder shaft, the doorways and the drop chute all work from the same four lines of edge-checking.
Special tiles are just more flags
Milestone 1's tile flags were SOLID, PLATFORM, HAZARD. The new tiles slot into the same bitmask: a conveyor is SOLID | CONVEYOR with a direction, a crumbling floor is SOLID | CRUMBLE with a hit-point count, a ladder is LADDER. The player controller gained three small behaviours to honour them:
- Conveyors push you through the same collision pipeline as walking — so a belt can pin you against a wall but never shove you through one. Belt speed 0.5 vs walking speed 1.3: you can fight the belt and win, which is the classic tension (Manic Miner's belts worked exactly this way).
- Crumbling floors decay per tick stood on (22 ticks ≈ 0.44s per tile), with cracks drawn in three stages so you can see the betrayal coming. State lives on the room's tilemap instance, so leaving and re-entering regrows the floor — original JSW behaviour.
- Ladders switch the controller into a climb state: gravity off, up/down to move, jump to leap off, and landing on solid ground exits the state automatically. A ladder poking through a floor hole is the entire vertical-transition mechanic — no special case needed.
Rooms reset; progress doesn't
The rule that makes flick-screen games fair: rooms rebuild on every entry (guardians re-arm, bridges regrow — because rebuilding the room object is one constructor call), but collected trinkets live on the World, keyed by room and index, so they stay banked forever. Death costs you your position in the room, never your progress. Getting that split right is most of what "feels fair" means in this genre.
Inertia became a switch, not a hack
Milestone 1 faked Jet Set Willy's momentum-free movement by cranking acceleration to near-instant values. That worked, but it buried a real design axis inside a magic number. The profile now says it outright:
inertia: false, // full speed the tick you press, dead stop the tick you release
With inertia on, accel governs the speed-up, friction the slow-down, exactly as before; gravity is independent of all of it. With it off you get the purest 8-bit movement — velocity is the stick position. It's a checkbox in the Movement Lab now, and toggling it mid-room is the fastest possible education in why both schools existed.
The lesson to take away
Aim your engine at a game, not at features. Every mechanic in this milestone was pulled in by The Manor needing it, and the demo immediately punished designs that only worked in theory — the first bridge layout was uncrossable coming back, the first belt speed was unbeatable walking against it. The headless test suite from milestone 1 scaled up too: Node now plays the manor — climbs the ladder into the room above, rides the belt, stands on the bridge until it drops, walks into the guardian and checks the death rules — all in deterministic 50Hz ticks, no browser required.
Next milestone
Sound: a SID-flavoured three-voice WebAudio synth (square, triangle, noise — jump blips, pickup chimes, and a title jingle). Plus real sprite art to retire the placeholder rectangle man, and the Impossible Mission somersault.
Milestone 1 — The Movement Lab (13 July 2026)
Shipped: fixed-timestep core, C64 palette and integer-scaled video, one-button joystick input, tile collision, scrolling camera, and a data-driven player controller with two physics profiles. Play it here.
Decision 1: 50Hz fixed timestep, because determinism is the game feel
The PAL C64 ran at 50 frames per second, and every game's physics was "per frame" by definition — there was no other clock. Retro-64 does the same thing deliberately:
update()runs in exact 20ms steps, decoupled from the display via an accumulator. A 144Hz monitor renders more often; the simulation never changes.- All physics constants are in pixels per tick. Giana's gravity is 0.14. That number means the same thing on every machine, forever.
- Deterministic physics made the engine testable without a browser: milestone 1 shipped with a Node test that actually plays the room — walks into the spike pit, dies, respawns, jumps, and asserts the apex lands within a pixel of
jumpVel² / 2·gravity.
That last point paid off immediately: the test caught a bug on day one. (In the test, as it turned out — the sim landed the player on top of a platform between two measurements and shifted the baseline. Deterministic replays made it a five-minute diagnosis.)
Decision 2: game feel is data, not code
The central bet of the whole engine. The player controller is ~80 lines, and it reads everything from a profile object:
| Value | Giana school | Jet Set Willy school |
|---|---|---|
| Max speed | 1.7 px/tick | 1.0 px/tick |
| Acceleration | 0.09 (builds up) | instant |
| Air control | 70% | zero — the arc is committed |
| Gravity | 0.14 | 0.09 (floatier) |
| Jump impulse | 3.4 (≈41px high) | 2.55 (≈36px, ~1.1s airtime) |
| Variable jump | yes — release to cut | no — tap and hold are identical |
Same code. Radically different games. The Movement Lab exposes every one of those numbers as a live slider precisely so you can feel the design space — the difference between Mario-school and Manic-Miner-school platforming is about eight floating-point numbers.
The subtlest rule: in a committed-arc profile, what happens when you walk off a ledge rather than jumping? Answer (and what the originals did): you fall carrying your walking velocity. The engine records your speed every grounded tick so the moment your feet leave the floor — by jump or by cliff — the arc is sealed.
Decision 3: the C64's constraints are the art direction
- 320×200 internal resolution, integer-scaled up with
image-rendering: pixelated— fractional scaling smears pixels, so the canvas only ever grows in whole multiples. - The 16 "Pepto" palette colours, by index. Engine code says
C64.LIGHT_RED, never a hex value. The screen is VIC blue with a light-blue border because that's what a C64 looked like the moment you turned it on. - 8×8 tiles — the machine's character resolution. Rooms are 64×25 character grids, built procedurally with named, coordinate-placed features rather than counted-out ASCII art (we tried ASCII art first; counting to column 54 four times is how bugs are born).
- A one-button joystick. The input layer models exactly what a C64 game had: four directions and fire. Up-to-jump and fire-to-jump both work, because both conventions existed and the arguments about which was right have never ended.
Decision 4: axis-separated tile collision
The classic approach, and still the right one: move horizontally, resolve against the tile grid, then move vertically and resolve again. Corners solve themselves. Jump-through platforms fall out almost for free — a platform only stops you if your feet were above it on the previous tick, which is one comparison.
At 50Hz with speeds well under 8px/tick, nothing can tunnel through a tile, so the collision test is a single edge row/column scan. The whole thing is ~60 lines.
The lesson to take away
If you're building your own: do the fixed timestep first. Every other system — tuning, testing, replays, the feel itself — got easier because a tick is a tick is a tick. Variable-timestep platformers can be made to work, but you'll spend the project multiplying by delta-time and wondering why jumps feel different on a laptop.
Next milestone
Flick-screen room transitions (a world map of rooms, Willy-style), patrol enemies with the classic turn-at-edge and fixed-loop behaviours, and the first special tiles: conveyors, crumbling floors, ladders. That's the point where the engine stops being a lab and starts being a game.
Retro-64 is pure vanilla JavaScript — no frameworks, no build step. The engine lives at /public/retro64/ as plain ES modules you can read in the browser: engine/ is the machine, playground/ is this site's movement lab built on it.