<feed xmlns='http://www.w3.org/2005/Atom'>
<title>pardes.git/src/board_memory.zig, branch release-0.24</title>
<subtitle>Pardes</subtitle>
<id>https://git.0x4200.cafe/pardes.git/atom?h=release-0.24</id>
<link rel='self' href='https://git.0x4200.cafe/pardes.git/atom?h=release-0.24'/>
<link rel='alternate' type='text/html' href='https://git.0x4200.cafe/pardes.git/'/>
<updated>2026-09-07T16:59:12Z</updated>
<entry>
<title>Refactor panes and filesystem; replace FUSE with 9P</title>
<updated>2026-09-07T16:59:12Z</updated>
<author>
<name>Gabriel Schneider</name>
<email>gbrls@0x4200.cafe</email>
</author>
<published>2026-09-06T21:11:36Z</published>
<link rel='alternate' type='text/html' href='https://git.0x4200.cafe/pardes.git/commit/?id=60367d8fe23f6af98ec28e3cf6c2094dfe332df0'/>
<id>urn:sha1:60367d8fe23f6af98ec28e3cf6c2094dfe332df0</id>
<content type='text'>
Consolidate pane, layout, memory and host code. Serve 9P by default over Unix sockets, with runtime mounts and optional TCP/QUIC transports. Remove FUSE and obsolete proof-of-concept examples.

Fix highlighting and terminal-history performance, expand differential and stress-test infrastructure, sort navigation results while preserving the next occurrence, add syntax-colored Braille minimaps, remove SPC-k, and document 9P interaction as a repository skill.
</content>
</entry>
<entry>
<title>9p: the client half, and a board that serves its own tree over the UART</title>
<updated>2026-08-28T01:07:32Z</updated>
<author>
<name>Gabriel Schneider</name>
<email>gbrls@0x4200.cafe</email>
</author>
<published>2026-08-27T19:42:15Z</published>
<link rel='alternate' type='text/html' href='https://git.0x4200.cafe/pardes.git/commit/?id=147ebd4a36ec7199074ba05bcfb79d4a656c0b74'/>
<id>urn:sha1:147ebd4a36ec7199074ba05bcfb79d4a656c0b74</id>
<content type='text'>
Step 5 of the 9P chain (docs/9p.typ 12.5, docs/registry.typ 9P-22, 9P-11, BOARD-1).

THE CLIENT. `Client` in src/9p.zig is the mirror of `Server` and the same shape:
sans-io, no allocator, no threads, no descriptor, caller-owned buffers, and it
builds freestanding. 152 bytes of struct against the server's 9,488, because a
client owns neither a fid table nor a park table -- the far end does.

The API is submit / push+output+wrote / take. Completion is a PULL: a callback
would fire inside push, inside the transport's read, inside the host's poll
dispatch, which is exactly where fs9_service says filesystem work must not
happen. `take()` returns the next completed operation or null, which is
`Server.next()`'s loop-until-null contract read from the other side. Tags are a
fixed 16-entry table indexed BY the tag, so an out-of-order reply -- which 9P
allows and both reference clients rely on -- costs one bounds check. The reply's
TYPE is checked against the request's op, because a tag is only as good as the
table behind it. A `Done` borrows the input buffer and is valid until the next
call; `take()` releases the previous frame on entry, so the rule is mechanical
rather than remembered, and read data and error strings are zero-copy.

And one real caller, so this is not a library with no user: the `9p` word takes
a dial and a path, walks another instance's tree, and opens the bytes in a pane
like any other `Look`.

THE BOARD. A SECOND image, not a second role: the console runtime keeps UART0
bidirectionally and is behaviourally untouched. On the new one the UART carries
9P AND NOTHING ELSE -- no ANSI, no vaxis, no allocator, no heap module. The loop
is uart.read -&gt; push / retry+next -&gt; handle -&gt; reply / output -&gt; writeSome -&gt;
wrote. `writeSome` is new and additive: `write`'s bounded spin DROPS bytes on a
stalled transmitter, which on a protocol stream truncates a reply mid-message
and desynchronises for good, where a short count cannot. BOARD-1's one divider
write raises the line to 921600.

  88,000 B text, 49,424 B bss, an 88,080-byte image -- 5.7% of the 1,536,000 B
  partition, against the console image's 809,536 B.

