Commit Graph
134 Commits
Author SHA1 Message Date
Robert Allan JamesandClaude Sonnet 5 5d2e1c2184 font.4th: default system font-set capsule, 113 glyphs (item 4.3.6c)
Punch list §25 item 4.3.6c complete. Digits, upper/lowercase, ASCII
punctuation, Latin-1 Supplement, and General Punctuation, each a
stroke-drawing word in the 4.3.6 em-square convention.

Corrected mid-build: the first pass used G-LINE only, missing that
CIRCLE/ARC/ELLIPSE already existed (4.3.3b). Reworked all round shapes
to use them via new em-square-aware wrappers (G-CIRCLE/G-ARC/G-ELLIPSE/
EM-R, fabric.4th 5000-5002), bounded to quarter/half/three-quarter/
full sweeps to avoid unbounded per-glyph angle tuning. font.4th also
redefines DISPATCH-GLYPH/DRAW-GLYPH themselves, not just the six
DISPATCH-* bucket words, since fabric.4th's originals were compiled
early against the 4.3.6b placeholders (CASE early-binding, §27.6.5).

Three-arch acceptance boot clean, Stadium conservation unchanged.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-09 20:26:14 -04:00
Robert Allan JamesandClaude Sonnet 5 4427986c96 fabric: codepoint -> glyph dispatch (DISPATCH-GLYPH, TOFU)
Punch list §25 item 4.3.6b complete.
capsules/fabric.4th blocks 4921-4924: DISPATCH-GLYPH routing via
WITHIN to six bucket words (DISPATCH-DIGIT/-UPPER/-LOWER/
-ASCII-PUNCT/-LATIN1/-GENPUNCT), TOFU fallback, DRAW-GLYPH. Buckets
carry one placeholder stroke word each (G-TEST-*), not the real
113-glyph set -- that's item 4.3.6c's scope, deliberately deferred.

Also: merged two lines in block 4920 (DECODE-UTF8) to fit mkcapsule's
real 64-char x 16-line block limit once a trailing blank separator
line is counted against it -- mechanical reformat, re-verified via a
DECODE-UTF8 regression check (65/176/8212, unchanged).

Verified live on amd64: one representative codepoint per bucket plus
one out-of-range codepoint, all seven DRAW-GLYPH results matched
expected exactly (400/600/450/250/550/700/500-TOFU). Three-arch
acceptance boot clean, Stadium conservation unaffected.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-09 19:15:05 -04:00
Robert Allan JamesandClaude Sonnet 5 24d6b9e20e fabric: UTF-8 decoder (DECODE-UTF8)
Punch list §25 item 4.3.6a complete.
capsules/fabric.4th blocks 4918-4920: UTF8-SEQ-LEN/UTF8-CONT? verbatim
from §27.6.2, DECODE-UTF8 dispatching to four UTF8-ASSEMBLE-N helpers
(1/2/3/4-byte). Factored into helpers after the single-word draft hit
mkcapsule's real 64-char x 16-line block limit. Verified live on
amd64: a 6-byte test buffer (ASCII + deg-sign + em-dash) decoded to
65/176/8212 across three sequential calls, exact match against
hand-computed codepoints, buffer fully consumed. Three-arch acceptance
boot clean, Stadium conservation unaffected.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-09 19:00:36 -04:00
Robert Allan JamesandClaude Sonnet 5 82e948afe9 fabric: em-square glyph coordinate convention (EM-X/EM-Y/G-LINE)
Punch list §25 item 4.3.6 complete.
capsules/fabric.4th blocks 4916-4917: EM-UNITS, GOX/GOY/GSIZE/GCOLOR,
EM-X/EM-Y/G-LINE per §27.6.1, scaling/translating em-square strokes
into CART-PLOT screen coordinates via */. Verified live on amd64 via
a temporary probe (built, run once, reverted): two G-LINE test shapes
at GSIZE 100 and GSIZE 50, one leg each exercising a negative em-y
value chosen to hit */'s truncate-toward-zero behaviour, not a
multiple of EM-UNITS. Screendump pixel-bbox extraction matched
hand-calculated raster coordinates exactly on both shapes. Three-arch
acceptance boot clean, Stadium conservation unaffected
(resident_sum=43691 reservoir=21845 sum=65536 on all three).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-09 18:53:02 -04:00
Robert Allan JamesandClaude Sonnet 5 980f902c7b chore: commit stray CaptainBob.png and an uncited riscv64 boot log
CaptainBob.png: untracked repo-root image, added per Captain Bob's
direction.

logs/20260808-002843/riscv64/: a riscv64 boot log from ~00:28, minutes
after the committed 4.3.5 acceptance boots. Not cited anywhere in
FABRIC.md. It ends in the scause=0xF store-page-fault at 0x0c201000
that item 4.3.5a later formally traced to riscv64 having no
software-controlled paging -- an earlier, informal hit of the same
bug, predating 4.3.5a's own cited diagnostic logs (10:19am+).
Committed for the audit record per this repo's "logs are committed
artifacts" convention, superseded by but not duplicating 4.3.5a's
own logs.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-09 18:35:24 -04:00
Robert Allan JamesandClaude Sonnet 5 0a03ba7871 docs: deep-derive glyph rendering design (4.3.6-4.3.6g), FABRIC.md \xc2\xa727.6.1-6
Expands the previously sparse \xc2\xa727.6 to match the derivation depth of
\xc2\xa727.3/\xc2\xa727.5.1/\xc2\xa727.5.2 -- worked line items, not just prose scoping.
Every primitive used (*/, WITHIN, CASE/OF/ENDOF, CREATE, EXECUTE, ',
LSHIFT/RSHIFT/AND/OR) was confirmed against src/word_source/*.c before
being used in a formula; no $/0x hex literal syntax exists in this tree,
so all formulas use decimal only.

Concrete design landed, in order derived:
- EM-UNITS 1000, standard Type1/OpenType em-square proportions; EM-X/
  EM-Y/G-LINE scale-and-translate into CART-PLOT space via */ (noting
  */'s "avoid overflow" rationale is vestigial at this VM's 64-bit
  cell_t width -- not a bug, just an observation)
