Phase 6's contents, built ahead of it

Phase 6 is reached when a guest runs far enough to submit a command buffer. That is gated on phases 4 and 5. The contents of phase 6 - decoding shader bytecode, translating it, and turning a submission into something a backend can run - are not gated on anything, and are being built in parallel so that when the first submission arrives the loop is already fast.

This deliberately runs ahead of the dependency spine (principle 6), which is a real cost and worth stating: everything below is verified against material generated here. It cannot be wrong in any way its own tests would notice. The whole point of the ordering is that the first real submission is a check, not a beginning.

The endpoint is unchanged: framebuffer diffing, the only cheap mechanical oracle this project will ever have.

Step What State
G1 Instruction decode; every encoding family checked against a reference disassembler done - 18/18 families, differentially checked over 10 fixtures
G2 Per-opcode operand layouts solved from probes, not transcribed done - 66 opcodes, zero probes rejected
G3 SPIR-V emission, with the module checking its own identifiers done - and it has since caught a malformed new opcode before a driver saw it
G4 Translation: registers, guest memory, execution mask, comparisons, per-lane divergence done
G5 Control flow, as a dispatch loop rather than reconstructed structure done
G6 Execution on a real device, as the test oracle done - 225 tests on the shader side, a large fraction executing
G7 Instruction breadth 94% - 120/127, 8/10 fixtures complete (D554, re-derived from orbistoun-cli shaders; this row said 110/127 and 6/10, which was stale before exp was translated and staler after). Four instructions remain, in two families: VINTRP's three, which need no capture, and MIMG's image_sample, which needs the resource model
G8 Submission pipeline: packets to register writes to a shader to a running module structure done, synthetic input only; survives arbitrary and truncated streams
G9 Packet vocabulary verified against something external harness done, corpus empty - needs a capture
G10 Resource model: descriptors, buffers, images, render targets guest side done - the V# buffer-descriptor decoder (D204) and MUBUF/MTBUF-plain translation landed (worklogs 097, 101; D203/D205). The one capture-free remainder is narrow-component format conversion in MTBUF (packed UNORM/SNORM/float/etc., which typed_buffer_memory still refuses) - not yet done. The host side (descriptor base -> host memory) and images/render targets still need G11 or a capture.
G11 Graphics pipelines in the Vulkan backend; today it dispatches compute only begun - the backend builds one, and a translated fragment export reaches an attachment through it (D550, D553). Steps (a), (b) and (c) are done; step (d) - which registers configure colour buffer zero - needs a capture
G12 Framebuffer diffing the harness is built - it draws a hand-written fragment shader's colour into an attachment over a differing clear, carries a varying through interpolation, and reads every pixel back (D549, D550, D554). A translated module has been through it (D553)
G13 Subgroup fidelity, the level that would actually be used at speed done (D146) - one invocation per lane, mask by ballot; reports the subgroup width it needs
G14 Performance: collapse the single-block dispatch loop, persist the shader cache deferred until there is something to measure

What each remaining step is waiting on

G7 - instruction breadth. The unblocked work is done. What used to be listed here as "blocked on documentation" - the three division helpers - is translated and executing (D143, D144); the published reference for this generation was fetched for the encoding families and turned out to specify all three. The last instruction that needed nothing structural, v_fmac_f32, is done too.

That is no longer true of all of them. exp translates at the fragment stage for mrt0 (D553), and VINTRP needs no capture either: its operands are solved, and the attribute and channel it names are in the instruction rather than in guest register state (D554). What still needs the resource model is the typed buffer accesses (MTBUF) and image sampling (MIMG).

The oracle can now check an interpolation when one is translated: its vertex shader carries a Location 0 varying and its fragment shader reads it, so a value the three corners agree about arrives unchanged at every pixel and one they disagree about does not (D554). That had to come first, for the reason this whole section opens with.

Down to ten worklist entries and three fixtures, from thirteen and four. The untyped accesses (MUBUF) are translated, and the typed ones are decoded - operands solved for all five opcodes, and the format field measured - with only their translation left. Updated 2026-08-21.