THE COMPTIME BRIDGE, which is the part worth reading. `board9p.caps` is the ONLY
place the GPIO tree is described; node ids, parents, names, permissions,
handlers, buffer size and the per-pin directories are all derived from it, and
`fan.dirs` makes `gpio/&lt;n&gt;/value` one table entry serving eleven pins. Modes are
derived from which handlers a file has rather than declared. A second capability
is a table entry, not new tree code.

JP1 became a real table in the new leaf `src/board_pins.zig`, with the ASCII
drawing RENDERED from it at comptime and the pin list COLLECTED from it -- the
9P image links no core and so cannot import board_memory.zig, and copying the
table was not acceptable. A golden test pins the drawing byte for byte, the
console's own shape test still passes, and the identical bytes are present in
all three artifacts.

PROVED. Two daemons: B read A's `/1/body` through the `9p` word into a pane,
byte-identical to plan9port's `9p read` of the same path. Both board images
build. No hardware was attached, so nothing about the board is claimed beyond
what builds and what the host tests cover.

zig build unit-test 585/585. fs-bench unchanged and still zero allocations on
every read row.

---

REVIEW FIXES FOLDED IN. Steps 3, 4 and 5 were verified on the happy path and
then adversarially reviewed by three agents; eight defects, six fixed here, five
of them reproduced with measurements before and after. Full writeup in
docs/registry.typ `9P-27`. In brief:

  * a remote crash of the WHOLE daemon: one `size[4]` of zero plus one byte hit
    `unreachable` in `fs9_service.fill`. Also 99.7% of a core when the stuck
    buffer made `room == 0` return without reading. Now `srv.dead` is a hangup,
    checked before the room guard.
  * the editor froze 177 s on a dial: `connect(2)` ran on a still-BLOCKING
    socket before the deadline existed, and a full accept backlog waits forever.
    Now non-blocking with the wait spent against the budget. After: 2.03 s.
  * a 64 KiB pty read is exactly `queue_cap` and wiped every unread byte AND
    dropped itself. `notePtyOutput` splits at half the cap. Deterministic.
  * four silent sockets denied `--fs9` forever; connections now expire on the
    same five-second rule the frontend transport already had.
  * EMFILE spun a core; the listener pauses and leaves the poll set, as the
    frontend listener does.
  * `max_fids = 32` made `find` over `9pfuse` fail with 57 consecutive
    `Rerror`s -- refuting this step's own acceptance clause. 256 for a host,
    `board_fids` 32 for the microcontroller.

Found clean and worth recording: `sig` reaches the foreground process group; the
two-namespace pty lookup is right over both transports; `PaneFile`'s u4 wall is
guarded; reader counts release on every abrupt-death path; `fs_origin` routing
and the reply arithmetic hold under probing.
</content>
</entry>
<entry>
<title>One core behind N frontends, the board's own runner moved in, and every board cap on one screen</title>
<updated>2026-08-27T12:47:39Z</updated>
<author>
<name>Gabriel Schneider</name>
<email>gbrls@0x4200.cafe</email>
</author>
<published>2026-08-26T16:27:46Z</published>
<link rel='alternate' type='text/html' href='https://git.0x4200.cafe/pardes.git/commit/?id=11f380f6d7222f2cad93c2cdf13701ea1f903d47'/>
<id>urn:sha1:11f380f6d7222f2cad93c2cdf13701ea1f903d47</id>
<content type='text'>
## The wire is the effect stream, not a new protocol

`pardes --detach` leaves a core running with no terminal; `pardes --attach` is a frontend that owns
a terminal and a socket and nothing else. N frontends on one core all look at the same screen —
`screen -x`, not N sessions.

