01 · THE MACHINEWhat Zhaozhou is
Zhaozhou is a console architecture aimed at real, fabricated silicon: a physical machine of its own. The working proving ground along the way is an FPGA core for the SuperStation One, a MiSTer-family board built around a Cyclone V FPGA. That core is a step on the road, not the destination. It is not a miniature PC graphics card. The project charter calls it “a purpose-built machine for transforming small authored inputs into overwhelming geometric motion.”
The defining idea inverts how detail normally works. On a conventional machine, a model carries its own level of detail: an asset is “high poly” or “low poly” by nature. On Zhaozhou, detail is a budget, allocated by how much screen a thing actually occupies this frame. An army of a hundred creatures collapses smoothly from full geometry down to tiny meaningful moving forms as it recedes, and nothing about the assets changes.
The second idea is that the ground remembers. Terrain is a live surface deformed by small deterministic programs. Craters, ridges, trenches and travelling waves are not painted-on effects; they are the terrain itself, and they stay.
What it is for
- Large outdoor battlefields whose terrain permanently records damage
- Huge quantities of particles, many as real low-polygon geometry rather than flat sprites
- Large procedural spells: geometry generated on the fly, not authored
- Two-player split-screen at 60 frames per second
- A Steam release of the game, once the machine exists
The three display modes
| Mode | 3D render area | Scanout canvas | Purpose |
|---|---|---|---|
| Z60 | 384 × 240 | 384 × 240 | Primary single-player mode |
| Storm | 320 × 240 | 320 × 240 | Maximum overdraw, particles and transparency |
| Duo | 2 × 256 × 192 | 512 × 240 | Two 4:3 views plus a strip of cheap 2D interface space |
Split-screen was designed in from the start, not bolted on: Duo draws only 6.7% more 3D pixels than the single-player mode (98,304 against 92,160), and the simulation, terrain deformation and particles are shared between the two views.
The rules that do not bend
- A late frame is never partially shown. The previous complete frame repeats and the miss is recorded.
- When a frame gets expensive, form gives way first. Detail, particles and effects degrade before frame timing does.
- No hardware feature exists without an executable reference and tests that compare the two.
- Reserve is a feature. At least 10% of the chip is kept free until the complete core is stable.
02 · GALLERYWhat the reference renderer produces
These images are the reference renderer's actual output, converted straight from its 16-bit canvas without regrading, then scaled 3× with no smoothing so 240p reads on a modern display. Each caption says exactly what produced it.

Duo: the committed golden frame 512 × 240 displayed · shown at 3×
The full two-player picture: two independent 256×192 views of the same world from different cameras, centred by black border bands. Visible: the 360° sky, a heightfield island deformed by a terrain program, a crack-ring scar stamped into the ground's persistent damage record, a marker sigil per view, and additively-blended particles.
This frame earned its place by catching a real bug. The renderer and the hardware design had implemented two incompatible readings of how a split-screen frame is stored in memory; read it the wrong way and you get a garbled, dark image. The stored layout was ratified, both sides now agree, and this exact frame is pinned in the test suite by checksum. The two hashes below are the committed constants, and this render reproduces both.
mode=DUO 512x240 displayed (2 x 256x192 views + 48 black border lines) status=0 commands=14 resource_misses=0 canvas_crc32c=0x47893F4C displayed_crc32c=0xC3ED350F audio_events=2 terrain_velocity_samples=81

Sky: the dome, and a real sun 384 × 240 (Z60) · shown at 3×
A dusk sky rendered on its own. The backdrop is a real 360° cylinder of 48 gradient columns, closed by a zenith cap above and an under-plane below, with a scrolling cloud sheet: 1,844 triangles, rendered like any other geometry, projected with true perspective. One elevation ramp drives every layer, and the layer joins are required to be colour-continuous, so no seam survives from horizon to zenith. Both of those are recent fixes: the sky used to project linearly (painting both sides of the cylinder onto the screen) and each layer carried its own colour, which drew the cap and ground rims as hard elliptical outlines.
The sun is new too. It is a star from the console's 12-class star system: a distance-washed white core, a corona read through the same 64-entry colour ramp as the disc, and a lens ghost chain, drawn by the celestial compositor rather than the old flat additive quad.
mode=Z60 384x240 single view, DrawSky only status=0 canvas_crc32c=0xB8968C1F sky primitives emitted=1840 (1536 drum bands + 48 zenith cap + 128 under-plane + 128 cloud + 4 sun) tick=1234 (cloud scroll) drum_yaw=0x1800 rot_proj: scale 18, pitch cos 17 / sin 4 (rotation-only, validated)