G10 - the resource model was listed as needing a real submission. That is now only half true, and the half that changed is worth acting on.

Its guest side is documented: a buffer resource descriptor is a 128-bit value held in four consecutive scalar registers, its fields are named, and the shader-visible use of them is given bit by bit. Building that decoder unblocks MUBUF and MTBUF without a capture

That prediction held. MUBUF is translated through exactly that decoder, and MTBUF is decoded on top of it: the descriptor and the addressing are shared, and what a typed access adds is a format conversion. Its operands are solved and its format table is measured (D203); the translation is what remains, and it needs no capture either.

One caveat, checked rather than assumed: the general buffer addressing equation is a figure in the published PDF, so a text extraction drops it. Reading it means reading that page as an image, which is a step rather than a given.

Its host side - what a descriptor becomes in Vulkan - is designable but not verifiable without real data. exp and VINTRP need render targets and fragment inputs, which is G11.

G11 - the graphics pipeline, and with it exp, which blocks more shaders than anything else on the worklist and is the reason no fragment shader translates.

Two of its three obstacles are now gone, and this section said otherwise until D551 checked. The backend does build a graphics pipeline, and a fragment shader's Location 0 output does reach colour attachment zero - orbistoun-gpu-vulkan/tests/draw.rs proves it on a device. What is left of the export is (1) emitting a Fragment module rather than a compute one, and (3) the attachment configuration a capture would settle.

An export is not a memory write. exp mrt0 v0,v1,v2,v3 means this is colour zero, and the destination is a render target - a thing that exists only inside a graphics pipeline. Three things stand between here and there:

  1. The translated module has to be a fragment shader: execution model Fragment, with output variables. Every module emitted today is a compute dispatch writing a storage buffer.
  2. The backend has to build a graphics pipeline - render pass, attachments, a draw. It dispatches compute and nothing else.
  3. Something has to say which attachment mrt0 is - its format, size and address. That is register state the guest writes, and it cannot be invented (D104).

Only the third needs a capture. In the order worth doing them:

Step Needs a capture What it buys
b Render pass, a draw with a hand-written fragment shader, and read the attachment back no done (D549, D550). orbistoun_gpu_vulkan::framebuffer::draw_with clears to red, draws a hand-written fullscreen triangle whose fragment shader writes blue, and every pixel comes back blue. The two colours differ on purpose: the clear is what failure looks like
a Solve the export's operand layout by probe no already done, and this table did not say so. opcode-operands.toml carries the EXP entry solved from ten samples, and decoding `0xF8000000
c Emit Fragment modules with output variables no done (D553). orbistoun_translate::wavefront::Stage::Fragment emits the execution model, OriginUpperLeft and a Location 0 output in the entry point's interface, and skips the observation epilogue - which is what keeps a fragment module inside a device that requests no features. exp mrt0 translates into a store to it, and orbistoun-gpu-vulkan/tests/translated_export.rs draws the translated module and the hand-written one over the same clear and compares every pixel
d Which registers configure colour buffer zero yes the mapping D104 refuses to invent

(b) is done. Framebuffer diffing was described above as the only cheap mechanical oracle this project will ever have, and it was the one part of that sentence that was not built. It is now: orbistoun_gpu_vulkan::framebuffer clears an attachment, builds a graphics pipeline, draws three vertices through hand-written shaders, and reads every pixel back (D549, D550).

The shaders are hand-written on purpose - orbistoun_spirv::fullscreen_triangle_vertex_module and constant_colour_fragment_module, assembled instruction by instruction. The oracle exists to check the translator, so a shader the translator produced could not check it.

Four breaks were tried across the two steps and each landed somewhere different: dropping the clear fails the first pixel, a copy region one row short fails the last row, a draw of zero vertices leaves the clear's red everywhere, and a triangle that no longer covers the viewport fails at pixel (3, 0) while the origin still passes. A test sampling one corner would have missed two of the four.

The remaining steps (a), (c) and (d) are now all verified by it, which is what doing (b) first was for.