The codec (`src/detached/wire.zig`) carries exactly one `Event` or one `Host.VTable` call per
message. That is not a coincidence and it is why there is no third vocabulary to keep in step: the
core's IO seam was already a struct of function pointers with plain-data arguments, so a socket is
a legal implementation of it. `nested.zig`'s socket could not be reused — it carries a builtin
command line, and a command line cannot carry a frame.

ARCHITECTURE-NEUTRAL on purpose, not as decoration. The frontend on the far end may be
riscv32-freestanding on the ESP32-P4 while the core is x86_64 Linux, so every field is an explicit
little-endian fixed width and no message is a blit of a native struct. A protocol that only works
between two builds of the same compiler would have thrown away the one frontend that motivated it.

## The board comes in; its toolchain stays out

`src/p4.zig` becomes `src/esp32p4.zig`, and the pardes half of `../05-zig-p4` — the vaxis-over-
serial runner, the UART editor terminal, the keystroke rescue ring, the on-die test suite — moves
into `src/esp32p4/`. `build.zig.zon` gains `.zig_p4 = .{ .path = "../05-zig-p4" }`, so
`zig build -Dplatform=esp32p4 -Desp32p4-firmware` builds, flashes, monitors and self-tests the
board from this repo's `build.zig`.

The DIVISION is the point. What moved is what only pardes wants: the runner that drives a pardes
core over a serial line. What stayed is everything a second project would also want — the HAL, the
register/radio/oracle layers, the linker script, `_start`. `zig_p4` declares no dependencies of its
own and its `build()` early-returns when it is not the root package, so this costs the package
graph exactly zero packages and the editor's own builds nothing at all.

## limits.zig: nine forgettable places become one budget

Nine `platform == .esp32p4` capacity tests lived in nine files. They were never nine decisions —
they are ONE decision, how much memory this build may spend, taken nine times where no reader could
see the total. `src/limits.zig` puts the whole budget on one screen with every cap named against
what it is measured against, derived from two booleans.

The payoff is testability on a machine that is not the board: the caps are ordinary comptime values,
so a host build can be compiled against the board's numbers and the parking, eviction and clamping
paths a 240 KiB core takes get exercised by the normal test suite instead of only over a UART.

## A bare `zig build`

`zig build` with no arguments now builds the tty and GUI binaries and installs them into
`~/.local/bin`, and says so once on stdout with the flag that overrides it. The old default built
one binary into `zig-out` — a path nothing on a `PATH` ever looks at, which made "build it" and
"use it" two different commands for no reason.
</content>
</entry>
<entry>
<title>A Gpio word that flips one pin, JP1 drawn in ASCII, and these words only on the P4</title>
<updated>2026-08-26T15:40:03Z</updated>
<author>
<name>Gabriel Schneider</name>
<email>gbrls@0x4200.cafe</email>
</author>
<published>2026-08-26T15:40:03Z</published>
<link rel='alternate' type='text/html' href='https://git.0x4200.cafe/pardes.git/commit/?id=fbc194068687e49a8490c85c9f1257a2f2bb9079'/>
<id>urn:sha1:fbc194068687e49a8490c85c9f1257a2f2bb9079</id>
<content type='text'>
## Gpio

`Gpio 33` flips one pad and answers on the message row with what it did:

    GPIO 33: 0-&gt;1
    GPIO 33: 1-&gt;0

Bare `Gpio` draws the header instead, because the first question about a header is which pins it
has. The pin number is DECIMAL and it is the only literal in board_memory.zig that is - every
other one is an address, and addresses come off datasheets and linker maps that print hex, which
is why that file made everything hex two commits ago. A GPIO number is not an address, it is part
of a NAME: the schematic says GPIO47, the datasheet's pin table says 47, and `Gpio 20` meaning pin
32 would be a trap laid for the one argument anybody types from memory.

## The toggle is the host's, not the editor's

