Files
cnc_wams/wasm-port/docs/porting-steps-standalone.md
2026-06-09 06:13:28 +08:00

38 KiB
Raw Blame History

LinuxCNC WASM Porting Steps

Goal

Build a separate WASM-based CNC simulation program that:

  • uses LinuxCNC source as the semantic source of truth,
  • does not modify the original linuxcnc/ source tree,
  • is managed independently from native LinuxCNC,
  • provides HTML + JavaScript frontend plus OPFS persistence,
  • matches LinuxCNC software behavior as closely as practical except for realtime hardware driving.

Non-Negotiable Constraints

  1. linuxcnc/ is upstream and read-only for the port effort.
  2. Any source adaptation needed for WASM happens on copied or generated files under wasm-port/.
  3. No direct edits inside linuxcnc/src, linuxcnc/lib, linuxcnc/tests, or linuxcnc/web are part of the port workflow.
  4. LinuxCNC GUI code is reference-only. The migrated UI is rebuilt in HTML + JavaScript.
  5. LinuxCNC compute logic should be reused before any reimplementation is considered.

Workspace Structure

Create and keep these directories under wasm-port/:

  • docs/ Planning, architecture, and validation documents.
  • vendor/linuxcnc/ Copied source files selected for the port.
  • patches/ Patch files against the vendored copies.
  • tools/ Scripts that extract files from ../linuxcnc, apply patches, and verify drift.
  • runtime/core/ WASM-targeted C/C++ code and wrappers.
  • runtime/sdk/ JavaScript/TypeScript wrapper over the WASM module.
  • runtime/ui/ HTML + JavaScript frontend.
  • runtime/opfs/ Browser file adapter.
  • tests/native/ Native extracted-core regression tests.
  • tests/browser/ Browser/WASM regression and smoke tests.

Ported Program Directory Structure

The migrated program itself should be managed as a standalone product under wasm-port/, with LinuxCNC acting as an upstream source provider.

The recommended final structure is:

wasm-port/
├── docs/
├── vendor/
│   └── linuxcnc/
│       └── src/
│           ├── emc/
│           │   ├── ini/
│           │   ├── kinematics/
│           │   ├── rs274ngc/
│           │   └── tp/
│           ├── hal/
│           │   └── components/
│           └── libnml/
│               └── posemath/
├── patches/
├── tools/
├── runtime/
│   ├── core/
│   │   ├── include/
│   │   ├── shims/
│   │   ├── adapters/
│   │   ├── canon/
│   │   ├── session/
│   │   ├── simulation/
│   │   ├── linuxcnc_wrap/
│   │   └── c_api/
│   ├── sdk/
│   │   └── src/
│   ├── ui/
│   │   ├── public/
│   │   └── src/
│   │       ├── panels/
│   │       ├── preview/
│   │       ├── state/
│   │       ├── machine/
│   │       └── files/
│   └── opfs/
├── tests/
│   ├── native/
│   ├── wasm/
│   └── fixtures/
├── build/
│   ├── native/
│   └── wasm/
└── dist/
    ├── sdk/
    └── web/

Directory Responsibilities

  • vendor/linuxcnc/ Holds extracted upstream LinuxCNC source files. These are copied or generated from ../linuxcnc and are the only place where source-level port patches are applied.
  • patches/ Stores every patch applied to vendored LinuxCNC files. Patches are tracked here instead of being mixed into the original source tree.
  • tools/ Holds extraction, sync, verification, manifest generation, and compatibility-check scripts.
  • runtime/core/ The standalone simulation runtime. This is where vendored LinuxCNC source is wrapped, adapted, and exposed to the rest of the ported program.
  • runtime/core/include/ Public internal headers for the standalone runtime.
  • runtime/core/shims/ Small compatibility headers and implementation stubs required to compile vendored LinuxCNC code outside the native runtime.
  • runtime/core/adapters/ Host boundary adapters such as file IO, logging, simulation HAL, and runtime-state providers.
  • runtime/core/canon/ Canonical motion event collection and serialization.
  • runtime/core/session/ Per-simulation session ownership for interpreter state, planner state, machine state, and controller state.
  • runtime/core/simulation/ Higher-level simulation orchestration built on top of reused LinuxCNC compute modules.
  • runtime/core/linuxcnc_wrap/ Thin wrappers around vendored LinuxCNC entry points. Prefer wrappers here over editing vendored source directly.
  • runtime/core/c_api/ Stable C ABI exported to the WASM layer.
  • runtime/sdk/ JavaScript/TypeScript SDK that calls the WASM module and hides memory management and ABI details from the frontend.
  • runtime/ui/ The actual CNC simulation web application built with HTML + JavaScript.
  • runtime/ui/src/panels/ Operator panels, machine configuration panels, parameter editors, and controller-status panels.
  • runtime/ui/src/preview/ 2D/3D path preview, 5-axis visualization, joint/world overlays, and playback views.
  • runtime/ui/src/state/ Browser-side state management for session lifecycle and UI coordination.
  • runtime/ui/src/machine/ Machine-model-specific UI logic.
  • runtime/ui/src/files/ File import/export and project/session handling UI logic.
  • runtime/opfs/ OPFS-backed file services, snapshot persistence, and path mapping.
  • tests/native/ Native extracted-core regression tests run before WASM build validation.
  • tests/wasm/ WASM tests for Node and browser environments.
  • tests/fixtures/ Shared INI, G-code, parameter, tool-table, and machine fixtures.
  • build/ Intermediate build output. This is disposable.
  • dist/ Deliverable artifacts such as generated web bundles and SDK packages.

