Replicator software RSW-1 — a native macOS slicer whose output is boundary field states, and whose viewport is the RH-1 build volume with the whole machine running on the GPU: scanning real objects, and growing new ones out of feedstock.
.pattern file.
Every pixel of that is a simulation today and a hardware backend later, with
no rewrite in between.
Part 0 — What changed from v0.2, and why
| v0.2 (2026-07-21) | v0.3 (this document) | why | |
|---|---|---|---|
| platform | Browser app; React + TS + Tailwind; WebGPU/WGSL; handed to a Lovable builder session | Native macOS app. Swift 6 + SwiftUI + Metal, built with
SwiftPM. Single window, single process, plus a fieldc CLI
that links the same core |
The matter layer is the product, and it is not a browser workload. Four GPU tiers must share buffers with the renderer with zero readback and no 4 GB / workgroup / storage-buffer ceilings; the particle and occupancy state must live in unified memory beside the field. Native also buys the one thing v0.2 had no answer for — a headless screenshot path (§16) |
| geometry | The ideal KOSMOS sphere (Rc = 200 mm, Goldberg screen at 150 mm). "RH-1 cylindrical geometry is a later config preset, not v0" | The RH-1 cylinder is the default preset and the product surface.
The sphere survives as sphere-v0, a validation-only preset
whose sole job is to keep the analytic Mie gate runnable |
A slicer's viewport is its build volume; you cannot ship a slicer for a cylinder that shows a sphere. Keeping the sphere as a gate fixture costs one preset and preserves the acceptance discipline — no gate is weakened, one is relocated |
| scope | "v0 is the converter": import → emulated scan →
.pattern. Gor'kov particle layer deferred to M6+ |
The matter layer is in scope from M4. Radiation force, particle transport, melt, solidification, and a growing occupancy volume are first-class | Because that is the request, and because the machine's own doctrine demands it: KOSMOS §8 mandates a twin of "acoustic FEM + thermal + particle dynamics" so the machine is "sized, not guessed," and every force, pressure, trap-stiffness and weld-power number in the corpus is deferred to "a number the digital twin pins." This app is where that debt gets paid |
lpoh/ or papers/ — not one Pa, not
one mN. The only propagator ever written down is the discrete
Kirchhoff–Helmholtz monopole sum in
lpoh/sims/S3_e_screen_renders_bulk.py, explicitly labelled "a
stated convention, not a claim." What does exist is ~2,400 lines of
real algorithm in the v0.2 web build (resonance-weaver): FDTD in
WGSL, matrix pencil, Goldberg tessellation, voxelizer, Mie, STL import.
Those are the port source; everything in §§10–13 below
is new work. Say so in commit messages; do not let the spec's
confidence read as existing capability.
Part I — Concepts
A 3D-printer slicer converts geometry into toolpaths because its tool is a point. The Replicator's tool is the entire boundary field, so this compiler converts geometry into time-sequences of boundary drive states — and, in reverse, converts measured boundary responses into stored object patterns. One architectural law governs everything:
The emulator API is the hardware API. All physics targets an abstract gated machine; whether frames are consumed by the GPU wave emulator or by physical electronics is a backend swap. The product is real software from day one: a converter and simulator now, the control stack later, no rewrite.
The unfamiliar part is that a slicer for this machine must simulate its own output to be useful at all. A FDM slicer can emit G-code without modelling plastic, because a nozzle's effect is local and obvious. A boundary field's effect is global and non-obvious — you cannot look at 24 complex drive amplitudes and know what shape they mold. The emulator is not a preview feature bolted onto the compiler; it is the compiler's error function. That is why the four solver tiers of §§10–13 carry as much of this document as the UI does.
RH-1 is a cylinder because its work happens between two circular boundary plates. The mechanical paper models it as eight active faces of a would-be shell — between the "6" and "12" rungs of the boundary ladder:
| surface | n | carrier | role |
|---|---|---|---|
| circular caps (top & bottom) | 2 | EM microwave | toroidal-pulse launch + super-resolution sensing; each holds the Ø80 bore, the bore winding, and the optical sightline — the axis |
| back-wall phononic panels | 6 | acoustic | force and momentum: holds, moves, sculpts matter — the only channel with real force-per-watt — the equator |
| front glass (rotating door) | 1 | — | the one uncommanded face; passive, but its rotation adds aperture diversity to the scan |
| base bay | 1 | — | generation only; no field elements |
The mnemonic the hardware spec fixes, and which the UI should teach without a tutorial: sound is the muscle, microwave is the ears and the axial drive, light is the fingertip and the eyes, the bore winding is the experiment.
Above the physical surfaces sits the abstract machine the compiler actually targets. At any frequency f, machine–object interaction is completely described by the scattering matrix S(f): drive vector a in (each component = amplitude and phase at one gate — a chord), response b = S(f)·a out. For the canonical 12-gate abstraction, reciprocity leaves 78 independent complex entries. Everything the machine can learn about an object through its gates is S(f).
Three special drive states give physical intuition and, later, scan strategy: zeros of S — drives the object completely absorbs (coherent perfect absorption; the field "hugs" the object and nothing echoes); eigenvectors of S — patterns scattered back into themselves, changed only in phase (characteristic modes); poles of S — the object's ring-down resonances, its acoustic fingerprint.
Causality makes S(f) an analytic function of frequency, hence determined by its discrete resonances. The compressed, physical, machine-native representation of an object is therefore the chord list:
chord[k] = ( complex frequency p_k , port-vector r_k ) # where the object rings, and the gate pattern that # addresses that ringing. ~100 bytes per chord.
Properties: resolution = number of chords stored (ordered
by contribution; progressive streaming — objects sharpen as chords
arrive); .pattern (scan output) and .fcode (build
input) share this alphabet — the scanner measures
chords, the builder plays them; and the format stores what triangles cannot:
response = geometry + material + interior in one object. Known caveat, handled:
bare eigenfrequency lists have rare "isospectral drum" ambiguities;
port-vectors add the spatial information that breaks them.
The unifying operational picture: scan is call-and-response; build is call-and-response run until the response matches.
.pattern is its compressed
form.Two consequences the software must reflect. The build verifies itself continuously — listening is mW-class and always on, so every instant of fabrication is also a scan, and the UI should never present "build" and "inspect" as disjoint modes with a wall between them. And feedback beats open-loop fidelity: the machine needs to recognize the target ring, not to possess a perfect forward model. That is the luthier's method (tap-tuning plates to target tones) and industrial resonant-ultrasound QC, made exact.
Throughput = bandwidth, with one hard limit: a resonance of quality factor Q at frequency f takes ~Q/f to ring up or resolve — no electronics outruns the ring. The win is parallelism across the band. Acoustic ~10⁵/s across a 100 kHz band; EM ~10⁹⁺/s across GHz bands. Hence the loop's natural role split: acoustic builds, EM verifies — matter-moving chords are acoustic and slow; the EM octaves listen at ~10⁴× the rate.
This is the single most important architectural decision in the document. The naive design — one FDTD, used for everything — fails on arithmetic. FDTD in this chamber at 40 kHz is ~8 M cells and ~1 µs timesteps; a single 40 ms capture is ~30k steps, tens of seconds of wall clock. A viewport that updates when you drag a slider needs milliseconds. Those are not the same program.
| tier | model | cost | used for |
|---|---|---|---|
| T0 — propagator | Rayleigh–Sommerfeld / angular-spectrum monopole sum over surface elements. A dense linear operator H: drive → complex field | ~ms | the interactive viewport; the forward model inside the inverse solver; trap placement; everything you touch with a slider |
| T0.5 — BEM (added v0.4) | Boundary-element scattering off the workpiece. For a rigid scatterer in air only the surface is unknown, so the problem is orders smaller than a volume solve | ~0.1–2 s | The missing middle. T0 ignores scattering; T1 is too slow to iterate. T0.5 is scattering-aware and still interactive-ish — it is what the compile loop should actually run against |
| T1 — FDTD | Staggered-grid linear acoustic FDTD with a sponge layer. Scattering, transients, ring-down, material interiors | ~10–60 s/run | scan runs, S(f) assembly, and the truth against which T0 and T0.5 are validated (gate G5). Time-domain is mandatory here: the matrix pencil eats ring-down, which a frequency-domain solver cannot produce |
| T1f — CBS (added v0.4, optional) | Convergent Born Series in the frequency domain — matrix-free, FFT-based | ~1–4 s | Steady state at one frequency does not need 3,500 timesteps. Measured on the target machine: a 200³ complex FFT pair is 34.6 ms, so 30 iterations ≈ 1.0 s — 13–45× faster than T1, converged and exact. Caveat: CBS convergence degrades with impedance contrast, and rigid-in-air (~10³) is the worst case — which is exactly why T0.5 BEM is listed above it |
| T2 — matter | Gor'kov radiation potential + drag + gravity; GPU particle integration | ~ms/frame | levitation, traps, droplet transport, the build's visible motion |
| T3 — consolidation | Lumped-capacity thermal per particle; melt/wet/latch; occupancy grid; surface extraction | ~ms/frame | the object appearing; build progress; inspect-vs-target diff |
T0 and T1 are the same physics at different fidelity, and they are wired together by an acceptance gate rather than by faith: G5 requires T0's field to match T1's steady state inside the build volume to better than 5% relative L2. When that gate holds, the interactive tier is trustworthy; when it breaks, the UI is lying and the build fails loudly.
Part II — The end state
Written as a walkthrough, because that is the specification most likely to be read correctly. Nothing below is aspirational framing; each numbered step maps to a milestone in §20.
.fcode file — the artifact this whole
program exists to produce..pattern file. Chords stream in and the reconstruction
sharpens as they arrive.MachineBackend protocol
has two implementations: Emulated (the four tiers) and
Serial (frames to an RP2350 over USB). The same
.fcode drives both. On the day hardware exists, the app does not
change — a picker in the machine panel changes.fieldc shot --contact-sheet renders every screen of the
application, both themes, into a single labelled PNG. That is how a session
— human or agent — checks the whole UI in one look. See Part IV;
this is not a nice-to-have.Part III — Build specification
These were verified on the target machine (Apple M5, 10 GPU cores, macOS 26, Swift 6.2.3, Command Line Tools only — no Xcode installed) before being written down. They are rulings, not preferences.
| ruling | decision | verified |
|---|---|---|
| platform | macOS 14+. Swift 6 + SwiftUI + Metal. Built with SwiftPM
(swift build), no .xcodeproj |
SwiftUI / Metal / MetalKit / ModelIO all import and link under the
CommandLineTools SDK. A SwiftUI App compiles with
swiftc -parse-as-library |
| shaders | Written as .metal source, shipped as package
resources, compiled at runtime via
device.makeLibrary(source:options:) |
Confirmed working with no Xcode present (the offline metal
compiler is unavailable; the runtime compiler is part of the OS). Two
bonuses fall out: no build-system coupling, and
hot-reloadable kernels — edit a
.metal file, hit reload, see the field change |
| testing (corrected v0.6) | Not XCTest. A ~60-line harness inside
FieldCore/Testing, run via fieldc test |
XCTest ships with Xcode, which is not installed, so
swift test fails with no such module 'XCTest'.
Requiring a 10 GB Xcode install to run the suite would contradict
the zero-dependency ruling. Verified consequence: the suite runs
anywhere Swift does, including bare CI containers. An earlier draft of
this spec assumed Tests/FieldCoreTests XCTest targets;
that was wrong for this toolchain |
| dependencies | Zero external packages in the shipped app. No ML
framework, no numeric library, no 3D engine. SVD via Accelerate
(zgesvd) or one-sided Jacobi, FFT hand-rolled, mesh
loading via ModelIO. Two sanctioned code liftings,
both permissive and both attributed: Inviwo (BSD-2)
for the volume-raycast shaders, and Deepwave (MIT) as
the reference to port PML + adjoint from |
Everything needed is in Foundation / simd / Accelerate / Metal /
SwiftUI. Zero runtime deps means swift build works
offline and the harness has nothing to mock. Writing a raycaster or a
PML from scratch when a BSD-2/MIT reference exists is not
craftsmanship, it is a schedule risk |
| the twin (revised v0.4) | Do not hand-roll it. Use
AcousTools (MIT, pip install acoustools,
v1.1.2 2026-06-05, UCL, actively maintained) as the primary reference
implementation, and k-wave-python (LGPL-3, native
arm64) as the independent second oracle |
AcousTools is PyTorch-tensor-native and implements exactly this architecture: a free-field piston/Rayleigh propagator, a BEM scattering propagator, analytic Gor'kov gradients, and explicit propagation-matrix caching for static geometry. It is T0, T0.5 and T2 with the inverse problem attached, already published and validated. A twin I write myself is a worse oracle than one built by the group that builds these machines. LGPL note: link k-wave-python, do not transcribe it |
| MLX? | Not in the app. MLX/Python remains the offline validation twin only (§19) | The in-app hot loop is a memory-bound stencil, a scatter-gather particle push, and a dense complex matvec — three things hand-written Metal does better than any tensor framework, and which must share buffers with the render pass with no readback. The one place a tensor framework would earn its keep is the inverse solver's autodiff, and that need vanishes because T0 is linear: its adjoint is HH, exact and free (§14) |
| viewport | One MTKView, one renderer. Field, particles, surfaces and
machine geometry are drawn from the same buffers the compute
passes write. No readback anywhere in the display path |
This is the reason the whole program is native and single-process |
| persistence | Project files, .pattern and .fcode as JSON
headers with binary Float32 payloads in a sidecar or appended blob.
Documented, versioned, round-trip tested |
— |
| the web build | resonance-weaver becomes port source, then
reference. Its matrixPencil.ts,
goldberg.ts, voxelizer.ts,
mie.ts, stlImport.ts,
fdtd.wgsl.ts port to Swift/Metal nearly line-for-line.
After M7 it is archived, not maintained in parallel |
~2,400 lines of physics/scan/geom/verify/io already written and partially gated |
field-compiler/
Package.swift
Sources/
FieldCore/ # PURE. No AppKit, no SwiftUI, no Metal.
Geometry/ machinePreset.swift rh1.swift sphereV0.swift
goldberg.swift spiralGrating.swift scene.swift
Model/ schemas.swift materials.swift fcode.swift pattern.swift
Solvers/ propagator.swift(protocol) fdtd.swift(protocol)
gorkov.swift thermal.swift inverse.swift
referenceCPU/ # small, slow, obviously-correct twins
Scan/ scanRunner.swift spectra.swift matrixPencil.swift
chords.swift calibration.swift
Verify/ mie.swift sfError.swift energy.swift gates.swift
IO/ stl.swift project.swift receipts.swift
FieldGPU/ # Metal. Implements the Solver protocols.
device.swift bufferPool.swift kernelLibrary.swift
propagatorGPU.swift fdtdGPU.swift particlesGPU.swift occupancyGPU.swift
Renderer/ renderer.swift passes/ offscreen.swift
FieldUI/ # SwiftUI. Every view is a pure function of a value struct.
Shell/ appShell.swift toolbar.swift transportBar.swift
Viewport/ viewportView.swift overlayChips.swift cameraController.swift
Panels/ objectList.swift inspector.swift chordList.swift
gateReport.swift logDrawer.swift
Scenes/ sceneRegistry.swift # THE screenshot registry (§16)
Theme/ tokens.swift dark.swift light.swift
FieldCompilerApp/ # @main. Target ~200 lines. Wires Core+GPU+UI. No logic.
fieldc/ # CLI. Same core. sim|scan|compile|build|shot|film|gate|twin-export
Shaders/ # .metal sources, shipped as resources, compiled at runtime
propagator.metal fdtd.metal particles.metal occupancy.metal render.metal
Tests/
FieldCoreTests/ # unit + physics gates
FieldUITests/ # snapshot tests
__snapshots__/ # golden PNGs, committed
Scripts/
twin/ # MLX/Python validation twin (§19)
contact_sheet.py
Receipts/ # every gate run writes here; committed
Cylindrical frame: z along the axis, origin at the lower plate face, z increasing upward. All mm. These are the canonical numbers from the hardware spec §3/§8 and the mechanical build sheet §10; they are the same numbers the Blender renders use, and the app should agree with those renders pixel-for-pixel in silhouette.
| element | geometry |
|---|---|
| build volume | cylinder, Ø280 × 300 — r ≤ 140, z ∈ [0, 300]. This is the viewport's subject. |
| caps ×2 | Ø300 discs (r ≤ 150) with Ø80 bores (r ≥ 40) at z = 0 and z = 300; ~20 stack thickness; mirror-imaged (opposite handedness). Lower face = base deck; upper face-down in the crown |
| spiral grating | 12 arms at 30° increments; r(φ) = r₀·ecotα·φ; one arm sweeps r = 45 → 145 over 0.75 turn (⇒ cotα ≈ 0.2483, α ≈ 76.0°); slot widens 2.8 → 6.0 outward. Slots are voids in the conductor, not traces — get this right in the render or the caps look wrong |
| bore winding | 44 turns toroidal at the bore rim. Inert in v1 (§7) |
| panels ×6 | 42.5 wide × 300 tall × 5 thick, at r = 155, centred in the 15.7° clear gaps of the 30°-pitch column ring. Face: graded hex phononic screen standing 0.9 proud, cells large at the bottom (low f) grading small at the top (high f) — rainbow trapping. 4 PMN-PT drivers behind each ⇒ 24 acoustic channels |
| columns ×7 | 40 tangential × 20 radial × 300 tall, at r = 160 (spanning 150–170), 30° spacing across the rear. Light strip on the inner face — this is the power-up progress indicator. All 7 carry RX strips; 3 carry service bundles |
| glass | two 4 mm low-iron half-cylinders: rear Ø344 fixed, front Ø364 rotating outside it, detents at 0° (sealed) and 180° (open). Door state is a model property: open ⇒ drive power gated to mW listening (the Hall interlock), which the UI must enforce, not merely depict |
| body | Ø360 × 560 overall (Ø380 over the front glass); base 180 below the deck, build 300, crown 80. Mass ≈ 14 kg |
Other presets. sphere-v0: Rc =
200 mm chamber, Goldberg screen at Rs = 150 mm, 12 gates
at icosahedral vertex directions (class-I geodesic, n=4, take the dual; the 12
cells dual to the original icosahedral vertices are the pentagons = the
gates). Exists only so gate G4 (analytic Mie) stays runnable —
a sphere is where closed-form truth lives. table-1plate: single
cap, half-space, Rayleigh–Sommerfeld; the cheapest sanity preset.
lpoh-icosa12: 12 transducers on the icosahedral directions
(±1,±φ,0 and cyclic, normalized), ~50 mm gap — so
the app can also drive the bench hardware that actually exists.
Discretize each active surface into elements of area ≤ (λ/2)². Each element e at xe with outward normal ne, area Ae, and complex drive ue contributes a Rayleigh–Sommerfeld (baffled-piston, far-from-element) term to the complex pressure at field point x:
p(x) = Σ_e u_e · H_e(x),
H_e(x) = (i ρ₀ c₀ k A_e / 2π) · D_e(θ) · e^{ i k r } / r ,
r = |x − x_e| , cosθ = (x − x_e)·n_e / r ,
D_e(θ) = directivity; v1 uses the circular-piston form
2 J₁(k a sinθ) / (k a sinθ) with a = equivalent
element radius, falling back to cosθ for a ≪ λ.
Stack the He over field points and you have a dense complex matrix H of shape (Nvoxels × Nchannels), and the forward model is one matvec: p = H u. For RH-1 at 40 kHz with a 2 mm evaluation lattice over the build volume, Nvoxels ≈ 1.4 M and Nchannels = 24 → ~34 M complex MACs per evaluation: sub-millisecond on the M5, and the matrix is small enough (~270 MB at Float32 complex) to precompute and cache per (preset, frequency, lattice). That cache is what makes the viewport interactive and the inverse solver cheap.
Two exactness notes worth writing into the code as comments, because they will otherwise be rediscovered painfully. First, H is linear in u, so the adjoint needed by the inverse solver is literally HH — no autodiff, no framework, no approximation. Second, T0 knows nothing about scattering off the workpiece; it is a free-field propagator. That is exactly why G5 exists (T0 vs T1 in an empty chamber) and exactly why T1 owns the scan path (where the object is the whole point).
Velocity for Gor'kov comes from the same sum, differentiated analytically: v = −(1/iωρ₀)∇p, with ∇He in closed form. Do not finite-difference the pressure field for this — the force is a gradient of a quantity that is already a gradient, and the error compounds visibly as trap jitter.
Standard staggered-grid (Yee-style) acoustic FDTD, ported from the v0.2
WGSL kernel. Two coupled fields on the voxel grid: pressure p at cell centres,
velocity v at face centres. Per-cell material arrays rho[],
c2[] from the voxelizer.
v[f] −= dt/(rho_face · dx) · (p[cell+] − p[cell−]) // 3 components
p[c] −= rho[c]·c2[c]·dt/dx · ( Σ_faces v_out ) // divergence
stability : dt = 0.5 · dx / (c_max · √3) // CFL with margin
boundary : cylinder wall + caps rigid (v·n = 0);
16-cell sponge shell inside every wall,
p,v *= exp(−σ(d)·dt), σ ramping quadratically in
depth d — models real chamber loss and guarantees
ring-down decays (a lossless closed cavity never stops
ringing, and its S(f) is garbage)
source : soft source over the active gate's element patch,
p += A · exp(−((t−t₀)/τ)²) · sin(2π f₀ t)
record : every step, average p over each gate patch
→ portRecord[gate][step]
Sizing, with the arithmetic shown — because this is where a design either is or is not feasible, and the number should not be a surprise at M3. Domain 340³ mm bounds the panels at r=155 plus a sponge margin. At 40 kHz, λ = 8.575 mm and dx = λ/5 = 1.715 mm → 198³ ≈ 7.8 M cells; p + 3v at Float32 = ~125 MB plus material arrays. dt = 0.5 · 1.715e−3 / (343·√3) = 1.44 µs, i.e. ~17 steps per period. An axial round trip is 0.6 m / 343 = 1.75 ms → ~1,200 steps; a 30k-step capture is ~43 ms simulated, ~25 round trips — enough ring-down for the matrix pencil. At a memory-bound ~1010 cell-updates/s that is ~45 s per run, ~10 min for a 12-run scan. Show the live field while it runs; the wait is the show.
Material presets (ρ kg/m³, c m/s), carried forward from v0.2: air (1.204, 343), PLA (1240, 2220), aluminium (2700, 6320), steel (7850, 5900), glass (2500, 5640), water (998, 1481), rubber (1100, 1600).
This is the new physics, the thing the corpus has been deferring to "the digital twin," and the reason the app is worth building rather than reading about.
For a particle of radius a ≪ λ, density ρp, sound speed cp, in a time-harmonic field with local complex amplitudes p and v, the acoustic radiation potential is (Bruus 2012 form of Gor'kov 1962):
U_rad = (4/3)πa³ [ f₁ · (½)κ₀ 〈p²〉 − f₂ · (¾)ρ₀ 〈v²〉 ] κ₀ = 1/(ρ₀c₀²) (compressibility of the medium) f₁ = 1 − κ_p/κ₀ (monopole contrast) f₂ = 2(ρ_p − ρ₀) / (2ρ_p + ρ₀) (dipole contrast) 〈p²〉 = ½|p|² , 〈v²〉 = ½|v|² (time averages of the complex amplitudes) F_rad = −∇U_rad
The acoustic contrast factor Φ = f₁ + (3/2)f₂ decides the physics: Φ > 0 traps at pressure nodes (which is every solid-in-air case we care about), Φ < 0 at antinodes. Compute U on the same lattice T0 evaluates, take −∇U by central differences of the potential (which is smooth), and sample per particle with trilinear interpolation.
F_z = −(4/3) π a³ k E_ac Φ sin(2kz), Φ = f₁ + (3/2)f₂, E_ac = P₀²/(4ρ₀c₀²)An earlier draft quoted the literature's F_z = 4πa³kE_acΦsin(2kz), which is wrong here for two independent reasons that happen to compound: (i) the commonly-tabulated ΦB is the Settnes–Bruus acoustophoretic contrast factor and carries a factor 1/3 (ΦB = Φ/3); and (ii) the sign follows the pressure convention — with a sin() field the pressure node sits at z = 0, so a positive-contrast particle must be pushed toward it, which requires the leading minus. Quoting the literature form against a sin() field silently inverts the trap, turning nodes into antinodes — a bug that yields a plausible-looking levitation sim that is wrong everywhere. Verified by symbolic differentiation of the potential; both normalizations agree once matched. This is exactly the class of error the gate ladder exists to catch, and it was caught on the first run.
Particle equation of motion, integrated with velocity Verlet on the GPU:
m · dv/dt = F_rad + m&g − 6πμa(v_p − v_fluid) + F_contact
μ_air = 1.81e−5 Pa·s ; optional F_stream from acoustic streaming (off by default,
behind a flag, because it is not gated)
Trap capture is the condition |Frad| > mg, and the standing-wave case gives the closed form the gate needs: for a 1D standing wave p = P₀sin(kz), Fz = 4πa³kEacΦsin(2kz) with Eac = P₀²/(4ρ₀c₀²). That closed form is gate G7.
Per-particle lumped-capacity thermal state, driven by whichever heat stage the preset selects (the doctrine is emphatic that "the sound IS the heat" is superseded — 40 kHz absorption in a sub-mm bead cannot sinter PLA, and the "nanosecond weld" figure is dropped as having no bench basis; printing takes a separate heat source):
m c_p dT/dt = Q_stage(z) − h A (T − T_amb)
states: FEEDSTOCK → IN_TRANSIT → MOLTEN (T > T_melt)
→ WETTED (contact with a latched neighbour)
→ LATCHED (T < T_glass; position frozen into the
occupancy grid, particle retired)
Anchors to calibrate against, from the printer-concept doc: a 200 µm
PLA bead is ~5 µg and needs ~0.8 mJ to melt (~8 mW over a
100 ms transit); tin, nylon, PETG and soda-lime glass are tabulated
alongside; a 200 µm PLA droplet cools below Tg in
1–10 ms in still air. The occupancy grid is a 3D
uint8 texture at build resolution; the visible surface is
marching cubes over it, re-extracted on change with a dirty-region queue.
Build progress = latched-and-inside-target / target; overspill =
latched-and-outside-target / target. Both are shown; the second is the honest
one.
Given a target amplitude distribution |p|* over a set of control points (trap positions, or the target's surface shell), find the complex drive u. Three methods, all sharing the cached H, selectable in the inspector and raced against each other in the gate report:
| method | iteration | character |
|---|---|---|
| IBP (iterative back-propagation) | u ← HH (target phase applied to current field); normalize | trivial, fast, mediocre contrast — the honest baseline every other method must beat |
| GS-PAT | Gerchberg–Saxton on the reduced point-to-point matrix HcHHc (control points only, so the matrices are tiny), then one back-projection | the standard for multi-trap holography; milliseconds; the default |
| Diff-PAT | gradient descent (Adam) on L(u) = Σc wc(|Hu|c − |p|*c)² + λ‧‖u‖², with ∇L supplied analytically via HH | slower, best fidelity, handles amplitude constraints and per-channel power limits; the one to use when compiling a whole object rather than a handful of traps |
Hardware constraints enter as projection steps inside the loop, not as post-hoc rounding: per-channel amplitude ceiling, phase quantization (the bench anchor is ~1° at 40 kHz on an RP2350 at 150 MHz), and the spec's λ/20 per-band phase tolerance. A drive that only works at infinite precision is not a compile result; it is a bug that will reappear on hardware.
The measured calibration matrix multiplies in ahead of H: the power-up self-calibration measures every element's TX→RX transfer and stores it as that machine's identity matrix, and the mechanical paper is blunt that "without this matrix the field compile is blind." The emulator therefore simulates a calibration — with configurable per-element gain and phase error — so that the compile path exercises the same code the hardware will.
slot k → gate 2ᵏ mod 13 = 2, 4, 8, 3, 6, 12, 11, 9, 5, 10, 7, 1.
It is ~20 lines and it is the thing to race in the scan-policy comparison..pattern, with the calibration reference
recorded in the metadata.The design brief is a clean slicer, and the discipline that implies is subtractive: a person who has used Bambu Studio or PrusaSlicer should be productive without a manual, and every control that cannot justify its pixels should be in a disclosure triangle or gone. The instrument content — and there is a lot of it — earns its place by being the viewport, not by being more panels.
┌─ toolbar ──────────────────────────────────────────────────────────────┐ │ [Scan | Compile | Build | Inspect] RH-1 v PLA v [ Compile ] │ ├───────────┬────────────────────────────────────────┬───────────────────┤ │ objects │ [field][traps][matter][solid][bnd] │ inspector │ │ 240pt │ │ 300pt │ │ │ │ │ │ cup.stl │ T H E V I E W P O R T │ machine │ │ PLA │ │ carrier / band │ │ │ (cylindrical build volume, the │ field / verb │ │ │ machine around it, overlays on) │ matter │ │ │ │ solver │ │ │ │ quality │ ├───────────┴────────────────────────────────────────┴───────────────────┤ │ > || < > -----o--------- t=12.4 ms build 34% S(f) err ___ │ └────────────────────────────────────────────────────────────────────────┘
The transport bar is the slicer's layer slider, re-pointed at time: scrub
the .fcode frame timeline, step, play, loop a range. The
S(f)-error sparkline lives beside it because §4 says the build's
acceptance criterion belongs in the user's peripheral vision, not in a
dialog.
Machine geometry as described in §10, drawn to be recognizable against the Blender renders: caps as amber-lit bullseyes with the spiral face and lit bore, the six back panels as hex-tiled sheets glowing with their live boundary state, the columns as dark bars with white light strips. Build volume as a translucent ticked cylinder. Front glass rotatable, with the interlock enforced.
Everything else the viewport draws is sorted by epistemic status — not by object type, and not by aesthetics. The machine has no camera. It never sees the inside of its own chamber; it drives a boundary, listens to the echo, and reconstructs. A viewport that quietly draws the mesh we already have in memory is showing the user something the instrument will never possess. Three layers, rendered three ways:
| layer | contents | rendered as | because |
|---|---|---|---|
| KNOWN | machine chrome, build-volume cylinder, dimension ticks, the imported target mesh, the cartridge | crisp CAD — sharp, dimensioned, unambiguous | These are files and specifications; we know them exactly. Rendering them holographically would destroy slicer legibility and be dishonest in the opposite direction — pretending not to know something we do |
| SIMULATED | the field, the particles, the latched voxels | direct volumetric / instanced draw from the sim buffers | We are the simulator. This is ground truth of the model — which is not the same as ground truth |
| RECONSTRUCTED | whatever the boundary measurements actually support | holographic reconstruction from the port records (§16.4) | This is all the real machine will ever have |
Global toggle: Machine View / God View. The KNOWN layer is drawn in both; the other two swap. Machine View is the default in Scan and Inspect — the hardware spec's product staging is scanner-first, so in those modes the reconstruction is the product — and God View is the default in Compile and Build, where the user is authoring rather than measuring. The toggle is always one keystroke away, and the difference between the two views is a first-class metric with its own acceptance gate (G14), not a curiosity.
Overlays are chips along the viewport's top edge, each independently toggleable:
Camera: orbit / dolly / pan; number keys for front / top / iso / home; perspective–orthographic toggle. Empty state is the machine alone, composed well enough to screenshot.
The reconstruction ladder, cheapest first. Each rung is a separate, selectable operator over the same port records, so their outputs can be compared side by side in the same viewport:
| rung | method | cost | ship |
|---|---|---|---|
| L0 | Kirchhoff–Helmholtz / delay-and-sum backprojection. f(x) = Σg ag·eikr/(4πr) over gates g. One Metal kernel, O(Nvox×Ngates) | ms | M7, default |
| L1 | DORT / time-reversal operator. Take the SVD of S(f) directly — never form SHS, which squares the dynamic range. Rank by Gavish–Donoho (2.858·median σ when noise is unknown); each significant right-singular vector backprojects to one scatterer. Free reciprocity QC falls out: ‖K−KT‖/‖K‖ > 0.1 means a calibration fault, not a target. Read the warning below before implementing the imaging half | ms | M7 |
| L2 | Regularized linear inversion. min ‖Hm − b‖² + λR(m), with H the T0 operator and R Tikhonov or L1. Reuses the inverse solver's machinery and its free adjoint HH (§14) | s | M8, optional |
| L3 | FWI-style iterative inversion against T1 by the adjoint-state method — the honest answer to the box above | min | M10+, scheduled not promised |
Before the renderer, the information budget — because the most attractive wrong idea in this project is that a machine with this much field control can simply compute its way out of a measurement gap. It cannot, and the reason is worth stating precisely rather than as a slogan. Four kinds of diversity are available; only two of them add independent information:
| diversity | adds information? | why |
|---|---|---|
| Transmit-pattern — beamforming, coded excitation, holographic drive, any clever u | No | With N elements every transmit pattern is a linear combination of the N individual element responses. Patterned transmission cannot synthesize a measurement that was never made. It buys SNR and conditioning — real and worth having, but not new k-space |
| Frequency | Yes | Different ω probes different k-space. Genuinely new samples — and this machine is broadband by construction (octave addressing; the printed holograms steer by frequency, so every frequency is a different illumination geometry from a static aperture) |
| Position — real or virtual | Yes | A new viewpoint is a new row in the measurement operator. Crucially, a reflection off a known wall places a virtual source at the mirror-image position — position diversity with nothing moving (§18) |
| Optically-placed virtual sources — photoacoustic spot-scanning (§17.6) | Yes | A pulsed laser absorbed at a surface launches ultrasound from that spot, so the optical channel can place an acoustic source anywhere it can see — including on the workpiece. This is position diversity with no motion and no added transducers, and it is strictly better than rotating the scene. Established art: laser ultrasonics |
| Adaptive / sequential selection — "smart exploration", AI-guided scanning, next-best-measurement | No | It allocates a finite measurement budget over the information the aperture already offers. That is a policy, not a source — and it is the right place for intelligence in this system |
The fourth row deserves its own paragraph, because it is the one that pays off daily. Resolution in this format is chord count, and chord count should be spatially non-uniform: spend chords where the object rings complex, stop early where it is homogeneous, refine where the residual is high. A large single-material volume and an intricate lattice of the same bounding box do not deserve the same budget, and a scanner that gives them the same budget is either wasting minutes or missing detail. This is already latent in the design — progressive chord streaming ordered by weight (§3), the adaptive scan-policy race at M6, and call-and-response's "until it rings true" stopping criterion (§4) are all the same mechanism seen from three angles. Make it explicit: detail-adaptive chord budgeting is a first-class feature, not an optimization.
The moment a learned prior or a regularizer fills null space, the output contains plausible data rather than measured data. Two failure modes converge here and produce the identical artifact: a wrong Green's function (§16.3 above) and a confident prior both yield sharp, detailed, meaningless images. Neither announces itself.
For this machine that is not a nitpick, because of a property no ordinary
scanner has: the scan output is also the build acceptance
criterion (§4). A .pattern is not merely a picture
— it is what the builder plays and what the loop listens for. An
inferred interior therefore becomes a fabricated-from-a-guess
interior, and the error is laundered through a fabrication step that looks
like verification.
provenance: measured | inferred tag in .pattern, and
so does every reconstruction derived from it. The viewer renders the
difference — inferred content is visually distinct at a glance,
never merely footnoted. Export, receipts and the gate report all carry the
measured/inferred split. The model may propose; only the aperture
asserts.
Two consequences that follow immediately and should be built in, not retrofitted. Inferred chords are excluded by default from the build acceptance criterion — you may look at them, you may not fabricate to them, and promoting one to load-bearing is an explicit, logged user action. And the coverage channel below is the rendering half of the same commitment: provenance says where a value came from, coverage says whether the aperture could have seen it at all. Ship both.
This is the single most important rendering decision in the document. The specular gap is a null-space of the measurement operator, not an algorithm failure: a facet is measurable only if some (Tx, Rx, f) triple makes the bistatic bisector parallel to its normal. No regularizer recovers it, and no amount of compute changes that. So the renderer's job is not to hide it — it is to show it.
Carry a tri-state, borrowed from OctoMap's occupancy model, and never collapse it to one scalar:
Compute the confidence field properly rather than by eye. Two methods, in increasing order of honesty: a visibility-cone mask (per voxel, which (Tx,Rx,f) triples could see a facet at each orientation — cheap, geometric, and already enough to answer "why did that face vanish"); and a random-walk confidence map in the Karamalis sense — the probability that a random walk from a voxel reaches a transducer, a derived coverage field obtained as a Laplacian solve rather than a rendering trick. Ship the cone mask at M7; the Laplacian version is a clean upgrade.
The strongest version of the idea, and worth building because it is honest by construction: render level-crossing probability (Pöthkow) — P(the surface passes through this voxel) — instead of an isosurface. Roughly 30 lines of MSL, two fetches per sample, and with it you cannot accidentally draw a crisp lie. Related and equally cheap: display the local point-spread-function width, so a low-resolution region is physically incapable of rendering sharp detail.
Volumetric ray-march of the complex reconstruction in a Metal pass: amplitude → intensity (log-compressed); confidence → extinction, via a 2D transfer function (value, confidence) → (rgb, extinction) so that low confidence drives extinction toward zero — regions read as visibly hollow, not merely tinted. Optionally animate uncertainty (Lundström's probabilistic animation): uncertain regions boil, certain regions are steady.
over blending of many hues averages to mush; domain coloring is
inherently a 2D technique and does not lift to a volume. This was specified
wrongly in an earlier draft. The correct idioms, in order of preference:
animate Re{p·e−iωt} so that phase
becomes motion (the k-Wave idiom, and the most legible by a wide
margin); render isophase surfaces; or display ∇arg(p), which is not
cyclic and therefore composites correctly.
Isosurface extraction remains available for export, always labelled with the threshold that produced it and always accompanied by its coverage statistics.
rg16Float (Re, Im) plus
r16Float confidence, not r32Float:
32-bit float textures are not hardware-filterable below Apple9
(M3/M4), and the 16-bit pair is filterable everywhere at half the bandwidth.
512³ then costs ~537 MB — trivial in unified memory.MTLFXTemporalScaler. MetalFX
is the single biggest performance lever available, and its temporal filter is
also what makes probabilistic animation read as intentional rather than
broken.modules/opengl/glsl/utils/*, BSD-2, actively
maintained) has compositing, classification with opacity correction, gradients
and intersection; GLSL→MSL is mechanical. Lift it with attribution rather
than writing a raycaster from scratch.The research lane has run the only holographic-reconstruction experiment this project owns (S3-e, 2026-07-19), and it returned two results that constrain this renderer. Both are carried here as rules rather than as background:
Dark instrument panel by default; light theme fully supported and
equally screenshot-tested — a light theme that is not in
the golden set will rot within a week. Colour tokens centralized in
Theme/tokens.swift; no literal colours in views. Field colormaps
are perceptually uniform and identical across themes, because a physics
colormap that changes with chrome is a lie.
A survey of the mid-2026 open-source landscape was run before committing to this architecture (three parallel research passes, 2026-07-26). The conclusions are unusually clear-cut, so they are recorded as rulings rather than as options.
End-to-end neural PDE surrogates: also rejected, and this one was settled by measurement on the target machine rather than by reading:
acoustic_scattering_maze), FNO scores VRMSE 0.5062
against a plain ConvNeXt U-Net's 0.0153 — 33× worse, and
barely 2× better than predicting the mean. On high-frequency Helmholtz,
U-Net and HNO reach H¹ error 1.0 — the gradient field is
pure noise, i.e. no phase survives at all.Put the network inside the physics loop, never in place of it.
This recurred independently three times in the survey — the Born-series-structured operator that was the only thing to work on OpenBreastUS, the neural warm-start that preserves solver convergence guarantees (NOWS: an honest 25–90% runtime saving, not 1000×), and the learned Born-series preconditioner. If any learned component is ever added here, it must be of that shape: it accelerates a real solver and cannot change the answer. Everything else is deferred indefinitely.
| what | license | for |
|---|---|---|
| AcousTools (UCL) | MIT | the validation twin — T0, T0.5 and T2 with analytic Gor'kov gradients, already published and maintained |
| k-wave-python | LGPL-3 | independent second oracle; native arm64. Link, do not transcribe |
| Inviwo volume shaders | BSD-2 | the raycaster (§16.3) |
| Deepwave | MIT | reference for PML + adjoint before writing MSL |
| trellis-mac (TRELLIS.2) | MIT | the only learned model adopted at start, and only to
generate the test-fixture corpus — verified
running on MPS, ~3.3 min/mesh. Pin to
--no-texture to stay inside 32 GB and to avoid its
CC-BY-NC background-remover step |
provenance: inferred by construction (§16.3) —
a shape-completion prior is precisely the thing that fills null space with
plausible geometry, which is useful for looking at and disqualifying for
fabricating to. G17 is what keeps that boundary from eroding.SH-SAS, ESM-based-NAH, the SAS reconstruction code)
carry no licence file at all, which means all rights
reserved, not "public."
| lever | what the naive model assumes | what the machine does |
|---|---|---|
| 1. The cavity is the aperture | Rayleigh–Sommerfeld in open air; DOF ≈ channel count | RH-1 is a closed high-Q cavity with calibrated walls. Time-reversal focusing through the multipath makes the cavity the aperture, and controllable DOF per channel scale with the time–bandwidth product B·τreverb, not with channel count — published down to single-channel focusing in chaotic cavities (Draeger & Fink). This is the same multi-bounce lever ranked first for imaging in §18, applied to transmit by reciprocity, with the same honest cost (it needs the calibrated Green's function) and the same mitigation (the machine measures its own cavity; T1 has walls, T0 does not) |
| 2. The spectrum is the address bus | one frequency; a frequency-flat gate→element map | The panels' printed holograms steer by frequency — the graded hex screen radiates a different computed pattern per tone. So a chord is 24 DOF per tone with per-tone aperture patterns, not "the same 24 DOF, louder". A frequency-flat map models a plain array, not RH-1. And at the force level the tones compose: for well-separated frequencies the cross terms time-average out, so per-tone Gor'kov force fields add — co-locate the main lobes and the sidelobes land in different places and average down |
| 3. Force, not spot size | a pressure sidelobe level stands in for precision | The deliverable is force on an object, and precision has three separate axes that "spot size" conflates: (a) steering resolution of the potential minimum — continuous, set by phase resolution and servo SNR, routinely far sub-λ, and closed-loop via the 0.5λ toroidal-pulse servo; (b) trap stiffness (N/m at SI drive — blocked on the physical-amplitude fix); (c) selectivity / parallelism — where spot size and parasitic traps genuinely bite, because sidelobes grab stray powder |
Consequences now implemented. G9b stands as informational, but is explicitly scoped to monochromatic free-field T0 and is not the machine's parasitic-trap floor. The real gate is defined at the force level: G9d — parasitic-to-main trap depth ratio under an N-tone chord, per-tone Gor'kov superposition, in the walled model. Frequency-dependent gate→element patterns (the rainbow map) are in the machine model as a stub table, to be replaced by the surface hologram Z(r) once the surface compiler exists (§17). And the 24-vs-512 comparison is rerun broadband, with walls, at equal time–bandwidth before any channel-count finding ships.
The general rule this is an instance of, worth stating once: a null measured on a model that assumes away the mechanism under test indicts the model, not the hardware. The research lane already carries this as an anti-pessimism guardrail; it applies to this app's own outputs too.
(Note 2026-08-01: the canonical home of the cross-carrier crossings is now The Two Carriers §6b; this section remains the software-facing copy until the next compiler revision.)
The transduction note maps phonon↔photon crossings at the element level — piezo, PMUTs, sonoluminescence, the polaritonic roadmap row. What that leaves unexploited is the crossing in the build volume itself: the two carriers interacting in flight, in the air and in the melt. Three verbs, each with measured art behind it, and all three are just the holographic-synthesis equation (§17) with one carrier as reference wave and the other as recording medium.
155 dB -> p = 1,125 Pa dn = 2.3e-6 1.38 optical waves over 300 mm theta 1.6e-4 rad 165 dB -> p = 3,557 Pa dn = 7.3e-6 4.37 optical waves over 300 mm theta 5.1e-4 rad 175 dB -> p = 11,247 Pa dn = 2.3e-5 13.8 optical waves over 300 mm theta 1.6e-3 radSo Δn ~ 10⁻⁵ and "several optical wavelengths of accumulated phase" are both correct at the kPa class the muscle channel reaches.
| verb | mechanism | what it buys this program |
|---|---|---|
| 1. Sound steers light (acousto-optic) |
Sound is a density wave; density sets refractive index. The acoustic hologram is therefore also a volumetric, reprogrammable optical element at microsecond refresh. Commodity art: AOMs | In air, deflection stays ~10⁻⁴ rad — a fine-trim and modulation channel along the axial sightline, not a steering channel. Genuine beam shaping belongs to the melt (see the caveat below) |
| 2. Light reads sound (schlieren / refracto-vibrometry) |
The same Δn lets the optical channel see the acoustic field — full-field, tomographic, live. Measured art: schlieren imaging of ultrasound, laser Doppler vibrometry through air | Attacks the Green's-function problem head on. The drive field stops being something inferred from RX ports and becomes something photographed — a physically independent calibration path that does not pass through the microphone chain at all |
| 3. Light writes sound (photoacoustic) |
A pulsed or modulated laser absorbed at a surface launches ultrasound from that spot. Established NDT art: laser ultrasonics | A programmable acoustic source aperture on any optically visible surface, including the workpiece — genuinely new rows in the measurement operator, with no motion and no added transducers. (Rev 2026-07-30, ruling R2: spot-scanning is merely the delta basis of this operator, and the worst one — patterned bases carry the multiplex SNR advantage.) |
§16.3 established that only frequency and position diversity add independent information. Photoacoustic excitation supplies position diversity from a channel already in the architecture — and per rev 2026-07-30 (ruling R2) it does so holographically, not by scanning: project a structured light pattern onto the object and the photoacoustic conversion launches a shaped acoustic wavefront whose emitter geometry is the light pattern. The workpiece surface becomes a programmable phased array whose elements are optically defined. Spot-scanning ("walk a laser spot across the object") is the delta-function basis of this operator and the worst basis: patterned (Hadamard-class) bases put the full optical power budget into every measurement (MEASURED art: structured-illumination photoacoustics, single-pixel imaging). This is the no-moving-parts answer to the problem §18's turntable discussion was circling — and it is strictly better than a turntable, because the source aperture is written on the workpiece itself, at optical precision, rather than rotating the whole scene.
It also composes with the call-and-response frame exactly: tap the object at a chosen point with light, listen with the whole screen. That is the luthier's tap-tuning made literal, with the tap placeable to microns.
virtualSource kind whose position is set per-frame by the
optical channel rather than fixed by the boundary.The control/muscle principle (R1). Light addresses, measures, and calibrates; sound and induction exert force and heat; electrons only amplify at the boundary edge and carry DC power — never information. Not a preference: §17.6 bound (i) makes every cross-carrier coupling parametric, so light cannot be muscle; it is the machine's independent information plane.
The stem is one bidirectional TX/RX holographic port (R2, R2-b). Baseline addressing element: commodity SLM at the bore; roadmap: the optically-addressed metasurface SLM (Fan et al., Nat. Nanotechnol. 2026 — 756 nm pitch, 2.3×10¹² px·s⁻¹·cm⁻²), making the chain photonic end-to-end. Outbound, the stem speaks the caps' own alphabet at optical frequency: toroidal (flying-doughnut) chords, MEASURED (Zdagkas et al., Nat. Photon. 2022; Vignjevic et al., arXiv:2509.10924 — pulse shaper × polarization converter, no metasurface required), with two gifts: self-healing (imperfect synthesis corrects in flight) and isodiffraction (each frequency keeps its own radius — the frequency→radius map carried by the pulse; the on-axis null shares the bore with transport). Inbound, state tomography of returned pulses reconstructs the density matrix in space⊗polarization⊗spectrum (MEASURED: concurrences 0.80/0.91) — a generalized hologram whose purity deficit reads the object's roughness/depolarization as data. Single-aperture position self-encoding via space-time nonseparability: MEASURED at microwave (Wang et al. 2024, cm-class). OPEN: anapole spectroscopy; real-time (vs projection-scanned) tomography. The acousto-optic vernier stands: 5.1×10⁻⁴ rad at 165 dB ≈ 0.15 mm trim over the axial sightline.
Topological registers (R13). Drive and readback states carry integer skyrmion-number labels on both carriers, robust through the chamber's reverberant chaos — robust-extraction toolkit MEASURED and published (Peters et al., Adv. Photonics 8:023001, 2026; explicitly valid for acoustic fields); programmable-metasurface topological TX MEASURED at microwave (Chen et al., Nat. Electronics 2026).
Full rulings: REPLICATOR_CAP_STACK_RULINGS_2026-07-30.md
(R1–R13).
The specular gap is a null-space of the measurement operator: a facet is measurable only if some (Tx, Rx, f) triple makes the bistatic bisector parallel to its normal. Per §16.3, only frequency and position diversity enlarge that set. The good news is that this machine has an unusual amount of both, and its best move requires no moving parts at all.
| # | mitigation | needs motion? | status |
|---|---|---|---|
| 1 | Multi-bounce / half-skip / full-skip imaging. A reflection off a known chamber wall places a virtual source at the mirror-image position — genuine position diversity with a static aperture and a static object. RH-1 is a closed cavity with known walls, so every wall is a mirror and the machine's effective aperture is far larger than its transducer count suggests | no | deployed industrial NDT practice (Zhang / Wilcox / Drinkwater 2010). Build first. M8 |
| 2 | Frequency diversity. Broadband by construction, and the printed holograms steer by frequency — so each frequency is a different illumination geometry for free. In a high-Q cavity, reverberation multiplies this further | no | native to the design. DOF capped at ≈ Q × bandwidth — a real budget, and one the machine is built to maximize. M7 |
| 3 | Segmented ITO on the front glass. The hardware spec already carries this as a "latent upgrade to segmented addressable electrodes" — it converts the passive front ~120°, the one uncommanded face, into an active surface. This closes the aperture gap at its source rather than working around it | no | the most on-doctrine answer, and already in the spec. Model it as a preset; report what it would buy |
| 4 | The rotating glass door. RH-1 already has a moving element, and the spec already credits it with "an aperture-diversity sweep" | yes, existing | free aperture diversity from hardware that exists. M8 |
| 5 | Object rotation (turntable). Not in the plan — see the no-moving-parts ruling below | yes, new | simulated as a control only; nothing may depend on it |
The obvious objection to ranking wall multipath first is that it is more Green's-function-dependent than direct imaging, not less — you are placing virtual sources that exist only if the cavity's response is known precisely, and §16.3 names a wrong Green's function as the project's biggest technical risk. The objection would be decisive for a machine that had to assume its forward model. This one does not.
The machine measures its own cavity. The empty-chamber reference run, the per-plate TX→RX identity matrix measured at assembly and at every power-up, and the double-precision twin gates are not incidental calibration chores — they are exactly the measurements that turn wall multipath from a liability into aperture.
That is the whole argument, and it is why the ordering inverts relative to a naive reading. A machine that self-calibrates its cavity gets to spend that calibration on virtual apertures. The prerequisite is real — multi-bounce imaging must be gated behind a measured cavity Green's function, never a modelled one — but the prerequisite is already on the roadmap for independent reasons (§6 step 7, and the power-up sequence the UI animates). Rank multi-bounce first because the calibration exists, and gate it on that calibration passing.
This ruling has one real consequence and it is worth working through rather than asserting: if there is no turntable, what validates the multi-bounce reconstruction? The argument for the turntable was that a wrong Green's function produces confident, sharp, meaningless images, and only an independent view distinguishes the two. That problem does not go away when the turntable does. It needs a different answer, and there is a better one.
The standard way to validate an inverse problem without ground truth is cross-validation against measurements the reconstruction never saw. Two forms, both free, both static, and both stronger than a turntable because they test the forward model directly rather than by proxy:
Two further static checks that cost nothing: reciprocity (already G15) and inter-rung agreement — L0, L1 and L2 disagreeing beyond their expected characters is a signal that at least one is wrong. And for absolute accuracy rather than self-consistency, the right instrument is not a second view but a known reference artifact: a machined phantom whose geometry is known to metrology tolerance, placed in the chamber. Phantoms are how ultrasound and NDT establish absolute accuracy, they require no motion, and their ground truth is exact rather than merely independent — which makes them strictly better than a turntable for the job the turntable was proposed to do.
So the turntable is not retired to the bench; it is removed from the plan, and G18 is rewritten around held-out prediction. If a bench session later wants a rotating stage as an extra cross-check, nothing forbids it — but no gate, no milestone and no reconstruction path may require it.
The emulator should still model all five rows, because the M6 policy race is how we find out what each is worth. The expectation to test: rows 1–3 close the gap, row 4 adds a little for free, and row 5 — simulated but not built — tells us how much, if anything, we gave up by refusing to move.
Two further design-time rulings, both free and both easier to apply now than later. Choose the array layout by sum-coarray design (dTx = NRx·dRx, with gcd(NTx, NRx) = 1): this puts real nulls in the two-way pattern at zero runtime cost, which no amount of weighting can buy back later. And discard coprime / nested / MRA processing — those are difference-coarray results for incoherent sources, and this machine is coherent reflection-mode, which needs the sum coarray. Getting that distinction wrong is a subtle and expensive error.
Finally, a framing to keep in the code comments, because it prevents a whole class of overclaim: delay-and-sum is the adjoint, not the inverse. Coherence-factor, DMAS and similar tricks suppress how artifacts look without restoring unsampled k-space — ship them as display layers, and never feed their output into anything downstream.
holographic_memory_and_compute_plan.html). That plan rules that the
build chamber must not double as the machine's computer — a mode
count settles it: 18.5 L holds ~10⁶ acoustic modes but ~10¹⁸
optical ones, so "the build chamber is a poor computer because it is a good
replicator." Three cavities instead: the build chamber
(actuator), an optical compute toroid (processor), and an
optical phase-change store (memory).
.fcode { version, machine, calibrationRef, band,
frames: [ { t, verb: ADD|REMOVE,
drive: [ per-channel { band, re, im } ],
rxGates: [...] } ] }
.pattern {
version,
meta {
name, date, source: emulated|hardware, machine, calibrationRef,
reconstruction: { rung, greensFunction: measured|modelled },
counts: { measured, inferred } // the split, visible at the top
},
material, band,
chords: [ {
p: [re, im], r: [ per-gate complex ], weight,
provenance: "measured" | "inferred", // REQUIRED — no default
inferredBy?: "prior-id/version", // required iff inferred
coverage?: float // 0..1, aperture support
} ],
sBandRef?, meshRef?
}
Three schema rules, all load-bearing (§16.3). provenance
has no default — a reader that encounters a chord
without it must reject the file rather than assume "measured", because the
assumption is the failure. inferredBy is mandatory whenever
provenance is inferred, so a fabricated interior can
always be traced to the thing that fabricated it. And the measured/inferred
counts are duplicated in meta so the split is visible without
parsing the chord list — a reviewer, a log line, or a build gate should
not have to scan an array to learn that a third of a pattern is a guess.
protocol MachineBackend {
func describe() -> MachinePreset
func calibrate() async throws -> CalibrationMatrix
func play(_ frames: FCodeStream) async throws -> PortRecords
func listen(_ gates: [GateID], steps: Int) async throws -> PortRecords
}
// EmulatedBackend : the four tiers.
// SerialBackend : frames over USB to an RP2350. M9.
The protocol is deliberately narrow, and every UI action routes through it. If a feature cannot be expressed as calibrate / play / listen, it is a simulator feature and does not belong above the backend line.
Part IV — Testability
The request was for software you can easily test, especially by looking at screenshots. Testability of a GPU application with an animated 3D viewport is not achievable by adding tests afterwards; it is achievable only by constraining the architecture up front. These five constraints are what make the rest of Part IV possible, and none of them may be relaxed for convenience.
| law | statement | consequence |
|---|---|---|
| L1 | No logic above FieldCore. Every SwiftUI view is a pure function of a plain value struct. Views own no state and compute no physics | Any screen can be rendered headlessly from a literal fixture — which is exactly what the screenshot harness does |
| L2 | One core, two front-ends. The app and
fieldc link the same FieldCore. Anything the
UI can do, the CLI can do, through the same call |
Every behaviour is reachable from a shell, so every behaviour is scriptable, diffable, and CI-able |
| L3 | Determinism is a flag. --deterministic
pins the RNG seed, freezes the animation clock at a stated frame,
fixes window metrics, colour scheme, and device-selection order |
Screenshots stop flapping. Without this, golden-image testing is a source of noise rather than signal, and the team quietly abandons it |
| L4 | Every run writes a receipt. JSON to
Receipts/: config hash, git SHA, device name, timings,
every gate metric with its threshold and verdict |
Claims quote receipts. This is the repo's existing discipline ("every claimed number ships with its receipt — script path plus output file") applied to an app |
| L5 | No gate weakening. Thresholds change only in a commit that changes nothing else and states the reason in its message | Carried verbatim from v0.2, where it read "the builder must not weaken an acceptance gate to pass it." It was right then |
Two capture paths, composited into one image, both verified to work on this machine with no Xcode, no window server, and no screen-recording permission:
ImageRenderer at
fixed scale. Confirmed: renders a view tree to PNG from a plain command-line
binary.MTLTexture instead of a drawable, read back once to a
CGImage. Same code path as on-screen, so a screenshot cannot
diverge from what a user sees.fieldc shot --scene build-midway --theme dark --size 1440x900 --out shots/ fieldc shot --all --out shots/ # every scene × both themes fieldc shot --contact-sheet --out sheet.png fieldc shot --record # overwrite goldens; never automatic fieldc film --script demo/build.json --out frames/ # numbered PNGs of a sequence
The scene registry is the contract.
FieldUI/Scenes/sceneRegistry.swift holds an array of
(id, description, fixture). A UI state that is not in the registry
does not ship — adding a panel means adding a scene, and the pull request
that forgets is caught by a test asserting that every top-level view type
appears in at least one scene. Minimum registry at v1: empty,
imported, compiled, field-slice,
traps, build-start, build-midway,
build-complete, scan-running,
chords, gate-report, machine-lpoh,
machine-sphere, error-unsupported-gpu,
door-open-interlock.
Golden comparison. Tests/__snapshots__/<scene>.<theme>.png,
committed. Comparison is perceptual (per-pixel ΔE with a small tolerance,
plus a cap on the fraction of changed pixels) rather than bit-exact, because
GPU rasterization is not bit-stable across OS updates. On failure, write
<scene>.actual.png and a red-highlighted
<scene>.diff.png to
.build/snapshot-failures/ and name them in the assertion message.
fieldc shot --contact-sheet produces one labelled PNG grid of
every scene in both themes. It is how a reviewer — person or agent
— inspects the entire application in a single look, and it is the
artifact to attach to any "here is where the UI stands" report. Regenerate it
in CI on every push and keep the current one at docs/contact-sheet.png.
Numeric, blocking, and each one external to the emulator — analytic formulas, conservation laws, or the independent twin. Agreement with itself proves nothing; that principle is inherited from the sim lane and is non-negotiable here.
| id | gate | threshold | from |
|---|---|---|---|
| G1 | voxelizer: Ø60 mm sphere STL | voxel volume within 2% of 4πr³/3 | M1 |
| G2 | energy conservation: empty chamber, sponge off, rigid walls | total energy drift <1% over 10k steps | M3 |
| G3 | time of flight: axial pulse round trip | within 2% of 2·300 mm/c₀ | M3 |
| G4 | the Mie gate (on sphere-v0):
60 mm rigid sphere at centre |
5 lowest scattering resonances within 3% of analytic Mie | M3 |
| G5 | T0 vs T1: single 40 kHz tone, empty chamber, T0 field vs T1 steady state | relative L2 <5% inside the build volume | M3 |
| G6 | standing wave: opposed-cap drive | node spacing = λ/2 ±1%; node count = ⌊2L/λ⌋ | M2 |
| G7 | Gor'kov analytic: 1D standing wave force on a small sphere | within 2% of F_z = 4πa³kEacΦsin(2kz) | M4 |
| G8 | levitation: 200 µm PLA bead released near a trap | settles within λ/20 of the analytic node; stiffness within 5% of analytic | M4 |
| G9 | inverse solver: single focus at 5 random points | peak within 0.5 mm of target; sidelobes <−10 dB; GS-PAT and Diff-PAT both beat IBP on contrast | M5 |
| G10 | chords: S(f) rebuilt from top-K | relative L2 <10% at K=8, monotone decreasing in K | M7 |
| G11 | formats: .pattern / .fcode
export → import |
bit-exact round trip | M7 |
| G12 | the twin gate: raw port records vs the MLX double-precision twin on identical configs | relative difference <10⁻³ | M8 |
| G13 | build closure: Ø20 mm sphere, full pipeline | latched occupancy ≥95% of target; overspill ≤2% | M6 |
| G14 (v0.4) |
Machine View vs God View — scan a known synthetic object, compare the thresholded reconstruction against sim ground truth | report both raw IoU/Chamfer and the coverage-weighted version restricted to observed regions. The gap between them is the specular-gap cost and is the number this gate exists to publish. No pass/fail threshold on raw IoU — a threshold there would reward hallucination | M7 |
| G15 (v0.4) |
Reciprocity QC on every measured or simulated S(f) | ‖K − KT‖F / ‖K‖F < 0.1. Free, runs on every scan, and catches calibration faults that would otherwise be read as targets | M7 |
| G16 (v0.4) |
T0.5 vs T1 — BEM scattering off a rigid sphere against the FDTD truth | relative L2 <5% in the build volume, matching G5's bar for the free-field tier | M3 |
| G17 (v0.5) |
Provenance integrity — no inferred content may cross into fabrication unnoticed (§16.3) | every chord carries provenance; a file missing it is
rejected, not defaulted; every
inferred chord carries inferredBy;
meta.counts matches the chord list exactly; and a
.fcode compiled from a pattern containing inferred chords
fails to build unless each was explicitly promoted by
a logged user action | M7 |
| G18 (v0.6) |
Held-out prediction — the no-moving-parts validation of the cavity Green's function and of multi-bounce (§18) | reconstruct from a subset of gates and frequencies, then
predict the withheld ones: relative error on held-out
measurements <15%, and no worse than
1.5× the in-fit residual. A wrong cavity
Green's function cannot predict a measurement it was not fitted
to — this is the gate that catches a confident, sharp,
meaningless image before it reaches a .pattern. Run it on
every reconstruction, not just at M8 | M8 |
| G19 (v0.6) |
Phantom accuracy — absolute, not self-consistent | a machined reference artifact of known geometry reconstructs within the coverage-weighted G14 bound. Static, exact ground truth, no motion — this is what replaces the turntable for absolute accuracy | M8 |
G14 deserves a note, because it is the gate that encodes this document's central honesty commitment. It is deliberately not a pass/fail bar on reconstruction accuracy. Setting one would create pressure to fill unmeasured regions with plausible geometry — which is exactly the failure mode §16.3 exists to prevent, and exactly what a learned prior would do if pointed at this metric. The gate's product is a pair of numbers and the distance between them.
The validation twin lives at Scripts/twin/.
Revised in v0.4: it is no longer hand-rolled. The primary oracle is
AcousTools (MIT) — free-field propagator, BEM
scattering, and analytic Gor'kov gradients from the group that builds these
machines — with k-wave-python (LGPL-3, native arm64) as
an independent second opinion where the two disagree.
fieldc twin-export writes the config and raw port records as JSON;
make twin runs both comparisons and writes a receipt. Hand-rolled
MLX remains available for the pieces neither covers, and one measured fact
makes that easier than expected: MPS complex64 and FFT do work
on current PyTorch, and MLX's FFT primitive declares gradients — the
widely-repeated claim that Apple GPUs cannot do complex FFT is stale. What is
still missing is complex linear algebra on MPS; do those on CPU via
Accelerate.
Solvers/referenceCPU/
holds a small, slow, obviously-correct implementation of every GPU kernel.
Every kernel is tested against its reference on a tiny grid. When a Metal
kernel is wrong, this is how you find out which one, in seconds.fieldc gate --shader-dir ./Shaders can gate an edited
kernel without rebuilding the app.--trace out.jsonl logs solver state
per step: energies, residuals, particle counts, timings. Post-hoc analysis
without a debugger, and the raw material for a performance regression check.make build · make test (unit + gates +
snapshots) · make gates (physics only, writes a receipt)
· make shots (regenerate the contact sheet) ·
make twin. If a contributor has to remember a longer command than
that, the harness has failed.Part V — Milestones
| # | build | blocking gate |
|---|---|---|
| M0 | SwiftPM package; app launches to an empty dark window;
fieldc exists; snapshot harness and contact sheet wired;
CI green |
Harness first, deliberately. fieldc shot --contact-sheet
produces a PNG; one golden committed and compared |
| M1 | RH-1 scene graph, viewport, camera, overlay chrome, STL import, GPU voxelizer, object list | G1; ≥6 scenes in the sheet; silhouette matches the canonical render dimensions |
| M2 | T0 propagator + H cache; Field overlay live at ≥30 fps while dragging the slice plane | G6 |
| M3 | T1 FDTD in Metal; sponge; energy readout; runtime estimator;
sphere-v0 preset |
G2, G3, G4, G5, G16 — the hardest cluster in the project; do not proceed past a soft pass |
| M4 | T2 matter layer: Gor'kov, particle integration, Traps and Matter overlays, cartridge and bore feed | G7, G8 |
| M5 | Inverse solver (IBP / GS-PAT / Diff-PAT); target-from-STL; hardware
constraint projections; Compile emits .fcode |
G9 |
| M6 | T3 consolidation: thermal, latch, occupancy, marching cubes, ghost target, transport scrubber, build/overspill metrics | G13; fieldc film renders the full build
sequence |
| M7 | Scan mode: power-up sequence, calibration, coded runs, S(f), matrix
pencil, chord list, .pattern I/O, progressive streaming;
Machine View L0+L1, coverage channel,
provenance tagging, frequency diversity,
detail-adaptive chord budgeting |
G10, G11, G14, G15, G17 |
| M8 | Twin cross-check; receipts; light theme; gate report screen; full scene registry; performance baselines; multi-bounce virtual apertures, door-sweep diversity, and the turntable validation view (§18) | G12, G18, G19; every scene green in both themes; contact sheet published |
| M9 | MachineBackend split; SerialBackend to
RP2350; lpoh-icosa12 preset driving real bench hardware |
A recorded .fcode replays identically through
Emulated, and produces the frame log
Serial would consume, byte-for-byte |
| M10+ | On request from the supervising session: the full five-way specular-gap race (§18), segmented-ITO preset, scan-policy races (identity vs Welch–Costas vs adaptive), EM carrier preset, toroidal-pulse position dictionary, melt-sculpting mode, reclaim loop | specified when scheduled |
Part VI — Register
The emulator implements validated physics only: linear acoustics on a voxel grid, classical loss, Gor'kov radiation forces over that linear field, lumped-capacity thermal transport, and rigid-body particle motion. It contains no transmutation, no open-sector mechanism, and no free-energy accounting. The bore winding's potential-port role is OPEN in the science paper and is inert geometry here.
Its agreement with itself proves nothing — which is why every milestone carries an external gate: analytic Mie, energy conservation, the closed-form Gor'kov standing-wave force, and the independent double-precision twin. The claims ladder is unchanged from v0.2: kernel matches twin → emulator matches closed-form physics → only then a design tool.
One addition to the register, and it is the reason this app is worth building rather than reading about. The corpus repeatedly defers its hardest numbers — trap stiffness versus payload, the nonlinear-onset drive level, the focused weld-power budget at a node, loop bandwidth versus trap stiffness — to "a number the digital twin pins," and mandates a twin of "acoustic FEM + thermal + particle dynamics" so that the machine is "sized, not guessed." This application is that twin, with a viewport. Every force and pressure it emits is therefore a model number carrying the model's assumptions, not a measurement, until a bench matches it. Label them that way in the UI, in exports, and in receipts. A number that looks measured because it appeared in a nicely-rendered panel is the specific failure this register exists to prevent.
Finally, the compiler and the formats are real from day one. The
.pattern emitted by the emulated scanner is the format the
hardware scanner will fill; the .fcode compiled here is the
frame stream the electronics will consume; and the boundary-first architecture
is the control stack the machine will run.