Commit Graph

111 Commits

Author SHA1 Message Date
David Montero 375b952d47 refactor(examples): move Pico W WiFi examples to the pro overlay seam
The Pico W WiFi showcase examples are a paid-overlay feature now (the WiFi
engine moved to the overlay). Read them through a build-time `@pro` seam so the
SSR prerender + gallery + sitemap include them when built with the overlay, and
OSS gets an empty stub.

- data/examples.ts: import { proExamples } from '@pro/data/proExamples' (static,
  build-time) instead of the local examples-picow-wifi.ts; delete that file.
- src/__pro_stub__/data/proExamples.ts: OSS no-op (empty list) for the @pro alias.
- vitest.config.ts: mirror the @pro alias (stub by default / overlay when
  VITE_PRO_BUILD) so tests loading examples.ts resolve it.
- scripts/generate-sitemap.mjs: also parse <PRO_OVERLAY_PATH>/data/proExamples.ts
  when building with the overlay (the script reads example IDs from source text,
  so it can't follow the alias).
- Tests: drop the picow-wifi import/usage from the 5 OSS example tests (they
  validate the OSS set now); prune the 4 obsolete picow netlist snapshots. The
  overlay's proExamples get their own coverage in pro/.../__tests__/.
2026-06-15 15:54:44 +02:00
David Montero ec40204f92 feat(picow): wire a visible LED to GP2 in the relay example
Confirmed the RP2040 GPIO -> PinManager -> wokwi-led path is fully wired
(identical to the AVR path): a wokwi-led wired anode->GP2, cathode->GND
lights when MicroPython drives GP2 HIGH. The board's componentId is its
boardType ('pi-pico-w') since loadExample's addBoard gives the first
board of a kind id == kind.

Add a red LED on GP2 to picow-wifi-relay-web-server and flip the relay
logic to active-high (ON => GP2 HIGH => LED lit) so the toggle is visible
on the canvas, not just in the device panel.
2026-06-14 07:20:52 +02:00
David Montero 1478f7f543 fix(picow): make the device panel a draggable, non-modal floating window
The first iframe panel was a full-screen modal with a backdrop: it
covered the canvas and blocked the editor, so you couldn't watch the
board react or keep clicking buttons/wiring while it was open, and it
couldn't be moved.

Now it's a small floating panel docked top-right, draggable by its title
bar, resizable, with NO backdrop — the canvas and editor stay fully
interactive underneath.

Also: the relay and async-led device pages now show a big colored ON/OFF
indicator (these are web-server demos; the GP2/onboard LED isn't drawn on
the canvas, so the panel is where you see the state flip).
2026-06-14 06:28:29 +02:00
David Montero 80d2086907 fix(picow): open the IoT gateway in an in-app iframe, not a new tab
The Pico W emulation runs in THIS browser tab via requestAnimationFrame.
Opening the gateway with target=_blank / window.open backgrounds the
emulation tab; the browser then pauses its rAF, the simulated chip
freezes, and the gateway can no longer reach the server on it — the
request times out (502) and toggles do nothing.

Render the served page in a same-tab iframe panel (openDeviceGateway) for
the Pico W so the emulation stays in the foreground and keeps answering.
The ESP32 is unchanged (its server runs in QEMU on the backend, immune to
tab visibility), so it keeps opening in a new tab.

Also: the async-led page now shows the LED state (the board's onboard LED
isn't drawn on the canvas) so the toggle has visible feedback.
2026-06-14 04:19:48 +02:00
David Montero 2dcdabe72f fix(picow): web examples use relative URLs + a resilient server loop
The served page loads under /api/gateway/<id>/, so absolute fetches like
fetch('/on') hit velxio.dev/on instead of the chip — the LED/relay/servo
controls did nothing. Use relative paths (fetch('on')) so they resolve
under the gateway. The relay example now has real ON/OFF buttons and
wraps its blocking accept loop in try/except so a dropped browser
connection can't kill it.

e2e now fires two sequential requests (first with a browser-sized header)
against a non-resilient blocking server and asserts both are served.
2026-06-14 03:11:54 +02:00
David Montero 1aba6e2e48 feat(picow): surface the IoT gateway in the UI (parity with ESP32)
- SerialMonitor linkifies http://10.13.37.x (the Pico W subnet) the same
  way it already does http://192.168.4.x for the ESP32, turning the
  sketch's printed URL into an 'Open IoT Gateway' link.
- SimulatorCanvas shows the clickable WiFi badge for the Pico W too
  (normalizing its 'started' status, which carries the fixed IP, to
  got_ip so it reuses the ESP32 badge styling + launcher).
- async-led and servo-web examples print a clickable http://<ip>/ line
  so the gateway link appears (relay-web-server already did).

Gated e2e (CYW43_GATEWAY_E2E=1) drives the real emulator + a running
backend and asserts the served page comes back through /api/gateway.
2026-06-14 02:15:53 +02:00
David Montero 4d80a9d1c3 fix(sim): Pico W MicroPython loads the RPI_PICO_W firmware (network + CYW43)
The RP2040 MicroPython loader always fetched the plain RPI_PICO build, which
ships no `network` module and no CYW43 WiFi driver. Every Pico W WiFi/MQTT
example therefore failed at `import network` ("no module named 'network'"),
which surfaced as a compile/run error in the editor.

- getFirmware()/loadUserFiles() are now variant-aware. pi-pico-w boards load
  RPI_PICO_W-20230426-v1.20.0 (network/socket/ssl + the CYW43439 driver) and
  write the LittleFS at the W board's flash offset (0x12c000, 212 blocks)
  instead of the plain Pico's 0xa0000/352. The W firmware spans flash to
  ~0xab000 and would otherwise be clobbered by the filesystem. Each variant
  gets its own IndexedDB cache key.
- The variant is selected by the presence of the already-wired CYW43 emulator
  (attachCyw43 runs for pi-pico-w boards only).
- loadMicroPython swaps in a fresh RP2040 each run, so the CYW43 PIO-FIFO hooks
  are re-installed on the new instance; otherwise the driver's gSPI traffic
  never reaches the emulator and WiFi never comes up.
- Bundle micropython-rp2040w.uf2 as the offline fallback.
- Point the ThingsBoard example at the simulator's Velxio-GUEST network.
2026-06-12 16:46:41 +02:00
David Montero Crespo 61c549de47
Merge pull request #237 from davidmonterocrespo24/fix/picow-wifi-examples-boardtype
fix(examples): move WiFi/MQTT 100-days examples to Pico W (network module)
2026-06-12 10:58:19 -03:00
David Montero a44a3e700f fix(examples): WiFi/MQTT 100-days examples must run on Pico W, not plain Pico
8 MicroPython examples that use `import network` (Blynk IoT relay, ThingsBoard
IoT, OTA update, DHT11 HTTP CSV logger, async LED control, web servo, websocket
LED, IoT relay web server) had boardType "raspberry-pi-pico". A plain Pico
(RP2040) has no WiFi and no `network` module, so they failed at runtime with
`ImportError: no module named 'network'` (the banner even shows "Raspberry Pi
Pico with RP2040"). Move them all to "pi-pico-w", which has WiFi + network.
2026-06-12 15:57:23 +02:00
David Montero 0fc76611b2 feat(examples): ESP32 WiFi + MQTT (PubSubClient) gallery example
Adds a self-contained ESP32 networking example for the /examples gallery
(addresses feature request #115). The sketch joins the emulator AP
"Velxio-GUEST", connects to a public MQTT broker (broker.hivemq.com:1883),
then publishes to its own topic and subscribes to it so each message
round-trips through the broker and toggles GPIO2 -- no external client or
local broker needed; just open the Serial Monitor.

Verified end to end in QEMU: WiFi associates (IP 192.168.4.15), DNS resolves
and outbound TCP to :1883 succeeds via slirp NAT. PubSubClient is auto-
installed via the example's `libraries` field.
2026-06-12 15:52:11 +02:00
David Montero 22de488de2 feat(microsd): SD-over-SPI card storage for AVR, RP2040 and ESP32
Add a working microSD card part backed by a FAT16 image, following the
Wokwi storage model: the project's own workspace files are auto-copied
onto the card (free), and an optional "SD Card" panel uploads extra
files (gated as a paid feature by the velxio.dev overlay; OSS default
allows it).

Frontend (in-browser AVR / RP2040):
- ProtocolParts.ts: rewrite the microsd-card part from a handshake stub
  into a real SD-over-SPI device (reply-first Ncr timing, SDSC byte
  addressing, single/multi-block read+write, CSD/CID, full CMD set).
- utils/fatImage.ts: dependency-free FAT16 super-floppy builder (8.3 + LFN).
- utils/sdCardFiles.ts: assemble the card image from workspace files plus
  uploaded files; base64 helpers.
- components/simulator/SdCardPanel.tsx + ComponentPropertyDialog: upload UI.
- DynamicComponent + useSimulatorStore: build and inject the image on run.
- lib/proSdCardGate.ts: overlay-installable gate for the upload action.
- data/examples-storage-microsd.ts: Arduino Uno + ESP32 gallery examples.

Backend (ESP32 via QEMU):
- services/esp32_sd_slave.py: synchronous SD-over-SPI slave (Python port of
  the browser part) with a sparse backing store, idle-state R1 tracking and
  real CRC16 on data blocks when the host enables CRC (CMD59) -- both
  required by ESP-IDF's sdspi driver.
- esp32_worker.py: route SPI bytes to the slave (returns MISO synchronously)
  and feed write-only bulk transfers.
- esp32_lib_manager.py + routes/simulation.py: forward the FAT image
  (sd_card.image_b64) from the start config into the worker.

Tested:
- frontend: protocol-parts, fat-image, sd-card-gate and microsd-real-firmware
  (real Arduino SD.h on avr8js) -- 86 passing.
- backend: test_esp32_sd_slave (10) covering the ESP-IDF init sequence and
  CRC16; validated end to end by running a real SD.h sketch in libqemu-xtensa
  (mount, directory listing, read and write-readback).
2026-06-11 03:59:53 +02:00
David Montero 7674b7b15e feat(examples): declare library manifests for the two lib-using AVR examples (P2.4-arduino examples)
lcd-hello -> ['LiquidCrystal'], uno-servo -> ['Servo']. These were the only
non-ESP32 gallery examples using a USER library without a manifest; loading +
compiling them now sends the library scope (resolved from the content-addressed
cache) instead of falling back to the global scan-all. Every other non-ESP32
example is core-only (Wire/SPI are core-bundled; the RP2040 core bundles Servo,
so pico-servo needs no manifest) or already declared its libraries.
2026-06-08 04:24:02 +02:00
David Montero c671c9b21c data(examples): complete ESP32 example library manifests with transitive deps (P2.4)
Auto-completed the library manifests for the 9 ESP32-family examples that use
external libraries, so each declares its full dependency set (direct +
transitive). Found genuinely-missing deps that the previous fields omitted:

- esp32-dht22, c3-dht22:  + Adafruit Unified Sensor
- esp32-mpu6050, esp32-bmp280, esp32-oled, esp32-doom:  + Adafruit BusIO
- esp32cam-lcd-preview:  add manifest [Adafruit GFX Library, Adafruit BusIO, Adafruit ILI9341]

Each completed manifest was validated by compiling the example against ONLY
its manifest (manifest-scoped resolution, no fallback). esp32-servo / c3-servo
were already complete. This unblocks turning on scoped resolution for the
gallery (P2.3): with complete manifests, scope picks the declared libs and
excludes strays, and the P2.3-safety fallback covers any residual gap.
2026-06-06 20:14:17 +02:00
David Montero Crespo 6b281c9d6d
Merge pull request #217 from davidmonterocrespo24/feat/chipbus-phase0
Feat/chipbus phase0
2026-06-06 03:18:45 -03:00
David Montero Crespo 70558eb31b feat(examples): mixed digital+analog coexistence demo (MCU + AND gate + transistor)
A board example proving digital and analog coexist in ONE circuit: the Arduino
drives two logic levels, a physical AND gate combines them, and the AND output
switches an NPN 2N2222 transistor that drives the "motor" LED. Verified live: it
compiles, the MCU drives the AND gate (5 V), the transistor conducts and the LED
lights — MCU -> logic gate -> transistor -> load works across the digital and
ngspice motors together.

Known limitation (sim-mixedmode step 2, pending): the ngspice side does not
re-solve on every MCU pin edge, so a fast (1 Hz) blink does not track in real
time — the analog output changes on a slower cadence. User-driven / slow changes
track fine. Snapshot updated for the new example.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 23:09:41 -03:00
David Montero Crespo a5edeb74c7 fix(examples): AND Gate Alarm uses latching slide switches (was momentary buttons)
The AND Gate Alarm needed BOTH inputs HIGH at once, but it used momentary
pushbuttons buffered through an Arduino — with one mouse you can only hold one
button at a time, so the AND never fired and the alarm could never be
demonstrated.

Rebuilt it as a board-less digital circuit: two SLIDE switches (they latch) feed
a real AND gate that drives the alarm LED. Slide both switches ON and they stay,
so the alarm arms. No MCU / compilation — it runs on the digital gate engine.
Verified live: the LED lights only on 11 (00/01/10 -> off, 11 -> on).

Snapshot updated: the new board-less and-gate-alarm netlist, plus the digital
bucket count label (38 -> 39) from the earlier ripple-counter example.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 22:42:15 -03:00
David Montero Crespo b93b1b42c5 feat(digital-gate-engine): 4-bit ripple counter gallery example + sequential controller fix
Adds the first board-less SEQUENTIAL gallery example (digital-ripple-counter-4bit):
four T flip-flops chained into a ripple counter, LEDs showing the binary count,
clocked by a slide switch. Impossible on the SPICE engine (no edge detection at
DC) - it runs on the digital gate engine.

Controller fix (found by testing the counter live): the controller rebuilt the
network on every change, which reset flip-flop state so a counter never counted.
Now the network is built once and KEPT ALIVE; a switch toggle applies
incrementally via setSwitch (preserving sequential state), and a rebuild happens
only on a structural change (components/wires). Correct for combinational AND
sequential circuits.

examples-digital.test.ts: flip-flop examples are digital-engine-only, so they are
exempt from the SPICE-mapping / has-a-gate / netlist checks (the "logic" check
now accepts a gate OR a flip-flop). digitalgate-engine-examples: a correctness
test clocks the real counter example and asserts it counts 1..15,0 in binary.

Verified live (?digitalgates default ON): the counter counts 0..6 on the canvas;
and the complex examples all work - comparator-4bit (A=B correct), decoder-3to8
(perfect one-hot x8), alu-slice-1bit (32 combos deterministic), multiplier-2x2
(3*3=9, 7 distinct products), adder-subtractor-4bit (5+3=8).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 21:58:42 -03:00
David Montero Crespo 47adb0b1c8 fix(chipbus): Galaksija boots + displays + types live in the browser
The gallery example loaded but the Z80 never visibly ran: the screen stayed
frozen on garbage. Two multi-chip async-load races, neither caught by the
existing headless tests (which drive RESET manually and attach the display
before boot):

1. RESET edge-vs-level race. The Z80 only left reset on the RISING edge of
   RESET (a pin watch). In the browser the 7 chips instantiate asynchronously,
   so the small power-on-reset chip releases RESET before the larger Z80 has
   registered its watch -> the edge is lost and the CPU stays in reset forever.
   Fix: on_clock samples the RESET level (hardware-accurate; RESET is
   level-sensitive) so a missed edge self-corrects. An undriven RESET reads low,
   so the CPU safely stays in reset until something drives it high.
   Repro/guard: chipbus-galaksija-reset-race (race ordering must still boot).

2. Display-snoop load-order race. galaksija-display was a passive write-snoop;
   the ROM paints the screen ONCE at boot then idles, so a display that comes up
   late misses every write and shows stale content forever. A snoop cannot
   recover writes it never saw. Fix: fold the screen into the RAM chip
   (galaksija-ram-display) and render from the ACTUAL video RAM (0x2800-0x2BFF,
   internal 0x0800 with A0-A12 wiring) on a ~30 fps timer - correct regardless
   of load order, exactly how the real machine scans video RAM.
   Repro/guard: chipbus-galaksija-display-snoop-race (late snoop shows nothing)
   + chipbus-galaksija-ram-display (renders even when first paint is post-boot).

The example now has 6 chips (RAM+display merged, gdisp dropped), 76 wires.
Verified live in the browser: boots to "@'READY", shows the ">" prompt, and
pressing A echoes ">A_" through keyboard -> Z80 -> video RAM -> display. The
full chipbus suite is 45/45.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 15:47:30 -03:00
David Montero Crespo 94627b99d2 feat(chipbus): Galaksija keyboard - type BASIC over the bus
Adds a memory-mapped keyboard so you can type into the Galaksija. Based on
the libretro Galaksija core's scheme (not guessed): reading 0x2000+offset
returns 0xFE when the key at that matrix offset is held, 0xFF otherwise;
the keyMap gives the offset per key ('A'=1 ... Enter=48, Space=31, etc.).

- galaksija-keyboard.c: drives reads of 0x2000-0x203F from a keys[] table and
  exports set_key(offset, down) for the host to push key events. Never drives
  outside the keyboard range.
- galaksija-ram.c: ram-64k variant that yields reads of 0x2000-0x203F to the
  keyboard (writes still go to RAM), so the two never fight for the bus.
- ChipRuntime: ChipInstance.hasKeyboard + setKey() expose the chip's set_key.
- CustomChipPart: bridges browser keydown/keyup (by KeyboardEvent.code, via
  GALAKSIJA_KEY_OFFSET) into the chip, ignoring keystrokes while the code
  editor or an input is focused so typing code is never hijacked.
- The gallery example gains the keyboard chip (now 7 chips, 99 wires) and uses
  galaksija-ram.

Test chipbus-galaksija-keyboard: pressing 'A' (offset 1) makes the BASIC
monitor echo "A" after its ">" prompt and advances the cursor. 41 chipbus
tests pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 14:37:42 -03:00
David Montero Crespo a393e3e91d feat(chipbus): Galaksija home computer gallery example + browser perf throttle
Ships the full Galaksija (1983 Z80 home computer) as a runnable Retro
gallery example, plus the pieces needed to run a multi-chip bus live in the
browser.

Gallery example (examples-retro-intel.ts, id 'galaksija-z80-computer'):
Z80 + galaksija-rom (public-domain ROM A+B) + ram-64k + inverter (A13
decode) + galaksija-display + a power-on reset chip, wired chip-to-chip
over the bus (76 wires), no board. Click Resume and it boots the real ROM
to the "READY" prompt on the green display. Chip wasm is embedded
(wasmBase64) so it runs without a backend compile.

- ChipRuntime.tickTimers gains a wall-clock budget (CustomChipPart passes
  6 ms): a faithful-but-slow event-driven bus can't run a real-time CPU in
  one animation frame, so without a cap a Z80 fetching over the settle
  kernel froze the tab. With the budget the sim advances slower than real
  time (boots over a few seconds) and the UI stays responsive; fast
  single-chip examples finish under budget and are unaffected.
- galaksija-display: blits its framebuffer on a ~30 fps timer instead of on
  every character write, so a clear-screen burst doesn't flood the canvas.
- reset-gen: power-on reset (pulses RESET high, ties WAIT/BUSREQ/INT/NMI
  high) so the machine boots on Resume without a manual reset.
- chipbus flag now defaults ON (override with ?chipbus=off): chip-to-chip
  buses are a core capability; single-chip and board nets never take this
  path, so the only thing enabled is multi-chip buses, previously broken.

Verified live in the browser: the example boots and renders "@'READY" with
the ">_" prompt, responsive. Full suite 2084 pass (5 pre-existing,
unrelated env failures).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 13:49:33 -03:00
David Montero Crespo 9ba8687743 fix(epaper): correct orientation across all boards + Pico VCC wire
ePaper panels rendered rotated/misaligned on AVR and RP2040 (e.g. the 2.13"
Pico clock came out sideways and clipped). The ESP32 worker decoder was just
taught to compose in the controller's native RAM geometry and rotate to the
display orientation, but the browser-side SSD168xDecoder (used by AVR/RP2040)
still composed at display dims with no rotation, so the two diverged.

- SSD168xDecoder.ts: port the worker's native-window compose + rotation.
  * Size RAM to the longer side both ways so a rotated native layout
    (128x296 behind a 296x128 panel) isn't truncated.
  * Compose in the active RAM window, then rotate via the inverse of
    Adafruit_GFX setRotation(1). Detect orientation by BYTE width so a
    non-multiple-of-8 native width (the 2.13" panel is 122 px) is handled.
  * Track the UNION of windows per frame: paged drivers (GxEPD2 page height
    < panel) set one partial window per page, so compose must use the full
    native area, not just the last page's strip. Fixes the all-white render
    on paged panels (1.54" Uno, 4.2" Pico, 7.5" ESP32).
  * Add an isBwr option: B/W panels treat 0x26 as a 2nd mono plane (white
    only if both planes white), tri-colour panels keep red-wins.
  * Default the active window to display geometry; the firmware overrides it.
- EPaperPart.ts: pass isBwr = cfg.palette === 'bwr' to the decoder.
- esp32_spi_slaves.py / esp32_worker.py: mirror the byte-aware rotation +
  window-union in the worker, and derive is_bwr from panel_kind on the
  runtime sensor_attach path too (fixes the tri-colour ESP32 alert badge).
- test_epaper/ssd168x_decoder.py: re-port the golden reference to match
  (keeps the 3-way TS/Python/worker identity invariant). Tests updated to
  construct tri-colour cases with is_bwr/palette='bwr'.
- examples-displays-epaper.ts: the Pico VCC wire referenced '3V3(OUT)',
  which the velxio-pi-pico-w element doesn't expose (it has '3V3'), so the
  wire snapped to the board corner. Use '3V3'.
2026-06-04 23:15:37 -03:00
David Montero aa4c8123f2 fix(examples): wire board-less button power via 2.l so the pushbutton enters SPICE
The pushbutton SPICE mapper reads pins '1.l' and '2.l', but killbits/counter
wired the power side to '2.r' (an un-unioned sub-pin), so netLookup('2.l')
returned null and the button was omitted from the netlist entirely — pressing
did nothing electrically board-less. Wire the power side via '2.l' so the
button becomes a real (pressed -> 0.01 ohm) bridge to VCC, which the pull-down
+ connectChipInputsToSolve then turn into a HIGH the chip reads.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 05:23:51 +02:00
David Montero dd62b94c1d feat(sim): custom chips read inputs (buttons/switches/sensors) with no board
The chip-output board-less path existed (chipPinDrives -> SPICE voltage sources
-> LEDs). The INPUT direction was missing: a chip pin wired to a pushbutton had
its net solved by ngspice, but nothing fed that net's state back to the
PinManager key the chip reads via vx_pin_read. So a board-less chip could light
LEDs but never read a button (verified: i8080 counter stayed at 0 on press).

connectChipInputsToSolve subscribes to the electrical store and, after each
solve, thresholds every wired chip input pin's net voltage to HIGH/LOW and
triggerPinChange()s the chip's synthetic pin — updating getPinState (polling)
and firing onPinChange edges. Pins the chip is actively driving are skipped so
it never fights its own outputs. Hooked alongside connectAnalogInputsToMcu in
start.ts. Solver-agnostic; reads only the electrical store shape.

Also gives the board-less button examples a pull-down on each chip BTN pin so
they read a clean LOW when open (a button-to-VCC floats HIGH otherwise):
i8080-button-counter (2) and i8080-killbits (8).

- new connectChipInputsToSolve.ts; start.ts wiring.
- examples-retro-intel: pull-down resistors + wires for the button examples.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 05:09:51 +02:00
David Montero 694f038988 feat(sim): Stop halts custom chips + LEDs go dark; retro chip examples go board-less
Phase 1 of the run-system/UX work.

Stop bug: a programmable chip kept running after Stop when a board was present.
The chip rAF tick gated only on board presence (!boardless), so with a board it
ticked forever. Now it gates on the actual run state: board-less -> electrical
paused flag; with board(s) -> board.running. handleStop also clears every chip's
output drives (clearAllChipDrives) and re-solves so chip-driven LEDs go dark on
Stop instead of freezing at their last frame.

Examples to board-less (regulated power supply, no Arduino — the Arduino only
ever supplied 5V):
- z80-larson-scanner -> 'Z80 Comet Scanner': board-less, a faster TWO-LED comet
  (scanner.s) so it's visually distinct from z80-larson-no-board's single-bit
  walk; green/blue LEDs.
- i8080-killbits -> board-less (psu + resistors), keeps killbits.s as the chip's
  editable program; buttons re-powered from the supply.
- i8080-button-counter -> board-less (psu + resistors); behaviour chip, program
  baked in, so it shows a note (no editable file) and runs standalone.
banner-streamer stays Arduino-based (its TX/RX go through the AVR USART bridge).

- CustomChipPart: run-state-aware tick gate.
- EditorToolbar: clearAllChipDrives() helper + handleStop clears chip drives.
- examples-retro-intel: 3 conversions; drop now-unused sketch consts; add the
  larsonScannerAsm comet program.
- Tests: board+chip routing now uses an inline synthetic example (gallery chip
  examples are all board-less).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 04:32:44 +02:00
David Montero 780b80778c feat(custom-chip): newly-added programmable chip auto-gets an editable program; chaser-c goes board-less
Two fixes from live testing feedback:

1. Adding a programmable chip (Z80/8080) from the gallery created NO program
   group — only the chip(s) from the example had one. Root cause: 'programmable'
   was detected by a non-empty programFile, but a fresh chip's programFile is
   empty until the user writes one. Now detection uses the canonical signal —
   chip.json's programTargets — via isProgrammableChip(). When such a chip
   lands with no program yet, the file explorer seeds an editable program.c
   (DEFAULT_CHIP_PROGRAM_C, a working walking-LED skeleton) into its own group
   and stamps programFile/programTarget onto the component so Compile/Run can
   build it. Behaviour/driver and predefined chips (no programTargets) still
   get no group — edited in the chip designer.

2. z80-led-chaser-c now runs board-less on a regulated power supply (no Arduino,
   mirroring z80-larson-no-board) — the Arduino only ever supplied 5V and added
   confusion. chaser.c stays the chip's editable program in its own section.

- romCompileService: isProgrammableChip(), DEFAULT_CHIP_PROGRAM_FILE/_C.
- FileExplorer: detect by programTargets; auto-seed program.c + persist
  programFile/programTarget for fresh chips.
- examples-retro-intel: chaser-c -> board-less (psu + 8 resistors + 8 LEDs),
  drop the now-unused Arduino sketch const; fix a stale sdcc --code-loc comment.
- Tests: board+chip case moved to z80-larson-scanner (still board-based);
  isProgrammableChip unit tests.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 22:52:11 +02:00
David Montero 5a23e89eb5 feat(custom-chip): program lives in its own editor group, not the board sketch
A programmable custom-chip (a CPU emulator that runs a ROM/program, e.g. the
Z80 or 8080) now keeps its program (larson.s, chaser.c, ...) in a dedicated
editor file group — group-chip-<chipId> — rendered as its own collapsible
section in the file explorer, exactly like each board owns its sketch group.
Behaviour/driver chips and predefined chips carry no programFile and get no
group; they stay editable only in the chip designer.

Fixes two reported issues on the Z80 examples:
- /example/z80-larson-no-board: the board-less chip example now opens its
  program (larson.s) as the active group, editable on the left — previously
  the editor showed but no file appeared.
- /example/z80-led-chaser-c: the chip program (chaser.c) no longer shows as
  a sibling tab inside the Arduino sketch group; it sits in its own chip
  section instead. The board group shows only sketch.ino.

Details:
- useEditorStore: chipFileGroupId()/CHIP_GROUP_PREFIX helpers.
- loadExample: seedChipProgramGroups() routes each chip's programFile into its
  own group (seeded from the example files), sweeps stale chip groups, keeps
  the program OUT of the board group, and for a board-less chip example makes
  the chip group active so the program is the editable file shown.
- EditorToolbar.prepareCustomChips: resolves the program from the chip's own
  group (falls back to board files for older projects) before assembling ROM.
- FileExplorer: renders one collapsible section per programmable chip with an
  IC icon; clicking switches the editor to the chip group. Lazy-creates a
  group for chips dropped on the canvas.
- projectPayload + vlxFile: serialise chip groups alongside board groups and
  include them in the dirty-check hash, so chip-program edits persist on
  save / autosave / .vlx export and round-trip via replaceFileGroups on load.
- Regression tests for board-less + board+chip routing and stale-group sweep.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 22:07:56 +02:00
David Montero fe001d728c fix(examples): board-less chip example — Run enabled on load + editable program
Two UX bugs in the board-less "Z80 Larson Scanner (no board)" example:

- It loaded "running" (electrical sim defaults to paused=false), so Run was
  disabled and Stop enabled even though the chip hadn't started — the user had
  to Stop then Run. loadExample now starts a board-less example that contains a
  custom chip in the STOPPED state (paused=true) so Run is enabled; pure
  analog/digital circuits stay live.
- The chip's program wasn't editable: it shipped a pre-baked ROM and the
  board-less loader only setCode'd into an orphan file group (no-op → blank
  editor). The example now ships larson.s as a real file (programFile), and
  the board-less loader points the editor at the default group and loadFiles()
  the example's files, so the program shows on the left and is editable, like
  the board-backed examples. Run compiles it.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 21:30:42 +02:00
David Montero d5b9a9ceb5 feat(custom-chip): run custom chips with no board (general-purpose sim)
Velxio can now simulate one or more custom-chip CPUs with NO Arduino/ESP32
board on the canvas — a general-purpose electronics simulator, not an
MCU-only one.

- DynamicComponent: board-less parts get the real shared flat PinManager
  (instead of a no-op stub) so a custom chip's digital pin writes/reads reach
  the LEDs/inputs wired to it.
- CustomChipPart: the rAF tick respects board-less Run/Stop (freezes while
  the electrical sim is paused); board behaviour is unchanged.
- EditorToolbar.handleRun: board-less Run compiles each chip's WASM/ROM and
  re-attaches the parts (restartParts) so they pick up the fresh WASM, then
  resumes the solver.
- useSimulatorStore.restartParts(): bump hexEpoch to force part re-attach.
- New example "Z80 Larson Scanner (no board)": a programmable Z80 + 8 LEDs +
  the adjustable power-supply component, no MCU. The chip drives the LEDs
  through the synthetic-pin + ngspice path added earlier.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 21:04:17 +02:00
David Montero a57951d854 fix(examples): z80-led-chaser-c dir must be signed char (SDCC z80)
SDCC treats plain `char` as unsigned on Z80, so `dir = -1` read back as 255,
`if (dir > 0)` was always true, the "walk right" branch never ran, and the
bit just shifted left until it fell off the end and the LEDs went dark after
one pass. Use `signed char dir`. Verified in a chip-WASM harness: with plain
char the chaser does 8 LED writes then stops; with signed char it walks the
bit back and forth continuously (14894 writes). Completes the C example fix
together with dropping --code-loc 0x100 in c_compile.py.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 20:23:17 +02:00
David Montero 1d6961d03c fix(z80-cpu): map RAM over the whole 0x8000-0xFFFF so vanilla SDCC C runs
SDCC's z80 crt0 sets SP=0x0000 and makes its first stack push at 0xFFFF.
The chip only mapped RAM at 0x8000-0xBFFF (0xC000+ was MMIO/ignored), so the
stack landed on unmapped memory and a plain C program crashed inside crt0 —
before main — which is why z80-led-chaser-c compiled but drove nothing.

Extend RAM to cover 0x8000-0xFFFF (32 KB) with the MMIO window 0xC000-0xC0FF
carved out and checked first, in scripts/make-z80-cpu.py + regenerated
z80-cpu.c. Now SDCC's default stack works and "write C from scratch, click
Run" just works — no manual `LD SP` needed (dropped from chaser.c). Bumped
the chip WASM initial memory to 4 pages to hold the larger RAM buffer. Larson
(asm, SP=0xBFFF, LED at 0xC000) is unaffected.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 13:31:26 +02:00
David Montero bf36487642 fix(examples): z80-led-chaser-c set the Z80 stack pointer in main
SDCC's z80 crt0 defaults SP to 0x0000; on the z80-cpu chip's memory map
(RAM 0x8000-0xBFFF, MMIO at 0xC000+) the stack would grow into unmapped
high memory and the program crashed on the first CALL (delay), so the LEDs
never moved. Set SP to the top of RAM (0xBFFF) at the start of main, the
same thing the asm Larson example does with "LD SP, 0xBFFF". Verified the
ROM runs and walks the LEDs.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 06:09:28 +02:00
David Montero a77dbcafad fix(examples): z80-led-chaser-c used invalid SDCC __at() cast syntax
`(*(volatile unsigned char __at(0xC000)))` uses __at as a cast operator,
which neither avr-gcc nor sdcc accept (sdcc: "syntax error: token -> ')'").
__at is a storage specifier, not an operator. Use the portable absolute-
address pointer form `(*(volatile unsigned char *)0xC000)`, which sdcc -mz80
compiles cleanly. Verified: produces a 462-byte ROM.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 05:53:53 +02:00
David Montero 65b2c02f9b feat(custom-chip): one-click Run for programmable CPU chips
Compile/Run now makes every custom-chip on the canvas live in a single
click instead of requiring a manual trip through the chip designer plus a
separate ROM compile:

- Each custom-chip's C source is auto-compiled to WASM when it has none
  yet (via /api/compile-chip), and programmable CPU chips get their
  program file (larson.s, chaser.c, ...) assembled/compiled to ROM bytes
  (via /api/compile-rom) and injected, all before the board starts.
- Chip-program files are excluded from the arduino-cli sketch build, so
  SDCC-only syntax such as __at(0xC000) no longer breaks the Arduino
  compile (this is what made the Z80 LED-chaser-C example error out).

Fixes the Z80 examples that either errored on Run (z80-led-chaser-c) or
compiled but did nothing (z80-larson-scanner, whose chip never had WASM
or ROM). Works for any circuit built from scratch with a programmable
CPU chip, not just the bundled examples.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 04:47:26 +02:00
David Montero 18f2e4598a feat(examples): add ESP32 Doom raycaster (ILI9341) gallery example
A full-screen Wolfenstein/Doom-style raycaster for ESP32 + ILI9341 over
hardware SPI (Adafruit_ILI9341, block writes), with auto-demo and 4 control
buttons. Doubles as an emulation-speed benchmark. Category: games.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-02 05:29:17 +02:00
David Montero 909c94c5fb feat(examples): add single-board Raspberry Pi 3/4/5 GPIO examples
Six gpiozero (Python) examples to exercise the Pi 3/4/5 QEMU Linux boards
with different sensors/actuators. All strictly digital — the Pi has no ADC
and PWM is not simulated, so this covers the GPIO in/out paths that work:
  - [Pi 3] Blink an LED
  - [Pi 3] Running Lights (5 LEDs)
  - [Pi 4] Button Toggles LED
  - [Pi 4] RGB LED Color Cycle (digital, 7 colors, pwm=False)
  - [Pi 5] PIR Motion Alarm
  - [Pi 5] Traffic Light

Structure mirrors the existing Pi example (boards[] + vfsFiles['script.py'],
run via 'python3 /home/pi/script.py'); LEDs wired directly like
nano-button-led. gpiozero is used because it works across Pi 3/4/5 (RPi.GPIO
doesn't on Pi 5). Adds a smoke test loading all six (board kind, components,
wiring consistency, gpiozero script present in the VFS).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-02 04:36:58 +02:00
David Montero daa46c6724 fix(sim): desktop QEMU sims use injected sidecar URL; fix nano-button-led wiring
- ESP32 / Raspberry Pi / STM32 / Pico-W bridges built their WebSocket URL
  from a bespoke API_BASE() that read only VITE_API_BASE (fallback
  localhost:8001) and ignored the desktop shell's runtime-injected
  window.__VELXIO_API_BASE__. On the desktop the sidecar runs on a random
  127.0.0.1 port, so the sim WebSocket dialed localhost:8001 and never
  connected: compile succeeded but the simulation never started. Honor
  __VELXIO_API_BASE__ first; web (/api) and dev (localhost:8001) unchanged.
- nano-button-led example: button was wired D2->1a and 1b->GND (same
  terminal), tying D2 to GND permanently. Rewire D2->1.l and GND->2.l
  (opposite terminals), matching the other examples.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-02 03:24:19 +02:00
David Montero Crespo 0d5a1d5838 feat(motors): fix stepper rotation + add A4988 driver (real Fritzing SVG)
The stepper-motor and biaxial-stepper parts only decoded a one-hot wave-drive coil sequence, so they never rotated under the common two-phase full-step / Stepper.h / AccelStepper drive that Wokwi's own examples use -- only the servo moved. Rewrote both decoders to track the net magnetic-field vector of the coils (atan2 of the H-bridge currents), so the rotor follows wave, two-phase full-step and half-step drive alike, whether driven directly from GPIO or through a driver's outputs.

Also adds an A4988 STEP/DIR stepper driver (parity with Wokwi's wokwi-a4988): velxio-a4988 element renders the real Pololu A4988 Fritzing breadboard SVG (public/components/a4988.svg); MotorDriverParts.ts finds the wired stepper via the netlist and advances it one (micro)step per STEP rising edge in the DIR direction (MS1-3 microstep + active-low ENABLE). Metadata in component-overrides.json. Three examples (Uno/ESP32/Pico) wire MCU STEP/DIR -> A4988 -> stepper, coil map aligned to Wokwi (1A->B+,1B->B-,2A->A+,2B->A-).

Verified in-browser: motor rotates on Arduino Uno (avr8js) and Raspberry Pi Pico (rp2040js). tsc --noEmit clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-31 16:28:34 -03:00
David Montero Crespo 310271bdb2 feat(components): make KY-040 rotary encoder discoverable + add example (#104)
The KY-040 rotary encoder was already fully simulated (wokwi-ky-040 element + PartSimulationRegistry 'ky-040' driving CLK/DT quadrature and the SW button) and present in the catalog, but unfindable: named 'KY040', in the 'other' category, with no rotary/encoder search tags and a placeholder thumbnail. A user searching 'rotary encoder' got nothing (issue #104).

- generate-component-metadata.ts: let component-overrides.json patch category, description and tags on scanned wokwi parts (previously only name/thumbnail) -- the fields the picker category tab and ComponentRegistry.search() actually use. - component-overrides.json: ky-040 override -> name 'KY-040 Rotary Encoder', category 'input', rotary/encoder/knob tags, description, real encoder thumbnail SVG. - examples.ts: KY-040 + Arduino Uno example (quadrature read + SW reset). Regenerated components-metadata.json; searching rotary/encoder/knob now returns the KY-040. tsc clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-31 14:30:59 -03:00
David Montero Crespo 6813891f91 feat(boards): add 6 STM32 boards (F4 Discovery, Olimex H405, Netduino 2/+2, Pill variants)
Adds stm32-f4-discovery, stm32-olimex-h405, stm32-netduino-plus2, stm32-netduino2, stm32-blackpill-f401 and stm32-bluepill-f103cb, mapped to existing qemu-lcgamboa machines (netduinoplus2, olimex-stm32-h405, netduino2, stm32vldiscovery). A generic inline board renderer (no SVG) draws the Discovery/Olimex/Netduino boards from a header pin layout; the Pill variants reuse the Blue/Black Pill SVGs. Per-board onboard-LED pin and polarity via STM32_LED. One blink+serial example per board.

tsc --noEmit clean; all new FQBN pnum variants present in STM32 core 2.12.0; worker smoke tests pass for the new machines.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-31 03:19:26 -03:00
David Montero Crespo ca8dcedcc7 feat: STM32 (Blue Pill / Black Pill) QEMU emulation + Pro board gating
STM32 emulation (open-core, runs via libqemu-arm in the backend worker):
- backend: stm32_lib_manager + stm32_worker (GPIO, USART, I2C/SPI device models
  reusing the ESP32 slaves, live sensor updates), arduino_cli STM32 branch,
  start_stm32 simulation route.
- frontend: Stm32Bridge + Stm32BluePill(/BlackPill) web components (Wokwi SVGs),
  board kinds, Interconnect/boardPinMapping/boardProtocols wiring, example
  projects (blink, serial, I2C BMP280/MPU6050/DS1307/SSD1306/weather, 7-seg,
  RGB, button, switch, stepper, cross-board interconnect).
- Raspberry Pi 4/5 board elements + thumbnails.

Pro board gating (generic OSS->Pro seam; entitlement logic lives in the overlay):
- lib/proBoardGate.ts: isProBoardKind (STM32 + every QEMU Raspberry Pi),
  installBoardGateImpl/boardGateDecision, triggerProUpgradePrompt.
- PRO badge on those boards in the component picker; gate at the picker add +
  the run backstop (startBoard).
- backend/app/services/board_access.py: server-side enforcement seam for the
  simulation WebSocket; STM32/Pi unavailable -> Pro-framed message.
- desktop: generic QemuDownloadPrompt + Stm32QemuPrompt (download-behind-license,
  mirrors the ESP32 prompt).
- .gitignore: never ship libqemu-* binaries in the public image.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-30 19:06:14 -03:00
David Montero b09c8339ac fix(examples): use hardware I2C (not SoftI2C) in smart-ui-eyes
OSError: [Errno 19] ENODEV at ssd1306.SSD1306_I2C(...) on
100d-esp32-oled-smart-ui-eyes-animation-time-and-weather-micropython.

MicroPython SoftI2C bit-bangs GPIO directly. Velxio's ESP32 QEMU
bridge listens on the emulated I2C peripheral (registers slaves like
0x3C wokwi-ssd1306 against it) and doesn't decode bit-banged GPIO
toggles as I2C frames, so the OLED never sees any writes and
i2c.writeto() returns ENODEV on first use.

machine.I2C(0, ...) routes through the hardware I2C peripheral that
QEMU emulates, the registered slave receives the bytes, the OLED
panel updates. Same code path the other working SSD1306 MicroPython
examples on this repo already use.

API surface is identical to SoftI2C — only the constructor differs —
so the rest of the user sketch needs zero changes.
2026-05-23 18:06:42 +02:00
David Montero 0d43c5f892 fix: relax -Werror for user sketches + un-nest /* */ in robot-desktop-eyes
Two related fixes for the ESP32 Arduino-compat compile path:

(a) backend/app/services/esp-idf-template/main/CMakeLists.txt:
    Demote -Werror=comment / =parentheses / =sign-compare / =narrowing
    / =write-strings / =missing-field-initializers / =reorder back to
    plain warnings. ESP-IDF's project defaults are stricter than what
    Arduino/arduino-cli users expect, so common Arduino idioms (nested
    /* */, missing field initializers in struct literals, etc.) were
    failing builds that compile fine in the Arduino IDE. -Wall stays
    on; we just stop the abort.

(b) examples-robot-desktop.ts (robot-desktop-eyes example):
    Replace the nested /* xTaskCreatePinnedToCore( ... /* Task function. */
    ... */ block with `#if 0 / #endif` so the inner block comments
    don't terminate the outer one. Even with -Wno-error=comment the
    real-syntax-level issue (the first inner `*/` closes the outer
    comment, leaving the rest of the lines as bare code) would still
    bite, so this needs an actual code fix.
2026-05-23 16:19:56 +02:00
David Montero 1a6e2a23a7 fix(examples): auto-install libs for robot-desktop-eyes
Sketch fails to compile out of the box with
  fatal error: ESP32Servo.h: No such file or directory
because the ESP32Servo / U8g2 / DHT / Adafruit Unified Sensor libs
aren't part of arduino-esp32 and weren't declared on the example.

loadExample.ts already iterates `example.libraries` and runs
arduino-cli lib install for any missing entry before the user
touches Compile. Adding the four real deps the sketch needs gets
the example compiling cleanly on a fresh container without any
manual Library Manager dance.
2026-05-23 08:49:26 +02:00
David Montero 2dea3f0448 fix(examples): correct SSD1306 + big-sound-sensor pin names
robot-desktop-eyes and the day-30/100-days OLED example wired the
SSD1306 OLED with SDA/SCL/VCC, but the wokwi-ssd1306 element
exposes pinInfo as DATA/CLK/VIN/GND. The mismatched names couldn't
resolve, so all three wire endpoints fell back to (0,0) of the
component and visually attached to the corner instead of the pins.

Same class of bug on the wokwi-big-sound-sensor in
robot-desktop-eyes: the element has AOUT/DOUT (no plain OUT). The
sketch uses digitalRead(SOUND_PIN), so route to DOUT.

The COMPONENT_PIN_ALIASES map in wokwiZip.ts only normalises on
.zip import — static examples have to use the real pinInfo names.
2026-05-23 08:23:15 +02:00
David Montero 5bd541e56f fix(examples): add OLED + buttons circuit to ESP32 smart-ui-eyes example
The 100d-esp32-oled-smart-ui-eyes-animation-time-and-weather-micropython
example had components: [] and wires: [] — the MicroPython code wired
an SSD1306 OLED on I2C (GPIO 21/22) plus two buttons (GPIO 14, 27) but
the circuit had nothing on the canvas, so users saw a bare ESP32 board
and the simulation was missing every peripheral the code drives.

Adds:
- wokwi-ssd1306 on I2C (3V3 / GND / SDA=21 / SCL=22)
- two wokwi-pushbuttons wired HIGH-when-pressed (3V3 → 1.l, 2.l → GPIO
  14 / 27) to match the `if pin.value(): pressed` check in main.py
2026-05-23 08:16:13 +02:00
davidmonterocrespo24 28826e928e feat(examples): import robot_desktop — ESP32 animated-eyes face
Pulls https://github.com/davidmonterocrespo24/robot_desktop into the
examples gallery as a real-world ESP32 + sensors project. Cozmo-style
desktop robot: SSD1306 OLED face that blinks, looks around, and shows
emotions; DHT11 weather mode triggered after 10 min idle; PIR wakeup
from sleep; LDR-driven sleep when the room goes dark; sound-triggered
reactions; and two eyebrow servos.

Ships as 34 separate files (one .ino + 33 headers / source) rather
than the usual single-sketch flatten. The face engine
(Eye / EyeTransition / EyeVariation / FaceBehavior / FaceExpression
/ FaceEmotions / BlinkAssistant / LookAssistant / …) splits
responsibility across enough classes that flattening would obscure
the design. Velxio's multi-file `files: [{ name, content }]`
mechanism handles this cleanly — the editor mounts the .ino as the
active sketch and the rest sit in the same workspace.

Pre-placed components match the original board's pin map verbatim
from Common.h:

  - SSD1306 OLED on I²C (SDA=21, SCL=22 — ESP32 default)
  - DHT11 on GPIO 15
  - PIR motion on GPIO 4
  - Big sound sensor on GPIO 2
  - Photoresistor on GPIO 34 (ADC1)
  - Right eyebrow servo on GPIO 12
  - Left eyebrow servo on GPIO 13

Arduino libraries (U8g2lib, DHT, ESP32Servo, Adafruit_Sensor) are
auto-installed by velxio's Library Manager on the first compile.

Category 'displays', difficulty 'advanced', tags cover both the
sensor list and the project's identity (cozmo / robot / animation /
eyes) so the gallery search surfaces it from multiple angles.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-23 00:29:25 -03:00
David Montero Crespo 603b791daa feat(attiny85+customchip): full ATtiny85 ADC/Timer0 + custom-chip pipeline fixes
ATtiny85 (AVRSimulator + collectPinStates + connectAnalogInputsToMcu + SimulatorCanvas + Attiny85Element + examples):
- Add attiny85AdcConfig with correct register addresses (ADMUX=0x27,
  ADCSRA=0x26, ADCSRB=0x23, ADCL=0x24, ADCH=0x25, DIDR0=0x34, adcInterrupt=0x08).
  Without this, analogRead() polled the wrong address forever and the
  firmware hung on first ADC read.
- Add attiny85Timer0Config + instantiate AVRTimer so OVF fires at the
  ATTinyCore-expected ~1.024 ms cadence. delay() advance is still blocked
  on avr8js TIFR auto-clear semantics (separate upstream issue, see
  ATTINY85_TIMER0_UPSTREAM_ISSUE.md in velxio-prod test plan).
- Map ATtiny85 ADC channels to PB-style pin names (PB5/PB2/PB4/PB3 -> 0..3)
  in connectAnalogInputsToMcu so SPICE node voltages reach the right ADC
  channel.
- Recognise /^PB\d+$/ in collectPinStates.pinNameToArduinoPin so wires
  named "PB1" emit v_attiny85_pb1 V-source and the LED responds to MCU
  writes. Previously every PB-wire returned -1 and SPICE saw no source.
- SimulatorCanvas: subscribe pin 1 (PB1) for the built-in LED on the
  attiny85 board kind (Digispark convention), instead of falling through
  to the pin-13 default.
- Attiny85Element: remove the hand-drawn "yellow LED" circle that was
  floating above the chip. The bare DIP-8 has no on-board LED; examples
  wire a real wokwi-led + resistor instead.
- examples.ts: add a real wokwi-led + 220 Ohm wokwi-resistor + wires to
  attiny85-blink, and add missing series resistors to attiny85-button-led
  and attiny85-ntc-sensor. attiny85-pwm-fade was already correct.

Custom-chip pipeline (CustomChipPart + simulatorBridges):
- Add a requestAnimationFrame loop that calls instance.tickTimers() every
  frame in CustomChipPart. Chips that register vx_timer_create (e.g. an
  i8080 stepping its core, or a sensor publishing samples) had timers
  added to the queue but nothing fired them; tickTimers was dead code.
- Gate the ESP32 backend path with detectSimulatorKind(sim)==='esp32'.
  The previous `typeof sim.registerSensor === 'function'` check matched
  AVR and RP2040 simulators too (they expose registerSensor for I2C
  sensor proxies), routing client-side chips to a non-existent ESP32
  worker on those boards.
- Replace direct simulator.usart.writeByte calls in avrUartTx with a
  JS-level FIFO + setTimeout(1ms) drainer. avr8js writeByte drops bytes
  under burst load (a chip emitting print_string lost ~99% of bytes via
  non-immediate, or kept only the last byte via immediate). The drainer
  attempts one non-immediate write per tick and retries on RXC busy /
  RXEN off. Added a guard for ATtiny85 (no USART -> would queue forever).

End-to-end verified: i8080-banner-streamer now prints the boot banner
followed by "uptime ticks: 0xNN" lines stepping every ~50 ms, executing
real Intel 8080 instructions inside the WASM chip.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-19 18:10:08 -03:00
David Montero Crespo dd22bcfe50 fix(ui+spice+example): interactive wokwi components, NTC formula, photoresistor alias
Three independent fixes uncovered during a systematic example-by-example
audit (plan/full_test_plan/):

1. DynamicComponent.handleMouseDown was calling e.stopPropagation()
   unconditionally in the capture phase. That swallowed pointerdown
   BEFORE wokwi-potentiometer / pushbutton / slide-switch / joystick
   could see it, so the rotary knob would not rotate and buttons
   wouldn't press even with a real OS mouse. Now we skip the swallow
   when the click target is an inner wokwi-* element during a live
   simulation, letting the wokwi component own its own pointerdown
   while still allowing the canvas drag-to-rearrange flow on the
   wrapper / non-interactive surface.

2. examples.ts uno-ntc (and pico-ntc) sketch had the NTC divider
   formula inverted relative to both the SPICE mapper topology
   (VCC -> R_NTC -> A1 -> R_pull -> GND, the standard module wiring)
   and real wokwi-ntc-temperature-sensor modules. Moving the slider
   to 60 C made the firmware print -3.42 C. Flipped the formula to
   r = SERIES_R * (VCC - v) / v. Now slider 60 C -> Serial reports
   60.12 C and A1 voltmeter shows 4.00 V.

3. componentToSpice.ts photoresistor mapper was only registered under
   the bare key `photoresistor`, but example components use the
   metadataId `photoresistor-sensor`. Added an alias so the LDR +
   pull-down divider gets emitted for the real component instance.

All three reproduce visually in seconds; documented per-example in
plan/full_test_plan/examples/.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-19 15:10:42 -03:00
David Montero Crespo 0e2f0790db feat(chips): C-to-Z80 compile via SDCC + LED chaser example
Adds a third format to /api/compile-rom: `c` (C source compiled by SDCC
to Z80 bytes). Same chip-program flow as 8080/Z80 asm — write C in a
project file, click Compile, click Run.

Backend:
- backend/app/services/c_compile.py — async SDCC wrapper. Locates the
  sdcc binary on PATH (or via SDCC env var, or common Windows install
  paths) and shells out with target=mz80 + --code-loc 0x100 --data-loc
  0x8000. Parses the resulting Intel HEX into raw ROM bytes. Pure 8080
  is rejected with a clear error (SDCC has no 8080 backend; Z80 ROMs
  also run on the i8080-cpu chip if you avoid Z80-only ops).
- rom_compile.py: compile_rom is now async; the new c branch delegates
  to c_compile. compile_rom_endpoint awaits it.

Frontend:
- romCompileService: RomFormat gains 'c'; formatForFile maps .c/.cpp to
  'c'. isChipProgramFile intentionally still excludes .c — disambiguation
  happens at the EditorToolbar level.
- EditorToolbar: the chip-program path also fires when a custom-chip
  has programFile === activeFile.name (regardless of extension). That
  lets .c files route to /api/compile-rom (SDCC) when bound to a CPU
  chip, while .c files NOT bound to any chip continue to route to
  arduino-cli as before.

Docker:
- Dockerfile.standalone adds `sdcc` to the apt-get install list, so the
  prod image ships with SDCC out of the box.

Example:
- /examples/z80-led-chaser-c — z80-cpu chip + chaser.c (a Larson
  scanner written in C with __at() MMIO definitions). Compiles cleanly
  with SDCC's --code-loc 0x100 default crt0.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 00:31:10 -03:00
David Montero Crespo 96ef12b585 feat(chips): programmable Z80 chip + Larson scanner example
Adds the Zilog Z80 to the programmable-retro-CPU lineup. Same compile-rom
flow that landed for the 8080 in PR #189: write Z80 asm in a project
file, click Compile (backend assembles via in-tree two-pass asm-z80),
click Run, the chip emulator boots from the resulting ROM bytes.

Backend:
- backend/app/services/asmz80.py — two-pass Z80 assembler covering the
  practical demo subset: LD r,n / r,r' / rp,nn / (nn),A / A,(nn) +
  ALU r/n + INC/DEC + JP/JR/DJNZ/CALL/RET + PUSH/POP + IN/OUT +
  EX/EXX + LDIR/LDDR/IM/NEG + RLCA/RRCA/RLA/RRA + the simple
  ED-prefix variants. Not yet: CB-prefix bit ops, DD/FD index ops.
- rom_compile.py routes target=z80 through the new assembler.

Chip:
- frontend/src/components/customChips/examples/intel/z80-cpu.{c,chip.json}
  Generated by scripts/make-z80-cpu.py from the existing z80.c emulator
  (same clean-room implementation that passes ZEXDOC end-to-end). The
  external pin/bus protocol is replaced with internal RAM + ROM + MMIO
  for LED/BTN/UART. 35 KB WASM.

Example:
- /examples/z80-larson-scanner — Knight-Rider-style walking LED.
  Demonstrates JR/DJNZ/RLCA which the 8080 can't run.

Plus a small Z80 smoke-test asm under scripts/.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 00:21:08 -03:00