01 — THE MACHINEWhat Zhaozhou is
Zhaozhou is a console architecture designed for the SuperStation One — a MiSTer-family board built on a Cyclone V FPGA. It is not a miniature PC GPU. The charter puts it plainly: it is “a purpose-built machine for transforming small authored inputs into overwhelming geometric motion.”
The defining idea is an inversion of how detail normally works. On a conventional machine, a model carries its own level of detail: an object is “high poly” or “low poly” as a property of the asset. On Zhaozhou, detail is a budget allocated by projected pixel importance — how much screen a thing actually occupies this frame decides how much geometry it gets. An army of a hundred creatures collapses continuously from full geometry to tiny meaningful moving forms, and nothing about the asset changes.
The second idea is that the ground remembers. Terrain is a live heightfield driven by field programs — small deterministic bytecode programs that deform it — plus a persistent per-patch surface sheet that records scarring. Craters, ridges, trenches and travelling waves are not decals. They are the terrain, and they stay.
What it is for
- Large outdoor battlefields with deformable terrain that permanently records battle damage
- Huge quantities of particles, many represented as actual low-polygon geometry rather than sprites
- Large procedural spells — geometry generated on the fly, not authored
- Two simultaneous local-player cameras and hard 60 Hz presentation
The three frozen 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 48 scanlines of cheap 2D interface space |
Duo costs almost nothing extra to raster: 98,304 3D pixels against Z60’s 92,160 — 6.7% more. The second camera adds geometry work, but simulation, terrain deformation, creature animation and particle simulation stay shared. Split-screen was designed in from the start rather than bolted on.
The laws that do not bend
- 60 Hz is a contract. A late frame is never partially displayed. The previous complete frame repeats and a deadline fault is recorded.
- The frame rate does not negotiate; form negotiates. LOD, particles, material cost and auxiliary effects degrade coherently before frame timing does.
- No hardware feature exists without an executable reference, counters, captures and differential tests.
- Reserve is a feature. At least 10% fabric and timing headroom is kept until the complete core is stable.
02 — GALLERYWhat the oracle produces
These four images are the reference renderer’s actual output, converted from its RGB565 canvas without regrading, then scaled 3× with nearest-neighbour so 240p reads on a modern display. Each caption says exactly what produced it.
Reminder: reference-oracle renders, not hardware. These came out of
zref::render, a deliberately slow, integer-only, CRC-able C++ implementation with
no floating point anywhere in the render path. Its job is to be the definition of correct, not
to be fast.

Duo — the committed golden frame 512 × 240 displayed · shown at 3×
The full Duo displayed stream: two independent 256×192 player views side by side on a 512×192 canvas, plus the 48 border scanlines (24 above, 24 below) that centre it in the 240 active lines. Both views draw the same world with different cameras. Visible: the 360° sky drum, a heightfield island deformed by a terrain field program, a crack-ring scar stamped into the persistent surface sheet, a screen-space marker per view, and additively-blended particle sprites.
This is not a picture chosen to look good — it is the exact frame the
repository’s render_golden test pins by CRC. The two hashes below are the
committed constants in that test; this render reproduces both, which is what makes the
image evidence rather than decoration.
mode=DUO 512x240 displayed (2 x 256x192 views + 48 black border lines) status=0 commands=14 resource_misses=0 canvas_crc32c=0xB56D4F2A displayed_crc32c=0x8F17C2F6 audio_events=2 terrain_velocity_samples=81

