summaryrefslogtreecommitdiff
path: root/experiments
diff options
context:
space:
mode:
Diffstat (limited to 'experiments')
-rw-r--r--experiments/pg-09.pngbin303363 -> 302306 bytes
-rw-r--r--experiments/pg-10.pngbin66687 -> 220988 bytes
-rw-r--r--experiments/pg-11.pngbin0 -> 255697 bytes
-rw-r--r--experiments/report.typ105
4 files changed, 97 insertions, 8 deletions
diff --git a/experiments/pg-09.png b/experiments/pg-09.png
index 704b63d..d076622 100644
--- a/experiments/pg-09.png
+++ b/experiments/pg-09.png
Binary files differ
diff --git a/experiments/pg-10.png b/experiments/pg-10.png
index 9a76b56..ec8372b 100644
--- a/experiments/pg-10.png
+++ b/experiments/pg-10.png
Binary files differ
diff --git a/experiments/pg-11.png b/experiments/pg-11.png
new file mode 100644
index 0000000..26069e3
--- /dev/null
+++ b/experiments/pg-11.png
Binary files differ
diff --git a/experiments/report.typ b/experiments/report.typ
index 1601cef..0403a1a 100644
--- a/experiments/report.typ
+++ b/experiments/report.typ
@@ -839,23 +839,112 @@ here — the grapheme walk, the per-cell writes, the fill — returned between n
21 µs, because the cost is not in any one of those but in doing the whole frame again.
Nothing in this repository can avoid work pardes has already done.
+= Experiment 5 --- how big can the window be
+
+The 40×12 grid was never a decision about the screen. It was memory, and the constraint was
+four copies of every cell: vaxis keeps a `Screen` and an `InternalScreen`, pardes keeps its
+`Surface`, and the shell keeps a shadow of it to diff against. Two of those four became dead
+weight the moment the emitter started writing the wire itself — allocated every session,
+never read. Sizing them to *one cell* removes the memory ceiling outright: the heap now
+reports 336 KB free at every geometry tried, including ones that used to fail outright.
+
+What is left is the honest limit. Every frame walks the whole grid, so the round trip is
+linear in the cell count at *0.87 µs per cell*, measured.
+
+#figure(
+ table(
+ columns: (auto, auto, auto, 1fr),
+ align: (left, right, right, left),
+ stroke: none,
+ table.hline(),
+ table.header([geometry], [cells], [round trip], []),
+ table.hline(stroke: 0.5pt),
+ [40×12], [480], [3 628 µs], [the old ceiling],
+ [*56×14*], [*784*], [*3 930 µs*], [*the new default: +63% area, +40% width*],
+ [56×16], [896], [3 965 µs], [over 4 ms on the phase-randomised instrument],
+ [60×18], [1 080], [4 114 µs], [],
+ [64×20], [1 280], [4 281 µs], [],
+ [80×24], [1 920], [4 809 µs], [the classic terminal, one flag away],
+ [100×30], [3 000], [5 743 µs], [],
+ [120×36], [4 320], [6 923 µs], [],
+ [140×42], [5 880], [8 310 µs], [the largest that runs at all],
+ [160×48], [7 680], [---], [links, then traps at boot],
+ [200×60], [12 000], [---], [does not link],
+ table.hline(),
+ ),
+ caption: [`-Dp4-cols` / `-Dp4-rows`, because none of this is a constant. The two failures at
+ the bottom are different failures and both are worth naming: 200×60 is refused by the
+ linker — `.bss will not fit in region l2mem, overflowed by 76 036 bytes`, that `.bss` being
+ the shadow grid, sized at compile time — while 160×48 links and then traps, the same
+ pressure arriving at runtime as a collision instead of as a diagnostic. Neither is a heap
+ problem any more, which is the surprise: the heap has 336 KB spare while `.bss` runs out.],
+)
+
+= Three bugs the latency work walked straight past
+
+All three were invisible to every number in this document, and two of them had been there
+since the port began.
+
+*Keystrokes were being lost.* Reported as "a key is stuck and is only sent when I send a new
+event". It was neither stuck nor late. The loop is read, apply, render, write, and the write
+blocks while the transmit FIFO is full — real backpressure, and it should stay, because half
+an escape sequence leaves the host terminal in the wrong colour for the rest of the session.
+But nothing drained the RECEIVE FIFO during that wait, and that FIFO is 128 bytes, or 11 ms
+of wire. Measured by counting what the firmware's loop actually took off the UART: a 200-byte
+burst arrived as 197, a 300 as 257, a 600 as 478. Draining the receiver inside the wait fixes
+the first window; a second one — applying the input, which costs 44 µs a keystroke on an empty
+line and 63 µs at 640 characters — needed the loop to hand the editor eight bytes at a time
+instead of a hundred and twenty-eight. With both, 4 096 bytes in a single write arrive intact,
+and past that the loss is *counted* rather than silent.
+
+*Every escape sequence was being shredded.* `vaxis.Parser` resolves a buffer containing
+nothing but `0x1b` as the Escape key — deliberately, and correctly for a terminal, where the
+kernel hands over a whole sequence in one read. This wire hands over one byte at a time, 87 µs
+apart, so the first byte of every sequence arrived alone and was resolved as Escape and the
+rest arrived as ordinary keys. A mouse click came through as ten key presses, and the `0`
+among them is "go to column zero" in normal mode — which is exactly where the cursor kept
+landing, and why this looked at first like a coordinate bug. Arrow keys, function keys and the
+host's own in-band resize reports were all being taken apart the same way. The shell now holds
+a lone ESC for 10 ms, two orders of magnitude longer than the wire needs and imperceptible to
+the person pressing it.
+
+Finding it took instrumenting the ABI to print the tag of every event the shell applied. Ten
+`key_press` where one `mouse` belonged is not something any amount of reading the coordinate
+arithmetic would have shown, and it had been read twice.
+
+*The mouse was never enabled.* With the sequences intact, that was one line: the shell already
+handled mouse events, because it mirrors the tty shell. Spelled out rather than taken from
+`vx.setMouseMode`, which asks for `1002;1003;1004;1006` — 1003 being any-motion tracking, a
+report per cell the pointer crosses with no button held. On this line that is dozens of
+15-byte reports for one sweep, arriving as input the editor must parse while it paints, and
+arriving whether anyone wants it or not. The console thins drag reports to 25 a second on the
+way in, newest-wins, which is right for motion and only for motion: where the pointer passed
+through is not information an editor can use.
+
+
#figure(
table(
columns: (auto, 1fr),
align: (left, left),
stroke: none,
table.hline(),
- table.header([found], [not fixed]),
+ table.header([found], [outcome]),
table.hline(stroke: 0.5pt),
- [`vx.resize` fails], [A runtime geometry change takes its allocation-failure path,
- restores the previous size and returns: 80 bytes go out where 1 392 should, and
- the screen keeps its old shape. Reproduced with the shadow grid compiled out, so
- it predates it. The board has one geometry per session — which is also why the
- staleness test compares two firmwares rather than forcing a repaint by resizing.],
+ [`vx.resize` fails], [A runtime geometry change took its allocation-failure path, restored
+ the previous size and returned: 80 bytes went out where 1 392 should, and the screen kept
+ its old shape. Reproduced with the shadow grid compiled out, so it predated it, and left
+ unrepaired through three experiments --- which is also why the staleness test compares two
+ firmwares rather than forcing a repaint by resizing.
+
+ *Fixed, and not on purpose.* The failing allocation was vaxis's two grids, and the emitter
+ stopped needing them; there is nothing left to reallocate, so the path that failed is gone
+ rather than repaired. Verified on the die: an in-band report re-lays the screen from 56
+ columns to 30 and back. The pleasant kind of consequence, and worth recording as a
+ consequence rather than as a fix --- nobody set out to repair it.],
table.hline(),
),
- caption: [A bug the optimisation work walked into and characterised but did not
- repair.],
+ caption: [A bug that three experiments characterised and did not repair, removed by a change
+ aimed at something else entirely.],
)
= Ranked by measured benefit