From e4aae82ca52504f1c8f3e862a955bedd8e904404 Mon Sep 17 00:00:00 2001 From: David Montero Date: Thu, 18 Jun 2026 04:57:00 +0200 Subject: [PATCH] =?UTF-8?q?feat(sim):=20P4=20slice=202=20=E2=80=94=20burnt?= =?UTF-8?q?=20parts=20go=20open=20+=20LED=20joins=20the=20charred=20visual?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - The live solver now excludes runtime-destroyed components from the netlist, so a burnt part actually goes OPEN: its current stops and anything it fed loses power (cascading failure), the way real hardware behaves once a part burns out. Filter is in CircuitSimulationService.runSolve (no-op when nothing is burnt). - The LED's burnout now also marks it in the shared burntComponents set, so a burnt LED gets the same charred + smoke-badge visual (and is opened in the solve) as a resistor / capacitor, on top of going dark. --- frontend/src/simulation/parts/BasicParts.ts | 9 +++++++++ .../src/simulation/spice/CircuitSimulationService.ts | 11 ++++++++++- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/frontend/src/simulation/parts/BasicParts.ts b/frontend/src/simulation/parts/BasicParts.ts index 76428465..3d428ea7 100644 --- a/frontend/src/simulation/parts/BasicParts.ts +++ b/frontend/src/simulation/parts/BasicParts.ts @@ -1,5 +1,6 @@ import { PartSimulationRegistry } from './PartSimulationRegistry'; import { useElectricalStore } from '../../store/useElectricalStore'; +import { useSimulatorStore } from '../../store/useSimulatorStore'; import { emitPropertyChange } from './partUtils'; /** @@ -171,6 +172,14 @@ const LED_BURNOUT_A = 0.1; /** Surface a circuit fault for an LED — console + a UI event the toolbar shows. */ function reportLedFault(componentId: string, kind: string, message: string): void { console.warn(`[led] ${componentId}: ${message}`); + // Mark it destroyed in the shared burnt set (P4): the canvas renders it + // charred + a smoke badge, and the solver opens it — same treatment as a + // burnt resistor/capacitor. (The LED also goes dark via its own update.) + try { + useSimulatorStore.getState().markComponentBurnt?.(componentId); + } catch { + /* store unavailable (test env) — ignore */ + } if (typeof window !== 'undefined' && typeof window.dispatchEvent === 'function') { try { window.dispatchEvent( diff --git a/frontend/src/simulation/spice/CircuitSimulationService.ts b/frontend/src/simulation/spice/CircuitSimulationService.ts index 2972e07c..f388937b 100644 --- a/frontend/src/simulation/spice/CircuitSimulationService.ts +++ b/frontend/src/simulation/spice/CircuitSimulationService.ts @@ -40,6 +40,9 @@ export interface SimulatorStorePort { end: { componentId: string; pinName: string }; }>; boards: Array<{ id: string; boardKind: string; pinStates?: Record }>; + /** Components destroyed at runtime (P4) — excluded from the netlist so a + * burnt part actually goes open. Optional for non-store ports. */ + burntComponents?: Set; }; subscribe(listener: (state: unknown, prev: unknown) => void): () => void; } @@ -279,8 +282,14 @@ export class CircuitSimulationService { private async runSolve(): Promise { const state = this.simStore.getState(); + // P4: a runtime-destroyed part is excluded from the netlist so it actually + // goes open — its current stops and anything it fed loses power (cascading + // failure), the way real hardware behaves once a component burns out. + const burnt = state.burntComponents; + const liveComponents = + burnt && burnt.size > 0 ? state.components.filter((c) => !burnt.has(c.id)) : state.components; const snap = { - components: state.components, + components: liveComponents, wires: state.wires, boards: state.boards.map((b) => ({ id: b.id,