- UTF-8 decoder: full bit-level 1/2/3/4-byte assembly formulas. Found
  mid-derivation that the 3-byte path is v1-required, not future-i18n-
  only -- smart quotes/dashes/ellipsis are Unicode General Punctuation
  (U+2000-206F), NOT Latin-1 Supplement despite the original framing
  lumping them together
- Dispatch: bucketed CASE/OF/ENDOF chains (DISPATCH-DIGIT/-UPPER/-LOWER/
  -ASCII-PUNCT/-LATIN1/-GENPUNCT) routed by WITHIN range checks, chosen
  over a flat 256-entry xt-table despite the table composing more
  cleanly with override -- explicit tradeoff, not an oversight
  - Glyph-word contract ( -- em-advance ), TOFU placeholder for misses,
    ~8-10 block budget estimate for dispatch alone
- Character list corrected and finalized: 113 glyphs (95 ASCII + 11
  Latin-1 Supplement + 7 General Punctuation), itemized
- User font override: found CASE's early binding means redefining a
  single glyph word does NOT retroactively change an already-compiled
  DISPATCH-* caller (same binding behavior 4.3.3b found for CART-PLOT).
  Resolved: override replaces a whole DISPATCH-* bucket word, not
  individual glyphs -- explicit granularity tradeoff for keeping CASE
- TEXT entry point sketched at algorithm level

DECODE-UTF8's exact byte-consumption stack mechanics and TEXT's loop
are deliberately left as algorithm-level spec, not final FORTH -- this
project's history (VARIABLE alignment, Q.TO-INT sign bug) shows exactly
this class of stack/precision bug surfaces only under live testing, not
design review; presenting untested stack-juggling code as settled would
be the "confidently wrong" mistake \xc2\xa725.0 rule 4 warns against.

Punch-list items 4.3.6-4.3.6g (\xc2\xa725.5) updated to match: concrete Done-
when criteria replacing several that were previously vague or TBD
(4.3.6b now names the six buckets; 4.3.6c names the 113-glyph list;
4.3.6e goes from "TBD" to a real, testable override-mechanism
criterion; 4.3.6f requires proportional-width verification). Still no
implementation -- scoping and derivation only, per \xc2\xa725.0 rule 2.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-09 18:14:48 -04:00
Robert Allan JamesandClaude Sonnet 5 b69dcfd050 docs: scope glyph rendering (4.3.6-4.3.6g), next after 4.3.5f
Punch list \xc2\xa725.5 additions (4.3.6-4.3.6g, unchecked) and new \xc2\xa727.6
rationale section, scoped per the 2026-08-07 sequencing note. Captures
requirements dictated this session: UTF-8-aware decoding, ASCII + Latin-1
typographic repertoire for v1, i18n-ready codepoint-indexed design, one
capsule per font-set (not per glyph), a default system font wired into
init.4th's boot chain as early as the capsule/VM bootstrap allows, with
user-font-override support -- additive to, not replacing, the existing
font_8x16.c VT100 console path. Flags two open items found while scoping
rather than deciding them unilaterally: an unused encoding field on
block_subsystem.h, and the open call on which capsule file eventually
houses the glyph data. No implementation yet -- scoping only, per \xc2\xa725.0
rule 2.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-09 16:55:01 -04:00
Robert Allan JamesandClaude Sonnet 5 40f3e3ab44 config: disable background-session worktree isolation for this repo
Background-session edits were being forced into an isolated git worktree,
defaulting to a branch off origin/master -- which has none of this
branch's FABRIC.md content -- conflicting with this repo's subversion-
style workflow (commit directly to the working branch, no side branches
without explicit permission). Sets worktree.bgIsolation: none so
background-session edits land directly in the shared checkout, consistent
with the existing CLAUDE.md policy.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-09 16:54:50 -04:00
Robert Allan JamesandClaude Sonnet 5 d0835c674b FORTH: KEY-EVENT unified keyboard interface, closes 4.3.5-4.3.5f (item 4.3.5f)
Punch list §25 item 4.3.5f complete. Keyboard-input slice done.

New KEY-EVENT ( -- keycode pressed -1 | 0 ) converges amd64's i8042,
riscv64's and aarch64's virtio-keyboard-pci behind one shape. Translation
needed is minimal: XT Set-1 make codes and Linux input keycodes are
numerically identical across the standard 84-key block (a documented
historical property of the Linux input subsystem, confirmed against this
host's own headers and this tree's own prior live observations), so amd64
needs two lines, riscv64/aarch64 need none. Verified live with a real
keypress on all three architectures: identical "-1 1 30" output everywhere.
Three-arch acceptance boot clean.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-08 12:48:05 -04:00
Robert Allan JamesandClaude Sonnet 5 a373c9124a aarch64: virtio-keyboard-pci, interrupt-driven keyboard input (item 4.3.5e)
Punch list §25 item 4.3.5e complete.

Extended virtio_input.c with a GIC-routed interrupt path alongside 4.3.5c's
PLIC one -- same capability walk, feature negotiation, eventq handling
(confirming §27.5.1's prediction that these items would share most of the
driver). aarch64_irq_handler() dispatches to virtio_input_isr() before its
EOIR write, same claim-dispatch-complete ordering riscv64 uses. Verified
with a real QEMU sendkey keypress: exact KEY_A/press match, two real
interrupts serviced, identical result to riscv64. Found (not fixed) an
unrelated pre-existing bug: BYE's cold-reset path faults on aarch64,
discovered incidentally since nobody had exercised it from a monitored
session before. Three-arch acceptance boot clean.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-08 12:29:27 -04:00
Robert Allan JamesandClaude Sonnet 5 2a2d7c5e5e aarch64: GIC SPI wiring for virtio-input (item 4.3.5d)
Punch list §25 item 4.3.5d complete.

