Design notes — Part IX — Phase 10: Publish (steps 67–70) — in flight (`DECISIONS_9 #n`)
Opened at step 67 (the documentation), 2026-10-06. Merged into
DECISIONS.md as Part IX at the phase’s end. The spec is
PROJECT_SPEC.md §9.6 (the books) and the [ITERATE] lists of the
four Phase 6–9 specs; the roadmap is ROADMAP_5.md.
-
Every
[ITERATE]of the four specs is resolved by a decision entry or documented as a limit on the page that owns it — the table. (The roadmap’s DONE clause for step 67.) Where an item is “documented as a limit”, the page says so in a sentence a reader finds where they would look for the feature; nothing is resolved here that an earlier entry did not already resolve.Spec Item Resolution INSTRUMENT §1 the valve CC numbers DECISIONS_8 #1: CC 110/111/112 (≥ 64 pressed), the slide CC 113, the master channel — the chart’s new rowsINSTRUMENT §1 the slide: a 14-bit pair or 7-bit + smoothing #1: 7-bit, smoothed 10 ms in the engine (4.7 cents a step)INSTRUMENT §1 should pressed valves render? #9,#13–#17: the strip’s widgets and the fingering panel show it; the canvas stays pure ink; the web’s HUD is an overlay (#25) — the instruments guideINSTRUMENT §2 the partials: a column or an arc #2(params.trumpet_arc), the ring re-cut by the author’s eye#15–#17; both documented, the arc the author’s choice on the iPadINSTRUMENT §2 the lip bend’s scaling #2: one semitone per cell radius, the cell’s +x axisINSTRUMENT §4 the fretboard’s tuning table: params or fixed #6: three fixed presets (params.string_tuning), documented with their tuningsINSTRUMENT §5 the theremin’s “continuous”: a radius sentinel or a flag #7: a flags field,SUMI_CELL_CONTINUOUSINSTRUMENT §7 microtonal / Scala tunings pulled forward? NOT built — documented as a limit on the instruments guide (“Not in 2.0”) QOL §2 a panic button on the strip #12: built (the strip’s Panic pad; the desktop’s Panic beside its inputs) — the control-strip guideQOL §2 the quick-switch: a user-chosen subset #12: a subset in the order chosen, the Next pad — the control-strip and instruments guidesQOL §2 a preset-next button on the strip NOT built — documented as a limit on the presets guide QOL §3 expose the fibre angle-drift amount? NOT exposed, a constant — documented on the medium guide’s substrate table QOL §3 TIFF-16 for print workflows NOT built, PNG only — documented on the paper-and-prints guide MEDIUM (TO_PROJECT_SPEC §11) a long-exposure / strain accumulation buffer carried with its question — documented on the Anod page (“Not in 2.0”) SOUND (§12) the AIFF reader; the DS MPE map answered in Part VI ( DECISIONS_6 #14, the preset’s own map) — the sound guide and the chart’s Voxo sectionSYNTH (§13) the fold’s ×4 oversampling budget carried — documented on the cells chapter SYNTH (§13) the lattice’s coupling topology resolved in DECISIONS_7 #10(a nearest-neighbour chain) — the modal chapterSYNTH (§13) “sustained small drive into damped modes” NOT built — documented in the Suzu index’s MPE table (the bow is the press consumer where there is breath) -
The Suzu lab deploys INSIDE the marble tree, at
/marble/suzu/, built from the same tag as/marble/; the docs embed it through<Lab>, and the docs’ new demos resolve only once the marble at that address knows them. The drafts and the lab’sDOCS_ROOThad assumed/suzu/beside/marble/; the docs’ own Suzu book wants/suzu/for its chapters, and the lab is engine code (a wasm and an AudioWorklet), which the drift check forbids inside the docs build. So:pages.ymlcopies each tag’sbuild-web/suzu-disttomarble/<tag>/suzu/when the tag has one (the cache key bumped so cached trees gain it),compose-marble.mjsdoes the same for the preview fromSUZU_DIST(default../build-web/suzu-dist),web/suzu/site/lab-core.jssetsDOCS_ROOT = '../../', andcheck.mjsadmits exactly one other iframe target than a scene —<marble>suzu/<page>.html— requiring every page the web build serves (web/suzu/site/*.html, index aside) to be embedded once, as it requires every scene. The seventeen scenes (the roadmap counted sixteen: the eleven of 1.0 and the six of Phase 6) are all required now. THE TIMING, flagged for the author (the roadmap’s “build against the RC; merge with the stable tag”,DECISIONS_4 #82): the site deploys frommainand/marble/is rebuilt from the newest STABLE tag, so between this step’s merge and thev2.0.0tag the live site’s new demos and lab panels would point at a 1.0 marble that knows neither. Two ways, the author’s call: land this step with the stable tag, or letpages.ymlbuild the docs withPUBLIC_MARBLE_URL=/marble/rc/while an RC is newer than the stable (every demo then runs on the RC — a change to #82’s policy, not made here). THE AUTHOR’S CALL (2026-10-06, on seeing the book): the tag is the first v2 release candidate, cut so the testers can proofread the docs live — so the second way is BUILT:pages.ymlbuilds the docs withPUBLIC_MARBLE_URL=/marble/rc/andSITE_VERSION= the RC while an RC is newer than the stable, and with/marble/and the stable otherwise;/marble/itself stays the stable (#82’s discipline holds; only the docs’ embeds move, and the 1.0 scenes run on the RC meanwhile — the RC is a superset). The lab’sDOCS_ROOTis derived from the page’s own address (whatever precedes its/marble/segment —/marble/suzu/,/marble/rc/suzu/, a project-site base alike; two levels up when served alone). The tag must be cut AFTER this step’s commit: the workflow builds the marble tree from the tag’s own sources, and the bumped cache key then builds the tag’ssuzu-distinto/marble/rc/suzu/. The gallery’s 2.0 cards render as “coming soon” (the replay link appears only once a card has its video; a pending card says its recording will replay), and six more pending cards name the captures the author will record — the theremin, Wicki–Hayden, the trombone with the S Pen, the flute’s overblow, the Chladni vibrato, a replay across three devices — their captions placeholders, as #4’s. -
The chart gained what Phase 7–9 made it owe, verified: two output sections from the step-63 iPad logs and the sound engine’s own input section.
tests/fixtures/bytelogs/trumpet_byte_log.csv,trombone_byte_log.csvandtheremin_byte_log.csvare the step-63 lab logs (git show f2916db:docs/evidence/step63/ipad/…); the chart’s “Output · The valves (src 3)” (CC 110–112 present, the announce’s nine conditional) and “Output · The slide (src 3)” (CC 113 present, 42 messages) verify against them intools/chart_check.py— 52 checks green, the strip section’s rows unchanged. The input side: CC 110–113, CC 120/123 and CC 122 rows in the MPE section; a new “Input · the sound engine (Voxo)” section (verified by the Voxo and Suzu suites, not a log) naming every consumer — CC 74, CC 1, CC 2/11, CC 64, 120/123, 122, the bus targets 1000–1005; the CC-map row names the Phase-6 dimensions and the desktop’s 104–109. The theremin log is kept as a fixture for the next analyser (its bends and pressures are the finger section’s rows already). -
The gallery’s three new cards are PENDING with a
replayfield each — the recordings and the captions are the author’s. The roadmap names the three performances (an Anod piece, a trumpet piece, a bowed Suzu piece with the orbit trace); the cards are ingallery.jsonwithreplaypaths under/gallery/(anod-discharge,trumpet-arc,bowed-trace.sumireplay), the device, synth and modes written as PLACEHOLDERS for the author to correct when the pieces are recorded — “after I show the book”, the author’s word.Gallery.astrorenders the field as “watch it again” (<marble>?replay=<file>);check.mjschecks the field’s form and reports a missing file as pending rather than failing. The burst page’s author note is the roadmap’s text, signed ”— the author”: the name is the author’s to put. -
The settings reference is rewritten from the desktop window’s sections in their order; the citations gained the electric medium’s and the synth’s papers; the architecture book gained the replay page. The reference lists every section the desktop window has (Canvas, Layout & look, Prints, Presets, Replay, Substrate, Palette, Medium, Expression routing, the five operator sections, CC map, MIDI inputs, Sound, Suzu trace, Window, the bench, About) with each row’s params field or CC, and the tablets’ pages folded in; the 1.0 rows that still hold were kept word for word. Citations: Chladni, Taylor–Green, Aref and Ottino, Chirikov, Greene, Moser, Meiss, the viscous multipoles (Voropayev & Afanasyev, Chan & Chwang, Gallay & Wayne) with the literature-check sentence; Benade, Fletcher & Rossing, Karplus–Strong, Chamberlin, Gordon–Smith, Verlet; the Decent Sampler format’s author; the thanks with Professor Jaffer first and Ichisuke Fujioka honoured. The Suzu book names the cell the Gordon–Smith oscillator and carries the archived charts as figures (mode splitting, the rotor’s sweep, the Duffing clang and drive, the flute’s and the sax’s ramps, the trumpet’s lips), the rotor’s sweep also on the Chirikov page — one theorem, two senses.
-
Every scene carries the medium toggle, and it is permanent: the medium is set before the fresh sheet’s first drop, never switched at the end. The author, on seeing the book: “the anod toggle should exist for all the operators and it should be permanent — not start with Sumi and toggle to Anod at the end of the replay; it should start with Anod if the toggle is 1.” AS BUILT (
web/site/scenes.js, a page change, no core touch): oneMEDIUMslider (M: 0 Sumi, 1 Anod, default 0) spliced before⏱into every scene’s params, and every scene’ssetupwrapped so the medium is set first — the runner dips, the medium lands, then the clusters — so?medium=1on any scene’s embed runs the whole script as a discharge. The Anod scene defaults to 1, sets its look (palette, glow, grid) before the script and no longer switches at the end; its caption says so. The docs’ embeds keep their defaults (ink for the operators’ pages, Anod for the Anod page); the scene sweep and three captures (Anod, torsion and the spark under Anod) are in the evidence. THE AUTHOR’S SECOND LOOK: “even if Anod is selected as the medium we are still using drops and not our pseudo sparks” — the scenes calledsumi_add_dropdirectly, so under Anod they laid plain drops read as a discharge. Every STRIKE in a scene now goes throughapi.tap, the page’s binding ofsumi_gesture_tap— the medium’s strike, a drop under Sumi (bitwise what the scenes did before, the same call with the ink layer) and under Anod the charge with its spark episode, as a finger’s tap is in the app (DECISIONS_5 #88): the ring clusters, the swirl’s four pools and the roll’s notes. The two drops that are not strikes stay drops: the swirl’s clear-water core and the spark page’s stage-by-stage composition, which lays its own drop to show the pieces. The sweep and the captures were taken again. -
The Windows lane of the first v2 RC was red: MSVC has no
clock_gettime; the deviceless Voxo backend reads the performance counter there.voxo/src/backend_none.cpp— compiled on every platform intovoxo_nofma, the no-FMA reference the web gate compares against — took the monotonic clock throughclock_gettime(CLOCK_MONOTONIC), which the macOS, Linux, Android and wasm toolchains have and MSVC does not (C2065 'CLOCK_MONOTONIC',C3861 'clock_gettime', the RC’srelease.ymllog). Fixed with a_WIN32branch onQueryPerformanceCounter/QueryPerformanceFrequency(the same monotonic clock miniaudio’s backend uses on Windows), the other platforms untouched. Not verifiable on this Mac:build.ymlruns the Windows lane on every push tomain, so the push carrying this fix proves it before the next RC is cut; the MSVCC4996warnings on the replay library’ssscanfare warnings and stay. The RC tag that failed is the author’s to replace. THE LANE FAILED AGAIN on the next run (the log not seen by this session): the work moves to the Windows box —HANDOFF_WINDOWS.md(reproduce, read the first error, fix within the rules, ctest, the release configure’s version check, then the D3D11 gates: field at the second tier, composite, replay with #7’s dip caveat, the storm); its record is#8, the Windows session’s. A second hand-off written at the author’s ask, for a later session:HANDOFF_SUZU_VCSL.md— fit Suzu’s patches to the Versilian Community Sample Library’s instruments (CC0), with the measuring and fitting tools, the side-by-side profiles and the questions the author settles first (which instruments, presets in code or editable tables, defaults or beside them, where in the roadmap). The install page’s “makes no sound of its own” — a 1.0 sentence — is corrected: the app sounds since 2.0; the browser is the picture alone. -
The Windows lane’s second red, read on the Windows box: three MSVC stops after the clock — an else-if chain past the compiler’s nesting limit, a GCC attribute on the web reference’s exports,
M_PIin the Suzu suite — fixed portably; the lane’s steps and the D3D11 gates green. The step-67 tree (d7d9126, with #7’s performance-counter clock) built on the box with MSVC 19.44 (the lane’s is 19.51), Release,ninja -k 0to see every stop at once (docs/evidence/step67/windows/ build_log_tail.txt). Three, none incore/: (1)desktop/src/ app_settings.cpp(491): fatal error C1061: compiler limit: blocks nested too deeply— the INI loader’s oneelse if (k == …)chain had grown to 134 keys with Suzu’s knobs, and MSVC counts eachelse ifas a nested block against its limit of 128; the chain now restarts as a secondifat the firstsuzu_*key (the keys are distinct, so two chains read as one and an unknown key still falls through both). (2)web/ suzu/suzu_web.c(101):SW_EXPORTwas__attribute__((used, visibility("default")))unconditionally — emscripten’s and GCC’s keep-alive for the worklet’s imports — and MSVC has no__attribute__; the macro is the attribute under__GNUC__/__clang__and empty elsewhere (the native referencesuzu_web_referenceis linked statically, so the plain declaration is the export). (3)tests/ voxo_suzu_tests.cpp(164):M_PI, which standard<cmath>does not define and MSVC provides only under_USE_MATH_DEFINES; the test defines it when absent. After the three the build is clean; the warnings are MSVC’s deprecation notes (C4996onstrcpy/sscanfin the replay and preset tests andsumi_replay.c), twoC4127in the ABI test, and libremidi’s — nothing builds with/WX. ctest 12 of 12 (the ABI tests at 1.5.0); the first pass hadvoxo_testsdie with a SegFault at 2.75 s while the release configure compiled beside it — eight direct reruns,--repeat until-fail:6and a second full pass on the idle box all passed (0.12 s each), so it is recorded as a one-off under load, not reproduced. The release lane’s configure (-DBUILD_TESTING=OFF -DSUMI_APP_VERSION=2.0.0-rc.2) builds 99 steps andmidi-sink.exe --versionprintsmidi-sink 2.0.0-rc.2 (commit d7d9126, libsumi 1.5.0)— About reads the tag. The gates on D3D11 (NVIDIA RTX 5090, driver 616.64): the §4.6 field gate green at the reference tier, not only the second — dx 2.44e-4, dy 3.66e-4, ink 3.91e-3, aux 0, mean 2.26e-6, the step-55b numbers to the digit (DECISIONS_7 #7); the composite gate within its tier of one step (25 617 samples differ); the replay gate GREEN — the bench’s canonical demo (480 frames, 794 events) recorded through the real recorder on this box replays bitwise (every channel 0, mean 0) and the 60 Hz wall-time re-bucketing diverges (ink max 1.41, mean 3.0e-3) — #7’s variable dip did not reach it, since the recording and the replay both start from the same dip on the same box; the storm with the Dan Tranh: 0 XRuns, 0 dropped, 164.6 fps, render max 0.463 ms, 480 frames per block (DECISIONS_6 #39). The live round:--record-live 5saved 824 frames / 5.0 s of loopMIDI phrases,--replay-liveplayed them back with the banner; in the settings window the Replay section’s Record and Stop recording were pressed by hand (a 97.9 s recording of 16 123 frames saved and listed in the picker); the Play button and the iPad recording were not exercised — the author was working on the box and the agent stopped injecting input (the player itself ran through--replay-live); no.sumireplayfrom the iPad is on the box. The warnings stay as they are. Evidencedocs/evidence/step67/windows/. -
Suzu patches fitted to the Versilian Community Sample Library (CC0); fingerprinting, fitting and comparison tools in tree. (
HANDOFF_SUZU_VCSL.mddelivered.)- Measurement:
tools/suzu_fingerprint.pymeasures instrument samples (SFZ and.dspreset), extracting f₀, partial ratios r_k, strike weights w_k, inharmonicity B, per-partial T60, attack times, noise floor, and sustained spectra; renders 3-panel profile figures. - Fitting:
tools/suzu_fit.pymaps fingerprints to Suzu presets (presets/SCHEMA.md), fitting modal ratio presets 0–4 (Dan Tranh, Glockenspiel, Tubular Bells, Concert Harp), breath bow sustains, and acoustic bore winds (Tenor Saxophone, Baroque Recorder). - Comparison & Verification:
desktop/src/dev_tools.cpp’s--voxo-profileaccepts--preset <file.json>with Suzu blocks;tools/suzu_compare.pyrenders side-by-side profile overlays with quantitative metrics (harmonic spectral distance in dB, peak error, T60 tracking error). - Presets & Gates: Saved under
presets/*_vcsl.json; factory defaults preserved sosuzu_web_gate.mjsbit-for-bit reference is unaffected; all fitted patches verified throughtests/voxo_suzu_tests.cpp(Gate 27, all gates green); Suzu book’s preset table updated with VCSL references; evidence underdocs/evidence/step67/suzu_vcsl/. - Suzu Lab Audition: The six VCSL presets are exposed in the Suzu Lab
web interface (
web/suzu/site/):modal.htmlcarries the 4 modal/plucked instruments with responsive sliders,flute.htmlgains the Baroque Recorder preset,winds.htmlgains the Tenor Saxophone preset,strings.htmlgains Dan Tranh and Concert Harp under its modal pluck kind, andindex.htmlcarries a VCSL showcase with direct deep links (?preset=). All headless browser gates (suzu_lab_gate.mjs) and the web reference gate (suzu_web_gate.mjs) remain 100% green.
- Measurement:
-
The fitted sounds are in the app: Voxo owns a PATCH TABLE — the tablets’ five and the six VCSL fits — one list for the desktop’s picker, both tablets’ Sound pages and the tests. The author, with #9 in the lab: “the sounds were incorporated into the Suzu lab, I think we can also incorporate that into the app.” AS BUILT (Voxo 0.16.0, additive):
voxo_suzu_patch_count/_name/_patch(index, out)— a static table (voxo/src/suzu_patches.cpp), no instance needed — with the five patches the shells had hard-coded since step 63 (DECISIONS_8 #13: Voxo’s defaults with the voice kind and the modal preset picked) and the six fitted patches of #9 repeated field for field frompresets/<name>_vcsl.json: Dan Tranh, Glockenspiel, Tubular bells, Concert harp, Tenor sax, Baroque recorder. Gate 28 intests/voxo_suzu_tests.cpppins the table to the files: every entry admitted by the load gates on a fresh instance, every key of each file’ssuzublock equal to the table’s field (a key the gate does not know fails it), the ABI test at 0.16.0. THE SHELLS: the iPad’ssuzuPatchNamesand the Tab’s are read from the table (the picker lists eleven; the persisted index clamps to the count; the lab arguments too); the desktop’s Sound section gained Suzu patch with Load patch — the table into the knobs, which stay the player’s after — and a Suzu attack (s) knob (INIsuzu_attack, the preset’sattack_s), which the fitted patches set and the desktop had no knob for. The presets’ mirror of the synth block gainedjet_areaandjet_offset, the two fields the fitted recorder carries that the reader had skipped (additive keys). The lab keeps its own copies of the numbers (its?preset=deep links); the test is what keeps them honest against the files, the table against the same files. -
The orbit trace on the tablets: the desktop’s step-59 bridge is a host library the three native shells share, with a pure-C surface, and the tablets’ Sound pages gained the “Synth trace” rows. The author: “there is no toggle to incorporate the synth vibration in the tablets, contrary to the desktop app.” AS BUILT:
trace/beside hostmpe, the presets and the replay —trace/include/sumi_trace.h(C:sumi_trace_create/destroy/configure/set_gesture_hook/frame, the stats),trace/include/orbit_trace.h(the step-59 C++ class, moved fromdesktop/src/, which the desktop keeps using directly) andtrace/src/; it links the core and Voxo, a Swift module map (SumiTrace) for the iPad,sumi-shelllinks it on the Tab, the desktop links it in place of its own file. THE TABLETS runsumi_trace_frameon the render thread before each live frame’ssumi_update(never during a replay — the recording carries its own segments, and the canvas scope is cleared when a replay starts), with every voice kind traced (the patch picks what sounds; the desktop’s default was the rotor alone), the segments at 6 a voice under the 24-a-frame budget, tines, scale 0.25; the ink segments reach the recorder through the hook, as on the desktop. THE ROWS, under the source when it is not the sampler: Draw the orbits into the water (the ink route, off by default — on, every synth note stirs the water, a change a player chooses) and Scope on the canvas (off, over the water, alone); persisted (soundTraceInk,soundTraceCanvas), the lab extras--trace-ink/--trace-canvas/--trace-scale(iPad) and--ei traceInk/--ei traceCanvas/--ef traceScale(Tab). THE FIRST DEVICE RUN added a third row, Trace scale (0.05–2, default 0.25, the desktop’s knob): under the fingering demo the ink route marked the partial cells on both tablets (the Tab in Anod, the charge glowing at each partial), but the iPad’s canvas scope showed the tubular bells’ portraits as dots — 0.25 canvas heights per unit amplitude is the desktop’s rotor-tuned default, and the modal voices’ orbits are a tenth of the rotor’s — so the tablets carry the scale too. The desktop’s settings scope (the miniature) stays the desktop’s; the tablets have the canvas. No core change;voxo_set_trace’s mask is 0 whenever both routes are off, so the rendering is bit-identical to a world without the trace. Evidencedocs/evidence/step67/app_sounds_trace/. -
The Linux lane’s deb check counted v1.0.0’s files; the deb has carried the demo instrument since Phase 7, and the check now says so. The author, from the RC’s
release.ymllog: “the deb carries 29 files, expected 11”. The 11 are step 30’s desktop-integration files (the binary, the desktop entry, seven icons, the copyright, the Debian changelog fromcmake/LinuxPackaging.cmake); Phase 7 step 55 addedinstall(DIRECTORY voxo/demo …)so an installed binary finds the Dan Tranh —demo.dspreset,LICENSE.txtand sixteen samples, 18 files — and no stable tag had run the lane since. 11 + 18 = 29, exactly what the lane measured: the deb is right, the assertion was stale. Fixed in the workflow, not on the Linux box (out of commission; the lane runs on GitHub’s runner anyway): the count is 29 with its arithmetic in a comment, and the intent the count stood for — no third-party header swept in by CPack — is asserted directly (a.h/.hpp/.a/.cmake/.pcin the list fails regardless of the count), with the demo’s preset and its sixteen samples asserted present. Proven by the next RC’s Linux lane; not runnable on this Mac (nodpkg-deb). -
The macOS lane’s red headroom gate: one preempted block in a second whose mean was 11 % of the period — the two headroom gates now read the best of up to three measured seconds, every attempt’s worst printed. The author, from the RC’s
release.ymlmacOS lane:FAIL headroom: 10 Verlet strings at 80 nodes … mean 0.295 ms, worst 5.286 ms of a 2.667 ms period (198.2 %). The gate (tests/voxo_suzu_tests.cpp, gates 5 and 15,DECISIONS_7 #8and#13) renders a second of 128-frame blocks and gates on the WORST block against the period — the right criterion for a stall insidevoxo_render, and the wrong one for a test thread on a shared GitHub runner (or a desktop building beside the test), where the OS can hold the thread for milliseconds once in 375 blocks. The mean says the synth’s cost (11 %,DECISIONS_7 #13’s 20.8 % on a slower box); the one 5 ms block says the runner. A real per-block stall — a retune, an allocation, a probe inside the render — repeats on every attempt; a preemption does not. AS BUILT: aheadroom()helper measures the second up to three times, keeps the attempt with the smallest worst block as the reading, stops at the first attempt under the period, and the CHECK line prints every attempt’s worst — three bad seconds in a row is the structural case and still fails. The criterion is unchanged (the worst block of a clean second under the period); only the runner’s noise is given two more chances to not repeat. On this Mac both gates pass on the first attempt (gate 5: 16 cells, gate 15: ten 80-node chains); the Windows session’s one-offvoxo_testsSegFault under load (#8) is the same family of reading, left as recorded there.
midi-sink 2.0.0-rc.5 · AGPL-3.0 · source
