Errata — LinuxCNC Code Notes

master 2.10.0~pre1 — unreleased
HEAD caa13ca6 (2026-07-30) · audited 2026-08-03

“Much of this information is now outdated and has never been reviewed for accuracy.”

docs/src/code/code-notes.adoc — “Intended audience”, opening section

The document warns you itself. This audit takes the warning at its word and puts the Code Notes against the source of the master branch — both block diagrams, the motion command list, the libnml and CMS chapters, the tool table, the documented semantics of every surviving command, handler by handler in command.c — and, beyond the Code Notes, a sampled audit of the G-code reference against the interpreter.

Twenty-seven corrections. Two commands declared “always accepted” can in fact be rejected. A defect described as “currently broken” has been fixed without the text moving — twice. Three documented commands still have no handler at all, and nothing even emits them. The G-code reference documents two errors that are implemented nowhere. What holds up is listed as such — sixteen points, including one whole chapter. Fixes for the actionable errata are prepared as three patches (see upstream/ beside this file).

27corrections
16confirmed correct
73of 76 commands have a handler
3upstream patches prepared

⚠ Which LinuxCNC does this describe?

Every finding here is against master, which is unreleased. The newest tag in the repository is v2.9.10. Most of the errata hold for 2.9 as well — HAL, the emcmot segment, the queues, the servo cycle, joints versus axes — but erratum 3 does not.

The commit that moved all I/O handling out of the iocontrol process and into milltask is 764655eb4d (2023-05-16). git tag --contains returns nothing for it and git branch -r --contains returns only origin/master. If you run 2.9.x — the current stable — iocontrol is still a separate process on your machine. Ask which version you are on before acting on that one.

A second date worth keeping straight: iotaskintf.cc was dropped from the task Submakefile back on 2011-08-03 (d56fdbfcbb) — twelve years before the I/O handling itself moved. Two separate events, easily conflated.

Sheet A

The diagram as published

faithfully redrawn · error markers in red

Topology and captions follow the original. Blocks struck through or ringed in red carry a numbered marker keyed to the errata tables below.

GUI operator interfaces CMD STAT ERR NML EMCTASK RS-274 (GCODE) INTERPRETER SEQUENCING LOGIC CMD STAT ERR NML? 1 CMD STAT ERR NML NON-REALTIME REALTIME 9 SHARED MEMORY BUFFER FIFOS? 2 EMCMOT TRAJECTORY PLANNER FORWARD KINEMATICS INVERSE KINEMATICS LIMIT & HOME STATUS 8 AXIS 1 6 INTERPOLATOR Σ PID SERVO UNIT CONVERT 5 AXIS N (identical) REALTIME HARDWARE DEVICES ENCODER CNTR D/A CONVERTER LIMIT SWITCHES MOTOR / ENC. EMCIO 3 COORDINATING CONTROLLER SPINDLE CONTROLLER 7 COOLANT CONTROLLER AUXILIARY CONTROLLER NON-REALTIME HARDWARE DEVICES MISSING FROM THE DIAGRAM HAL — the layer that now carries all coupling between motion and the hardware 4 interp_list and TC_QUEUE — the two real queues of the system, never drawn 10
Sheet Aredrawn after docs/src/code/LinuxCNC-block-diagram-small.png — original captions kept · in the repository unchanged since 2012-11-19, commit b60c20198e (git log --follow shows a single commit); the drawing's content is plainly older but nothing in the repository dates it further back
Errata

Corrections 1 to 10 — overall diagram