Source Ownership Rule

The ported program's own source code is expected to live under:

  • runtime/core/
  • runtime/sdk/
  • runtime/ui/
  • runtime/opfs/
  • tests/
  • tools/
  • docs/

Vendored LinuxCNC code must live under:

  • vendor/linuxcnc/

The original upstream tree must remain outside the ported program's source ownership boundary:

  • ../linuxcnc/

Patch Placement Rule

If a LinuxCNC source file needs adaptation:

  1. extract it into vendor/linuxcnc/;
  2. patch the vendored copy only;
  3. record the patch in patches/;
  4. document why the patch exists in docs/ or in the patch header.

Do not place LinuxCNC source patches under runtime/.

Build Graph Rule

The standalone build should flow like this:

  1. tools/ extracts upstream files into vendor/
  2. patches/ are applied to vendored copies
  3. runtime/core/ compiles vendored files plus wrappers
  4. runtime/sdk/ consumes the generated WASM module
  5. runtime/ui/ consumes the SDK
  6. runtime/opfs/ provides browser persistence services
  7. tests/ validate native and browser behavior

This ensures the ported program remains independently buildable while still tracking LinuxCNC as the semantic source of truth.

Phase 0: Freeze Upstream Reference

Purpose: Make the LinuxCNC source baseline explicit before extraction starts.

Steps:

  1. Record the exact upstream commit from linuxcnc/.git.
  2. Record local build assumptions:
    • compiler version
    • emscripten version
    • python version
    • node version
  3. Record the first supported LinuxCNC fixture set:
    • one 3-axis machine
    • one non-trivial kinematics case
    • one 5-axis machine
  4. Record the first G-code feature set:
    • linear motion
    • arc motion
    • canned cycles
    • offsets and coordinate systems
    • subroutines
    • numeric and named variables
    • representative 5-axis programs

Outputs:

  • upstream revision note
  • supported feature baseline
  • supported machine baseline

Phase 1: Build the Source Reuse Map

Purpose: Identify exactly which LinuxCNC source files are needed.

