smol▚
connecting…
ESP32-C3 · 72×40 OLED · no_std Rust

One binary. Many apps. Many chips.

smol is a platform for very small computers. One no_std Rust binary carries a whole registry of apps — a clock, a mesh-wide multiplayer snake, a roster radar, a living creature, and a 260K-parameter transformer that writes children’s stories with no WiFi and no cloud — and you move between them with the one button the board has. Underneath, the fleet services come free with the platform: boards find each other over ESP-NOW with no router in the middle, elect a gateway, publish themselves into Home Assistant, take every setting back from it without a reflash, and update themselves with signed firmware over the air — even the WiFi-less ones, over the mesh. It started on a $3 chip. Today it is five targets across four chip families — RISC-V and Xtensa, a 72×40 monochrome OLED and a 2.8″ colour touchscreen, all speaking one wire format to each other.

RISC-V 160MHz 72×40 1-bit OLED BT 5 LE 4MB flash
ESP32-C3 SUPERMINI〰〰 smol v0.1
▲
▼
◀
▶
A
B
It plays games too — this is Block Digger's real render logic, live in your browser. ▲▼◀▶ move · A dig · B place
↓ or watch it write a story
01 — The platform

One binary carries all of it.

smol is not an app with some features bolted on; it is a small platform, and everything else on this page is something running on it. Across the whole fleet there is one firmware source, and for any given board one image — the chip and the tier are chosen together, per build. Inside it, every screen is a plugin — its own state struct implementing one trait — and a single table wires them into the menu you reach with the board’s one button. There is no allocator, no Box<dyn>, no vtable, and no dynamic loading: the whole registry is resolved when the binary is built, so a plugin costs you flash and nothing else. Which plugins are present is a build tier, not a runtime setting — and the tiers stack, so the cheapest board and the fleet gateway run the same code paths with a different slice compiled in.

◇
Static dispatch
Enum-delegation: one centralized match hands each hook to the live plugin. Every call inlines — no vtables, no function pointers.
◇
A union, not a sum
Only one screen is ever live, so the active plugin’s state is a tagged union sized to the largest variant — less RAM than keeping every app’s state side by side.
◇
The menu builds itself
A &'static [AppDesc] table is the registry. Add a variant, a module and a row — the BOOT-button menu picks it up.
◇
Hardware is borrowed
One display, one radio, one sensor block, handed to a plugin per call. A plugin never owns the hardware, so two apps can never fight over it.
◇
Tiers, not toggles
default is the clock and the games with no radio at all; wifi adds the network and the whole OTA stack; espnow adds the mesh. Each tier contains the one below it.
◇
A very small core
Boot, an always-on mesh background block, and a dispatch core of about a dozen lines. Everything a person actually looks at is a plugin.
02 — The targets

One fleet, many kinds of silicon.

smol began as one binary on one $3 chip. The roster is now five targets across four chip families, and the folder targets/ is the roster — one directory per board, each carrying a manifest that declares its chip, its firmware flavour and whether it produces a download. Adding a folder adds a target; there is no second list anywhere. Badges are honest, and the distinctions behind them are the ones that have cost us time: 🟢 glass-verified — a human watched it work · 🟡 mesh-proven — heard on the mesh, but the fleet firmware only compiles for it, which is a real claim and a much weaker one than builds.

🟢
ESP32-C3 · 0.42″ OLED
c3-oled — the original console: games, the Bard, the Familiar. The fleet's heart, $2.76. The only chip whose stack floor comes from a measured on-hardware peak.
🟢
ESP32-C3 SuperMini
c3 — the $1 headless node. Literally the same image as the OLED board; the display simply isn't answered at boot.
🟢
ESP32-S3 · 2.8″ touch
s3-cyd — the first Xtensa board, and the only one running both firmwares. Glass-verified 2026-08-26: boots, paints, takes touch, meshes, and reaches NTP + MQTT + Home Assistant — and took a firmware update over the air the same day, the first cross-architecture OTA in smol's history — by WiFi self-fetch, not a mesh receive (#518). CI still can't compile Xtensa; the release job provisions the toolchain itself.
🟢
ESP32-C6 Watch
c6-watch — a full touch smartwatch on the same SMOLv1 wire format, shipping from its own repo and living here as a full-history subtree. Live on the mesh as ids 122/236.
🟡
ESP32-C5 CYD
c5-cyd — 2.8″ touch, dual-band Wi-Fi 6 + 802.15.4. The first non-C3 silicon ever heard on the mesh (peer 176, 2026-08-24). Its fleet firmware is checks-only — no measured memory budget yet, so no linked image — but its GUI flavour does have a download.
03 — The matrix

