feat(sim): P4 slice 2 — burnt parts go open + LED joins the charred visual

- 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.
This commit is contained in:
David Montero 2026-06-18 04:57:00 +02:00
parent cbcf6ba81e
commit e4aae82ca5
2 changed files with 19 additions and 1 deletions

View File

@ -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(

View File

@ -40,6 +40,9 @@ export interface SimulatorStorePort {
end: { componentId: string; pinName: string };
}>;
boards: Array<{ id: string; boardKind: string; pinStates?: Record<string, unknown> }>;
/** Components destroyed at runtime (P4) excluded from the netlist so a
* burnt part actually goes open. Optional for non-store ports. */
burntComponents?: Set<string>;
};
subscribe(listener: (state: unknown, prev: unknown) => void): () => void;
}
@ -279,8 +282,14 @@ export class CircuitSimulationService {
private async runSolve(): Promise<void> {
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,