POLYTONE — the AI-native programming language

Changelog

Versions follow 0.PHASE.SPRINT — the minor version is the roadmap phase, the patch the global sprint: v0.21.135 reads “Phase M21, Sprint 135”. The whole beta stays 0.x; 1.0.0 is reserved for the finished product.

v0.40.2522026-08-23latest

Sprint 252 — M40 opens: one card, actually

  • The benchmark harness now sends the evaluated language card — byte-identical with the IDE's runtime card and the Pro CLI's embedded one. One card, three consumers, one shared provider prompt cache, and a new gate pins the third consumer so it can never drift again. The methodology page carries the honest comparability note: runs 1–7 sent the raw source form, so tokens-to-green compares directly only within one card form. Ride-along: llms-full.txt's five stale media-format versions corrected. Phase M40 opens as the second open-ended rotation container — next up: Baseline 8 over the 36-task corpus.
v0.39.2512026-08-23

Sprint 251 — M39 close: the review and the proof

  • The streaming phase closes with its review — and with last phase's lesson turned into method: 115 shared adversarial event streams were fed to both decoder twins and compared byte for byte. 112 agreed on first contact; every defect lived exactly where no execution had crossed. The two serious ones: an HTML gateway error came back as an empty success (the repair loop burned rounds against a dead gateway — the stream path now captures the HTTP status and refuses every dishonest shape, proven against a live socket), and a mid-stream provider error was silently swallowed on both surfaces (both decoders now surface it; partial text never reaches verification). The proof even caught two latent collision bugs in temp-file staging — the root cause of a flake the previous review had observed but could not reproduce. Phase M39 complete: both surfaces stream, honestly.
v0.39.2502026-08-23

Sprint 250 — the IDE's live progress

  • The carry-over open since Sprint 120 closes: the IDE's shipped-but-unwired streaming transport now drives every key-based generation, and the panel's status line counts the completion as it arrives — 'generating… 1,234 chars'. A delta only ever becomes a number, never a code preview: the product rule that the human never sees code the compiler hasn't verified decides what a delta may become, on both surfaces now. Buffered responses decode once at the end, HTTP errors keep the exact shape the retry logic already classifies, and a UTF-8 character split across network chunks is held back byte-exactly — the same rule the CLI's transport follows.
v0.39.2492026-08-23

Sprint 249 — M39 opens: the CLI streams

  • Phase M39 takes the step two phase records designated next: streaming. The CLI now receives completions as Server-Sent Events, decoded incrementally by an exact mirror of the IDE's shipped-but-unwired transport — same event framing (CRLF included), same usage events, same tolerance for keepalives. Live progress is a counter on stderr, never the code itself: the product rule that the human never sees code the toolchain hasn't verified holds in the terminal too. Absent usage stays honestly flagged as estimated, and provider errors surface as their own message. Next: the IDE's own live progress closes its Sprint-120 carry-over.
v0.38.2482026-08-23

Sprint 248 — M38 close: the review and the proof

  • The Pro-parity phase closes with its adversarial review: twenty findings, every verdict by execution, seventeen fixed — three of them in the IDE, because parity sometimes means the IDE moves too. The two serious ones: concurrent runs could shred the local ledger mid-line (one atomic write per record now, pinned by a race test), and replaying a session never re-verified media documents on either surface — a garbage image document could land under a 'verified' verdict; both surfaces now render replayed media through the real codecs. Everywhere the mirrors had been executed against each other they held; every defect sat at an edge they had not yet crossed. The proof walks the whole arc: a routed run, the ledger with both tiers attributed, the exported session replayed green into a fresh project. Phase M38 complete.
v0.38.2472026-08-23

Sprint 247 — sessions are code, on both surfaces

  • The CLI's ledger exports as a polytone-session v1 file — the same document the IDE exports — and a session replays anywhere with no model call: the verified change-sets re-apply and the final state re-verifies locally through the real pipeline; only a green final state is written into the project. Parsing and filtering mirror the IDE verbatim (a shared session file is untrusted: status filter, shape checks, the path boundary). One committed fixture is validated from both sides — the IDE's own parser in its test suite, the CLI's parser plus a full green replay in the Rust suite — so neither surface can drift the format silently.
v0.38.2462026-08-23

Sprint 246 — the ledger reaches the terminal

  • Every gen/fix run now records itself in a local intent ledger — the subscription's honesty guarantee in the terminal, local only, never telemetry. The records carry the IDE's own field names verbatim, so the ledger doubles as the foundation for session export. 'polytone ledger' prints lifetime totals and the per-model split with the IDE's exact reading rules, pinned by tests: corrupt lines are skipped, malformed counts sum as zero instead of poisoning the totals, verified counts by the flag set at generation time, and an escalated run's cheap-tier tokens attribute to the cheap model. Reading is ungated on purpose — the totals are the honesty layer.
v0.38.2452026-08-23

Sprint 245 — M38 opens: cheap/strong routing in the CLI

  • Phase M38 opens — the Pro CLI gains the IDE's Pro features, starting with routing: a configured cheap_model goes first, and the strong model sees the task only when the cheap tier's bounded repairs fail. Verification is local and free, so a failed cheap attempt costs cheap tokens, never strong ones. The stop rules mirror the IDE exactly — a verified cheap run or a provider failure never escalates — and the token ledger itemizes each tier. Pinned by execution: the strong provider is never called when the cheap tier succeeds, and after an escalation the strong candidate is the one that lands.
v0.37.2442026-08-23