New `Host.VTable.pull_gpio_toggle`, and a `GpioFn` in the p4 ABI (hence version 2), rather than
board_memory reaching for GPIO_OUT the way `Poke` two functions above it would happily do.

Writing that register is not the job. A pad has to be pointed at the GPIO function in the IO MUX,
routed in the GPIO matrix, given drive strength and an input buffer with its pulls cleared, and
only then driven - four register files behind a per-pin table. That code already exists in
`05-zig-p4/src/hal/gpio.zig`, it is the same `configureOutput` the blink demo has always used, and
its register numbers are checked against ESP-IDF's own headers on the die by `zig build diff`. A
second copy inside the editor object would be a second copy under no test, and getting it wrong on
a pin that boots as something else is how you lose the console you are typing on.

Reported levels are the OUTPUT bits, before and after, because that is what a toggle means: the
level this board is driving. A pad's input buffer on an unconnected header pin reads the air.

## JP1, read off the schematic rather than remembered

The diagram is the vendor's own wiring, from sheet 2 "Expand IO" of
`01-esp32p4-m3/docs/JC-ESP32P4-M3_schematic.pdf` - the only document that carries this mapping. The
specification PDF's "Interface Description" page turned out to be a marketing render, and there is
no board user guide; the chip datasheet has a package pinout, which is not a header.

That sheet is a 872x1168 raster (`pdfimages -list` - the PDF embeds no vectors, so rendering it
larger adds nothing), and at that size the rows around pin 14 are genuinely ambiguous by eye. So
the mapping came from the drawing's geometry instead: thirteen wires leave each side of the symbol,
a net wire runs ~100 px to its label and a power stub ~21 px. Pin 8's wire is 21 px, which is what
identifies it as unconnected rather than as the first of the GPIO4x labels - the reading that had
GPIO47 one row higher and shorted GPIO45 to the ground bracket.

Cross-checked against a second source that has been in the tree all along: `05-zig-p4/build.zig`
documents `-Dled=20` as "JP1 pin 17", and GPIO20 lands on pin 17 here. Both facts are asserted in
the test, so the diagram cannot drift from either.

## Peek, Poke, Hexdump and Gpio are now the P4 build's alone

`board_memory.enabled` was `os.tag == .freestanding and !isWasm()`, on the argument that these
words are a property of having no operating system rather than a product configuration, and that a
predicate spelled out of `builtin` cannot drift the way a hand-maintained enum can.

Tidy, and it answered the wrong question. A word only exists if some shell offers it, and the
shells are the platforms. `Gpio` settles it beyond argument: its whole content is one board's
header, and a second freestanding port would need its own pinout rather than inheriting this one.
"Bare metal" was never the requirement, "this board" was, and the two only looked identical
because there is currently one of them. The old predicate's real work was excluding wasm -
`freestanding` too, where an address is an offset into a linear memory the engine owns - and naming
`p4` excludes it by construction instead of by a term somebody has to keep remembering. The target
is now the witness rather than the gate.

Absent means not compiled: the tty binary contains no `+Gpio`, no `+Hexdump`, no `ES_I2C_SDA` and
no `MisalignedAddress`.

## The boot buffer's lines are checked, not eyeballed

Three times now a line in that tour has been one or two characters too long for a 56-column grid,
and every time it was found by reading the die's screen - the expensive way to measure a string
literal. The text is a named `boot_buffer` with a test over it, six lines came down to fit with
margin, and the tour gained `Gpio`.

