2 Commits

Author SHA1 Message Date
6e7b53b988 A numeric watch has a site, and an empty window has none
`flan-watch-ghost-call-regexp' allowed one hyphenated segment until this
lane's sibling widened it, so `watch-num-i64' matched as far as
`watch-num', required whitespace, found `-i64' and backtracked to
failure: the row appeared in the watch buffer as usual and the call site
got no inline value at all.  Nothing automated covered the widening.  It
does now — a block pinning both accumulator heads beside the four
one-segment ones, verified by putting the `?' back and watching exactly
those two checks fail.

The other half of that handoff asked for a test of `watch_render_num''s
`n=0' branch.  It cannot be written: `s->num' is set in one place,
`watch_record', which always falls through to `s->n += 1', and the clear
and the increment sit inside one odd-generation window.  The reader
never compares the slot's epoch to the global one, so a reset does not
make a slot read as empty either.  The branch is dead, and the live
consequence is that a read taken after `:reset t' and before the
program's next sample reports the previous window — which is why the
existing test waits for the count to drop rather than reading once.
Recorded in HANDOFF-f3.md with the two ways out, both of which belong to
whoever owns runtime/flan_dev.c.
2026-09-13 12:56:26 +07:00
fc319474b6 A hot loop keeps five numbers, and the window is the editor's
PORTING.md Tier 1 item 5. The spy half of the watch was already built — the
pushed table, the buffer, the inline ghost text. What was missing is spy-num,
which is the part that item calls least obvious and most valuable, and it is
what this is.

A slot keeps count, min, max, last and mean. Each answers a question you can
ask without building a query: n is the first thing wrong when a loop is wrong,
the range is what one sample can never show you, last is what the scalar watch
would have given you, and the mean is a running sum divided at read time
because a mean accumulated as a mean drifts. A small ring of the last N
samples was the other candidate and loses — N out of 91,200 is a sample of the
tail of the loop rather than of the loop, and past five numbers every richer
answer is a UI for building a query.

The write path does no formatting, which is the feature rather than an
optimisation: a snprintf per sample at thousands a frame is a HUD that costs
more than the game. A sample is a load, five compares and the slot's seqlock;
the listener thread renders once per editor tick.

The window is since the editor's last tick, and that is a deliberate
divergence from watch.clj, where the stats are cumulative until reset-spies!.
Cumulative min and max reach the session's extremes within seconds of play and
then never move again, so the two most useful of the five go dead exactly when
you start interacting with the thing you are debugging — and this tool exists
to show you a number while you drag the mouse. Reset is its own message and
never a side effect of reading, because a destructive read makes looking
change what is there and anything that polls would shorten the window under
the editor that owns it. It bumps one epoch counter and clears no slot; a slot
clears itself on its next sample, so the reader never writes the table.

Ghost text needed one character. The call regexp allowed one hyphenated
segment, so watch-num-i64 backtracked to failure and a numeric watch got no
inline value while appearing normally in the buffer.

dune test is green, run twice. HANDOFF-f3.md carries the reasoning, the two
small gaps left behind it, and what did not work on the way.
2026-09-13 10:35:20 +07:00