Sprint 244 — M37 close: the review and the proof

  • The Pro-CLI phase closes with its adversarial review — thirteen findings confirmed by execution, eleven fixed, two recorded. The two serious ones: the CLI's answer parser diverged from the IDE's grammar in ways real model output triggers (it now reimplements the IDE's exact semantics, quirks included), and a changed subdirectory module could be applied 'verified' without its own tests running (every changed module now verifies as its own entry). The license verifier accepts exactly the IDE's key set, curl staging carries the toolchain's full transport hygiene, and the proof walks the whole product as a test: keyless refusals through the real binary, a taught-then-repaired session ending in an applied change-set the pipeline itself re-confirms green. Phase M37 complete.
v0.37.2432026-08-23

Sprint 243 — the bridge and the page

  • 'polytone agent' makes the Pro CLI a first-party IDE backend: the prompt arrives on stdin, the completion leaves on stdout, errors speak on stderr — exactly the 'Local agent (CLI)' protocol POLYTONEide has carried since Sprint 119, so selecting it and typing 'polytone agent' is the whole integration. A pinned test proves a license refusal keeps stdout empty. And the CLI got its public page: /cli/ documents the verified loop in the terminal, the one-subscription-two-surfaces licensing, the IDE bridge and the full command reference — documented publicly, deliberately not available free; the POLYTONEide Pro plan now lists the CLI.
v0.37.2422026-08-23

Sprint 242 — the loop reaches the terminal

  • 'polytone gen' and 'polytone fix' bring the verified loop to the terminal: context slice plus the frozen language card in, the candidate checked AND tested in a scratch copy of the project, bounded teach-repair on failure — only a green change-set ever touches your files. Prompts, the answer grammar, the path boundary and every repair message mirror the IDE verbatim, so both surfaces teach models identically and share provider prompt caches; the card itself is extracted from the IDE at build time, one card for IDE, benchmark and CLI. Providers: Anthropic, OpenAI, OpenAI-compatible local endpoints via system curl, plus a deterministic mock for tests. The token ledger reports provider-reported usage and says so when it had to estimate.
v0.37.2412026-08-23

Sprint 241 — M37 opens: the Pro CLI's gatekeeper

  • Phase M37 opens: the POLYTONE CLI — documented publicly, not available free, the Pro subscription's USP. The free toolchain (ptc) stays complete and free; 'polytone' brings POLYTONEide's verified, token-saving generation loop to terminals, scripts and CI, behind the SAME offline PTPRO license the IDE verifies — one subscription, two surfaces. Sprint 241 ships the gatekeeper: 'polytone license activate/remove' with a fail-closed gate whose refusal teaches the way in, a hand-rolled SHA-256 + ECDSA P-256 verifier pinned by FIPS vectors and fixtures signed through the real issuance mechanics, and a parity gate that locks the CLI to the IDE's public key and checkout URL.
v0.35.2402026-08-14

Sprint 240 — baseline 7: 34/36 · 36/36 on the grown corpus

  • The seventh full measurement, first over the 36-task corpus: pass@1 34/36, pass@2e 36/36 after one repair round. Two of the three brand-new v4 tasks passed cold on attempt 1 — the task descriptions carried the fresh format surfaces on their own. Both attempt-1 failures were one taught class each and healed in one shot: the teaching-error loop is the product, and seven runs now chart it from 21 to 34 with every step attributable. The tool-call audit stayed clean: nothing beyond the language card and each agent's own task was opened.
v0.35.2392026-08-14

Sprint 239 — the corpus learns the v4 surfaces

  • Three new tier-M benchmark tasks aim the generation corpus (now 36) at exactly the surfaces the format sprints shipped: weighing a stereo field through render_stereo, building and probing a v4 wipe transition, and a rider film with two simultaneous sprites. Every reference solution passed the exact judge path first-run. The coverage gate learned history: the newest published run must cover the full corpus, older runs keep the corpus of their day.
v0.35.2382026-08-14

Sprint 238 — .ptv v4: wipes, riders, scale

  • The film timeline learns transitions and simultaneity: 'wipe' slides a directional reveal between scenes, 'also move' rides the move row above it so two or more sprites tween at once over the same scene, and a 'scale' clause stretches a sprite across its tween — all version-gated behind version: 4, with keyword-led, order-free move clauses. The rider re-renders its frame span, so the old move path is untouched, and the long-standing byte pin proves v1–v3 films render bit-for-bit as before. The video editor speaks all of it: new buttons, direction picker, scale fields, and a version line that follows the content.
v0.35.2372026-08-14

Sprint 237 — the studio's v4 controls

  • The new stereo surface reaches the hands: every track chip carries a pan slider and an echo pair (delay in steps, decay appearing once echo is on — seeded codec-legal so an emitted document is always valid), riding the standard undo path. The grid's audition preview pans with the active track, so the ear hears the placement before the codec renders it — the render stays the only truth.
v0.35.2362026-08-14

Sprint 236 — .pta v4: pan, echo, stereo

  • The audio format learns space: 'pan' places a voice between two stereo channels and 'echo' gives it a tempo-synchronous damped repeat train — both as order-free voice clauses gated by version: 4. Echo is implemented as event replication, so the whole optimized synth pipeline is untouched; stereo arrives as additive API (render_stereo + a two-channel WAV bridge) with a pan law under which a centered score's channels equal the mono render exactly — the long-standing byte pin proves older documents render bit-for-bit as before. The studio round-trips the new clauses and plays v4 sessions in stereo.
v0.35.2352026-08-14

Sprint 235 — the z-buffer audit: the overflow class closed

  • The new depth buffer went through the audit rhythm — verdicts by execution. One real defect class confirmed: a giant-coordinate model at the workshop's minimum zoom put vertices behind the camera, projected them ~1e13 px off screen, and the fill's integer edge products overflowed and crashed the render — and the old painter crashed the same way inside draw_polygon, so the class was older than the z-buffer. All five raster sites now clamp screen coordinates to ±1e9: every product stays inside 64 bits, and no point that was ever on a screen moves — the byte-pinned hashes prove the clamps change nothing for sane scenes. The stale painter's-algorithm doc lines were swept too.
v0.35.2342026-08-11

Sprint 234 — the z-buffer: surfaces can never overlap again

  • The screenshots showed the painter's algorithm failing structurally: whole-triangle depth sorting can never be right for scenes with a spanning ground plane and adjacent box faces — surfaces painted over each other, boxes looked carved, spheres sank into the ground. Every render path now runs a per-pixel depth buffer, perspective-correct: occlusion is decided per pixel, so the artifact class is gone categorically — on the fast orbit, the fine settle, and the showcase alike, at every angle of the turntable. The ground shadows became correct too. A tenth of a second more per fine render — the price of correctness.
v0.35.2332026-08-11

Sprint 233 — baseline 6: thirty-three of thirty-three, cold

  • The sixth full measurement: every task passes on the first attempt — the corpus is fully green cold for the first time, zero repair rounds. The membership line added one sprint earlier healed the last stable residual exactly as the data predicted. Six runs now chart the whole loop from twenty-one to thirty-three, every step attributable to a taught class: card lines from failure data, fix-first errors, method synonyms, one API shape decided by the models' own prior, multi-error reporting, and the final line the data demanded.
v0.35.2322026-08-11

Sprint 232 — round spheres: smooth shading in the showcase

  • The showcase render smooth-shades: vertex normals accumulate the adjacent face normals, each vertex gets one shade, and a face paints the mean of its three — spheres, tori and cones finally read round while genuinely flat geometry stays flat. Carried in the maturity pool since the format era, and the exact faceting visible in the owner's screenshots. Measured at no cost, and showcase-only by design: the byte-pinned classic paths are unchanged, hashes verified.
v0.35.2312026-08-11

Sprint 231 — baseline 5 and card v14: the data names the line

  • The fifth full measurement: fully green for the third time, every failure repaired in one round on its teaching error. The dataset's stable attempt-one residual across four runs is a single spelling — contains on a Map — because the card taught index assignment but never membership. Card v14 adds the one line the data demands. Five runs now chart the loop working: the residual gets named and taught each time the data repeats it.
v0.35.2302026-08-11

Sprint 230 — the M36 audit: six confirmed, six fixed

  • The audit over the pub-bounded machinery, every verdict by execution. The gravest: an imported generic function referenced as a value passed the checker but panicked the compiler — a three-line program, a common model idiom, and a bypass of both new gates. The imported path now carries the same teaching guard the local path always had. The reach-rule advice named an impossible fix — every wrapping it suggested reproduces the identical error, executed to prove it — and now names only working ways out, with a forwarded type parameter getting its own words. And hover was the third signature renderer that dropped the bounds.
v0.35.2292026-08-11

Sprint 229 — the surface teaches the new capability

  • The doc generator and the context slice dropped the bounds from generic signatures: an imported largest with an Ord bound rendered without it — and the slice is a generating model's whole view of an import, so the missing bound invited exactly the call the checker must then refuse. Both renderers now print the bounds; the language card teaches the new pub-bounded capability in one line.
v0.35.2282026-08-11

Sprint 228 — pub bounded functions: built-in bounds travel

  • The spec's oldest remaining not-yet narrows: a pub function may carry built-in trait bounds. The bounds ride the exported signature, the importer enforces them at the call site exactly like a local caller, and the solved type must be a built-in scalar — the synthesized methods land in the exporting module, where the instance lives. A record satisfier is a teaching error naming the real reason and the wrapper way out; a user-trait bound still refuses at the declaration. Verified across the boundary with new differential fixtures, first run green.
v0.35.2272026-08-11

Sprint 227 — baseline 4: fully green again, the repair generalizes

  • The fourth full measurement: thirty of thirty-three pass attempt one and all thirty-three the repair round — fully green for the second time. The load-bearing delta: the task whose second wrong pattern nests inside a broken match arm, unreachable by any error collection, repaired in a single round — the repair prompt now teaches that a seen error class likely applies to spots the checker could not reach yet, and the model generalized the fix to both patterns. Teaching beats collection where collection cannot go.
v0.35.2262026-08-10

M35 close — workshop shine, the true record

  • One shine session proves the phase through the committed runtime: the workshop settles into the showcase render deterministically, one click makes a real turntable film, and a mirrored composition renders with its exact-family halves agreeing pixel for pixel — the geometry-versus-rasterization contract stated honestly. The phase's record: the light, the fit, the wow, and a nine-for-nine executed review. The improvement rotation resumes.
v0.35.2252026-08-10

Sprint 225 — the M35 review: nine confirmed, nine fixed

  • The adversarial review over the shine phase, every verdict by execution — nine confirmed, all fixed in the same sprint. The gravest: the turntable-film click was dead in production, because twenty frames at the shipped size exhausted the sandbox's default fuel while CI only ever ran a toy size — the handler now passes a real budget and CI runs the shipped configuration. The settle freeze halved by rendering at the proven size and upscaling; a huge-coordinate model no longer traps the painter; deleting a chain chip cannot strand a repeat; the symmetry flag resets with its checkbox; the clamped lead expands on click; short viewports keep a usable floor.
v0.35.2242026-08-10

Sprint 224 — the song chain becomes visible

  • The sound studio's wow feature: the song chain renders as colored chips — every pattern gets a stable hue hashed from its name, repeat tokens ride as dashed badges, an unknown token flags itself red, and a plus menu appends any declared pattern. Deleting a chip follows the codec's own rule: a repeat is bound to the name before it, so removing the name takes its repeat along. The text input stays the truth; the chips are a live view over the same chain. With this, all five tools carry their wow: showcase render and turntable film, symmetry brush, filmstrip, chain chips, and the site editor.
v0.35.2232026-08-10

Sprint 223 — the filmstrip

  • The video editor's wow feature: a filmstrip under the player — up to twelve evenly spaced frames as clickable thumbnails, decoded from the real rendered film. The whole film at a glance, any moment one click away, and the playing frame's thumbnail stays highlighted while the player runs, scrubs, or jumps. Pure presentation over the frames the player already decoded — no new render work.
v0.35.2222026-08-10

Sprint 222 — the symmetry brush

  • The image workshop's wow feature: one toggle, and every stroke paints its reflection across the vertical axis. Not a brush mode — it hooks the single commit seam every tool flows through, so pixels, lines, rectangles, circles, polygons and even gradients all mirror: one gesture, both halves, any tool. Each op family reflects precisely — a horizontal gradient swaps its stops so the ramp reads the same — and text honestly declines. The mirrored ops land in the document as ordinary format lines: the export is still the drawing.
v0.35.2212026-08-10

Sprint 221 — one screen, and the stale-blob fix

  • The live error the owner hit — module mesh has no pub function — was a stale cache: a deploy replaces the runtime blob under the same path, and a browser that cached the old bytes kept running old semantics. The runtime fetch is now cache-busted by the release version, so every deploy's page loads its own blob, guaranteed. And the workspace fits one screen: each tool panel caps at the viewport and scrolls internally, the headline shrinks and the lead clamps to two lines — the page itself no longer scrolls on desktop.
v0.35.2202026-08-10

Sprint 220 — the turntable film: one click, two codecs

  • The model workshop's wow feature: one click renders twenty showcase frames a full revolution around the model — sky, ground shadows, supersampling, at your current pitch and zoom — assembles them into a real video value and exports through the y4m codec. The model becomes a film, playable anywhere, reproducible from the exported program like every tool artifact. The frame loop leans on the phase's own machinery: bulk appends, the thirteen-times-faster painter, and an honest status line reporting the real render time.
v0.35.2192026-08-10

Sprint 219 — the layout pass: every tool fits its page

  • The screenshot bugs, fixed at the shell: a video move row carries eight controls and ran past the panel edge — timeline entries now wrap inside their panel; the sound grid forced a sideways scroll at sixteen steps — cells now flex so the raster fits the panel width; the image canvas floated small in an empty panel — it now fills it, and clicks scale for free. The shared shell gained a whisper of depth and hairline title separators.
v0.35.2182026-08-10

Sprint 218 — M35 opens: the light

  • The model workshop stops looking flat: a new showcase renderer places every scene under a dusk-gradient sky and casts soft ground shadows — each triangle projects along the key light onto the base plane — supersampled twice over. Purely additive: the existing render entries are byte-untouched, and the whole thing is affordable as the default because the painter got thirteen times faster earlier in the phase. The workshop now settles into this view at 640 by 448, filling its panel; dragging keeps the fast path, and picking a custom background keeps the classic flat render alive.
v0.34.2172026-08-08

Sprint 217 — the M34 audit: two confirmed, two fixed

  • The phase audited itself, every verdict by execution, and both confirmed defects were fixed in the same sprint. A failed for-loop pattern leaked its scope frame — invisible when the first error aborted everything, but under multi-error reporting a later top-level function hit the nested-function guard with a fabricated error, and a match arm's cleanup removed the wrong scope, suppressing exactly the sibling errors the previous sprint exists to surface. And a mutating method on an indexed receiver evaluated an impure index twice: a random draw in the index read one element and wrote back over another, desynchronizing the generator — the index is now pinned once, shared by read and write-back, with a differential fixture pinning both backends.
v0.34.2162026-08-08

Sprint 216 — sibling match arms report independently

  • A broken match arm no longer hides its siblings: arm patterns bind arm-locally, so the walk continues past a failed arm and collects the later arms' errors as independent extras — the first error stays byte-identical, and exhaustiveness over broken arms is skipped because it would cascade. And the deeper truth of the motivating case is recorded honestly: its second wrong pattern nests inside the broken arm's body, unreachable until the first is fixed by any strategy short of full pattern error-recovery — settled as out of proportion for the win, with the sibling class real, covered, and pinned.
v0.34.2152026-08-08

Sprint 215 — the judge speaks every error; an honest correction

  • ptc test and run now print every independent declaration-level error before aborting — execution still needs a fully-checked program, but the report may be rich, and the judge's output is the repair prompt: this is what puts multi-error reporting into the teaching loop. And an honest correction: replaying the real benchmark candidate showed its two wrong field names live in one function, which per-declaration isolation does not split — the records say so now, and the precise next candidate is named: match-arm patterns bind arm-locally, so collecting pattern errors across the arms of a single match is the surgical extension the motivating case actually needs.
v0.34.2142026-08-08

Sprint 214 — one check, every independent error

  • One check now reports up to five independent declaration-level errors — the measured token-economy lever, since each surfaced error can save a whole repair attempt. The architecture is the minimal sound one: every table is complete before the first body is checked and function bodies see no top-level variables, so declarations are independent by construction; the first error stays byte-identical, and the diagnostics array was the JSON contract from day one, so nothing bumps. The language server publishes them all, the workbench's plus-N-more path lights up, and the CI proof pins the motivating failure: the benchmark task whose two stacked wrong field names once cost an extra fifty-thousand-token attempt now surfaces both in a single check.
v0.34.2132026-08-08

Sprint 213 — intent blocks: settled measured-not-now

  • The phase's most speculative candidate got its verdict from the instrument, not from taste: across all three baselines, tokens-to-green is the fixed per-attempt cost times the number of attempts — emitted code is two orders of magnitude below it, and every improvement came from removing semantic misfires, not from writing less. An intent block that saves boilerplate cannot move the number while attempts dominate; the lever is first-attempt correctness. Settled the way Num and Byte once were: the keyword stays reserved, the spec records the decision, and the strongest sub-idea — intent examples as the source from which test blocks derive — is filed for re-opening under an output-token-sensitive metric. The effort goes where the measurement points: multi-error reporting, where each surfaced error can save a whole attempt.
v0.34.2122026-08-08

Sprint 212 — the envelope plateau

  • The tonal synthesis loop skips the settled envelope: inside the plateau every factor is exactly one or settled, and IEEE arithmetic keeps the constant bit-exact — so the majority of every held note pays one window compare instead of two to three divisions per sample. Audio render gained another sixteen percent, thirty cumulative since the pass began, byte-identical on the reference document and the zero-attack, zero-release edge cases alike.
v0.34.2112026-08-08

Sprint 211 — baseline 3: the tuple decision validated

  • The third full measurement against the improved toolchain: thirty of thirty-three pass attempt one and thirty-two the repair round. The pixel-probe task now passes cold via exactly the tuple shape both earlier baselines had guessed — yesterday's design decision validated by today's measurement. A new candidate fell out of the run: stacked errors of the same class surface one per repair round, so a single repair cannot see them all — multi-error reporting joins the candidate list.
v0.34.2102026-08-08

Sprint 210 — get_pixel speaks the models' prior

  • get_pixel returns a tuple now: in both full benchmark baselines the generating model independently wrote case Some with a destructured r, g, b — the destructuring form is the prior, and tuples destructure by pattern where a list needed index reads. The two measured attempt-one failures this shape caused were the motivation. Migrated in one move across the stdlib's asserts, four examples, the benchmark reference, the guide, and the regenerated API reference — every gate green on the first run.
v0.34.2092026-08-08

Sprint 209 — the audio synthesis pass and the move memo

  • The audio synthesis loops shed their per-sample calls: the wave dispatch compared up to three texts per sample before reaching the sine — it is now an integer computed once per event with the math inlined verbatim, the min and clamp helpers are inlined branch for branch so even the zero-attack NaN path stays bit-exact, invariant conversions are hoisted, and the accumulator prefills in chunks. Audio render is seventeen percent faster, byte-identical on a seven-voice document and the envelope edge cases alike. The video move arm draws each distinct pixel offset once and replays it — twenty-two percent faster, byte-identical.
v0.34.2082026-08-08

Sprint 208 — List.slice and the y4m frame dedup: the replay film in 0.3 seconds

  • Slice's strict half-open contract, which Text and Bytes already carried, now lives on List too — the consistency a generating model expects, executed as one host-level copy. The y4m export uses it to deduplicate repeated frames: a play entry emits identical frames, and each distinct frame now converts once, its plane bytes replayed — native list equality is a single opcode. The twenty-entry replay film's whole pipeline fell from twenty-eight seconds to zero point three, byte-identical. The measurement also cleared a false lead: most of the earlier bench's time was its own interpreted sha256 over six megabytes.
v0.34.2072026-08-08

Sprint 207 — List.extend: a frame append is one copy

  • The language gained its third mutating method: extend, a bulk append executed as one host-level copy where a per-element push loop cost ten interpreted operations per element. It rides the existing in-place machinery — a plain-local receiver appends without a full copy, and extending a list with itself keeps the copying path and reads the old value, exactly as the mutation rules promise. No encoding change; a new differential fixture pins interpreter and compiled backend to the same bytes, and the card teaches the method. The video pipeline cashes it immediately: a twenty-entry replay film renders nine times faster, byte-identical.
v0.34.2062026-08-08

Sprint 206 — a film rasterizes each scene once

  • The video renderer memoizes rendered scenes: a film replaying one scene across many timeline entries paid a full rasterization pass per entry — now each distinct scene renders once, on first use, so a broken but unused scene still never errors and every error text lands exactly where it used to. Byte-identical throughout. The honest measurement names the next target: frame assembly now dominates, one interpreted push per integer per frame — a bulk extend method for lists will make a frame append a single host-level copy.
v0.34.2052026-08-08

Sprint 205 — the flood fill sheds its quadratic stack

  • The flood fill's pop rebuilt the whole work stack on every visited pixel, making a large fill quadratic in the canvas area — a forty-fill document at 512 by 384 did not finish in five minutes. The pop is now a cursor: the slot array only grows, a top index marks the live end, and pushes overwrite spent slots. The former-timeout case renders in three seconds, at least a hundredfold, and the output is byte-identical — every reference hash unchanged, the differential and all suites green. The sprint's own benchmark sizing found the bug: the measurement instrument keeps finding the next target.
v0.34.2042026-08-08

Sprint 204 — baseline 2: the corpus is fully green

  • The second full measurement against the improved toolchain: pass at one rose from 21 to 30 of 33, pass at two-with-error from 30 to 33 — the corpus is fully green for the first time. Mean tokens-to-green fell sixteen percent and the run's total spend twenty, because fewer repairs were needed; the three remaining attempt-1 failures each repaired in a single round, one of them on exactly the new Map-synonym teaching error. The measure-teach-measure loop has closed its first full cycle, every delta attributable and pinned.
v0.34.2032026-08-08

Sprint 203 — the CoW-defeat pass: 13× on the 3D render

  • The mesh render was cloning the whole frame buffer once per triangle: the polygon painter delegated through a field assignment whose read keeps a second live reference during the call, so copy-on-write duplicated the buffer on every first write. Direct field writes — the set_pixel pattern — make a 1166-triangle fine render thirteen times faster, and the output is byte-identical, sha256-pinned before and after. The y4m emitter builds all three planes in one pass, also pinned. And method near-misses now teach the spelling first: contains on a Map answers with .has — the benchmark's repair round fixed a task in one shot on exactly that error. One candidate was refuted by measurement and reverted; the number decides.
v0.34.2022026-08-08

Sprint 202 — the allocation pass: cloning a value is O(1) again

  • The meticulous performance review found the interpreter's remaining hot-path cost: allocation churn. Cloning a record was a malloc and cloning an enum was two — the value doc's own O(1)-clone claim was false for nominal types. Names now live behind reference counts, text literals are pre-wrapped at decode time so a literal in a loop is a bump instead of a malloc per pass, and text indexing walks the length only on the error path. Measured: a record-and-enum loop dropped thirty percent, a literal-split loop eleven. Semantics pinned unchanged — sixty-two suites, the 46-fixture differential, and every blob budget green.
v0.34.2012026-08-08

Sprint 201 — card v10: taught from the failure dataset, measured twice

  • The language card learned the baseline's attempt-1 failure classes — including two pure card gaps: trim() and map index assignment existed all along but went unmentioned. Measured twice on the twelve tasks that failed attempt 1 under v9: six passed fresh, the residue was a single class — a bare empty literal without its type — and one card line later exactly those four healed. Ten of twelve former failures now pass attempt one cold, a projected corpus pass at one of about 31 of 33. The residue names the next targets: a Map answers has, not contains, and get_pixel's list shape surprises models.
v0.34.2002026-08-08

Sprint 200 — errors name the fix first: the return class falls

  • The baseline's entire never-green class, closed by two teaching errors: a bare Ok, Err, or Some statement inside a function returning Result or Option now says write 'return Ok(...)' — it used to point away from the fix — and a discarded value whose type equals the declared return type says write 'return' in front of it; the previous repair had followed the old wording literally and still failed. The delta is measured, not claimed: the recorded never-green candidates re-entered the repair round against the new toolchain and went three of three green, and both new messages are pinned as CI replays.
v0.34.1992026-08-08

Sprint 199 — the Fable baseline: the phase has its number

  • The opening measurement, actually run: Claude Fable 5 as the generator over the full 33-task corpus — per task one cold agent reading exactly the frozen card and the task, tool-call-audited so nobody peeked, judged by the exact ptc-test path; failures got one repair agent fed the real toolchain error. pass@1 21 of 33, pass@2e 30 of 33, tokens-to-green median 50372 harness tokens — the before/after instrument every M34 approach must now move. The first data-driven target fell out of the failures themselves: all three never-green tasks die on one class, a value left in expression position where return would have carried the type.
v0.34.1982026-08-08

Sprint 198 — M34 opens: the token economy, the instrument

  • The measurement before the claim: the generation benchmark now accounts tokens per task and attempt from the provider's own usage fields and reports tokens-to-green — what a task costs until the tests are green: the first attempt alone when it passes, both attempts through the teaching loop, nothing when never green. The instrument never estimates: a silent provider yields null, and a needed attempt without usage never becomes a partial sum. This is the number Phase M34 optimizes — every coming approach must move it.
v0.33.1972026-08-08

M33 close — the web viewer, the true record; the tool arc complete

  • The adversarial review confirmed eleven defects, every verdict reached by executing the code — led by a pending edit that could land in the wrong file after navigation, its cross-browser trigger the Alt-arrow shortcut hijacking word-left inside the source editor. All fixed, each with its regression pinned. The proof: an authored site session — a template page edited to reference the demo's image and 3D model — renders through the real codecs, deterministic and byte-stable. Phase M33 complete, and with it the whole M29–M33 arc: all five tools rebuilt on the shared shell, every format rendering through its own codec.
v0.33.1962026-08-08

Sprint 196 — the site editor: the browser you can write in

  • The source pane became a live editor: edit the viewed document and the page re-renders as you type — a .ptw page, a .pti image, or a whole .pt app. New files start from minimal codec-valid templates, one per format, and every template is test-pinned to render through its own codec. Rename, delete, download, reset, and a bounded site undo complete the workshop: the demo site is now yours to rewrite.
v0.33.1952026-08-08

Sprint 195 — the browser grows tabs, history, bookmarks

  • A tab strip above the address bar — each tab carries its own history and cursor, close with ×, open a fresh one with +. The left panel now lists the active tab's history newest-first, and clicking jumps the cursor there without killing the forward branch; a real navigation push does. Bookmarks live behind the ☆ in the bar and survive route revisits as pure shell state — never part of any document. Alt with the arrow keys navigates like a browser, bound once at module level.
v0.33.1942026-08-08

Sprint 194 — M33 opens: the web viewer, the foundation

  • The web viewer joined the shared tool shell — the last of the five tools: a site panel with the document list and a live source view on the left, the browser with address bar, history buttons and a new reload on the right; the page title now surfaces in the shell. And .ptm addresses finally render: the codec has listed model.ptm among legal link targets since Sprint 30, but the shell showed only a byte-count note — a mesh bridge closes the gap, and the demo site gained a 3D shrine. The site itself became mutable state, the coming site editor's foundation.
v0.32.1932026-08-08

M32 close — the video editor, the true record

  • The review's two confirmed defects, both fixed: the total readout now speaks the codec's truth — per-entry frame quantization with the one-frame floor, not the nominal sum that drifted at low fps (the verifier reproduced the drift by running the real duration logic) — and a duration edit re-renders the timeline, so total and clamped value show immediately. The proof: an eased film, authored the way the UI authors it, round-trips byte-identically and renders deterministically through the real codec. Phase M32 complete; next: M33, der Web-Viewer, the arc's final tool.
v0.32.1922026-08-08

M32 — the scrub player

  • Every frame reachable by hand: the film player gains a scrub slider and a frame-and-seconds readout. Scrubbing pauses playback and paints the exact frame from the decoded film; play resumes from wherever the hand left off.
v0.32.1912026-08-08

M32 — easing becomes authorable

  • The ease clause gets its select: since 0.27.169 the editor could import a codec-valid move with ease in-out and round-trip it — but never author one. Every move row now carries an ease select feeding the same field the round trip already pins, plus a per-row duplicate button and a total-duration readout beside the timeline heading.
v0.32.1902026-08-08

M32 opens — the video editor, the foundation

  • The video editor moves onto the shared tool shell: the library on the left — scenes, sprites, the embedded soundtrack and the live .ptv document — the timeline, player and exports on the right. Pure layout; every seam and test untouched.
v0.31.1892026-08-08

M31 close — the sound studio, the true record

  • The adversarial review reproduced four format-truth defects against the live codec, and all four are fixed: deleting a pattern drops the repeat tokens bound to it — no stranded chain, no silent rebind; an emptied chain teaches at render; the hold rule is enforced everywhere, orphaned holds decaying when a note cycles off and the import refusing a leading hold with the codec's own words; consecutive repeats are legal — the codec's actual rule is any earlier pattern entry, and the studio now mirrors it instead of a stricter invention; and a pattern cannot claim the repeat form as its name. The proof: a full v3 session — drums, master, holds, repeats — round-trips through the studio's own parse and emit and renders deterministically through the real codec. Phase M31 complete; next: M32, der Videoeditor.
v0.31.1882026-08-08

M31 — the live studio

  • Cells speak when you set them: placing a note plays a short WebAudio preview in the active track's waveform — drums as shaped noise bursts, a kick through a low-pass — pure UI feedback born from the click gesture, so autoplay policies are satisfied and the codec's deterministic render stays the only real sound. And mute and solo, honestly scoped: per-track toggles filter what Render and Play sends to the sandbox, while the document and every export stay the full truth. All tracks muted teaches instead of rendering silence.
v0.31.1872026-08-08

M31 — the chain and the holds

  • xN repeats survive the round trip: the session keeps the song chain raw — song: beat x3 imports as the two tokens it is, re-exports byte-identically, counts toward the v3 version choice, and the inline guard validates repeats by the codec's own rule. Before, imports expanded repeats into plain names and every re-export lost them. And note holds enter the grid: the equals sign is the codec's own cell form since v1 — the studio used to refuse it on import; now a hold row under the note grid toggles it per step, and the old rejection test became the round-trip test it deserved.
v0.31.1862026-08-08

M31 opens — the sound studio, the foundation

  • The studio moves onto the tool shell: tracks, patterns, song chain and the live .pta document left, the note grid and player right. The master headroom finally gets its slider — the state, exporter and importer could all carry it since 0.23.149; only the control was missing (below 100 the document declares v3, the version following the content as ever). And patterns become manageable: rename with the chain following token for token, duplicate with deep-copied cells, delete with the chain cleaned.
v0.30.1852026-08-08

M30 close — the image workshop, the true record

  • The adversarial review earned its keep: three review dimensions, every finding independently re-verified against the live file — five confirmed, five fixed. The redo branch now dies when a fresh action forks history (the intended clear had silently not applied; the review caught the drift), a route revisit resets both stacks, the keydown listener no longer stacks per visit, and a crafted layer name can no longer fabricate the hidden marker. The proof: every op form the new UI emits renders green through images.draw_ops, and one full session — stamps, filters, drawn v5 ops, a hidden layer — renders deterministically through the tool's own renderProgram. Phase M30 is complete; next per the arc plan: M31, das Sound-Studio.
v0.30.1842026-08-08

M30 — the workshop

  • Layers become workable: rename — the name stays one clean token for the layer-comment convention, escaped in the row as ever — and duplicate with deep-copied ops. Pattern stamps (frame, grid, sun) append parametric op groups sized to the canvas — ordinary ops on the active layer, so undo, redo and the document carry them. And keyboard shortcuts: one key per tool, Cmd or Ctrl plus Z for undo, with Shift for redo, ignored while typing.
v0.30.1832026-08-08

M30 — the v5 tools arrive in the UI

  • The codec's whole v5 vocabulary, drawable: an ellipse tool (two clicks — center, then radii), a polyline tool (double-click closes), an outline checkbox for rect, circle and ellipse, and the size slider now gives line its v5 width. An eyedropper picks any rendered pixel into the color well, four filter buttons append whole-canvas ops to the active layer — undo and redo carry them like any op — and redo joins undo: a bounded branch that dies when a fresh action forks history.
v0.30.1822026-08-08

M30 opens — the image workshop, the foundation

  • The image editor moves onto the shared tool shell: a two-panel workshop — tools, palette, layers and the live .pti document on the left, a large scrolling canvas on the right — plus zoom 8x and Fit, and the lead finally says v5, the format it has exported since Sprint 141. Every pure seam and safety pin unchanged.
v0.29.1812026-08-08

M29 close — the model workshop, the true record

  • One end-to-end test walks a whole workshop session — load a curated scene, edit it the way the shape list does, regenerate the document, settle a fine render at a chosen zoom and background — every step the tool's own code path, the output deterministic. Phase M29 is complete. What the phase turned the 3D page into: the reported render failure diagnosed and pinned by eleven permanent tests; a two-panel workshop with the shared tool shell; wheel zoom and a camera HUD; supersampled, fill-lit, background-chosen rendering with the fast path byte-stable throughout — the differential never moved; a turntable; three curated scenes; .ptm import; and the model block as an editable, round-trip-pinned shape list. Next, per the arc plan: M30, der Bildeditor.
v0.29.1802026-08-08

M29 — the workshop

  • The model block became an editable shape list: every line of the document appears as a row — kind, its numeric fields in the grammar's own order (all seven shapes, triangle's at-less form included), the optional yaw, the color — with per-field inputs, duplicate and delete, add-shape buttons with sensible defaults, and a fifty-step undo. The source stays the single truth: the list is a parse of it, every edit regenerates it, and a line the grammar does not cover survives untouched as raw text. The round trip is pinned by tests over every curated example, and every default shape renders green through the real codec.
v0.29.1792026-08-08

M29 — the view learns to look good

  • mesh.render_view_fine: the same rasterizer rendered at twice the size and box-averaged down — crisp edges from identical geometry — plus a soft fill light so undersides keep their color instead of falling to black, and a chosen background. The fast path is untouched: render_view_from delegates into a shared parameterized core that stays byte-for-byte what it always produced, so the differential held 46 of 46 without touching a fixture. The workshop settles through the fine path while the quarter-resolution orbit stays fast, a background picker joins the toolbar, and a turntable toggle auto-orbits the model — reduced motion honored, any drag stops it.
v0.29.1782026-08-08

M29 opens — the model workshop, the foundation

  • The 3D report, diagnosed and pinned: the owner's screenshots showed v0.25.159, which carried the dropped-render class — a render requested while one was in flight was silently discarded, so a cold start could strand a blank view. The fix shipped in 0.27.169 and is live; this sprint pins the whole path with permanent end-to-end tests, so the class cannot return unnoticed. And the viewer became a workshop: a real two-panel layout, a 480 by 360 view that scales to its panel, wheel zoom backed by a new stdlib surface — mesh.render_view_from, whose zoom is a factor on the automatic framing distance, 1.0 exactly equal to render_view by delegation — three curated example scenes, each rendered green by the tests, .ptm file import, and a camera HUD.
v0.28.1772026-08-08

M28 close — Platz & Spielraum, the true record

  • The proof is one end-to-end test: a single playground session whose main takes all four sandbox capabilities — the clock knob dates the report and seeds the dice, env names the author, a data file feeds disk.read — and which Check type-checks without a print, Format fixes as a fixpoint, two seeded runs reproduce byte for byte, and whose written .pti renders to a real .ppm through the panel's codec path. First-run green. Phase M28 is complete: the footprint loop took compiler/target from 44 gigabytes to a sub-gigabyte steady state and removed the cause, shrank the shipped web runtime from 1770 to 1160 kilobytes on the measured s-profile, and bounded it all with gates; the playground became a workbench — Format, Check, sandbox knobs, data files, and an output panel that renders every format through the real codecs. No next phase is scheduled — the owner directs what follows.
v0.28.1762026-08-08

M28 — the output panel learns every format

  • A written native document renders in place: a program that writes a .pti, .pta, .ptm, .ptw or .ptv through disk now gets a Render button on its file card — the playground runs the Viewer & Player's own per-format program in the sandbox, the real codec, nothing reimplemented, and previews its outputs with the very card builders the panel already has: canvas for .ppm, an audio player for .wav, a sandboxed page for .html, playable video for .y4m. Text outputs become readable, not just downloadable — every written text-format file gains a collapsed source preview, and .bmp files preview natively as images. Two end-to-end tests pin the loop: a program writes a .pti, and the panel's render path turns it into a real .ppm through the image codec.
v0.28.1752026-08-08

M28 — Playground v3: the whole toolchain in the page

  • Format and Check join Run, Tests and Share: Format runs the same polytone-fmt the CLI runs — a new entry point in the web runtime, fifteen kilobytes, well inside the size budget — so the playground's canonical form and ptc fmt cannot drift; Check type-checks through the context slice without executing anything, marking the first diagnostic's line. And the sandbox knobs make capabilities real in the browser: a clock field seeds the sandbox clock and, through the host's derivation, the Rng too — one knob, both deterministic; an env field feeds sys.var; and the module bar accepts data files for disk.read. A main taking Clock, Rng, Env or Fs now runs meaningfully in the playground, deterministic by construction.
v0.28.1742026-08-08

M28 — the loop's exit gate: measured, bounded, gated

  • The loop measured its own cost and corrected course: benching the compiled backend against the old untuned blob showed optimize-for-size-z cost 4 to 28 percent runtime for only about 9 percent more size than s — so the blob profile is now opt-level s, within 3 percent of the untuned blob's speed at minus 35 percent size (web runtime 1770 to 1145 kilobytes, VM runtime 314 to 253). The measurement lives as a comment on the profile itself, so the next tuner inherits the data. The differential stayed 46 of 46.
  • And the gates, so nothing regrows: preflight gained wasm blob size budgets — web at most 1300 kilobytes, VM at most 320; a regression past them fails the gate and demands a deliberate, measured raise — plus a non-failing target-directory hygiene note past 10 gigabytes naming cargo clean. The VERSION-file routine proved itself on this sprint's own bump: the version change rebuilt one leaf crate in about five seconds where it used to re-fingerprint the whole workspace.