Sky — the drum, cap, clouds and sun 384 × 240 (Z60) · shown at 3×
A dusk sky set rendered on its own. The backdrop is a real 360° drum: a 48-column cylinder of two vertical gradient bands, closed by a 16-triangle zenith cap above and a 10240 × 10240 under-plane below, with a scrolling cloud sheet and an additively-blended sun quad over the top. 1,686 primitives, emitted once and rendered per view.
The faint vertical seams are honest: at this phase the band pass is double-sided and does not depth-test, so the far wall of the drum shows through the near one. The inside-facing winding is already recorded in the emitter for the RTL freeze — this is a known software-preview deviation, not a rendering accident.
mode=Z60 384x240 single view, DrawSky only status=0 canvas_crc32c=0x40766771 sky primitives emitted=1686 (1536 drum bands + 16 zenith cap + 2 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 whole terrain story in one frame. A 25 × 25 authored heightfield
patch is the island. Over it runs crater_ring, a real compiled earth field
program, whose height output is added to every column inside its footprint — that is the
ring lifted out of the hillside, computed live rather than authored. A
SurfaceStamp then writes a crack ring into the patch’s persistent 64 × 64
surface sheet, which darkens the shading where the ground was hurt.
The scar outlives the frame that made it: the sheet is per-patch and persistent, so a later frame with no stamp command renders identically. That is the “terrain records battle damage” claim, made mechanical.
mode=Z60 384x240 single view status=0 commands=11 resource_misses=0 canvas_crc32c=0x8438ECA7 patch=25x25 heightfield, +-12 m envelope, 8 m peak TerrainField program=crater_ring hash=0x484ADD8D (28 instrs) params: centre (0,0) r_in 3 m r_out 7 m amplitude 4.0 m; tick 19 of 100 -> phase 0.190 SurfaceStamp: crack ring tag 1, radius 7 m, ring width 4 m, strength 0xC000 terrain_velocity_samples=625

crater_ring — a field program’s height lane 256 × 256 data map · shown at 3×
This one is a data visualisation, not a rendered scene.
It is the crater_ring earth program’s height output lane, evaluated
by the field interpreter on a regular grid across its footprint and false-coloured on a
linear ramp from zero (dark) to the maximum (bright). No camera, no lighting, no
perspective — just the number the program returns at each point.
Field programs are compiled bytecode, not shaders: a builder in
TypeScript emits a .zprog image, the image is hashed and CRC’d, and exactly
one interpreter evaluates it — the same one the renderer calls when it deforms terrain.
The C++ and TypeScript interpreters are held byte-identical by differential tests.
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
Native-resolution copies of all four images are published alongside the scaled
ones under renders/native/, in case you want the untouched pixels.
03 — STATUSWhere the project actually is
Every number below was read out of the repository. Nothing here is rounded up, and the zeroes are as load-bearing as the rest.
The maturity ladder
Every block advances one rung at a time, and each advance must be justified by pinned evidence that a validator checks against git history. The ladder has seven rungs. The project stands on rung four, for six blocks out of eighty-seven.
Of the 87 blocks, 72 are FPGA/RTL blocks and 15 are software. The six at
RTL_VERIFIED are the memory and I/O spine — the VRAM arbiter, the HPS bridge, the
memory guard, the input snapshot and rumble paths, and the audio FIFO — verified in simulation
against their C++ references, with formal properties where the property is expressible.
Six further blocks carry blocked_on: hardware
(SYS.PLL, SYS.RESET, SYS.CDC, MEM.SDRAM,
SW.TOOLS.REPORT, SW.TOOLS.BOARDPROBE). The ledger validator refuses
to advance them past SPECIFIED no matter what evidence is offered, until the
hardware lane opens. That is a mechanically enforced honesty rule, not a convention.
Tests
| Suite | Count | What it covers |
|---|---|---|
| ctest, total registered | 57 | the whole C++/RTL lane |
· labelled fast | 47 | the gate every change must pass |
· labelled nightly | 43 | long randomized soaks |
· labelled lint | 12 | RTL and package lint gates |
· labelled formal | 6 | bounded model checking (skips cleanly without a solver) |
| TypeScript test cases | 115 | 12 files across 4 npm workspaces |
SystemVerilog files under fpga/rtl | 12 | 9 modules + 3 packages |
| Code, excluding generated output | ~38,600 | lines of C++, TypeScript and SystemVerilog |
Phases
The charter defines fourteen phases, 0 through 13, each with a hard acceptance gate. One phase has a written gate report. Substantial merged work exists beyond it — memory, input, audio, the Form language frontend, the whole software renderer these images come from — but no gate report covers it, so it is not claimed as gated.
The one outstanding Phase-1 gate is “one tiny Form program parses, type-checks and lowers to deterministic C++”, deliberately deferred to Phase 3 by the build plan. The report that records this says so itself, in writing.
First synthesis
Synthesis has been run exactly once, and not on the console.
| Metric | Result |
|---|---|
| Toolchain | Quartus Prime 17.0.2 Lite |
| Device (provisional) | Cyclone V 5CSEBA6U23I7 |
| ALMs | ≈ 51 |
| Registers | 96 |
| Pins | 92 |
| DSP blocks | 0 |
| M10K blocks | 0 |
| Errors | 0 |
Three caveats, all of which matter more than the numbers. The design under synthesis is the
ABI probe — a 52-line streaming CRC-32C engine over the generated ABI package
— not the console. The part is provisional: the real device
ordering code is Phase-0 board truth and is not yet known. And the numbers live in a commit
message rather than a parsed report, because the report parser is deliberately untested until
it has a real Quartus fitter report to eat. Consequently no block records a measured resource
figure, and no block 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. This is that list.
- Board truth. SDRAM memtest, real timing constants, sequential and strided
bandwidth, burst latency versus length, clock-tree stability, input latency. The file
reports/board_truth.jsondoes not exist — by design. No probe result has ever been simulated or assumed, so the SDRAM timing constants remain explicitly cycle-approximate. - The MiSTer framework. The upstream
sys/directory is not vendored yet, which blocks the PLL, reset and clock-domain-crossing blocks. - Synthesis and timing closure. Fitter reports, resource figures per block, and any claim that timing closes with margin.
- The top three maturity rungs.
SYNTHESIZED,INTEGRATEDandHARDWARE_PROVENare unreachable for all 72 RTL blocks until the hardware lane opens. - The soak tests. The eight-hour simultaneous video/input/audio/memory stress and the twenty-four-hour memory stress. Not runnable, and explicitly not simulated.
And what simply is not built yet
- The Wound Lab demo does not exist. The charter specifies it in detail — one floating island, two player wizards, two 256×192 cameras, three creature species, twelve units per player, and four spells (Raise, Break, Wave, Shatter). None of it is implemented; the directory holds a placeholder file and nothing else. The marker sigils in the renders above are 8×8 test patterns standing in for wizards, not wizards.
- The RTL rasterizer is not started. All five raster blocks — tile store,
edge walker, early-Z, fragment and resolve — are contract-only at
SPECIFIED, and the raster RTL directory is empty. The rasterizer that produced every image on this page is the software one, in C++. That is Phase 4 and 5 work. - No geometry frontend, no creature system, no particle RTL, 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 RTL is written: an executable reference implementation, directed tests, randomized differential tests against that reference, and formal properties where a property can be stated. A block advances one rung at a time and each advance is pinned to evidence a validator re-checks against git history. Six blocks are blocked on hardware and no amount of evidence can move them — the tool enforces it.
The command ABI is generated from a single interface definition into C++, TypeScript and SystemVerilog, and the three are held byte-identical by a conformance test that fails on any drift. The field-program interpreter exists twice, in C++ and TypeScript, and a fuzz-parity test compares them on generated corpora. Frame packets are sealed, validated fail-safe, and replayed; canvases are pinned by CRC-32C. There are no floating-point operations anywhere in the render path — the software renderer is integer-only precisely so that hardware can match it exactly rather than approximately.
The part that is easy to get wrong
Differential testing catches disagreement, not shared error. Adversarial review rounds — reading the code against the frozen specification, hunting for defects the suite cannot see — have found real bugs while the entire test suite was green:
A 216 amplification in the spline operation. Both
interpreters computed the final Catmull-Rom term as v · 215 instead
of v / 2. Every non-knot input saturated to the maximum representable value.
Spline knots evaluate with v = 0, which hid it — and the C++/TypeScript
differential test compared two implementations that shared the identical mistake, so it
could not possibly have caught it.
A sky-geometry collapse that made the sky invisible. The drum column angle was computed with a shift that is zero for every column, so all 48 columns shared one angle. All 1,536 band triangles degenerated to vertical lines and the rasterizer dropped every one of them at the zero-area check. The 360° skybox silently did not exist, while the layer-census, UV and scroll tests all stayed green — they counted primitives and checked texture coordinates without ever asking whether a pixel had been drawn. The fix came with an assertion that pins the geometry itself: zero band pixels before, 371 after.
An invisible sun, in the same review. A hand-rolled fixed-point-to-byte narrowing produced 256, which truncates to 0 in a byte — so for any sun energy above one third, the additive quad contributed exactly nothing. The commit message notes this is the same defect class as an earlier finding, reintroduced by writing a second implementation of a frozen conversion instead of calling the one that already existed.
Also caught: a fill rule that dropped every shared-edge pixel on both sides of a seam, a sine table read one entry past its end, and world-space markers that grew with distance instead of shrinking. Each fix ships with a test that fails if the bug is reintroduced.
The lesson the project keeps re-learning, and keeps writing down: a green suite is evidence about the tests, not about the machine. Counting primitives is not the same as checking that a pixel was written.