From 2d6d8bd1c2b91338751dfbd4a1944d274e9d1f0a Mon Sep 17 00:00:00 2001 From: wangdequan Date: Sun, 30 Aug 2026 06:34:59 +0800 Subject: [PATCH] Keep long G-code execution running past full planner queue --- web/worker/replay.mjs | 8 ++++---- web/worker/sim-session.mjs | 10 +++++++++- web/worker/test_xyzac_3d_chips.mjs | 9 +++++++-- web/worker/xyzac-command-ledger.mjs | 5 +++-- web/worker/xyzac-session.mjs | 15 +++++++++------ 5 files changed, 32 insertions(+), 15 deletions(-) diff --git a/web/worker/replay.mjs b/web/worker/replay.mjs index 3feba6f..e976c34 100644 --- a/web/worker/replay.mjs +++ b/web/worker/replay.mjs @@ -2,18 +2,18 @@ const EVENT_SCHEMA = "event-log.v1"; const SNAPSHOT_SCHEMA = "snapshot.v1"; const REPLAY_SCHEMA = "replay-timeline.v1"; -export function canonicalJson(value) { +export function canonicalJson(value, path = "$") { if (value === null) return "null"; if (typeof value === "number") { if (!Number.isFinite(value)) throw new Error("canonical_nonfinite"); return JSON.stringify(value); } if (typeof value !== "object") { - if (typeof value === "undefined" || typeof value === "function" || typeof value === "symbol") throw new Error("canonical_unsupported"); + if (typeof value === "undefined" || typeof value === "function" || typeof value === "symbol") throw new Error(`canonical_unsupported:${path}`); return JSON.stringify(value); } - if (Array.isArray(value)) return `[${value.map(canonicalJson).join(",")}]`; - return `{${Object.keys(value).sort().map((key) => `${JSON.stringify(key)}:${canonicalJson(value[key])}`).join(",")}}`; + if (Array.isArray(value)) return `[${value.map((item, index) => canonicalJson(item, `${path}[${index}]`)).join(",")}]`; + return `{${Object.keys(value).sort().map((key) => `${JSON.stringify(key)}:${canonicalJson(value[key], `${path}.${key}`)}`).join(",")}}`; } export function fnv1a64(value) { diff --git a/web/worker/sim-session.mjs b/web/worker/sim-session.mjs index f575ec2..937017f 100644 --- a/web/worker/sim-session.mjs +++ b/web/worker/sim-session.mjs @@ -151,7 +151,10 @@ export class SimSession { const lightweightXyzacStep = input.kind === "SIM_STEP" && Boolean(this.xyzacProgram); const currentMotion = this.wasmMotion.read(); const completedMotion = program.status === 4 && currentMotion.state?.queue_depth === 0; - const motionDelta = lightweightXyzacStep && input.payload?.response === "motion_delta" && !completedMotion; + // Compact payloads are only valid for successful motion samples. Error + // responses must retain their code/reason instead of being rewritten into + // a delta with undefined fields. + const motionDelta = kind === "COMMAND_RESULT" && lightweightXyzacStep && input.payload?.response === "motion_delta" && !completedMotion; const panelSnapshot = motionDelta ? null : this.panelSnapshot({ task, program, input, tick, motionTrace: statePayload.motion_trace, includeToolpath: !lightweightXyzacStep, includeConfiguration: !lightweightXyzacStep, includeStableRuntime: !lightweightXyzacStep }); const compactProgram = motionDelta ? { schema: program.schema, @@ -1415,6 +1418,11 @@ export class SimSession { if (this.xyzacProgram) { const wasmState = this.xyzacProgram.snapshot(); if (![2, 3].includes(wasmState.status)) return; + // LinuxCNC's task/interpreter blocks on a full trajectory + // planner queue. Let the motion phase consume one segment + // before reading another source line instead of converting + // normal planner backpressure into a program fault. + if (this.wasmMotion.read().state.queue_depth >= this.wasmMotion.queueCapacity) return; if (this.pendingProgramControl !== 0) { if (this.wasmMotion.read().state.queue_depth !== 0) return; const current = this.wasmMotion.read().state.position; diff --git a/web/worker/test_xyzac_3d_chips.mjs b/web/worker/test_xyzac_3d_chips.mjs index 7f153ad..63b3a8a 100644 --- a/web/worker/test_xyzac_3d_chips.mjs +++ b/web/worker/test_xyzac_3d_chips.mjs @@ -44,13 +44,18 @@ assert.equal(send("POWER", { active: true }).kind, "COMMAND_RESULT"); assert.equal(send("HOME", { all: true }).kind, "COMMAND_RESULT"); const run = send("PROGRAM_RUN"); assert.equal(run.kind, "COMMAND_RESULT"); -const stepped = send("SIM_STEP", { cycles: 16, response: "motion_delta" }); +let stepped = null; +for (let cycle = 0; cycle < 300; cycle += 1) { + stepped = send("SIM_STEP", { cycles: 1, response: "motion_delta" }); + assert.equal(stepped.kind, "COMMAND_RESULT"); +} assert.equal(stepped.kind, "COMMAND_RESULT"); assert.equal(stepped.payload.program.program_status, "running"); -assert.ok(stepped.payload.program.current_line > 0); +assert.ok(stepped.payload.program.current_line > 82); assert.ok(stepped.payload.execution_trace.length > 0); assert.ok(stepped.payload.program.execution_line_text.length > 0); assert.equal(stepped.payload.motion_authority, "wasm"); +assert.equal(Object.hasOwn(stepped.payload.xyzac_ledger.entries.at(-1), "response"), false); console.log(JSON.stringify({ schema: "xyzac-3d-chips-program.v1", diff --git a/web/worker/xyzac-command-ledger.mjs b/web/worker/xyzac-command-ledger.mjs index 317f3ed..321856b 100644 --- a/web/worker/xyzac-command-ledger.mjs +++ b/web/worker/xyzac-command-ledger.mjs @@ -45,10 +45,11 @@ export class XyzacCommandLedger extends OperationLedger { return { status: "reconcile_required", operation_id: operationId, retry_allowed: false, reconciliation }; } - audit() { + audit({ includeResponses = true } = {}) { const exported = super.export(); const pending = this.pending(); - const unsigned = { schema: XYZAC_COMMAND_LEDGER_SCHEMA, operation_schema: LEDGER_SCHEMA, phases: [...LEDGER_PHASES], scope: this.scope, entries: exported.entries, pending_count: pending.length }; + const entries = includeResponses ? exported.entries : exported.entries.map((entry) => { const { response, ...summary } = entry; return summary; }); + const unsigned = { schema: XYZAC_COMMAND_LEDGER_SCHEMA, operation_schema: LEDGER_SCHEMA, phases: [...LEDGER_PHASES], scope: this.scope, entries, pending_count: pending.length }; return { ...unsigned, state_hash: fnv1a64(unsigned) }; } } diff --git a/web/worker/xyzac-session.mjs b/web/worker/xyzac-session.mjs index 381c2d9..7ad8cbd 100644 --- a/web/worker/xyzac-session.mjs +++ b/web/worker/xyzac-session.mjs @@ -28,12 +28,15 @@ export class XyzacSession extends SimSession { accept(input) { const response = super.accept(input); - if (response?.payload && typeof response.payload === "object") { - response.payload.xyzac_session = this.authority(); - response.payload.xyzac_ledger = this.ledger.audit(); - response.payload.xyzac_scheduler = this.scheduler.export(); - } - return response; + if (!response?.payload || typeof response.payload !== "object") return response; + // super.accept stores the response in the operation ledger. Enrich a + // shallow client copy so the next ledger audit cannot recursively embed + // every previous response inside the stored response object. + const payload = { ...response.payload }; + payload.xyzac_session = this.authority(); + payload.xyzac_ledger = this.ledger.audit({ includeResponses: input.kind !== "SIM_STEP" }); + payload.xyzac_scheduler = this.scheduler.export(); + return { ...response, payload }; } }