5 Nov 2026
?rain=): drops computed in the shader on a hashed grid, each a small ring, with faint high plips when sound is on. In ink mode, ripples and raindrops bend the ink and paper a little and are shaded like a pale wash. Technical write-up in BLOG.md.3 Nov 2026
ONLY=shots node smoke.js passes.node smoke.js 11 checks.1 Nov 2026
race.js): 0.25 / 0.5 / 1 nm legs, 1 or 2 laps with a leeward gate, 1 / 3 / 5 min start with signals, OCS, a 360 for touching a mark, finish through the start line; race clock and next-mark HUD, Course tab with laylines and track, minimap, Time x1 to x8, ORC VMG target time and bests in localStorage. node --test 24 (with an autopilot that sails two laps), node smoke.js 16 checks.1 Nov 2026
node --test race.test.js (12), node smoke.js (32 checks).31 Oct 2026
04560004IWP), and nine coefficients (sail lift and drag, spinnaker efficiency and clearance, hull friction, heel drag, wave-making resistance as a power of the Froude number, righting moment, windage) are fitted to its 117 speeds from 4 to 24 kn by calib/calibrate.js (auto-trim every point in parallel workers, Nelder-Mead on the coefficients, five rounds). 2.3% RMS error, 6% worst; VMG angles within 2 to 4 degrees from 6 kn up. New hull: Froude-number wave resistance, crew righting moment, windage. node --test model.test.js (16), node smoke.js (11 checks).node --test model.test.js (9), node smoke.js (6 checks).node --test model.test.js (14), node smoke.js (10 checks).node --test model.test.js (15), node smoke.js (11 checks).29 Oct 2026
1 Nov 2026
node smoke.js 44 checks.28 Oct 2026
node smoke.js 44 checks.node smoke.js 42 checks.node smoke.js 41 checks.node smoke.js 39 checks.scan_sibelius2_va_keyless.png; bun test 55, eval unchanged.node smoke.js 38 checks.?cracks=) for crack count, reach, shard shift and shock; cracks run out from the impact over half a second with a glint on the tips; each wedge between cracks shifts and turns as one piece and shades as it tilts (after ksenia-k’s broken glass pen); smaller colour split on text; Shake and Drop moved clear of the Direction label. node smoke.js 38 checks.node smoke.js 36 checks.fx.js) over the canvas frame. Shards shift the picture under them, crack edges split colour and catch the light, the panel bleeds ink from the impact with an oily rim; touches send ripple rings, drags leave a wake, Shake and cracks splash. Ripples On / Off in the sheet (?ripples=off); 2D fallback without WebGL2. Smoke Chromium runs with SwiftShader WebGL. node smoke.js 35 checks.World.ignorePair), then collide like any other pair; it tips over its foot when it drops. node smoke.js 32 checks.ctx.filter; pieces at rest blur the backdrop once, cut to their outline, pieces in flight the live frame, four a frame), Light appearance with light wallpapers, Clear icons, iOS 26’s widgets page (calendar, Home accessory tiles that toggle), an Apple Maps-style 3D driving view (extruded buildings, route ribbon, labels on the ground, fog), double-tap cracks the glass and knocks pieces loose, a new Phone glyph, Helvetica after SF Pro in the font stack. Gravity starts after 5 s or at the first touch, 1.5 g by default, Shake twice as hard with a screen quake. 1.6 ms CPU a frame headless. node smoke.js 32 checks.node smoke.js 24 checks.node smoke.js 19 checks (both sizes)..phys element is measured in the live layout and then moved by a CSS transform from its rigid body, so it keeps working while it tumbles (map driving, music, clocks, gauges, taps). Gravity: Down with a dial / arrow keys, device Tilt, Drive (lateral g from a simulated route’s turns, rumble, potholes), Zero-G; 0–3 g. Pinned pieces are knocked loose by a hit over 260 px/s; Restore eases them home. physics.js: boxes (Box2D Lite clipping) and circles, warm-started sequential impulses, soft mouse joint. node --test (5), node smoke.js (13 checks).node smoke.js 163 checks.26 Oct 2026
pickCleanup in session.js, tested.bun test (67).inkexport.js; 4 unit tests and a smoke check that renders the exported PDF and finds the red mark and not the blue one (49 checks).annotations.js): ink attached to the music, not the page. A stroke is anchored to the note under it (fingering, breath mark, word), to a span of notes on one staff (circle, bracket: it stretches with the notes, its margins stay a fixed size), or to a moment on the staff (margin notes). It is stored in staff spaces relative to that anchor and redrawn on any surface: it keeps its note through corrections, on the transposed page, at another scale, and re-engraved onto other systems (a span splits at the line break). A mark whose note is deleted is drawn where the note was (or at its moment), flagged lost, and can be re-attached. Surfaces now report each event’s own staff top, so a mark on the bass staff of a grand staff is measured from that staff. 18 model tests on a toy surface, plus the minuet photo (correction, transposition, deletion) and StaffInk’s real layout (re-engraved narrower); 122 notation-ir tests, smoke unchanged (33 + 46).surface.js): one interface for anywhere music is visible - lines(), place(id, edge) (a list: a note can show in several places, or none), eventAt(point), view(), optional barOf / barAt / placeAnchor / lostAt. Marker layout and hit testing, selection bands, playhead maps and time-from-a-point are written once against it. StaffInk’s engraved page (staffink/surface.js) and ScoreShift’s photo (scoreshift/surface.js) now implement it, and both apps’ hand-written geometry is gone (app code −97 lines net); no change in behaviour, all 33 + 46 smoke checks pass as before. 100 notation-ir tests, including the generic features against a toy surface and both real surfaces.session.js): a camera session saves itself to IndexedDB (every 20 s, on hide, on stop), Save this session for any other; Open shows it as a match with a 3D replay (slider, play), Download / Open… as .json. The live clock now carries on across a lens switch (a new stream restarts at 0, which reset the speed chart). Fixed the whole page scrolling on a phone (the screen-reader zone list escaped the side panel). Video / still-frame recording is in TODO.md. bun test (63), node smoke.js (all ok).camzoom.js), from the track’s zoom range where the browser exposes one (Chrome on Android, Safari 17+), else the phone’s other back lenses (Safari’s Ultra Wide and Telephoto by name; unnamed Android lenses as Lens 2, 3 to try). Switching lens keeps the video element and the heatmaps; the court is found again. Remembered per browser. bun test (61), node smoke.js (fake phone camera: steps, zoom applied, lens switch).tools/match.mjs passes each chunk’s own. Measured on two files: with good sound kept flights start on a heard hit 2.3× as often as chance, changing ~4% of kept flights; on a highlights soundtrack without the hits, nothing. Earlier today: long ball tracks fitted piece by piece instead of rejected whole at their first bounce, flights entirely next to a player dropped, one ball at a time (green-box-on-flight 1–16% → 5–67% on cc.mp4); older flights fade on the video and the 3D view. bun test (55), node smoke.js (all ok).score.js alignParts, measure.at, used by timeline() and the notation-ir adapter), instead of from the top of the page, where it played over the opening bars (Sibelius 2 viola part, 8 of 17 pages; found through MidiViz #046). Fixture scan_sibelius2_va_divisi.png; 54 tests, fixtures unchanged.25 Oct 2026
bun test (54), node smoke.js (22).coaching.js): for each player, where they stood when the opponent struck the ball, and how often that was on the T (within 1 m); volleys, where they were taken and where the opponent was then; and every shot’s first floor bounce on its own map by shot type. Racket hits are found by carrying the ball back to a player’s reach, and hitters are labelled through each rally by alternation. Three maps (both players or one) plus a summary; also in match.json, the export and the match.mjs summary. On a 41-minute game: hitter known for 115 of 415 shots, so the numbers are thin until the ball is fitted on more shots. bun test (51), node smoke.js (21).node smoke.js (all ok).node smoke.js (all ok).tools/squashai_fetch.mjs and tools/pbox_eval.mjs score player detection against the SquashAI datasets’ 2,392 hand-drawn player boxes on 1,253 PSA broadcast stills. Baseline: court fitted on 83% of frames; on those, 33% of players found with feet in place, 47% overlapping one of ours, feet 12% of box height off, 33 cm on the floor. It is broken down by video, by distance and by overlapping pairs. T domination per player (tcontrol.js): of the time both are seen, the share each is nearer the T, plus time within 1 m of it and the average distance. Shown in the heatmap titles and the readout, and written to the export, match.json and the match.mjs summary. bun test (45), node smoke.js (20).predictPath: drag, gravity and the simulator’s bounce model through walls and floor, stopping at the second bounce, an out line or the tin; impacts as dots, out in red). A drive predicted from its first 0.18 s stays within 3 cm of the truth through the front wall and two bounces. bun test (38), node smoke.js (19).tools/match.mjs). Each track’s shirt colour is the median of its clean reads (not while the players are within 0.9 m or occluded); tracks seen together in one camera stretch are linked into chains labelled as a whole, so a restarted track keeps its partner’s opposite; k-means centres refined under those constraints. On a 7½-min PSA highlights file: 63 of 63 tracks with their own shirt (was 40 of 65, 1125 detections under the wrong player), detections on the right shirt 0.61 → 0.99. --merge <out> re-merges saved chunks. bun test (37).PlayerTracker scores both pairings jointly with position and shirt, refuses a box on a clearly different shirt, and coasts a hidden player for up to 1.5 s instead of starting a new track. On the main camera of a 7½-min PSA highlights file: identity switches 136 → 5, steps on the right shirt 0.74 → 0.99, steps with only one player found 451 → 356 (shirt-labelled, so the count favours the colour term; six close moments checked by eye). Synthetic players unchanged (median 1.7 cm). bun test (37), node smoke.js (19).match.png being mirrored (x = 0 is the right wall facing the front, and was drawn on the left). node smoke.js (19).commentary.js): each shot built around its front-wall contact (measured, or a flight carried on to / back from the wall) and named lob / drop / kill / boast / drive, straight or crosscourt, with length, tightness to the side wall and pace; corner sprints and a player pinned in a corner; lines rewritten in place when a flight is refitted, Speak to read them aloud, shots in match.json from tools/match.mjs (main camera only: flights from court-level and side cameras are left out). Side panel resizable (drag its edge), two columns: commentary feed, floor plan, front wall with every ball contact and the last one in green (last floor bounce too), each player’s own heatmap with distance and top speed. Clear now also resets heatmaps, bounces, speeds and commentary. bun test (36), node smoke.js (17).audio.js: sharp onsets above 2 kHz) decoded from the opened video; a heard impact where a lost or not-yet-started flight would meet a surface becomes a bounce, and the audio delay is estimated from seen impacts only when it beats chance. Player speed chart (last 30 s). tools/match.mjs: a whole match file in parallel chunks (court tracked through cuts, players, ball, sound), players linked across cuts by shirt colour, per-player heatmap / distance / speed over time and every bounce with its hitter, match.json (opens in the page) and match.png; a 15-min PSA file in ~3 min on 30 processes. Player fixes found on whole files: the walk past a score bug must give a person’s height (the bug’s own bars were taken for feet, 20 % of detections at the back wall), caption-tab edges may have small gaps, the bars under a bug are masked, foreground that never moves within a camera stretch is masked, and the tin band seen level is no longer taken for a graphic. bun test (30), node smoke.js (15).bounces.js). The ball worker now gets every new video frame through requestVideoFrameCallback instead of only the frames the court worker analysed, which had left gaps of several frames. bun test (26), node smoke.js (13).node smoke.js (12).node smoke.js (11).rally.js, rally-worker.js, tools/rally_run.mjs; bun test (23), node smoke.js (11).Sim.shift() moves every texture, the wet state and the undo history, changed tiles leaving are read back, gzipped into a new IndexedDB tiles store (v3), and tiles near the window are decompressed ahead so panning never waits; ruling follows world coordinates. Size / water / paint as three value chips over one slider (tap to choose, drag a chip to change it), zoom by dragging the % between + and − (the zoom bar is half as tall), a layout pass that keeps every panel on any screen (⋯ overflow menu for the top-right bar, scrolling tool strip, shrinking slider, popovers clamped and scrolling; checked at 320 × 568, 560 × 420, 844 × 390, 1280 × 600), tool names on tap or hover, + in the page bar for a new page (N). node smoke.js 118 checks.track.js). Model line samples are projected with the last camera and a colour profile is read across each at the frame’s own resolution (peak of the both-sides-alike response nearest the prediction, in the learnt colour of that kind of line, continuing along it), so a frame costs 1–7 ms instead of ≈ 50 ms. A camera that has not moved (judged by the joint Gauss-Newton step, < 0.2 px) is held exactly, and offsets averaged over held frames refine it every 12 frames; pan / tilt / zoom are fitted on a fixed mount (position prior), which removes the zoom / distance drift, and the mount is released on cumulative evidence that it moves (then a constant-velocity prior). The full tracker covers lost or doubtful frames, the first frame after a slow detection and long gaps; cameras held for a while are remembered and picked up again after a cut without a detection. Fixed-mount synthetic pan / zoom clip: position within 1.6 cm on every frame, floor 0.4 cm at the 95th percentile (full tracker 3.9 m); moving-camera clip 10 cm (2.4 m); PSA footage 90–99 % of frames held, zero jitter. tools/track_eval.mjs, tools/make_ptz_video.mjs, ?track=full, ?mount=moving; bun test (18), node smoke.js (9).tools/batch.mjs; bun test (14), node smoke.js (9).Add Squash Court - the camera from a squash court’s lines. A pixel is on a painted line when the pixels either side are alike and both differ from it the same way (three scales, four directions, normal from the doubled-angle sum), so lines of any colour on wood, dark floors, white walls and glass fire but edges don’t; sub-pixel centre lines linked into straight segments, short ones (text, grain, crowd) dropped, then a nearest-line distance map. ~6000 seed cameras from where squash cameras stand, scored by model-on-image coverage, local stochastic search, then Gauss-Newton ICP over rotation, position, focal and k1 (Tukey, shrinking scale, plausibility bounds; the back-wall line ignored from behind it), judged both ways at 640 and 480 px. Video tracks from the previous frame and re-detects after three bad frames. UI: overlay, click to metres on floor or walls, rectified floor and front wall, 3D view with the frustum, camera JSON. Synthetic sweep 17/20 (median 1.6 cm, 0.03°, 0.08 % focal); 11 of 18 PSA broadcast stills fitted by eye (misses are close-ups); detection ≈ 450 ms, tracking ≈ 60 ms per frame. bun test (11), node smoke.js (9: demo pose, click, export, tracked clip at ~19 fps headless).
?rainbow=. Puddles reflect a varied overcast sky with drifting lighter and darker masses, stronger ripples, and a low puddle camera shot to see them up close. Smoke: 27 shots.?lowlatency=1/0 still overrides. Auto stays standard on Android. Smoke: 32 (toggle, writing after a swap, Android UA picks standard); the offline check now finds the current cache instead of a hard-coded version.26 Oct 2026
moveClip (arrangement.js) and cliporder.js.24 Oct 2026
?weather=clear for none): a day or three at a time, deterministic in the day number so scrubbing replays the weather, about one day in five most of the year, more in April, about half the days in the tsuyu rainy season, a third in the September autumn rains, none while it snows; the time zoom keeps the real sky for its real-time start. Puddles collect in the hollows of the path as it rains, ringed with darker wet mud, reflecting the grey sky (more at grazing angles), with spreading raindrop ripples. Smoke: 25 shots.playheads.js (timeMap, cursorsAt, headsAt, drawPlayheads), and perform() trace segments now carry their clip marker. ScoreShift draws the line on its own canvas, so a frame redraws only the line. 88 notation-ir tests; smoke: StaffInk 30, ScoreShift 44.?rain=0…1, ?weather=rain, rain.js) at any time of day and in every animation: a low stratus deck with darker drifting rain bands, colours drained toward a cool grey at the same brightness, no direct sun, shafts, flare or cloud shadows, raised ambient and thicker haze, wind-slanted stateless rain streaks that give way to snow, darkened wet grass and path, no birds, airliner or fireflies, and rain hiss, patter and drips in the sound. Smoke: 23 shots (day-cycle dawn, two rain frames).?anim=zoom, journey.js): starts at the real clock and date in real time, then over 44 s speeds up to one day every 8 s (the sun crosses the sky east to west), one day every 3 s (about four days and nights), and one month every 4 s, where it holds. The rate is eased in log steps and the simulated time is its integral, so the progress slider and ?p= jump to any point. The hour runs the full 24 hours through the time-of-day keyframes; past about a day per second the days lock to one hour (15:00 default, lock), each night shrinking to a blink, and from then on every day gets its own clouds and light. The date sets the season (blossom about 1 April, first snow mid December). Clock overlay with date, time and speed; Start time zoom from now. Also a slow camera drift during every time-lapse (motion-control-style swing around the tree with a slight push and rise, drift, pauses while dragging). Smoke: 18 shots, all three animations checked live, and Chromium is found in any Playwright browser folder.arrangement.js, shared with StaffInk; clicksFor gives metronome clicks for any program (looped bars click every pass). 81 notation-ir tests, 43 ScoreShift smoke checks.analyzeAfter(norm, fifths) reads a page in the key the previous page ended in: staves that print no key take it (and key changes inside a staff count from it), while a printed signature, a cancellation, a keyless section (now marked key.keylessSection) or a printed key that disagrees keep the page’s reading; analyze(norm, { fifths }) is now that default rather than an override, and the page’s own inference still runs. PDF renders cancel with an AbortSignal and clean up their page; a reader pool that fails to open closes the readers it had opened. Fixtures spliced from the Telemann crops (tools/make_key_fixtures.mjs); fixtures and degraded conditions unchanged (pitch 98.3 / 95.3 / 96.5 / 90.4%). 51 tests.scan_sibelius2_va_p4.png; fixtures and degraded conditions unchanged. 52 tests.scan_sibelius2_va_p4_top.png. 53 tests.23 Oct 2026
?anim=day|year). Day cycle: noon, afternoon, golden hour, magic hour and night over a 50 s loop, holding the night and fading through dark back to noon. Four seasons, a year over 80 s from seasons.js (sampleSeason(s), s = 0 deep winter): patchy snow melt (one snowAt field shared by ground, grass and flowers, mountain snow line climbing), the bare tree blossoming pink and dropping petals, spring green deepening to summer, wildflowers, the autumn turn through all six colours to red, shedding in turn order (per-card drop rank, cards shrink away as the canopy goes bare while the leaf fall thickens), then a bare crown of forking twigs with snow on the limbs, falling snow (snow.js, stateless points in a camera-centred box), buried grass with straw tips, greyer winter skies and quieter birds and insects. Clouds run on their own clock (uCloudT) 30× faster while playing, and each day of the year gets its own cloud cover and light, for a time-lapse look. Controls: play, scrubber, jump to season, seconds per loop, cloud speed-up; dur, p, play=0, season, lapse. 16 smoke shots plus a live check that both animations play.arrange.js, 7 tests.markers.js): clips as coloured brackets split at line breaks and stacked when they overlap, loop counts, jumps as arrows, label chips, hit testing, and lost markers red and dashed where their note was. ScoreShift draws the page’s saved program on the photo; after a correction deletes one of its notes, that marker turns red and Play names it. 6 tests; smoke checks for both apps (StaffInk 29, ScoreShift 42).25 Oct 2026
| Aquarelle: back to the first interface’s floating toolbars, keeping everything since. One button per brush on the left (tap the selected one again for its settings; smudge / eraser for their shape), size / water / paint sliders beside them with values on top, the pigment palette with the big mix well and + Mix along the bottom (rainbow well or mix well for the colour wheel), menu, gallery, wet dot and Pen | Paint top left, undo / redo / hair dryer (hold, double-tap) / tilt / layers / share / full screen / help top right. The sheet is fitted into the space the panels leave, so nothing sits on it (on narrow screens the sliders move to the right). Pen mode swaps the palette for the pen bar. E-paper theme redone for the new panels; phones drop tilt, full screen and help into the menu. 65 smoke checks. |
22 Oct 2026
ss:L<staff>:q<x in quarter spaces>:<voice>, barlines ssb:L<staff>:q<x>) instead of the order events are met, so a deleted or added note no longer shifts every later id. Anchors match a head re-read a few pixels off (within half a staff space, same voice first) and report a deleted one (AnchorError) instead of choosing its neighbour; checkProgram lists every marker that lost its note. ScoreShift keeps the page’s program in the .scoreshift project and playProgram() replays it after reopening, with the corrections applied. 69 tests, 41 ScoreShift smoke checks.written), and beats / syncopation switch the rules off. Ignoring the printed values, it reproduces the engraved rhythm of 14 of 15 fixtures. 65 tests.audio.js, no samples), off until a gesture: ♪ button, M key, Sound folder with music / ambience volumes, ?sound=1. Ambience follows the scene: two-band wind noise scaled by the wind setting with gusts and leaf rustle, songbirds by day (more when the flock crosses), a far jet rumble under the contrail, higurashi cicadas at dusk, crickets at night. Music: slow generative felt piano over a detuned-saw pad, 64 bpm in D major on the royal-road progression (IVmaj7 V iii7 vi, ii7 V7 I), pentatonic melody landing on chord tones, lower and sparser at night; generated stereo hall and a limiter. Also since the first version: bird flocks and a contrail that cross wherever the camera looks, an airier canopy with a canopy gaps slider (airy), and a mixed autumn palette: each leaf pad one of six colours (green to orange-red and brown) with warm shadows, moved through early to late autumn by a turned slider (red-dominant at 1); the old all-orange autumn kept as maple. node smoke.js: 12 shots plus an offline render of the sound for day, dusk and night (fails on silence, NaN or clipping) and a live ♪ button check.interpret() derives bars from the meter or printed barlines (printed ones win and re-phase the meter), splits notes across bars into ties, infers tuplets from the duration (1/12 = triplet eighth, 1/20 = quintuplet sixteenth), fills gaps with rests or leaves paper, and spells with a chosen accidental scope (bar / note / time window for free time); bar status (ok, pickup, end, under, over, free) is reported, never repaired. Layout as one least-squares fit with minimum gaps (pool-adjacent-violators): engraved, proportional or anchored to the ink / the photo. Anchors (note, printed barline, nth bar, point on a staff line) resolve when used, so they follow edits; positionAt maps time back to the page. perform() plays authored programs - clips, walks taking jump markers, seq / par / loop / seeded choose, gap compression, confidence floors, alternative readings per loop pass - on parallel cursors, with StaffInk’s articulation rules (slur legato, staccato, staccatissimo, tenuto, marcato, accents, fermata). Pitch, duration and the Web Audio player moved into it and now back both apps. Adapters: StaffInk <-> IR (plays exactly what StaffInk plays) and ScoreShift -> IR (same pitches at the same times on all 15 fixtures); ScoreShift’s ✎ Edit in StaffInk opens the page, as written or transposed, in StaffInk with a report of what it cannot hold (second voice, 5-tuplets, tenor clef). playProgram() in both apps. PLAN.md: model decisions, adapter contract, gaps, next steps. 57 tests; StaffInk 54 + 28 smoke checks, ScoreShift 45 + 40.| Aquarelle pen mode and e-paper. Pen | Paint switch: ink goes straight into the active layer through its own pass (no water, anti-aliased edge, even line within a stroke, undoable, composited only under the stroke): ballpoint, fineliner, brush pen, highlighter (transparent glaze), eraser nib, stylus eraser end; ten inks chosen to stay distinct on Kaleido colour e-paper plus six highlighter colours in a bottom pen bar, + / round button for the colour disc, touch-and-hold to pick from the page; notebook pages (1264 × 1680) with lined, grid or dotted ruling drawn in the screen pass. E-paper theme (white, black outlines, no shadows or animation, drying redrawn a few times a second), auto-detected for Bigme / Boox / Kobo / PocketBook; laid out and checked at the Bigme B7 Pro’s 632 × 840 CSS px. No desynchronized canvas on Android. Switching paintings now waits for a save in flight (a quick New could lose the previous canvas). 53 smoke checks. |
21 Oct 2026
bun test (10), node smoke.js (26 checks: real pen/touch/mouse strokes, flow, drying, mixing, glaze, undo/redo, lift, pinch, export, save/restore, iPad/phone layouts, offline) plus a painted landscape.bun test (9) and node smoke.js (shots, demo ride, mouse-drawn strokes, snap, erase, steer).node smoke.js renders 7 still shots through headless SwiftShader and fails on shader errors or blank frames.6 Oct 2026
.scoreshift file (source file + edits of every page + settings) that Open restores anywhere. Edits are anchored where symbols were recognised (edits.js): pitch steps, accidentals, values / dots / triplets / ties / graces, articulations, deleted notes and rests, clefs, keys, key changes, time signatures. snapshots/coda.json snapshots the whole CODA reading bar by bar; bun test lists the bars that change, UPDATE_SNAPSHOTS=1 accepts. 43 tests, 36 smoke checks.3 Oct 2026
<grace/>, switchable in the popup. Transposed page: ties and slurs move with their notes when found whole (CODA 26 of 27), articulations and bowing marks move with their note, symbol ink lost under staff lines is put back when pasting (stems no longer break, hollow heads keep their edges more often), and no more white squares where a line runs through a moved head. 41 tests, 34 smoke checks.<technical>; bowing fixture 22/22, CODA bowings match the page. Grace notes move with the transposition (smoke check: all 7 grace heads ink at their new place). 41 tests, 34 smoke checks.<grace slash="yes"/>. New grace fixture (6/8 found); Telemann extras 22 → 26 (the new ones are real grace notes its answer key omits). 40 tests.tools/make_dynamics.mjs), including ones touching stems; on CODA all dynamics are read and attached correctly. Hairpins and slurs recognised; dynamics drive velocity, hairpins ramp it, slurs play legato; MusicXML <dynamics>, <wedge>, <slur>. Interpreted view gets a Show menu toggling bar numbers, values, articulations, ties, slurs, dynamics/hairpins, voices and text boxes. New expr fixture. 38 tests.<key> per measure. Thick / repeat barlines on scans accepted. Telemann IMSLP: typeset 99.6 → 99.7%, Bärenreiter scan 98.6 → 98.7%, keys 261 → 262/264; two crop regression tests (33).<backup>. Staccato, tenuto and accents read (15/15, 7/7, 5/5 clean, no false marks), played (shorter / louder), exported and editable. Tied notes keep their accidental across barlines and system breaks. Rows of letters outside the staff are text, so “o”/”p” of tempo marks and dynamics are no longer notes. Double flats reassembled (5/5 clean). Two new fixtures (two voices; articulations + double accidentals); bar accuracy now compares onsets, so voice splits don’t matter. Clean: 137/140 bars; CODA still 67/67; Telemann unchanged. oemer’s notehead U-Net compared and written up (helps phone photos, too big and slow to adopt). 34 tests.2 Oct 2026
tools/imslp_fetch.mjs (resolves IMSLP downloads in headless Chromium, renders pages), tools/groundtruth.js, tools/imslp_eval.js (edit-distance alignment on pitch, per-staff clef/key). Pitch 98.1 → 99.6% typeset, 96.0 → 98.6% scan; keys 227 → 263 and 243 → 261 of 264 (the recognition fixes landed in d384f84).tools/compare.mjs scores MusicXML against truth or each other. Neural options surveyed (Legato 430 MB, SMT 86 MB: no symbol positions; homr AGPL; oemer segmentation for later): instead a 98k-parameter MLP (glyphnet.js, 130 KB int8, ~0.1 ms/glyph, plain JS) trained on 120 synthetic Verovio pages in 5 fonts (tools/make_glyphset.mjs, tools/glyphnet.mjs), reading note accidentals 29/30 on phone photos where the stroke rules read 20/30. Rules and classifier now compete per symbol (classifier may add or retype at ≥ 90%, never delete); split double sharps reassembled; rest values scored by template + classifier with runner-ups kept for the bar check; bar repair can drop a stranded unbeamed head; beam stacks stop at a slur; staccato dots no longer dot a rest. Pitch: scan 94.6 → 96.0%, photo 96.4 → 97.2, phone 83.0 → 91.6; bars on phone 89 → 97 of 124. CODA sheet now matches the adjudicated reference in all 67 bars (Audiveris 55). 31 tests.rhythm.js counts beams and flags per stem (beams stacked from the stem end, stopping at a slur), dots (rejecting tie pieces cut up by staff removal), grace notes, and matches rests, time-signature digits, C / cut C and tuplet numbers against SMuFL glyphs of 5 fonts rasterized by a new canvas-free path rasterizer; time signatures are read as a region split at the middle line (the digits fuse through it) with a prior on real meters; ties link same-position heads. score.js builds parts and measures and checks every bar against its meter: pickups and section ends may be short, otherwise the cheapest unambiguous edit (dot, rest value, unmarked beamed triplet, beam count, flag, half/quarter, false rest, ignored tuplet number) makes it full, else the bar is flagged. Interpreted view shows values and bar checks; tap to set value/dot/triplet/tie/grace, delete a rest, set a time signature or play from a bar. ▶ Play with a playhead at a set tempo; ⬇ MusicXML 4.0 (as written or transposed with <transpose>) and ⬇ MIDI. Fixtures gain every bar’s events and three rhythm tunes written as MEI (Verovio’s ABC importer drops quarter triplets and chord lengths): clean 99.6% note values, 123/124 bars exact; photo 96.0% / 111. Real page: the CODA viola audition sheet (Brahms 2 + Fledermaus, Finale-style engraving) reads all 67 bars adding up after fixing wide tilted half-note holes, a tempo letter read as a flat, a cut C read as a sharp, keys of different pieces on one page voted together, slur-fused barlines, a double sharp read as a whole note. Pitch on fixtures also up: clean 96.0 → 99.0%, scan 91.8 → 94.6%. 30 tests, 32 smoke checks.1 Oct 2026
bun test cases (21). PDFs render on a pool of up to 4 pdf.js documents that keep their last page decoded; probe renders double as thumbnails; probes and export share a worker pool: 30-page scan opens in 6 s (was 12), exports in 22 s (was 27).bun test (18), node smoke.js (17 checks through the real UI, export and offline PWA).30 Sep 2026
29 Sep 2026
train.js, zero deps), scores 94.4% top-1 / 99.3% top-3 on held-out writers 81-100. Notes are then parsed structurally (stem, heads at one end, fill density) for pitch and chords; beams, ties/slurs and scribble-erase are gestures checked against what is already engraved, with pending ink committed tentatively first. Alternatives strip after every symbol; a tap fixes it and stores a $P template so recognition adapts.</> in one stroke or two lines. Tuplet digits are $P templates cut from real HOMUS time-signature numerators (train.js --digits); dynamics are synthesised italic-letter templates (weakest part, corrections take over). Only matched outside the staff or beyond the stems, with a guard for whole notes on ledgers. Dynamics and hairpins drive playback velocity; MusicXML time-modification/tuplet/dynamics/wedge. Edit bar gets a triplet button and a dynamics row.bun test (37) + node smoke.js (18 checks with real pen and touch pointer events over CDP).26 Sep 2026
proxy.js re-serves it in Digitraffic’s shape; US live AIS (NAIS / MarineCadastre) is not public. Lanes come from NOAA ENC Direct TSSLPT/TSEZNE/PRCARE/TSSBND/FAIRWY layers (harbour + coastal, verified CORS). Pinhole camera with roll, near-plane clipping so lanes you stand in still draw, curvature-dropped sea surface, true horizon dip, hull footprints from A/B/C/D, dead reckoning, CPA/TCPA. bun test with captured fixtures + headless Chromium smoke render.maxAllowableOffset), 2D sight-line vs shoreline test per vessel at 2 Hz. Ships behind a headland render dimmed/dashed with distance to the blocking shore; lane labels behind land are dropped. Viewer standing on land is handled by skipping the first crossing of the ring that contains them.26 Sep 2026
KingCo_PropertyInfo ArcGIS MapServer answers point+distance queries with WGS84 polygons, appraised land/improvement values and a 3-year sales layer, all with CORS; Nominatim for place names. geo.js does centroid/angular-span/point-in-polygon and a pinhole projection from (bearing, pitch); Android Euler angles are rotated to the back-camera direction so any phone orientation works. Labels, ground-plane outlines when tilted down, radar, detail sheet with sales history and assessor links, simulated mode for desktop. bun test (33) on a captured live fixture + live smoke test.pds_prop_report MapServer, market land/impr values) and the WA State Parcels Project layer on DNR’s server as the fallback for Pierce and every other WA county (values match King County’s own service exactly; DOR land-use codes decoded). Labels moved to rooftops: building height from OSM height/building:levels via Overpass (CORS works from a browser; 37/134 buildings mapped in the U-District), else estimated from use class + improvement value per ft² of lot (log fit through $60→1, $1000→6, $8500→24 storeys; reproduces the Brooklyn Ave skyline). Dashed roof outlines + corner verticals. PWA: manifest, icons, service worker (shell cache-first, data network-first with fallback), IndexedDB parcel store with fetched-coverage circles so revisited streets work with no signal. Verified offline reload in headless Chromium. 48 tests.unionArcs, which computes the uncovered arc intervals per circle — canvas destination-out tricks left antialiasing ghosts) and amber dotted circles for pending downloads; “Precache around me” with a slider, request estimate and a hex-lattice planner (planPrecache, tested gap-free) that skips already-covered circles and runs two requests at a time; 0.5 mi of the U-District = 19 requests, ~3k parcels, 5 MB, ~2 s. Service worker shell strategy changed to stale-while-revalidate so edits reach installed copies. 53 tests.Track needs 30 m over 90 s, planAhead lays a 3-lane corridor of fetch circles, allowAuto gates on navigator.connection type / saveData with Off / Wi-Fi / Any), sharing one download runner with the manual button. 59 tests.maxAllowableOffset 0.5 m, 52→18 KB gzipped per circle), paging in dense cores, Overpass skipped when heights are already known, rolling 24 h data meter with a daily budget gating auto-precache. CPU: rings converted once to a metric frame and decimated, planar parcelViewXY replaces per-vertex haversine, 30 fps cap with change detection, radar footprints on a cached offscreen bitmap, union arcs cached — main thread 0% idle → 96% idle still / 74% turning with 740 parcels. Battery: 720p camera, sensors and camera suspended when hidden, low-battery mode via Battery API. Large commercial: stacked condo/air-rights parcels merged into one label with summed value and a units list, wide parcels anchor the label where you are looking, county-server outage falls back to the statewide layer (Snohomish went down mid-session). 68 tests.fetch_data.js into two small bundled JSONs; market.js gives assessed value per unit, $/ft² living, unit mix, year built, ZIP typical rent/condo/home with 1y change, and an estimated rent per unit (ZIP rent × size^0.6 × age factor) with gross yield. Assessor storey counts now override the height estimate. “Per unit” price mode. The detail sheet states what assessed value is (1 Jan assessment-year estimate, not last sale). 76 tests.?sim=calabasas. 81 tests.terrain.js looks up ground elevation per parcel (Open-Meteo, 100/call) and under the viewer (USGS 3DEP), cached on a 30 m grid, so hillside lots sit at their real height; GPS altitude is geoid-corrected, ground-subtracted and fused across fixes weighted by altitudeAccuracy, trusted only under 12 m uncertainty (“~floor 25 ±1” in the chip), with a manual Floor override in settings. From a high-rise the street grid now drops below the horizon instead of every label sitting 30° too high. ?sim=…&floor=24 / &alt=166. 87 tests.bun test + live NOAA smoke test.s57.js reads ISO 8211 / S-57 .000 cells directly in the browser (DecompressionStream unzip, DataView parsing, IndexedDB store); vector S-52 chart, depth and routing without network. Verified identical to ENC Direct on US5SEAGK. Bundled 62 central Puget Sound cells (4.9 MB) in vibes/sailnav/charts/ with auto-load around the boat; fetch_charts.js refreshes them. Vendored Leaflet + service worker + manifest: whole app runs offline after first load (verified with network disabled in headless Chromium).26 Sep 2026
17 Feb 2026
13 Oct 2025
15 Jul 2024
14 Jul 2024
11 Jul 2024
10 Jul 2024
16 March 2024
11 Feb 2024
6 Feb 2024
5 Feb 2024
2 Feb 2024
1 Feb 2024
30 Jan 2024
28 Jan 2024