Tests: the pinout's width, its thirteen aligned pin rows, GPIO20-on-17 and pin-8-unconnected; the
decimal-versus-hex distinction; every boot-buffer line. Full suite green - unit-test, snap 95/95,
hxdiff 481/0, hxparity 561/0, image-harness, pdf-harness, mupdf-check - and tty, p4, gui,
p4 at 80x24, p4 with the fade forced on. On the die `p4-bench --check` is 5/5, the fifth being a
new one: three `Gpio 33` runs must report 0-&gt;1, 1-&gt;0, 0-&gt;1, because the alternation is the only
oracle a hardcoded string could not fake.
</content>
</entry>
<entry>
<title>Drop the 0x from what Peek, Poke and Hexdump print</title>
<updated>2026-08-26T14:34:02Z</updated>
<author>
<name>Gabriel Schneider</name>
<email>gbrls@0x4200.cafe</email>
</author>
<published>2026-08-26T14:34:02Z</published>
<link rel='alternate' type='text/html' href='https://git.0x4200.cafe/pardes.git/commit/?id=18a8e39c551e08eca9ed26e2c13580ccdd36b955'/>
<id>urn:sha1:18a8e39c551e08eca9ed26e2c13580ccdd36b955</id>
<content type='text'>
Two columns back, and a better reason than the two columns.

Every number these words read is hex - there is no other kind, and they refuse a decimal
one - so a prefix on the output restates what the whole file already says. Dropping it buys
something worth more than the width: an address in a dump can be typed straight back into a
Peek without editing it, because bare hex is exactly what the parser now wants. Output that
is valid input beats output that is decorated.

No platform question to answer either: `enabled` is freestanding-and-not-wasm, so these
three words exist only on bare metal. There is no host format to stay consistent with.

On the die, 44 columns of a 48-column body:

    40000020  32 54 cd ab  00 00 00 00  |2T......|
    40000030  30 2e 31 00  00 00 00 00  |0.1.....|

    5011002c: wrote deadbeef, reads deadbeef
    5011002c: deadbeef
    501101a4: fc48777d
    501101a4: 4b4ae238

The last two are the same command twice - LP_SYSTEM_REG_RNG_DATA, which is what makes it
the honest demonstration that a register is not memory.

unit-test, and `p4-bench --check` 4/4 on the board.
</content>
</entry>
<entry>
<title>Fit Hexdump to the board's width, and cut the boot buffer down to addresses</title>
<updated>2026-08-26T14:11:46Z</updated>
<author>
<name>Gabriel Schneider</name>
<email>gbrls@0x4200.cafe</email>
</author>
<published>2026-08-26T14:11:46Z</published>
<link rel='alternate' type='text/html' href='https://git.0x4200.cafe/pardes.git/commit/?id=fc7d3274c41ebc83911c5a6d1225cecea270cc86'/>
<id>urn:sha1:fc7d3274c41ebc83911c5a6d1225cecea270cc86</id>
<content type='text'>
## Eight bytes a row on the P4

`hexdump -C`'s sixteen needs 79 columns: ten for the address, forty-eight of hex, a gap,
and eighteen of ASCII gutter. The board drives 56 columns of which seven go to the line
numbers, so every row wrapped onto a second display line and the columns stopped lining
up - which is the entire value of the layout. Eight fits in 46 and keeps every property
that matters, including a gap at the halfway mark, because the eye counts in fours and
eights rather than in sixteens.

Verified on the die:

    0x40000020  32 54 cd ab  00 00 00 00  |2T......|
    0x40000030  30 2e 31 00  00 00 00 00  |0.1.....|

That is the app descriptor: 0xABCD5432 and the version string, read out of flash by a
command typed with no 0x on either argument.

## The boot buffer is shorter, and its addresses are named

The first draft opened with four lines of prose explaining that there is no operating
system. True, unhelpful, and it cost a third of a fourteen-row window before the first
command. One header line earns its place; the rest of the screen is addresses.

The two LP registers at the end are now named, because they are named in ESP-IDF's own
headers and the names are the interesting part: 0x5011002c is LP_SYSTEM_REG_LP_STORE0, a
general-purpose retention register that holds what you put in it, and 0x501101a4 is
LP_SYSTEM_REG_RNG_DATA, the hardware random generator. Between them they demonstrate the
whole point of a volatile read - one address gives back what was written, the other never
gives the same answer twice:

    Poke 5011002c deadbeef  -&gt;  0x5011002c: wrote 0xdeadbeef, reads 0xdeadbeef
    Peek 5011002c           -&gt;  0x5011002c: 0xdeadbeef
    Peek 501101a4           -&gt;  0x501101a4: 0x0b099791
    Peek 501101a4           -&gt;  0x501101a4: 0xfc97f3b7