v0.28.1732026-08-08

M28 opens — the footprint loop, iterations 1–3

  • Measured first, then fixed: compiler/target had grown to 44 gigabytes — 43 of them debug artifacts across 411,287 files — because every sprint's workspace-version bump re-fingerprints every crate, the whole workspace recompiles, and stale artifacts never leave. Iteration 1 cleaned it to 24 megabytes. Iteration 2 removed the cause: the crate version is frozen on purpose, the real toolchain version lives in one VERSION file, injected at build time into exactly the three crates that display it — a sprint bump now recompiles three leaf crates instead of the world, and the docs-record gate holds the file to the changelog so ptc version can never lie.
  • Iteration 3, the bytes every visitor downloads: the shipped wasm blobs got a real build profile (none existed) — optimize-for-size, fat LTO, one codegen unit, stripped symbols. The web runtime shrank from 1770 to 1042 kilobytes, minus 41 percent; the VM runtime from 314 to 237. The release binary gained thin LTO and stripping. And the differential stayed 46 of 46 against the optimized blobs — semantics are pinned by the gate, not by the optimizer.
v0.27.1722026-08-08

M27 close — the abstraction arc completes

  • Phase M27 (the debt pass, Sprints 168–172) is complete, and with it the whole M25→M26→M27 plan of record is fully executed: every user type owns its behaviour (M25, methods), traits abstract over it with static dispatch (M26, contracts, bounds, Ord and Display down to the builtins and across modules), and the debts those features made worth paying are paid (M27): the hashed Set/Map backing closed a five-phase-old finding with nothing observable changed and a 20,000-element workload dropping from 7.5 seconds to 0.03; the whole M23 register closed, fourteen findings each with a regression test; Num and Byte are settled as never types, closing spec §9's last language deferral by decision; and the benchmark corpus grew to 33 with tasks that need the new surface. The codec stayed v9 across all three phases; the differential grew from 36 to 46 fixtures, every one passing on its first run. No next phase is scheduled — the owner directs what follows.
v0.27.1712026-08-08

M27 — the benchmark learns the new surface

  • Three corpus tasks that need methods and traits, so the generation loop can measure whether the M25/M26 priors now match: shape_area (tier S — a record method summed over a list), money_order (tier M — with Ord: and with Display: on a record, the hidden tests forcing the trait forms: "{Money(cents: 5)}" == "$0.05" and .lt() as a method), and season_label (tier S — enum Display reading a payload through match self:). Every reference passed its own hidden tests through the exact judge path on the first run; the corpus grows from 30 to 33, the CI gate covers all of them, the benchmark page gains a Methods & traits area, and the drift gate holds the site table to the task directories row for row. Fresh pass@1 and pass@2e runs stay owner-executed with their own key, as every phase records.
v0.27.1702026-08-08

M27 — Num and Byte are settled, never types

  • Spec §9's last language deferral closes — by decision, not arrival. Num and Byte are settled as never types: a Num supertype would reintroduce exactly the Int/Float coercion ambiguity the split exists to prevent, and a scalar Byte would give one value two spellings — a Bytes element is already an Int 0–255. Both names stay reserved with permanent teaching errors naming the form to write, and the Num error no longer says deferred: 'Num' is not a type — POLYTONE keeps Int and Float separate on purpose; choose one explicitly. §9 now holds only intent blocks and the formal memory model.
v0.27.1692026-08-08