every row is checkable in the source
#What the diagram claimsWhat master saysEvidence
1 “NML?” between EMCTASK and shared memoryThe task→motion link would be an NML channel with its CMD/STAT/ERR triplet. It is not NML. The task writes straight into an RTAPI shmem segment opened by motmod with key 100, through usrmotWriteEmcmotCommand(). And there is exactly one NML triplet in the whole system — GUI to task — not three. motion.c:44
usrmotintf.h:67
linuxcnc.nml
2 “FIFOS?” between shared memory and EMCMOTThe crossing into real time would be done with queues. No. It is a single slot: emcmot_command_t command, guarded by command_mutex, with a commandNum counter whose echo the task waits for in commandNumEcho. One command at a time, 1 s timeout. The only ring in that segment is emcmot_error_t — 32 × 1024 B, lock-free MPSC. motion_struct.h:20-21
motion.h:211, 584
motion.h:746
3 EMCIO, a fourth processA separate discrete I/O controller with its own NML channels to EMCTASK. The process is gone. The task Submakefile carries # disabled: emc/task/iotaskintf.cc, and the 14 iocontrol.0.* pins are created by milltask itself. The iocontrol name is kept so existing .hal files keep working. task/Submakefile:13
taskclass.cc:41,133
4 HAL appears nowhereThe motion controller would talk to hardware directly. Structural omission, and the heaviest one: the diagram predates HAL. Today all coupling goes through a 2 MiB shared block (key 0x48414C32) holding components, pins, signals, functions and threads. The motion controller does nothing but write to pins. hal_priv.h:120-122
motion.c:518+
5 PID SERVO, D/A CONVERTER, ENCODER COUNTER, LIMIT SWITCHES inside EMCMOTServo loop and I/O would be internal to the motion controller. All moved out. They are independent HAL components, wired in a .hal file by the integrator. The comment in control.c is unambiguous: the final motor position goes “to the HAL (which routes it to the PID”. Nothing in src/emc/motion/ computes a PID. control.c:180
hal/components/pid.c
6 “AXIS 1 … AXIS N”One controller per axis, axis and motor conflated. These are joints, not axes. Since the joints/axes rework a joint is a motor and an axis is a Cartesian coordinate: EMCMOT_MAX_JOINTS 16 against EMCMOT_MAX_AXIS 9. On a trivkins machine they coincide; on a delta or a hexapod they do not. emcmotcfg.h:25,31
7 SPINDLE CONTROLLER inside EMCIOThe spindle would be driven outside real time. The spindle moved into real time. motmod exports spindle.N.on, speed-out, at-speed, index-enable, orient… for 8 spindles. It had to: threading and rigid tapping need servo-rate synchronisation. motion.c:709-734
emcmotcfg.h:33
8 Planner, kinematics and homing as fixed blocksThree functions welded into EMCMOT. They are separately loaded modules: tpmod, homemod, and one kinematics module out of 19. Each can be replaced by a custom implementation without recompiling the rest. In master, cruckig — a 34-file pure-C port of Ruckig — brings a finite-jerk planner. tp/tpmod.c
motion/homemod.c
tp/cruckig/
9 The “NON-REALTIME / REALTIME” lineImplies a kernel boundary. By default it is not one any more. With the uspace flavour on a PREEMPT_RT kernel, “real-time” components are SCHED_FIFO user-space threads hosted by rtapi_app. The boundary has become a scheduling boundary. Kernel-module mode survives only for RTAI. rtapi/uspace_posix.cc
rtapi/rtai_rtapi.c
10 No queue shown anywhereThe diagram writes “FIFOS?” where there are none, and nothing where there are. Both real queues are elsewhere and absent from the drawing: interp_list, the std::deque that absorbs how far the interpreter runs ahead of the machine; and TC_QUEUE, the ring of 2000 segments (≈ 1 MB) that gives the planner the lookahead it needs to blend corners. nml_intf/interpl.hh:46
tp/tcq.h
emcmotcfg.h:70
Sheet B

The corrected diagram

same topology, ten corrections applied

Deliberately laid out like Sheet A so the comparison can be made block by block. Amber marks what was corrected or added; blue marks what the original already had right.

GUI · halui · linuxcncrsh axis · gmoccapy · qtvcp · touchy · gscreen emcCommand 8192 B · queue emcStatus 20480 B emcError 8192 B · queue NML the only triplet · owned by linuxcncsvr milltask RS-274 interpreter · rs274ngc interp_list std::deque unbounded 10 sequencing emctaskmain.cc iocontrol.0.* 14 HAL pins former io process 3 tool, coolant, estop — no NML channel left for I/O RTAPI shmem · key 100 · emcmot segment emcmot_command_t SINGLE SLOT mutex + commandNum 2 emcmot_status_t seqlock head / tail + config, internal emcmot_error_t MPSC RING 32 × 1024 the only real queue here not NML, not a FIFO: one exchange at a time 1 ordinary scheduling SCHED_FIFO — rtapi_app, user space a scheduling boundary, no longer a kernel boundary — except under RTAI 9 motmod + loaded modules tpmod trajectory planner cruckig · finite jerk homemod homing *kins forward and inverse kinematics · 19 modules 8 TC_QUEUE ring · 2000 segments ≈ 1 MB 10 spindle × 8 spindle.N.on · speed-out at-speed · index-enable 7 limit and home status JOINT 1 … 16 — a motor, not an axis · 9 separate Cartesian axes 6 cubic interpolator backlash + screw comp output: motor_pos_cmd on a HAL pin — the controller stops there WHAT THE DIAGRAM GOT RIGHT cubic interpolator · forward and inverse kinematics · limit/home status RS-274 interpreter · sequencing logic · NML triplet to the GUI encoder and motor closing the loop · the principle of a real-time boundary HAL 2 MiB shared block · key 0x48414C32 · components ▸ pins ▸ signals ▸ functions ▸ threads absent from the original — yet it now carries every bit of hardware coupling 4 HAL COMPONENTS — wired by the integrator, moved out of the controller 5 pid stepgen pwmgen encoder estop_latch limit3 124 .comp + 25 .c source files DRIVERS AND HARDWARE hostmot2 · Mesa hal_parport gpio · rpi, beaglebone motor · encoder · limit switches · spindle — 23 driver files + hostmot2 (42 modules, one driver)
Sheet Bestablished by reading master at HEAD caa13ca6 · markers = errata numbers
Sheet C

The second diagram — joint controller

verdict: largely accurate

The document carries a second diagram, of a single joint controller. It ages far better than the first: all ten pins it shows still exist, at the same points in the chain. Two corrections only.

FEEDBACK TO KINEMATICS pos-fb Σ motor-pos-fb ← HAL motor-offset COMMANDS FROM KINEMATICS coarse-pos cubic interpolator pos-cmd backlash & screw error comp Σ motor-pos-cmd → HAL free-pos-cmd free-vel-lim free mode trajectory planner simple_tp.c free mode JOINT HAL PINS — ALL VERIFIED PRESENT pos-lim-sw-in neg-lim-sw-in home-sw-in amp-enable-out amp-fault-in index-pulse-in 11 MISSING FROM THE DIAGRAM The whole axis.L.* family — axis.x.pos-cmd, axis.x.teleop-vel-cmd, external offsets — is absent. The diagram knows only the joint. Same root cause as erratum 6: the joints/axes split postdates it. 12
Sheet Credrawn after docs/src/code/emc2-motion-joint-controller-block-diag.png · blue = verified present
Errata

Corrections 11 to 15

joint diagram, command list, libnml, tool table
#What the document claimsWhat master saysEvidence
11 Pin “index-pulse-in”A plain pulse input on the joint controller. It is called joint.N.index-enable and it is not an input: it is a bidirectional HAL_IO pin, hence a handshake — the requester sets it, the encoder driver clears it when the index is seen. It is moreover created by homemod, not motmod — a direct consequence of erratum 8. homing.c:254-255
homing.c:113,537
12 The joint controller ignores axesOnly joint quantities flow. The whole axis.L.* family is missing: axis.x.pos-cmd, axis.x.teleop-vel-cmd and the external offsets. It carries Cartesian jogging and plasma torch height control, which act in parallel with the chain drawn. motion.c · axis.c
emcmotcfg.h:31
13 “This section simply lists all of the commands”27 documented commands, presented as the inventory. The cmd_code_t enumeration holds 76. Seven documented names no longer exist, and 57 commands were never documented — including whole features: reverse run, rigid tapping, finite jerk, external offsets, spindle orient, multi-spindle. motion.h · cmd_code_t
code-notes.adoc:227-740
14 “LinuxCNC manages tool information in a tool table file”A single text file. Storage was rebuilt: three back-ends coexist in src/emc/tooldata/mmap (readers map the table read-only), an external database driven by [EMCIO]DB_PROGRAM, and NML. A third-party program can therefore own the tool data. None of this appears in the chapter. tooldata_mmap.cc
tooldata_db.cc
taskclass.cc:147
15 “type — SHMEM, LOCMEM, FILEMEM, PHANTOM, or GLOBMEM”Five buffer types announced as valid in the NML config file. Two of the five do not exist. FILEMEM and GLOBMEM are recognised nowhereGLOBMEM survives only in a comment on the buffer_type field declaration. Only three types actually construct an object: PHANTOM (:729), SHMEM (:819), LOCMEM (:849). A fourth string, RTLMEM (:844), is recognised solely in order to be refusedrcs_print_error("RTLMEM not supported."). cms_cfg.cc
libnml/buffer/
Part 1

The command list

erratum 13 in detail

The widest gap in the document. The “Commands” chapter runs five hundred lines and presents itself as the inventory of the motion controller's commands. It covers a quarter of them.

19 documented and still valid
57 never documented
0actual chapter coverage: 25 %76 commands

Seven documented names match no symbol in the code:

EMCMOT_ENABLE_AMPLIFIEREMCMOT_DISABLE_AMPLIFIEREMCMOT_ACTIVATE_JOINT EMCMOT_DEACTIVATE_JOINTEMCMOT_SCALEEMCMOT_HOMEEMCMOT_CLEAR_PROBE_FLAG

The first two are gone outright: amp enabling became the joint.N.amp-enable-out pin. The others were renamed — EMCMOT_JOINT_ACTIVATE, EMCMOT_JOINT_DEACTIVATE, EMCMOT_JOINT_HOME, EMCMOT_CLEAR_PROBE_FLAGS in the plural — and SCALE was split into FEED_SCALE, RAPID_SCALE and SPINDLE_SCALE.

Among the 57 missing, these are not details — they are whole machine features.

Reverse runEMCMOT_REVERSE, EMCMOT_FORWARD — replaying a program backwards.
Finite jerkEMCMOT_SET_JERK, SET_JOINT_JERK_LIMIT, SET_AXIS_JERK_LIMIT.
Switchable plannerEMCMOT_SET_PLANNER_TYPE, SETUP_ARC_BLENDS.
Rigid tappingEMCMOT_RIGID_TAP, SET_SPINDLESYNC.
Cartesian axesThe whole EMCMOT_SET_AXIS_* family — limits, velocity, acceleration, locking joint.
SpindleSPINDLE_ORIENT, SET_NUM_SPINDLES, SET_SPINDLE_PARAMS, brakes.
External offsetsEMCMOT_SET_OFFSET — plasma torch height control rests on it.
OthersJOG_ABORT, JOINT_UNHOME, SET_PROBE_ERR_INHIBIT, SET_DOUT, SET_AOUT.
Part 2

libnml, CMS and the tool table

verdicts
intact

The libnml and CMS chapters. Every class described still exists at the stated path: linklist.hh, shmem.hh, memsem.hh, timer.hh, cms.hh, nml.hh. The description of the config file format, the B and P lines, the update() mechanism and the NML base class is accurate. This is the best-aged part of the document — unsurprisingly: libnml is inherited NIST code nobody rewrites.

two dead types

Two of the five buffer types do not existFILEMEM and GLOBMEM (erratum 15). Only PHANTOM, SHMEM and LOCMEM construct anything; RTLMEM is recognised only to be refused. In fairness the document says a few lines later that “SHMEM [is] the only buffer type to use” — the error is signalled without being corrected.

concepts intact

The toolchanger model. The random / nonrandom distinction, pocket 0 as the spindle, the special meaning of T0 under nonrandom, copy versus swap: all still accurate. It is the storage that changed, not the model (erratum 14).

A constant: names outlive structures

Three times in this audit a name survived what it designated. Pins are still called iocontrol.0.* though the iocontrol process is gone. The INI section holding RANDOM_TOOLCHANGER, TOOL_TABLE and DB_PROGRAM is still [EMCIO], named after a component that no longer exists.

This is not negligence, it is compatibility: thousands of user-written .hal and .ini files keep working. But it explains why documentation ages so badly here: the names stay right while the structure they describe has been dismantled. A reader trusting the names will conclude iocontrol is a process. It has not been one for a long time.

Part 3

Semantics of the surviving commands

“Requirements” and “Results” against command.c

Knowing that a name exists says nothing about behaviour. Here are the nineteen surviving commands, each against its handler. Four are contradicted, three have no handler, four are not described at all — and eight hold up.

CommandVerdictWhat the audit findsEvidence
ABORTincomplete The document writes “I don't know exactly what kind of stop results” for teleop mode. The code answers: axis_jog_abort_all(0). It also adds a spindle at-speed diagnostic the text ignores.command.c:492
FREEaccurate The deferred flag switch is described correctly: the handler only sets coordinating = 0 and teleoperating = 0.command.c:583
TELEOPmoved The handler is now one call: switch_to_teleop_mode(). The described conditions no longer live in the handler; they migrated into that function.command.c:618
COORDaccurate Same deferred mechanism as FREE. Description conforms.command.c
ENABLEcontradicted “Requirements: None” is false. Rejected if the motion.enable pin is low. See erratum 16.command.c:1366
DISABLEaccurate Always accepted, effect deferred to the controller cycle. Conforms.command.c
ENABLE_WATCHDOGdead No case in command.c. The document gives it a full section. Erratum 20.command.c
DISABLE_WATCHDOGdead Same. The document admits “Currently nothing”, which stays true — but it is a phantom command.command.c
PAUSEincomplete The handler matches (tpPause() then paused = 1), but following the flag into the planner answers the document's own open question — and finds an omission that matters. Erratum 25.command.c:1230
tp.c:243-252
RESUMEaccurate stepping = 0, tpResume(), paused = 0. Conforms.command.c:1252
STEPcontradicted “Requirements: None” is false. Rejected with an error unless the planner is already paused. Erratum 17.command.c:1261
OVERRIDE_LIMITScontradicted The defect documented as “currently broken” has been fixed. Erratum 19.command.c:702
JOG_CONTcontradicted No longer free-mode only, and five new rejection conditions. Erratum 18.command.c:796
JOG_INCRcontradicted Same handler, same gaps.command.c
JOG_ABScontradicted Same handler, same gaps.command.c
SET_LINEundocumented The document says “(More later)”. Nothing to audit — there is no description.
SET_CIRCLEundocumented (More later)”.
PROBEundocumented (More later)” — for an entire probing feature.
SET_TELEOP_VECTORdead (More later)”, and no handler either. Erratum 20.command.c

The two false “Requirements: None”

The document — ENABLE

None. The command can be issued at any time, and will always be accepted.

command.c:1366

The command is refused when the hardware input is low. An operator who has not armed the enable chain gets a rejection, not an acceptance.

if ( !hal_get_bool(emcmot_hal_data->enable) ) { reportError(_("can't enable motion, enable input is false")); } else { emcmotInternal->enabling = 1; ... }
The document — STEP

None. The command can be issued at any time, and will always be accepted.” and “The trajectory planner resumes, and later pauses”.

command.c:1261

STEP only acts on an already paused planner. Otherwise, an explicit error. That is the opposite of a command that is always accepted.

if(emcmotStatus->paused) { emcmotInternal->idForStep = emcmotStatus->id; emcmotStatus->stepping = 1; tpResume(&emcmotInternal->coord_tp); emcmotStatus->paused = 1; } else { reportError(_("MOTION: can't STEP while already executing")); }
Errata

Corrections 16 to 25

semantics and deeper reads
#What the document claimsWhat master saysEvidence
16 ENABLE — “Requirements: None”“The command can be issued at any time, and will always be accepted.” There is a condition. ENABLE is rejected with an error message if the motion.enable HAL pin is low — a hardware condition: the enable chain must be armed. The clause “if forward kinematics are not available, switch to free mode” is still accurate — that is the KINEMATICS_INVERSE_ONLY test. command.c:1366
17 STEP — “Requirements: None”“Always accepted. The planner resumes, and later pauses.” The command only acts when emcmotStatus->paused is true. Otherwise it is refused with “can't STEP while already executing”. The document describes exactly half the behaviour. command.c:1261
18 JOG_CONT / JOG_INCR / JOG_ABS“Rejected if the machine is not in free mode, or if any joint is in motion. The joint is identified by emcmotCommand->axis.” Three gaps. Jogging is no longer free-mode only: the handler tests !GET_MOTION_TELEOP_FLAG() and also serves the teleop case, where jogging is Cartesian. The field is joint, not axisaxis now means a Cartesian coordinate (erratum 6). And five rejection conditions unknown to the document were added: the jog-inhibit pin, homing in progress, a jogwheel already active on that joint, a locking joint needing unlock, and jogging further onto a limit — the last no longer silent but raising SET_JOINT_ERROR_FLAG. command.c:796-840
19 OVERRIDE_LIMITS — “This is currently broken”“Once an OVERRIDE_LIMITS command is received, limits are ignored until another OVERRIDE_LIMITS re-enables them.” And: “Limits on all joints are over-ridden.” The defect was fixed; the document still describes the bug. The code comment now says the opposite: “they are automatically re-enabled at the end of the next jog” — precisely the behaviour the document presented as unrealised intent. Second gap: only limits actually tripped are overridden, through a mask built from GET_JOINT_NHL_FLAG and GET_JOINT_PHL_FLAG — not “all joints”. command.c:702-730
20 Three commands described as operationalSET_TELEOP_VECTORinstructs the motion controller to move along a specific vector”, plus ENABLE_WATCHDOG and DISABLE_WATCHDOG, each with its Requirements/Results section. All three have no case in command.c. Issuing them does nothing. For the watchdogs the document half-admits it (“Currently nothing”); for SET_TELEOP_VECTOR it does not — that one is presented as a motion command. motion.h vs command.c
21 Internal note dated 6/5/2020“The cmd_code_t enumeration contains 73 commands, but the switch statement in command.c contemplates only 70.” The document therefore already contains a self-audit — itself now stale. The figures today are 76 and 73. The gap of three is unchanged and covers exactly the same three commands as in 2020: nothing was fixed in six years, neither the code nor the note. Adversarial re-check: a whole-tree search shows the three appear only in motion.h and motion-logger.cnothing even emits them. code-notes.adoc:242-246
motion.h · command.c
22 “For an example of such a bug, see the treatment of axes in src/emc/ini/initraj.cc:loadTraj()”Offered as a live example of a joints/axes bug. Fixed. The code now reads: “originally, this code would only set axes X, Y and Z … Now all axes are set”. The second instance of the document preserving a bug report past its repair (the first was OVERRIDE_LIMITS, erratum 19) — at two, it is a characteristic, not an accident: the document records defects and never their repair. initraj.cc:203-205
23 The EMCIO chapter versus its own diagramThe block diagram shows EMCIO as a fourth process; the prose opens “The I/O Controller is part of TASK” — then describes an “iocontrol main loop process” anyway. The document contradicts itself, independently of which version matches the code. The first line was updated for the merge into task; the rest of the chapter still describes the dead separate process. code-notes.adoc:754-767
24 TELEOP requires all joints homedStated unconditionally. True only when kinType != KINEMATICS_IDENTITY. On a trivkins machine, teleop needs no homing at all. Found by following the handler's delegate, switch_to_teleop_mode(). motion.c:173-178
25 PAUSE — “I don't know if it pauses all motion immediately, or if it completes the current move”The document's own open question, plus “Requirements: None”. Neither. The machine decelerates to a stop mid-segment at that segment's accel/jerk limits. And the omission that matters: pause is silently ignored while the segment is position-synchronized to the spindle — threading (G33) and rigid tapping — because tpGetFeedScale() forces 1.0 there, bypassing pause and feed override. Pressing pause during a G33 does nothing until the synchronized move ends. Nothing in the chapter says so. tp.c:243-252
tp.c:2782-2787

Counting method

command.c holds 76 case EMCMOT_* labels for 73 distinct values: the three jog commands appear twice, in two different switch statements — one for handling, the other only for an error message. Against the 76 enumeration values, three commands are left with no handler.

Part 4

Beyond the Code Notes — the G-code reference

sampled: G33 · G33.1 · G76 · G64 · G96

The same method pointed at the documentation users actually read: docs/src/gcode/g-code.adoc (2 786 lines) against src/emc/rs274ngc/. A sampled audit — five families tied to spindle synchronization and path control — and a finding of a different kind: the reference documents errors that are implemented nowhere.

#What the reference claimsWhat the interpreter saysEvidence
26 G33 / G33.1 — “It is an error if … The requested linear motion exceeds machine velocity limits due to the spindle speed” No such check exists — not in the interpreter, not in task, not in motion. Triple-confirmed through independent oracles: the NCE_* error catalog, every _() string in the interpreter, and all 26 .po translation catalogs — the message has never existed in any translated release. Meanwhile two real errors were missing from the documented list: K word required (NCE_K_WORD_MISSING_WITH_G33) and F word forbidden (NCE_F_WORD_USED_WITH_G33). Also stated now: “spindle not turning” tests the commanded M3/M4 state, not measured rotation. interp_check.cc:375-378
interp_convert.cc:5496-5529
27 G96 — “It is an error if … A feed move is specified in G96 mode while the spindle is not turning” No such check exists anywhere. The spindle-not-turning checks apply only to G33/G33.1/G76 and the tapping cycles. A plain G1 in CSS mode with a stopped spindle is accepted without a word. Documented in passing: without D, CSS mode applies no RPM limitSET_SPINDLE_MODE(s, 1e30). interp_cycles.cc:270
interp_convert.cc:5087

Why nobody caught them

Both phantom errors describe physically sensible constraints — things a reader would believe a CNC ought to check. That is exactly why they survived: they are plausible. This documentation does not only rot by aging; it also contains invented safety checks that were perhaps once planned and never implemented.

In-code defects found in passing (code, not docs)

interp_convert.cc:5534 — G76's $-validity check reports “Invalid D-number in G76 cycle”, the wrong word name. command.c:1475 and :1553 — PROBE and RIGID_TAP carry the copy-pasted comment “requires … enable off” while the test three lines below requires enable on. command.c:1966SET_AXIS_LOCKING_JOINT's debug message prints a name matching no command.

Fixes are prepared

Errata 15–17, 19, 21–23 and 25–27 are fixed in three patches on the audit-fixes branch of the local clone, exported to upstream/ with a README mapping each patch to its errata — all three verified to apply cleanly on pristine master. The complete 76-command inventory the Code Notes never had is written out in motion-commands-reference.md. Submission upstream is the repository owner's call.

Confirmed

What the Code Notes still get right

verified, unchanged

An audit that only found faults would be dishonest. Sixteen points hold, including one whole chapter and one I expected to be obsolete.

The NML triplet to the GUICMD / STAT / ERR between interfaces and task: accurate, and it is the only one in the system.
EMCTASK's contentsRS-274 interpreter and sequencing logic really do share one process.
The cubic interpolatorStill present and active: joint->cubic, cubicDrain(). It bridges the trajectory rate and the servo rate.
Forward and inverse kinematicsBoth directions and their place in the loop are right — only their modularity changed.
Limit and home statusThe “LIMIT & HOME STATUS” block still matches something real in the controller.
Encoder and motor at the end of the chainThe physical loop closes as drawn.
The principle of a real-time boundaryIt still exists and structures everything; its nature changed, not its existence.
The joint controller's pinsTen pins verified one by one: pos-lim-sw-in, neg-lim-sw-in, home-sw-in, amp-enable-out, amp-fault-in, motor-pos-cmd, motor-pos-fb, pos-fb, motor-offset, backlash and screw comp. All present.
The free mode plannersimple_tp.c is still there, and joint jogging still goes through it.
libnml's classesAll six described classes exist at the stated paths. The best-aged chapter of the document.
The toolchanger modelrandom versus nonrandom, copy versus swap: the description is still right.
Pocket 0 and T0 semanticsPocket 0 = spindle; T0 = “no tool” under nonrandom only. Unchanged.
FREE — deferred switchingThe document explains the handler does not make the transition but requests it by clearing two flags. That is exactly what the code does.
PAUSE and RESUMEtpPause() / tpResume() and the paused flag: conform to the description, with no hidden condition.
DISABLEAlways accepted, effect deferred to the controller cycle — unlike its counterpart ENABLE.
ENABLE's kinematics clause“If forward kinematics are not available, switch to free mode”: the KINEMATICS_INVERSE_ONLY test is still there.

The one I expected to correct, and could not

The per-joint cubic interpolator looked like the obvious candidate for removal — many modern planners emit the setpoint directly at the servo rate. On checking, cubic.c is still compiled and control.c still calls cubicDrain() on every joint. The original diagram was right about this.

Scope — what remains unverified

Two earlier limits have since been lifted: the delegate functions were followed (switch_to_teleop_mode() yielded erratum 24, the pausing flag yielded erratum 25), and the 57 undocumented commands are now written up in motion-commands-reference.md. The chapters “Backlash and Screw Error Compensation” and “User Interfaces” turned out to be single-line FIXMEs — empty; “Task controller — State” and “Reckoning of joints and axes” check out (the latter with one stale bug pointer, erratum 22).

Still unexamined: “Terms and definitions”, “Adding custom NML commands”, the tool G-codes (T, M6, G43, G10 L1, M61, G41/G42) and the internal state variables. The G-code reference audit is a sample — five families out of a 2 786-line document. Other files under docs/src/code/ — style guide, building, writing tests — are out of scope.

Sources

References

Document auditeddocs/src/code/code-notes.adoc · LinuxCNC-block-diagram-small.png · emc2-motion-joint-controller-block-diag.png
Task ↔ motion linksrc/emc/motion/motion_struct.h · motion.h · usrmotintf.h
EMCIO removalsrc/emc/task/Submakefile · src/emc/task/taskclass.cc
HALsrc/hal/hal_priv.h · src/hal/hal_lib.c
PID outside motionsrc/emc/motion/control.c:180 · src/hal/components/pid.c
Joints and axessrc/emc/motion/emcmotcfg.h
Spindle in real timesrc/emc/motion/motion.c:709-734
Loadable modulessrc/emc/tp/tpmod.c · src/emc/motion/homemod.c · src/emc/tp/cruckig/
RTAPI flavourssrc/rtapi/uspace_posix.cc · rtai_rtapi.c
Queuessrc/emc/nml_intf/interpl.hh · src/emc/tp/tcq.h
Joint controllersrc/emc/motion/homing.c:254 · motion.c · simple_tp.c
Command listsrc/emc/motion/motion.h — cmd_code_t
Tool tablesrc/emc/tooldata/ — mmap, db, nml · taskclass.cc:141-147
CMS buffer typessrc/libnml/cms/cms_cfg.cc · src/libnml/buffer/
Command semanticssrc/emc/motion/command.c — 492, 583, 618, 702, 796, 1230, 1252, 1261, 1366
2020 self-auditdocs/src/code/code-notes.adoc:242-246
Deeper reads (22–25)initraj.cc:203-205 · motion.c:173-178 · tp.c:243-252, 2782-2787
G-code reference (26–27)docs/src/gcode/g-code.adoc vs interp_check.cc:375-378 · interp_convert.cc:5496-5529, 5087, 2221
Phantom-error oraclesrs274ngc_return.hh · all _() strings · 26 .po catalogs
Prepared fixesupstream/0001…0003.patch · motion-commands-reference.md