All four run on the die, all with bare hex. Peek and Poke had not been tested there before
this - only Hexdump had, which I had let stand as though it covered all three.

snap 95/95, hxdiff 481/0, hxparity 561/0, unit-test, tty/p4/gui.
</content>
</entry>
<entry>
<title>Every literal in Peek, Poke and Hexdump is hex; boot the P4 into a tour of the bus</title>
<updated>2026-08-26T12:56:04Z</updated>
<author>
<name>Gabriel Schneider</name>
<email>gbrls@0x4200.cafe</email>
</author>
<published>2026-08-26T12:56:04Z</published>
<link rel='alternate' type='text/html' href='https://git.0x4200.cafe/pardes.git/commit/?id=27a749d3a2155419fb72bab66b1e78bcd99882ae'/>
<id>urn:sha1:27a749d3a2155419fb72bab66b1e78bcd99882ae</id>
<content type='text'>
## Hex, always

Base-0 parsing accepted `0x4ff40000` and `1341390848` and refused a bare `4ff40000`, on
the grounds that guessing between hex and decimal would let one typo address somewhere
else entirely. The reasoning was sound and the conclusion was still wrong: the ambiguity
it guarded against is not a real one. Every address anybody has ever typed at these three
words is hex - it came off a datasheet, a linker map, or a previous dump's own output, all
of which print hex - so the base was never in doubt, and demanding `0x` on every one of
them was a toll on the common case to protect a case that does not arise.

The COUNTS go with them, and that is the part worth saying out loud rather than leaving as
a surprise: `Hexdump 4ff40000 100` shows 0x100 bytes, which is 256, not one hundred. One
rule for every literal beats two rules that each fit their own argument better, because
the second kind has to be remembered at the moment you are concentrating on something
else. What these words PRINT is hex too now, clamp notes included, so a number can go back
in where it came out.

## And the board boots into somewhere worth looking

The empty output buffer was honest and useless. The three words that make this port
interesting all take an address, and a board's address space is precisely the thing you
cannot guess - so the boot buffer is now a tour of it: the image's own rodata and code in
flash, the firmware's data and the editor's heap in L2MEM, the mask ROM, UART0, the
systimer, GPIO_OUT and an IO_MUX pad, and one harmless Poke.

Every address comes from this repository rather than from memory, which is what makes them
worth trusting: the flash and RAM figures are the linker script's own ORIGINs in
`05-zig-p4/build.zig`, and the peripheral bases are the `DR_REG_*` values `05-zig-p4/src/hal`
uses. Each command sits alone on its line because an argument list ends at the last
argument - a trailing comment would be `ExtraArgument` - so the notes go above the lines
they describe. Lines are kept inside 48 columns because the first draft wrapped every one
of them at the 56-column grid, which reads like a bug.

Verified on the die: the buffer renders one line per line, and putting the cursor on
`Hexdump 40000020 60`, selecting with `x` and pressing Tab opens a dump whose first bytes
are `32 54 cd ab` - 0xABCD5432, the ESP app-descriptor magic - with the version string
right behind it. Bare hex, no prefix, reading real flash.

snap 95/95, hxdiff 481/0, hxparity 561/0, unit-test, tty/p4/gui.
</content>
</entry>
<entry>
<title>A fourth platform: pardes as ESP32-P4 firmware, bytes in and bytes out</title>
<updated>2026-08-25T20:13:54Z</updated>
<author>
<name>Gabriel Schneider</name>
<email>gbrls@0x4200.cafe</email>
</author>
<published>2026-08-25T16:01:13Z</published>
<link rel='alternate' type='text/html' href='https://git.0x4200.cafe/pardes.git/commit/?id=939e3a7288d6782139cac36f091ccd1d41cdfc0a'/>
<id>urn:sha1:939e3a7288d6782139cac36f091ccd1d41cdfc0a</id>
<content type='text'>
`-Dplatform=p4 -Dtarget=riscv32-freestanding` emits a single freestanding OBJECT
exporting a seven-function C ABI, not an executable. The board's toolchain
(../05-zig-p4) owns `_start`, the linker script and the UART driver and links this
in. The seam is bytes rather than types, so neither side can accidentally depend
on the other's internals, and a signature that drifts fails at link time.