M27 — the M23 register closes

  • All fourteen open M23 findings (rounds 3–6), each with a regression test — nothing from M23 remains open. Mesh: out-of-order trailing clauses teach the order, the packed sort key's bounds are documented, and the 3D viewer queues a render requested mid-flight instead of dropping it, so a cold start can no longer strand the quarter-res upscale. Video: the editor imports and round-trips a codec-valid ease move; an audio: block after scenes or the timeline is an error, not silently swallowed; blank lines inside the embedded score survive both directions; and the film program renders byte-for-byte the score the .ptv embeds — one normalized source, two uses that cannot drift.
  • Web: a * inside a code span is literal, so inline spans always nest — strong and code tags can no longer interleave; duplicate headings get numbered anchors (#intro, #intro-2); and the table parser teaches one-head-first-then-rows and demands a head. Benchmark infra: all five format-doc titles name their newest version, a zero-test judge run fails instead of passing vacuously (runner and CI gate both), scratch candidates carry model and PID so parallel runs cannot corrupt each other, the drift gate pins headline pass counts to the rows' own arithmetic, and a typo'd --only errors loudly instead of reporting zero tasks normally.
v0.27.1682026-08-08

M27 opens — the hashed Set/Map backing

  • Sets and maps are hash-indexed, closing a debt deferred five phases (M12 F1.7): elements and keys carry a structural hash mirroring equality exactly — order-independent for sets and maps, -0.0 files with 0.0, and any value can still be a key, records, tuples and nested collections included. Membership, get, has, add and key update are O(1) expected instead of a linear equality walk; building n elements is O(n) instead of O(n²). Nothing observable changed — the spec needed no edit beyond its version line: iteration order is still insertion order, dedup still keeps the first occurrence, a repeated map key still keeps its position and takes the last value, and equality is still order-independent.
  • A mutating method on a plain local now runs in place: s.add(v) used to round-trip its receiver through the stack as a shared reference, so copy-on-write duplicated the whole set every call. Lowering now takes the receiver out of its slot for the call — existing opcodes only, the codec stays v9 — whenever no argument mentions the receiver; s.add(s.len()) keeps the copying path and still sees the old value, pinned by a test. Together: a 20,000-element set-and-map workload dropped from 7.5 seconds to 0.03 — an algorithmic win, not a constant. The fixture passed the differential on its first run: 46 fixtures, old and new runtime agreeing byte for byte.
v0.26.1672026-08-08

M26 close — spec §9 loses its oldest entry

  • Phase M26 (Abstraction II — traits, Sprints 161–167) is complete, and spec §9 loses its oldest entry: traits, deferred there in Sprint 1, are closed across §30 — trait declarations and with blocks held to the exact contract, bounds and multi-bound on generic functions, Self, the built-in Ord and Display (equality deliberately structural, never a trait), and builtins, generic types and imported stdlib types under bounds. Static dispatch through the existing monomorphizer the whole way: the codec stayed v9 all phase, the lowerer, PTIR, VM and compiled backend learned nothing, and the differential grew from 39 to 45 fixtures with every one passing on its first run. A records-only close; next, per the plan of record: M27, the debt pass.
v0.26.1662026-08-08

M26 — the payoff: builtins, stdlib, and the proof

  • The scalar builtins carry the built-in traits they honestly support: Int, Float and Text are Ord (the types < orders), those plus Bool are Display — so one bounded sort serves a user type, Int, Text, and a stdlib type alike. The compiler synthesizes the method (Int.lt IS <, Int.show IS the spelling) only where a bound actually lands, so an unused bound costs nothing. And an imported pub type satisfies a built-in trait bound through the with block it exported — built-in traits mean the same thing in every module, so the impl travels with the type: time.Instant is Ord and Display (sorts on the timeline, spells its .iso() form), time.Duration is Ord, http.Status is Display. The fixture passed the differential on its first run: 45 fixtures.
  • The M26 proof, examples/leaderboard.pt: a user trait implemented in with blocks, Self, Ord and Display on a record, an enum and a generic type, multi-bound functions, and one bounded sort serving user, builtin and imported types alike — the design claim is a test ("{p}" == p.show(): the spelling is a call, dispatch is static). Pure, seven deterministic tests, first-run green, CI-gated. The guide gained a Traits section with recorded outputs, and the IDE language card bumped to v9 to teach the whole trait surface — and to correct a line that had been false since Sprint 159.
v0.26.1652026-08-08

M26 — multi-bound; generic types under bounds

  • A type parameter may now carry several traits, joined with + — fn podium[T: Ord + Display](a: T, b: T) orders with one bound and spells with the other. A call on the parameter resolves against ALL of its traits, and ambiguity cannot arise: two traits that declare the same signature cannot be bound together (a teaching error at the function — a call on T must have one meaning), and repeating a trait teaches that one mention grants the whole contract. Propagation carries the whole list, and the error's fix joins it: declare this function's parameter as [U: Ord + Display].
  • An instantiated generic satisfies a bound through its base's with block — Ranked[Int] implements what Ranked implements, clearing Sprint 162's defer error. The bound's method is itself a template, instantiated per call through the same bindings that substitute the types — still fully static, no vtables. This also closed a silent gap: a generic owner's Display typechecked but interpolation fell back to the structural spelling; now "{Ranked(label: "z", value: 9)}" is the call it is. The fixture passed the differential on its first run: 44 fixtures. Trait-side type parameters stay out deliberately — Self already gives a trait's signatures the implementing type.
v0.26.1642026-08-07

M26 — Display: a type spells itself

  • Display is the second built-in trait: a type that implements it chooses its own text form in interpolation — with Display: holds one signature, fn show(self) -> Text, and "{m}" on a Money value prints $12.34 instead of Money(cents: 1234). An interpolation segment whose type implements Display IS the call: "{m}" is "{Money.show(m)}", rewritten by the monomorphizer where methods hoist, so the lowerer, PTIR, codec, VM and compiled backend learned nothing — the new fixture passed the differential on its first run, 43 fixtures. A [T: Display] bound carries the spelling into a generic body, resolved per instance; the segment's own type decides, statically — a type without Display, and a container of Display elements, keeps the structural spelling. The method is named show, not to_text: the prelude's Bytes.to_text returns Option[Text], and one name must keep one shape.
  • print of a non-Text value now teaches the door any value fits through: "print takes a Text — interpolate the value instead: print(\"{x}\") spells Money (or any value) as Text" — the old mismatch suggested changing the declared type. The spelling reaches print through interpolation, so a Display type's own form arrives with no print-specific rule.
v0.26.1632026-08-07

M26 — Self and the built-in Ord

  • Self now names the implementing type in any member signature — fn twin(self) -> Self, fn tied(self, other: Self) -> Bool — in plain methods, trait signatures, and with blocks alike. It substitutes where methods hoist, so nothing below the type checker learns it; on a generic owner it arrives fully applied, and inside a bounded function a bound's Self is the parameter itself: fn lt(self, other: Self) on a T receiver takes another T. Self is never a declarable name.
  • Ord is the first built-in trait: predeclared in every module and implemented like any other — with Ord: fn lt(self, other: Self) -> Bool, whether self orders strictly before other. One signature, from which any sort, minimum or comparison chain builds through a [T: Ord] bound: the new fixture sorts two different user types through one bounded insertion sort and passed the differential on its first run — 42 fixtures. And equality is deliberately NOT a trait: == and != are already structural for every value, so an Eq bound would grant nothing a type does not have; the key contract for hashed collections is a later, separate decision.
v0.26.1622026-08-07

M26 — bounded generics: [T: Shape]

  • A generic function's type parameter may now carry a trait bound — fn total_area[T: Shape](shapes: List[T]) — so it can finally call something on its parameter instead of only moving it around. Inside the body a T-typed value has exactly the bound's methods, s.area() resolving against the trait's signature, mut self under the usual mutating rules; on an unbounded parameter a method call stays a teaching error. At every call site the inferred type argument must implement the bound — a record or enum with the with block, or another parameter carrying the same bound, which is how a bounded value flows through helper functions. Violations teach the exact fix: add 'with Shape:' to Plain's declaration; declare this function's parameter as [U: Shape].
  • Static dispatch, zero cost: the checker records a bounded call as T.method, and the monomorphizer substitutes the concrete owner per instance through the same bindings that substitute the types — each instantiation calls the implementing type's own method directly, no vtables, no runtime dispatch, nothing below the type checker changed. The new fixture (two instantiations of every template, bound propagation, and mut self through a bound) passed the differential on its first run: 41 fixtures.
v0.26.1612026-08-06

M26 opens — traits: trait + with Trait:

  • Traits land — the item spec §9 had deferred since Sprint 1. A trait names a behaviour contract: trait Shape: opens an indented list of method signatures (docs allowed, bodies not — a body is a teaching error naming the with block it belongs in, mut self receivers included), and a type implements it inside its own declaration with a `with Shape:` block after its fields and plain methods — behaviour stays where the type is, the same one-canonical-place rule methods follow. The block must implement exactly the trait's signatures: every one present, nothing extra, same receiver form, same parameter types, same return type — each violation a teaching error naming both sides.
  • An implementation IS a method IS a function: a with block's methods join the type's method surface — same one-name-one-thing rules, same context-slice and ptc doc presence, and the same Type.method hoisting path — so the lowerer, PTIR, the codec (v9), the VM and the compiled WASM backend learned nothing, and the new traits fixture (record + enum + mut self through a trait) passed the differential on its first run: 40 fixtures. Twelve new type-checker tests and six parser tests pin the contract. Phase M26 continues per the plan of record: bounded generics [T: Shape], Eq/Ord, Display, then the stdlib payoff — static dispatch only, through the existing monomorphizer.
v0.25.1602026-08-06

M25 — the proof; phase close

  • The M25 proof: examples/itinerary.pt, one program in which every behaviour lives on the type it belongs to. A day-trip planner: an enum method reads its payload with match self (Mode.pace, Mode.label), a record method calls a method on its own field (Leg.duration reaches self.mode.pace()), a mut self method builds the plan in place (Plan.add), a generic method infers from its receiver and reorders its type's parameters (Ranked[K, V].flipped() -> Ranked[V, K]), and the stdlib's own methods chain across modules (Instant.plus, Duration.in_minutes, Table.text/int/keys on the parsed TOML). The phase's design claim is a test: t.plus(d) == time.add(t, d) — a method IS a function. The program is pure, no capabilities and no mocks, so its seven tests are deterministic by construction; all seven passed on the first run. CI-gated in the proof suite beside pulse, digest, roster, logparse and gallery.
  • Phase M25 (Abstraction I — methods on user types, sprints 155–160) closes with this release. Spec §13/§15 no longer say records and enums have no methods. The design held end to end: a method is a function, hoisted on the lowering path only, so the lowerer, PTIR, the codec (v9, unchanged all phase), the VM and the compiled WASM backend learned nothing; the differential grew 36 to 39 and passed each new fixture on its first run; a method costs exactly what a call costs. Next, per the plan of record: M26 traits — abstracting over exactly the methods this phase added.
v0.25.1592026-08-06

M25 — methods on generic types

  • A generic type declares methods too. A method of Pair[A, B] is a generic function over the declaring type's own parameters, inferred at every call from the receiver — so a return type may name them and even reorder them, as in fn swapped(self) -> Pair[B, A]. It monomorphizes like any other generic call: the receiver's instantiated key maps back to the base name through the meta table, the call routes through the same inference every generic call uses with the receiver as its first argument, and the monomorphizer's instance rename claims the same call site, so a generic method still ends up a plain, specialized function. The two teaching errors that named this limit are gone, and their tests now assert the capability instead. Differential 39.
v0.25.1582026-08-06

M25 — the surface learns methods

  • The IDE now completes and hovers a project's own methods: after any receiver that is not a module or an enum, a type's methods are offered first with the prelude's behind them, each labelled with the type it belongs to — the same honesty the prelude receivers already had, since the context slice carries no expression types. The language card went to v8 and teaches the method form: declared after the fields or variants, a bare self, match self for an enum's payload, mut self for a method that rewrites its receiver, and the three rules that keep a type's surface unambiguous.
  • Fixed: an enum's method lines were read as variants. Since Sprint 155 a type's surface carries fields or variants AND methods, one per line, so typing Status. offered "fn allowed(self" as though it were a variant. Member lines are now told apart by shape.
v0.25.1572026-08-06

M25 — methods cross modules; the stdlib payoff

  • A pub method of a pub type is now part of its module's surface, under the same Type.method name it carries everywhere else — so an importer resolves r.area() on a shapes.Rect with no import-specific rule, mut self included. The stdlib then put it to work: time.Instant gained civil, iso, plus, minus, until, is_before and is_after; time.Duration gained in_minutes, in_hours, in_days and abs; http.Status gained ok, client_error and server_error; http.Response gained ok, text and header; http.Request gained with_header; and toml.Table gained get, has, text, int and keys. Every one of them delegates to the free function that already existed, so nothing was removed or renamed.
  • wall.now().plus(time.days(1)).iso() is now the natural spelling of what took three nested calls — and it is the spelling a model reaches for first, which was the whole argument for methods. ptc doc shows a type's methods where the type is, each with its doc on the member line, and the API reference was regenerated: 24 modules, 179 items.
v0.25.1562026-08-06

M25 — enum methods and mut self

  • An enum now declares methods after its variants, exactly as a record does after its fields — same self receiver, same rules — and a method reaches a payload the only way anything does: by matching self. A method declared mut self rewrites its receiver: it returns Void, needs a block body, and follows the rules the mutating prelude methods follow — a named binding declared mut, never a temporary and never a lambda capture.
  • Values are values, so "rewrite" means what it means everywhere: a call and a store. c.bump(5) is exactly c = Counter.bump(c, 5) — the receiver arrives under a private name, is copied into a mut self local the body mutates, and is returned, with the monomorphizer turning the call site into the assignment. No aliasing is introduced anywhere, and once again nothing below the type checker learns a new concept, so the compiled WASM backend ran the new fixture unchanged (differential 38).
v0.25.1552026-08-06

M25 opens — methods on records

  • Phase M25 opens the abstraction arc, and the gap it closes was measured rather than guessed: spec §9 has deferred traits since Sprint 1, and §13 still said plainly that records have no methods. Every user type was inert data and every behaviour a free function, so a model writing POLYTONE had to abandon its single strongest prior — that a type owns its behaviour — on every program. Methods come first because they are what a trait abstracts over. A record now declares its methods after its fields, in the same block, taking the receiver as a bare self: a class body is the dominant prior across corpora, it needs no new keyword, and it keeps one canonical place to look for a type's behaviour. The rules are teaching errors — self first and never annotated, fields before methods, one name means one thing — while two different records may both declare area(), the receiver's type selecting it.
  • The design is the result: a method IS a function. r.area() means exactly Rect.area(r) — the checker registers, checks and resolves it as one, and the monomorphizer hoists it to the top level on the lowering path only, so fmt still round-trips the source as written. The lowerer, PTIR, the codec, the VM and the compiled WASM backend learn nothing at all, which is why the differential passed methods on its first run (37 fixtures) and why a method costs exactly what a call costs. No codec change. Ride-along fix: a postfix chain gave every link the same source position, so .foo in a.b().foo() reported at a; each field access now carries its own — better errors, and what makes per-call-site method resolution possible at all.
v0.24.1542026-08-05

M24 — the first session (phase complete)

  • Phase M24 opened with a question — what is still urgently missing to actually program with POLYTONE? — and its three gaps were found by sitting down and trying, not by guessing. So the proof is that session itself, executed as a test: ptc new notes scaffolds and says what to do next; ptc test is green before a single edit; real code goes in and a comparison is wrong; ptc check names it with a position and does not run the program (asserted: the program's own output must be absent from a check); ptc context carries the signature and doc the editor completes and hovers from; ptc run fails two frames deep and prints the call stack (asserted: three frames, innermost first, each positioned); the guard goes in and the session ends green and canonical. A matching "Your first session" guide section teaches the same seven steps to a human — including getting it wrong twice, which is what a first session actually consists of.
  • Phase M24 is complete: three gaps found by trying, all closed — call stacks (150), ptc new (151), and code intelligence in the IDE, inline diagnostics (152) and completion + hover (153), both reading one context slice per idle pause that resolves imports and checks without executing. Two process gates that were remote-only — cargo fmt --check and the fixture expectations — are now in preflight, at six gates, and CI runs --no-fail-fast so one failing binary can never again hide the others. The M21+M22 audit (M23) stays partial and says so in its record: rounds 1 and 2 cleared, three findings taken by M24, rounds 3 to 6 open with their full statements and no S1 among them.
v0.24.1532026-08-05

M24 — completion and hover in the IDE

  • Sprint 152 taught the editor to report a mistake; Sprint 153 teaches it to prevent one — from the same source. One context slice per idle pause answers both questions, what is wrong and what is in scope. Candidates come from the slice's own items — kind, signature, members, doc — in three tiers by how much can honestly be known: after module. the module's pub surface exactly; after Enum. its variants, where a unit variant inserts without parentheses because Status.Active() is a teaching error; and bare, the file's own declarations plus the imported module names — never an imported item unqualified, which would suggest code that does not compile. Ctrl/Cmd-Space opens the list, Enter accepts, and pointing at a name shows its signature, fields or variants, and doc.
  • Two fixes found en route: a capability could take a record's name — record Env: and enum Rng: were accepted where record Fs: was correctly refused, because collect_types carried a private copy of the builtin-type list that never learned about Env and Rng when they joined the capabilities in M13; one list now feeds both, and a test walks all five capabilities through all three positions. And cargo fmt --check was CI-only, so a formatting drift from Sprint 151 sat on develop for two sprints — preflight runs it now, the same lesson as the deploy smoke.
v0.24.1522026-08-05

M24 — inline diagnostics in the IDE

  • Sprint 152 opens the third gap: the workbench editor was a textarea with syntax highlighting and Tab — you learned about a mistake by running the program, while a full type checker sat in the same sandbox the run panel already loads. Now, after every idle pause, the workbench type-checks through the context slice — which resolves imports and checks without executing, so typing never runs your program — and paints a marker on each reported line, the message on hover; a status line names the first problem and jumps the caret there on click. The DOM-free core carries five tests: parseDiagnostics never throws on a malformed payload, checkFiles swallows sandbox failures, and debouncedCheck collapses a burst into one run and drops a superseded result.
v0.24.1512026-08-05

M24 — ptc new

  • Sprint 151 closes the second of the three gaps: there was no way to start. Fourteen commands and no scaffolding, so a first-time user had to guess every convention. ptc new <name> writes an app — main.pt with a real function, a real test block, and the capability form in a comment — plus a README naming the four commands that matter; --lib writes a package instead: a pub module with the /// doc lines the publishing gate requires, a polytone.pkg in the exact order ptc pack validates, and its README. The scaffold runs, tests, and formats clean with no edits, and a package survives ptc pack all the way to its registry line. A CI gate runs that promise itself, and the guide plus the IDE language card (v7) teach it.
v0.24.1502026-08-05

M24 opens — call stacks

  • Sprint 150 opens Phase M24, The programming experience — owner-directed: what is still urgently missing to actually program with the IDE and POLYTONE? The answer came from sitting down and trying: no call stack on a runtime error, no ptc new, and an IDE editor without code intelligence. This sprint ships the first: a runtime error now carries the path that reached it — each frame names its function and position, innermost first, callers at their call sites. a() → b() → c() used to report only 'line 2'; it now prints the whole chain down to main. Both audiences win: a human debugging, and the pass@2e repair loop, which sees exactly what the human sees.
  • Also: preflight now checks the fixture expectations — running the full workspace suite revealed media_bridges.expected had drifted since the Sprint-145 stylesheet change, five sprints behind green local gates; the gate closes that class. And M23's F3.1 lands: the hemispheric ambient was inverted for box tops, sphere upper hemispheres and cylinder caps, brightening undersides and darkening tops — the normal is now oriented toward the camera before its y is read.
v0.23.1492026-08-05

M23 — the R1/R2 backlog

  • Sprint 149 clears the images/audio rounds. The studio now reads and writes the whole .pta v3 surface: it accepted version: 3 but none of its content — master: fell into the header else-branch and song: p x3 failed chain validation, so a codec-valid v3 document could not be imported and the round trip was lossy for every v3 file. master joined the session (emitted only below 100, lifting the document to v3 like a drum wave), and the import expands xN repeats with the codec's exact rule. Plus: the two image dispatches now agree on brighten's range, the missing degenerate-geometry and studio/drums coverage landed, and the even-width line's parity bias is documented.
v0.23.1482026-08-05

M23 opens — the M21+M22 audit

  • Sprint 148 opens Phase M23 (sprints 148–152) — owner-directed: look for issues and refine all logics. The pick follows the project's rhythm: M21 (the benchmark infrastructure) and M22 (five format versions in seven sprints) were the only never-audited phases. The record (M23-REVIEW.md, six rounds, ~31 findings) lands with five confirmed S1s fixed ahead.
  • The S1s: the ellipse outline was a filled lens at unequal radii (both radii were inset by 1 — no inset at all when rx != ry; rx == 1 skipped the test entirely) → an exact boundary test. The xN song repeat shadowed pattern names (a document declaring pattern x2: could never play it) and mis-parsed xx4 / silently dropped x1x → declared names win, strictly x<digits>. The studio exported drums in a version: 2 document its own codec rejects → the document declares the version its content needs. And nav: addresses were never validated while link addresses are, so item javascript:alert(1) reached exported HTML as a live link → nav carries the link rule.
v0.22.1472026-08-05

M22 close — the proof + the true record

  • Sprint 147 closes Phase M22 (tool maturity, sprints 140–147). The proof: examples/gallery.pt — one program composing all five upgraded formats (a v5 image with ellipse/outline/polyline/filters, a v3 tune with drums/master/repeats, a v3 film with an embedded soundtrack and an eased move, a v3 model with cylinder + yawed torus through the upgraded rasterizer, and a v4 page with nav/quote/table/spans embedding the image), main(disk: Fs) the whole effect footprint, six deterministic tests on mock_fs — CI-gated beside pulse/digest/roster/logparse.
  • The disposition: the format/codec core of every round shipped — five format versions, all additive and version-gated, all tested — plus the live orbit, the dark-mode stylesheet, the v4 demo site, the native 404 page, and the restored Viewer & Player. Carried forward deliberately: the tool-UI pool (drag-preview, chromatic grid, scrub player, examples browser, …) — human-facing polish, cleanly scoped for a dedicated phase. The format layer — the part an LLM writes — is done. Phase M22 complete.
v0.22.1462026-08-05

M22 — the site eats its own v4

  • Sprint 146 (the R6 window): the demo site the web viewer ships went v4 — index and about carry a nav: menu, a quote, a table:, and inline spans, so the new format is what visitors actually see. The address bar gained datalist autocomplete over the site's documents, and the 404 stopped being a status strip: it is now a native .ptw v4 error page — heading, quote, and a nav of the documents that do exist — rendered through the same codec as every other page: the browser behaves like a browser. The remaining pool moves to the 147 disposition.
v0.22.1452026-08-04

M22 — .ptw v4: real documents

  • Sprint 145 clears M22's R5 core. .ptw v4: quote (it was literally the codec's own unknown-block teaching example), note callouts, table: with head/row lines, nav: menus, and inline *bold* / `code` spans in text/item/quote/note — balanced pairs wrap, odd counts stay literal. Headings carry deterministic anchor ids, so #anchor links finally resolve. And the register's highest look-per-line payoff: the bridge stylesheet — a dark palette via prefers-color-scheme, styled inputs/buttons/tables/quotes/nav, pixelated image rendering; codec-internal, no format change. web.pt 15/15.
v0.22.1442026-08-04

M22 — .ptv v3: the embedded soundtrack

  • Sprint 144 kills the video format's biggest immaturity: the soundtrack is now embedded. A bare audio: opens an indented block of .pta source lines (the sprite-block mechanic, indentation preserved), so the score travels inside the document — reopening loses nothing; the v2 reference form stays legal. The editor embeds on export and round-trips on load — the data-loss class M19 could only warn about is dead. Plus ease in|out|in-out on move — motion stops looking mechanical. All v3-gated; codec tests 9/9, web 99.
v0.22.1432026-08-04

M22 — .ptm v3 + the live orbit

  • Sprint 143 clears M22's R3 core. .ptm v3: cylinder/cone/torus (sphere-rule segments) and an optional yaw <degrees> clause on any shape line — rotation about the shape's centroid, one line per shape stays the grammar. The rasterizer: per-vertex trig hoisted, the O(t²) insertion sort replaced by a packed-Int host .sorted(), and a hemispheric ambient term joined the key light — depth cues instead of the flat look. The viewer: a quarter-resolution render fires on every pointermove and the release settles at full res — the 771 ms drag-then-wait became a live orbit. Three new codec test blocks (mesh.pt 16/16); api.ts 179 items.
v0.22.1422026-08-04

M22 — .pta v3: drums, master, repeats

  • Sprint 142 clears M22's R2 core: the sound format stops being a chime demo. Drum waveforms — noise, kick (a sine whose pitch falls from 3× the note), snare, hat — synthesized via a deterministic LCG reseeded per event, so renders stay byte-identical under ptc test; master: <0-100> as the mix's headroom valve (the clipping model is now documented); song: verse x4 repeat sugar. All additive and version-gated; three new codec test blocks (17/17); the studio accepts v3 documents and the drum waveforms. Deferred within the phase: chromatic grid, live audition, track lifecycle, pan/echo/stereo, the wav cache.
v0.22.1412026-08-04

M22 — .pti v5: the drawing vocabulary

  • Sprint 141 clears M22's R1 core: .pti v5 grows the image format from demo primitives to a real vocabulary — ellipse (filled/outline), rect/circle outline variants, line stroke widths (1–64), polyline (a brush stroke is one op, not one line per segment), and the whole-canvas filters invert/grayscale/brighten. Wired through both codec dispatch paths, version-gated with teaching errors; six new pub helpers, three new codec test blocks, api.ts regenerated (176 items). The spec gains the v5 section, the documented layer/(hidden) round-trip convention, and the corrected glyph claim; the editor emits v5, accepts the new ops, and clamps edge taps.
v0.22.1402026-08-04

M22 opens — the tool-maturity register

  • Sprint 140 opens Phase M22 (sprints 140–147) — owner-directed, with screenshots as the first evidence: much still looks simple and rudimentary; go through it in detail, tool by tool, spec by spec. Five audits hardened correctness; M22 reviews maturity. The register (M22-REVIEW.md): six rounds by tool, ~48 findings, each with the concrete upgrade — .pti v5 drawing vocabulary + drag-preview + incremental render; .pta v3 drums/pan/echo/stereo + live audition + a chromatic grid; .ptm v3 shapes + a live orbit + vertex normals; .ptv v3 embedded soundtrack + easing + a real scrubbing player; .ptw v4 (quote/table/nav/model/inline spans) + a real stylesheet with dark mode; the playground suite: examples browser, native-format tabs, cross-tool handoff. Every format bump additive and version-gated.
  • Fixed ahead: the Viewer & Player app was inert — its source textarea lacked the data-ov-src hook, so init bailed and Render, samples, and Open-file did nothing; one attribute restores the flagship all-formats viewer.
v0.21.1392026-08-04

M21 close — the true record

  • Sprint 139 closes Phase M21 (Generation excellence, sprints 133–139) — the phase that turned 'leading LLM programming language' from a claim into standing, measurable infrastructure: the instrument (task corpus, hidden-judge protocol, BYO-key runner, corpus-rot CI gate), 30 tasks across the full surface with tiers, the public /benchmark/ page with its no-fabricated-numbers rule, the live-site incident fixed and made structurally impossible, language hardening from the phase's first real failure dataset (Text.slice, the qualified-enum teaching error, Card v6), and the CI-gated delta proof (fail → pass, nonsense → teaching). Differential 35 → 36; codec v9 unchanged all phase.
  • Carried forward, deliberately: frontier baseline runs are local and BYO-key — publishable into the benchmark page at any time; every future phase can feed the loop new tasks, new failure data, and new deltas. The generation-improvement loop is now standing infrastructure.
v0.21.1382026-08-04

M21 — the delta proof (replays)

  • Sprint 138 turns the Sprint-137 hardening into a measured delta: the recorded first-attempt candidates from the phase's first failure dataset (the Sprint-133 authoring session, verbatim) are replayed against the current toolchain through the exact judge path, CI-gated so the delta can never silently regress. content_tag_attempt1 failed then (no method 'slice' on Text) and passes now, verbatim — fail → pass, the language grew to meet the model. json_pluck_attempt1 still fails (the pattern is wrong) but the error now teaches the qualified form — the repair signal pass@2e depends on. log_scan_attempt1 still fails (a semantics miss no compiler error can prevent), but the assertion diff carries the actual values, and Card v6 teaches the rule.
  • The /benchmark/ page documents the replays (then/now, per candidate) — showing only what has actually run; fresh frontier pass@1/pass@2e runs stay local and BYO-key.
v0.21.1372026-08-04

M21 — hardening from the first failure data

  • Sprint 137 hardens from the phase's first generation-failure dataset — the corpus-authoring session itself (an LLM writing POLYTONE cold, its stumbles documented). Text.slice(from, to) now exists: the method models reach for reflexively, character-based, strict [from, to), bounds-checked with the same teaching error as Bytes.slice — the two slice methods agree on semantics. Typeck + the shared VM (both backends), spec §14, the Prelude explorer, the guide, and a differential fixture (35 → 36, Unicode included).
  • The unqualified imported-enum pattern (case Json.Str against a json.Json subject) used to produce a mismatch that reads like nonsense; it now teaches the qualified form and names the exact pattern to write. Card v6 carries the dataset's remaining lessons: captures returns [whole match, group 1, …], imported enums match qualified, for ch in text walks characters, \{ for literal braces, and Text.slice. The content_tag reference solution uses the natural slice form — the corpus tracks the language it measures.
v0.21.1362026-08-04

M21 — the live-site incident (the deploy unblocked)

  • The phase's first data-driven hardening — and the first real-world failure M21 surfaced was the deploy pipeline itself: the live site's changelog sat frozen at v0.13.91. Root cause: the deploy workflow's first step, smoke.mjs, still exercised the ambient env() builtin that Sprint 92 retired for the Env capability — so the smoke step failed on every push for 44 sprints and the site never advanced while the repo moved on. The smoke now runs the capability form (sys.var), and the full deploy chain was reproduced green locally.
  • The changelog page's lead still described the pre-renumbering '0.SPRINT.0' scheme; it now teaches 0.PHASE.SPRINT and the 0.x-for-the-whole-beta rule. And the durable fix: the deploy smoke joined preflight as the fourth fast gate (~1 s) — a gate that only runs remotely and unobserved is not a gate.
v0.21.1352026-08-04

M21 — the benchmark page + the publishing pipeline

  • Sprint 135 gives the benchmark its public face: the /benchmark/ page carries the honest methodology (the ptc test judge, appended hidden tests, capability-clean mocks, the no-cherry-picking rule), the full 30-task corpus table with tiers and areas, and a results section that renders published runs — with an explicit empty state until one exists: this page never fabricates a number, what appears here has actually run. Baseline runs stay local and BYO-key.
  • The publishing pipeline: benchmark.ts holds the task table and the benchmarkRuns array a local run is committed into; the runner aggregates pass@1/pass@2e per tier and prints the publish instruction. A drift gate requires benchmark.ts to mirror benchmarks/gen/tasks exactly — same set, same tiers — and any published run to cover the full corpus.
v0.21.1342026-08-04

M21 — the corpus grows to 30 tasks

  • Sprint 134 triples the generation-benchmark corpus, 10 → 30 tasks, now spanning the full language surface: the five media codecs (image/audio/mesh/web/video probes), the complete capability set under mocks (env_mode, clock_iso, api_status, save_report — joining dice_walk and config_port), generics (uniques[T], swapped[A,B]), the Result surface (parse_point, total_of with ?-propagation), collections and the prelude (histogram, run_length, row_sums, set_overlap), the bitwise operators (bit_parity), hex_dump, and async (task_batch). Every task stays capability-clean — the judge touches no network, disk, clock, or entropy.
  • Every task now carries a difficulty tier (S/M) in its title line, so the report can break pass rates down by difficulty from sprint 135 on. The CI gate requires the full set (≥ 30) and runs every reference solution through the exact judge path: 30/30 green — and all twenty new references passed their hidden tests on the first run, the Sprint-133 syntax gotchas applied: the pass@2e thesis in miniature.
v0.21.1332026-08-03

M21 opens — the generation benchmark

  • Sprint 133 opens Phase M21 (Generation excellence, sprints 133–139) — owner-directed: make POLYTONE the leading LLM programming language. After five audit passes the codebase is hardened end to end; M21 turns the founding claim into a measured, improvable number: give a model a task, let it write POLYTONE, and let ptc test judge — deterministically, no human in the loop. benchmarks/gen/ holds the instrument: a task corpus (prompt, hidden judge, reference solution), ten seed tasks spanning the surface from the prelude to csv/json/patterns/crypto/time, tuple destructuring, and the Rng/Fs capabilities under mocks — so a run touches no network, disk, clock, or entropy.
  • The runner (run.mjs, local and BYO-key — never in CI) sends the frozen IDE language card plus the task, extracts the code block, appends the hidden tests, and runs ptc test; after a failure the model sees the actual error and gets one repair attempt. pass@1 and pass@2e are both first-class — the thesis that POLYTONE's errors teach is itself measured. The CI gate (gen_corpus.rs) requires every reference solution to pass its own hidden tests through the exact judge path, so the corpus can never rot: 10/10 green. Reports always show every task and every attempt — no cherry-picking.
v0.20.1322026-08-03

M20 close — the true record

  • Sprint 132 closes Phase M20 (the second full-codebase audit, sprints 127–132). A records-only close: the M20-REVIEW.md disposition marks every one of the 34 findings across eight rounds fixed or recorded — nothing deferred, no finding shrank to a non-defect. The six S1s (the monomorphization non-termination, the parser stack overflow, the capability-inference bypass, and three stdlib crash/hang paths) were all real and are all fixed with regression tests — the widest S1 count of any audit pass, fitting the widest scope: the M1–M8 original-feature substance had never had a dedicated audit. R2 (VM/PTIR/codec) had no S1/S2 and R8 was swept clean.
  • The audit's recurring theme — decoder/transport input-hardening — is now uniformly closed: every parser, decoder, and transport in the tree bounds its input and errors loudly. Codec stayed v9 the whole phase; differential 35; stdlib 24 modules; web 98 / ide 124 tests. With M20, every phase through M18 has been audited at least once (M12, M14, M17, M19, M20).
v0.20.1312026-08-03

M20 fix-sprint — the R6/R7 backlog

  • Sprint 131 clears M20's Round 6/7 (POLYTONEide + toolchain) backlog — the last fix-sprint before the close. IDE: the Generate precondition required an API key for every kind but 'compatible', locking out the local-cli and anthropic-oauth backends; a data-driven configShortfall reads the catalogue's auth mode and names what is actually missing (F6.1). An escalated run's cheap-tier tokens now land on the cheap model — the record carries the router's perTier split and byModel attributes each tier honestly (F6.2). Streamed OpenAI-shaped requests ask for their usage chunk (stream_options.include_usage), so streamed ledgers report real counts (F6.3), and the SSE decoder accepts CRLF event framing (F6.4).
  • Toolchain: the MCP stdio transport read lines unbounded — the exact class the LSP's M19 cap closed; a bounded reader (16 MiB) drains an oversized line, answers a parse error, and keeps the session alive (F7.1). LSP header lines are capped at 64 KiB (F7.2), one non-UTF-8 byte no longer terminates the MCP session (F7.3), MCP context rejects out-of-range/non-integer positions in CLI parity (F7.4), and a malformed Content-Length is named instead of misreported as missing (F7.5). Coverage for all of it: ide 124 tests, MCP/LSP stdio suites +3 each (F6.5/F7.6).
v0.20.1302026-08-03

M20 fix-sprint — the R4/R5 backlog

  • Sprint 130 clears M20's Round 4/5 (web/docs + media formats) backlog — docs and web TypeScript only. The spec no longer documents the retired env() builtin: §21 presented it as live (echoed in §8, §12, and §22), contradicting §29's Sprint-92 retirement — all four sites now describe the Env capability (F4.1). timelineRow, the actual stored-XSS culprit of the M12 review, is exported and covered by the HTML-safety gate (F4.2). paintThumb routes through the shared decodePpm with its zero-dimension guard instead of a private drifted parse (F5.1).
  • The media codecs stay on their documented grammar: the studio's empty-song document round-trips (a bare song: line, F5.2); parsePtv gates the v2 surface, bounds fps to 1–30, and validates #rrggbb (F5.3); model3d's literal escape is promoted to polytone-web-shared as sourceLiteral — completed with brace escapes — and used by all four media tools (F5.4); an imported multi-decimal duration survives re-serialization (F5.5); and parsePti/parsePta enforce the 4096 size bound and the pattern-name charset (F5.6). Web 98 tests, shared 16.
v0.20.1292026-08-03

M20 fix-sprint — the R3 backlog

  • Sprint 129 clears M20's Round 3 (stdlib) backlog — pure stdlib, no codec or compiler change. F3.4: images.from_ppm_bytes capped decoded sizes at 1024, but canvas/render produce images up to 4096 per side, so a wider image failed the to_ppm_bytes → from_ppm_bytes round trip; the bound now matches (1–4096 per side). F3.6: video.to_y4m_bytes divided by frame_size, trapping on a hand-built zero-size Video; it now emits the header alone, staying total. F3.5: a dead .ptw sub-clause in the web button-target check is removed (a .ptw path already fails ends_with('.pt'); behaviour unchanged).
v0.20.1282026-08-03

M20 fix-sprint — the R1/R2 backlog

  • Sprint 128 clears M20's Round 1/2 (compiler frontend + VM/PTIR/codec) backlog, led by the sixth S1. F1.1: polymorphic recursion (deep[T] calling itself at List[T]) type-checked but expanded forever in the monomorphizer (ptc check passed, ptc run aborted) — the pass now bounds monomorphized type-argument depth at MAX_MONO_TYPE_DEPTH = 64 and returns a teaching error, and monomorphize now returns a Result. F2.1: the PTIR decoder rejects a structurally invalid blob (zero functions, an out-of-range Call/test index, a jump past the end) instead of panicking in the VM. F2.3: fetch_args bounds-checks the wasm32 host args blob.
  • Also: the InvalidNumber message states the true Int bound (2^63 − 1) and the i64::MIN literal recipe (F1.4); the RngInt cost comment is corrected and the full-i64-span draw is tested (F2.2); and two value-semantics edges are documented in place — an empty Set and an empty Map both render {} (the empty-map literal; a set has no literal form), and float NaN follows IEEE-754 so it never equals itself as a Set element or Map key (F2.4/F2.5). No codec change (v9); both wasm blobs rebuilt, differential 35/35.
v0.20.1272026-08-03

M20 opens — the second full-codebase audit

  • Sprint 127 opens Phase M20 (the second full-codebase audit, sprints 127–132) — the fifth supervised fine-tuning pass, owner-directed to re-run the audit over M1–M8 + M18 at primary depth (the original-feature substance never dedicatedly audited), M13/M15/M16 as regression. The review record M20-REVIEW.md lands with eight rounds by area (~34 findings) and five of six confirmed S1s fixed ahead.
  • The S1s fixed ahead: a deeply nested expression no longer overflows the parser and aborts ptc check / the LSP / the MCP server — a depth guard (MAX_EXPR_DEPTH = 128) yields a clean teaching error (F1.2); a capability can no longer be smuggled into an inferred container (let xs = [wall] was accepted while the annotated List[Clock] form was rejected — the M19 F5.1 hole class) (F1.3); web.render no longer traps on a bare heading line (F3.1); texts.pad_left/pad_right no longer loop forever on an empty fill (F3.2); and mesh.render_view no longer crashes on a builder-made mesh (F3.3). The sixth S1 (F1.1, non-terminating monomorphization on polymorphic recursion) leads sprint 128. No codec change (v9); both wasm blobs rebuilt.
v0.19.1262026-08-02

M19 close — the true record

  • Sprint 126 closes Phase M19 (the supervised fine-tuning audit of the Phase M9 surface, sprints 121–126). A records-only close: the M19-REVIEW.md disposition marks every one of the ~19 findings across eight rounds fixed or recorded — nothing deferred, and (unlike M14/M17) no finding shrank to a non-defect, so the M9 surface held up cleanly and the three confirmed S1s (F1.1 the y4m/ppm decoders, F5.1 the async-fn-returns-capability leak, F6.1 the dropped hidden layer) were real and are fixed. Two rounds were swept clean (R6 media-tool HTML-safety, R8 the record); the R7 M9 verified-loop hardenings were confirmed intact under M18's backends. Codec stayed v9 the whole phase; the differential grew 32 → 34 → 35; stdlib held at 24 modules.
  • Next: Phase M20 (the second full-codebase audit, sprints 127–132) — M1–M8 + M18 primary, M13/M15/M16 regression, organized by area.
v0.19.1252026-08-02

M19 fix-sprint — the R7 backlog

  • Sprint 125 clears M19's Round 7 (IDE) backlog — the last fix-sprint before the close (Round 8 was clean). The IDE's provider dispatch (route a config to its key/oauth/cli runner, then wrap in withRetry) is extracted from the DOM-heavy intentpanel.ts into a testable core module, complete-router.ts, with injected runners; complete-router.test.ts covers the routing, the retry-wrap, a non-retryable error, and retry exhaustion (F7.2).
  • Streaming completions now honour the provider's token counts — Anthropic's message_start/message_delta usage, OpenAI's final usage chunk — via usageFromEvent and SSEDecoder.usage(), falling back to a ~4-char estimate only per field the provider omitted, so an enabled streamed tier reports real usage instead of silently switching its ledger to estimates (F7.3). And a stale roadmap pointer that called usage-delta threading a 'follow-up (120)' — which sprint 120 never wired — is corrected (F7.1).
v0.19.1242026-08-02

M19 fix-sprint — the R5/R6 backlog

  • Sprint 124 clears M19's Round 5/6 (async/Task + media tools) backlog. tests/fixtures/async_capability.pt covers a Task that captures an Rng (via fixed_rng) through an async fn and defers the draw — pinning on both backends (differential 34 → 35) that the deferral works and that re-running one Task re-draws identically. Spec §28 now reconciles Task with capabilities (§29): capture-and-defer, main's effect footprint as an upper bound, no capability return, and the Rng re-draw behaviour (F5.2/F5.3/F5.4).
  • The video editor shows a note that .ptv references but does not embed the soundtrack — export .pt to keep it — instead of losing it silently on reopen (F6.2). And the sound studio flags an unknown song-chain token inline rather than only as a raw codec error at render (F6.4). No codec or compiler change.
v0.19.1232026-08-02

M19 fix-sprint — the R3/R4 backlog

  • Sprint 123 clears M19's Round 3/4 (crate depth + codegen/differential) backlog. The LSP framing layer caps Content-Length at 16 MiB before allocating, so a crafted header can't crash the server with a multi-GB allocation (F3.1). polytone-wasm-rt's pt_alloc returns null on a length past isize::MAX instead of aborting the module, mirroring polytone-web-rt's buffer_layout guard (F3.2).
  • The compiled-WASM differential now walks tests/fixtures recursively like the VM harness, so a subdirectory fixture is no longer silently skipped — the modules/ leaf modules join the set, differential 32 → 34 (F4.1). bench-compiled.mjs reports a per-program failure row instead of aborting the whole report on one nonzero exit (F4.2), and a dead ptc-version tail after the exit gate is gone (F4.3). No codec change; the differential holds 34/34.
v0.19.1222026-08-02

M19 fix-sprint — the R1/R2 backlog

  • Sprint 122 clears M19's Round 1/2 (shared runtime + web harness/gates) backlog. The wasm freshness gate now derives the two blob crates' path-dependency closure and fails if INPUTS misses a crate, so a forgotten dependency can't ship a stale-semantics blob behind green CI (F1.3). The highlighter's number scanner stops at .., so `for i in 0..10:` renders 0, .., 10 instead of one number (F1.4).
  • And the per-item api-parity gate fails loudly on any pub <kind> gen-api.mjs does not handle — the lexer has a trait keyword, so a pub trait would otherwise be silently undocumented and ungated (F2.1). Runtime TS plus a build-gate change — no compiler, stdlib, or codec change.
v0.19.1212026-08-02

Phase M19 opens — the M9 audit + three S1s

  • Sprint 121 opens Phase M19 (the M9 audit), the fourth supervised fine-tuning pass, over the Phase M9 surface (the consolidation pass, sprints 63–70) — emphasis on the web/media/shared-runtime/async-Task surface no later pass re-audited. It carries M19-REVIEW.md (~19 findings across eight rounds) and three confirmed S1s, all fixed ahead.
  • F1.1: the shared y4m/ppm decoders threw on a valid-looking zero-dimension/zero-fps header (new ImageData(0,0), a runaway 1000/fps loop) → they now return null. F5.1: an async fn returning a capability inferred Task[Cap], leaking a capability into a List via tasks.all → the typechecker rejects it. F6.1: the image editor silently dropped hidden layers on .pti export → a hidden layer is preserved as commented-out ops the codec skips and parsePti restores. Clean verdicts: the five M9 IDE-loop hardenings hold under M18's backends, media-tool HTML-safety is not bypassable, the record is consistent. No codec change; differential holds 32/32.
v0.18.1202026-08-02

Phase M18 complete — ledger, proof, ship-readiness

  • Sprint 120 closes Phase M18 (IDE model backends). A cross-backend token ledger (intents.byModel, pure + tested) aggregates the intent history per model; the ledger line shows the per-model split when more than one model was used. The backend-matrix proof (a core.test.ts invariant) asserts every ProviderKind appears once in PROVIDER_CATALOG, each has exactly one credential mode matching its auth, and availableProviders hides exactly the desktop-only kinds off the desktop — the backends are coherent by construction. oauthConfigured(client) gates the subscription-login path on a real client id.
  • Phase M18, complete: the workbench talks to many model backends well — Anthropic (key or subscription login), OpenAI, a local HTTP runner (Ollama, with discovery), and a local agent CLI — through one data-driven catalogue, with streaming + retry and a cross-backend ledger. Adding a backend is one catalogue entry plus its transport. Carried forward: the OAuth go-live (the external Anthropic registration) and live streaming progress in the panel (the transport ships tested-and-ready). IDE product work (Phase M8 lineage) — no compiler, stdlib, or codec change across the whole phase.
v0.18.1192026-08-02

M18 — a local-CLI agent backend

  • Sprint 119 adds a local-cli backend: instead of an HTTP API, the IDE shells out to an agent command the user already has installed and logged in (claude -p, llm, a wrapper), so it bills against that tool's own auth — the non-API local path, no registration. The native run_agent Tauri command spawns the command, writes the prompt to stdin on a separate thread (so a large prompt can't deadlock against the child's stdout pipe), and returns stdout or the exit status + stderr. cargo check/clippy clean.
  • local-cli.ts (pure + tested): buildCliPrompt flattens the card + conversation, parseCommand splits binary/args, runCliComplete runs it through run_agent. A new catalogue entry (auth "command") plus a providerFields.command flag shows a command box — still data-driven; ProviderConfig gains an optional command; completeFor routes it (wrapped in the same withRetry as every backend). IDE product work (Phase M8 lineage) — no compiler/stdlib/codec change.
v0.18.1182026-08-02

M18 — streaming (SSE) + retry-with-backoff

  • Sprint 118 adds the streaming engine (streaming.ts, pure + tested): deltaFromEvent reads a text delta from either wire shape (Anthropic content_block_delta, OpenAI choices[].delta.content); SSEDecoder buffers an event split across network chunks so a delta is never lost or doubled; streamComplete drives an async chunk source, emits each delta, and returns the accumulated result; buildStreamRequest sets stream: true.
  • And retry-with-backoff (retry.ts, pure + tested): isRetryableStatus (429 + transient 5xx), isRetryableError, backoffMs (exponential with an 8s ceiling), and withRetry with an injected sleep. completeFor now wraps every backend in withRetry, so a rate limit or a transient upstream failure backs off and retries instead of sinking the generation. The streaming transport ships tested-and-ready; threading deltas into the panel as live progress is a follow-up. IDE product work (Phase M8 lineage) — no compiler/stdlib/codec change.
v0.18.1172026-08-02

M18 — a first-class local-model backend

  • Sprint 117 makes the local-runner path (Ollama, llama.cpp, LM Studio) a first-class backend. localmodels.ts (pure + tested): buildModelsRequest targets the OpenAI-compatible /v1/models (which all three serve), parseModels reads both the OpenAI shape (data[].id) and Ollama's native /api/tags shape (models[].name), and discoverModels returns the installed models or throws an actionable "not reachable at <url> — is it running?" error.
  • In the config UI a local backend shows a "↻ models" button that fills a datalist on the model input — pick from installed models instead of typing — plus a status line, with the base-URL box defaulting to Ollama's localhost:11434. It stays catalogue-driven: providerFields gained a discover flag (true for the none-auth local kind). IDE product work (Phase M8 lineage) — no compiler/stdlib/codec change.
v0.18.1162026-08-02

Phase M18 opens — a data-driven provider picker

  • Sprint 116 opens Phase M18 (IDE model backends). The IDE's config UI is rendered from the provider catalogue instead of hardcoded options — one PROVIDER_CATALOG entry is one backend. The <select> is built at mount, and the credential fields follow each provider's auth: a metered backend shows the API-key input, subscription login shows the Login-with-Claude box, a local endpoint shows neither. Desktop-only backends (the OAuth loopback) appear only in the Tauri shell.
  • New pure helpers (providers.ts) make the form a tested function of the catalogue: availableProviders(desktop) filters by platform, providerFields(kind) returns which credential fields to show, providerMeta(kind) looks a kind up. Adding a backend no longer means touching the picker markup or its show/hide logic. IDE product work (the Phase M8 lineage) — the compiler, stdlib, and codec are untouched.
v0.17.1152026-08-02

Phase M17 complete — the R8 fixture + the true record

  • Sprint 115 closes Phase M17. tests/fixtures/pattern_walk.pt (F8.1) — a self-contained mini-regex matcher (a recursive Node enum walked by a match_here that threads deduped position sets via .to_set().to_list() with a Star fixpoint, plus tuple-for-destructuring and Map.entries()) — gives the compiled backend differential coverage on the import-using M15/M16 shape the single-file harness couldn't reach. Differential 31 → 32, CI-gated.
  • Phase M17 (the full-codebase audit) is complete: the third supervised fine-tuning pass, the widest scope yet — M15 (tuple decomposition) and M16 (the patterns regex rewrite), neither reviewed since it shipped, at primary depth, plus a regression re-sweep of the M12/M14 surface. Eight rounds, ~20 findings in M17-REVIEW.md, every one fixed or recorded; R3 (codec) and R5 (toolchain) clean, no deferred findings. The load-bearing find was the design seam behind both S1s: captures had grown a second matching engine in M16 never reconciled with matches; unifying them closed a real ReDoS and a real correctness disagreement at once.
v0.17.1142026-08-02

M17 fix-sprint — the R6/R7 web + IDE backlog

  • Sprint 114 clears M17's Round 6/7 (web/docs + IDE) backlog, all brought up to the M15/M16 surface. Spec §19 now lists toml and uuid and the M15 lists/maps additions (F6.1). The guide gained a "Tuples & destructuring" section — let (a, b), case (a, b):, the nested for (rank, (name, score)) in … binder, the no-.0/.1 rule, and Map.entries() — plus a patterns.captures example and toml/uuid in the stdlib tour (F6.2).
  • The IDE language card bumped CARD_VERSION 4 to 5 to teach tuple decomposition, Map.entries, and the richer patterns/toml/uuid — it had predated the whole M15/M16 surface (F7.1). A stale "17 fixtures" guide count went evergreen (F6.4), and the api.ts freshness gate now checks per-item (a new pub fn added without regenerating is a red test), not just per-module (F6.3). Docs, tests, and the frozen card only — no stdlib, codec, or compiler change.
v0.17.1132026-08-02

M17 fix-sprint — the R1/R2 compiler backlog

  • Sprint 113 clears M17's Round 1/2 (compiler frontend + VM/lowering) backlog. A refutable let/for binder error now points at the culprit sub-pattern and names it — let (a, 0) = pair underlines the 0 and says "…but this is a literal" (a nested Some(b) says "…a constructor") — via a new first_refutable walk whose .is_none() is exactly the old irrefutability gate, so the check is unchanged and only the diagnostic is sharpened (F1.2).
  • The VM's TupleGet out-of-range arm is now a named unreachable! reporting the index and the tuple's arity, matching its non-tuple arm, rather than a generic .expect (F2.1). And tuple-match exhaustiveness is pinned by tests (F1.1): the product of a Tuple[Enum, Enum] is not decomposed, so enumerating all combinations is still non-exhaustive without a catch-all (case _: or an irrefutable case (a, b):). No codec or instruction change; wasm blobs rebuilt for the typeck/VM edits and re-stamped, differential holds 31/31.
v0.17.1122026-08-02

M17 fix-sprint — the R4 stdlib backlog

  • Sprint 112 clears M17's Round 4 (stdlib) backlog. patterns: an inverted bound a{2,1} (min greater than max) is now a parse-time teaching error rather than a pattern that silently matches nothing (F4.3), and doubled-quantifier rejection is symmetric — a*{2}, a{2}{3}, and a{2}+ all teach like a** does (F4.4). A bare { that is not a valid bound still reads as a literal.
  • toml gains trailing # comments (a comment after a value on the same line), respecting a # inside a quoted string so a URL keeps its fragment (F4.6); the header now documents that quoted/dotted keys are unsupported and that a bare key belongs to the most recent [header]. Coverage lands for patterns parser edges (empty alternation branches, a stray close-paren, (*)), toml value and scoping edges (empty string / empty array / trailing comma / escapes), and lists enumerate/unzip/zip_with on the empty list (F4.5/F4.7/F4.8/F4.9). Pure stdlib, no codec or compiler change.
v0.17.1112026-08-02

Phase M17 opens — the full-codebase audit + a captures unification

  • Sprint 111 opens Phase M17, the third supervised fine-tuning pass (owner-directed: "Supervised fine tuning of absolut all"), the widest scope yet — M15 (tuple decomposition) and M16 (the patterns regex rewrite + captures) at primary depth, plus a regression re-sweep of the M12/M14 surface. It carries the review record (M17-REVIEW.md) and the two confirmed S1s, both fixed ahead of the fix-sprints.
  • patterns.captures now shares matches' engine (F4.1, F4.2 — two S1s). captures had used a separate backtracking walk never reconciled with the deduped position-set engine behind matches. Two consequences fell out of that seam: on a nullable repeat like (a?)+ against "", matches returned true but captures returned None (they disagreed on whether the text matched at all); and the walk catastrophically backtracked — captures("(a*)*c", 24 a's + b) ran over 20 s while matches was instant. Both are fixed by unifying captures onto the same deduped position→captures simulation: position dedup removes the blow-up and a shared zero-width fixpoint makes the two engines agree on match/no-match by construction. Two regression tests; pure stdlib, no codec change, differential holds 31/31.
