velxio/test/test_circuit/autosearch/01_findings.md

123 lines
6.6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Hallazgos — sesión 2026-04-15
## Resumen ejecutivo
Se construyó un sandbox bajo `test/test_circuit/` que valida end-to-end la emulación de circuitos analógicos + digitales + Arduino real vía `avr8js`. **25 tests pasan** en ~2.9 s. La propuesta del `plan1.md` (ngspice WASM, 40 MB) puede **reemplazarse por un solver MNA en JS puro** de ~500 líneas con resultados correctos en los casos probados.
## Lo que funciona
### Solver DC/transitorio (src/solver/)
- **MNA (Modified Nodal Analysis)** con eliminación gaussiana de pivote parcial.
- Ground implícito (no aparece en la matriz).
- Ruido numérico controlado con `G_min = 1e-12 S` en diagonal.
- **Newton-Raphson** iterativo para no-lineales (diodo / LED / BJT), con:
- límite SPICE-style `pnjlim` para evitar `exp()` overflow en la primera iteración.
- damping adaptativo (max 0.5 V por paso por nodo).
- convergencia < 1 µV en 38 iteraciones típicas.
- **Transitorio** con backward Euler (método de companion para el capacitor). La clave: **no ejecutar `solveDC()` inicial antes del primer paso**, porque con capacitores abiertos en DC se calcula un estado falso que luego queda como `V_prev`. En su lugar, sembrar `prev` desde `Vinit`.
### Componentes implementados
| Categoría | Componentes |
|---|---|
| Pasivos lineales | Resistor, VoltageSource, CurrentSource, Potentiometer, Switch |
| Pasivos dinámicos | Capacitor (backward Euler) |
| No-lineales | Diode (Shockley), LED (5 colores), BJT_NPN (Ebers-Moll simplificado) |
| Paramétricos | NTCThermistor (modelo β) |
### Integración avr8js ≡ Velxio
Se copió fielmente el patrón de Velxio:
- `new CPU(program, 8192)`, `new AVRIOPort(cpu, portXConfig)`, `new AVRADC(cpu, adcConfig)`, `new AVRTimer(cpu, timerXConfig)`.
- Listeners por puerto mapeo `(portName, bit) → Arduino pin`.
- `adc.channelValues[ch] = volts` para inyectar voltajes en tiempo real.
- `cpu.data[ocrAddr]` para leer duty de PWM.
El test `avr_blink.test.js` carga la **misma `.hex` que usa Velxio** y dispara el mismo LED simulado con corriente real calculada por el solver. Funciona.
### E2E: potenciómetro → PWM → LED
| Wiper | V_A0 | PWM duty | I_LED | Brillo |
|---|---|---|---|---|
| 0 % | 0.00 V | 0.0 % | 0.00 mA | 0.000 |
| 25 % | 1.25 V | 24.7 % | 0.00 mA | 0.000 |
| 50 % | 2.50 V | 49.8 % | 3.26 mA | 0.163 |
| 75 % | 3.75 V | 74.9 % | 8.77 mA | 0.439 |
| 100 % | 5.00 V | 100.0 % | 14.38 mA | 0.719 |
El PWM duty cycle rastrea el wiper con < 1 % de error. La no-linealidad IV del LED emerge naturalmente (por debajo de su knee no conduce).
### E2E: termistor NTC
Error máximo de recuperación de temperatura: **0.05 °C** en el rango 0-50 °C (β=3950). Prácticamente el error de redondeo del ADC de 10 bits.
## Lo que no se probó / gaps
- **Inductores** no se implementaron (no había casos de uso en el scope).
- **MOSFET** queda para una futura iteración (BJT alcanzó).
- **Análisis AC/frecuencia** (Bode, resonancia) fuera del scope.
- **Co-simulación ciclo-a-ciclo** entre AVR y solver. Los tests resuelven el circuito una vez después de cada cambio de pin, no en cada ciclo. Para PWM se usa `duty · V_supply` como fuente equivalente, no la conmutación real precisión aceptable para brillo medio, pero no modela ripple.
## Problemas encontrados (y solución)
### 1. Diodo explotaba en la primera iteración de Newton
- **Síntoma**: `Vd` saltaba a 5 V (apertura circuital cuando `gd ≈ 0` en iter 0). Siguiente iter: `exp(5/0.026) ≈ 1e84` NaN.
- **Fix**: `pnjlim` dentro del stamp del diodo. Primera iteración: `Vd = min(Vd, Vcrit)` donde `Vcrit = nVt · ln(nVt / (√2 · Is))`. Iteraciones siguientes: paso logarítmico si `|ΔVd| > 2nVt`.
### 2. Capacitor no cargaba
- **Síntoma**: `V(τ)` era 5 V en vez de 3.16 V. El `solveDC()` inicial veía el cap abierto `Vout=5V` este estado se usaba como `V_prev`.
- **Fix**: `runTransient()` ahora siembra `state.prev` desde `capacitor.Vinit` directamente, sin llamar a `solveDC()` inicial.
### 3. ADC daba valores 0-3 en vez de 0-255
- **Síntoma**: leer `ADCH` con `ADMUX = 0x40` retornaba solo los top-2 bits del resultado 10-bit (derecha-ajustado por defecto).
- **Fix**: activar `ADLAR=1` `ADMUX = 0x60`. Ahora `ADCH` contiene los 8 MSB.
### 4. Offsets RJMP incorrectos en programa hand-assembled
- **Síntoma**: loop saltaba a dirección equivocada tras añadir más instrucciones.
- **Fix**: cálculo manual cuidadoso. Cada `STS`/`LDS` ocupa 2 words. `RJMP k` salta a `PC+1+k`.
## Mejoras sugeridas para Velxio (main app)
1. **Adoptar este solver** para la simulación eléctrica en lugar de `ngspice-wasm`. Ventajas:
- 0 KB de dependencia WASM (vs 40 MB)
- Inicialización instantánea
- Fácil extender con componentes custom de wokwi-elements
2. **LED brightness realista**. Actualmente Velxio muestra LEDs como on/off booleanos. Con el solver:
- `led.brightness(state)` [0, 1] basado en corriente directa
- El ajuste del brillo del componente wokwi-led puede hacerse con `--wokwi-led-brightness` CSS var o similar.
3. **Potenciómetro funcional**. El componente ya existe visualmente pero el wiper no afecta a nada. Con este solver:
- `pot.setWiper(x)` recalcular inyectar V en `adc.channelValues[ch]`
- Habilitar drag interactivo del wiper.
4. **Warnings al usuario**:
- Si la corriente por un LED > 25 mA → overlay rojo "quemaría el LED".
- Si un diodo queda en reverse breakdown → overlay amarillo.
- Si no hay resistencia en serie con un LED → advertencia pre-simulación.
5. **Corridas en Web Worker** si el circuito supera ~50 componentes, para no bloquear la UI. El solver como está es ~3 ms por `solveDC()` con 10 componentes en Node → debería ser < 30 ms con 100 en browser.
## Performance (measurements en WSL2, Node 22)
| Suite | Tests | Duración |
|---|---|---|
| passive.test.js | 11 | 8 ms |
| transient_rc.test.js | 3 | 22 ms |
| diodes.test.js | 7 | 13 ms |
| avr_blink.test.js | 2 | 2.4 s (loop CPU real) |
| e2e_pot_pwm_led.test.js | 1 | 90 ms |
| e2e_thermistor.test.js | 1 | 121 ms |
| **Total** | **25** | **~2.9 s** |
Prácticamente todo el tiempo se va en avr8js ejecutando los 32 M de ciclos del blink. El solver de circuitos es irrelevante en el presupuesto.
## Próximos pasos recomendados
1. **Integrar** un wrapper del solver en `frontend/src/simulation/` (nuevo archivo `CircuitSolver.ts`).
2. **Conectar** con `useSimulatorStore.wires` y `components` para auto-construir el `Circuit` objeto.
3. **Hook** en `PinManager.onPinChange()` con debounce de 50 ms `circuit.solveDC()`.
4. **Reutilizar** las fixtures de HEX que ya tiene `frontend/src/__tests__/fixtures/`.
5. **Añadir** MOSFET, Zener, op-amp ideal (GMIN stamping) cuando haya demanda.