diff --git a/wasm-port/docs/compatibility-validation.md b/wasm-port/docs/compatibility-validation.md index c4e1f1c..32e34cd 100644 --- a/wasm-port/docs/compatibility-validation.md +++ b/wasm-port/docs/compatibility-validation.md @@ -25,7 +25,7 @@ The native validation script runs these checks in order: 3. `tools/verify_no_standalone_cnc_semantics.sh` Confirms standalone code has not reintroduced `Interp::convert_g()`. 4. `tools/verify_native_linuxcnc_fixture_baseline.sh` - Runs a narrow side-by-side fixture baseline through upstream + Runs a side-by-side fixture baseline through upstream `../linuxcnc/bin/rs274` and compares normalized canonical events for fixtures that do not require standalone-only runtime adapters. 5. `tools/build_native_probes.sh` @@ -108,8 +108,9 @@ Current validation is native-only. WASM, browser, SDK, OPFS, and full machine-session validation remain future work. The current fixture expectations validate standalone behavior against both the -vendored LinuxCNC source path and a first upstream `rs274` side-by-side -baseline for simple parser/conversion fixtures. Fixtures that depend on +vendored LinuxCNC source path and an upstream `rs274` side-by-side baseline for +parser/conversion, offsets, feed-control, comment/logging, numbered-parameter, +spindle-orient, file-finish, and tool-reload fixtures. Fixtures that depend on standalone-only runtime adapters, HAL/INI/tool table setup, or richer machine session state still need dedicated native LinuxCNC baselines. diff --git a/wasm-port/docs/drift-report.md b/wasm-port/docs/drift-report.md index 56f3ede..fcf3921 100644 --- a/wasm-port/docs/drift-report.md +++ b/wasm-port/docs/drift-report.md @@ -66,9 +66,10 @@ semantic rewrites: now have standalone interpreter fixture coverage through vendored LinuxCNC source. - Fixture expectations are checked against the standalone vendored source - path. A first upstream `rs274` side-by-side baseline now covers simple - parser/conversion fixtures; adapter-heavy fixtures still need dedicated - native LinuxCNC baselines. + path. The upstream `rs274` side-by-side baseline now covers simple motion, + offsets, feed-control, comment/logging, numbered-parameter, spindle-orient, + file-finish, and tool-reload fixtures; adapter-heavy fixtures still need + dedicated native LinuxCNC baselines. ## Current Drift Conclusion diff --git a/wasm-port/docs/porting-steps-standalone.md b/wasm-port/docs/porting-steps-standalone.md index d8f5e47..f22e31e 100644 --- a/wasm-port/docs/porting-steps-standalone.md +++ b/wasm-port/docs/porting-steps-standalone.md @@ -502,12 +502,13 @@ Current verified progress: 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 a first +- `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 parser/conversion fixtures - (`minimal_linear`, `length_units`, `plane_selection`, and - `modal_incremental`) and compares it with the standalone fixture events, - without introducing a project-authored CNC semantics oracle. + native LinuxCNC canonical output for simple motion, offsets, feed-control, + comment/logging, numbered-parameter, spindle-orient, file-finish, and + tool-reload fixtures, filters native output to the event classes each + standalone fixture explicitly expects, and compares the result without + introducing 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 diff --git a/wasm-port/tools/verify_native_linuxcnc_fixture_baseline.sh b/wasm-port/tools/verify_native_linuxcnc_fixture_baseline.sh index e5b11d0..58c10eb 100755 --- a/wasm-port/tools/verify_native_linuxcnc_fixture_baseline.sh +++ b/wasm-port/tools/verify_native_linuxcnc_fixture_baseline.sh @@ -8,9 +8,19 @@ CANON_FIXTURE_DIR="$ROOT_DIR/tests/fixtures/canon" FIXTURES=( minimal_linear + comment_logging + coordinate_offsets + cutter_comp_motion + feed_control_modes + g53_machine_coordinates length_units plane_selection modal_incremental + numbered_params + percent_file_finish + position_params + spindle_orient + tool_reload ) if [[ ! -x "$UPSTREAM_RS274" ]]; then @@ -31,6 +41,31 @@ normalize_standalone_events() { line = $0 sub(/^canon_event=/, "", line) + if (line ~ /^COMMENT: /) { + sub(/^COMMENT: /, "", line) + print "COMMENT|" line + next + } + + if (line ~ /^MESSAGE: /) { + sub(/^MESSAGE: /, "", line) + print "MESSAGE|" line + next + } + + if (line ~ /^LOG(OPEN|APPEND)?: /) { + op = line + sub(/:.*/, "", op) + sub(/^[^:]+: /, "", line) + print op "|" line + next + } + + if (line == "LOGCLOSE" || line == "FINISH" || line == "RELOAD_TOOLDATA") { + print line + next + } + if (line ~ /^SELECT_PLANE plane=/) { value = line sub(/^SELECT_PLANE plane=/, "", value) @@ -55,6 +90,39 @@ normalize_standalone_events() { next } + if (line ~ /^SET_G5X_OFFSET index=/) { + split(line, fields, /[[:space:]]+/) + for (i in fields) { + split(fields[i], pair, "=") + values[pair[1]] = pair[2] + } + print "SET_G5X_OFFSET|" values["index"] "|" norm(values["x"]) "|" \ + norm(values["y"]) "|" norm(values["z"]) "|" norm(values["a"]) "|" \ + norm(values["b"]) "|" norm(values["c"]) + delete values + next + } + + if (line ~ /^SET_G92_OFFSET /) { + split(line, fields, /[[:space:]]+/) + for (i in fields) { + split(fields[i], pair, "=") + values[pair[1]] = pair[2] + } + print "SET_G92_OFFSET|" norm(values["x"]) "|" norm(values["y"]) "|" \ + norm(values["z"]) "|" norm(values["a"]) "|" norm(values["b"]) "|" \ + norm(values["c"]) + delete values + next + } + + if (line ~ /^SET_XY_ROTATION rotation=/) { + value = line + sub(/^SET_XY_ROTATION rotation=/, "", value) + print "SET_XY_ROTATION|" norm(value) + next + } + if (line ~ /^SET_FEED_RATE rate=/) { value = line sub(/^SET_FEED_RATE rate=/, "", value) @@ -62,6 +130,64 @@ normalize_standalone_events() { next } + if (line ~ /^SET_FEED_MODE /) { + split(line, fields, /[[:space:]]+/) + for (i in fields) { + split(fields[i], pair, "=") + values[pair[1]] = pair[2] + } + print "SET_FEED_MODE|" values["spindle"] "|" values["mode"] + delete values + next + } + + if (line ~ /^SET_MOTION_CONTROL_MODE /) { + split(line, fields, /[[:space:]]+/) + for (i in fields) { + split(fields[i], pair, "=") + values[pair[1]] = pair[2] + } + print "SET_MOTION_CONTROL_MODE|" values["mode"] "|" norm(values["tolerance"]) + delete values + next + } + + if (line ~ /^SET_NAIVECAM_TOLERANCE tolerance=/) { + value = line + sub(/^SET_NAIVECAM_TOLERANCE tolerance=/, "", value) + print "SET_NAIVECAM_TOLERANCE|" norm(value) + next + } + + if (line ~ /^DWELL seconds=/) { + value = line + sub(/^DWELL seconds=/, "", value) + print "DWELL|" norm(value) + next + } + + if (line ~ /^ORIENT_SPINDLE /) { + split(line, fields, /[[:space:]]+/) + for (i in fields) { + split(fields[i], pair, "=") + values[pair[1]] = pair[2] + } + print "ORIENT_SPINDLE|" values["spindle"] "|" norm(values["orientation"]) "|" values["mode"] + delete values + next + } + + if (line ~ /^WAIT_SPINDLE_ORIENT_COMPLETE /) { + split(line, fields, /[[:space:]]+/) + for (i in fields) { + split(fields[i], pair, "=") + values[pair[1]] = pair[2] + } + print "WAIT_SPINDLE_ORIENT_COMPLETE|" values["spindle"] "|" norm(values["timeout"]) + delete values + next + } + if (line ~ /^STRAIGHT_(TRAVERSE|FEED) /) { split(line, fields, /[[:space:]]+/) for (i in fields) { @@ -84,11 +210,16 @@ normalize_upstream_rs274_output() { return sprintf("%.12g", value + 0) } + function trim(value) { + sub(/^[[:space:]]+/, "", value) + sub(/[[:space:]]+$/, "", value) + return value + } + function args_of(call, args) { args = call sub(/^[^(]+\(/, "", args) sub(/\).*$/, "", args) - gsub(/[[:space:]]/, "", args) return args } @@ -105,6 +236,39 @@ normalize_upstream_rs274_output() { next } + if (line ~ /^COMMENT\("/) { + value = args_of(line) + sub(/^"/, "", value) + sub(/"$/, "", value) + print "COMMENT|" value + next + } + + if (line ~ /^MESSAGE\("/) { + value = args_of(line) + sub(/^"/, "", value) + sub(/"$/, "", value) + print "MESSAGE|" value + next + } + + if (line ~ /^LOG(OPEN|APPEND)?\("/) { + op = line + sub(/\(.*/, "", op) + value = args_of(line) + sub(/^"/, "", value) + sub(/"$/, "", value) + print op "|" value + next + } + + if (line == "LOGCLOSE()" || line == "FINISH()" || line == "RELOAD_TOOLDATA()") { + value = line + sub(/\(\)$/, "", value) + print value + next + } + if (line ~ /^SELECT_PLANE\(CANON_PLANE_/) { value = line sub(/^SELECT_PLANE\(CANON_PLANE_/, "", value) @@ -121,12 +285,86 @@ normalize_upstream_rs274_output() { next } + if (line ~ /^SET_G5X_OFFSET\(/) { + args = args_of(line) + split(args, values, ",") + print "SET_G5X_OFFSET|" trim(values[1]) "|" norm(values[2]) "|" norm(values[3]) "|" \ + norm(values[4]) "|" norm(values[5]) "|" norm(values[6]) "|" norm(values[7]) + next + } + + if (line ~ /^SET_G92_OFFSET\(/) { + args = args_of(line) + split(args, values, ",") + print "SET_G92_OFFSET|" norm(values[1]) "|" norm(values[2]) "|" norm(values[3]) "|" \ + norm(values[4]) "|" norm(values[5]) "|" norm(values[6]) + next + } + + if (line ~ /^SET_XY_ROTATION\(/) { + args = args_of(line) + print "SET_XY_ROTATION|" norm(args) + next + } + if (line ~ /^SET_FEED_RATE\(/) { args = args_of(line) print "SET_FEED_RATE|" norm(args) next } + if (line ~ /^SET_FEED_MODE\(/) { + args = args_of(line) + split(args, values, ",") + print "SET_FEED_MODE|" trim(values[1]) "|" trim(values[2]) + next + } + + if (line ~ /^SET_MOTION_CONTROL_MODE\(/) { + args = args_of(line) + split(args, values, ",") + mode_name = trim(values[1]) + if (mode_name == "CANON_EXACT_STOP") { + mode = 1 + } else if (mode_name == "CANON_EXACT_PATH") { + mode = 2 + } else if (mode_name == "CANON_CONTINUOUS") { + mode = 3 + } else { + next + } + print "SET_MOTION_CONTROL_MODE|" mode "|" norm(values[2]) + next + } + + if (line ~ /^SET_NAIVECAM_TOLERANCE\(/) { + args = args_of(line) + print "SET_NAIVECAM_TOLERANCE|" norm(args) + next + } + + if (line ~ /^DWELL\(/) { + args = args_of(line) + print "DWELL|" norm(args) + next + } + + if (line ~ /^ORIENT_SPINDLE\(/) { + args = args_of(line) + split(args, values, ",") + print "ORIENT_SPINDLE|" trim(values[1]) "|" norm(values[2]) "|" trim(values[3]) + next + } + + if (line ~ /^SPINDLE\.[0-9]+\.WAIT_ORIENT_COMPLETE\(/) { + spindle = line + sub(/^SPINDLE\./, "", spindle) + sub(/\.WAIT_ORIENT_COMPLETE.*/, "", spindle) + args = args_of(line) + print "WAIT_SPINDLE_ORIENT_COMPLETE|" spindle "|" norm(args) + next + } + if (line ~ /^STRAIGHT_(TRAVERSE|FEED)\(/) { op = line sub(/\(.*/, "", op) @@ -140,11 +378,33 @@ normalize_upstream_rs274_output() { ' } +filter_to_expected_event_keys() { + local expected_norm="$1" + + awk ' + NR == FNR { + key = $0 + sub(/\|.*/, "", key) + keys[key] = 1 + next + } + + { + key = $0 + sub(/\|.*/, "", key) + if (key in keys) { + print + } + } + ' "$expected_norm" - +} + for name in "${FIXTURES[@]}"; do gcode_file="$GCODE_FIXTURE_DIR/$name.ngc" expected_file="$CANON_FIXTURE_DIR/$name.events" native_raw="$TMP_DIR/$name.rs274.raw" native_norm="$TMP_DIR/$name.rs274.normalized" + native_filtered="$TMP_DIR/$name.rs274.filtered" expected_norm="$TMP_DIR/$name.expected.normalized" if [[ ! -f "$gcode_file" ]]; then @@ -170,14 +430,15 @@ for name in "${FIXTURES[@]}"; do normalize_upstream_rs274_output < "$native_raw" > "$native_norm" normalize_standalone_events < "$expected_file" > "$expected_norm" + filter_to_expected_event_keys "$expected_norm" < "$native_norm" > "$native_filtered" - if [[ ! -s "$native_norm" ]]; then + if [[ ! -s "$native_filtered" ]]; then echo "no normalized upstream rs274 events for fixture: $name" >&2 sed -n '1,160p' "$native_raw" >&2 exit 1 fi - if ! diff -u "$expected_norm" "$native_norm"; then + if ! diff -u "$expected_norm" "$native_filtered"; then echo "native LinuxCNC rs274 baseline mismatch for fixture: $name" >&2 exit 1 fi