Steps:

  1. Map each required capability to LinuxCNC files:
    • G-code interpreter: ../linuxcnc/src/emc/rs274ngc/*
    • parameter tables and named variables: ../linuxcnc/src/emc/rs274ngc/interp_*
    • INI parsing: ../linuxcnc/src/emc/ini/inifile.*
    • planner: ../linuxcnc/src/emc/tp/*
    • kinematics: ../linuxcnc/src/emc/kinematics/* and selected files from ../linuxcnc/src/hal/components/*.comp
    • posemath: ../linuxcnc/src/libnml/posemath/*
  2. For each file, classify it:
    • copy unchanged
    • copy plus shim
    • copy plus light patch
    • not included in phase 1
  3. For each file, record dependencies:
    • HAL
    • RTAPI
    • NML
    • native file IO
    • Python
    • GUI
  4. Save the mapping as a table under wasm-port/docs/.

Outputs:

  • file-level reuse matrix
  • dependency matrix

Phase 2: Build the Extraction Pipeline

Purpose: Keep LinuxCNC source untouched while making port-specific copies available.

Steps:

  1. Write extraction scripts in wasm-port/tools/.
  2. Copy selected upstream files into wasm-port/vendor/linuxcnc/.
  3. Preserve relative structure where useful, for example:
    • vendor/linuxcnc/src/emc/rs274ngc/...
    • vendor/linuxcnc/src/emc/ini/...
  4. Store every modification as:
    • a patch in wasm-port/patches/, or
    • a thin wrapper outside the vendored file
  5. Add a verification script that compares upstream file hashes against the vendored source list.

Rules:

  • if upstream changes, re-run extraction;
  • reapply patches only in the standalone workspace;
  • never patch ../linuxcnc directly.

Outputs:

  • reproducible extraction pipeline
  • vendored source tree

Phase 3: Build the Native Extracted Core

Purpose: Prove the reusable LinuxCNC compute code works outside the full runtime.

Steps:

  1. Create wasm-port/runtime/core/ as the portable core layer.
  2. Add wrapper translation units instead of editing vendored files where possible.
  3. Start with these subsystems in order:
    • INI parser
    • numeric parameter table
    • named parameter logic
    • interpreter core
    • planner
    • kinematics
  4. Build a native CLI harness first, before any WASM build.
  5. Replace host edges with abstractions:
    • file provider
    • HAL adapter
    • runtime state provider
    • logging adapter

Outputs:

  • portable native core
  • native harness executable

Current verified progress:

  • tools/build_native_probes.sh builds the INI parser probe, interpreter state probe, named-parameter harness, RS274 compile probe, and the minimal interpreter harness from the standalone wasm-port/ workspace.
  • tests/native/verify_native_probes.sh validates that all native probe exit codes are zero and that the minimal interpreter harness emits a STRAIGHT_TRAVERSE canonical event for G0 X1.0 Y2.0, plus SET_FEED_RATE and STRAIGHT_FEED canonical events for G1 X3.0 Y4.0 F120.0.
  • The same validation is now fixture-driven through tests/fixtures/gcode/minimal_linear.ngc and tests/fixtures/canon/minimal_linear.events.
  • tests/fixtures/gcode/modal_incremental.ngc and tests/fixtures/canon/modal_incremental.events add G90/G91 distance-mode switching plus modal G1 carry-forward coverage. The temporary wrapper behavior is based on LinuxCNC interp_convert.cc::convert_distance_mode() and interp_convert.cc::convert_straight().
  • tests/fixtures/gcode/position_params.ngc and tests/fixtures/canon/position_params.events cover Z-axis motion plus current-position parameter visibility for #5420, #5421, and #5422. The parameter source basis is LinuxCNC interp_parameter_def.hh and interp_namedparams.cc.
  • tests/fixtures/gcode/canned_cycles.ngc and tests/fixtures/canon/canned_cycles.events cover LinuxCNC canned-cycle conversion through vendored interp_cycles.cc, including G81, G82, G83, G80 cancellation, dwell, peck drilling, and incremental L repeats.
  • runtime/core/linuxcnc_wrap/linuxcnc_interp_minimal_runtime.cpp now records coordinate-system canonical boundary calls emitted by vendored LinuxCNC conversion code: SET_G5X_OFFSET, SET_G92_OFFSET, SET_XY_ROTATION, CANON_UPDATE_END_POINT, USE_LENGTH_UNITS, and SELECT_PLANE. tests/fixtures/gcode/coordinate_offsets.ngc and tests/fixtures/canon/coordinate_offsets.events pin G55, active G10 L2, G92, G92.1, and G54 behavior from interp_convert.cc::convert_coordinate_system(), interp_convert.cc::convert_setup(), and interp_convert.cc::convert_axis_offsets().
  • tests/fixtures/gcode/length_units.ngc and tests/fixtures/canon/length_units.events pin G20/G21 unit switching and the USE_LENGTH_UNITS canonical boundary emitted by vendored interp_convert.cc::convert_length_units().
  • tests/fixtures/gcode/plane_selection.ngc and tests/fixtures/canon/plane_selection.events pin G17/G18/G19 plane switching and the SELECT_PLANE canonical boundary emitted by vendored interp_convert.cc::convert_set_plane().
  • tests/fixtures/gcode_errors/cutter_comp_plane_change.ngc and tests/fixtures/canon_errors/cutter_comp_plane_change.expected pin the LinuxCNC error path for attempting a plane change after G41.1 enables cutter radius compensation. The source basis is vendored interp_convert.cc::convert_cutter_compensation_on() and interp_convert.cc::convert_set_plane().
  • runtime/core/linuxcnc_wrap/linuxcnc_runtime_state_stubs.cpp now initializes setup::cutter_comp_firstmove to the same initial value as vendored rs274ngc_pre.cc, allowing the first cutter-compensated straight move to follow LinuxCNC interp_convert.cc::convert_straight_comp1() instead of the subsequent-move path. tests/fixtures/gcode/cutter_comp_motion.ngc and tests/fixtures/canon/cutter_comp_motion.events pin positive G41.1/G40 compensated straight-feed output through vendored interp_convert.cc and interp_queue.cc.
  • tests/fixtures/gcode/g53_machine_coordinates.ngc and tests/fixtures/canon/g53_machine_coordinates.events pin G53 machine coordinate straight-traverse behavior through vendored interp_find.cc::find_ends() and interp_convert.cc::convert_straight(). tests/fixtures/gcode_errors/g53_incremental.ngc and tests/fixtures/canon_errors/g53_incremental.expected pin the LinuxCNC rejection path for G53 while incremental distance mode is active.
  • runtime/core/linuxcnc_wrap/linuxcnc_interp_minimal_runtime.cpp now records feed/control canonical boundary calls emitted by vendored LinuxCNC conversion and queue code: SET_TRAVERSE_RATE, SET_FEED_REFERENCE, SET_FEED_MODE, SET_MOTION_CONTROL_MODE, and SET_NAIVECAM_TOLERANCE. tests/fixtures/gcode/feed_control_modes.ngc and tests/fixtures/canon/feed_control_modes.events pin G93, G94, G95, G61, G61.1, and G64 P/Q behavior from interp_convert.cc::convert_feed_mode(), interp_convert.cc::convert_control_mode(), and interp_queue.cc.
  • runtime/core/linuxcnc_wrap/linuxcnc_interp_minimal_runtime.cpp now records probe canonical boundary calls emitted by vendored interp_convert.cc::convert_probe(): TURN_PROBE_ON, STRAIGHT_PROBE, and TURN_PROBE_OFF. tests/fixtures/gcode/probe_semantics.ngc and tests/fixtures/canon/probe_semantics.events pin G38.2, G38.3, G38.4, and G38.5 probe type handling.
  • runtime/core/linuxcnc_wrap/linuxcnc_interp_minimal_runtime.cpp now records speed/feed synchronization and rigid-tap canonical boundary calls emitted by vendored interp_convert.cc::convert_straight(): START_SPEED_FEED_SYNCH, STOP_SPEED_FEED_SYNCH, and RIGID_TAP. tests/fixtures/gcode/threading_sync.ngc and tests/fixtures/canon/threading_sync.events pin G33 spindle-synchronized straight feed and G33.1 rigid tap behavior.
  • runtime/core/linuxcnc_wrap/linuxcnc_interp_minimal_runtime.cpp now records NURBS G5 canonical boundary calls emitted by vendored interp_convert.cc::convert_nurbs(): NURBS_G5_FEED. tests/fixtures/gcode/nurbs_g5_semantics.ngc and tests/fixtures/canon/nurbs_g5_semantics.events pin G5.2 control-point collection and G5.3 NURBS feed emission.
  • runtime/core/linuxcnc_wrap/linuxcnc_interp_minimal_runtime.cpp now records NURBS G6 canonical boundary calls emitted by vendored interp_convert.cc::convert_nurbs(): NURBS_G6_FEED. tests/fixtures/gcode/nurbs_g6_semantics.ngc and tests/fixtures/canon/nurbs_g6_semantics.events pin G6.2 order, interpolation mode, control-point, weight, and K segment handling.
  • runtime/core/linuxcnc_wrap/linuxcnc_interp_minimal_runtime.cpp now records spindle-orient canonical boundary calls emitted by vendored LinuxCNC conversion and queue code: ORIENT_SPINDLE and WAIT_SPINDLE_ORIENT_COMPLETE. tests/fixtures/gcode/spindle_orient.ngc and tests/fixtures/canon/spindle_orient.events pin M19 R/P/Q behavior from interp_convert.cc::convert_m() and interp_queue.cc.
  • runtime/core/linuxcnc_wrap/linuxcnc_interp_minimal_runtime.cpp now records tool-table canonical boundary calls emitted by vendored interp_convert.cc::convert_setup_tool(): SET_TOOL_TABLE_ENTRY. tests/fixtures/gcode/tool_table_setup.ngc and tests/fixtures/canon/tool_table_setup.events pin G10 L1 tool offset, diameter, front/back angle, and orientation behavior.
  • runtime/core/linuxcnc_wrap/linuxcnc_interp_minimal_runtime.cpp now records the tool-data reload boundary call emitted by vendored interp_convert.cc::convert_modal_0(): RELOAD_TOOLDATA. tests/fixtures/gcode/tool_reload.ngc and tests/fixtures/canon/tool_reload.events pin G10 L0 dispatch without adding standalone tool-table reload semantics.
  • runtime/core/linuxcnc_wrap/linuxcnc_interp_minimal_runtime.cpp now records interpreter state-tag boundary calls emitted by vendored LinuxCNC interp_write.cc::write_state_tag() and interp_convert.cc::update_tag(): UPDATE_TAG. tests/fixtures/gcode/state_tag_motion.ngc and tests/fixtures/canon/state_tag_motion.events pin straight-motion state tag emission without deriving modal state in standalone code.
  • runtime/core/linuxcnc_wrap/linuxcnc_interp_minimal_runtime.cpp now records rotary indexer lock/unlock boundary calls emitted by vendored interp_convert.cc::issue_straight_index(): UNLOCK_ROTARY and LOCK_ROTARY. A dedicated linuxcnc_indexer_harness pins the LinuxCNC single-axis G0 A... indexer path, including the surrounding motion-control boundary calls, without adding standalone rotary-indexing semantics.
  • runtime/core/linuxcnc_wrap/linuxcnc_interp_minimal_runtime.cpp now records the canonical flush boundary emitted by vendored LinuxCNC file-reading code: FINISH. tests/fixtures/gcode/percent_file_finish.ngc and tests/fixtures/canon/percent_file_finish.events pin %-delimited file handling from rs274ngc_pre.cc::open() and interp_read.cc::read_text(). The native verification script treats this fixture as file-mode-only because % is a LinuxCNC program-file delimiter, not a valid MDI command.
  • runtime/core/linuxcnc_wrap/linuxcnc_interp_minimal_runtime.cpp now records the reset/queue-drop boundary emitted by vendored rs274ngc_pre.cc::reset(): ON_RESET. tests/fixtures/gcode/file_open_reset.ngc and tests/fixtures/canon/file_open_reset.events pin the LinuxCNC file-open reset path without adding standalone queue-management semantics.
  • runtime/core/linuxcnc_wrap/linuxcnc_interp_minimal_runtime.cpp now records the interpreter initialization boundary emitted by vendored rs274ngc_pre.cc::init(): INIT_CANON. A dedicated linuxcnc_interp_init_harness calls vendored Interp::init() and pins the LinuxCNC initialization sequence: INIT_CANON, USE_LENGTH_UNITS, SET_G5X_OFFSET, SET_G92_OFFSET, SET_XY_ROTATION, and SET_FEED_REFERENCE, without changing the minimal G-code fixture harness startup path.
  • runtime/core/linuxcnc_wrap/linuxcnc_interp_minimal_runtime.cpp now records comment logging canonical boundary calls emitted by vendored interp_convert.cc::convert_comment(): LOGOPEN, LOG, LOGAPPEND, and LOGCLOSE. tests/fixtures/gcode/comment_logging.ngc and tests/fixtures/canon/comment_logging.events pin LinuxCNC comment logging dispatch without adding standalone comment semantics.
  • tools/verify_native_linuxcnc_fixture_baseline.sh now runs an expanded side-by-side baseline against upstream ../linuxcnc/bin/rs274. It normalizes native LinuxCNC canonical output for simple motion, offsets, feed-control, comment/logging, numbered-parameter, spindle-orient, file-finish, and tool-reload fixtures. It now also includes file-open reset and O-word subroutine fixture coverage, threading/rigid tap coverage, NURBS dispatch boundary coverage, comparable canonical runtime edge calls, and the program-end cleanup calls emitted after M2. Native output is filtered to the event classes each standalone fixture explicitly expects, while upstream rs274 output gaps such as WAIT, hidden NURBS control-point detail, and standalone modal-state assertions remain covered by the standalone harness rather than being treated as side-by-side evidence. The comparison does not introduce a project-authored CNC semantics oracle.
  • tests/fixtures/gcode_errors/g1_zero_feed.ngc and tests/fixtures/canon_errors/g1_zero_feed.expected pin the negative G1 zero-feed case. The source basis is LinuxCNC interp_convert.cc::convert_straight() and rs274ngc_return.hh::NCE_CANNOT_DO_G1_WITH_ZERO_FEED_RATE.
  • This proves the current extracted interpreter slice can parse and execute a small fixture set through a standalone canonical event sink.
  • Important direction change: the minimal wrapper behavior is a migration probe, not the program body. Do not expand it into a separate CNC implementation. The next work must retire hand-written wrapper semantics and move execution through vendored LinuxCNC interpreter source such as interp_convert.cc, interp_read.cc, interp_check.cc, and interp_execute.cc.
  • tools/build_native_probes.sh now includes linuxcnc_interp_convert_source_probe, which directly compiles vendored interp_convert.cc. The required emcStatus shim is limited to the native status boundary used by tag_arc() for machine units; it is not a project-authored CNC behavior implementation.
  • tools/build_native_probes.sh now also includes direct source compile probes for vendored interp_read.cc, interp_check.cc, and interp_execute.cc. These probes keep the next interpreter-core migration work focused on LinuxCNC source entry points and expose missing standalone runtime shims before wrapper behavior is expanded.
  • The direct source compile probes now cover the remaining vendored rs274ngc interpreter core files used by the standalone harness: modal_state.cc, interp_array.cc, interp_internal.cc, interp_arc.cc, interp_inverse.cc, interp_cycles.cc, interp_g7x.cc, interp_queue.cc, interp_find.cc, interp_namedparams.cc, interp_write.cc, interp_o_word.cc, and rs274ngc_pre.cc. rs274ngc_pre.cc is compiled with the existing standalone UNIT_TEST/LINUXCNC_STANDALONE_USE_RS274_PRE_STATE boundary that isolates Python runtime integration from the browser simulation core.
  • tools/build_native_probes.sh now includes direct source compile probes for the vendored LinuxCNC trajectory-planner and posemath files used by linuxcnc_tp_api_probe: tp.c, tc.c, tcq.c, spherical_arc.c, blendmath.c, sp_scurve.c, ruckig_wrapper.c, the selected cruckig/*.c sources, emcpose.c, posemath.cc, _posemath.c, and sincos.c. These probes use the same TP_FLAGS as the standalone TP harness, including the existing -fpermissive boundary required by upstream enum conversions in tp.c.
  • tools/verify_vendor_sync.sh now validates the LinuxCNC source extraction boundary before native probes run. It rejects duplicate manifest entries, missing or extra vendored files, and byte-level drift between each manifest file under vendor/linuxcnc/ and the matching file under ../linuxcnc/. tools/source-manifest.txt was also de-duplicated so the manifest is a single authoritative extraction list.
  • Direct source compile probes now also cover vendored interp_base.cc and gomath.c. interp_base.cc uses a standalone EMC2_HOME compile-time path boundary for LinuxCNC's dynamic interpreter lookup, and gomath.c is compiled as C with gcc so its LinuxCNC C linkage is preserved. The standalone rtapi.h shim was made C/C++ compatible for this C-source boundary without changing vendored LinuxCNC files.
  • inifile.cc now has its own direct source compile probe in addition to the existing INI parser harness. With this probe, every .c and .cc file listed in tools/source-manifest.txt has a native source-level compile check under the standalone build boundary.
  • tools/build_native_probes.sh now emits build/native/source-probes.tsv while compiling vendored *_source_probe targets. The native verification script compares that map against every .c and .cc entry in tools/source-manifest.txt, so future LinuxCNC source additions fail validation unless they also get an explicit standalone source compile probe.
  • tools/verify_no_standalone_cnc_semantics.sh now guards against reintroducing a standalone Interp::convert_g() definition outside vendor/linuxcnc/. This keeps G-code group conversion on the vendored LinuxCNC interp_convert.cc path instead of allowing the wrapper layer to grow another project-authored conversion implementation.
  • docs/scope-and-baseline.md records the current upstream LinuxCNC commit, local tool versions, and fixture baseline. tools/verify_upstream_baseline.sh now runs before vendor sync validation so extraction drift is checked against the intended upstream HEAD, not an accidental checkout change.
  • docs/source-reuse-map.md records the current vendored LinuxCNC source reuse matrix and dependency matrix, tying each extracted group to its standalone runtime boundary and validation path.
  • docs/compatibility-validation.md records the current validation chain, source coverage requirements, native harnesses, fixture coverage, and validation boundaries.
  • docs/drift-report.md records current no-drift enforcement, allowed standalone runtime boundaries, known gaps, and the current drift conclusion.

Phase 4: Port INI Parsing Without Editing Upstream

Purpose: Move LinuxCNC config parsing into the standalone runtime.

Steps:

  1. Vendor:
    • ../linuxcnc/src/emc/ini/inifile.cc
    • ../linuxcnc/src/emc/ini/inifile.hh
    • ../linuxcnc/src/emc/ini/inifile.h
  2. Add shim headers under wasm-port/runtime/core/shims/ for small dependencies only.
  3. Replace file loading through:
    • wrapper-level adapter, preferred
    • minimal vendored patch only if unavoidable
  4. Preserve:
    • #INCLUDE
    • relative includes
    • recursion checks
    • line continuation
    • duplicate section merge behavior
    • typed query behavior
  5. Add tests for INI semantics under tests/native/.

Involved LinuxCNC source:

  • src/emc/ini/inifile.cc
  • src/emc/ini/inifile.hh
  • src/emc/ini/inifile.h

Phase 5: Port Parameter Tables and Variable Files

Purpose: Preserve LinuxCNC numeric parameter behavior exactly enough for simulation.

Steps:

  1. Vendor:
    • ../linuxcnc/src/emc/rs274ngc/interp_parameter_def.hh
    • ../linuxcnc/src/emc/rs274ngc/interp_array.cc
    • ../linuxcnc/src/emc/rs274ngc/interp_internal.hh
    • ../linuxcnc/src/emc/rs274ngc/rs274ngc_pre.cc
  2. Extract:
    • setup.parameters[]
    • required_parameters[]
    • readonly_parameters[]
    • parameter file load/save logic
  3. Replace native file writes with a standalone file service abstraction.
  4. Keep LinuxCNCs parameter text format in phase 1.
  5. Add regression tests for:
    • missing file
    • out-of-order parameters
    • zero-fill behavior
    • required parameter persistence
    • read-only protection

Involved LinuxCNC source:

  • src/emc/rs274ngc/interp_parameter_def.hh
  • src/emc/rs274ngc/interp_array.cc
  • src/emc/rs274ngc/interp_internal.hh
  • src/emc/rs274ngc/rs274ngc_pre.cc

Phase 6: Port Named Parameters and Interpreter State

Purpose: Preserve LinuxCNC variable semantics and controller-visible state.

Steps:

  1. Vendor:
    • ../linuxcnc/src/emc/rs274ngc/interp_namedparams.cc
    • ../linuxcnc/src/emc/rs274ngc/interp_internal.hh
    • ../linuxcnc/src/emc/rs274ngc/interp_fwd.hh
    • selected interpreter files needed by setup/state handling
  2. Preserve:
    • context.named_params
    • PA_READONLY
    • PA_GLOBAL
    • PA_USE_LOOKUP
    • PA_FROM_INI
    • built-in named parameters from init_named_parameters()
  3. Preserve lookup order:
    • local
    • global
    • _ini[...]
    • _hal[...]
    • optional Python providers later
  4. Export state outward rather than redesigning it in JS.
  5. Add regression cases for:
    • local/global scoping
    • built-in state variables
    • _ini[...]
    • _hal[...]
    • read-only errors

Involved LinuxCNC source:

  • src/emc/rs274ngc/interp_namedparams.cc
  • src/emc/rs274ngc/interp_internal.hh
  • src/emc/rs274ngc/interp_fwd.hh
  • src/emc/rs274ngc/interpmodule.cc

Phase 7: Replace HAL Runtime With a Simulation HAL Adapter

Purpose: Keep LinuxCNC interpreter-visible HAL behavior without migrating HAL itself.

Steps:

  1. Do not vendor the whole HAL runtime as a target runtime dependency.
  2. Use LinuxCNC HAL source as reference only for interface semantics:
    • ../linuxcnc/src/hal/hal.h
    • ../linuxcnc/src/hal/halmodule.cc
    • ../linuxcnc/src/emc/ini/inihal.cc
  3. Define a standalone HAL adapter interface:
    • lookup by name
    • typed numeric value
    • connection/existence status
  4. Make _hal[...] reads resolve through this adapter.
  5. Seed the adapter from simulation config and runtime state.
  6. Add tests for:
    • existing names
    • missing names
    • disconnected signals
    • type conversion

Involved LinuxCNC source:

  • src/hal/hal.h
  • src/hal/halmodule.cc
  • src/emc/rs274ngc/interp_namedparams.cc
  • src/emc/ini/inihal.cc

Phase 8: Port the Interpreter Core

Purpose: Build the standalone G-code execution engine without touching upstream files.

Steps:

  1. Vendor selected files from ../linuxcnc/src/emc/rs274ngc/.
  2. Keep original source as intact as possible in vendor/.
  3. Add wrappers or minimal patches only in the standalone area.
  4. Replace these external edges:
    • file open/read
    • world sync
    • HAL reads
    • logging
    • runtime callbacks
  5. Preserve:
    • modal state
    • subroutines
    • offsets
    • tool handling
    • parameter interactions
    • error semantics
  6. Add a canonical event sink interface in standalone code.

Involved LinuxCNC source:

  • src/emc/rs274ngc/interp_execute.cc
  • src/emc/rs274ngc/interp_read.cc
  • src/emc/rs274ngc/interp_check.cc
  • src/emc/rs274ngc/interp_convert.cc
  • src/emc/rs274ngc/interp_cycles.cc
  • src/emc/rs274ngc/interp_find.cc
  • src/emc/rs274ngc/interp_write.cc
  • src/emc/rs274ngc/interp_o_word.cc
  • src/emc/rs274ngc/rs274ngc_pre.cc
  • src/emc/rs274ngc/rs274ngc_interp.hh

Phase 9: Port Planner and Kinematics

Purpose: Preserve LinuxCNC motion planning and 5-axis simulation behavior.

Steps:

  1. Vendor selected planner files from ../linuxcnc/src/emc/tp/.
  2. Vendor selected kinematics files from:
    • ../linuxcnc/src/emc/kinematics/
    • selected .comp sources mirrored into standalone code where needed
  3. Replace loadable-module assumptions with a registry in the standalone runtime.
  4. Keep original math and state logic intact as far as practical.
  5. Add tests for:
    • planner outputs
    • forward/inverse kinematics
    • 5-axis world/joint transforms
    • LinuxCNC five-axis M428/M429/M430 remap assets and switchkins machine configuration links

Current five-axis switchkins status:

  1. Vendored LinuxCNC 5axiskins, xyzac-trt-kins, and xyzbc-trt-kins sources are compiled and probed natively.
  2. Vendored LinuxCNC sample machine assets now cover bridge-mill, table-dual-rotary, and table-rotary-tilting INI/HAL/tool/demo/remap files.
  3. M428, M429, and M430 remain LinuxCNC REMAP entries backed by vendored LinuxCNC NGC subroutines; do not replace them with standalone M-code handlers.
  4. linuxcnc_remap_parse_harness now links vendored interp_remap.cc and validates the LinuxCNC Interp::parse_remap() path for xyzac-trt, xyzbc-trt, xyzab-tdr, and bridge-mill NGC remap descriptors, matching each vendored machine's LinuxCNC-defined M428/M429/M430 set.
  5. linuxcnc_remap_hal_sync_harness validates vendored LinuxCNC M68 and M66 synchronization through the standalone HAL adapter boundary.
  6. linuxcnc_5axis_remap_execute_harness executes the xyzac-trt and xyzbc-trt switchkins M429 -> M428 -> M430 -> M429 paths plus the xyzab-tdr two-remap M429 -> M428 -> M429 path and the bridge-mill M429 -> M428 -> M430 -> M429 path where M428 selects default bridge-mill kinematics, loads vendored machine tool tables through LinuxCNC tooldata_load(), and runs vendored xyzac_switchkins.ngc, xyzbc_switchkins.ngc, xyzac_switchkins_test_1.ngc, xyzac_switchkins_test_2.ngc, xyzac_switchkins_test_3.ngc, boat-xyzac.ngc, boat-xyzbc.ngc, impeller-7bl-xyzac.ngc, and xyzab-tdr-demo.ngc, plus bridge-mill 5axisgui.ngc through LinuxCNC remap/file execution paths.
  7. The WASM interpreter core now links vendored interp_remap.cc plus the Python-only runtime edge stub, and the Node WASM smoke runs vendored xyzac_switchkins.ngc, xyzbc_switchkins.ngc, xyzac_switchkins_test_1.ngc, xyzac_switchkins_test_2.ngc, xyzac_switchkins_test_3.ngc, boat-xyzac.ngc, boat-xyzbc.ngc, impeller-7bl-xyzac.ngc, xyzab-tdr-demo.ngc, and 5axisgui.ngc through the SDK C ABI without JavaScript M-code or kinematics semantics.
  8. Browser smoke and the INI panel UI now expose the same validated 5-axis remap execution path by copying vendored LinuxCNC sample-machine files into the interpreter WASM filesystem and calling the SDK C ABI.
  9. LinuxCNC upstream tests/remap/duplicate-o-word and tests/remap/m30-interaction are now vendored unchanged and validated through the generic remap file execution path. Native, Node WASM, and browser interpreter smokes load the vendored INI, program, and remap subroutines, then call vendored Interp::parse_remap(), open(), read(), and execute(); standalone code only supplies the INI/file path boundary.

Next work:

  1. Continue broadening source-backed remap coverage through LinuxCNC upstream tests that can run through NGC REMAP/O-word/file execution without Python semantics.
  2. Evaluate additional vendored LinuxCNC five-axis sample machines and demos after the TRT boat/impeller, TDR demo, and bridge-mill demo paths are stable in native, Node WASM, browser, and UI remap paths.

Involved LinuxCNC source:

  • src/emc/tp/tp.c
  • src/emc/tp/tc.c
  • src/emc/tp/tcq.c
  • src/emc/tp/blendmath.c
  • src/emc/tp/sp_scurve.c
  • src/emc/tp/ruckig_wrapper.c
  • src/emc/kinematics/*.c
  • src/hal/components/xyzab_tdr_kins.comp
  • src/hal/components/xyzacb_trsrn.comp
  • src/hal/components/xyzbca_trsrn.comp
  • configs/sim/axis/vismach/5axis/bridgemill/*
  • configs/sim/axis/vismach/5axis/table-dual-rotary/*
  • configs/sim/axis/vismach/5axis/table-rotary-tilting/*

Phase 10: Build the Standalone WASM Program

Purpose: Keep the migrated program completely outside native LinuxCNC management.

Steps:

  1. Build the standalone native core first.
  2. Add a standalone WASM export layer under:
    • wasm-port/runtime/core/
    • wasm-port/runtime/sdk/
  3. Use vendor/ sources plus standalone wrappers as build input.
  4. Do not compile from ../linuxcnc directly in the final WASM product build.
  5. Emit:
    • wasm module
    • JS loader
    • standalone SDK

Outputs:

  • independently managed WASM simulation core

Phase 11: Build the Frontend and OPFS Layer

Purpose: Keep the standalone port program separate from LinuxCNC GUI code and storage.

Steps:

  1. Build frontend files under:
    • wasm-port/runtime/ui/
    • wasm-port/runtime/opfs/
  2. Implement:
    • HTML shell
    • JavaScript control panel
    • OPFS persistence
    • file import/export
    • machine/controller state views
  3. Keep LinuxCNC GUI code reference-only.
  4. Ensure the frontend depends only on the standalone SDK, not on native LinuxCNC binaries.

Outputs:

  • standalone browser program

Phase 12: Validate Drift Against Upstream

Purpose: Prove the standalone port still tracks LinuxCNC behavior.

Steps:

  1. Use native LinuxCNC tests and fixtures as semantic baselines.
  2. Compare:
    • LinuxCNC native behavior
    • standalone native extracted core
    • standalone WASM behavior
  3. Maintain a drift report.
  4. Re-run extraction if upstream source changes.
  5. Keep patches small and traceable.

Outputs:

  • drift report
  • compatibility regression suite

Management Rules For Daily Development

Use these rules continuously:

  1. Never edit files under linuxcnc/ as part of the port.
  2. Any needed modification to upstream logic must be applied to vendored copies only.
  3. Any upstream sync must be script-driven and repeatable.
  4. Every shim must be documented.
  5. Every local patch against vendored source must be stored as a patch file or clearly isolated wrapper.

Keep these documents under wasm-port/docs/:

  • scope-and-baseline.md
  • source-reuse-map.md
  • state-porting-strategy.md
  • wasm-build-strategy.md
  • frontend-architecture.md
  • opfs-file-model.md
  • compatibility-validation.md
  • drift-report.md

Immediate Next Step

Continue from the current verified extracted-core baseline without adding project-authored CNC semantics:

  1. Keep all Interp::... interpreter member behavior on vendored LinuxCNC source. Standalone code may only provide documented runtime-edge stubs such as the current Python/remap boundary.
  2. Expand WASM interpreter coverage by routing more existing native fixture paths through runtime/core/linuxcnc_wrap/linuxcnc_interp_wasm.cpp, using the same vendored interpreter source set as the native harness.
  3. Promote remaining standalone-only fixture expectations to native LinuxCNC baselines where possible, especially adapter-heavy paths such as INI/HAL named parameters, tool-change host state, and richer machine session state.
  4. Move browser-facing work through SDK and OPFS adapters only after the core behavior is validated against native LinuxCNC or vendored-source harnesses.
  5. Before adding any CNC feature, update tools/source-manifest.txt, extract the LinuxCNC source file, add a source probe or harness, and document the reuse boundary in docs/source-reuse-map.md.