v0.16.1102026-08-02

Phase M16 complete — the patterns proof

  • Sprint 110 closes Phase M16 with examples/logparse.pt, a log-line parser. One regex — (\d{4})-(\d{2})-(\d{2}) (INFO|WARN|ERROR) (\w+): (.*) — exercises every feature the phase added: bounded quantifiers, a group with alternation, and six capturing groups. It drives both patterns.matches (validate a line) and patterns.captures (pull the fields into a record), then filters and extracts across many lines. Pure text, deterministic, CI-gated beside pulse, digest, and roster.
  • Phase M16, complete: patterns went from a flat token matcher to a real regex engine — alternation, groups, {n,m}, and capture extraction — all pure stdlib, with no compiler or codec change the whole phase, on a deduped position-set matcher that stays polynomial and ReDoS-safe. It stays a documented subset (no backreferences, lookaround, or lazy quantifiers). This clears the last item deferred out of M13 and M15; the next phase is unplanned.
v0.16.1092026-08-02

patterns.captures — pulling values out

  • The payoff of sprint 108's groups: patterns.captures(pattern, text) returns the whole match plus each group's captured substring, in group order, when the pattern matches in full. captures("(\d+)-(\d+)", "12-345") gives Some(["12-345", "12", "345"]); a non-match is Ok(None). A group that didn't participate captures the empty string; a repeated group captures its last iteration. This is what makes richer patterns useful for pulling values out of text.
  • matches keeps its fast, ReDoS-safe position-set engine and is unchanged; capture needs per-path group spans (deduping would merge distinct captures), so it runs a separate backtracking walk that threads the spans — value semantics discard a failed branch's captures for free, and a progress-required guard on repeats keeps it terminating. Groups are numbered by a post-parse pass, so matches still treats them transparently. The pulse.pt URL regex and every prior test stay green. On the API reference (170 items).
v0.16.1082026-08-02

Phase M16 opens — the regex engine

  • patterns, POLYTONE's regex module written in POLYTONE, gets a real engine. matches now supports alternation a|b, groups (...), and the full quantifier set * + ? {n} {n,} {n,m} — any of which may nest — on top of the existing literals, ., [classes], and \d\w\s. The flat token list became a recursive Node AST with a recursive-descent parser (parse_alt → parse_concat → parse_repeat → parse_atom).
  • The matcher is a Thompson-style position-set simulation: match_here returns the deduped set of reachable end-positions rather than yes/no, so matching is polynomial and ReDoS-safe. A pathological pattern like (a|a)*b or (a*)*c returns at once instead of catastrophically backtracking into the fuel limit — the safety comes free from deduping with Set[Int], no explicit NFA graph. Everything that matched before still matches: all prior tests, the four teaching-error contracts, and the pulse.pt URL regex are green. A documented subset — no backreferences, lookaround, or lazy quantifiers.
v0.15.1072026-08-02

Phase M15 complete — the tuple proof

  • Sprint 107 closes Phase M15 with examples/roster.pt, a tournament-roster builder that exercises tuple decomposition end to end: it zips parallel name/score lists, enumerates them for seeding, destructures each pair nested right in the for binder (for (i, (name, score)) in …), groups players by tier, walks the groups with for (t, group) in grouped.entries():, stamps a UUID from Rng, and renders it all through toml — round-tripped back in the tests. fixed_rng pins the output, so it is deterministic and CI-gated beside pulse and digest.
  • Phase M15, complete: tuples went from build-only to fully decomposable — patterns only, no positional .0/.1, an LLM-first call for named binders over magic indices. The six sprints: tuple patterns (102), for-destructuring + Map.entries (103), the collection payoff (104), then the two M13 deferrals it unblocked, uuid (105) and toml (106), then this proof. The stdlib grew from 22 to 24 modules. The next phase is unplanned; the recorded carry-forward is richer patterns (alternation, groups, {n,m}) — a regex-engine change.
v0.15.1062026-08-02

toml — a documented subset

  • The second M13 ride-along: toml, parse and serialize. stdlib/toml.pt handles a documented subset — key = value pairs, [table] section headers, # comments, and four value kinds (double-quoted strings with escapes, integers, booleans, single-line arrays) — and round-trips. Floats, dates, nested tables, and multi-line strings are out, an LLM-first call for reliability over completeness like patterns and csv.
  • The value model shows off M15: a Value enum (Str/Int/Bool/Arr) and a Table record whose ordered pairs are a List[Tuple[Text, Value]], walked with for (k, v) in table.pairs: — tuple decomposition earning its keep in the stdlib. Pure POLYTONE; embedded in the browser runtime (24 stdlib modules) and on the API reference (169 items). No codec or compiler change.
v0.15.1052026-08-01

uuid v4 on the Rng capability

  • The first M13 ride-along: uuid, deferred back then for want of randomness and now unblocked by the Rng capability. stdlib/uuid.pt's uuid.v4(gen: Rng) -> Text draws sixteen bytes from the generator, sets the version nibble to 4 and the variant bits to 10 (RFC 4122), and renders the canonical 8-4-4-4-12 lowercase-hex form. Because randomness is a capability, a program that mints UUIDs declares it in main's signature — and fixed_rng(seed) makes the output reproducible, so the self-tests pin the shape, version, and variant deterministically.
v0.15.1042026-08-01

The collection payoff

  • Tuple decomposition pays off where it hurt most — the collection stdlib that spent M13 working around the missing feature. lists gains enumerate (each element with its index), zip_with (combine two lists element-wise), and unzip (split a list of pairs back into a pair of lists — the inverse of zip). unzip was impossible before: it both destructures each pair and returns a tuple, two things only decomposition allows.
  • The maps combinators — merge, map_values, invert, filter — are rewritten to for (k, v) in m.entries():, retiring the parallel keys()/values() index loops the module carried through all of M13. Behaviour is identical; the code is roughly half the size, and the invariant test that only existed to defend the workaround is gone. The API reference is regenerated (22 modules, 164 items). No codec or compiler change.
v0.15.1032026-08-01

for-loop destructuring + Map.entries()

  • Pair iteration is now idiomatic, cashing in sprint 102's tuple patterns. The for-loop variable became a pattern, so for (k, v) in pairs: destructures each element — a bare name (for x in xs:) still binds directly with no extra copy. A new Map.entries() builtin returns the (key, value) pairs as List[Tuple[K, V]], so for (k, v) in m.entries(): walks a map's entries directly; for x in map: still iterates the keys.
  • let and for binders must now be irrefutable: a refutable pattern there (like let (0, x) = …, where the literal 0 would be silently ignored) is a teaching error pointing you at match — which also closed a gap left in sprint 102. No new opcode, so no codec change; the differential (VM ≡ compiled WASM) holds at 31.
v0.15.1022026-08-01