Island: deformation and damage 384 × 240 (Z60) · shown at 3×
The terrain story in one frame. The island is a 161 × 161 grid of
height values: 51,200 triangles at 15 cm spacing. Over it run six applications of
one compiled terrain program, crater_ring, each with its own centre, radius
and strength. Those are the overlapping crater rims, computed live rather than sculpted
by an artist. Two more commands stamp crack rings into the island's persistent damage
sheet, which darkens the shading where the ground was hurt.
The scar outlives the frame that made it: the damage sheet persists, so a later frame with no stamp command renders identically.
mode=Z60 384x240 single view status=0 commands=17 resource_misses=0 canvas_crc32c=0x817A8155 patch=161x161 heightfield, +-12 m envelope, 8 m peak, 0.150 m grid spacing (sub-metre) geometry: 25600 cells -> 51200 triangles; terrain decided 18092 of 92160 pixels (coverage after overdraw, not fragment invocations) TerrainField program=crater_ring hash=0x484ADD8D (28 instrs), 6 applications stacked main crater: centre (0,0) r_in 3 m r_out 7 m amplitude 4.0 m; tick 19 of 100 -> phase 0.190 SurfaceStamp: 2 crack rings, tag 1 - outer radius 7 m width 4 m strength 0xC000, inner radius 2.5 m width 1 m strength 0x9000 terrain_velocity_samples=32114

crater_ring: a terrain program, visualised 256 × 256 data map · shown at 3×
This one is a data visualisation, not a scene. It is
the raw height output of the crater_ring program, sampled on a grid and
false-coloured from dark (zero) to bright (maximum). No camera, no lighting, just the
number the program returns at each point of the ground.
Terrain programs are compiled bytecode, not shaders: a compiler emits a small program image, the image is checksummed, and exactly one interpreter evaluates it, the same interpreter the renderer calls when it deforms terrain. The interpreter exists twice, in C++ and TypeScript, and tests hold the two byte-identical.
crater_ring earth field program, hash=0x484ADD8D, 28 instructions grid=256x256 over x,z in [-10 m, +10 m]; height output lane only params: p0/p1 centre (0,0), p2 r_in 3 m, p3 r_out 7 m, p4 amplitude 4 m phase=0.190 (age 19 of 100 ticks) height range: 0.0000 m .. 2.3227 m (fx16 raw 0 .. 152220) saturating evaluations: 0 of 65536
In motion: the deformation reel
Deformation is the one thing a still cannot show. These loops are the reference renderer's actual frame sequences, encoded with each subject's own exact colour palette and verified by decoding the file back and comparing every frame byte-for-byte against the source. What the loop shows is what the renderer drew: no re-compression, no dithering, no interpolation.
Development-state renders. Two renderer changes re-captured every loop on this page: the sky now projects with true perspective under a colour-continuity law (the old sky read as flat ovals), and islands use the dual-heightfield format with a modelled top and bottom surface, so holes can be punched clean through. All pinned checksums were re-derived and re-committed with those changes, deliberately and in writing. Surfaces are still flat-shaded solid colour: the texturing stage is specified but deliberately not built yet.

Membrane: a travelling wave 384 × 240 (Z60) · 64-frame loop · shown at 3×
The signature behaviour: ground acting as a rubbery
membrane, not dirt with a hole punched in it. A terrain program,
wave_pool, expresses a travelling radial wave using only the instruction
set the hardware will freeze. The wave is exactly periodic by construction, so the loop
closes without a seam. Every frame is a full evaluation of the program over the whole
island, then the ordinary render. Nothing here is a canned animation.
subject=terrain-wave mode=Z60 384x240 single view, reference oracle (zref::render) frames=64 step=8 ticks (frame f: tick = f*step) programs: wave_pool hash=0x8BDCEB63 (27 instrs), impact_wave hash=0x82F5F4E4 (30 instrs) field_apps=1 stamps=0 debris=0 shake_frames=0 palette=120 unique colours (palette-exact by construction, <=256 enforced) travelling radial wave, n=2 whole cycles per loop: frame 63 steps straight back to frame 0 sequence_crc32c=0x0222090B gif: 64 frames at 20 fps, 467,446 B, palette 120 of 256, paletteuse=dither=none (no palettegen) encode verified: decoded GIF matches every source frame byte-for-byte (CRC-32C cross-checked) inter-frame change: median 8.0% of pixels, max 8.8%, min 7.2%

