SDK
How Miblo animates a screen with 80 KB of RAM
Miblo's memory: what is fixed, what a scene costs, the 8 and 12 KB floors, what is lent to the network and how to read the diagnostics when a scene does not load.
Miblo has 80 KB of RAM for everything: the firmware, Wi-Fi, your sessions, the pet, the card and the app's scene on the screen. There is no frame buffer (a picture of the screen would take 115,200 bytes) and no virtual memory: what does not fit does not run. This page says where that memory goes, what a scene costs, what Miblo lends to the network and how to read the diagnostics when a scene does not load. The animation guide (Animations on Miblo's screen) explains how to animate; this is the arithmetic behind it.
The budget
Of the 80 KB, the firmware takes a fixed part, there before any connection: 51.3 KB on Miblo 1.28.1 (54.8 KB on 1.28.0). The rest is the heap, which feeds the Wi-Fi's buffers, every open connection, every request from the bridge while it is read, the app's card, the pet's buffers, the items' captures and the scene. On a paired Miblo with a card set and the bridge's stream up, about 17 to 19 KB are free between one request and the next (measured on Amon). A scene has to fit inside those KB, beside the floors below.
Three numbers organise everything:
| Number | What it is |
|---|---|
| 12 KB | The network's reserve. A request that needs N bytes (its body, the JSON document of its answer) is served only when N + 12 KB are free, in one whole block of N + 4 KB; otherwise the answer is 503 busy. It is what keeps Wi-Fi alive |
| 8 KB | The player's floor. A scene loads when its need + 8 KB are free (in a block of need + 4 KB), and gives its memory back when the heap drops under 8 KB after a request |
| 16 KB | The reserve of the ordinary off-screen drawing layers (a filled mesh item, for instance). The items' composition strips do not use this reserve: they use the 8 KB floor |
What is fixed
Inside the 51.3 KB are the firmware's state, the fonts and drawings kept in RAM, the sessions' limits, the alert queues and the formats of its own. Since 1.28.1 three things that were fixed follow the use instead:
The sessions' texts. The names, tools, models and details of the snapshot's rows sat in a fixed 1,536-byte block. They now live in a pool sized to the content, with every repeated text stored once: 20 typical sessions use 234 bytes. The cap is still 1,536 bytes (a larger snapshot leaves its last rows without text, never a cut text). That is 2.4 KB more free.
The pet's buffers. A bitmap pet (MPET1) draws from a 1,152-byte frame; a vector pet (MPET2) uses scratch tables. Miblo gives those buffers back whenever the pet is off the screen (the App screen, the Overview, an app alert, the display off) and when the pet's format changes; the logo and the identity stay, and the pet's next draw reloads what it needs.
The constants. Some of Miblo's own formats moved to the flash.
The app's card, when there is one, takes up to 1 KB of heap while it is on the screen.
What a scene costs
A tile scene (the screen.animate path for art that fits in 256 tiles, with or without a per-line effect) costs three parts in RAM:
| Part | Bytes |
|---|---|
| The resident tiles | the set's compacted size (below) |
| The player | 3,104 |
| The palette cache | 528 |
The tiles are the part that varies. In the file every tile takes 32 bytes (64 pixels of 4 bits). In RAM Miblo keeps each one in the shortest form that reproduces its pixels exactly, decided when the scene loads:
| Colours in the tile | How it is kept | Bytes |
|---|---|---|
| 1 | a constant | 1 |
| 2 | two indices and 1 bit per pixel | 9 |
| 3 or 4 | four indices and 2 bits per pixel | 18 |
| 5 to 16 | the file's 4 bits per pixel | 32 |
Plus a 2-byte directory entry per tile. The compact form is used only when the total, directory included, is smaller than the file's 32 bytes × tiles; no colour is dropped, nothing changes in the file or in the SDK, and drawing keeps reading from RAM, never from the flash. The Radar, with 237 tiles, went from 7,584 to 3,816 bytes of tiles: the whole scene from 11,724 to 7,448 bytes. With the 8 KB floor it loads when about 15.6 KB are free in a block of 11.5 KB. A small scene (few tiles, flat colours) costs 4 to 5 KB and loads with about 12 to 13 KB free.
What helps, in the art: fewer distinct tiles and fewer colours per tile. A flat background is one byte per tile; a gradient is a 32-byte tile in every cell. The rules of the guide's chapter 4 hold here for the same reason they hold for speed.
What helps, in the fps: nothing. The rate does not change a scene's memory. The player reads each step's map from the flash in 512-byte pieces; a step that only moves sprites does not read the map again.
Items over the animation
The card is drawn over the scene. A still item (big, text or bar with no fx and no live) is composed in memory with the animation's pixels, strip by strip of 240x16 pixels and only on the rows where it has ink, and goes to the panel in one push: the panel never receives the background without the letters. That holds the first time the item appears, when its value changes and at every frame moving under it.
The memory of that:
A 240x16 strip at 4 bits asks for 1,953 bytes. It is admitted with the 8 KB floor (not the 16 KB reserve of the ordinary layers), so it fits beside a loaded scene.
When memory is to spare, Miblo keeps the capture of every still item (up to 6 KB for all of them, taken only from what is left above 12 KB + 4 KB) and composes without drawing the item again. When it is not, it composes with a temporary strip and returns it before the network needs it.
The captures are the first memory Miblo lends, before the scene.
An item with fx, a ring, a spark or a mesh does not go this way: at every step its box is repainted whole (the animation's pixels first, then the item). That is why the guide asks for small items and few effects over the art.
What is lent, and when
A scene is a cache of the flash: Miblo may drop it at any moment and load it again where it was, at the same step, with the effect's clock carrying on. While the memory is lent, the scene's picture stays on the glass (there is no frame buffer to erase), and the items keep being drawn the old way.
The order of the loans, for a request that does not fit with the 12 KB reserve:
The items' captures go first (their budget goes to zero).
The scene goes next, if the request asks for more than 2 KB, or if, asking for less, it does not fit beside the scene with 8 KB to spare. A small request (a poll, a 1 KB document) runs beside the scene.
And the floor: after a request, when under 8 KB are left with the scene loaded, the captures and then the scene are given back to the network.
The return:
Never while a request is being read.
After a hold: 150 ms after a loan to a request, 2 s after the floor.
When the scene's need + 8 KB are free (the captures count as free: they go first) in one whole block of need + 4 KB. A shortage at that instant is tried again 250 ms later; a file that does not open or a player that fails, 5 s later.
Only with the App screen on the panel. Three seconds after the App screen leaves, the scene's memory is given back for good.
POST /api/screen/anim-play answers 503 busy only when the scene would not fit even after the request is over (need + 8 KB over the free heap + what is reclaimable + 4 KB). Otherwise it is taken and loads when the heap has room.
How to read the diagnostics
Everything below is read on the device, with the paired computer's token (Authorization: Bearer), or through the bridge where said.
GET /api/info
| Field | What it is |
|---|---|
heap | The free heap at the instant of the answer, inside the handler, after it allocated its own document (~2 KB). Do not compare it with idleHeap |
idleHeap | The free heap sampled by the main loop, outside any request. This is the number to compare between readings and between versions |
maxBlock | The largest whole free block. Much smaller than idleHeap means fragmentation |
minHeapParse | The lowest free heap seen while a snapshot was read |
petDrawingBytes, guestDrawingBytes | The drawing buffers of your pet and of the visiting pet, if loaded (0 when given back) |
petLogoBytes | The custom pet's logo, which stays |
snapshotTextBytes, snapshotTextUsed | The sessions' text pool: allocated and used |
GET /api/screen
| Field | What it is |
|---|---|
panel | What is on the glass now: app, main, desk, summary, alert, pet, limits, preview, boot, setup, code, disconnected, updating, off (display off) or other. shown only says whether the App screen is pinned; it does not say it is on the glass |
anim.loaded | The scene is loaded in RAM |
anim.yielded | It is not, because its memory was lent; it comes back by itself |
anim.need | What loading costs: compacted tiles + player + cache (while it is not loaded) |
anim.why | While it is not loaded: how many passes of the loop were refused for each reason since the last load. notShown (the App screen is not on the panel), pending (a request being read), hold (the wait after a loan), free (need + 8 KB were not free; lastFree says how much was), room (no whole block; lastFree and lastBlock), failed (the file or the player) |
anim.yields, anim.yieldNeed | How many times the scene's memory was lent to a request since it started playing, and what the last one asked for |
anim.floorYields | How many times it was given back by the 8 KB floor |
anim.compose | ok: strips composed over the animation; noLayer: strips that did not fit in memory at that instant (the item was repainted the old way); inexact: captures that did not represent the item exactly (the same). ok counts strips, not frames, and an item drawn from its kept capture does not count |
anim.heap, anim.tileBytes, anim.tileRawBytes | With the scene loaded: what it takes, the compacted resident tiles and what they would be uncompacted |
anim.real | { fps, drawMs }: the rate achieved and the last draw |
governor | Every animating kind (anim, fx, meshWire, meshFlat, transition, live): its current fps, its last draw and the average |
miblo screen status
The bridge reads loaded, yielded, why and yields from the device and prints a reason on the animation's line:
Animação: blocos de Clima · 8 fps pedidos · — fps real · — ms · não carregada (sem memória livre: 7,9 KB livres).
Animação: blocos de Radar · 10 fps pedidos · 9 fps real · 48 ms · memória emprestada 3×.panel, floorYields and compose do not go through the bridge: read them on the device.
Why the scene did not load
miblo screen status says | anim.why | What is going on | What to do |
|---|---|---|---|
| "a tela do app não está no Miblo" | notShown | The App screen is not on the panel (another screen, the rotation on another app, the display off). The scene loads only with it on the glass | Nothing: it loads on the app's turn. Check panel |
| "uma requisição em curso" | pending | The bridge is sending something (a card, a live batch) on every pass | Normal for moments; constant, it is a bridge sending too much (a card every second, for instance) |
| "devolvendo a memória emprestada" | hold | The 150 ms or 2 s wait after a loan | Nothing |
| "sem memória livre: N KB livres" | free | need + 8 KB were not free. lastFree says how much was | A smaller scene (fewer distinct tiles, fewer colours per tile); fewer things open on the Miblo (another computer's stream, a visiting pet) |
| "sem um bloco de memória inteiro: N KB livres" | room | There are free bytes, but not in one block of need + 4 KB: fragmentation | As above; maxBlock in /api/info shows the block |
| "o arquivo ou o player falhou" | failed | The file in the slot does not open or does not validate | Send the scene again (screen.animate); the SDK rewrites the slot |
| "(memória emprestada a uma requisição)" | yielded with no counts | The scene was loaded and gave way to a request; it is back in 150 ms | Nothing |
| "memória emprestada N×" | yields | The scene plays, but gives way often | Normal a few times a minute; every second, it is the bridge sending requests too large (big cards, snapshots every second) |
A scene stuck on free or room with the App screen on the glass is this page's arithmetic: its need (anim.need) + 8 KB does not fit in idleHeap. The anim.compose numbers say whether the items are being composed (ok growing) or repainted the old way (noLayer growing): in the second case the heap is at its limit during the draw, even with the scene loaded.