Phase M15 opens — tuple decomposition

  • Tuples can finally be taken apart. Until now (a, b) constructed a tuple but nothing could read an element back — lists.zip was write-only and maps walked every entry through parallel keys()/values() lists. Now let (a, b) = pair destructures in a binding and case (a, b): destructures in a match; elements nest (let ((a, b), c) = …) and may be refutable (case (0, name):). Decomposition is by pattern only — there is no positional t.0/t.1 access, an LLM-first call for named binders over magic indices.
  • Under the hood: a new PatternKind::Tuple and StmtKind::LetPattern, and a TupleGet PTIR opcode (codec version 8 → 9, the phase's one encoding change) that reads a tuple element positionally — the index is a compile-time constant, always in range. The lowering reuses the record-pattern path machinery, and the token-based formatter round-trips the new syntax for free. A differential fixture keeps the compiled backend in step with the reference VM (30 → 31 cases). The stdlib zip is no longer a dead end.
v0.14.1012026-08-01

Phase M14 complete — the true record

  • Sprint 101 closes Phase M14 (Supervised fine-tuning) the way sprint 88 closed M12: the true record. The owner directive that opened M12 — supervise and fine-tune everything that exists — reissued for the surface M13 added and never reviewed. Eight rounds, 22 findings, every one fixed or recorded with a disposition: behaviour fixes (the nested-Rng-draw miscompile fixed ahead, base64 strict padding, csv reject-after-quote, the Rng lowering invariant), the docs and coverage the review found missing, and a CI gate that makes the API-reference drift impossible to reland.
  • Two reported findings shrank to verified non-defects on adversarial verification, and the toolchain round swept clean — a reminder that verifying each finding before it becomes work is load-bearing. The differential grew 28 → 30 over the phase. The M13 surface is hardened and the record honest; the next phase is unplanned, its seed the M13 feature carry-forward (tuple decomposition, richer patterns, toml, uuid v4).
v0.14.1002026-08-01

M14 language card + differential (R7/R8)

  • The IDE's language card — the frozen system prompt that drives its token-saving generation loop — had gone stale since M11: it still listed the retired env() builtin and knew nothing of the whole M13 surface. It now teaches the bitwise operators, the full stdlib (sets, maps, base64, hex, url, crypto, csv), and the Env and Rng capabilities, so an LLM generating POLYTONE in the IDE can actually use everything the language ships. CARD_VERSION 3 → 4.
  • The differential harness (VM ≡ compiled WASM) is single-file, so it could never reach the bitwise-heavy crypto and base64 modules through an import — precisely the code most likely to differ on shift and mask edges. A new self-contained fixture inlines those patterns (a sha256 sigma function, base64 six-bit packing, a crc32 step), so the compiled backend is now checked against the reference VM on them too. Differential 30/30.
v0.14.992026-08-01

M14 web/docs hardening (R6)

  • Sprint 99 clears the R6 web/docs backlog. The docs-site API reference had silently drifted — nothing regenerated it after M13, so seven whole modules were undocumented. It is regenerated from the shipping stdlib (22 modules, 161 items, including sets, maps, base64, hex, url, crypto, csv and the new lists combinators), and — the root-cause fix — a CI gate now asserts api.ts documents every stdlib module, so a missing module is a red test rather than silent drift.
  • Spec §19's module list is reconciled with what ships (the M11 and M13 modules added), and its 'the reference cannot drift from what compiles' claim is rewritten to describe the real mechanism — generated, committed, and CI-gated. The guide's standard-library section, which still said 'v0.1 ships ints, lists, texts', now teaches the grown stdlib and shows a batteries example computing a SHA-256 and Base64 in pure POLYTONE.
v0.14.982026-08-01

M14 stdlib hardening (R4)

  • Sprint 98 clears the R4 stdlib backlog — the six M13 modules get stricter parsing and honest cost docs. base64 decode now rejects malformed padding ('=' is valid only as a suffix of the final group, so 'Zm=v' and 'Zg==Zg==' are errors), and csv parse rejects content after a closing quote ('ab'c is malformed per RFC 4180). Canonical round-trips are unchanged. The base64 non-canonical-bits leniency, the csv empty-row ambiguity, the O(n²) cost of lists.unique/sort_by, and digest's 'checks paths are distinct, not deduplicates' wording are all documented.
  • The coverage the review found missing is closed: url decode's lowercase escapes and non-UTF-8 error path, crypto's over-long-key HMAC branch (RFC 4231 case 6) and a multi-byte crc32 vector, sets/maps empty-operand cases plus an explicit test that keys() and values() stay parallel (the invariant the maps combinators rest on), and digest.pt's non-UTF-8 and malformed-CSV error paths. Every stdlib module stays green under the self-test gate.
v0.14.972026-08-01

Phase M14 opens — the M13 hardening audit

  • Phase M14 (Supervised fine-tuning) opens the way M12 did: an audit of the surface the previous phase added, run round by round with each finding confirmed. The review confirmed ~22 findings across eight rounds (the full record is M14-REVIEW.md); one S1 was fixed ahead. F2.1: a Rng draw nested in another draw's argument — g.int(g.int(0, 5), 10) — lost an advance and correlated the two draws, because the lowering snapshotted the receiver before evaluating the arguments. Now the arguments lower first and the receiver last, so a nested draw evaluates exactly like the sequential form; because exec_simple is shared, the VM, browser runtimes, and compiled-WASM backend are all corrected with no codec change.
  • The rest of the R1/R2/R3 compiler backlog: the Rng draw lowering now asserts its mut-local invariant instead of falling through to a VM panic (F2.2); spec §29 documents the rng.int modulo bias and nested-draw semantics (F2.3); the codec 'every instruction' round-trip corpus now includes the bitwise and materialize opcodes (F3.1). One reported finding was verified a non-defect — the variant-pattern name-span already points at the field name, just like records (F1.1) — and the toolchain round (R5) swept clean.
v0.13.962026-08-01

The batteries proof — Phase M13 complete

  • examples/digest.pt closes Phase M13 the way pulse.pt closed M11: one end-to-end proof program, held green in CI. A content-addressed manifest builder whose main(disk: Fs, dice: Rng) is the whole effect footprint — it reads a CSV of (path, content) rows through Fs, dedupes them through a Set and orders them with the generic collections, sha256-addresses each row, base64-encodes the payload, stamps the batch with a random build id from Rng, and emits a JSON manifest through Fs. Every M13 battery — csv, sets/lists, crypto, base64, json, Rng — composed into one pipeline.
  • The CI gate runs ptc test on mock_fs/fixed_rng — no disk, no entropy — so the six tests verify deterministic bytes out (the exact manifest byte count is pinned by the reproducible fixed_rng build id). Phase M13 complete at 0.13.96: the stdlib grew from 16 to 22 embedded modules and the effect set reached its full Clock/Http/Fs/Env/Rng. Carried past M13 as the next phase's seed: tuple decomposition, richer patterns, toml, and uuid v4 (now unblocked by Rng). LLM-first calls that held: patterns stays a subset (not full PCRE), yaml is out, random/env are capabilities.
v0.13.952026-08-01

The Rng capability — randomness, made visible

  • Rng is the fifth capability and the first stateful one. A program that needs randomness takes it in main's signature — fn main(dice: Rng) — so non-determinism is visible, never ambient. rng.int(lo, hi) draws a uniform inclusive integer (lo > hi is a runtime error), rng.float() draws in [0, 1). fixed_rng(seed) builds a deterministic generator for tests; the real, entropy-seeded one arrives only under ptc run. The engine is SplitMix64 — not cryptographic, so reach for crypto when you need that.
  • A draw advances the generator, so an Rng is a mut binding — mut gen = dice; gen.int(1, 6) — enforced by the new MethodSig::MutatingReturning (a method that mutates its receiver and returns a value); calling on an immutable Rng is a teaching error. PTIR gained RealRng/FixedRng/RngInt/RngFloat (codec v7→v8), the differential VM≡WASM suite grew a fixed-rng case (27), and the effect set is now the full Clock/Http/Fs/Env/Rng.
v0.13.942026-08-01

Data & hashing — csv, sha256, hmac, crc32

  • Pure-POLYTONE crypto, proving the bitwise operators earn their keep: crypto.pt has sha256/sha256_hex (FIPS 180-4), hmac_sha256 (RFC 2104), and crc32 (IEEE 802.3), all in the language itself — 32-bit arithmetic masked with & 0xffffffff, the round-constant tables clean because hex literals already exist. Verified against the FIPS SHA-256 vectors, RFC 4231 HMAC test case 2, and the CRC-32 check value 0xcbf43926.
  • csv.pt: RFC 4180 parse (text → rows of fields) and serialize (rows → text). Fields with a comma, quote, or newline are double-quoted with embedded quotes doubled; a quoted field may span newlines; parse accepts LF and CRLF. Both modules are embedded in the browser runtime (22 stdlib modules now). toml and uuid are deferred — toml is a fiddly subset, and uuid v4 needs random (itself pending a mutating-method extension).
v0.13.932026-08-01

Encoding — base64, hex, url

  • The encoding batteries, clean now that the bitwise operators exist: base64.pt (encode/decode over Bytes, RFC 4648, verified against the RFC test vectors), hex.pt (decode — the inverse of the builtin Bytes.to_hex, case-insensitive), and url.pt (RFC 3986 percent-encode/decode — unreserved characters pass through, every other UTF-8 byte becomes %XX, round-trips Unicode). Pure, generic, self-tested.
  • All three are embedded in the browser runtime and covered by the stdlib self-test gate; they build only on primitives the differential already pins (bit ops, Bytes, Text). Richer patterns (alternation, groups, {n,m}) is deferred — a regex-engine change with backtracking correctness deserves its own focus, not a rushed addition alongside three encoding modules.
v0.13.922026-08-01

The environment is a capability — the last ambient effect retired

  • env becomes the fourth capability (Env): fn main(sys: Env) receives the environment, sys.var(name) -> Option[Text] reads a variable, mock_env(vars) builds a deterministic one. The ambient env(name) builtin is retired with a teaching error naming the capability form. With Env gone ambient, main's signature is now the whole effect footprint — the capability model's promise, finally complete.
  • The method is sys.var (not sys.get) because get would collide with Map.get at lowering, which has no type info; var reads naturally — the environment variable named X. PTIR EnvVar removed (opcode reserved), RealEnv/MockEnv/EnvGet added, codec v6→v7. Real and mock paths both verified VM ≡ compiled WASM.
  • Scope note: this sprint was planned as random + env. env is done; random is deferred to its own sprint. A useful RNG needs advancing state (gen.int(1,6) twice must differ), but capability methods are pure and capabilities are immutable values — so a stateful RNG needs a method-system extension (mutating methods that also return a value). That is a real feature, not to be rushed alongside a capability. Recorded, like the tuple gap.
v0.13.912026-07-31

Generic collections — sets, maps, list combinators

  • Pure, generic, self-tested stdlib modules filling the gaps around the builtin collections. The enabler: List.to_set() and Set.to_list() builtins — together they give set construction from any list, including an empty one (([]: List[T]).to_set()), the only way to name an empty Set since a set literal needs an element.
  • sets.pt: union, intersection, difference, symmetric_difference, is_subset, is_superset, is_disjoint (total — disjoint operands yield the empty set). maps.pt: get_or, merge, map_values, from_lists, invert, filter (entries via parallel keys()/values()). lists.pt: any, all, find, unique, flatten, chunk, min_by, max_by, sort_by (a stable generic insertion sort), group_by. map/filter/fold/sorted stay builtins.
  • A gap surfaced and was recorded honestly: tuples can't yet be decomposed (no .0/.1, no t[0], no pattern), so zip/enumerate-style pair output is write-only and maps reads entries through parallel lists instead of pairs. Tuple decomposition — a small language feature — is the natural next enabler, noted for a follow-up rather than rushed into this sprint.
v0.13.902026-07-31

Bitwise operators — the language primitive

  • The primitive M13's batteries are built on: &, |, ^, <<, >>, and unary ~ on Int, through the whole pipeline (lexer, parser, typeck, PTIR opcodes 82–87 / codec v6, VM, fmt, spec). Hashing, encoding, checksums, and flag work were impossible to write cleanly without them, and an LLM reaches for them by reflex. Int-only, with teaching errors on Float; ~0 == -1 (64-bit two's-complement) and >> sign-extends.
  • Precedence is an LLM-first decision: the bitwise operators bind tighter than comparison and looser than arithmetic, matching Rust and Python — so flags & MASK == MASK parses as (flags & MASK) == MASK, avoiding C's infamous &-below-== footgun. A shift amount outside 0..=63 is a runtime error, not a wrapped surprise. A bitwise differential case keeps the VM and compiled WASM identical.
v0.13.892026-07-31

Sharpening — Phase M13 (Batteries) opens

  • M13 opens by clearing the deferred M12 debt — the diagnostic and text-perf sharpening the batteries stand on. Pattern field errors now underline the offending field name, not the subpattern after it (the AST pattern field carries a name span, threaded through parser → typeck → PTIR lowering).
  • for ch in text: is now O(n), not O(n²): the loop indexed a Text with chars().nth(i) per step, a quadratic decode. A new MaterializeText PTIR instruction (codec v5) snapshots the char list once at loop entry — behaviour identical, Unicode included, the win is wall time. A text_scan benchmark exercises it. And the value-cycle error never lands at column zero anymore.
  • Honestly deferred: the hashed Set/Map backing (the other half of the perf finding) stays a focused follow-up — Set/Map keys aren't restricted to hashable primitives, so an order-independent hash that exactly mirrors equality across the shared runtime is a dedicated change, not one to rush alongside an AST change. It stays fuel-bounded and correct today.
v0.12.882026-07-31

The true record — Phase M12 complete

  • The consolidation close. Phase M12 — the fine-tuning pass — is complete: eight review rounds, eight fix-sprints, fifty-six findings, every one fixed or honestly recorded in the backlog of record. Sprint 88 cleared the last polish item that fit a clean close (the expression-position parse error now teaches 'the line ended — a value is missing' instead of naming a token kind).
  • Honesty over a green checkmark: three findings that want focused work with ripple — the VM char-cursor + Set performance, the pattern field-name error span (an AST change through parser/typeck/PTIR), and a cosmetic value-cycle span — are recorded as deferred beyond M12, each with an explicit disposition and a forward pointer. None is a loose end. The record is internally consistent; the next phase is not yet planned.
v0.12.872026-07-31

Tests / CI / the record: the findings pass

  • Round 8 found the CI content comprehensive (16 gates across every layer) and the record internally consistent — the findings were depth and process, not a missing gate. The tracked coverage holes are now all closed: nested-interpolation spans (the position shift, previously only structure was tested), VM-internal fuel exhaustion (an infinite loop under a bounded budget → the teaching error, previously only exercised downstream in web-rt), and non-BMP LSP diagnostic columns as UTF-16 not scalars.
  • The non-BMP test is a nice one: it compares an ASCII marker with a 🎼 (one Unicode scalar, two UTF-16 units) at the same scalar column and asserts the reported column differs by exactly one — a char-based offset would report them equal, which was the bug. F8.1/F8.2 (the Sprint-81 gate-incident root cause and the pulled-forward remediation record) were fixed in sprint 82 and re-verified. Tests only — no wasm rebuild for logic.
v0.12.862026-07-31

IDE + launch infra: the findings pass

  • The launch infrastructure and license verification were found clean in the review; the findings are in POLYTONEide's verified loop. isSafeProjectPath now rejects percent-encoded traversal (..%2fevil.pt decodes to ../evil.pt) — defence in depth, since nothing URL-decodes a project path today (F7.3). The two session-replay S1s (a path-escape on import, a malformed-session crash) were fixed in-round (F7.1/F7.2).
  • License keys can now carry an optional expiry (owner decision): an expires field enforced fail-closed after the signature verifies, checked offline against the local clock — no telemetry. Absent means perpetual, so every previously-issued key stays valid; sign-license.mjs takes an optional valid-days. The at-scale revocation policy stays a launch-time decision; the mechanism now exists (F7.5).
  • The verified loop's fenced-block parser broadened its path= capture from ASCII-only to any non-whitespace run (F7.4): a block whose path= held a backslash or a non-ASCII filename used to fail to match entirely and vanish; it now captures, so a valid Unicode name works and an unsafe one routes into the path-boundary teaching error that names it. IDE-only — no wasm rebuild for logic.
v0.12.852026-07-31

Web platform: the findings pass

  • The roadmap is now a collapsible phase → sprint tree: each phase is a branch with a progress bar and state pill, the active phase opens by default, and finished history folds away — replacing the flat 88-row timeline.
  • Security: writing the crafted-input HTML-safety tests surfaced a stored XSS the earlier name-field fix had missed — the video editor rendered an imported .ptv's ops text raw into a textarea (a </textarea><script> ops line broke out), and the timeline dropdowns rendered imported scene/sprite names raw. All now escaped, with tests pinning name and ops on every media-row helper (F6.7/F6.3).
  • The roadmap's SEO description was three phases stale ('phases M1–M8') and fed the meta description, OG tags, JSON-LD, and the prerendered page — now evergreen (F6.2). Plus boot robustness: currentRoute() no longer throws on a malformed %-path (F6.4). Web-only — no wasm rebuild.
v0.12.842026-07-30

Toolchain (CLI + MCP): the findings pass

  • Usage help never pollutes stdout on error: an unknown command prints 'error: unknown command' plus the usage text to stderr and exits non-zero, so a script piping ptc's output can't mistake a typo for success. print_usage and the error path share one usage_text() source so the two can't drift (F5.7).
  • CLI ↔ MCP parity for malformed input: 'ptc check a.pt b.pt' now errors on the extra argument instead of silently checking only b.pt; the MCP context tool rejects a 0 line/column (positions are 1-based) instead of resolving to no item; and the MCP run tool rejects a non-string args entry instead of dropping it and running a shorter list than the caller passed (F5.10).
  • New CLI integration coverage (crates/ptc/tests/cli.rs) for the failing and empty branches that had none — run on a missing file / runtime error / with pass-through args, test on a red block and on a file with no tests, fmt --check on a non-canonical file plus its idempotent rewrite, and the doc/build missing-argument paths — each pinning exit code and message together so a regression can't ship green (F5.9).
v0.12.832026-07-30