Impact: strike, wave, rebound, settle 384 × 240 (Z60) · 80-frame loop · shown at 3×
Sacrifice's most famous moment, mechanised. One program,
impact_wave, drives the whole event: the strike dents the ground, a ring
wave expands outward, the centre rebounds through a damped wobble, and everything
returns exactly to rest. Ballistic debris and a decaying screen shake ride along. The
loop starts and ends at rest, so the restart reads as the next strike landing.
subject=terrain-impact mode=Z60 384x240 single view, reference oracle (zref::render) frames=80 step=8 ticks (frame f: tick = f*step) programs: wave_pool hash=0x8BDCEB63 (27 instrs), impact_wave hash=0x82F5F4E4 (30 instrs) field_apps=1 stamps=0 debris=8 shake_frames=10 palette=129 unique colours (palette-exact by construction, <=256 enforced) strike -> expanding annular wave -> centre rebound -> settle; debris + screen shake (erupt-style garnish); starts and ends at rest sequence_crc32c=0x4F97AD9B gif: 80 frames at 20 fps, 297,670 B, palette 129 of 256, paletteuse=dither=none (no palettegen) encode verified: decoded GIF matches every source frame byte-for-byte (CRC-32C cross-checked) inter-frame change: median 4.3% of pixels, max 11.0%, min 1.1%

Scars: damage that accrues 384 × 240 (Z60) · 96-frame sequence · shown at 3×
Three strikes land in sequence: the same compiled
impact_wave program applied three times with different centres and start
times, stacking in command order. As each wave settles, a crack ring is stamped into the
island's persistent damage sheet, and the darkened scars stay. The
sequence holds at the end so the accumulated scarring reads.
subject=terrain-scars mode=Z60 384x240 single view, reference oracle (zref::render) frames=96 step=8 ticks (frame f: tick = f*step) programs: wave_pool hash=0x8BDCEB63 (27 instrs), impact_wave hash=0x82F5F4E4 (30 instrs) field_apps=3 stamps=6 debris=0 shake_frames=0 palette=121 unique colours (palette-exact by construction, <=256 enforced) three strikes in sequence; surface-sheet scars persist and accrue (the loop restart is the sequence replaying) sequence_crc32c=0x86069EA1 gif: 96 frames at 20 fps, 300,753 B, palette 121 of 256, paletteuse=dither=none (no palettegen) encode verified: decoded GIF matches every source frame byte-for-byte (CRC-32C cross-checked) inter-frame change: median 4.9% of pixels, max 7.4%, min 0.0%

Breach: a hole punched clean through 384 × 240 (Z60) · 64-frame sequence · 320 m island · shown at 3×
The new island format doing the thing it exists for. This island has real thickness: a top surface and a modelled underside, five metres thick at the rim and about twenty-seven at the heart. A crater digs down through it; when the ground gets thin enough, cells give way entirely and simply cease. No top, no bottom, sky straight through the island, debris falling out of the world. Because neighbouring cells share corners, the ground sags toward the hole before dropping; the crumble comes from the geometry itself, not from an animation.
Where Sacrifice dressed every hole with a fixed fake curtain wall, these walls end at the island's actual underside. 246 cells give way across twelve frames, every one a discrete, exactly-reproducible event.
subject=terrain-breach mode=Z60 384x240 single view, reference oracle (zref::render) frames=64 step=8 ticks (frame f: tick = f*step) programs: wave_pool hash=0x8BDCEB63 (27 instrs), impact_wave hash=0x82F5F4E4 (30 instrs) field_apps=0 stamps=0 debris=8 shake_frames=10 palette=127 unique colours (palette-exact by construction, <=256 enforced) 320 m dual-heightfield island (2.0 m pitch = 25 Island-Patch pages of ground, one Phase-3 envelope patch), modelled keel 5..27 m thick; 36-step bake ramp digs a 30 m pframe_0000_crc32c=0xEE8179E2 gif: 64 frames at 20 fps, 110,404 B, palette 127 of 256, paletteuse=dither=none (no palettegen) encode verified: decoded GIF matches every source frame byte-for-byte (CRC-32C cross-checked) inter-frame change: median 0.9% of pixels, max 16.0%, min 0.0%
The sky and the stars
The sky is geometry and the stars are palettes. Star colour never exists at draw time: the disc and its corona are drawn as intensity through a 64-entry colour ramp the CPU rebuilds each frame, which is how Noctis IV coloured its entire universe, and it is native territory for a palette console. Lens flares are frozen-table sprite splats into a quarter-resolution glow plane; there is no shader anywhere, because the machine refuses to have any.