Which board does what.

Five boards, four chip families, two firmwares — so “does smol do X?” has stopped having one answer. This is the short version; the full matrix lives in docs/CAPABILITIES.md, where all 69 rows carry a reason on every cell that isn’t green. Three things worth knowing before you read it. Parity is judged per flavour, not per board: the fleet firmware and the touch GUI firmware are genuinely different machines, and the S3 runs both — so it gets two columns. The GUI flavours are a superset of the plain node, which is JP’s ruling and not our inference: every fleet feature is in scope on a touch board too, so a fleet feature the GUI firmware hasn’t implemented yet is a gap with an issue number against it, never “not applicable.” And a red cell never means “not done” — it means the hardware cannot: the $1 board has no screen because none is fitted, and the watch has no Bluetooth earbuds because that silicon has no ISO link layer at all, a question closed forensically rather than deferred. Every amber cell links the issue that closes it, and the cell is that issue’s acceptance test.

✅ integrated🔶 partial 🛠 hardware allows it, firmware doesn’t yet ❌ the hardware cannot📋 planned— n/a to this flavour
capabilityc3c3-oleds3 · fleets3 · GUIc6 · GUIc5 · GUI
mesh & fleet services
SMOLv1 mesh membership✅✅✅✅✅✅first non-C3 on the mesh
Crown election / gateway✅✅✅🔶observe-only #278🔶observe-only🔶 #488
WiFi + NTP + MQTT → Home Assistant✅✅✅✅✅🔶HA dark for now #488
HA discovery + Update entity✅no _soc yet #498✅✅🛠own topics #492🛠own topics #492🛠 #492
Keyed config, no reflash✅11 keys✅✅🛠only S/U/R #490🛠 #490🛠 #490
Custom screens✅✅✅🛠not rendering #473🛠 #473🛠 #473
over-the-air updates
Mesh-OTA receive✅✅🔶the 345→1405 roll was a WiFi self-fetch, not a mesh receive (#518)🔶compiled #495🔶unproven on the C6 #495🔶 #495
Mesh-OTA relay / serve✅✅🔶client only🔶 #495🔶 #495🔶 #495
HTTP / push OTA✅✅✅✅✅zero-touch🔶 #488
Ed25519 signed-image verify✅both paths✅✅🔶mesh yes, HTTP no #489🛠build-monotonicity only #489🛠 #489
display, input, output
Display❌no panel fitted✅SSD1306 72×40✅ILI9341V, 4× scaled✅ILI9341V✅CO5300 AMOLED✅ST7789
Touch❌none fitted❌🛠no fleet driver✅FT6336U✅FT3168🔶XPT2046, poll-only
Status LED✅✅✅WS2812 over RMT🛠pin only #491❌none fitted🛠pin only #491
smol Cast → WLED matrix🔶✅🔶mirror blank #483🔶default-off #493🔶 #493🔶 #493
apps, sensors, power
Mesh Snake (head-to-head)✅✅✅🛠 #494🛠 #494🛠 #494
The Bard (on-device transformer)🔶on glass, off-fleet — 6,720 B short #497🔶same🔶14,400 B short #497🔶opt-in🔶30,480 B short #497🔶
Audio out❌no codec❌🛠codec ACKs, no path #476🔶acoustic proof pending #477✅🛠header un-scoped #486
Audio in / voice❌❌🛠mic fitted #476📋phase 2 #478✅ES7210 + speech-to-text🛠 #486
Battery %❌❌🛠ADC fitted, unread #479🛠declared, no reader #479✅AXP2101🛠 #486
Motion sensor · die temperature❌·✅❌·✅❌·❌no S3 TSENS❌·❌✅·✅❌·❌
radios
802.15.4 (Zigbee / Thread)❌no radio❌❌no radio❌📋mutually exclusive with ESP-NOW📋back-burnered #399
Bluetooth LE🔶silicon has it; refuted in practice🔶🔶not built🔶✅GATT server🔶
build & release
CI builds a linked image✅✅🛠no Xtensa toolchain on runners #413🛠🛠needs a wider ROM region🛠no measured budget row #485
Published download✅✅by design: use the c3 image✅✅✅✅
Stack-floor provenance✅derived from a measured peak✅same🔶observed-sufficient #484🔶 #484🔶a boot assert, not a measurement🛠none of its own #485
04 — Vitals

Read straight off the silicon.

Every number below was pulled from the chip over USB with esptool — not a datasheet guess. This is exactly what we're working with.

▚ the exact board — ESP32-C3 SuperMini + 0.42″ OLED on AliExpress ↗

01
Processor
RISC-V · single core · 160 MHz
02
Memory
~400 KB SRAM · no PSRAM
03
Flash
4 MB embedded (XMC)
04
Radio
Wi-Fi + Bluetooth 5 LE
05
Display
0.42″ SSD1306 · 72×40 · 1-bit
06
Link
USB Serial/JTAG · /dev/ttyACM0
05 — The Bard

The board writes you a story.

The Bard is the app that proves the platform: it is a plugin like any other, it shows up in the same one-button menu as the clock, and it is the reason the rest of this page is not a toy claim. Press the button and a real transformer starts composing — 260,000 parameters, int8, from the same llama2.c/TinyStories lineage the big toy models come from — on a chip with 400 KB of RAM and no floating-point unit. The weights never load: the model is executed in place from flash, memory-mapped, so all 283 KB of it costs zero bytes of RAM. There is no WiFi, no cloud and no gateway in the loop — a board alone in a drawer can tell a tale. Every node gets its own protagonist, so id8 tells owl stories and its neighbour tells dragon ones.

72×40 · SSD1306✒ composing

Typing at the board's real reveal rate — 160 ms per character, the firmware's own default. About five lines of fourteen characters fit at once, so the screen is a window the story moves past — not a container for the story. This excerpt loops; the board itself never stops.

Verified, not asserted

It really is a transformer, and we can prove it.

The interesting risk in a port like this is not that it crashes — it's that it runs beautifully and produces subtly wrong arithmetic. So the forward pass was pinned against two independent implementations: an int8-exact reference mirroring the accumulation order, and a from-scratch fp32 one written during review. It agrees token-for-token, 146 of 146 ids, full text. The obvious reference — upstream runq.c — was disqualified: it silently drops in-dimension elements 128..171 of every w2 row when the row length isn't a multiple of the group size. The golden gate is therefore cross-implementation, not cross-port.

260Kparameters, int8
283,096 Bblob, XIP from flash — 0 B of RAM
202 msper token on glass (274 max)
55,440 Bstack high-water — identical across 5 reports, so the endless window never creeps

🟢 On glass — a 67-token story on the bench board, then a canary soak that survived a hard power-cycle from the inactive OTA slot. Set Bard:0 from Home Assistant and a node boots straight into composing. And it no longer ends. A sliding-window rewrite (#302) removed the terminal state, so a tale runs indefinitely — verified on glass: the pace was changed from 160 to 120 ms/char mid-narration from Home Assistant, without a restart, and the stack high-water came back byte-identical at 55,440 B across five consecutive reports spanning many tale boundaries. A window that reused memory imperfectly would creep; this does not move at all. Honest edges: generation in endless mode runs 224–274 ms/token (max 281) — 10–35% slower than a bounded story, because attention now always spans the full window, and the spread tracks whether the radio is bursting, not the model. Verified on the bench and canary (id8); the fleet-wide roll rides the next release train. Endless mode is near-continuous compute, so it is the fleet's worst-case power draw — no battery-life claim is implied. · #300 ↗ · PR #301 ↗ · #302 ↗

The story on the left is not mock-up text. It is the actual deterministic output of the model blob committed to this repo — regenerate it yourself with tools/bard_reference.py and you will get the same owl, the same Jack, the same toys.

06 — Games

A game console you can lose in your pocket.

The games are plugins in the same registry as everything else — a fistful of them sharing the glass with a clock, a story engine and live telemetry, all in the one binary. The headline act is mesh multiplayer: a shared world every board roams at once, over an ESP-NOW mesh, no server in sight. The Block Digger up top is running its actual render logic in your browser — that's the kind of thing that fits.

◆ MMO · flagshiprunning fleet-wide
◇ world map — not what the glass shows
SHARED WORLD · 256² ◇ x128 y92 you ◈ Ashen Vigil Mystic Chalice ◈
and this is the glass — 72×40, the window your board sees

one board's viewport into the shared 256×256 world — peers dead-reckoned so a dropped frame never ghosts them

◈ mesh leaderboardexample · not a live feed
1Ashen Vigil214
2you · Eldritch Jewel188
3Mystic Chalice151
World Snake · MMO

One world. Every board. Over the mesh.

A single 256×256 world that every board's snake roams at once. Your viewport scrolls with your head — no walls, the world wraps — and rival boards slide into view drawn by their magical realm names. Score is length, and length is your seat on a mesh leaderboard every node agrees on. It runs on 18-byte state frames at 5 Hz; peers are dead-reckoned between frames, so lag never turns a snake into a ghost.

Flashed & running on all three bench boards (July 2026) (waves 3+4) · live N-board play is the last check · issue #5 ↗

  1. 1Tap the one button to turn — the only control the shared world asks of you.
  2. 2Eat to grow. On the mesh, length is rank.
  3. 3Peers roam into your viewport by name — real boards, live over ESP-NOW.
  4. 4Grab a treasure for a timed power, then outlast the field.
Treasure Powers

Six spells for the shared world.

In World Snake, rare treasures grant a single timed power. They spawn deterministically — every board computes the same treasure, in the same cell, from the shared mesh clock, so nobody negotiates — and each reads in a single bit of ink. All six are flashed + running on all three bench boards (July 2026) (waves 3+4, phasing tested 52/52). Names are drawn from the same realm-sigil corpus that names the boards.

◌
Wraith Veil
Phantom
Slip between worlds. Phase through every body — yours and theirs. For a few seconds nothing can kill you, and you kill no one; your snake flickers like a half-real thing.
tell · flicker~6 s
»
Zephyr Rune
Haste
The wind takes your tail. Move half again as fast — outrun a closing coil, or run a rival down before they wake to the danger.
tell · streak~5 s
◈
Aegis Ward
Shield
One killing blow turned aside. The ward drinks a single lethal hit and shatters. Spend it well — it will not come twice.
tell · haloone hit
✦
Midas Sigil
Golden Growth
Every morsel worth three. While it glows, food feeds you triple length — and on the mesh leaderboard, length is rank.
tell · sparkle~8 s
✧
Mothlight Lantern
Reveal
The dark gives up its secrets. Your compass blooms with every treasure and every rival on the field, wherever they roam.
tell · HUD glow~10 s
✷
Phoenix Ember
Rebirth
Death is a formality. Fall while the ember burns and you rise at once, whole — but the fire spends itself, and only saves you once.
tell · emberonce

One power at a time · effects expire on the shared clock · a lost frame can't leave you ghosted · player guide ↗

The lineup

More to play.

Everything below lives in the same binary — pick it from the BOOT-button menu. From the one that started it all to five games built for this exact board.

Block Digger · Bluetooth Stadia pad (Bluepad32)
◀ ▶ walk ▲ jump A dig B place
07 — Apps

Everything else it runs.

This is the rest of the registry. Every card is a plugin in the same binary, reached from the same button — no installs, no downloads, no second image. Some need no radio at all and run on a board with nothing but a battery; others are compiled in only at the mesh tier because they consume the roster. What decides is the build tier, not a setting. Each card links to its source.

The Mesh Familiar on glass
One creature lives on the whole fleet. It inhabits a single board — mood, hunger, growth on that OLED — and hops to a neighbour over the mesh when you unplug it. Non-holders point a Weasley-clock arrow to wherever it is; greet, call and feed it. Exactly-one-holder arbitration + migration + orphan re-election. Migration human-verified on glass. Issue #57.
#flagship #mesh #espnow
Clock flashed
NTP + Pacific time, big digits, chip-temp & battery readout. Now mesh-time-synced (boards agree over ESP-NOW, newest sync wins) and shows the board's magical name. Bare-metal Rust.
#rust #wifi #mesh #sensors
Benchmark flashed
Live ESP-NOW link test: FPS, TX/RX rate, round-trip latency, loss %, RSSI. A mode in the Rust firmware. Its mesh-view — a per-node table of magical names, RSSI, sync state and roles — landed fleet-wide (issue #8).
#espnow #diag
Walkie-talkie planned
Push-to-talk voice over ESP-NOW — feasible on the bare C3 with an I²S mic + amp. Parts in docs.
#voice #espnow
Relay Bridge verified
An in-range board relays a far peer's telemetry out: ESP-NOW → WiFi burst → Home Assistant over MQTT. Single-hop; browsing ruled out (250 B MTU). End-to-end proven on hardware and sustained — leaf → gateway → WiFi → HA, still flowing across a firmware upgrade. Fixes landed for a DHCP-budget stall and a drain race; one follow-up: a cold-ARP first-round retry. Issue #3.
#espnow #wifi #mesh
Signed OTA shipped
Over-the-air firmware with dual A/B slots and brick-safe rollback; each build wears a sigil VERSION name. Gateways self-update over WiFi (canary-one-board); the WiFi-less leaves update over the ESP-NOW mesh — the gateway relays an ed25519-signed image the leaf verifies before it flashes a byte. One runtime-NVS image serves the whole fleet. Hardware-proven — full ~1 MB images delivered over the mesh. Issues #6/#40/#32/#44.
#ota #ed25519 #mesh #sigil
Home Assistant Link verified
Every board mirrors live Home Assistant data on its OLED — battery voltage, a 6-segment state-of-charge page, and grid power — pushed over MQTT and cached so it survives the radio's sync gaps. MQTT-native, no cloud. Hardware-verified on all three bench boards (July 2026). Issues #10/#16/#17.
#ha #mqtt #display
No-Burst Mesh verified
Retire the burst + self-heal the mesh: the radio stays awake through a WiFi sync so the mesh never drops, and the single-active-gateway election auto-recovers a dead owner. Verified on hardware (all three bench boards (July 2026)): zero mesh loss across dozens of sync windows, same-channel reassociation, and fully-dark dead-owner takeover + split-brain co-boot. Caveat: cross-channel roam still not forced (#35, open). *(#31, the broker-dead-HELLOing residual, closed 2026-07-12; the live one to know about is #204 — a crown under bulk inbound goes downstream-deaf.)* Closes #23 + #14.
#mesh #espnow #wifi
08 — The config surface

Every knob, without a reflash.

A platform you have to re-flash to adjust is not much of a platform. smol boards take their settings over the air, from Home Assistant, on a keyed config channel (#56) — one retained MQTT message per knob, relayed on to the boards that have no WiFi of their own. Parsing is strict and panic-free by design: a malformed or unknown value is ignored and the board keeps what it had, because the failure mode of a config channel must never be a brick. Identity is the deliberate exception — a node’s id lives in NVS and OTA never touches it, which is exactly what lets one image serve the whole fleet.

S
Default screen
Which plugin a board boots into, and on which page. Set it from HA; it survives the next update.
L
LED & units
Peer-state LED mode and °C/°F, per node, live.
P
Plugin visibility
Which apps appear in that board’s menu at all — the registry is compiled in, but the menu is yours.
Y
Custom screen
Arbitrary text and entity values pushed onto the glass. The fleet uses it to ask a human to power-cycle a specific board.
G g
The IO registry
ESPHome inverted (#72): every digital driver is already in the image, and a runtime pin-map chooses which one binds to which GPIO at boot. Buttons, sensors, relays and LEDs, re-wired without a rebuild. Digital only so far.
B O
Broker & OTA host
Re-point a board at a different broker or update server, persisted in NVS, with a fallback that re-arms itself if the override stops answering.
09 — Fleet services

What you get for free by joining.

The interesting part of a platform is rarely the API; it is what a program gets simply by being on it. A board that joins the smol mesh inherits a set of services it never has to implement: it is discovered, named, timed, watched, and updated. None of that is app code. All of it is the reason an app on a $1 board can be three hundred lines long.

🔒
Signed OTA, over the mesh
Firmware is ed25519-signed and hash-verified into an A/B slot. A board with no WiFi at all is updated through the mesh by the gateway (#40), one canary board at a time — never broadcast to the fleet.
♛
Gateway election
Boards elect the best-connected node as gateway and re-elect when it stops answering. The fleet is never left without one, even when the only candidate is a poor one.
⏱
Mesh time
The newest authoritative NTP sync wins and propagates. A board that has never seen the internet still knows what time it is.
📡
Discovery
Each node publishes its own Home Assistant discovery config, so it arrives as native entities with no YAML written by hand.
⚖
Observability
A retained per-node health record — boot slot, reset reason, heap floor, link quality, update outcome — so a silent rollback is visible instead of mysterious.
★
Identity
Every board is given a name from a fixed corpus, derived the same way on every node, so logs and dashboards agree on what to call it.
10 — Get the firmware

Put it on a bare board.

Prebuilt images are on the releases page. You need a board, a USB cable and espflash. Per-target downloads have landed: the nightly now carries five images — the C3 fleet tier, both S3 flavours, the C6 watch and the C5 CYD's GUI — because the build walks the targets/*/target.toml manifests and packages every board that declares one, through the same reproducible-build path the over-the-air publisher uses. The two Xtensa images ride a job that installs their toolchain itself, which is why they exist despite CI being unable to compile that chip on a stock runner. Each image ships a NOTES.md beside it carrying its sha256 and that chip's stack-floor provenance in plain words — check the image against that number — or, if the release also carries a combined SHA256SUMS, against that with sha256sum -c --ignore-missing SHA256SUMS (the flag matters: you downloaded one image, not all of them). Which board gets which flavour is the capability matrix; the full release story — nightlies versus versioned releases, the reproducible-build ceremony, and the flashing traps — is in docs/RELEASES.md.

Read this first: published binaries carry placeholder credentials — including the mesh key. They are built from a clean checkout with throwaway values — good enough to compile, never good enough to ship — and the key is a fixed published placeholder on purpose, because a random per-build key would destroy the byte-reproducibility that makes an image's sha256 its identity. A board flashed from a download boots, drives the display and runs the menu, the games and the Bard. It will not join WiFi, reach a broker, or talk to an existing mesh.

🔑 So re-key before you trust your mesh. A key baked into a public binary is public: two boards flashed from the same download share a key anyone can extract. Placeholder-key boards can mesh only with other placeholder-key boards, and can never join a re-keyed fleet. To own yours, regenerate GROUP_KEY in rust/clock/src/secrets.rs — 32 random bytes, starting from secrets.rs.example — then rebuild and reflash. It is a rebuild, not a setting: the key is compile-time, and nothing on the dashboard can change it.

sha256sum <image>.bin   # compare with the sha in its NOTES.md
sha256sum -c --ignore-missing SHA256SUMS   # or this, if the release carries one
espflash erase-region --port /dev/ttyACM0 0xf000 0x2000
espflash write-bin --port /dev/ttyACM0 0x0 <image>.bin

The middle line clears otadata and is not optional on a board that has ever taken an update over the air: the board is running from the other slot, so a plain flash succeeds and then silently never runs. It spares nvs, so a provisioned node id survives. Afterwards, read the Loaded app from offset line — it names the slot that actually ran.

Boards already on the mesh do not update this way. They take ed25519-signed firmware over the air, one canary board at a time. Downloads here are for a board that does not have smol on it yet.

11 — Mission Control

How smol got built.

◇ The build ledger — 54 tasks and 35 agents, archived as of 2026-07-07. A record, not a live feed: the panels below show that snapshot, and each prints its own date.

This fleet updates itself. A team of agents built smol in the open — flashing firmware, wrangling git, and closing every wave behind an adversarial review gate — and it is now genuinely remarkable for $1–$3 boards: a shared-world MMO snake with treasure-powers; an ESP-NOW mesh with newest-wins time sync, realm-sigil names, and ~20-node-capable peer handling; an MQTT-native Home Assistant link (live battery, state-of-charge and grid on every OLED); remote per-node config straight from the dashboard (LED, units, plugin visibility, reboot, custom screens — even the MQTT broker leg and the OTA image host, and the physical pins: wire a button or lamp to a free GPIO and drive it from HA); per-node observability with config-drift detection and a live OLED mirror in HA; and — the headline — signed over-the-air updates that reach even the WiFi-less nodes over the mesh, plus The Mesh Familiar: one creature that lives on a board and hops to a neighbour when you unplug it. And tonight the fleet learned to heal its own updates: the WiFi-fetch deaf-window that stalled mesh-OTA was pinned at the packet level and answered two ways — proactive re-association (#217) and peer-sourced OTA (#237), where an updated board serves the next over the mesh so a single download seeds the whole fleet. And then the fleet learned to write: The Bard puts a real 260K-parameter transformer on the $3 chip, executed in place from flash, composing a fresh children’s story onto the 72×40 OLED at 202 ms per token — verified bit-for-bit against an independent reference before it was believed. And then it stopped being one chip: the roster is five targets across four chip families — RISC-V and Xtensa, monochrome and colour touch — with the ESP32-C6 watch in-tree as a subtree, an ESP32-C5 heard on the mesh, and an ESP32-S3 touchscreen glass-verified in August 2026 running both firmware flavours. Underneath it all, the async re-platform's first phase has merged. Hardware-verified on the fleet.

Build Tasks —

loading…

Agent Fleet —

loading…
12 — Roadmap

Where smol goes next.

Ideas we're kicking around. Edit this list as the project grows — it's your design doc.

◇ Design preview · not built

The dollhouse.

A vision for what a fleet of smols becomes at home: one ~$3 smol per room — an OLED panel showing who's home and what the light's doing, presence-driven. Scoped as issue #75, not yet built.

🛏️ Nursery · 👧 home 🛋️ Parlor · 💡 on 🍳 Kitchen · 🌙 dark 📚 Study 🛁 Bath 🏠 Attic · 🐈
Tap a room, shuffle the dolls, watch presence drive the lights — every panel on the real 72×40 OLED. Open the interactive dollhouse →
13 — Research

What the agents found.

A fleet of agents scouted the ESP32-C3 landscape while smol came together. The full reports live in the repo — here's the map.

14 — Built for this board

You're not alone.

Projects that run on this exact ESP32-C3 + 0.42″ OLED (72×40). A couple need their art re-drawn for the tiny screen — noted inline.

15 — Influences

Standing on shoulders.

smol didn't appear from nowhere. Its ESP-NOW → MQTT → Home Assistant plumbing is commodity — a well-trodden pattern many have built. What's actually novel is the whole: a no_std Rust game-console mesh on $3 boards, a deliberate single-radio burst time-share, and a retained-payload → mesh-rebroadcast downlink to display-only leaves. Below are the projects that shaped it, grouped by the thread each runs in — credited honestly, with what smol borrows and where it diverges. A few handed over portable ideas; most gave lineage, identity and nerve rather than code.

◇ Mesh & routing
◇ Local-first ESP ↔ HA
◇ Cheap-ESP audacity — smol's soul
◇ Remote-management UX
◇ Lean real-time embedded

◇ BLE presence (the #22 basis) — smol's beacon-tracking idea would build on Bermuda (HACS trilateration that reuses existing bluetooth_proxy anchors) and ESPresense (dedicated MQTT anchors). Prior art and reference, not code smol borrows.

16 — Pocket Watch

We designed smol a pocket watch.

No printable pocket-watch case existed for this board, so an agent generated one from scratch — a parametric round shell in the classic orientation: the chain bail and a removable crown-style USB-C cover stack at the top, with the OLED window, BOOT/RESET plunger bores and two clear-PLA LED light-pipes below. Inside: pockets for the board + a 502030 LiPo, a TP4056 charge slot, and a press-fit back lid. Three printable parts — body, lid, crown — and the OLED is rotated 180° in firmware so it reads upright when hung. These are live renders of the actual STL.

Pocket watch case, front view
Front — bail + crown at top, LED pipes below them, buttons flank the OLED
Pocket watch case, three-quarter view
¾ view — bail + crown + LEDs up top, USB-C on the rim
Removable crown / USB-C cover
The crown — a removable USB-C port cover
Diameter43.1 mm
Height28.8 mm
Battery502030 LiPo
ChargingTP4056 pocket
Partsbody + lid + crown
Watertight✓ printable
FilesSTL + generator ↗
17 — Print a case

Give smol a shell.

Exact-fit 3D-printable cases for the 0.42″ OLED SuperMini. No dedicated handheld shell exists yet — so for a pocket player, start from an editable one and add a button perch at GPIO9. Board is ~24.8×20.45 mm, USB-C on the short edge opposite the OLED. Full ranked list in docs/cases.md.

◇ Spread the word

Tell someone a $3 chip wrote a story.

smol is a hobby fleet, not a product — there is nothing to buy here and nothing to sign up for. If it made you want to solder something, that's the whole payoff. Send it on.