New apic_spi_enable(intid) generalizes item 0.6's PPI-only sequence to one
explicit SPI (IPRIORITYR/ISENABLER/ITARGETSR, ICFGR read-checked not
written). Verified via a software-pended SPI (GICD_ISPENDR, no device
needed) through the existing generic IRQ dispatch, which needed no changes.
Found and fixed a real bug during verification: PSTATE.I is still set at
apic_init()'s point in boot, so the first self-test run correctly latched
but never took the interrupt. Self-test code reverted after capturing
evidence -- interrupts.c has zero net diff, only apic_spi_enable() remains,
unused until 4.3.5e. Three-arch acceptance boot clean.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-08 12:15:23 -04:00
Robert Allan JamesandClaude Sonnet 5 8251aebcf8 riscv64: virtio-keyboard-pci, interrupt-driven keyboard input (item 4.3.5c)
Punch list §25 item 4.3.5c complete.

Amended from a nonexistent MMIO transport to PCI (matching the board's
actual virtio-blk-pci precedent). New virtio-input driver: eventq with
pre-posted buffers, PLIC source computed at runtime from PCI slot/pin
(derived live from this host's QEMU riscv64 DTB), mandatory ISR-status
read, PCI interrupt-disable-bit check. New VKBD-EVENT/VKBD-DEBUG FORTH
words. Verified with a real QEMU sendkey keypress: exact KEY_A/press
match, two real interrupts serviced, zero exceptions. Three-arch
acceptance boot clean.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-08 11:51:07 -04:00
Robert Allan JamesandClaude Sonnet 5 5f4df673c1 riscv64: PLIC bring-up, external-interrupt substrate (item 4.3.5b)
Punch list §25 item 4.3.5b complete.

sie.SEIE enabled, PLIC threshold/claim/complete wired into the trap
handler. Verified with a UART-loopback synthetic interrupt (PLIC has no
software set-pending register, unlike GICv2): claim_count=1, last_irq=10,
IIR confirms genuine receive-data cause, byte matched exactly. Self-test
code run once for evidence then fully reverted, per Captain Bob's ruling;
only the permanent substrate remains, no source enabled by default.
Three-arch acceptance boot clean, zero exceptions.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-08 11:24:47 -04:00
Robert Allan JamesandClaude Sonnet 5 36965cf812 riscv64: explicit Bare-mode satp switch (item 4.3.5a)
Punch list §25 item 4.3.5a complete.

Firmware (EDK2 RISC-V) leaves Sv57 paging active at kernel entry with an
identity mapping that has at least one confirmed hole (PLIC threshold
register). Verified live before acting: satp.MODE/PPN and __kernel_start's
address confirmed identity mapping; switched to Bare mode (csrw satp, x0 +
sfence.vma) in arch_early_init(), ahead of pmm_init()/vmm_init().
Three-arch acceptance boot clean, zero exceptions; riscv64's PLIC_THRESHOLD
write now survives (PLIC driver code itself lands separately with 4.3.5b).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-08 11:07:29 -04:00
Robert Allan JamesandClaude Sonnet 5 06283e5364 FABRIC.md: aarch64 keyboard punch list -- GIC-SPI derivation, no landmine
Live-probed GICD_ITARGETSR before starting 4.3.5d/e; survived cleanly,
unlike riscv64's PLIC (4.3.5a). Decoded QEMU's own virt DTB for the PCI
slot->SPI interrupt-map routing table and closed-form formula. Amended
4.3.5d's circular acceptance criterion to a synthetic GICD_ISPENDR probe
(mirrors 4.3.5b's fix for the same problem), and wrote the full 4.3.5e
virtio-input execution plan: device ID 0x1052, the real virtio_input_event
struct, the mandatory ISR-status read, and the pci_enable() interrupt-
disable-bit gap.

Log: logs/20260808-093228/aarch64/.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-08 09:56:20 -04:00
Robert Allan JamesandClaude Sonnet 5 e01c4e33e4 FABRIC.md: insert 4.3.5a -- riscv64 has no software-controlled paging
Discovered live while implementing what was 4.3.5a (PLIC bring-up,
renumbered 4.3.5b): riscv64 never activates its own page table
(load_cr3() is a no-op outside __x86_64__, arch.c's Sv39/SATP setup is
an explicit stub), so firmware's own mapping -- which doesn't cover the
PLIC's MMIO window -- is what's live for the kernel's whole lifetime.
Confirmed via a live store-page-fault, not inferred. Per Captain Bob's
ruling: stop, scope as its own item, decide the fix (Bare mode vs. real
Sv39) in a future session rather than embedded in the PLIC item.
4.3.5b-4.3.5e renumbered accordingly (4.3.5f was 4.3.5e); no items were
executed under the old numbering, so this is free to do cleanly now.

Documentation only. PLIC driver code (plic.c/plic.h, interrupts.c/
apic.c wiring, Makefile.starkernel) is written and reviewed but stays
uncommitted -- it cannot be verified until 4.3.5a unblocks it, and
riscv64 currently hangs in the fatal exception handler with it applied.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-08 00:47:31 -04:00
Robert Allan JamesandClaude Sonnet 5 6a97fa4c98 logs: add boot-run audit trail and DoE CSVs from item 4.3.5 work
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-08 00:18:58 -04:00
Robert Allan JamesandClaude Sonnet 5 88eb73cfe8 starkernel: item 4.3.5 -- amd64 I/O APIC + i8042 keyboard, interrupt-driven
Punch list §25 item 4.3.5 complete.

New ioapic.c/i8042.c drivers (MADT-derived I/O APIC base, no hardcoded
constants) plus a KBD-SCAN/KBD-DEBUG diagnostic word pair. Three real
bugs found and fixed en route, all blocking this item's own acceptance:
a fatal LAPIC spurious-vector crash (nothing had driven a real external
interrupt through the I/O APIC before), OVMF leaving the keyboard device
itself scanning-disabled (0xF4 fix), and isr.S's stub table only having
individually-numbered stubs through vector 32 -- everything above that,
including our IRQ1 vector 33, silently reported as vector 255 regardless
of which IDT slot actually fired. Verified live via QEMU sendkey against
KBD-SCAN: correct XT Set-1 make/break codes for two different keys.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-08 00:18:48 -04:00
Robert Allan JamesandClaude Sonnet 5 78ff335b97 FABRIC.md: note sequencing -- keyboard, glyph rendering, REPL, then 4.4
Recorded per Captain Bob's direction so the order survives between
sessions. Neither glyph rendering nor REPL is scoped yet -- each gets
Done-when criteria at its own checkpoint per 25.0 rule 2.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-07 22:59:04 -04:00
Robert Allan JamesandClaude Sonnet 5 8fffd69347 FABRIC.md: scope 4.3.5-4.3.5e -- interrupt-driven keyboard input, per-ISA split
Next after the 4.3.4 checkpoint. amd64 needs an I/O APIC (none exists;
legacy PIC is permanently masked), riscv64 needs a PLIC (external
interrupts were never enabled in Phase 0), aarch64 needs GIC SPI
routing (0.6 scoped PPI-only). Documentation only, no code.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-07 22:57:43 -04:00
Robert Allan James 7024e413f1 logs: add boot-run audit trail from multi-ISA framebuffer bring-up
Three-arch acceptance run (amd64/aarch64/riscv64) after adding -device
ramfb to aarch64/riscv64, plus the interactive GOP-verification and cube
sessions on both.
2026-08-07 20:13:22 -04:00
Robert Allan James e58381c121 fb/: untrack and gitignore -- throwaway local verification images
Reversing the earlier decision to track fb/ in git (made when first
setting up the directory for item 4.3.2). Captain Bob: these are
disposable screenshots for eyeballing framebuffer output during Console
work, never meant to be committed. Removed from git tracking (git rm
--cached) and added to .gitignore; files that still exist locally are
untouched, deletions already made locally are left as-is.

Also fixes item 4.3.4's amd64-only blind spot found in the process:
aarch64/riscv64 had no framebuffer device at all (GOP: protocol not
found) -- the "all three architectures boot clean" checks run all session
were REPL/dict_hash parity, a different thing from GOP presence, and
conflating the two was an error. Added -device ramfb (EDK2's
firmware-only GOP framebuffer) to both architectures' qemu targets in
Makefile.starkernel. Both now report GOP: linear framebuffer found at
800x600; cube rendering verified correct on both (screenshots not
committed, per the untrack above -- verified visually this session).
Standard three-arch acceptance boot re-run afterward, all clean,
dict_hash identical and unchanged from before this fix.
2026-08-07 20:13:17 -04:00
Robert Allan James c16eaf431f logs: add boot-run audit trail and DoE CSVs from item 4.3.4 work
Three-arch acceptance run (amd64/aarch64/riscv64) plus the interactive
CUBE verification session.
2026-08-07 15:29:10 -04:00
Robert Allan James fa300ef4c6 starkernel: item 4.3.4 -- checkpoint, draw a cube (no new bugs)
Adds VERT/EDGE/CUBE to capsules/fabric.4th (blocks 4913-4915). VERT
( n -- x y z ) reads bits 0/1/2 of a corner index as the X/Y/Z sign
(+-CS from center), so all 8 cube corners come from one word. EDGE
resolves both corners via VERT and calls LINE; CUBE is 12 EDGE calls
(4 bottom, 4 top, 4 vertical).

First item in the 4.3.3.x sequence with no new bug found -- a small signal
that Q.TO-INT, the VARIABLE alignment fix, and the LINE-STUCK? cap were
the real gaps rather than something still lurking in LINE/PROJECT/CART-Y.

Verified live on amd64: a centered, half-size-100 cube renders correctly
-- front/back face squares, back face offset diagonally up-right by
exactly the 45-degree cavalier projection's depth term, all 12 edges
connecting at the right corners.

All three architectures boot clean to ok> with the DoE completing;
dict_hash identical across all three and unchanged from 4.3.3a/4.3.3b.

FABRIC.md item 4.3.4 marked done. This is the checkpoint -- 4.3.x
groundwork stops here for review per this item's own acceptance criterion.
2026-08-07 15:29:04 -04:00
Robert Allan James a3fe0702eb logs: add boot-run audit trail and DoE CSVs from item 4.3.3b work
Three-arch acceptance run (amd64/aarch64/riscv64) plus the interactive
sessions used to test LINE/CIRCLE/ARC/ELLIPSE, the block-span test, and
verify the Q.TO-INT fix.
2026-08-07 15:21:13 -04:00
Robert Allan James cb4326c712 starkernel: item 4.3.3b -- geometry drawing wordset, fixed Q.TO-INT sign bug
Adds LINE (Bresenham in raster space, endpoints projected once each --
valid because the cavalier projection is linear), CIRCLE/ELLIPSE
(36-segment polygon approximation), and ARC (18 segments over a caller
radian range) to capsules/fabric.4th (blocks 4903-4912). TO-RASTER
factored out of CART-PLOT (same behavior) so LINE can reuse the
projection+flip for both endpoints.

Found mid-implementation: colon definitions cannot span block boundaries
in this capsule loader -- verified with a throwaway test capsule, the
continuation lands in a [CAPSULE][DEFER] path that never resolves. LINE's
body is split across LINE-SETUP/LINE-DONE?/LINE-STUCK?/LINE-STEP, each
self-contained within its block, rather than one long definition.

A fourth real bug, serious this time: CIRCLE's first live test rendered
only one quadrant, then hung the VM for several minutes on a follow-up
call. Root cause: q48_to_u64() (include/q48_16.h and
include/starkernel/q48_16.h, backing Q.TO-INT) did an unsigned logical
shift, corrupting any negative Q48.16 value into a huge garbage integer
instead of sign-extending -- inevitable once Q.SIN/Q.COS leave the first
quadrant. That garbage became a bogus LINE target with no bound on
LINE-STEP's Bresenham loop. Fixed q48_to_u64 to shift through a signed
int64_t intermediate (bit-identical for the non-negative case). Also added
LINE-STUCK? (LSTEPS vs FB-WIDTH+FB-HEIGHT, the true worst case for an
on-screen line) as a defense-in-depth cap against any future bad target.

Verified live on amd64 after both fixes: -65536 Q.TO-INT . now prints -1;
LINE/CIRCLE/ARC/ELLIPSE all complete without hanging or erroring, and a
combined screendump shows all four rendering correctly and distinctly.

All three architectures boot clean to ok> with the DoE completing;
dict_hash identical across all three and unchanged from 4.3.3a (expected
-- fabric.4th isn't loaded at boot, and the Q.TO-INT fix doesn't change
dictionary structure).

FABRIC.md item 4.3.3b marked done with full acceptance evidence.
2026-08-07 15:20:58 -04:00
Robert Allan James 01822585f6 logs: add boot-run audit trail and DoE CSVs from item 4.3.3a work
Three-arch acceptance run (amd64/aarch64/riscv64) plus the interactive
Q.SIN/Q.COS verification session.
2026-08-07 13:39:09 -04:00
Robert Allan James 36389e9d4a starkernel: item 4.3.3a -- Q48.16 trigonometry (Q.SIN/Q.COS)
Adds q48_reduce_angle() (range-reduce a signed Q48.16 angle into
[-PI_Q48, PI_Q48] via one integer division plus a bounded fix-up loop) and
q48_sin_approx/q48_cos_approx (Taylor series, terms n=3,5,7,9,11 for sin
and n=2,4,6,8,10 for cos, early exit below 10). Q.SIN/Q.COS registered as
FORTH words in q48_words.c, same pattern as Q.LOG/Q.EXP/Q.SQRT.

Found mid-implementation: this codebase has two independent Q48.16
implementations -- src/word_source/q48_16_words.c (hosted/vendored) and
src/starkernel/math/q48_16.c (kernel-only; the kernel build does not
compile the former at all). The hosted build linked fine after the first
pass; the kernel build failed with undefined references until the same
two functions were added to both .c files and both q48_16.h headers
(include/q48_16.h and include/starkernel/q48_16.h). Not fixed at the root
-- Q.LOG/Q.EXP/Q.SQRT already had this same four-file duplication,
unremarked until now -- just navigated correctly for this item.

Verified live on amd64 via serial injection: sin/cos at 0, +-pi/2, pi, and
3pi (range-reduction across multiple turns) all match expected values
within Taylor-series truncation error (<0.2%).

All three architectures boot clean to ok> with the DoE completing;
dict_hash identical across all three (0x291a660b05fa7b52).

FABRIC.md item 4.3.3a marked done with full acceptance evidence.
2026-08-07 13:39:05 -04:00
Robert Allan James fd1c8ce386 fb/: split into per-ISA subdirectories (amd64/aarch64/riscv64)
Screenshots stay tracked in git, same as before -- just organized so
verification images are easy to find per architecture as Console work
progresses across all three ISAs. scripts/qemu_screenshot.sh (amd64-only)
now writes into fb/amd64/.
2026-08-07 13:23:26 -04:00
Robert Allan James 938f5e87f9 logs: add boot-run audit trail and DoE CSVs from item 4.3.3 work
Three-arch acceptance run (amd64/aarch64/riscv64) plus the interactive
fabric.4th verification sessions used to test CART-PLOT and find the
VARIABLE alignment bug.
2026-08-07 13:21:02 -04:00
Robert Allan James ef9806977a starkernel: item 4.3.3 -- Cartesian coordinate machinery, found and fixed a VARIABLE alignment bug
Adds Module 28 (framebuffer_words.c/.h): PLOT ( x y color -- ), FB-WIDTH,
FB-HEIGHT -- raw hardware-boundary C primitives, kernel-only, no-op on
hosted builds, same pattern as every other module.

Adds capsules/fabric.4th (blocks 4900-4902, mkcapsule --lint clean):
COS45/Z->DELTA/PROJECT/CART-Y/CART-PLOT -- the 45-degree cavalier
orthographic projection and Y-flip, in FORTH per the compose-in-FORTH-first
rule (this is policy, not hardware access).

Found and fixed a second real bug while live-testing CART-PLOT over the
serial socket: defining_word_variable() (defining_words.c) captured
vm->here as a VARIABLE's address with no alignment call first, while
vm_load_cell/vm_store_cell require 8-byte-aligned addresses. This capsule's
VARIABLE ZD landed misaligned (945) purely by chance of what preceded it;
other capsules' variables happened to land aligned by luck, not guarantee.
Real deviation from FORTH-83/ANS, which specifies VARIABLE reserves an
aligned cell. Fixed with vm_align(vm) before capturing addr -- ALIGN
already existed as a word but VARIABLE wasn't calling it.

Verified end-to-end on amd64 via manual serial injection + QEMU screendump:
plotted 4 marker points (origin, +100 X, +100 Y, +50 Z) and confirmed all
landed at hand-calculated raster coordinates, including the diagonal
up-right shift for the Z-axis point -- the projection math is correct, not
just non-crashing. fb/fabric-test-cart-plot.png. 4.3.1's corner diagnostic
still renders correctly in the same shot, confirming no regression.

All three architectures (amd64/aarch64/riscv64) boot clean to ok> with the
DoE completing; dict_hash identical across all three
(0xc7f9adf885e306d2), confirming parity is unaffected.

FABRIC.md item 4.3.3 marked done with full acceptance evidence.
2026-08-07 13:20:52 -04:00
Robert Allan James fbf0625317 FABRIC.md: item 4.3.3 -- settle 45deg cavalier, split off 4.3.3a/4.3.3b
Scoping discussion for 4.3.3 (Cartesian coordinate machinery) surfaced a
larger ask: a full geometry drawing wordset (circles, arcs, ellipses,
radian-based trig), which doesn't exist anywhere in this codebase yet.
Split per the one-task-per-checkbox rule rather than overloading 4.3.3:

- 4.3.3 stays scoped to single-point plumbing: PLOT/FB-WIDTH/FB-HEIGHT (C,
  raw hardware boundary) plus PROJECT/CART-Y/CART-PLOT (FORTH, capsules/
  fabric.4th, blocks 4900+), per compose-in-FORTH-first.
- 4.3.3a (new): Q.SIN/Q.COS in q48_16.c, Taylor series, same pattern as the
  file's existing Q.LOG/Q.EXP/Q.SQRT.
- 4.3.3b (new): LINE/CIRCLE/ARC/ELLIPSE in FORTH, built on 4.3.3 + 4.3.3a.

Also settled: projection angle is true 45 degree cavalier (not 2:1
isometric); "1080p" is a sizing sanity check only, not a hardcoded
constraint (Q48.16's 48 integer bits comfortably cover it).
2026-08-07 11:48:50 -04:00
Robert Allan James ab96ac0970 starkernel: item 4.3.1 -- framebuffer orientation test, found and fixed a real color-swap bug
Adds fb_draw_orientation_test() (framebuffer.c/.h): fills the four raster
corners RED/GREEN/BLUE/YELLOW via fb_fill_rect. Wired into kernel_main.c
calling fb_init() directly -- console_fb_init()/vt100_init() removed from
the boot path, since vt100.c/console.c are superseded by the Console
drawing-fabric redesign (FABRIC.md ss27) and should not be exercised even
incidentally.

The diagnostic caught a real, pre-existing bug on its first run: framebuffer.c's
pack_pixel() had its FB_PIXEL_RGBX32/FB_PIXEL_BGRX32 branches swapped relative
to UEFI GOP's own byte-order naming convention, producing a clean R<->B channel
swap (G unaffected). Spatial placement was already correct -- no flip/rotation.
Fixed by swapping pack_pixel's two return bodies to match framebuffer.h's
already-correct doc comments; kernel_main.c's GOP-format switch needed no change.

Also item 4.3.2 -- QEMU screenshot capability. scripts/qemu_screenshot.sh
already existed (monitor socket + socat + HMP screendump), just unwired and
unused this session. Redirected its PNG output to a new top-level fb/
directory (tracked in git, not logs/, not a gitignored temp dir) and added a
python3+PIL fallback for PPM->PNG conversion since imagemagick isn't
installed here. Left as a standalone script for now, not wired into a
Makefile target.

FABRIC.md items 4.3.1 and 4.3.2 marked done with acceptance evidence.
2026-08-07 11:38:33 -04:00
Robert Allan James f3acfb9b47 FABRIC.md: mark 4.3.x as an open-ended, appendable sublist
Visual cue between 4.3.4 and 4.4 so it's clear at a glance where the
growing 4.3.x Console slice ends and 4.4 begins, without reusing the
document's --- convention (reserved for ## section breaks).
2026-08-07 10:35:17 -04:00
Robert Allan James d4282eb87b FABRIC.md: split 4.3.1 into 4.3.1-4.3.4, one task per checkbox
The prior commit bundled four separate tasks (test pattern, screenshot
capability, coordinate machinery, cube checkpoint) into a single numbered
list inside one checkbox, violating the punch list's own convention of one
task per box. Split into four checkboxes and updated §27 to match.
2026-08-07 10:33:13 -04:00
Robert Allan James f3967fa596 FABRIC.md: item 4.3.1 -- console drawing-fabric groundwork spec (new §27)
Documents the actual hardware-boundary state (framebuffer already wired via
console_fb_init, raw pixel primitives already exist), the missing QEMU
screenshot capability, and the coordinate-system decisions made in
discussion: bottom-left origin, Y-up, Z as depth-into-screen, fixed
orthographic projection as a placeholder (angle still open). Acceptance is
a cube on screen; fonts/scrolling/message-protocol are later 4.3.x items.
2026-08-07 10:31:04 -04:00
Robert Allan James 8bf9480db5 FABRIC.md: item 4.3 -- console discussion held, scope 4.3.1 (framebuffer + coordinate fabric)
CONSOLE.md superseded as a rough prior draft, not edited further. Console
design now lives in FABRIC.md going forward. 4.3.1 is the first slice:
framebuffer sanity check, QEMU screenshot capability, Cartesian/Z coordinate
machinery, and a cube as the first real exercise of the projection math.
2026-08-07 10:27:46 -04:00
Robert Allan JamesandClaude Sonnet 5 7ba7a126c9 logs: add boot-run audit trail and DoE CSVs from item 4.2 work
Serial logs and DoE run CSVs from this session's acceptance-test boots
(the amd64 GOT-indirect-addressing fix and item 4.2's Hermes-native-
on-the-Stadium work) -- committed per the standing convention that
these are audit artifacts, not scratch output.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-07 01:50:45 -04:00
Robert Allan JamesandClaude Sonnet 5 5a28458b21 starkernel: item 4.2 -- Hermes native on the Stadium (complete)
Migrates Hermes's message/channel lifecycle onto the Stadium's unified
heat/capacity economy: MSG-ALLOC/FREE-NODE and CH-ALLOC/FREE-NODE now
route entirely through stadium_admit()/stadium_evict(), replacing the
old local free-list + independent heat-field mechanism. Eight
kernel-only STADIUM-* FORTH primitives (ADMIT, EVICT, RES@, RES-PULL,
RES-PUSH, HEAT@, HEAT!, WORD-HEAT), VM.stadium_vm_id threaded through
all three vm_core.c dispatch sites (replacing item 4.1's hardcoded
vm_uuid_hera()), and the stadium_owner[idx] fix so evict-credit lands
in the VM that actually admitted a patron, not whoever owned cell 0.

This session's own contribution, on top of that pre-existing
implementation: found and fixed two bugs blocking the item's own K≡1.0
conservation self-check (HERMES-K was reading 0, not 65536):

- Q.SLOT admission-heat fix (capsules/hermes/init.4th): MSG-SEND/
  CH-ACCEPT admitted with Q.1 (the entire fleet-wide "1.0" unit) per
  item, a leftover from before the Stadium migration when each
  message/channel had its own unconstrained heat field. Instantly
  drained the shared, finite reservoir.

- Reservoir floor for word-execution admission (stadium_words.c):
  stadium_word_dispatch() (item 4.1) pulls STADIUM_WORD_HEAT_QUANTUM on
  every word dispatch, not just first admission -- exhausts a VM's
  entire reservoir in ~32 dispatches, starving any application-level
  economy sharing that VM's reservoir before it gets a chance to pull
  anything. word_dispatch_pull() now clamps word-execution's own pulls
  to leave a Q48_ONE/3 floor (same fair-share figure COMMON-CH's own
  floor already uses); application-level pulls are unaffected.

- STADIUM-WORD-HEAT primitive + stadium_words_resident_heat(): the
  floor deliberately leaves word-execution residents holding real
  heat, invisible to HERMES-K's original formula (MSG+CH+reservoir,
  no term for word patrons). Adding this term closes K to exactly
  65536 on all three architectures.

Also rules on two open scope questions in FABRIC.md: MBR-ALLOC/
MBR-FREE-NODE stay off the Stadium (membership records have no heat
field, never did -- the acceptance bullet's inclusion of them was a
completeness gesture predating a check of the actual layout), and
records the effort number (12 implementation files, +759/-120 lines).

Verified: all three architectures boot clean, full self-test passes,
Stadium conservation closes exactly (resident_sum + reservoir =
Q48_ONE) at both the C/Stadium level and the FORTH-level HERMES-K
check.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-07 01:49:23 -04:00
Robert Allan JamesandClaude Sonnet 5 0a7f144367 amd64: fix GOT-indirect addressing bug in dictionary fast-path lookup
vm_find_word() and dict_find_word_heat_aware() reference the same extern
globals (sf_fc_list/sf_fc_count/sf_fc_cap) but GCC compiled cross-TU
references to them with GOT-indirect addressing (R_X86_64_REX_GOTPCRELX)
under -fPIC. This freestanding, statically-linked UEFI PE image has no
dynamic linker to populate a GOT, so those reads silently returned NULL
instead of the array's real address -- amd64-only, and exquisitely
sensitive to unrelated code-size changes since the choice between direct
and GOT-indirect addressing is a per-call-site GCC heuristic.

Fix: -fno-pic -fno-pie for amd64 only (ARCH_CFLAGS, overriding
COMMON_CFLAGS's -fPIC, which riscv64's -shared loader link still needs).
Also removes -DPLATFORM_TIME_NO_INLINE, a prior one-off workaround for
the identical bug applied to sf_monotonic_ns() specifically, now
redundant. Adds R_X86_64_PC32/R_X86_64_PLT32 handling to
elf_apply_relocations() as a robustness fix for the non-monolithic
split-build path (dead code for the current monolithic boot, where OVMF's
own PE loader relocates the image, not this loader).

Verified: all three architectures boot clean and pass the full item-4.2
Hermes self-test, including MSG-DELIVER-ALL, which previously triggered
the corruption on amd64 only. Write-up in FABRIC.md under item 4.2.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-06 21:18:06 -04:00
Robert Allan JamesandClaude Sonnet 5 56ad128e2e word_source: add LSHIFT/RSHIFT bitwise-shift primitives
No shift primitive existed anywhere in the vendored VM word set. Adds
both as FORTH-83-extension words next to INVERT, guarded against stack
underflow and out-of-range shift counts (u >= 64).

Their absence was masking a real bug: capsules/hermes/init.4th's
CH-MINT-ID (item 4.2) calls LSHIFT to pack a 64-bit channel ID, which
was silently tripping the capsule loader's forward-reference retry
logic and splicing CH-REQUEST's body into CH-MINT-ID's definition.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-06 14:50:05 -04:00
Robert Allan JamesandClaude Sonnet 5 11cd6c8574 FABRIC.md: item 4.2 -- fourth ruling, two more STADIUM-* primitives
Punch list §25 item 4.2 acceptance amended (scoping only, no code).
Found while reading Hermes's actual implementation (capsules/hermes/init.4th):
MSG-COOL-ALL/CH-COOL-ALL mutate each live node's own heat field every
HERMES-TICK, and MSG-TOTAL-HEAT/CH-TOTAL-HEAT sum it -- none of the original
five primitives expose a resident cell's heat at all. Ruled: STADIUM-HEAT@
(read) and STADIUM-HEAT! (write, reconciling the reservoir delta atomically
in C -- same shape as stadium_word_dispatch()'s own cooling code, so
conservation is never left to FORTH to get right). Cooling cadence stays in
Hermes's own HERMES-TICK loop, not a new C-side per-tick sweep -- matches
HERMES.md's language constraint. Primitive surface is now seven, not five;
Done-when updated to match.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-05 15:59:05 -04:00
Robert Allan JamesandClaude Sonnet 5 2981ada2a5 starkernel: item 4.1a -- quota granting, Hermes's one-time birth grant
Punch list §25 item 4.1a complete.
New prerequisite item, found while scoping 4.2: no quota-granting mechanism
existed at all. Adds stadium_grant_quota(new_vm_id, from_vm_id) -- a
one-time initial grant at birth, distinct from item 1.3's still-unbuilt
recurring capacity-transfer arbitration. Splits the donor's free list evenly
by cell count, reassigns stadium_owner[] for every moved cell, and grants
the new VM a fresh Q48_ONE reservoir (not a split of the donor's -- per-VM
conservation, same pattern as Hera's own boot grant). Wired into every baby
VM's birth in capsule_birth.c.

Verified via a boot-time self-test in kernel_main.c using a synthetic
identity (not the real UUID pool, not a real capsule birth -- item 0.1's
Hera-alone pruning stays intact). All three architectures booted to ok> with
identical output: grant OK, Hera reservoir=0 (already fully committed to
resident words, correctly unchanged), test-vm reservoir=65536 (fresh
Q48_ONE). dict_hash identical across all three and unchanged from item 4.1's
baseline (0x3d4e1daf289da94f) -- confirms no dictionary word was added.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-05 15:12:30 -04:00
Robert Allan JamesandClaude Sonnet 5 edfc246579 FABRIC.md: new prerequisite item 4.1a -- quota granting for Hermes's birth
Punch list §25 new item inserted before 4.2 (scoping only, no code).
Found while scoping 4.2: no quota-granting mechanism exists at all --
quota_slot_for_vm() refuses every non-Hera vm_id by design, and both
stadium_admit()'s doc and item 3.2's DONE note defer per-VM free lists to
"when Hera assigns a VM its quota," which nothing builds. Distinguished from
item 1.3's still-open recurring capacity-transfer arbitration: this is
Hermes's one-time initial grant at birth, same shape as Hera's own
whole-pool grant at stadium_boot_init(), not an instance of the open
recurring loop. Ruled: reservoir is not split (Hermes gets her own fresh
Q48_ONE, per-VM conservation per stadium.c's own invariant comment); cell
count is an even split of Hera's current free list, touching no resident
(including pinned cell 0). 4.2 is marked blocked on this item.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-05 14:49:36 -04:00
Robert Allan JamesandClaude Sonnet 5 4b26be90c3 FABRIC.md: item 4.2 -- third ruling, VMUuid on VM struct (Option A)
Punch list §25 item 4.2 acceptance amended (scoping only, no code).
Found while scoping the C/FORTH primitive layer: all three
stadium_word_dispatch() call sites in vm_core.c hardcode vm_uuid_hera(),
already flagged inline as this item's job, but fixing it needs a running
VM* to know its own identity -- VMUuid exists only on VMRegistryEntry, never
on VM itself. Ruled: add a VMUuid field to VM under __STARKERNEL__ (Option
A), set once at birth from the same VMRegistryEntry.vm_id already assigned,
not threaded through the call chain (Option B). Done-when extended with a
Hermes-word-heat-lands-in-Hermes's-own-reservoir check.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-05 14:28:24 -04:00
Robert Allan JamesandClaude Sonnet 5 658459b564 FABRIC.md: rule and restate acceptance for item 4.2
Punch list §25 item 4.2 complete (scoping only, no code).
Two rulings taken before implementation starts: the stadium_owner[idx]
fix (§25.7) is explicitly folded into 4.2's scope rather than split out,
since a second VM with a quota is what makes the bug live; the C/FORTH
boundary is a small new kernel-only FORTH primitive layer
(STADIUM-ADMIT/EVICT/RES@/RES-PULL/RES-PUSH), registered like BIRTH/RUN/USE,
the only new C this item may add per HERMES.md's language constraint.
Surfaces two open questions for implementation time rather than resolving
them by assumption: the STADIUM_BEHAVIOUR_DELIVER/EXPIRE mapping for
messages/channels, and whether this dissolves HERMES.md's G8 deferral.
States Done-when acceptance, including the explicit effort-number report
§10 calls for.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-05 14:11:56 -04:00
Robert Allan JamesandClaude Sonnet 5 3d0b9351bd starkernel: item 4.1 -- hot words onto the Stadium, density-ranked eviction
Punch list §25 item 4.1 complete.
Replaces the round-robin hotwords cache with Stadium density-ranked
admission/eviction on the kernel side, via the §17.7 reservoir mechanism and a
kernel-side word_id -> cell_index map (no DictEntry change, dict_hash
untouched). Adds stadium_birth_hera() to close the cell-0 panic hazard,
STADIUM_WORD_HEAT_QUANTUM/STADIUM_WORD_COOL_RATE_Q48 Kconfig knobs (flagged
untuned), and a stadium_word_forget() FORGET coherence hook to close a
recycled-word_id aliasing gap.

Verified: all five hotwords_cache_* call sites in dictionary_management.c
bypassed under __STARKERNEL__; word dispatch feeds the Stadium at all three
vm_core.c physics_execution_heat_increment() sites; hosted make unaffected;
all three architectures booted to ok> with matching dict_hash
(0x3d4e1daf289da94f) and matching conservation stats (promotions=354
evictions=0, resident_sum=65536 reservoir=0 sum=65536).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-05 13:37:10 -04:00
Robert Allan JamesandClaude Sonnet 5 bd92c57834 FABRIC.md: unblock and restate acceptance for item 4.1
Records two decisions and restates item 4.1's Done-when criteria now that
§17.7 is DECIDED: word identity maps to a Stadium cell via a kernel-side
word_id -> cell_index table in stadium.c, not a new DictEntry field, keeping
the shared vendored struct and dict_hash untouched; acceptance no longer
leans on the old cache's HotwordsStats (being retired kernel-side, not
extended) but on new Stadium-side counters, the hosted build staying
unaffected, and a three-arch boot with dict_hash matching the pre-4.1
baseline.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-05 11:29:12 -04:00
Robert Allan JamesandClaude Sonnet 5 c0bbff21c9 FABRIC.md: correct §17.7's stadium_admit() cost claim -- free list already O(1)
3d73b8e described stadium_admit()'s O(N) scans as the typical cost, sourced
from item 3.5's commit note written before item 3.7 landed the per-VM free
lists. Reading stadium.c:312-381 directly: the free-list pop is the primary
path (O(1), no scan), and the fallback scan only triggers once a VM's own
floor is full, scoped to that VM's residents only, never the global array.
Option B is better justified than originally written, not just corrected.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-05 11:18:22 -04:00
Robert Allan JamesandClaude Sonnet 5 3d73b8e12a FABRIC.md: close §17.7 -- reservoir-based O(1) word heat transfer, DECIDED
Resolves the last open question: vm_physics_touch()'s O(n) proportional
fan-out (justified there only by rare fleet touches) isn't viable at word
dispatch frequency. Instead each VM's inner Stadium gets one reservoir
scalar; touch/cool/evict are all O(1) two-party transfers against it,
mirroring Hera's structural role at the fleet level rather than
peer-to-peer redistribution. Admission is the starter grant itself
(Option B): execution_heat stays fully inert in kernel builds, matching
the one-governor rule already committed, at the cost of running
stadium_admit()'s existing O(N) scan on every cold-word dispatch until
item 3.5's free list lands -- accepted debt, not a new gap. Corrects
abb1d92's invariant wording: residents sum to less than Q48_ONE, with
the reservoir holding the remainder.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-05 10:53:47 -04:00