It is more building ahead of the spine, with the cost this section opens by stating. The difference is that a framebuffer harness is checked against itself - draw a known colour, read it back, compare - so it is a genuine oracle rather than a self-consistent guess, which is exactly what the rest of this phase is short of.

G9 is the highest-value thing a capture would buy. The register vocabulary in crates/orbistoun-gpu/data/packets.toml is transcribed and its own comment calls it the least certain thing in the crate. A raw command buffer is data that still has to be read through that table. A capture of a builder call paired with the bytes it appended - the guest's graphics layer builds buffers through library calls before submitting them - checks the table itself. That is the same move that found a wrong encoding row in G1, and it is the difference between more data and an oracle.

G10 - the resource model splits in two, and this entry used to blur them.

The shader-visible half - what a descriptor contains and how an access computes its address from one - is documented and is built. The host half - how a descriptor's base address relates to anything the host allocated - is the one that should not be designed blind: every other unknown here fails loudly, whereas a resource layout guessed wrong produces a frame that renders and is subtly incorrect, which is the failure this project is least able to detect.

Only the second half waits.

G13 - subgroup fidelity was recorded as blocked on hardware. That was wrong and is worth being precise about, because it is the second time this table has misfiled the difference between "cannot be done" and "has not been decided".

What is true: the level's straightforward design maps one guest lane to one host invocation and needs the host subgroup to be as wide as the guest wavefront - sixty-four. The GPU here is thirty-two wide, so that design cannot run. Measured, not assumed: cargo run --example subgroup-size -p orbistoun-gpu-vulkan.

What does not follow: that the level is unavailable. A variant carrying two guest lanes per invocation assembles the sixty-four-bit mask from two ballots rather than one, and runs on thirty-two-wide hardware - which is most hardware, and is what is on this machine. It would be checkable against the wavefront model by the generated-program comparison that already exists.

It is a design decision, not a hardware limitation. D098 describes the one-lane design; committing to the two-lane one is a concept the decision log does not contain, which is why it waits for a person rather than being assumed into existence.

Not blocked, and available now

This section said "nothing with real payoff" and was wrong for the third time. It is left in below with what it claimed, because the pattern is more useful than the conclusion.

What it missed on 2026-08-21, none of which needed a capture:

The common thread: each was a question nobody had asked the existing tools, not a piece of missing information. "Available now" was being read as "is there new material", when the useful question was "does what we have actually say what we think it says".

The three previous claims failed the same way and this note is the fourth attempt at the lesson, so it is worth stating flatly: before recording this section as empty, run the generators and read what they refuse. They have refused something every time.

What it said

Nothing with real payoff. This is the honest state, arrived at twice by claiming it too early and being wrong, so it is worth being precise about what was checked.

Depth on what already exists has been done: the differential oracle is a generated property rather than a handful of examples (D136, and it found a wrong opcode number on its first widened run), and the decoder and pipeline are tested against material that is not instructions at all. Both were the answer last time this table said "blocked", and neither is left.

What remains unblocked is marginal - widening a generator that has already found what it was built to find, or driving the backend seam with a recording stub that consumes nothing. Neither is worth the code.

(Resolved, and the diagnosis in this paragraph was wrong - see the entry in BACKLOG.md. It was two tests in orbistoun-abi sharing a global array across parallel threads, not address contention, and orbistoun-mem had nothing to do with it. "Passes in isolation, fails in the workspace run" was read as a signature of contention when it is equally the signature of any intra-binary race.)

On instruction breadth specifically: of the twenty-one instructions still refused, three are the division helpers (blocked on the published instruction set) and sixteen need the resource model. exp, MTBUF, MUBUF, MIMG and VINTRP are all one dependency.

(Superseded. The division helpers are done, MUBUF is translated, and MTBUF is decoded. Ten opcodes remain refused across three fixtures: MTBUF's five need translation only, and exp, VINTRP's three and MIMG's one need the graphics pipeline or a capture.)

What remains that needs nobody:

Everything else waits on a capture. That is the expected shape - a subsystem built ahead of its inputs runs out of things it can check itself, and the useful response is to stop adding to it rather than to keep going on material it generated.