Unblock native XYZAC TP oracle
This commit is contained in:
145
scripts/run_xyzac_wasm_tick_trace.mjs
Normal file
145
scripts/run_xyzac_wasm_tick_trace.mjs
Normal file
@@ -0,0 +1,145 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import { readFile, writeFile } from "node:fs/promises";
|
||||
import { createHash } from "node:crypto";
|
||||
import { join, resolve } from "node:path";
|
||||
import createModule from "../build-wasm/linuxcnc-sim-core.js";
|
||||
import { loadSimulationCase, toWorkerConfiguration } from "../web/workspace/sim-case-loader.mjs";
|
||||
import { XyzacSession, CONTEXT_ID } from "../web/worker/xyzac-session.mjs";
|
||||
|
||||
const ROOT = resolve(process.cwd());
|
||||
const CASE = "linuxcnc/configs/sim/axis/vismach/5axis/table-rotary-tilting/xyzac-trt.ini";
|
||||
const PROGRAM = "linuxcnc/configs/sim/axis/vismach/5axis/table-rotary-tilting/demos/xyzac_switchkins.ngc";
|
||||
const SCHEMA = "xyzac-wasm-linuxcnc-tick-trace.v1";
|
||||
const AXES = ["x", "y", "z", "a", "b", "c", "u", "v", "w"];
|
||||
|
||||
const sha256 = (value) => createHash("sha256").update(value).digest("hex");
|
||||
const canonical = (value) => JSON.stringify(value, Object.keys(value).sort());
|
||||
const poseArray = (pose) => AXES.map((axis) => Number(pose?.[axis] ?? 0));
|
||||
const modeCode = (mode) => ({ MANUAL: 1, AUTO: 2, MDI: 3 }[mode] ?? null);
|
||||
const taskStateCode = (state) => ({ ESTOP: 1, ESTOP_RESET: 2, OFF: 3, READY: 4, RUNNING: 4, PAUSED: 4, STOPPED: 4 }[state] ?? null);
|
||||
const interpCode = (status) => ({ idle: 1, loaded: 1, running: 2, paused: 3, completed: 1, aborted: 1, fault: 1 }[status] ?? null);
|
||||
const execCode = (status, queueDepth) => (Number(queueDepth) > 0 ? 3 : ({ running: 2, paused: 2, loaded: 2, completed: 2, aborted: 2, idle: 2, fault: 4 }[status] ?? null));
|
||||
|
||||
function rowFrom(state, task, program, tick) {
|
||||
const position = state?.position ?? {};
|
||||
const motion = task?.motion ?? {};
|
||||
const visual = task?.visual ?? {};
|
||||
const limits = motion.limits ?? {};
|
||||
const joints = Array.isArray(visual.joints) ? visual.joints : Array.isArray(motion.joints) ? motion.joints : [];
|
||||
const homedAxes = Array.isArray(motion.homed_axes) ? motion.homed_axes : [];
|
||||
return {
|
||||
sample_index: tick,
|
||||
taskbeat: tick,
|
||||
task_heartbeat: tick,
|
||||
taskbeat_changed: true,
|
||||
task_state: taskStateCode(task?.state),
|
||||
task_mode: modeCode(task?.mode),
|
||||
interp_state: interpCode(program?.program_status),
|
||||
exec_state: execCode(program?.program_status, state?.queue_depth),
|
||||
current_line: Number(program?.current_line ?? 0),
|
||||
motion_line: Number(program?.current_line ?? 0),
|
||||
read_line: Number(program?.current_line ?? 0),
|
||||
actual_pose: poseArray(position),
|
||||
commanded_pose: poseArray(position),
|
||||
joint_actual: joints,
|
||||
joint_commanded: joints,
|
||||
velocity: Number(state?.velocity ?? 0),
|
||||
acceleration: null,
|
||||
feed_override: Number(motion.feed_override ?? 1),
|
||||
rapid_override: 1,
|
||||
feed_override_enabled: true,
|
||||
queue_depth: Number(state?.queue_depth ?? 0),
|
||||
active_queue: null,
|
||||
queue_full: null,
|
||||
motion_id: Number(state?.active_id ?? 0),
|
||||
active_segment_id: Number(state?.active_id ?? 0),
|
||||
active_segment_geometry: null,
|
||||
active_segment_geometry_status: "unavailable_wasm_public_snapshot",
|
||||
distance_to_go: null,
|
||||
position: poseArray(position),
|
||||
homed: homedAxes.map((value) => (value ? 1 : 0)),
|
||||
limits: ["x", "y", "z", "a", "b", "c", "u", "v", "w"].map((axis) => limits[axis] ? 1 : 0),
|
||||
kinematics_type: 4,
|
||||
motion_type: Number(state?.motion_type ?? 0),
|
||||
feed_hold_enabled: null,
|
||||
paused: program?.program_status === "paused",
|
||||
wasm_tick: tick,
|
||||
};
|
||||
}
|
||||
|
||||
const fetchImpl = async (url) => {
|
||||
try {
|
||||
const body = await readFile(join(ROOT, String(url).replace(/^\//, "")));
|
||||
return { ok: true, text: async () => body.toString("utf8") };
|
||||
} catch {
|
||||
return { ok: false, text: async () => "" };
|
||||
}
|
||||
};
|
||||
|
||||
async function run(maxTicks) {
|
||||
const bundle = await loadSimulationCase({ path: CASE, fetchImpl });
|
||||
if (bundle.status !== "ready") throw new Error(`case_load_${bundle.code ?? bundle.status}`);
|
||||
const source = bundle.files?.[PROGRAM];
|
||||
if (typeof source !== "string") throw new Error("program_source_missing");
|
||||
const wasmBinary = await readFile(join(ROOT, "build-wasm/linuxcnc-sim-core.wasm"));
|
||||
const core = await createModule({ wasmBinary });
|
||||
const session = new XyzacSession(core, { sessionId: "xyzac-native-wasm-oracle" });
|
||||
let seq = 0;
|
||||
const send = (kind, payload = {}) => session.accept({ schema_version: 1, context_id: CONTEXT_ID, session_id: session.sessionId, kind, seq: ++seq, operation_id: `oracle-${seq}`, payload });
|
||||
send("HELLO");
|
||||
send("SESSION_ATTACH");
|
||||
const loadedWorkspace = send("LOAD_WORKSPACE", { root_path: "/workspace", configuration: toWorkerConfiguration(bundle) });
|
||||
if (loadedWorkspace.payload?.status !== "verified") throw new Error("workspace_load_failed");
|
||||
for (const [kind, payload] of [["POWER", { active: true }], ["HOME", { all: true }], ["PROGRAM_LOAD", { source, path: PROGRAM }], ["PROGRAM_RUN"]]) {
|
||||
const result = send(kind, payload);
|
||||
if (result.payload?.status !== "verified") throw new Error(`${kind.toLowerCase()}_${result.payload?.code ?? "failed"}`);
|
||||
}
|
||||
const rows = [];
|
||||
let state = session.wasmMotion.read().state;
|
||||
let task = { ...session.task.export(), motion: session.motionProjection(), visual: { joints: state.joints } };
|
||||
let program = session.xyzacProgram.snapshot();
|
||||
rows.push(rowFrom(state, task, program, 0));
|
||||
for (let tick = 1; tick <= maxTicks; tick += 1) {
|
||||
const response = send("SIM_STEP", { cycles: 1 });
|
||||
if (response.payload?.status !== "verified") throw new Error(`sim_step_${response.payload?.code ?? "failed"}`);
|
||||
state = response.payload.wasm_motion?.state ?? session.wasmMotion.read().state;
|
||||
task = { ...response.payload.task, motion: response.payload.motion, visual: response.payload.panel_snapshot?.visual };
|
||||
program = response.payload.program ?? session.xyzacProgram.snapshot();
|
||||
rows.push(rowFrom(state, task, program, tick));
|
||||
if (program.program_status === "completed" && Number(state.queue_depth) === 0) break;
|
||||
}
|
||||
const traceHash = sha256(JSON.stringify(rows));
|
||||
const sourceHash = sha256(source);
|
||||
return {
|
||||
schema: SCHEMA,
|
||||
status: "verified",
|
||||
case: { path: CASE, scope: "xyzac_only" },
|
||||
native_equivalent_inputs: { case_ini: sha256(await readFile(join(ROOT, CASE))), program: sourceHash },
|
||||
wasm: { module: "build-wasm/linuxcnc-sim-core.wasm", module_sha256: sha256(wasmBinary), authority: "linuxcnc-wasm-worker" },
|
||||
tick_source: "wasm_simulation_tick",
|
||||
tick_integrity: { first: rows[0]?.wasm_tick ?? null, last: rows.at(-1)?.wasm_tick ?? null, row_count: rows.length, unique_count: new Set(rows.map((row) => row.wasm_tick)).size, strictly_monotonic: rows.every((row, index) => index === 0 || row.wasm_tick > rows[index - 1].wasm_tick) },
|
||||
rows,
|
||||
row_count: rows.length,
|
||||
trace_sha256: traceHash,
|
||||
errors: [],
|
||||
release_status: "STOP_RELEASE",
|
||||
};
|
||||
}
|
||||
|
||||
const args = process.argv.slice(2);
|
||||
const ticksIndex = args.indexOf("--ticks");
|
||||
const outputIndex = args.indexOf("--output");
|
||||
const maxTicks = Math.max(1, Math.min(100000, Number(ticksIndex >= 0 ? args[ticksIndex + 1] : 3000)));
|
||||
const output = outputIndex >= 0 ? resolve(args[outputIndex + 1]) : join(ROOT, "coverage/xyzac-wasm-tick-trace.report.json");
|
||||
try {
|
||||
const report = await run(maxTicks);
|
||||
const unsigned = { ...report, report_sha256: undefined };
|
||||
delete unsigned.report_sha256;
|
||||
report.report_sha256 = sha256(JSON.stringify(unsigned));
|
||||
await writeFile(output, `${JSON.stringify(report, null, 2)}\n`);
|
||||
console.log(JSON.stringify({ schema: report.schema, status: report.status, row_count: report.row_count, trace_sha256: report.trace_sha256, report_sha256: report.report_sha256 }));
|
||||
} catch (error) {
|
||||
console.error(JSON.stringify({ schema: SCHEMA, status: "blocked", error: String(error?.message ?? error) }));
|
||||
process.exitCode = 1;
|
||||
}
|
||||
Reference in New Issue
Block a user