PTIR/codec + stdlib/formats: the findings pass

  • json.parse now follows the JSON number grammar exactly (RFC 8259): leading zeros (01, 007, -01) and a bare trailing decimal point (1.) are teaching errors, and an exponent needs at least one digit. The lax accept-and-normalize forms broke round-trip fidelity — what parses back out is now what came in (F4.1).
  • The codec encoder asserts code.len() == spans.len() so a lowering divergence fails at the defect, not deep in an unrelated field (F3.3); opcodes 62/63 are reserved so a new instruction can't silently reinterpret an old blob's byte (F3.7); and the codec tests gained float bit-pattern round-trips (NaN/±Inf/−0.0 via to_bits, which assert_eq can't see), the i64 boundaries, and a multi-byte-UTF-8 string (F3.4/F3.5). The differential holds 26/26.
v0.12.822026-07-30

Compiler + VM: the findings pass — M12 opens

  • Phase M12, the fine-tuning pass, opens. Its eight-round review surfaced 55 findings across every layer; every S1 (rounds 1–7) was fixed ahead of the sprints and this bump records it: the generic-shadow and nested-exhaustiveness and Float→Int-boundary fixes, the capability legibility invariant (opaque + placement-restricted, no smuggling past a signature), the codec allocation-bomb cap, curl argument-injection, the fmt-gate and pipe and protocol fixes, the media-tool stored-XSS, the server-enforced download gate, and the IDE session-replay path check.
  • Sprint 82 clears the rest of the R1+R2 backlog: a builtin/capability name can't be a function name, async fn -> Task[T] teaches instead of double-wrapping, bare fixed_clock/mock_http/mock_fs teach 'call it', the lexer rejects a trailing '_' and an infinite float, the fuel doc matches the code, parse_request fails fast, the curl status surfaces a malformed code, and the request/render temp files get entropy + O_EXCL against a symlink race. The capability differential is already covered (26/26, VM ≡ compiled).
  • Also folded in: two early-access apps behind the same password — a universal Viewer & Player for all five native formats, and an honest classic→POLYTONE Converter (images→.pti, 3D→.ptm, text→.ptw; audio/video are intent formats, explained not faked). And scripts/preflight.mjs + an opt-in pre-push hook run the fast gates locally so a broken gate can't reach develop again. Version 0.12.82; both wasm blobs rebuilt.
v0.11.812026-07-29

The proof — M11 complete

  • examples/pulse.pt is the end-to-end program M11 was built toward: a data-fetching static-site generator whose main(wall: Clock, net: Http, disk: Fs) names the whole effect footprint in one line. It fetches a JSON feed over Http, decodes it with json into typed records — a malformed feed is a typed error naming the field, never a crash — validates every link with patterns.matches (bad URLs render without an anchor), stamps the build via Clock + time.to_iso, and writes one HTML page through Fs.
  • The loop is the proof: five test blocks run the same pipeline on mock_http/mock_fs/fixed_clock, so ptc test verifies a networked program with no network, no disk, and no wall clock — down to the exact byte count of the written page — and a new ptc integration test CI-gates it. The real path holds too: under ptc run the same program fetched from a live HTTP server via the system curl, stamped real time, and wrote a real file.
  • Consolidation closes the phase: the missing capabilities_fs fixture landed (every capability now has one), stale site copy caught up (the roadmap intro no longer hardcodes a sprint count, the footer reads spec v1.0), and the version moves to 0.11.81. Phase M11 complete — capabilities from model to proof: json, time+maths, patterns, Clock, Http, Fs and the retirement, the proof.
v0.11.802026-07-29

The filesystem is a capability

  • The Fs capability — the third capability, and the first retirement. Clock and Http were additive; Fs closes a hole: the ungated read_file/write_file builtins let any function touch the disk without its signature saying so, the one ambient effect that contradicted the capability promise. Now file I/O is disk.read(path) -> Result[Bytes, Text] and disk.write(path, bytes) -> Result[Int, Text] on an Fs you receive — exactly the old contracts, so migration is mechanical: thread disk, rename the call.
  • Retired with a teaching error, not a silent break — calling read_file/write_file now names the capability form to write instead ('fn main(disk: Fs)' and disk.read/disk.write), so a generator reaching for the old shape is corrected at compile time. Received, never constructed: Fs(...) is a teaching error; the real capability arrives through main (host files under ptc run, the sandbox's in-memory VFS in the browser — the seed-and-read-back preview contract is unchanged), and mock_fs(files), a Map[Text, Bytes], is the deterministic one: reads answer purely from the map, writes report their byte count without persisting.
  • The whole surface moved: all nine examples, the playground samples, the media-tool generators, the language card (v3), and every embedded test program now thread an Fs; codec v4 replaces the ReadFile/WriteFile opcodes with the Fs instruction family, and the differential gate holds both backends to one mock semantics. env stays the one ambient read — deferred until it earns its own capability — and args() stays ungated by design: it is main's startup input, not an effect. Version moves to 0.11.80.
v0.11.792026-07-29

http over the capability

  • The Http capability — the second capability, Clock's shape on a wider effect, so a networked program is now writable and verifiable. The pure request/response values live in a new http stdlib module (Method, Status, Request, Response, plus builders like get/post and is_success); the capability carries the one effect, net.send(request) -> Result[Response, Text]. A function that reaches the network takes an Http — its signature says so, and nothing without it can.
  • Received, never constructed — Http(...) is a teaching error. A program gets one as a parameter of main (the runtime does the real request under ptc run) or from mock_http(routes), a Map[Text, Response] keyed "{METHOD} {url}" that answers deterministically — the fixed_clock analogue. ptc test never calls main, so tests thread a mock and the loop never touches the network; a real request happens only at ptc run.
  • Real requests use the system curl — zero new dependencies, the same choice ptc vendor already makes (the toolchain carries no TLS stack of its own). The main-capability gate and the injection point, which hardcoded Clock, were generalized to a capability set, so native Fs (80) slots in mechanically. The differential gate (now 25 fixtures) holds the VM and the compiled runtime to one mock semantics. Version moves to 0.11.79; the API reference now lists 15 modules. v1 gap: real response headers are not exposed yet (additive).
v0.11.782026-07-28

The capability model + clock

  • The effect model M11 was built toward. An effect is a capability value threaded through signatures: a function that touches the outside world takes the matching capability as a parameter, so its type says what it can do — and nothing without that value can. Clock is the first capability; Http and native Fs will follow the same shape. The whole-program effect footprint is legible in main's signature, with no ambient authority to pass around.
  • Clock is an opaque type you receive, never construct — Clock(...) is a teaching error. A program gets one only as a parameter of main, where the runtime injects the real clock under ptc run, or from fixed_clock(instant), a deterministic clock pinned to an instant. clock.now() reads it and returns a time.Instant: a real clock reads the host, a fixed_clock returns its pinned instant.
  • The point is verifiability: ptc test never calls main, so tests receive no real clock — they build fixed_clocks and check clock-reading code without the real world. Real time crosses only at ptc run, through the typed host, the same boundary read_file/env already cross. The whole slice landed on one capability — checker, PTIR, the VM and its host, the compiled wasm runtime — and the differential gate (now 24 fixtures) holds both backends to one clock semantics. Version moves to 0.11.78; M11 block B has opened, with Http and native Fs next.
v0.11.772026-07-28

patterns — glob & regex-lite

  • The patterns module adds two pure Text matchers, enough to parse and validate the strings networked programs live on. matches_glob is the shell glob — * (any run, including empty), ? (one char), [a-z]/[!neg] classes, everything else literal; total, so a malformed class just degrades to a literal bracket.
  • matches is a regex-lite subset matched against the whole text — anchored at both ends, the right primitive for asking is this a valid X. It handles literals, . (any char), [classes], the shorthands \d \w \s (and negated \D \W \S), and the quantifiers * + ?, resolved by a backtracking matcher. A malformed pattern — a quantifier with nothing to repeat, a doubled quantifier, an unclosed class, a dangling backslash — is a teaching error, never a silent mismatch.
  • With patterns, M11 block A — the pure standard library (json, time, maths, patterns) — is complete. Block B is next: the capability model and clock, then http and native FS — the effect research these pure modules were built to be verified against. Version moves to 0.11.77; the API reference now lists 14 modules.
v0.11.762026-07-27

time + maths — pure values

  • The time module makes dates pure values. An Instant is a point on the UTC timeline (seconds since the Unix epoch), a Duration a signed span of seconds. Build one from validated calendar fields — of_civil range-checks every field, so February 30th is a teaching error, never a silently wrong date — break it back down with to_civil (weekday included), add and subtract durations, compare, and format as ISO 8601. The calendar math is Howard Hinnant's exact integer algorithms, which assume the truncating division POLYTONE already has.
  • Reading the actual clock is an effect, so now is deliberately absent — it arrives in Sprint 78 as a capability, and these value types are exactly what it will hand back. The whole module is pure and deterministic, which is what lets the verified loop check time-handling code without a real clock.
  • The maths module expands the Float surface beyond the four builtin primitives (abs/sqrt/sin/cos): pi/tau/e, Float min/max/clamp/sign, integer-exponent pow, hypot, tan, degree/radian conversion, lerp, and decimal rounding. The integer helpers stay in ints; this is the floating-point companion. Both modules are written in POLYTONE, self-tested, and embedded in the browser runtime. Version moves to 0.11.76; the API reference now lists 13 modules.
v0.11.752026-07-26

json — parse & serialize (M11 opens)

  • Phase M11 (capabilities) opens with json, the first pure stdlib addition. JSON is dynamically shaped and POLYTONE is statically typed, so a parsed document is a Json enum you match on (Null/Bool/Int/Float/Str/Arr/Obj) — never a dynamic any, and a missing key stays Map.get -> None. Match, don't guess.
  • parse is a recursive-descent parser returning teaching errors on malformed input — unclosed strings, trailing content, bad literals all name the position and what was expected. to_text serializes back to canonical compact JSON, round-tripping byte-for-byte with Int and Float kept distinct for fidelity. Written in POLYTONE, self-tested, embedded in the browser runtime so the playground and IDE resolve use json too.
  • Documented gap: the input escapes that name a codepoint the language can't yet build as a literal — \uXXXX, \b, \f — parse to a teaching error; literal UTF-8 round-trips fine, and output is always literal UTF-8. Additive under §27. Versions move to 0.11.75; both wasm blobs rebuilt and freshness re-stamped; the API reference regenerated to include json.
v0.10.732026-07-26

Launch infrastructure (flag-off) + M11 kickoff

  • Phase M10 (launch) on the record, and the version made honest for the sprints it shipped. The public launch is decoupled from 1.0.0 — per the owner's directive the go-live is a 0.x event, and 1.0.0 is a separate, later milestone: only when POLYTONEide is a serious product on the market. The whole beta stays 0.x.
  • The M10 launch infrastructure is merged, flag-off: a shared LAUNCHED/CHECKOUT_URL switch, a Paddle hosted-checkout scaffold (signing key in the environment, never the repo), a standalone Tauri v2 desktop shell, and the launch-flip surface — four tests prove the flip flips everything without flipping the real flag, which stays false. The product page gained an honest scope section (built today vs not yet).
  • Versions move to 0.10.73 across every package and the compiler (they had stayed 0.9.70 while sprints 71–73 shipped flag-off), and Phase M11 (capabilities, sprints 75–81) is kicked off on the roadmap: effects become explicit capability values so networked and data-heavy programs stay verifiable — the pure stdlib (json/time/patterns) first, then the capability model, http, and native FS.
v0.9.702026-07-24

The true record

  • The consolidation phase closes by making the record true. §19 of the spec is rewritten to the real nine standard-library modules — so §27's compatibility freeze covers the whole media/pkg surface everyone already relies on, not just the three modules it used to list. §8.4 stops presenting lifted limits as current and names the four builtins; §10 gains the block-lambda grammar; the footer reads 0.9.70 — sprints 1–70. CONCEPT's status is realized, the CLAUDE.md phase line and the M6/M7/M8 phase-complete markers are current.
  • The five git tags orphaned when the toolchain renumbered to 0.x (0.6.48, 0.7.49–0.7.52) are restored as annotated tags at their commits. Release safety hardened: the publish job no longer ships a downloads manifest advertising binaries that a failed build never produced, and CI and release get concurrency guards and a declared Rust toolchain shape.
  • And the reason this class of finding can never silently reopen: a docs-record CI gate ties the spec footer, the CLAUDE.md phase line, and the ROADMAP checkmarks to the shipped version — the single source of truth is the top changelog entry, and everything derived from it must agree, on every push. Phase M9 — the consolidation pass — is complete: 63 review findings across seven rounds, all fixed or honestly recorded.
v0.9.692026-07-24

IDE + site: the findings pass

  • The verified loop's five S1 findings are closed: model output can no longer write outside the project (a path-boundary check rejects ../ and absolute paths), the user's chosen target is no longer clobbered before the run, the verified verdict parses the test summary instead of substring-matching FAILED (a test literally named FAILED verifies now), a mid-loop provider error keeps the tokens it already spent in the ledger, and 'applied' no longer counts as 'verified' (a distinct flag set at generation).
  • The rest of the IDE findings too: token honesty (an estimate when the provider omits usage, guarded number fields), no-op change-sets dropped, zero-byte renders rejected, case-insensitive media extensions, and the Free ledger's count-everything total documented as the honesty guarantee it is. The IDE suite grew 43 → 60 tests. The LSP formats now — polytone-fmt wired to textDocument/formatting, the README made honest about what M10 still owes.
  • On the docs site: Error Lab subpages keep their own title and description when the SPA hydrates (two S1s — they used to become 'Start'), polytone-mcp appears on all three surfaces that omitted it, the static no-JS nav and sitemap fallbacks are current, and seven dead i18n keys are gone (web harness at 58). And a jsdom deploy-bundle smoke now mounts both apps before the FTPS upload, so a build that compiles but crashes at init can't reach production — the context benchmark, which had silently broken at the Sprint-63 extraction, re-runs at 0.9 (8.0× fewer characters on stdlib-media).
v0.9.682026-07-24

Media tools: the findings pass

  • The four S1 findings are gone: video thumbnails compile scenes as .pti v4 (the shipped demo renders again), the image editor's undo is snapshot-based and cannot corrupt across layer reorder/delete, the exported film .pt embeds its soundtrack inline (standalone runs have sound now), and Enter in the web viewer submits the input's own form group instead of the first button on the page.
  • Sessions reopen everywhere: the editor loads its own .pti, the studio its .pta (v1 and v2, envelope presets recovered), the video editor its .ptv — each import rejects unsupported constructs with a teaching message. The codec surface became reachable: a text tool and true N-gon polygons in the editor (which speaks v4 now), custom ADSR envelopes and 8 tracks in the studio. And all three tools have bounded snapshot undo.
  • The format docs tell the truth again: the overview inventory no longer claims four formats are 'planned', .pti gained its v4 text-op section, the .ptv doc's wrong teaching error is corrected, four titles carry their real versions. The OBJ bridge's geometry-only nature is documented — its bytes stay frozen per §27. Performance numbers come from one pinned re-runnable benchmark now. Every fix carries a regression test: the web harness doubled to 52.
v0.9.672026-07-24

async means something

  • The keyword that parsed for 66 sprints without meaning has the smallest true semantics now — exactly as decided in the review round: calling an async fn runs nothing and yields a Task[T]; .run() is the only way anything happens; tasks.all (a new stdlib module, written in POLYTONE) drives a list in fixed, deterministic order. No scheduler, no preemption, no await.
  • The implementation is the trick: a Task lowers onto the existing closure machinery — the VM and the compiled backend execute identical deferral with zero new instructions, and the differential gate holds it automatically (23/23, deferral order included). Teaching errors guard every misuse: a discarded Task, an async fn used as a value, calling a Task like a function, an async main, interpolating or comparing Tasks.
  • The surface follows everywhere at once: spec §28 (and §9's deferral list shrank again), the LSP shows the async prefix on hover, the language card is v2, the API reference carries the tenth stdlib module, and the guide plus a playground example teach the deferral with real recorded outputs — the first print proves nothing ran.
v0.9.662026-07-24

Codegen: the arc, closed and proven

  • The review's ground check said the codegen arc really closed in Sprints 51/52 — this sprint proves it harder: the differential corpus now drives all five media codecs through ptc build (a checksummed bridges fixture, 22/22 byte-identical with the VM), compiled-vs-VM bench tracks report wall time with asserted-identical outputs, and §9 of the spec no longer contradicts §12.
  • The wasm freshness gate: CI now hashes every source the committed browser runtime is built from (stdlib + the seven crates that feed it) against a stamp the rebuild routine writes — a stale committed wasm shipping old semantics behind green CI is impossible now.
  • Errors teach again where they had eroded: division/modulo by zero name the guard, integer overflow names the width, key-not-found names .has()/.get(), ptc's raw IO errors say what to pass, and the highest-frequency type error 'expected X, found Y' finally names its fix. Plus: ptc render — the CLI twin of the MCP render tool, same driver machinery, symmetry closed.
v0.9.652026-07-24

Crate depth

  • Cargo is at 586 tests (+100): the thin crates carry real suites now — ptir gained codec round-trips over real programs, exhaustive corrupt-input rejection, a STATIC_MESSAGES sync gate, and lowering-shape goldens; driver covers every public entry point; wasm-rt proves the host interface, the stack-overflow teaching path, and copy-on-write sharing edges; ast has its first tests.
  • The fixture corpus grew to 23 and the harnesses walk recursively — the modules/ trio is loader-checked instead of silent luck, and the new fixtures (Result propagation via ?, record patterns, the Sprint-37 iteration lifts, output-pinned numeric forms) joined the codegen differential gate automatically: 21/21. Block lambdas and empty-collection inference are fixtured too.
  • Hardening: MCP survives malformed JSON on the wire (-32700, binary-level test) and the LSP has a real stdio smoke; hostile buffer lengths no longer abort the browser-runtime wasm module and its response writers null-guard; every production expect() carries a justifying comment again (house rule restored); stdlib self-tests grew to 91 — the mesh builders and image primitives are tested by name, and ints.is_even is no longer dead surface. The fmt corpus gate is recursive.
v0.9.642026-07-24

The web test harness

  • web/ has a test script for the first time: Vitest rides the existing vite config (path aliases included), 23 tests across four suites, gated in CI — the last untested package in the monorepo now has a harness.
  • The share-link codec moved out of the playground into a pure seam (src/share.ts) and its review holes are fixed: an empty-project share no longer decodes into literal JSON garbage, crafted payloads with malformed tuples are rejected instead of smuggling undefined into typed fields, and legacy single-file links keep working — all round-trip-tested.
  • The consistency gates work: roadmap.ts↔ROADMAP.md checkmark parity and registry↔index.ptr summary equality (the page now shows the canonical manifest summaries). The parity gate caught its first real drift on arrival — the checkmarks missing at sprints 46 and 49 since their releases. Every curated playground example runs against the committed runtime in CI, with expected-fail flags for the two that fail on purpose.
v0.9.632026-07-24

One bridge, one highlighter

  • Phase M9 implementation opens with the end of the byte-copy era: the runtime bridge, the highlighter, and the y4m decoder live once in polytone-web-shared (runtime/web/), imported by the site and the IDE through one root pnpm workspace with one lockfile. The CI drift check and the wasm-sync script retired.
  • The reconciliation swallowed the unenforced near-duplicates too: the teaching-error position parser (the IDE's path-aware superset won) and the PPM decoder — which turned out to exist five times across the two apps, now exactly once, with the truncation guard everywhere.
  • The wasm is a declared build input now: the committed source of truth stays web/public/, and a vite plugin serves it in dev and emits it into the IDE's dist on build. First real unit tests at the source: the length-framed protocol against the committed wasm, highlighter token classes (known quirks pinned on purpose), the y4m/PPM decoders, error positions.
v0.8.622026-07-23

The launch surface, early access

  • POLYTONEide is deployed but not launched: the workbench moved to /app/ and opens only after the early-access password — checked as a salted SHA-256 hash (only the hash lives in the repo), with the unlock sticking per browser and a teaching error on a wrong attempt. Rotating the password invalidates every prior unlock.
  • The product page is the launch surface: coming-soon banner, the CI-gated benchmark numbers (a two-file toy is a wash; importing images is 7.6× fewer tokens per request), and pricing — Free 0 €, Pro ≈ 12 €/month intended and validated against the ledger before launch, Team later.
  • The /ide/ path collision is resolved: /ide/ is the product page, /app/ is the workbench — the deploy chain builds both. Public launch, hosted checkout, and the desktop (Tauri) beta follow when the product flips public; 1.0.0 stays reserved for that moment.
v0.8.612026-07-23

The product shell

  • License keys verify offline: a Pro key is PTPRO.<payload>.<signature>, ECDSA P-256, checked in the browser against an embedded public key — no account, no activation server, no phoning home. Hosted checkout launches with 1.0; until then Pro keys are issued manually.
  • Free/Pro gating, honest by design: Free keeps the whole verified loop, BYO keys, media previews, and the local token ledger. Pro unlocks cheap/strong routing, session export/replay, and the full intent history (Free shows the last 10).
  • First-run onboarding (one card, four sentences), a plan badge in the header, a license section under ⚙ with teaching errors for bad keys — and the POLYTONEide docs page on this site. Telemetry: none; the ledger is local.
v0.8.602026-07-23

Media-native development

  • Open a native document and the editor splits: the pane beside it renders the .pti/.pta/.ptm/.ptw/.ptv through its sandbox codec as you type — debounced, with the codec's teaching error in place when the document breaks. The welcome project ships a logo.pti so the pane demos itself.
  • Intents target documents now: the loop verifies every changed document by rendering it — a doc that does not render fails verification exactly like a module that does not compile, and the codec's teaching error becomes the repair prompt. One machinery for code and media.
  • Program outputs preview inline in the run panel: PPM onto a canvas, Y4M as a canvas player, WAV as audio, HTML in a sandboxed iframe — the playground's proven decoders, now in the workbench (with the y4m decoder as the third CI-enforced byte-copy).
v0.8.592026-07-23

Routing & the benchmark

  • Cheap/strong routing: the configured cheap model tries every intent first and escalates only when its bounded repairs fail verification. Local checks are free — a failed cheap attempt costs cheap tokens, never strong ones. Rounds and tokens attribute per tier; the intent record carries the winning model and an escalated flag.
  • The frozen language card ships as one cacheable system block (cache_control) — after the first request it reads from the provider's prompt cache. Frozen surface, cache that never invalidates: the §27 promise turned into cost mechanics.
  • benchmarks/context is the published, reproducible slice-vs-file-context benchmark, gated in CI against the committed runtime. The honest numbers: a two-file toy is a wash (0.9×), five modules 1.1× — and a program importing images ships 7.6× fewer tokens per request (2,021 vs 15,340). The savings grow exactly where projects do.
v0.8.582026-07-23

The intent ledger

  • An intent's answer is a change-set now: a PLAN line plus one path-labeled block per changed file — multi-file, new files included. Verification runs every changed module and then the project entry; the panel shows the plan and per-file diffs before you apply anything.
  • Every run is a first-class record with status (verified, unverified, applied, discarded), rounds, and token cost — the ☰ history lists them with expandable diffs, and the token ledger derives from the records. One source of truth, local only.
  • Sessions export as replayable polytone-session files: importing replays the verified change-sets in order and re-verifies locally (compile + tests) before touching the project. A replay never calls a model; an unverifiable replay never applies.
v0.8.572026-07-23

The verified loop

  • POLYTONEide generates now — and every candidate meets the toolchain before it meets you: intent → context slice → generation → sandboxed check + tests → bounded teach-repair, where the repair prompt is the teaching error line, not the file. The result is a diff marked verified (or honestly not), with Apply/Discard.
  • Bring your own key: Anthropic, OpenAI, or any OpenAI-compatible endpoint (local runners included). The key lives in localStorage and travels only to your provider. A local token ledger counts every run — tokens are your money; the ledger is UI, never telemetry.
  • polytone_context joined the browser runtime: the IDE slices context on the same polytone-driver machinery ptc context runs — plus one stable language card of the frozen surface as the cached system prompt. The loop is golden-tested against the real wasm with a scripted provider; no network in CI.
v0.8.562026-07-23

ptc context — the context compiler

  • ptc context computes the minimal true context for an edit from the module graph and emits it as the polytone-context v1 document: the file's own top-level items plus the pub surface of its direct imports — structured signatures with docs — current diagnostics as payload, and at --at the one enclosing item with its body. Never other bodies, never the prelude: that surface is frozen and belongs in one provider-cached language card.
  • The same slice is the seventh polytone-mcp tool ({file, line?, col?}) — agents slice instead of reading whole files. This is the token mechanism made concrete: an edit request ships hundreds of tokens, not a file tree.
  • The contract is frozen in spec §27 next to polytone-diagnostics: fields only ever added, version bumps only on breaks, exit 0 whenever a slice was produced — diagnostics are repair context, not failure. Golden-tested on three levels (driver, CLI, MCP session). The verified loop builds on this next (Sprint 57).
v0.8.552026-07-23

POLYTONEide — the workbench

  • Phase M8 opens: ide/ joins the monorepo as a self-contained package (strict TS, zero runtime dependencies). The bare workbench: project tree, tabs, the playground-proven overlay editor, and a run/test panel against the committed wasm runtime — the teaching error's position jumps to the failing file and marks the line.
  • Projects live in IndexedDB — autosaved, restored on boot, switchable, seeded with a welcome project. Local folders open via the File System Access API (explicit save-back, reconnect banner after reload); browsers without it get import and download.
  • Deployed under /ide/ next to this site (unlinked until Sprint 62). No AI yet — the verified loop arrives in Sprint 57 on the agent-tooling substrate. The compiler stays 0.7.54: no Rust changed.
v0.7.542026-07-23

Agent-native tooling

  • ptc check --json emits the polytone-diagnostics v1 document: ok plus diagnostics with file, 1-based line/col, phase (read|parse|module|type), and the teaching message with its position structural instead of embedded in prose. The contract: fields only ever added, version bumps only on breaks, exit 0 exactly when ok. And ptc check resolves imports now — files that ran fine finally check fine.
  • polytone-mcp — the toolchain as MCP tools over stdio: check, run, test, fmt, doc, render. Tool failures are teaching errors in the result; check returns the JSON document with isError false, so agents branch on ok. Ships in the release archives next to ptc and polytone-lsp.
  • Both fronts sit on the new polytone-driver library (check pipeline, diagnostics, docgen, embedded ptrender) — the substrate Phase M8's IDE loop drives next. The 'POLYTONE for agents' guide on this site documents the whole contract with recorded sessions.
v0.7.532026-07-23

ptc bench — the performance pass

  • ptc bench runs the benchmark suite — ordinary POLYTONE programs under bench/ — and reports two numbers per benchmark: ops (executed VM instructions, deterministic and machine-independent, verified identical across runs) and wall time. --record appends to bench/history.jsonl, so a performance change lands in the same commit as its proof; --check fails on ops regressions beyond 1% and runs in CI.
  • The first performance pass, judged by the suite: the gradient rasterizer interpolates per row and per column instead of per pixel — rasterizer −52.4% ops, codecs −29.8%, the whole suite −41% (127 → 72 ms), with byte-identical output verified against the previous stdlib.
  • The gate cuts both ways: two candidate VM optimizations (frame-vector pooling, shared value names) showed no measured win on the suite and did not land. Optimizations need proof, not plausibility.
v0.7.522026-07-22

Native codegen II — closures & the whole language

  • Function values compile: closures carry their captures in the value runtime and dispatch through the module's function table via call_indirect — higher-order stdlib code (map/filter/fold), captured environments, and recursion through function values all ride the WASM call stack.
  • args(), env(), read_file, and write_file reach the host through four runtime imports with the ptc run contract; a compiled media example writes a byte-identical PPM. CoW and the move optimization run the VM's own fast paths in compiled code.
  • ptc build compiles every fixture: the differential suite now requires all 17 to compile and match ptc run byte for byte — the codegen boundary is gone, the CI gate is total. 485 cargo tests, 82 stdlib self-tests.
v0.7.512026-07-22

Native codegen I — values in memory

  • ptc build compiles full-value programs to WASM: records, enums, lists, Maps, Sets, Text, Option/Result, match, for, with, the whole prelude, monomorphized generics, and multi-file programs — every reachable function becomes a real WASM function with real control flow, calls, and recursion.
  • One canonical semantics: the module carries its lowered program and drives the committed value runtime — the reference VM's own exec_simple compiled to WASM, values reference-counted in linear memory. Never a second implementation.
  • The differential suite is a CI gate: 14 of 17 fixtures compile and match the VM byte for byte; runtime errors carry the same teaching message and source position on both sides. Function values, args()/env(), and file I/O teach native codegen II (Sprint 52).
v0.7.502026-07-22

The publishing gate

  • ptc pack validates a package end to end before it meets a registry: strict manifest (teaching with pkg.render's words), fmt --check over every module, a /// doc on every pub item, and every test green — at least one test required, so an untested package cannot pass.
  • A green gate prints the exact index.ptr line to paste; a red one lists every problem and exits nonzero. Publishing stays a git push — the gate makes it safe.
  • The example packages pass their own gate: imaging-extras and poster-tools gained test blocks, poster-tools carries its vendor/ directory, and CI packs both on every push.
v0.7.492026-07-22

Packages learn dependencies

  • polytone.pkg version 2: a deps: section names required packages with minimum versions — same header discipline, teaching by version, v1 manifests keep parsing.
  • ptc vendor resolves registries transitively: depth-first, cycles are teaching errors naming the chain, conflicting requirements name both versions — and vendor/vendor.lock records every package as name, version, needed-by, origin.
  • poster-tools joins the live registry as the first package with a dependency — vendoring it pulls imaging-extras automatically; verified end to end against a local registry (cycle, conflict, and missing-dep errors included).
v0.6.482026-07-22

CI gate, spec audit, the promise

  • The compatibility promise, spec §27: the language surface, format versions, the stdlib pub API, bridge outputs, and the teaching contract are frozen from here on — new things may arrive, existing ones never change spelling. (Originally shipped as "1.0.0"; renumbered — POLYTONE stays 0.x through the beta.)
  • The spec survived a machine-checked drift audit — every POLYTONE code block runs through ptc check — and graduated to v1.0 (polytone-spec-v1.0.md, sections 1-27).
  • ci.yml gates every push: compiler tests, clippy, the canonical-formatting corpus, all 81 stdlib self-tests, the committed browser runtime, and the site build.
v0.6.472026-07-22

The API reference that cannot drift

  • A new API section documents every pub item of the standard library — 76 entries across 9 modules with signatures, field lists, and /// docs, filterable and wired into sitewide search.
  • It is generated, not written: gen-api runs ptc doc over the stdlib and parses the toolchain's own .doc.ptw output, so the reference always matches the code that ships.
  • Every stdlib page of the reference is also prerendered as a static page for bots and no-JS readers.
v0.6.462026-07-21

The compiler leaves the repo

  • Native toolchain downloads for Windows, macOS (Apple Silicon + Intel), and Linux: ptc and polytone-lsp with the stdlib and examples in one archive — the stdlib is found automatically next to the binary.
  • Built by CI on real runners on every version tag and delivered through the deploy chain to /downloads/ — stable filenames, SHA256 checksums, a VERSION stamp.
  • The site gains a Download page with per-platform install steps, and the project gains its badge: counter-spinning tone rings around a still P.
v0.6.452026-07-21

3D gets color and a camera

  • .ptm version 2: shapes take color #rrggbb or a palette name — the same color grammar as .pti — and the mesh carries one color per triangle. Version 1 files render unchanged; v2 surface under version: 1 teaches the bump.
  • mesh.render_view — a software rasterizer written in POLYTONE itself: auto-framed orbit camera (yaw/pitch in degrees), perspective projection, painter's algorithm, two-sided key-light shading. The model domain gets a picture without a second bridge.
  • The 3D viewer joins the site's tools: the document is a .ptm v2 file, dragging the canvas orbits the camera, and the session exports as .ptm, .obj, or a reproducing .pt program.
v0.6.442026-07-21

The language server

  • polytone-lsp speaks JSON-RPC over stdio: live diagnostics with the exact teaching errors ptc prints — imports resolve through the real module loader with your unsaved buffer substituted.
  • Hover shows signatures and /// docs, for local and qualified names like images.canvas.
  • Golden-tested with scripted sessions; Neovim, Helix, and VS Code setup in lsp/README.md.
v0.6.432026-07-21

The live registry — Phase M6 opens

  • This site IS the registry: packages plus a self-describing index.ptr under /registry/, published by git push through the deploy chain.
  • ptc vendor <registry-url> <name> consults the index — unknown names teach with the list — then fetches manifest and modules over HTTPS into vendor/.
  • Phase M6 planned toward v1.0: LSP, .ptm v2 + a POLYTONE-written 3D rasterizer, the generated API reference, CI + spec audit, then the compatibility promise.
v0.5.422026-07-21

Playground v2 — Phase M5 complete

  • Multi-file tabs: every tab is an importable module — modules are finally teachable, with a dedicated example crossing a module boundary.
  • Syntax highlighting over the editor, and error-line markers: teaching errors switch to the failing file's tab, highlight the line, and scroll to it.
  • Share links carry the whole file set. Phase M5 done: fmt/doc, four tool v1s, forms, and this — every tool now edits its native format with real depth.
v0.5.412026-07-21

.ptw v3 — apps with input

  • input and button blocks: a button submits every field above it as name=value arguments on its app address — state rides the address, apps stay pure functions of it.
  • web.form_value decodes submissions; the HTML bridge stays script-free — the viewer hydrates clicks and Enter.
  • The guestbook joins the demo site: sign it, and your entry rides the button address into the next render.
v0.5.402026-07-21

.ptv v2 + video editor v1

  • Sprites move: local-coordinate sprite blocks tween across scenes per frame (pixel-exact endpoints) — and scenes speak .pti v4, including the new text op with its built-in 5×7 font.
  • audio: references a .pta soundtrack — the codec stays pure, hosts resolve it, and films export as a .y4m + .wav pair.
  • The editor v1: sprite cards, move entries with coordinate fields, a soundtrack editor, and a synced audio player. The demo film has a title, a flying ball, and a riff.
v0.5.392026-07-21

.pta v2 + sound studio v1

  • Named pattern blocks with a song: chain (sample-exact expansion), swing in the header, per-voice ADSR envelopes, and ! accents — v1 documents sound identical.
  • The studio v1: tracks with envelope presets (organ/pluck/pad), pattern chips, an editable song chain, accent cycling, and 16 or 32 steps.
  • The .pta v2 document updates live under the grid — the sequencer still edits the format, now with real musical depth.
v0.5.382026-07-21

Image editor v1 — layers, brush, fill

  • Layers are op groups: painter's algorithm makes the flattened session a valid .pti v3 document — verified byte-identical to the layered render.
  • A real drag brush (polyline ops with live preview), flood fill (.pti v3's fill op), zoom, op-level undo — and .ppm import as the base picture.
  • The new engine images.draw_ops applies format op lines onto any image — the primitive that makes layers and imports composable.
v0.5.372026-07-21

ptc fmt, ptc doc & ergonomics

  • ptc fmt: the canonical formatter — comment-preserving, idempotent, and it refuses to write if a single token would change. The whole corpus is canonical now.
  • ptc doc renders a module's pub API to a native .ptw v2 page — documentation output is dogfood, rendered by POLYTONE's own web codec.
  • The deferred ergonomics landed: for iterates Maps (keys), Text, and Bytes; text[i] indexing; and block-bodied lambdas as the right side of a binding.
v0.4.362026-07-21

ptpkg v0 — Phase M4 complete

  • Packages: a directory with a polytone.pkg manifest — same header discipline as every native format, with pkg.render in the stdlib as the teaching decoder.
  • ptc vendor copies a package's modules into vendor/; imports resolve local → vendor/ → stdlib. The new Registry page curates the first packages.
  • Phase M4 done, Phase M5 planned: the depth pass — ptc fmt/doc, editor layers and brushes, .pta/.ptv v2, forms, a multi-file playground.
v0.4.352026-07-20

.pti v2 — palettes & gradients

  • palette: names colors for every drawing op, gradient fills run vertical, horizontal, or radial with exact endpoints, and polygon fills any shape from three points up.
  • The image editor grew a gradient tool, a triangle tool, and a palette swatch row — sessions still export as byte-exact .pt programs.
  • v1 files render byte-identically; the new surface in v1 documents teaches the version bump. The viewer's demo scene shows a gradient sky with polygon mountains.
v0.4.342026-07-20

.ptw v2 — inline media

  • image, film, and sound blocks reference native documents by address — and the web viewer's demo site now shows its picture, film, and tune inline.
  • The bridge embeds what it is given: to_html_bytes(doc, assets) turns supplied images into BMP data URIs — encoder and base64 written in POLYTONE, images' own bridge stays PPM.
  • Film and sound links hydrate into inline players in the viewer; v1 pages keep rendering unchanged, and media blocks in v1 teach the version bump.
v0.4.332026-07-20

Generic records & enums

  • record Pair[A, B] and enum Tree[T] — user types reach the level of the builtin Option/Result, with total construction inference and left-to-right sharpening.
  • Unit variants infer from the expected type exactly like None: let t: Tree[Int] = Tree.Leaf. Qualified across modules, recursive types included.
  • Runtime identity stays the base name — values print as Pair(first: 1, second: "one"); the VM never sees a type parameter. Plus lists.zip.
v0.4.322026-07-19

Generic functions

  • fn index_of[T](xs: List[T], x: T) -> Int — type parameters with total call-site inference. No turbofish: one way to call any function; conflicts teach.
  • Bodies check once with opaque parameters; monomorphization generates readable instances (index_of[Int]) transitively, across modules — the VM never sees a type parameter.
  • The stdlib's first generics: lists.index_of, reversed, take, drop — and video.pt dropped its hand-rolled Text-only search.
v0.4.312026-07-16

Record update & moves — Phase M4 opens

  • with: t with plays: t.plays + 1 — one canonical form for 'the same record, some fields changed', with full teaching errors and clean swap semantics.
  • Move analysis in lowering plus a fused in-place field write: the img = set_pixel(img, …) rebuild chain is zero-copy now — 5.05 s → 0.068 s (~75×) on the sprint-22 benchmark.
  • Snapshots stay safe: anything still shared copies exactly once, for the snapshot. 422 tests green.
v0.3.302026-07-16

The web viewer — Phase M3 complete

  • The pragmatic own browser: an address bar and history over a site of native documents — .ptw pages interlink, media render through their codecs, external links open in a new tab.
  • Apps: a .pt address runs the program — arguments ride in the address, the app writes a native document, the shell renders it. The counter demo proves the cycle.
  • Phase M3 complete: runtime, playground, canvas output, and four tools — every one runs real POLYTONE in the sandbox.
v0.3.292026-07-16

Video editor v0

  • The document IS the native .ptv: scene cards in the .pti drawing grammar with live sandbox thumbnails, and a play/fade timeline you can reorder.
  • Render produces Y4M in ~420 ms and the canvas player plays it in-page — frame math and crossfade interpolation verified against the decoder.
  • Three exports: .ptv, .y4m, and a .pt program that reproduces the film byte-exactly. Three of four planned tools are live.
v0.3.282026-07-16

Sound studio v0

  • A step sequencer whose document IS the native .pta format — the text updates live as you toggle cells, and chords become extra voices automatically.
  • Play renders through the audio toolkit in the sandbox (~550 ms for the demo riff) and the WAV plays in-page.
  • Three exports: .pta, .wav, and a .pt program that reproduces the song byte-exactly.
v0.3.272026-07-16

Image editor v0

  • A browser editor where every click runs real POLYTONE: draw tools, filters, undo — the UI generates programs against the images toolkit, the sandbox renders (~35 ms per operation).
  • The session is code: export a .pt program that reproduces your picture from scratch — verified byte-exact.
  • images.from_ppm_bytes — the export bridge's inverse, with teaching errors: existing pictures can enter the toolkit.
v0.3.262026-07-15

Canvas output

  • Films play on the site: a Y4M decoder — the exact inverse of the video bridge — drives a canvas player with play/pause; verified frame-exact against runtime output.
  • Live mode: the program re-runs as you type and still images repaint their canvas in place — the image toolkit becomes interactive.
  • No browser drawing API in the language: written files are the output system — one bridge per domain; the host decides what a file means.
v0.3.252026-07-15

The playground

  • This site now runs POLYTONE live: edit code, run it or its tests, and written media appear as previews — .ppm on a canvas, .wav in a player, .html in a sandboxed frame.
  • Eight curated examples, args() input, and share links that carry the source in the URL.
  • Fuel: an instruction budget turns infinite loops into teaching errors instead of frozen tabs.
v0.3.242026-07-15

The browser runtime

  • POLYTONE runs in the browser: the reference VM plus the whole compiler front end compiled to WebAssembly (~740 kB) — one canonical semantics instead of a second implementation.
  • Sandboxed by construction: file I/O hits an in-memory virtual filesystem — seed inputs, read written files back; a rendered .ppm goes straight to a canvas.
  • Records, enums, closures, match guards, the embedded stdlib, args()/env(), and ptc-test-format test mode — verified by a 9-check Node smoke test.
v0.2.232026-07-15

Media CLI

  • args() -> List[Text]: everything after the file on the ptc run command line — no argv[0] to skip, no separate count; main keeps its single canonical shape.
  • env(name) -> Option[Text]: an unset variable is None — never an error, never a silent empty string.
  • examples/ptrender.pt: one universal renderer that dispatches on the input extension and exports all five native formats through their bridges — the first real POLYTONE command-line tool.
v0.2.222026-07-15

Language pass: CoW, guards, re-exports

  • Match guards: case Some(n) if n > 0: — a Bool condition with the pattern's bindings in scope; guarded arms never count toward exhaustiveness.
  • Type re-exports: pub signatures may use imported pub types qualified (pub fn origin() -> geo.Point); importers get the whole reachable type surface installed transitively.
  • Copy-on-write values in the VM: heap values share until mutated. Same value semantics, paid lazily — passing, snapshotting, and storing big values is O(1), and the accidental deep-copy class is gone for good.
v0.2.212026-07-15

.ptv — native video

  • .ptv (POLYTONE Video) v1: named scenes in the .pti drawing grammar plus a timeline of play and fade entries — moving pictures as intent, never as frames.
  • The single Y4M bridge writes an uncompressed stream that ffplay, mpv, and VLC open directly.
  • The format family is complete at five: image, audio, 3D, web, video — every codec written and self-tested in POLYTONE.
v0.2.202026-07-15

Audio toolkit

  • The Sound-Studio core: silence, tone, append, mix, gain, repeat — and the classic ADSR envelope, all composable in code.
  • examples/compose.pt: a plucked melody over a sine bass, built programmatically and exported as WAV in under three seconds.
v0.2.192026-07-15

Image toolkit

  • The Bildverarbeitung core: crop, flips, nearest-neighbor scale, clipped blit — and map_pixels with lambdas, driving invert, grayscale, and brighten.
  • Two more VM fast paths (len() and field reads on locals) plus a copy-free .pti hot path: the full pipeline went from 105 seconds to 0.6.
  • examples/process_image.pt: decode a native scene, filter, flip, scale, export — end to end.
v0.1.182026-07-15

.ptw — native web · Phase M1 complete

  • .ptw (POLYTONE Web) v1: documents as a typed block tree — Block is a pub enum, the HTML bridge renders it with an exhaustive match and escapes everything, always.
  • No markup strings, no injection by construction — an author cannot produce broken HTML.
  • The format family is complete: .pti image, .pta audio, .ptm 3D, .ptw web — every codec written and self-tested in POLYTONE.
v0.1.172026-07-15

.ptm — native 3D

  • .ptm (POLYTONE Model) v1: placed primitives — box, plane, UV sphere — plus raw triangles, as canonical text with the language's own float rule.
  • The tessellator is stdlib POLYTONE; the single OBJ bridge exports for today's viewers.
  • examples/first_model.ptm: six intent lines become 479 vertices and 859 triangles.
v0.1.162026-07-15

.pta — native audio

  • .pta (POLYTONE Audio) v1: tempo, synth voices (sine/square/saw/triangle), tracker-style note patterns with holds and rests — musical intent as canonical text.
  • The synthesizer is stdlib code written in POLYTONE: equal-temperament tuning, click-free envelopes, mixing — exported through the single WAV bridge.
  • Float gains sin/cos, and the VM gains in-place fast paths for list building — indexed reads/writes and push on locals no longer copy.
v0.1.152026-07-15

.pti — the first native format

  • POLYTONE adopts no existing media formats — every format is designed new, for LLMs: text never binary, intent over samples, teaching decoders (formats/00-OVERVIEW.md).
  • .pti (POLYTONE Image) v1: a canvas plus pixel/line/rect/circle operations in one canonical text form — decoded and rendered by stdlib code written in POLYTONE.
  • examples/first_scene.pti renders end-to-end through the single PPM export bridge; decoder errors name line numbers and canonical forms.
v0.1.142026-07-15

Bytes & file I/O

  • The Bytes type: text.to_bytes(), list.to_bytes(), get/slice/to_hex — binary data with value semantics and hex display.
  • read_file and write_file return Results — I/O can fail, so the failure is always in the type.
  • POLYTONE wrote its first real image: examples/first_image.pt renders a binary PPM gradient — the opening move of the multimedia roadmap.
v0.1.132026-07-15

Standard library

  • The stdlib is written in POLYTONE itself: ints, lists, and texts under stdlib/ — every module self-tested with its own test blocks.
  • import falls back to the stdlib search path ($POLYTONE_STDLIB or the checkout's stdlib/); a local file always shadows the stdlib.
  • lists.range(1, 6).map(fn(x: Int) -> Int = x * x) — the language now composes with itself.
v0.1.122026-07-15

Type exports

  • pub record and pub enum cross module boundaries — qualified everywhere: shapes.Point, shapes.Color.Red, case shapes.Color.Rgb(…):
  • Nominal identity program-wide: a Point built inside the module equals a shapes.Point built by the importer.
  • Exhaustiveness works across modules and names missing cases in qualified form.
v0.1.112026-07-15

Modules & imports

  • Multi-file programs: import mathx loads the neighbor file; pub fn is the export; access is always qualified.
  • Cycles, self-imports, missing files, private access — all teaching errors; a typo lists the module's functions.
  • This documentation site shipped — every output on it recorded from the real compiler.
v0.1.102026-07-15

Test blocks & assert

  • Tests are a language construct: test "name": blocks plus the assert keyword, run via ptc test.
  • Failing comparisons report both operand values: left = [1, 4], right = [1, 5].
v0.1.92026-07-15

Function values

  • Lambdas as unnamed declarations (fn(x: Int) -> Int = x * 2), fn types, named functions as values.
  • map, filter, and fold on lists; captures are by value and read-only.
v0.1.82026-07-14

Enums

  • Sum types with unit and payload variants, always qualified (Status.Banned(reason: …)); exhaustiveness over every variant.
v0.1.72026-07-14

Standard prelude

  • Built-in methods on core types; everything fallible returns an Option — no null, no exceptions.
v0.1.62026-07-14

Records & match

  • Nominal records with one canonical constructor; match/case with enforced exhaustiveness.
v0.1.52026-07-14

WASM scalar backend

  • ptc build compiles the scalar subset to WebAssembly, differentially tested against the VM byte for byte.
v0.1.42026-07-14

VM execution

  • The PTIR stack machine: value semantics, checked arithmetic, source positions on runtime errors.
v0.1.32026-07-14

Type system

  • Local inference, explicit function boundaries, no truthiness, no implicit conversions; Option/Result and the ? operator.
v0.1.22026-07-14

Parser

  • The full grammar with teaching parse errors; pub requires a /// doc comment.
v0.1.12026-07-14

Lexer & spec

  • Spec v0.1 and the lexer: 4-space blocks, string interpolation, word operators (and/or/not).