diff --git a/js/components.js b/js/components.js index 8a3417d..143545d 100644 --- a/js/components.js +++ b/js/components.js @@ -53,6 +53,8 @@ export function saveComponentFromCircuit(name) { /** * Evaluate a component instance. * Simulates the internal circuit and returns an array of output values. + * IMPORTANT: Uses persistent internal state so latches/flip-flops retain + * their values between evaluations (just like the main circuit). */ export function evaluateComponent(gate, inputs) { if (!gate.component) { @@ -62,8 +64,11 @@ export function evaluateComponent(gate, inputs) { const comp = gate.component; - // Deep clone internal circuit for simulation - const internalGates = JSON.parse(JSON.stringify(comp.gates)); + // Persist internal gate state on the gate instance so latches hold their value + if (!gate._internalGates) { + gate._internalGates = JSON.parse(JSON.stringify(comp.gates)); + } + const internalGates = gate._internalGates; const internalConns = comp.connections; // read-only, no need to clone // Map external inputs to internal INPUT gates using stored inputIds @@ -133,7 +138,8 @@ export function evaluateComponent(gate, inputs) { } } - console.log(`[component] eval "${comp.name}" inputs=[${inputs}] → outputs=[${outputs}]`); + console.log(`[component] eval "${comp.name}" inputs=[${inputs}] → outputs=[${outputs}]`, + `(internal state preserved: ${gate._internalGates ? 'yes' : 'no'})`); return outputs; } diff --git a/js/renderer.js b/js/renderer.js index 45703b8..b47189b 100644 --- a/js/renderer.js +++ b/js/renderer.js @@ -162,6 +162,23 @@ function drawComponentGate(gate) { ctx.fillStyle = '#444'; ctx.fillText(getGateLabel(gate), gate.x + w / 2, gate.y + h - 6); + // Get port labels from the component definition + const comp = gate.component; + const inputLabels = []; + const outputLabels = []; + if (comp) { + const inputIds = comp.inputIds || []; + const outputIds = comp.outputIds || []; + for (const id of inputIds) { + const g = comp.gates.find(g => g.id === id); + inputLabels.push(g?.label || ''); + } + for (const id of outputIds) { + const g = comp.gates.find(g => g.id === id); + outputLabels.push(g?.label || ''); + } + } + // Input ports getInputPorts(gate).forEach(p => { const isPortHovered = state.hoveredPort && @@ -178,6 +195,16 @@ function drawComponentGate(gate) { ctx.strokeStyle = isPortHovered ? '#fff' : '#555'; ctx.lineWidth = 1.5; ctx.stroke(); + + // Port label (inside the gate, to the right of the port) + const label = inputLabels[p.index]; + if (label) { + ctx.font = '9px "Segoe UI", system-ui, sans-serif'; + ctx.fillStyle = '#aaa'; + ctx.textAlign = 'left'; + ctx.textBaseline = 'middle'; + ctx.fillText(label, p.x + PORT_R + 4, p.y); + } }); // Output ports @@ -195,6 +222,16 @@ function drawComponentGate(gate) { ctx.strokeStyle = isPortHovered ? '#fff' : '#555'; ctx.lineWidth = 1.5; ctx.stroke(); + + // Port label (inside the gate, to the left of the port) + const label = outputLabels[p.index]; + if (label) { + ctx.font = '9px "Segoe UI", system-ui, sans-serif'; + ctx.fillStyle = '#aaa'; + ctx.textAlign = 'right'; + ctx.textBaseline = 'middle'; + ctx.fillText(label, p.x - PORT_R - 4, p.y); + } }); }