Sky sweep: horizon to zenith 384 × 240 (Z60) · 64-frame loop · shown at 3×
The camera pitches from just above the horizon to straight up and back. The sky is five separate pieces of geometry: an under-plane, two drum bands, a zenith cap. The point of the loop is what you cannot see: the joins between them cross the whole frame during the sweep and never show, because a ratified continuity law requires the colour at every join to match exactly. Before that law existed, the cap and ground rims rendered as hard elliptical outlines; the project owner's report ("ovals, not sky") is what forced it.
subject=sky-sweep mode=Z60 384x240 single view, reference oracle (zref::render) frames=64 step=8 ticks (frame f: tick = f*step) programs: wave_pool hash=0x8BDCEB63 (27 instrs), impact_wave hash=0x82F5F4E4 (30 instrs) field_apps=0 stamps=0 debris=0 shake_frames=0 palette=79 unique colours (palette-exact by construction, <=256 enforced) camera pitch sweep, horizon to zenith and back; one elevation ramp drives under-plane, both drum bands and the zenith cap, so the layer joins cross the frame without a visiblframe_0000_crc32c=0x2DDF063E gif: 64 frames at 20 fps, 240,155 B, palette 79 of 256, paletteuse=dither=none (no palettegen) encode verified: decoded GIF matches every source frame byte-for-byte (CRC-32C cross-checked) inter-frame change: median 20.2% of pixels, max 87.7%, min 0.0%

