Enforce AXIS source parity across panel controls

This commit is contained in:
2026-09-02 23:46:00 +08:00
parent 8b7fc5221b
commit 03c7fdf8f0
10 changed files with 357 additions and 14 deletions

View File

@@ -549,6 +549,9 @@ export class SimSession {
});
if (result === null) return { action, enabled: false, reason: "linuxcnc_wasm_gate_unavailable", taskState: task.state, interpState: "UNKNOWN" };
let reason = result === 0 ? "linuxcnc_wasm_authority" : WASM_GATE_REASONS[result] ?? "linuxcnc_wasm_gate_unknown_command";
if (input.kind === "PROGRAM_RUN" && Number(program.status) === 5) {
return { action, enabled: false, reason: "xyzac_program_aborted_requires_restart", taskState: task.state, interpState: "IDLE", wasm: { io_ready: ready, io_estop: estop, program_status: program.program_status, queue_depth: wasmMotion.state.queue_depth, gate_result: result } };
}
if (result === 2 && input.kind === "POWER" && input.payload?.active !== true) reason = "xyzac_power_already_off";
if (result === 3 && input.kind === "POWER") reason = "xyzac_power_already_on";
if (result === 4 && input.kind === "MDI_EXECUTE") reason = "mdi_while_program_active";
@@ -1953,7 +1956,14 @@ export class SimSession {
if (result.status === "verified") this.xyzacProgram.setRapidVelocity(this.xyzacRapidVelocity);
if (result.status === "verified") { const subroutines = this.loadXyzacSubroutines(input.payload?.path); if (subroutines.status !== "verified") return finish(this.error(input, "program_subroutine", subroutines.code), "failed"); }
const preview = result.status === "verified" ? this.xyzacProgram.preview() : result;
if (result.status === "verified" && preview.status === "verified") { this.previewState = preview; this.task.setProgram("loaded", 0); }
if (result.status === "verified" && preview.status === "verified") {
this.previewState = preview;
// AXIS open_file_guts()/reload_file() leaves the task usable for
// AUTO single-step after an earlier abort. Recover the task edge
// before publishing the newly loaded interpreter state.
if (this.task.state.state === "STOPPED") this.task.recover();
this.task.setProgram("loaded", 0);
}
const finalStatus = result.status === "verified" && preview.status === "verified" ? "verified" : "error";
return finish(this.envelope(finalStatus === "verified" ? "COMMAND_RESULT" : "ERROR", input, { ...result, preview, program: this.xyzacProgram.snapshot(), task: this.task.export(), motion_authority: "wasm", command: input.kind, operation_id: input.operation_id ?? null }), finalStatus);
}
@@ -2116,6 +2126,13 @@ export class SimSession {
if (current.status === 1) this.xyzacProgram.start();
const result = this.xyzacProgram.step();
if (result.status === "verified") {
// AXIS AUTO_STEP returns to an interpreter-paused state after the
// single canonical block. Keep the WASM interpreter status and
// task status aligned so the Resume gate is authoritative.
if (result.program_status !== 4) {
const pauseResult = this.xyzacProgram.pause();
if (pauseResult.status !== "verified") return finish(this.error(input, "program_pause", pauseResult.code ?? "program_pause_failed"), "failed");
}
if (this.task.state.state === "READY") this.task.transition("RUNNING");
if (result.motion) this.wasmMotion.enqueue(result.motion);
this.wasmMotion.setPaused(true);
@@ -2175,6 +2192,11 @@ export class SimSession {
const block = input.payload?.block ?? input.payload?.command;
if (this.xyzacProgram) {
this.task.setMode("MDI");
// AXIS send_mdi_command() leaves the interpreter/TP runnable after
// a previous manual jog stop. JOG_STOP intentionally pauses the
// WASM TP, so clear that transport pause before queuing MDI motion;
// the queue itself remains authoritative for execution.
this.wasmMotion.setPaused(false);
const result = this.xyzacProgram.executeBlock(block);
if (result.status === "verified") {
const controlCode = Number(result.program?.control_code ?? 0);
@@ -2233,7 +2255,11 @@ export class SimSession {
if (this.motionAuthority === "wasm") {
if (input.kind === "JOG_STOP") {
this.wasmMotion.abort();
this.wasmMotion.setPaused(true);
// AXIS jog_off() stops the active jog, but it does not leave the
// task/TP in AUTO_PAUSE. Keep the LinuxCNC TP runnable so an
// already queued servo sample, MDI block, or spindle/IO command
// cannot surface a synthetic linuxcnc_tp_paused scheduler error.
this.wasmMotion.setPaused(false);
return finish(this.envelope("COMMAND_RESULT", input, { status: "verified", command: input.kind, motion: this.wasmMotionProjection(), motion_authority: "wasm", operation_id: input.operation_id ?? null }), "verified");
}
if (input.kind === "SET_OVERRIDE") {