The serial line is the whole of the I/O. `src/p4.zig` drives vaxis unchanged over
it: the renderer is a byte writer and `queryTerminalSend` is a byte writer, so the
terminal emulator on the host answers the capability handshake and the firmware
sees a real terminal. Measured going out over the wire on attach: alt screen,
in-band resize, cursor report, kitty keyboard, kitty graphics, DA1.

THREE WORDS EXIST ONLY HERE. `src/board_memory.zig` implements `Peek`, `Poke` and
`Hexdump`, gated on `builtin.os.tag == .freestanding and !isWasm()` - derived from
the TARGET, because they are a property of running with no OS under you rather
than a product option, and because wasm is freestanding too and is exactly what
must be excluded: in a browser an address is an offset into the linear memory this
editor's own heap lives in. Every access goes through `*allowzero volatile`: a
peripheral register is not memory, and address 0 is an ordinary unmapped address
on this bus. One 4 KiB cap per command, set by the console rather than the memory -
an unbounded dump would wedge the only console the board has for eleven hours.

Measured on ESP32-P4 rev v1.3 silicon, driven from a host terminal:

  Peek 0x501101a4              0x0e63ce71, then 0xaeaa6919 on a second read - the
                               RNG register, so the volatile loads are not folded
  Poke 0x5011002c 0xdeadbeef   LP_STORE0; a later Peek returned 0xdeadbeef
  Hexdump 0x5011002c 32        16 bytes a row, hex columns and an ASCII gutter
  Peek 0x50110001              `peek: MisalignedAddress` on the message row

That last line is the one that matters. A misaligned 32-bit access traps, and a
trap in firmware is a watchdog reset that takes the session with it, so the check
that turns it into a message is the reason the file is hand-written rather than a
generic reader.

BARE METAL BOOTS AN EMPTY OUTPUT BUFFER. Every other boot layout in `init` makes a
shell, and on this platform that is not a preference but an impossibility: nothing
to fork, no pty to give a terminal pane. Booting one anyway produced precisely what
that describes - a pane whose tag ends in `Filter`, no gutter, no buffer, and every
keystroke vanishing into the Fallback's silent pty. An output buffer is also what
the platform's own words want, since Peek, Poke and Hexdump each fill one.

Sized for the board rather than for a desktop:

* `allocators.zig` gains a p4 tier that is ALL fallback - every capacity is zero,
  so each arena spills immediately to the 384 KiB heap the firmware hands over,
  and no megabyte-shaped static reservation lands in `.bss`.
* `source_manifest.zig`'s allowlist is EMPTY on p4. The table is ~0.95 MiB of
  rodata against a 1.5 MiB flash partition; the firmware's filesystem is the
  serial host's, through the Host vtable.
* The grid is clamped and the clamp is measured, not guessed: every cell is paid
  for four times (vaxis Screen + InternalScreen, pardes Surface + previous_cells),
  so 40x12 fits and 80x24 exhausts the heap during `Pardes.init`.
* `Vaxis.resize` deinits both screens before allocating replacements, so a failed
  resize leaves vaxis rendering nothing. The p4 shell keeps the previous geometry
  on failure instead of leaving a half-applied one.

Also here: `output_pane_integration_test.zig` had an exhaustive switch over
`Platform` that adding `.p4` left unhandled, which broke `zig build unit-test`
outright - the native test binary is the one consumer no platform build compiles.
346 tests pass again.
</content>
</entry>
</feed>
