diff --git a/coverage/xyzac-trt-tool-preview.report.json b/coverage/xyzac-trt-tool-preview.report.json index fbfc71d..b98851f 100644 --- a/coverage/xyzac-trt-tool-preview.report.json +++ b/coverage/xyzac-trt-tool-preview.report.json @@ -74,7 +74,7 @@ }, { "path": "web/worker/sim-session.mjs", - "sha256": "6ffad5f415dac511b85670475c61c821c5b1cecea2003d812123a7bc3a0bb16f" + "sha256": "4d6bce53441013bccd656eec4d365267f5547767e96c1de6d6e6946ef3a65e18" }, { "path": "web/worker/test_xyzac_exact_preview.mjs", @@ -102,11 +102,11 @@ }, { "path": "web/react/src/App.tsx", - "sha256": "d97cbe5f9d57e3071a43f0669bc9babd652a3ac347c4db226f749ee2d77fed10" + "sha256": "e8d8beee65031cdcb70e8c5ab5b59db90e6b93ee2ac229fc4894d6da07caaeed" }, { "path": "web/react/src/styles.css", - "sha256": "9f1bd2fe7e365bebfc88ccc382a1ca7268be4e3b4bc5a07a10714be491be0d39" + "sha256": "0c523d532688228e29bcad6b828733a2f6b542cdb544584264c2f8d20ac9fad4" }, { "path": "wasm-core/src/sim_task.c", @@ -383,7 +383,7 @@ } }, "release_status": "STOP_RELEASE", - "report_sha256": "b7b9b7f02688d1f3fe1f1dab5165cc3426f0cb99aa04f951f2a1af1a89840e71", + "report_sha256": "8fceb9e6f6af1089b405fd7b57aec455fc11b34ae5dc81b59448ae1469472a73", "schema": "xyzac-trt-tool-preview-report.v1", "status": "verified", "task_id": "RTCP-10-TOOL-PREVIEW", diff --git a/coverage/xyzac-trt-wasm-authority-audit.report.json b/coverage/xyzac-trt-wasm-authority-audit.report.json index 1e380de..22f9268 100644 --- a/coverage/xyzac-trt-wasm-authority-audit.report.json +++ b/coverage/xyzac-trt-wasm-authority-audit.report.json @@ -128,10 +128,10 @@ "source_slice": "AXIS-XYZAC-PROGRAM-MDI", "sources": [ { - "lines": "122-1003", + "lines": "122-1004", "path": "web/worker/sim-session.mjs", "role": "session-authority-routing", - "sha256": "6ffad5f415dac511b85670475c61c821c5b1cecea2003d812123a7bc3a0bb16f", + "sha256": "4d6bce53441013bccd656eec4d365267f5547767e96c1de6d6e6946ef3a65e18", "status": "resolved", "symbol": "new ProgramRunner" }, @@ -161,10 +161,10 @@ "symbol": "export class MotionState" }, { - "lines": "122-1003", + "lines": "122-1004", "path": "web/worker/sim-session.mjs", "role": "session-authority-routing", - "sha256": "6ffad5f415dac511b85670475c61c821c5b1cecea2003d812123a7bc3a0bb16f", + "sha256": "4d6bce53441013bccd656eec4d365267f5547767e96c1de6d6e6946ef3a65e18", "status": "resolved", "symbol": "new ProgramRunner" } @@ -186,10 +186,10 @@ "symbol": "export class AuthoritativeRuntime" }, { - "lines": "122-1003", + "lines": "122-1004", "path": "web/worker/sim-session.mjs", "role": "session-authority-routing", - "sha256": "6ffad5f415dac511b85670475c61c821c5b1cecea2003d812123a7bc3a0bb16f", + "sha256": "4d6bce53441013bccd656eec4d365267f5547767e96c1de6d6e6946ef3a65e18", "status": "resolved", "symbol": "new ProgramRunner" } @@ -205,7 +205,7 @@ "recovery": "Chromium SIGKILL/profile-reopen, corruption detection and Firefox OPFS round-trip are verified" }, "release_status": "STOP_RELEASE", - "report_sha256": "2b7f0ee5b74ba0b961f9679a5bfa156e361c9268cc14a89bf42d12776a22085a", + "report_sha256": "960537fe609868696cecacac63eb693adf11cc37406c7b098e653a914d0f5365", "scans": { "js_gcode_matches": [ { @@ -274,12 +274,12 @@ "text": "const result = call(this.core, \"sim_interpreter_execute\", \"number\", [\"number\", \"number\", \"number\"], [this.interpreter, this.blockBuffer, bytes.length]);" }, { - "line": 1736, + "line": 1744, "path": "web/worker/sim-session.mjs", "text": "const result = this.xyzacProgram.executeBlock(block);" }, { - "line": 1755, + "line": 1763, "path": "web/worker/sim-session.mjs", "text": "const result = this.program.executeBlock(block);" }, @@ -292,7 +292,7 @@ "opfs_writer_matches": [ { "line": 1, - "path": "web/react/dist/assets/sim-worker-entry-CP8X4Y-6.js", + "path": "web/react/dist/assets/sim-worker-entry-BnpVdnWv.js", "text": "(function(){var e=(()=>{var e=self.location.href;return(function(t){t||={};var t=t===void 0?{}:t,n,r;t.ready=new Promise(function(e,t){n=e,r=t});var i=Object.assign({},t),a=[],o=typeof window==`object`,s=typeof importScripts==`function`;typ" }, { @@ -1496,7 +1496,7 @@ "files": [ { "path": "web/react/src/App.tsx", - "sha256": "d97cbe5f9d57e3071a43f0669bc9babd652a3ac347c4db226f749ee2d77fed10" + "sha256": "e8d8beee65031cdcb70e8c5ab5b59db90e6b93ee2ac229fc4894d6da07caaeed" }, { "path": "web/react/src/ThreeMachineView.tsx", @@ -1548,7 +1548,7 @@ }, { "path": "web/react/src/store.ts", - "sha256": "899f8c4e7ce512b91375ed47289d4a9f2b5146504acde4e23d0587018d7ec766" + "sha256": "98a2634b0658d55bd1874c13196b7ac4ffffcdc9ff4246882b81147dad65523d" }, { "path": "web/react/src/test_xyzac_threejs.mjs", @@ -1656,7 +1656,7 @@ }, { "path": "web/worker/sim-session.mjs", - "sha256": "6ffad5f415dac511b85670475c61c821c5b1cecea2003d812123a7bc3a0bb16f" + "sha256": "4d6bce53441013bccd656eec4d365267f5547767e96c1de6d6e6946ef3a65e18" }, { "path": "web/worker/sim-task.mjs", @@ -2094,10 +2094,10 @@ "scope": "selected_xyzac_simulation_only", "sources": [ { - "lines": "122-1003", + "lines": "122-1004", "path": "web/worker/sim-session.mjs", "role": "session-authority-routing", - "sha256": "6ffad5f415dac511b85670475c61c821c5b1cecea2003d812123a7bc3a0bb16f", + "sha256": "4d6bce53441013bccd656eec4d365267f5547767e96c1de6d6e6946ef3a65e18", "status": "resolved", "symbol": "new ProgramRunner" }, diff --git a/coverage/xyzac-trt-web-wasm-parity.report.json b/coverage/xyzac-trt-web-wasm-parity.report.json index fd16f6c..de9a476 100644 --- a/coverage/xyzac-trt-web-wasm-parity.report.json +++ b/coverage/xyzac-trt-web-wasm-parity.report.json @@ -94,19 +94,19 @@ "coverage/xyzac-trt-gate-matrix.report.json": "3a0dd402abd95e79243e0f61e62819d595110ac6d1c3ea2a3b2251424718ba86", "coverage/xyzac-trt-hal-tool-alarm.report.json": "666fb1273c2471cd5869aea83e847a657309ee8cb4915713e25f4a99c94b3943", "coverage/xyzac-trt-manual-control.report.json": "bd38d4f20322ae3e246438740b3e35827ab2ac6b7b16b8cee22920d2c24c1847", - "coverage/xyzac-trt-program-preview.report.json": "c4db73f5ebe63e26921425d44c9e914ca453300a54f51b9da05d709a9e68cf15", - "coverage/xyzac-trt-protocol-react.report.json": "af78f0fd639fca95c5406b8afdcf5305225d97aba448637745bd5a941aa19689", + "coverage/xyzac-trt-program-preview.report.json": "b3352590c5e1e48c938808ae452a63dbcadc1a1f1e1fbd4f14f56305ae447e6e", + "coverage/xyzac-trt-protocol-react.report.json": "cd6e765f44d42c11c30b94e3a75b32448b95c82ebd4c79f47bb321f5ba3e08dd", "coverage/xyzac-trt-recovery.report.json": "96df470c39326eab1979bd86669fb894335185806015479d586711f67c19ee05", "coverage/xyzac-trt-rtcp.report.json": "b1562d6d2c9cd5144c7c8c9b334bd8bf67919ea7297f103ec67cb32773608d08", "coverage/xyzac-trt-source-slice-inventory.report.json": "bf5f3fd36e1943e0dc42bafb2ed195fad147bcb3e56a8d208d7f01a3641829de", "coverage/xyzac-trt-startup.report.json": "56e040b645bcefc3d6c57244cb2d3aba16f421ff5137089d9c3336970dfd968b", "coverage/xyzac-trt-threejs.report.json": "5172d7118c555fca5f92bcb443b9c259ea0225346482558d4c33ef8a4f8da5cb", - "coverage/xyzac-trt-tool-preview.report.json": "c1643894bf1f2f1e49f43052731bef461ceaaf307d170e4877ac251d16e37013", + "coverage/xyzac-trt-tool-preview.report.json": "1980f3dfad0471bdef5025aec230af5c5ba477ebd5ecccb9ada732d435465efd", "coverage/xyzac-trt-trace-parity.report.json": "6c6b3500bc1189ff68513b74f0e5144879c3af7641349cb61737191303b76950", - "coverage/xyzac-trt-wasm-authority-audit.report.json": "f0d1de5dc69141fc85756b37d4c2a69ed3a8071b43962a5bd549f6465d94f3de", + "coverage/xyzac-trt-wasm-authority-audit.report.json": "57af1a779dc5d48b40b4a8576b7949aecaab2fd2eeaae7dfbbab35c06a254102", "coverage/xyzac-trt-workflow.report.json": "9d0ed087b17ec14c906f4268d2d616dbc3800da03f731376b2ef9ae44ca5c6e7" }, - "report_sha256": "59b7792f766f45b0dfc2f59829d3932d8dfa75097c890ef58c41030583c11178", + "report_sha256": "03d305c986a03aaaf6e04833b8e0d4095d840f89741582cd50be307f1aeed73c", "schema": "xyzac-trt-web-wasm-parity-report.v1", "source_coverage": { "axis_keyboard": 10, diff --git a/web/react/src/App.tsx b/web/react/src/App.tsx index f2913f7..8ad9608 100644 --- a/web/react/src/App.tsx +++ b/web/react/src/App.tsx @@ -87,10 +87,15 @@ function DroView({ axes, state, coordType, displayType, metric, setCoordType, se return PositionCoordinate setCoordType(event.target.value as "machine" | "relative")}>MachineRelativeDisplay setDisplayType(event.target.value as "actual" | "commanded")}>ActualCommandedUnits setMetric(event.target.value === "mm")}>mminchDisplayedActualRelative{labels.map((label, index) => { const actual = Number(axes[index] ?? 0); const target = Number(commanded[index] ?? actual); const relativeValue = Number(relative[index] ?? actual); const displayed = displayType === "commanded" ? target : actual; return {label}{format(coordType === "relative" ? relativeValue : displayed, index)}{format(actual, index)}{format(relativeValue, index)}; })}Units: {metric ? "mm" : "inch"} / deg{coordType} / {displayType}Tick: {state.tick}Hash: {state.snapshot?.state_hash?.slice(0, 12) || "-"}; } -function ProgramPane({ text, setText, activeGcodes, onLoad, onRun, onStep, onOpenFile, canLoad, canRun, canStep, currentLine, executionLine, executionLineText, diagnostic }: { text: string; setText: (text: string) => void; activeGcodes: string; onLoad: () => void; onRun: () => void; onStep: () => void; onOpenFile: () => void; canLoad: boolean; canRun: boolean; canStep: boolean; currentLine: number | null; executionLine: number | null; executionLineText: string; diagnostic: string | null }) { +function ProgramPane({ text, setText, activeGcodes, executionTrace, onLoad, onRun, onStep, onOpenFile, canLoad, canRun, canStep, currentLine, executionLine, executionLineText, diagnostic }: { text: string; setText: (text: string) => void; activeGcodes: string; executionTrace: Array<{ line: number; text: string; program_status?: string | null }>; onLoad: () => void; onRun: () => void; onStep: () => void; onOpenFile: () => void; canLoad: boolean; canRun: boolean; canStep: boolean; currentLine: number | null; executionLine: number | null; executionLineText: string; diagnostic: string | null }) { const lines = text.split("\n"); const mainLine = currentLine && currentLine > 0 && currentLine <= lines.length ? currentLine : null; - return {lines.map((_, index) => {index + 1})} setText(event.target.value)} spellCheck={false} aria-invalid={Boolean(diagnostic)} aria-label="G-code program" />{executionLine ? `Executing source line ${executionLine}` : "Execution idle"}{executionLineText || "-"}; + const traceRef = useRef(null); + useEffect(() => { + const node = traceRef.current; + if (node) node.scrollTop = node.scrollHeight; + }, [executionTrace]); + return {lines.map((_, index) => {index + 1})} setText(event.target.value)} spellCheck={false} aria-invalid={Boolean(diagnostic)} aria-label="G-code program" />{executionLine ? `Executing source line ${executionLine}` : "Execution idle"}{executionLineText || "-"}Execution trace{executionTrace.slice(-8).map((entry, index) => L{entry.line}{entry.text || "-"})}; } function DiagnosticsPanel({ state, gates }: { state: typeof INITIAL_STATE; gates: Record }) { @@ -235,7 +240,7 @@ export function App() { Manual Control [F3] setLeftTab("mdi")}>MDI [F5] setLeftTab("diagnostics")}>Diagnostics setRightTab("preview")}>Preview setRightTab("dro")}>DRO Jog Speed:50%Feed Override:{Math.round((state.snapshot?.overrides?.feed ?? 1) * 100)}% execute("SET_OVERRIDE", { feed: Number(event.target.value) / 100 })} />Spindle Override:{Math.round((state.snapshot?.overrides?.spindle?.[0] ?? 1) * 100)}%{(["path", "live", "limits", "velocity", "offsets", "extents"] as const).map((layer) => setLayers((current) => ({ ...current, [layer]: event.target.checked }))} /> {layer})} - { setPlotCleared(false); execute("PROGRAM_LOAD", { source: programText, path: PROGRAM_PATH }); }} onRun={runProgram} onStep={() => execute("PROGRAM_STEP")} canLoad={gates.load.enabled} canRun={gates.run.enabled} canStep={gates.step.enabled} currentLine={currentLine} executionLine={executionLine} executionLineText={executionLineText} diagnostic={state.error?.code ?? null} /> + { setPlotCleared(false); execute("PROGRAM_LOAD", { source: programText, path: PROGRAM_PATH }); }} onRun={runProgram} onStep={() => execute("PROGRAM_STEP")} canLoad={gates.load.enabled} canRun={gates.run.enabled} canStep={gates.step.enabled} currentLine={currentLine} executionLine={executionLine} executionLineText={executionLineText} diagnostic={state.error?.code ?? null} /> ; diff --git a/web/react/src/store.ts b/web/react/src/store.ts index 4baeb89..d61cdb1 100644 --- a/web/react/src/store.ts +++ b/web/react/src/store.ts @@ -24,6 +24,7 @@ export type FrontendState = { axisState: AxisStateView | null; gates: Record; toolpath: Array<{ id: number; source_line?: number | null; start: Record; end: Record; active?: boolean }>; + executionTrace: Array<{ line: number; text: string; program_status?: string | null }>; }; export const INITIAL_STATE: FrontendState = { @@ -48,6 +49,7 @@ export const INITIAL_STATE: FrontendState = { axisState: null, gates: {}, toolpath: [], + executionTrace: [], }; function payload(message: MessageEnvelope): T { @@ -56,6 +58,12 @@ function payload(message: MessageEnvelope): T { function projection(result: Record, message: MessageEnvelope, previous: FrontendState) { const panel = result.panel_snapshot as PanelSnapshotV1 | undefined; + const trace = Array.isArray(result.execution_trace) + ? (result.execution_trace as Array>) + .map((entry) => ({ line: Number(entry.line), text: String(entry.text ?? ""), program_status: entry.program_status ? String(entry.program_status) : null })) + .filter((entry) => Number.isSafeInteger(entry.line) && entry.line > 0) + : []; + const executionTrace = trace.length ? [...previous.executionTrace, ...trace].slice(-32) : previous.executionTrace; const delta = result.motion_sample as { tick?: number; pose?: Record; joints?: number[]; active_segment_id?: number; active_progress?: number } | undefined; if (!panel && delta && previous.snapshot) { const pose = delta.pose ?? {}; @@ -89,6 +97,7 @@ function projection(result: Record, message: MessageEnvelope, p program: (result.program as Record) ?? previous.program, axisState: (result.axis_state as AxisStateView) ?? previous.axisState, gates: (result.gates as FrontendState["gates"]) ?? previous.gates, + executionTrace, }; } if (panel?.schema === "axis-panel-snapshot.v1") { @@ -145,6 +154,7 @@ function projection(result: Record, message: MessageEnvelope, p hal: panel.hal ?? previous.hal, axisState: panel.axis_state, gates: { ...panel.gates, jog: panel.gates.jogStart, load: panel.gates.load, run: panel.gates.run, step: panel.gates.step, pause: panel.gates.pause, resume: panel.gates.resume, abort: panel.gates.abort, mdi: panel.gates.mdi, home: panel.gates.home }, + executionTrace, }; } const wasmMotion = (result.wasm_motion as { state?: Record } | undefined)?.state; @@ -186,7 +196,7 @@ function projection(result: Record, message: MessageEnvelope, p for (const segment of nextToolpath) segment.active = false; } const snapshot = { simulation_tick: message.simulation_tick ?? previous.tick, state_hash: String(result.state_hash ?? previous.snapshot?.state_hash ?? ""), task: result.task as SimSnapshotV1["task"], motion: { axes, joints, velocity: Array.isArray(motionState.velocity) ? motionState.velocity as number[] : [], homed: typeof motionState.homed === "boolean" ? motionState.homed : undefined, homed_axes: Array.isArray(motionState.homed_axes) ? motionState.homed_axes as boolean[] : undefined }, authoritative } satisfies SimSnapshotV1; - return { snapshot, toolpath: nextToolpath, task: (result.task as Record) ?? previous.task, program: programResult as Record ?? previous.program, devices: (result.devices as Record) ?? previous.devices, deviceIo: (result.device_io as Record) ?? previous.deviceIo, hal: (result.hal_runtime as Record) ?? previous.hal, plc: (result.plc as Record) ?? previous.plc, python: (result.python as Record) ?? previous.python, ioAck: (result.io_ack as Record) ?? previous.ioAck, axisState: (result.axis_state as AxisStateView) ?? previous.axisState, gates: (result.gates as FrontendState["gates"]) ?? previous.gates }; + return { snapshot, toolpath: nextToolpath, task: (result.task as Record) ?? previous.task, program: programResult as Record ?? previous.program, devices: (result.devices as Record) ?? previous.devices, deviceIo: (result.device_io as Record) ?? previous.deviceIo, hal: (result.hal_runtime as Record) ?? previous.hal, plc: (result.plc as Record) ?? previous.plc, python: (result.python as Record) ?? previous.python, ioAck: (result.io_ack as Record) ?? previous.ioAck, axisState: (result.axis_state as AxisStateView) ?? previous.axisState, gates: (result.gates as FrontendState["gates"]) ?? previous.gates, executionTrace }; } export function reduceFrontendState(state: FrontendState, message: MessageEnvelope): FrontendState { diff --git a/web/react/src/styles.css b/web/react/src/styles.css index 889b4c9..8d52edf 100644 --- a/web/react/src/styles.css +++ b/web/react/src/styles.css @@ -170,7 +170,7 @@ output { font: 12px ui-monospace, monospace; text-align: right; } .axis-slider { display: grid; grid-template-columns: repeat(3, minmax(170px, 1fr)); gap: 12px; } .axis-slider label { grid-template-columns: auto 48px; } -.axis-program-pane { display: grid; grid-template-columns: minmax(0, 1fr) 230px; flex: 0 0 205px; height: 205px; min-height: 205px; margin: 0 5px 5px; border: 2px solid #777; background: #f7f7f7; } +.axis-program-pane { display: grid; grid-template-columns: minmax(0, 1fr) 230px; grid-template-rows: minmax(100px, 1fr) auto auto; flex: 0 0 285px; height: 285px; min-height: 285px; margin: 0 5px 5px; border: 2px solid #777; background: #f7f7f7; } .axis-diagnostics { display: grid; gap: 8px; padding: 10px; min-height: 190px; overflow: auto; background: #f7f7f7; font-size: 12px; } .diagnostic-summary, .diagnostic-error, .diagnostic-row { display: flex; gap: 10px; align-items: baseline; flex-wrap: wrap; } .diagnostic-summary { padding-bottom: 6px; border-bottom: 1px solid #b3b3b3; } @@ -186,7 +186,13 @@ output { font: 12px ui-monospace, monospace; text-align: right; } .program-execution-line { display: grid; grid-column: 1; grid-row: 2; grid-template-columns: max-content minmax(0, 1fr); align-items: baseline; gap: 10px; min-width: 0; padding: 5px 8px; border-top: 1px solid #9aa8b4; border-bottom: 1px solid #9aa8b4; background: #e9f1f8; color: #173b5f; font: 12px/1.35 ui-monospace, monospace; } .program-execution-line span { font-weight: 700; white-space: nowrap; } .program-execution-line code { min-width: 0; overflow: hidden; color: #111; text-overflow: ellipsis; white-space: nowrap; } -.program-side { display: flex; grid-column: 2; grid-row: 1 / span 2; flex-direction: column; gap: 12px; padding: 8px; border-left: 1px solid #aaa; background: #d9d9d9; } +.program-execution-trace { grid-column: 1; grid-row: 3; min-width: 0; padding: 4px 8px; overflow: auto; border-bottom: 1px solid #9aa8b4; background: #f4f7fa; color: #253746; font: 11px/1.3 ui-monospace, monospace; } +.program-execution-trace strong { display: block; margin-bottom: 2px; font-size: 11px; } +.program-execution-trace ol { display: grid; gap: 1px; max-height: 64px; margin: 0; padding: 0; overflow: auto; list-style: none; } +.program-execution-trace li { display: grid; grid-template-columns: 42px minmax(0, 1fr); gap: 8px; min-width: 0; } +.program-execution-trace li span { color: #174d84; font-weight: 700; } +.program-execution-trace li code { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } +.program-side { display: flex; grid-column: 2; grid-row: 1 / span 3; flex-direction: column; gap: 12px; padding: 8px; border-left: 1px solid #aaa; background: #d9d9d9; } .program-actions { display: flex; flex-wrap: wrap; gap: 5px; } .active-gcodes-label { display: grid; gap: 5px; color: #333; } .active-gcodes-label textarea { width: 100%; min-height: 84px; resize: vertical; border: 1px solid #999; background: white; color: #222; font: 11px/1.35 ui-monospace, monospace; } @@ -208,9 +214,10 @@ output { font: 12px ui-monospace, monospace; text-align: right; } .axis-top-pane { grid-template-columns: 1fr; height: auto; min-height: 0; flex: 0 0 auto; } .axis-notebook { min-height: 330px; } .axis-preview { min-height: 320px; } - .axis-program-pane { flex: 0 0 auto; height: auto; min-height: 205px; grid-template-columns: 1fr; } + .axis-program-pane { flex: 0 0 auto; height: auto; min-height: 300px; grid-template-columns: 1fr; grid-template-rows: minmax(100px, 1fr) auto auto auto; } .program-execution-line { grid-column: 1; grid-row: 2; } - .program-side { grid-column: 1; grid-row: 3; border-top: 1px solid #aaa; border-left: 0; } + .program-execution-trace { grid-column: 1; grid-row: 3; } + .program-side { grid-column: 1; grid-row: 4; border-top: 1px solid #aaa; border-left: 0; } .active-gcodes-label textarea { min-height: 58px; } } @media (max-width: 620px) { diff --git a/web/worker/sim-session.mjs b/web/worker/sim-session.mjs index 080a9c3..f575ec2 100644 --- a/web/worker/sim-session.mjs +++ b/web/worker/sim-session.mjs @@ -180,6 +180,7 @@ export class SimSession { status: statePayload.status, cycles: statePayload.cycles, motion_trace: trace, + execution_trace: Array.isArray(statePayload.execution_trace) ? statePayload.execution_trace : [], motion_sample: motionSample, snapshot_mode: "motion_delta", motion_authority: statePayload.motion_authority ?? this.motionAuthority, @@ -1534,6 +1535,13 @@ export class SimSession { return finish(this.error(input, "scheduler", String(error?.message ?? error)), "failed"); } const program = this.xyzacProgram ? this.xyzacProgram.snapshot() : this.program.loaded ? this.program.snapshot() : null; + const execution_trace = program_steps + .map((step) => ({ + line: step.line ?? step.program?.execution_line ?? null, + text: step.program?.execution_line_text ?? "", + program_status: step.program_status ?? step.program?.program_status ?? null, + })) + .filter((entry) => Number.isSafeInteger(entry.line) && entry.line > 0); if (program) { const status = this.xyzacProgram ? (program.status === 4 ? "completed" : program.status === 2 ? "running" : program.status === 3 ? "paused" : "loaded") : (program.program_status === "completed" ? "completed" : program.program_status === "running" ? "running" : program.program_status); this.task.setProgram(status, this.xyzacProgram ? program.current_line : program.line); @@ -1544,7 +1552,7 @@ export class SimSession { const snapshot = this.timeline.checkpoint(input.seq, this.tick, snapshotState, this.timeline.snapshots.latest()?.snapshot_id ?? null); const lightweightXyzac = Boolean(this.xyzacProgram); return finish(this.envelope("COMMAND_RESULT", input, { - status: "verified", cycles, program_steps, + status: "verified", cycles, program_steps, execution_trace, ...(lightweightXyzac && cycles <= 32 && motion_trace.length ? { motion_trace } : {}), motion_authority: this.motionAuthority, task: this.task.export(), scheduler: this.scheduler.export(), authoritative: snapshotState.authoritative,
{executionLineText || "-"}
{entry.text || "-"}