Red giant: the boiling disc 384 × 240 (Z60) · 63-frame loop · shown at 3×
An S03 red giant from 1.5 radii, one of twelve star classes carried over from Noctis IV with their exact colour tables. The granulation churns every frame, and no pixel of the texture is ever rewritten: the surface is a static noise texture indexed through a 64-entry ramp, and the animation is the ramp rotating one step at a time. 63 frames is exactly one full revolution of the palette, so the loop closes on the period of the colour table itself. The corona reads through the same ramp, so it is automatically the star's own colour fading to black. The dots behind it are the procedural starfield: one star per 100,000-unit sector of space, existence and position a pure integer hash held bit-exact against the original game's arithmetic.
subject=star-boil mode=Z60 384x240 single view, reference oracle (zref::render) frames=63 step=6 ticks (frame f: tick = f*step) programs: wave_pool hash=0x8BDCEB63 (27 instrs), impact_wave hash=0x82F5F4E4 (30 instrs) field_apps=0 stamps=0 debris=0 shake_frames=0 palette=191 unique colours (palette-exact by construction, <=256 enforced) S03 red giant at 1.5 radii; granulation is a 63-entry palette rotation, zero texels rewritten per frame; ENLARGED to 80 px disc radius for legibility (was invisible at galleframe_0000_crc32c=0x76DC665C gif: 63 frames at 20 fps, 586,248 B, palette 191 of 256, paletteuse=dither=none (no palettegen) encode verified: decoded GIF matches every source frame byte-for-byte (CRC-32C cross-checked) inter-frame change: median 16.8% of pixels, max 19.1%, min 0.8%

Lens flare: the ghost chain 384 × 240 (Z60) · 64-frame loop · shown at 3×
A yellow star at forty radii sweeps the frame. The lens chain rides it: a twelve-spoke burst at the light, and three ghost copies mirrored through the screen centre at fixed fractions of the lens axis, each a shrunken starburst, exactly as the original renders them. The spoke lengths zig-zag through a frozen eight-entry table, so the star shape is jagged but perfectly stable frame to frame. Everything is additive sprite splats into a quarter-resolution glow buffer, tinted by the star's class colour, bounded at 16,384 texels a frame. As the light nears the frame edge the flare dims over the last 16 pixels instead of cutting, which removes the one hard pop the original had.
subject=noctis-flare mode=Z60 384x240 single view, reference oracle (zref::render) frames=64 step=8 ticks (frame f: tick = f*step) programs: wave_pool hash=0x8BDCEB63 (27 instrs), impact_wave hash=0x82F5F4E4 (30 instrs) field_apps=0 stamps=0 debris=0 shake_frames=0 palette=243 unique colours (palette-exact by construction, <=256 enforced) S00 at 40 radii; burst at the light, ghosts at -26/-77/-230 Q8.8 of the axis, quarter-res glow splats, class-colour tint; the flare dims over the outer 16 px instead of cframe_0000_crc32c=0x0100A54F gif: 64 frames at 20 fps, 207,623 B, palette 243 of 256, paletteuse=dither=none (no palettegen) encode verified: decoded GIF matches every source frame byte-for-byte (CRC-32C cross-checked) inter-frame change: median 5.8% of pixels, max 6.0%, min 1.2%

Pulsar: a strobe by law 384 × 240 (Z60) · 64-frame loop · shown at 3×
Class S11: a 250-unit core, the smallest object in the star table, with the airless-corona bake (hard bright core, steep skirt). Its flare is gated by the duty law carried over from the original: the burst is on only while the spin phase is in the first quarter turn. One comparator makes the cheapest exotic object in the catalogue. The blink rate comes from the star's own spin draw, 715 angle units per tick.
subject=pulsar mode=Z60 384x240 single view, reference oracle (zref::render) frames=64 step=8 ticks (frame f: tick = f*step) programs: wave_pool hash=0x8BDCEB63 (27 instrs), impact_wave hash=0x82F5F4E4 (30 instrs) field_apps=0 stamps=0 debris=0 shake_frames=0 palette=210 unique colours (palette-exact by construction, <=256 enforced) S11 pulsar at 40 radii; the flare strobes on the S2 duty law (spin_phase < 0x4000, one quarter of each rotation); ENLARGED to 28 px disc radius for legibility (was 4 px, read aframe_0000_crc32c=0xF3E9AC85 gif: 64 frames at 20 fps, 18,688 B, palette 210 of 256, paletteuse=dither=none (no palettegen) encode verified: decoded GIF matches every source frame byte-for-byte (CRC-32C cross-checked) inter-frame change: median 0.0% of pixels, max 0.2%, min 0.0%

Occlusion: the fade the original lacked 384 × 240 (Z60) · 64-frame loop · shown at 3×
A distant sun crosses behind the island at dusk. At six hundred radii the star is a two-pixel glint and its flare collapses to the signature horizontal streak, which falls out of the distance law for free. Visibility is one byte: a probe latches the effect-tag at the sun's screen position, and if the tag was not written by a glowing source, the sun is hidden. Noctis tested the same single pixel and its flare popped on and off in one frame; here a four-bit counter walks the flare's strength one step per frame, so the streak takes fifteen frames to melt away behind the island and fifteen to return. The probe reads a latched byte, never the framebuffer.
subject=flare-occlusion mode=Z60 384x240 single view, reference oracle (zref::render) frames=64 step=8 ticks (frame f: tick = f*step) programs: wave_pool hash=0x8BDCEB63 (27 instrs), impact_wave hash=0x82F5F4E4 (30 instrs) field_apps=0 stamps=0 debris=0 shake_frames=0 palette=112 unique colours (palette-exact by construction, <=256 enforced) S00 sun at 30 radii crossing behind the island; the effect-tag probe gates the flare and a 4-bit counter fades it 15 frames each way; halo_atmo variant (atmosphere = oframe_0000_crc32c=0xD1C9E814 gif: 64 frames at 20 fps, 103,633 B, palette 112 of 256, paletteuse=dither=none (no palettegen) encode verified: decoded GIF matches every source frame byte-for-byte (CRC-32C cross-checked) inter-frame change: median 1.8% of pixels, max 5.0%, min 0.0%
The star catalogue
The console carries a twelve-class star gamut taken over from Noctis IV with its exact colour tables, and the library now screens every live class. The six loops below are generated straight from the effects-library catalogue in the repository: the same file pins each loop's checksum, so a listing here is a claim the repository can re-derive. The other three live classes appear above (the yellow flare sweep, the boiling red giant, the pulsar); the three dead classes have no flare and no screen by spec law. Every moving sun drags a smear behind it now: eight fading ghosts of its own corona, rebuilt from captured history each frame.
| Class | Name | Colour | Library status |
|---|---|---|---|
| S00 | Yellow star | 255, 235, 162 | screened · crc 0xD20023CD |
| S01 | Blue giant | 121, 203, 255 | screened · crc 0xDFAFCD70 |
| S02 | White dwarf | 255, 255, 255 | screened · crc 0x048AB345 |
| S03 | Red giant | 255, 121, 81 | screened · crc 0xADC6EB7C |
| S04 | Orange giant | 255, 223, 130 | screened · crc 0x66299B68 |
| S05 | Brown dwarf | 130, 65, 40 | dead class · no flare, no screen (spec law) |
| S06 | Grey giant | 130, 113, 97 | dead class · no flare, no screen (spec law) |
| S07 | Blue dwarf | 40, 81, 255 | screened · crc 0xD3355069 |
| S08 | Multiple | 255, 130, 65 | screened · crc 0xCA637ABD |
| S09 | Infant star | 195, 130, 255 | screened · crc 0x5FBE7C1B |
| S10 | Runaway | 162, 40, 40 | dead class · no flare, no screen (spec law) |
| S11 | Pulsar | 0, 255, 255 | screened · crc 0x6F2A61FC |

Blue giant 384 × 240 (Z60) · 64-frame loop · shown at 3×
Large hot star, bright blue-white (30,50,63). Drifts with a smear: white-hot streak, blue fringe.
subject=blue-giant mode=Z60 384x240 single view, reference oracle (zref::render) frames=64 step=8 ticks (frame f: tick = f*step) programs: wave_pool hash=0x8BDCEB63 (27 instrs), impact_wave hash=0x82F5F4E4 (30 instrs) field_apps=0 stamps=0 debris=0 shake_frames=0 palette=48 unique colours (palette-exact by construction, <=256 enforced) S01 blue giant at 20 radii; large hot star with bright blue-white colour (30,50,63 VGA); compact corona and burst flare sequence_crc32c=0xDFAFCD70 gif: 64 frames at 20 fps, 172,778 B, palette 48 of 256, paletteuse=dither=none (no palettegen) encode verified: decoded GIF matches every source frame byte-for-byte (CRC-32C cross-checked) inter-frame change: median 4.8% of pixels, max 6.2%, min 2.2%

White dwarf 384 × 240 (Z60) · 64-frame loop · shown at 3×
Compact white star (63,63,63), rapid spin, fine granulation. Small star, long smear.
subject=white-dwarf mode=Z60 384x240 single view, reference oracle (zref::render) frames=64 step=8 ticks (frame f: tick = f*step) programs: wave_pool hash=0x8BDCEB63 (27 instrs), impact_wave hash=0x82F5F4E4 (30 instrs) field_apps=0 stamps=0 debris=0 shake_frames=0 palette=63 unique colours (palette-exact by construction, <=256 enforced) S02 white dwarf at 2 radii; compact white star (63,63,63) with rapid spin; five-pass box-smooth granulation; drifts with a long smear sequence_crc32c=0x048AB345 gif: 64 frames at 20 fps, 44,856 B, palette 63 of 256, paletteuse=dither=none (no palettegen) encode verified: decoded GIF matches every source frame byte-for-byte (CRC-32C cross-checked) inter-frame change: median 1.1% of pixels, max 1.6%, min 0.6%

Orange giant 384 × 240 (Z60) · 64-frame loop · shown at 3×
Warm giant, golden orange (63,55,32). Drifts with a white-hot smear fading to orange at the fringe.
subject=orange-giant mode=Z60 384x240 single view, reference oracle (zref::render) frames=64 step=8 ticks (frame f: tick = f*step) programs: wave_pool hash=0x8BDCEB63 (27 instrs), impact_wave hash=0x82F5F4E4 (30 instrs) field_apps=0 stamps=0 debris=0 shake_frames=0 palette=79 unique colours (palette-exact by construction, <=256 enforced) S04 orange giant at 2.5 radii; warm giant star with golden orange colour (63,55,32); drifts with a white-hot smear fading to orange at the fringe sequence_crc32c=0x66299B6frame_0000_crc32c=0x17E2CBC7 gif: 64 frames at 20 fps, 191,326 B, palette 79 of 256, paletteuse=dither=none (no palettegen) encode verified: decoded GIF matches every source frame byte-for-byte (CRC-32C cross-checked) inter-frame change: median 5.2% of pixels, max 6.6%, min 2.2%

Blue dwarf 384 × 240 (Z60) · 64-frame loop · shown at 3×
Compact deep blue star (10,20,63). The drift smear grades white to deep blue along the tail.
subject=blue-dwarf mode=Z60 384x240 single view, reference oracle (zref::render) frames=64 step=8 ticks (frame f: tick = f*step) programs: wave_pool hash=0x8BDCEB63 (27 instrs), impact_wave hash=0x82F5F4E4 (30 instrs) field_apps=0 stamps=0 debris=0 shake_frames=0 palette=126 unique colours (palette-exact by construction, <=256 enforced) S07 blue dwarf at 2 radii; compact deep blue star (10,20,63); the drift smear grades white to deep blue along the tail sequence_crc32c=0xD3355069 gif: 64 frames at 20 fps, 71,704 B, palette 126 of 256, paletteuse=dither=none (no palettegen) encode verified: decoded GIF matches every source frame byte-for-byte (CRC-32C cross-checked) inter-frame change: median 2.0% of pixels, max 2.2%, min 0.8%

Multiple 384 × 240 (Z60) · 64-frame loop · shown at 3×
Binary system: two bodies of one class orbit the barycentre once per loop, curved trails on both.
subject=multiple mode=Z60 384x240 single view, reference oracle (zref::render) frames=64 step=8 ticks (frame f: tick = f*step) programs: wave_pool hash=0x8BDCEB63 (27 instrs), impact_wave hash=0x82F5F4E4 (30 instrs) field_apps=0 stamps=0 debris=0 shake_frames=0 palette=121 unique colours (palette-exact by construction, <=256 enforced) S08 multiple system: two bodies of one class orbiting the barycentre, one revolution per loop, each with a curved trail (the §15 showpiece) sequence_crc32c=0xCA637ABD gif: 64 frames at 20 fps, 179,555 B, palette 121 of 256, paletteuse=dither=none (no palettegen) encode verified: decoded GIF matches every source frame byte-for-byte (CRC-32C cross-checked) inter-frame change: median 4.4% of pixels, max 5.2%, min 2.8%

Infant star 384 × 240 (Z60) · 64-frame loop · shown at 3×
Young protostar, purple (48,32,63), per-identity undertone. Drifts with a purple smear.
subject=infant mode=Z60 384x240 single view, reference oracle (zref::render) frames=64 step=8 ticks (frame f: tick = f*step) programs: wave_pool hash=0x8BDCEB63 (27 instrs), impact_wave hash=0x82F5F4E4 (30 instrs) field_apps=0 stamps=0 debris=0 shake_frames=0 palette=136 unique colours (palette-exact by construction, <=256 enforced) S09 infant star at 2 radii; young protostar, purple (48,32,63), per-identity undertone; drifts with a purple smear sequence_crc32c=0x5FBE7C1B gif: 64 frames at 20 fps, 115,812 B, palette 136 of 256, paletteuse=dither=none (no palettegen) encode verified: decoded GIF matches every source frame byte-for-byte (CRC-32C cross-checked) inter-frame change: median 2.8% of pixels, max 3.2%, min 1.6%
Untouched native-resolution copies of the stills live under
renders/native/. A dated, append-only diary of renders (what the
machine looked like then, beside what it looks like now) will join this gallery. A subject
only enters the diary once it is definitive for its phase; with terrain mid-migration and
texturing unbuilt, nothing qualifies yet, so the diary is still empty.
03 · STATUSWhere the project actually is
Every number below was read out of the repository at a pinned commit. Nothing is rounded up, and the zeroes are as load-bearing as the rest.
The maturity ladder
The design is split into 88 blocks: 73 hardware, 15 software. Every block climbs a seven-rung ladder one rung at a time, and each advance must cite evidence that a validator re-checks against git history. Nothing is on the top three rungs, because those require a physical board, and there is none.
The fourteen blocks at RTL_VERIFIED are the whole console shell: command
scheduling and DMA, the memory arbiter, guard and bridge, the video pipeline, input, audio,
and the debug counters and per-frame checksums, each verified in simulation against its C++
reference. Six further blocks are marked blocked on hardware; the ledger tool
refuses to advance them until a board exists, no matter what evidence is offered.
Phase 2 closed: the console runs as one machine
Until this week, every verified block had only ever been tested alone. Phase 2 composed them into one running machine: publish a sealed frame packet and the console fetches it, schedules it, copies the pixels, swaps buffers, scans out, and reports a per-frame checksum and counters, with no test scaffolding in the datapath. In the gate demo, two controllers move markers across a split-screen world through the real input path, and every one of 1,202 displayed frames matched the independent C++ reference over every byte. A randomized 10,000-frame soak (two and a half hours of simulated play) passed 205,026 checks with zero failures.
Composing the blocks revealed defects no per-block test could see: a data seam that silently carried only an eighth of each memory write, while its test compared exactly the eighth that survived; a deadlock between two blocks that are each correct alone; and both framebuffers sharing one DRAM bank, which shredded the display under real combined traffic. All three are fixed and pinned by tests.
The gate also measured that copying a finished frame off the machine for checking takes just under two frames, so debug captures run at 30 Hz. That number is the cost of the debug copy, not how fast the console draws.
Phases
The charter defines fourteen phases, each with a hard acceptance gate. Two have written, ratified gate reports.
The one outstanding Phase-1 gate (a tiny program in the console's own language parses, type-checks and compiles) was deferred into Phase 3, in writing, and Phase 3 is now underway.
Tests and proofs
The fast gate every change must pass currently runs 80 tests with zero failures (one test, a code-formatting check, skips on machines without the tool and is counted here as a skip, not a pass). Beyond it: long randomized nightly soaks, dedicated memory and video lanes, and 216 compiler test cases. The codebase is roughly 53,000 lines (26,600 C++, 16,900 TypeScript, 9,500 SystemVerilog), excluding generated code.
Ten properties are registered for formal proof, which is mathematical model-checking rather than testing. One of them is worth retelling. The memory arbiter's fairness bound was frozen at 40 cycles and cited as evidence for a full development wave, but the proof had never actually been run. The moment it was made real, 40 failed. The bound was re-derived from the actual burst arithmetic and then proven exactly tight: the proof fails at 51 cycles and passes at 52, so 52 is the true worst case rather than a number that happened to pass. The old formula stays in the spec, struck through, so the error remains visible. One property, the memory-refresh bound, is not established: its full proof does not finish on this machine, so it is excluded from the automated lane and recorded as unfinished.
First synthesis
Synthesis has been run exactly once, and not on the console: a 52-line checksum probe
went through Quartus to open the toolchain lane (about 51 logic elements, 96 registers,
zero errors, on a provisional Cyclone V part; the real device is Phase-0 board truth). No
console block records a measured resource figure, and none is SYNTHESIZED.
There is no timing-closure data and no bandwidth data; those directories exist and are
empty on purpose.
04 · BLOCKEDWhat needs physical hardware
The repository keeps a written list of everything it cannot honestly do without a board, and refuses to guess any of it.
- Board truth. Real memory timings, measured bandwidth, clock stability, input latency. The file meant to hold these measurements does not exist, by design. No probe result has ever been simulated or assumed.
- The MiSTer platform layer is not vendored yet, which blocks the clock, reset and clock-domain-crossing blocks.
- Per-block resource figures and timing closure. The synthesis toolchain is open locally, but the real chip is unknown until a board is probed, so no such claim is made.
- The top three maturity rungs, for all 73 hardware blocks.
- The long soak tests: eight and twenty-four hours of combined stress on real silicon. Not runnable, and explicitly not simulated.
And what simply is not built yet
- The Wound Lab demo is barely begun. The charter specifies a full scene: an island, two player wizards, three creature species, four spells. What exists is its first thin slice, the Phase-2 gate demo: two controllers moving markers on the composed machine. The sigils in the renders above are 8×8 test patterns standing in for wizards.
- The hardware rasterizer is not started. Its five blocks are specification-only and its RTL directory is empty. Every image on this page came from the software renderer. That is Phase 4 and 5 work.
- No geometry frontend, no creature system, no particle hardware, no compositor. Those are Phases 8 through 11.
05 · METHODHow it is being built
The interesting part of this project is not the renderer. It is the rule that nothing is allowed to exist as hardware until it already exists as something testable.
Every hardware block must have, before any circuit is written: an executable reference implementation, directed tests, randomized tests that compare the circuit against that reference, and formal proofs where a property can be stated. The command format is generated from a single definition into C++, TypeScript and SystemVerilog, and the three are held byte-identical by a test that fails on any drift. There is no floating-point math anywhere in the render path; the software renderer is integer-only precisely so hardware can match it exactly.
The part that is easy to get wrong
Comparing two implementations catches disagreement. It cannot catch a mistake they share. Adversarial review rounds, reading the code against the frozen specification, have repeatedly found real bugs while the entire test suite was green:
A 65,536× amplification in a curve instruction. Both the C++ and TypeScript interpreters made the identical mistake, so the test comparing them agreed perfectly, on the wrong answer.
An invisible sun. A brightness conversion overflowed so that full brightness wrapped around to zero: any sun above a third of maximum energy drew exactly nothing.
A sky that silently did not exist. A shift bug collapsed all 48 columns of the sky cylinder to a single angle, turning every one of its 1,536 triangles into zero-area slivers the rasterizer dropped. The tests counted primitives and checked coordinates; none of them ever asked whether a pixel had been drawn. The fix ships with an assertion that does: zero sky pixels before, 371 after.
White that resolved to magenta. The display dither adds a small offset before quantizing each colour channel, and for green the offset can push full brightness one step past the top of its 6-bit field, which wraps to zero. The comment in the code proved the overflow impossible for red and blue and assumed green behaved the same; green's dither is twice as strong and its headroom half. Nothing in the project had ever produced saturated pure white until the new sun did, and the sun promptly resolved to a white-and-magenta checkerboard. The clamp is now law and a test drives every dither phase of every near-white green.
Formal proofs that had never run. Two blocks carried "formally proven" badges resting on proofs the tool had never once elaborated; they passed vacuously. When the lane was made real, both failed immediately, and fixing one of them surfaced a genuine hole in the memory protection. The rule that came out of it: a proof may not be cited as evidence unless the tool has recorded a passing run in which the assertions were demonstrably reachable.
The lesson the project keeps re-learning, and keeps writing down: a green test suite is evidence about the tests, not about the machine. Counting triangles is not the same as checking that a pixel was written, and a proof that has never run is not a proof.