Skip to content

release: promote v0.4.0 to main - #264

Merged
sobechestnut-dev merged 18 commits into
mainfrom
dev
Jul 31, 2026
Merged

release: promote v0.4.0 to main#264
sobechestnut-dev merged 18 commits into
mainfrom
dev

Conversation

@sobechestnut-dev

Copy link
Copy Markdown

v0.4.0 — non-extrusion toolpath (CNC / laser / plotter), #189

Promotes devmain for the v0.4.0 publish. All 13 @chestnutlabs/* packages bump lockstep to 0.4.0.

Highlights

  • MoveKind.Cut + opt-in ModalChannel mechanism (toolPower) — tool-engaged productive moves are classified; laser power / spindle RPM live on an opt-in per-segment channel (FDM pays nothing, stays byte-identical). DD-012 phases 1–2.
  • Canned drilling cycles G81/G82/G83 (+G80/G98/G99) expand to real geometry.
  • Dialect families + validation tiers — GRBL-laser / GRBL-mill / LinuxCNC, evidence-based detection of header-less real files; honesty-tiered (experimental → claims inferred).
  • grbl-laser hardware-VALIDATED — first real-machine pass; its laser claims now report known.
  • Power / cut-vs-rapid color modes; STL export of the rendered toolpath (STL export from the demo/showcase #152).
  • Lexer handles multi-command lines, N line numbers, bare S/F — real CNC files now parse.
  • G0 rapids classify as Travel, not Cut (fix surfaced by the new validation harness).
  • Docs: RR-004 + DD-012 (+ spec references §9/§16), DD-017 (RS274NGC, Proposed), compatibility matrix, support policy.

FDM output is byte-identical throughout (golden-equivalence gate green).

⚠️ Merge as a MERGE COMMIT, not squash — preserves the release lineage release-publish.yml verifies (tag ∈ main).

sobechestnut-dev and others added 18 commits July 28, 2026 21:49
…lotter) (#246)

Records the #189 scope decision (RESOLVED — IN SCOPE, maintainer 2026-07-28),
de-risked by real-hardware validation (maintainer laser + partner CNC tables).
Audits current mis-classification (cut/burn/draw → Travel; tool state dropped;
geometry sound), and recommends DD-012:

- additive MoveKind.Cut = 1<<7 (composes like Wipe/Seam/ArcSegment; FDM output
  byte-identical since it only sets when E is absent + a tool-state holds)
- an opt-in ModalChannel mechanism (laser power / spindle / pen) that ALSO
  serves #180/RR-002's fan/temp/accel color channels — one mechanism, owned
  by DD-012 (build it once, don't drift the IR)
- canned-cycle (G81-G89) geometry expansion, per-dialect
- validation-TIERED dialect honesty: known only when machine-verified,
  inferred+experimental for synthetic/spec-only, unavailable when untested
- synthetic redistributable fixtures (no private design files committed)

Reconciles RR-002 / #180 as consumers of the DD-012 modal mechanism.

Co-authored-by: Nathaniel Chestnut <sobechestnut-dev@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
…epted] (#247)

* docs(design): DD-012 — non-extrusion toolpath & modal tool-state [Proposed]

The design for #189 (per RR-004's in-scope decision). Additive, FDM-safe:

- MoveKind.Cut = 1<<7 — tool-engaged productive move when E is absent
  (composes like Wipe/Seam; FDM IR byte-identical)
- one opt-in ModalChannel mechanism, OWNED here, serving CNC/laser tool-state
  (laser power / spindle / pen) AND #180/RR-002's fan/temp/accel color
  channels — DD-015 collapses into this
- canned-cycle (G81-89 + G98/G99) geometry expansion, per-dialect
- validation-TIERED dialect honesty (known only when machine-verified)
- no new package (extends core/parser/dialects/colors); synthetic fixtures

Decision menu D1-D8 open for maintainer acceptance. Updates the DD index and
retires the DD-015 reservation.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* docs(design): DD-012 Accepted — D1–D8 as recommended

Maintainer accepted D1–D8 as recommended (2026-07-28): no new package;
MoveKind.Cut bit; one shared opt-in ModalChannel mechanism owned by DD-012
(#180/RR-002 consume it → DD-015 retired); tool-state-driven with machine-class
an inferred hint; canned-cycle MVP (G81/G82/G83 + G80 + G98/G99); validation-
tiered dialects; reuse renderer + ramped colorers; synthetic fixtures + FDM
byte-identical gate + real-machine acceptance. Implementation unblocked.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Nathaniel Chestnut <sobechestnut-dev@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
…se 1, #189) (#248)

Non-extrusion toolpaths (CNC/laser/plotter) no longer collapse productive moves
into Travel. The parser tracks a tool-engaged modal state (M3/M4 on — incl. the
M03/M04 leading-zero form — M5 off) and classifies a no-E move while engaged as
the new MoveKind.Cut bit (composes with ArcSegment like the other kinds).

- toolpath-core: MoveKind.Cut = 1 << 7
- gcode-parser: tool-state modal (M3/M4/M5), tri-state path classification
  (extrusion | cut | travel), and the cutMoves capability (known once a
  tool-state modal is seen, else unavailable)
- FDM byte-identical: FDM never issues M3/M4, so Cut is never set and kinds are
  unchanged — proven by the native-golden corpus (demo-3dbenchy et al. show only
  the additive cutMoves key; kind hashes unchanged). CNC fixtures demo-easel and
  demo-mach3 are documented intentional adapter-divergences (Travel → Cut).
- tests: packages/gcode-parser/src/__tests__/non-extrusion.test.ts

DD-012 phase 1 of 6. Modal value channels (laser power/spindle S), canned
cycles, and dialect families follow.

Co-authored-by: Nathaniel Chestnut <sobechestnut-dev@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
…2 phase 1, #189) (#250)

Adds the shared opt-in ModalChannel mechanism (DD-012 D3) and its first channel,
toolPower (modal spindle/laser S while a tool is engaged).

- toolpath-core: ToolpathSegments.modal?: Record<string, Float32Array> — opt-in
  Float32 columns, present only when requested; unset = NaN, never fabricated 0.
- gcode-parser: budget-aware SoA writer grows opt-in modal columns in lockstep
  (bytes accounted per DD-003); ParseOptions.modalChannels requests them; the S
  register (M3/M4 + inline GRBL-laser S) feeds toolPower (NaN when off/M5);
  toolPower capability known-when-tool-state-seen, else unavailable; unknown
  ids warn (modal-channel-unsupported) and are ignored.
- Default parse pays nothing (no modalChannels ⇒ no modal, FDM unchanged).
- tests: 5 new (stamping incl. inline S, NaN-when-off, opt-out, FDM-unavailable,
  unknown-channel warning).

DD-012 phase 1: move model (Cut) + this mechanism done. Presentation labels
(Watts/RPM), canned cycles, dialects, and #180's channels follow.

Co-authored-by: Nathaniel Chestnut <sobechestnut-dev@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
…(DD-012 phase 2, #189) (#251)

CNC/LinuxCNC output often omits the G word on repeated moves (G1 X0 Y0 then
bare X10 Y0). The parser dropped those lines entirely (3 moves → 1 segment).
It now tracks the active G0-G3 mode and rewrites a coordinate-only line (leading
X/Y/Z, no G/M/T) into a synthetic copy of that command, so the dispatch switch
handles it unchanged — full path emitted, classified (Cut/Travel) and colored
(inline S → toolPower) consistently.

FDM byte-identical: slicers always emit the G word, so the continuation never
triggers; the native-golden corpus is unchanged (no regen). Prerequisite for
canned-cycle modal repeat.

- tests: 5 new (G1/G0 repeat, Cut under engagement, inline-S toolPower, inert
  before any motion mode).

Co-authored-by: Nathaniel Chestnut <sobechestnut-dev@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
…189) (#252)

CNC canned cycles previously produced zero geometry (holes vanished). They now
expand to explicit sub-moves:

- G81/G82: rapid to XY, rapid to R plane, feed to depth (Cut), rapid retract.
- G83 peck: feed down by Q, rapid-retract to R between pecks, until depth; each
  down-feed is a Cut.
- G98/G99 retract plane (initial Z / R); G80 cancels; G0-G3 also cancel.
- Modal repeat: bare X/Y drills another hole retaining Z/R/Q + initial plane.
- Rapids=Travel, plunges=Cut; new capability cannedCycles.

FDM byte-identical (no canned cycles in FDM); native goldens gain only the
additive cannedCycles capability, no geometry change.

- tests: 7 new (G81 plunge-to-depth, modal repeat, G98/G99 retract, G83 peck,
  G80 cancel, FDM regression).

Co-authored-by: Nathaniel Chestnut <sobechestnut-dev@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
… 3, #189) (#253)

Controller detection + the validation-tier honesty mechanism:

- gcode-dialects: new grblLaser / grblMill / linuxCnc adapters (LightBurn/$32/M4
  for laser; Grbl banner / % envelope + M3 for mills). Registered in the
  batteries worker.
- Each sets provenance (cnc.controller/machineClass/toolPowerLabel) + a
  validation tier. Per DD-012 D6, an EXPERIMENTAL dialect reports its
  non-extrusion claims (cutMoves/toolPower/cannedCycles) as `inferred` (never
  `known`) with a cnc-dialect-experimental disclosure — only for claims the file
  actually made (never fabricates an unused feature).
- All launch dialects ship experimental (synthetic fixtures); a one-line
  tier:'validated' flip promotes a controller's claims to `known` after a real
  hardware run (DD-012 §8/§15).

Geometry untouched (annotate/label only); FDM detection unaffected.

- tests: 5 new CNC dialect tests (detection, tier downgrade, no-fabrication,
  no FDM false-positive).

Co-authored-by: Nathaniel Chestnut <sobechestnut-dev@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
#189) (#254)

Two ColorModes consuming the #189 channels (DD-012 D7):

- power: ramps modal.toolPower (laser power / spindle RPM) like color-by-speed;
  auto-ranged (toolPowerRange) or explicit; NaN / no-channel → fallback (never
  fabricated). Capability-gated on toolPower in the Three renderer's
  isColorModeAvailable.
- moveKind: cut-vs-rapid — productive (Extrude|Cut) vs rapid (Travel); reads the
  always-present kind channel → always available.

Both flow through createSegmentColorer, so Three + Canvas-2D get them free. FDM
coloring unchanged.

- tests: 5 new (power ramp + NaN fallback, no-channel fallback, toolPowerRange,
  moveKind classes, segmentColor one-off).

Co-authored-by: Nathaniel Chestnut <sobechestnut-dev@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
…(DD-012 phase 5, #189) (#255)

- compatibility matrix: new "Dialects (non-extrusion — CNC/laser/plotter)"
  section (GRBL laser/mill, LinuxCNC — all EXPERIMENTAL tier, claims inferred
  until hardware-validated; reserved rows for Marlin-laser/Mach/plotters) +
  cross-cutting rows (Cut classification, toolPower channel, canned cycles,
  modal motion).
- support-policy: "Non-extrusion (CNC/laser/plotter) — experimental" subsection
  (geometry full; semantics inferred until validated; DSP/galvo out of scope).
- manual: "Non-extrusion — CNC/laser/plotter (experimental)" concept section.
- README: capability bullet + non-extrusion coloring note.

Docs-only. Phase 6 (exit) = real-hardware validation → flip tiers to validated.

Co-authored-by: Nathaniel Chestnut <sobechestnut-dev@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
…) (#256)

The inherited first-word lexer was FDM-shaped and dropped most real CNC/laser
G-code. The lexer now reads every G/M/T word on a line (G20 G17 G90, G91 G81,
S3400 M3 — so M3 spindle + G81 canned cycles are no longer lost), strips N-word
line numbers (Fanuc/Mach/TinyG), and latches bare S/F lines. A numeric guard
keeps letters embedded in extended commands (POLYGON, M486 T<count>) from
becoming spurious commands/tool-changes.

FDM byte-identical (slicers emit one command per line → multi-command path never
runs; goldens change only in reduced garbage warnings). demo-mach3 + one
adversarial binary fixture are documented intentional divergences. Dialect
extended-command observation (EXCLUDE_OBJECT_* via rawLine) preserved by firing
onCommand for codeless-but-non-blank lines.

Validated on real public samples: LinuxCNC arcspiral 16→5506 segments, TinyG
0→344. Tests: lexer-multicommand.test.ts (8).

Co-authored-by: Nathaniel Chestnut <sobechestnut-dev@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
A showcase-app action (kept in tools/demo, not the reusable packages, per the
#152/#118 scoping). An "Export STL" button downloads the productive toolpath
(extrude/cut moves) as a binary STL — each move a square-section tube, decimated
to a triangle budget with an honest "strided N of M" note (no silent truncation).
Self-contained: reads only the public ToolpathIR via renderer.ir.

Co-authored-by: Nathaniel Chestnut <sobechestnut-dev@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
…real files (#189) (#258)

The phase-3 detectors matched none of the real public samples (LaserGRBL,
LinuxCNC, GRBL CAM) — real output has no header and writes commands mid-line /
concatenated. Detection is now evidence-scored: a scoreEvidence extractor
(comments stripped, commands matched anywhere via trailing-digit lookahead) plus
process rules — laser = tool-on + S + no Z-plunge; mill = M3 + negative-Z plunge;
LinuxCNC = header or RS274NGC O-words. Extrusion tightened to E-on-a-move so
LinuxCNC M67 E0 analog laser power isn't mistaken for FDM.

Real files: 0/6 → 3/6 detected with correct machine class; misses are honest
(commented/absent spindle = no tool-state). Still experimental tier.

Fixtures are MIT-clean re-creations of the real fingerprints (no third-party
files committed — LinuxCNC samples are GPL). Tests: +5 in cnc.test.ts.

Co-authored-by: Nathaniel Chestnut <sobechestnut-dev@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
…rk (#259)

Refresh the status table: v0.3.0 shipped / v0.4.0 in prep; #188 bgcode
Closed/Accepted (signed off, shipped in v0.3.0); add the #189 CNC/laser row
(DD-012 phases 1–5 built+merged+documented at experimental tier, plus the lexer
fix, evidence-based detection, and #152 STL export). No package change.

Co-authored-by: Nathaniel Chestnut <sobechestnut-dev@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* fix: G0 rapids classify as Travel, not Cut (#189)

The non-extrusion Cut/Travel classifier keyed only on tool-state (M3/M4
latched), so on a router — spindle on across rapids — every G0 reposition
was counted as a cutting move. DD-012 D2 §4.2 already specifies rapids stay
Travel; this aligns the implementation: only a feed move (G1/G2/G3) with the
tool engaged and no E delta is Cut; a G0 rapid is Travel regardless of tool
state (a GRBL-laser also gates the beam off during G0).

Also adds the CNC/laser validation harness (tools/demo/validate.html) that
surfaced this — load a physically-cut file, check each claim (machine class,
cut-vs-rapid split, tool power, envelope, skipped commands) against the real
machine, export a markdown report. Local-only; no upload, no worker.

Geometry unchanged (only the demo-easel kind column shifts: 742/0 → 737/5);
FDM byte-identical. +1 regression test.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(demo): power-view legend + honest constant-vs-range power claim (#189)

The validation harness's Power view drew colored lines with no key, and the
power claim read "200–200" for a single-power job. Fixes from real-file use:

- Power range now measured over CUTTING moves only (a rapid can carry a
  latched S with the beam off — counting it over-reported the engaged tally).
- View legend is mode-aware: cut/rapid swatches, or a cold→hot power scale
  with the real min/max — or a "constant N (no variation)" note when the job
  never modulates power (with the swatch matching the flat tone drawn).
- Power claim phrases constant power as constant, not a degenerate range.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Nathaniel Chestnut <sobechestnut-dev@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Folds the reference landscape into the research (RR-004 §9) and design
(DD-012 §16) docs, making the spec-anchored-CNC vs. observed-only-laser
split explicit — the distinction that governs the validation tier:

- CNC milling is spec-anchored: RS274NGC (NIST IR 6556) + ISO 6983 +
  the LinuxCNC reference define motion, canned cycles, coordinate systems,
  #params, [expressions], O-words. Phase-7 gaps are documented features to
  build against the spec, with hardware as the check.
- Lasers are not spec-anchored: GRBL (source/wiki) + LightBurn (observed
  output) only — the machine is the source of truth, so laser dialects stay
  hardware-gated. Vendor extensions (e.g. mach3 plasma S=torch-height) escape
  every spec and are inferred by construction.

Provenance: cited as behavioral parity targets / where-defined pointers only.
ISO 6983 is copyrighted and the LinuxCNC reference is GPL/GFDL while this
project is MIT — no spec prose is copied. DD-012 decision log notes the
editorial addition (no decision change).

Co-authored-by: Nathaniel Chestnut <sobechestnut-dev@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
…189) (#262)

First DD-012 D8 hardware-validation pass. A real GRBL/LightBurn diode-laser
run (6161 moves incl. fill + offset-fill, full 0-1000 S power ramp) confirmed
machine-class detection, the Cut-vs-rapid split, and the toolPower channel
against the physical cut — all claims verified via the tools/demo/validate.html
harness.

- Flip grbl-laser tier experimental -> validated (cnc.ts): laser files now
  report cutMoves/toolPower as `known`, and the cnc-dialect-experimental
  warning is suppressed.
- grbl-mill and linuxcnc stay experimental (per-controller flip) until a real
  CNC run.
- Record the run as acceptance evidence in the new
  docs/design/DD-012-hardware-validation-log.md.
- Tests updated: grbl-laser asserts validated/known/no-warning; grbl-mill
  asserts it still downgrades to inferred (scope guard).

Co-authored-by: Nathaniel Chestnut <sobechestnut-dev@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
…7) (#263)

Drafts the design for the largest remaining #189 gap: LinuxCNC/CAM parametric
programs — numbered/named parameters (#100 = 5), expressions (X[#1 + sin[30]]),
and O-word subroutines/loops/conditionals — which the forward-only line
processor drops today.

Key decisions for acceptance (D1–D8):
- D1: on-demand program-buffered interpreter engaged ONLY when #/[ /O-words
  appear; FDM + simple CNC keep the untouched forward-only fast path
  (byte-identical). One motion/classification core; the interpreter only feeds it.
- D5: two new DD-003 limits (maxProgramIterations, maxCallDepth) so a while[1]
  or infinite recursion can't hang/OOM the worker — bounded, no eval, no I/O.
- Spec-derivable from RS274NGC (NIST IR 6556) / LinuxCNC — hardware only
  re-confirms; parity targets only, no spec text copied (RR-004 §6).

Proposed only — architecture-sensitive, so blocked until Accepted per governance.

Co-authored-by: Nathaniel Chestnut <sobechestnut-dev@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* release: version packages (lockstep)

* ci: trigger required checks on the v0.4.0 version PR (#249)

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Nathaniel Chestnut <sobechestnut-dev@users.noreply.github.com>
@sobechestnut-dev
sobechestnut-dev merged commit 606d5a7 into main Jul 31, 2026
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant