Files
Robert Allan JamesandClaude Sonnet 5 c5442c8377 docs: mark TRIPOD/HERMES/ARTEMIS/CONSOLE.md superseded by FABRIC.md/FABRIC-2.md
Captain Bob ruled directly: all four subsystem docs are superseded, not
individually assessed for partial staleness case-by-case. FABRIC.md
(design history) and FABRIC-2.md (current/living) are the sole
design-of-record for Tripod/Hermes/Artemis/Console work now.

Added a superseded-header banner to the top of all four .claude/*.md
files, pointing to FABRIC.md/FABRIC-2.md. Corrected .claude/CLAUDE.md's
own pointer paragraph, which previously claimed these four were
individually "authoritative" for their subsystems - that's now wrong.

Closes FABRIC-2.md's three open documentation questions (CONSOLE.md's
fate, HERMES.md's stale block map, 5.3's larger shrink-the-docs ask) at
once: the header approach makes reconciling a superseded document's
internal accuracy moot, and accomplishes what "shrink to a pointer" was
already trying to do.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-15 09:26:59 -04:00

576 lines
25 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# ARTEMIS.md — Artemis VM Architecture
# StarshipOS / StarForth — Captain Bob (Robert Allan James)
# This document constrains Claude Code behavior. Read it completely before touching Artemis.
---
> **SUPERSEDED (Captain Bob, 2026-08-15).** This document is no longer authoritative.
> `FABRIC.md`/`FABRIC-2.md` (repo root — read `FABRIC-2.md` first, it is the current/living
> one) are the sole design-of-record for Artemis work now. Kept here as historical record
> only; do not read this for current design authority, and do not cite it in place of
> `FABRIC.md`/`FABRIC-2.md`. Artemis becoming the pre-Artemis-closeout blocker (item 4.6) is
> tracked in `FABRIC-2.md`, not here. See `.claude/CLAUDE.md`'s own pointer.
---
## Language Constraint — Non-Negotiable
All Artemis implementation is in StarForth dialect ONLY.
This includes:
- Data structures
- Constants
- Variables
- Control flow
- Everything
C99 is forbidden unless explicitly blocked in StarForth AND explicit written permission
is given by Captain Bob for that specific construct. "I could not figure out how to do
this in StarForth" is not permission. Ask first. Wait for the answer.
If you are about to write a C struct, a #define, or a C variable — stop.
Implement it in StarForth or ask.
---
## When Stuck — Non-Negotiable
If you are stuck, STOP.
Do not confabulate.
Do not invent dictionary words that do not exist.
Do not fabricate primitives.
Do not guess at behavior.
Do not work around the problem silently.
STOP. Report exactly what is blocking you. Wait for instruction.
Silent confabulation has caused git resets on a project with a hard patent deadline.
It is not acceptable under any circumstances.
---
## What Artemis Is
Artemis is memory. She owns the external attached virtual disk image and everything
that lives on it. She manages block storage compudynamically.
Artemis does **not** move messages — that is Hermes.
Artemis does **not** make VM lifecycle decisions — that is Hera.
Artemis does **not** own the internal ramdisk — that is kernel territory.
---
## Immediate Goal
This is the complete scope for the current build. Nothing else. Everything past
this section is future material, preserved for when we circle back — not a
specification to build against today.
**The target:**
1. QEMU boots on all three architectures (amd64, aarch64, riscv64), each with
one attached raw virtual disk image — plain, no filesystem, one blank partition.
2. It is the **same single image** across all three arches, not one per arch.
3. Size: **~30MB**. Small enough for plain git, no LFS needed.
4. The image is git-tracked as a development artifact surface.
5. Artemis boots and begins managing the disk compudynamically.
### Flat Pool — No Zones Yet
For this pass, Artemis treats the entire 30MB image as **one flat pool**.
No tiering, no zone distinction, no migration. That sophistication is real
and preserved below (see Future material) but is explicitly out of scope
until this flat-pool foundation works.
### Arch-Neutral On-Disk Format
The same image is read and written by three different architectures. The
on-disk block/BAM layout must be fixed and arch-neutral — explicit byte
order, fixed cell widths — regardless of which host arch last touched it.
A block written under aarch64 must read correctly under riscv64.
### Persistence Across Arch and Reboots
**Data persists.** This supersedes the earlier "nothing persists between
runs" position — that was correct for the prior design and is no longer
correct now that the disk image itself is the point.
**Heat does not persist.** See Ground-State Rejoin, below.
### Boot-Time Disk State
At boot, Artemis must determine what she's looking at before touching
anything:
1. **BLANK** — disk is genuinely empty (no recognized marker). Initialize
fresh: free map spans the whole image, nothing allocated.
2. **LithosAnanke** — disk carries a recognized Artemis/LithosAnanke marker.
Load and resume from what's there.
3. **Unrecognized** — disk has *something* on it that is neither blank nor
a recognized marker. **Refuse to mount. Halt with a clear error.**
Do not silently treat unrecognized data as blank. Do not overwrite it.
This requires explicit human instruction to proceed, not an automatic
fallback.
### Data Loss Warning — Must Be Documented for Users and Agents
There is no filesystem layer under Artemis. No journal, no fsck, no
recovery guarantee. A bug, a bad write, or a crash mid-write can destroy
data with nothing underneath to catch it. This is a real, standing risk,
not a hypothetical — it must be disclosed plainly to anyone (human or
agent) working with this system, not buried in a design doc.
### Ground-State Rejoin
K≡1.0 is a **live-runtime** concept. It is not persisted and it does not
need to be. A device — including this same disk image — starts from
ground state (cold) every time it joins the fleet, whether this is the
first boot ever or the hundredth. The disk remembers *content*. It does
not remember *heat*. Every join is a cold join.
### Acceptance Criteria
1. Artemis correctly distinguishes BLANK / LithosAnanke / Unrecognized at boot.
2. On BLANK: free map initializes fresh across the whole 30MB flat pool.
3. Artemis can fetch a block and persist a block against the real disk image.
4. K conserves correctly across those fetch/persist operations.
5. Data written in one boot is present and correctly readable in a
subsequent boot — same image, same or different arch.
6. Unrecognized disk content halts the mount with a clear error — no
silent overwrite.
### Build Status — Implemented (2026-08-02)
Implementation happened; this section used to say "do not begin without
explicit instruction" — that instruction was given and the work is done
for this scope. `capsules/artemis/init.4th` implements the full flat-pool
target above.
**Corrected 2026-08-13 — the "boots live in the Tripod fleet... on every
kernel boot" claim below is stale since item 0.1 (`FABRIC.md` Phase 0),
same pattern already found and fixed in `TRIPOD.md`.** Two distinct things
were being conflated: Artemis's block-storage *device* (the virtio-blk
disk, `disk/artemis.img`) genuinely does auto-attach at the C level at
boot (`kernel_main.c`'s M7.pre step) on all three architectures — that
part is real and unchanged. But Artemis-as-VM-patron (capsule birth,
`CD-INIT`, announcing ready to Hermes) is not automatic: confirmed
`capsules/init.4th` births no VM but Hera, same as Hermes. Artemis's own
capsule work below (`ART-BOOT-DETECT`, `ART-FORMAT`, etc.) is real and
verified, but it runs on demand today, not as a boot-automatic fleet
join.
Acceptance criteria status, verified 2026-08-02:
1.`ART-BOOT-DETECT` — BLANK/LithosAnanke/Unrecognized classification,
confirmed working (both the blank→format and resume paths observed
live in boot logs).
2.`ART-FORMAT` — free map initializes fresh on BLANK, confirmed.
3.`BLK-FETCH`/`BLK-PERSIST` — confirmed working against the real
virtio-blk-backed disk image.
4. ⚠️ K is tracked and printed before/after alloc/free
(`ART-K-TOTAL`/`ART-SELF-TEST`), but nothing asserts the before/after
values actually match — implemented, not yet a hard-checked invariant.
5. ✅ Cross-boot AND cross-arch persistence — confirmed via
`ART-WRITE-TEST`/`ART-READ-TEST`, `PASS: persist-read` on all three
architectures against the same disk image (a write on one arch reads
correctly on the other two). Note: `disk/artemis.img` itself had been
silently stuck failing this exact test since before this repo's git
history begins (bad *data* from some pre-history run, not bad code)
until reformatted this same day — see `disk/README.md`.
6.`ART-HALT-UNRECOG` exercised against genuinely unrecognized disk
content and confirmed to leave the disk byte-for-byte untouched
(sha256 identical before/after), on amd64 and riscv64 — see
`disk/README.md`. This closes a real gap found and fixed 2026-08-02:
the *generic* block subsystem (`src/block_subsystem.c`) used to
auto-reformat any disk it didn't recognize by its own low-level
header magic, at attach time, before Artemis's Forth-level
BLANK/LithosAnanke/Unrecognized check ever ran — so a halted boot's
"Disk preserved" message was previously false. Fixed by splitting
the block subsystem's disk detection from disk commit: an
unrecognized/blank disk is now left `PROVISIONAL` (geometry computed
in memory only, all writes refused) until the disk's owner explicitly
calls the new `BLK-CONFIRM-FORMAT ( lbn -- )` primitive. Artemis calls
it from `ART-FORMAT` and `ART-RESUME` (`capsules/artemis/init.4th`
block 4126) — never from `ART-HALT-UNRECOG`. See
`src/block_subsystem.c`'s `blk_format_or_load_disk` /
`blk_commit_format` / `blk_subsys_confirm_format` for the mechanism.
Not done: everything under "FUTURE MATERIAL" below (zones, USB hot-plug,
ACL records, PKI) — that line remains accurate and unchanged.
### Surface Stress Test — permanent fixture, found a real bug (2026-08-02)
`ART-STRESS-TEST ( seed reps -- )` (`capsules/artemis/init.4th`, blocks 41604173) is a permanent, on-demand exercise of
the block-storage path across the whole data pool: `reps` distinct random blocks (coprime-stride walk, collision-free,
never touches anything already allocated by someone else), written, then verified after all writes complete so real
cache-eviction traffic gets forced through both caching layers. Every trial emits a `[ARTSTRESS]`-tagged CSV row to the
serial log (`HEADER`/`SUMMARY` markers make a truncated run unmistakable). Invoke as
`ART-STRESS` (fixed seed, 50 reps) or `<seed> <reps> ART-STRESS-TEST`. Not wired into the boot sequence — block 4170
carries the entry point commented out (`\ ART-STRESS`), same disable-by-default convention as
`ACL.4th`'s self-activation toggle, so ordinary boots pay nothing for it.
Its first completed run found a real, pre-existing data-corruption bug on amd64: 25 of 50 trials (50%) returned the
wrong byte pattern. Root cause, confirmed by source review and fixed in `src/word_source/block_words.c`
(not Artemis-specific — this is shared block-subsystem plumbing every VM using `BLOCK`/`UPDATE`/`FLUSH` goes through):
the VM-level "window" cache (`vm->blk_vm_cbuf[]`) stores a raw pointer directly into one of the block-subsystem's eight
`cache_slot_t` devblock-cache entries (`block_subsystem.c`). That cache's own FIFO eviction relocates entries via
struct-copy (`slot->cache[i] = slot->cache[i+1]`) once more than eight distinct devblocks are touched in one VM
session — the *addresses*
of `cache[0..7]` never move, but what they hold does, silently, aliasing any window pointer captured before the shift.
No prior test had ever touched enough distinct devblocks in one session to trigger it. Fixed by re-resolving the buffer
pointer by logical block number (`blk_get_buffer`)
immediately before every write-through-cache-pointer operation in
`block_word_update`, `blk_vm_evict`, and `blk_vm_flush_all`, instead of trusting a pointer captured earlier — confined
to the consumer side, no change to the block subsystem's cache architecture. Independent of, and predates, the
`BLK-CONFIRM-FORMAT` fix above; neither touches the other's code path. Re-verified 50/50 on amd64, aarch64, and riscv64
after the fix. Full write-up with hypothesis-tested analysis (a naive "consecutive writes fail together" reading did not
survive a formal runs test — see the report for what did and didn't hold up):
`experiments/artemis_stress/analysis/report/artemis_stress_report.pdf`.
---
## What Artemis Owns
- The free map over the flat pool (Immediate Goal) — zone-based Physical/Logical
BAMs are future material, see below
- Block fetch and persist operations against the disk image
- Block thermal state — heat tracking and compudynamic lifecycle
- Block metadata
- ACL lists — structured as Artemis-managed blocks, build deferred (see ACL section)
- Cold capsule store — evicted from Hermes cache (future chapter)
---
## Compudynamic Block Lifecycle
Artemis manages blocks compudynamically. Same model as everything else in StarshipOS:
- Block enters active use — born hot
- Cools over time with each heartbeat tick
- Frequently accessed blocks stay warm (heat refreshed on access)
- Cold blocks are candidates for eviction or reuse
- Block reap must rebalance K correctly
Artemis is the hardest VM to design because she has three interleaved
compudynamic concerns:
1. The free map — which LBNs are available
2. Block thermal state — how hot is each active block
3. K≡1.0 — fleet conservation across all block operations
These must be designed together — and were: see Build Status under
Immediate Goal, above, for what's actually implemented and verified.
---
## ACL — Access Control Lists
### Current State
ACL is a toggle. It is currently OFF.
A `\` comment disables ACL enforcement. When commented out, zuse is the
implicit and only user. No enforcement occurs.
```forth
\ ACL-ENFORCE ( disabled — zuse only )
```
### When ACL Turns On
ACL enforcement becomes mandatory when VMs outside the Tripod arrive.
Until then the toggle stays off.
When enforcement is enabled:
- Every VM has an identity
- Every operation is checked against the ACL
- Artemis is the authority — she holds the lists
### Security Model
Security grows with the threat surface. Not before it.
The architecture is always structurally correct — enforcement is simply
not active yet. No retrofit needed when the time comes.
Toggling ACL on must require zero structural changes to the codebase.
Only the `\` comment is removed. If toggling ACL requires anything else,
the implementation is wrong.
### Build Status — Deferred
All ACL activity — including implementing the variable-length record
scheme for ACL certs — is deferred until all three Tripod legs (Hera,
Hermes, Artemis) are working cleanly without ACL enforcement. The record
design (see Future material, Storage Design) is confirmed and ready when
that day comes; it is not a green light to build it now. Building core
Artemis storage does not require or depend on any ACL work.
---
## What Is Not Artemis
- **Internal ramdisk** — kernel territory, not Artemis scope
- **Message routing** — that is Hermes
- **VM lifecycle** — that is Hera
- **Cold capsule eviction from Hermes** — future chapter, not now
---
## Compudynamic Invariant
Every block Artemis manages participates in K≡1.0.
Block fetch, persist, and reap operations must rebalance K correctly.
Artemis's thermal contribution to the fleet is the sum of her active block heat.
Do not write code that breaks K≡1.0 and add a comment explaining why it's okay.
---
---
# FUTURE MATERIAL — Not In Scope
Everything below this line is real design work, preserved intentionally,
and is **not** part of the Immediate Goal above. Zones, multi-device pools,
hot-plug, thermal migration, and the ACL record geometry all belong here.
Do not implement any of this without Captain Bob explicitly reopening it.
### A Note on Zones
Earlier drafts of this document treated Zone 0 / Zone 1 / Zone 2 as fixed,
pre-assigned LBN ranges decided at boot. That was a mental-model
illustration, not a literal design commitment. The corrected framing:
**a device finds its own zone after it settles in** — zone membership is
an emergent property of compudynamic placement (heat, access pattern),
not a range assigned in advance. The detailed zone material below still
reflects the older fixed-range framing and needs to be reconciled with
this correction when this chapter is reopened. Do not build the fixed-range
version as written below without first resolving that.
---
## Storage Topology (Future)
Artemis manages four active storage types. Cloud blocks are a deferred future chapter.
### RAM Dedicated Blocks — Zone 0
- Direct-access block RAM; no block subsystem indirection
- LBN 0991 (1MB, per kernel memory layout)
- Artemis's hot working tier — fastest possible access
- Always present. K participation never zero.
- Physical BAM is compile-time constant
### Ramdisk Blocks — Zone 1
- Kernel ramdrive; LBN 20483071 (1MB)
- RAM-backed but accessed through the block subsystem — same I/O path as USB
- Always present. Warm tier.
- Physical BAM is compile-time constant (1024 blocks)
### System Blocks — Zone 2
- Block devices, additive/subtractive against one shared pool — devices can
arrive or depart at **any time**, not just at boot (corrected — see below)
- Artemis is owner of all block devices — no disambiguation, no pick rule
- Zone 2 is a pool: Physical BAM spans the total block count of all claimed devices
- Accessed through the block subsystem (same path as Zone 1)
- Cold/persistent tier. Largest. Slowest.
- If no device found at boot: Artemis enters NO-DISK mode (Zone 2 pool empty)
### USB Thumbdrive — Identity First, Then Storage
- Dual-role device: identity credential carrier AND storage
- Protocol: identity verified first; storage claimed after identity passes
- Hardware PKI anchor for ALL users and agent services (not zuse alone)
- Carries user certs and agent service identities (Ed25519, ACL Phase 8)
- After identity verification: storage portion claimed as a Zone 2 System Block
- Artemis must recognize and sequence this — identity gate before storage mount
### Cloud Blocks — Deferred Future Chapter
- Do not implement or design around.
---
## Device Discovery — Event-Driven, Not Boot-Only
**Correction:** external block devices are not boot-scan-only. USB devices
can arrive or depart at any time during a run. Detection is **event-driven**
— device add/remove triggers immediately, not on a tick poll.
Device presence is a runtime condition, not a boot invariant:
- **Device arrives** — admitted into the shared Zone 2 pool (additive).
Physical BAM extends to cover it. K rebalanced.
- **Device disappears** — must not crash; graceful K rebalancing; all
logical entries backed by that device reap immediately (subtractive).
- Multiple devices may be present simultaneously in one shared pool.
- The USB thumbdrive's identity-first gate is specific to that
credential-carrying device; plain storage devices do not carry an
identity-verification step.
The exact mechanism for growing the Zone 2 Physical BAM without a live
resize (e.g., per-device LBN segments rather than one array that grows)
is not yet settled — needs design work when this chapter reopens.
---
## Storage Design (Future)
Record design confirmed 2026-07-01: dynamic-directory scheme (offset +
length + identity + flags per entry) for variable-length records —
supersedes an earlier same-day consideration of fixed-size slots. This is
design-confirmed, not build-authorized. See ACL Build Status, above.
### Driving Principle
Artemis is managed compudynamically. The same heat/cool/reap model that drives
Hermes message lifecycle and word execution in Hera drives block lifecycle in
Artemis. Compudynamics is not bolted on — it IS the allocator.
### Two BAMs
Artemis uses two Block Allocation Maps with near-identical cell-based structure.
No zone tag in either — the physical LBN is the block's location. Zone membership
is derived from the LBN range at query time. A block that knows its physical LBN
knows everything it needs.
**Physical BAM — 2 cells per entry**
```
cell 0 — physical LBN
cell 1 — free flag ( 0=free 1=allocated )
```
- One entry per physical data block slot
- Per-zone array (each zone has its own PBAM)
- The raw substrate. Knows free/not-free. No heat, no identity.
**Logical BAM — 4 cells per entry**
```
cell 0 — physical LBN ( zone derived from LBN range )
cell 1 — heat ( Q48.16 — compudynamic lifecycle driver )
cell 2 — identity ( XXHash64 content hash — 1 cell on 64-bit )
cell 3 — flags
```
- Spans all zones in one array
- Heat=0 → reap: physical LBN returned to PBAM free pool
- Content-addressing, ACL records, and cold capsule storage live here
- Identity is derived from block content — same content, same identity
- Mutation is detectable: hash changes if content changes
- Dedup is natural: duplicate ACL certs, capsules, or data recognized without scan
- Consistent with capsule identity (capsule ID = XXHash64 content hash)
- Uses existing `xxhash64.c` — no new dependency
### Compudynamic Block Lifecycle (sketch)
1. **Alloc** — claim a physical LBN from Physical BAM, create a logical entry,
born hot (Q.1)
2. **Access** — heat refreshed on every read or write
3. **Cool** — each heartbeat tick applies Q-DECAY to all logical block heat values
4. **Reap** — logical entries at heat=0 release their physical LBN back to
the Physical BAM free pool
### K Participation
Every logical block's heat contributes to Artemis's K total.
Reap must credit K back. Alloc must charge K correctly.
The Physical BAM carries no K — only live logical blocks do.
### Thermal Zones
Artemis manages **three thermal zones**, ordered fastest to slowest:
**Zone 0 — Block RAM (LBN 0991)**
- Raw RAM, direct access, no block subsystem indirection
- Artemis's hot working tier — fastest possible access
- Always present. Artemis's floor. K participation never zero.
- Physical BAM is compile-time constant: 992 bits = 124 bytes
- No 4KB→3+1 geometry — Zone 0 is 1KB native, no sector alignment constraint
- 1:1 layout: one data block per LBN; no implicit metadata sibling blocks
- Block metadata lives in the LBAM entry, not on-block
- LBN 9922047: VM dictionary territory (kernel/kmalloc); Artemis does not touch
**Zone 1 — Ramdrive (LBN 20483071)**
- RAM-backed but accessed through the block subsystem —
same interface as an external device
- This is why it is Zone 1 and not Zone 0: it goes through the
same block I/O path as a USB device, just faster
- Always present. Warm tier.
- Physical BAM is compile-time constant (1024 blocks = 1MB)
**Zone 2 — External USB Device**
- Dynamic, event-driven presence (see Device Discovery, above)
- Accessed through block subsystem (same path as Zone 1)
- Physical BAM sized from discovered/claimed block count, grows and
shrinks as devices join/leave
- Cold/persistent tier. Largest. Slowest.
Block lifecycle across zones (sketch):
- Born hot in Zone 0 (direct block RAM)
- Cools → migrates to Zone 1 (ramdrive, same block subsystem as Zone 2)
- Cools further → migrates to Zone 2 (System Blocks, if present)
- Cools to zero in Zone 2 → reaped; physical slot returned to Zone 2 Physical BAM
- If Zone 2 absent: Zone 1 is the cold terminus; reap happens there
When Zone 2 disappears mid-run: all Zone 2 logical entries reap immediately,
K rebalanced, Zones 0 and 1 continue unaffected.
Each zone has its own Physical BAM. The Logical BAM spans all zones and
carries a zone tag per entry.
### Migration Policy
No zone is special. Blocks are blocks. The same compudynamic rules apply
uniformly across all zones:
- Every block cools each heartbeat tick via Q-DECAY regardless of zone
- When a block's heat crosses the migration threshold it moves to the next zone
- Zone membership is a property of current location, not a different regime
- No write-buffer, no forced flush, no per-zone special cases
- Pressure (Physical BAM full) resolves naturally: the coldest block migrates
first because it is coldest — not because of any zone policy
- Migration copies the block to the next zone and frees the source physical slot
- The Logical BAM entry zone tag is updated; heat carries over unchanged
- Reap happens at heat=0 in whichever zone the block occupies at that moment
There are no migration threshold values. Compudynamics provides the policy
directly: when a zone has pressure (Physical BAM full), the coldest block
migrates — heat ordering determines the candidate, zone pressure is the
trigger. No magic constants. Reap fires at heat=0, a natural zero-crossing.
Everything else is self-organizing.
### Physical BAM Block Geometry
The block subsystem operates in 1KB StarForth blocks. Physical devices
operate in 4KB sectors. The mapping:
```
1 physical 4KB device sector → 4 × 1KB StarForth blocks:
[ data ][ data ][ data ][ meta ]
LBN+0 LBN+1 LBN+2 LBN+3
```
- **3 data blocks** — usable storage, tracked in the Physical BAM
- **1 metadata block** — describes the preceding 3 data blocks
(heat, identity, flags per sibling; plus variable-length record index)
- Metadata position is implicit: always at (group × 4) + 3
- Physical BAM tracks data block slots only — 3 bits per 4KB sector
**Metadata block layout (LBN+3):**
```
[071] 3 × sibling descriptor: [heat | identity | flags] 72 bytes
3 cells × 3 siblings
[721023] variable-length record index 952 bytes
entry: [offset | length | identity | flags]
4 cells = 32 bytes per entry → ~29 entries per group
```
Variable-length records (ACL certs, ~200 bytes) are packed into the 3KB
data space of LBN+0/1/2. The metadata block's remaining 952 bytes index
them: offset into the data area, byte length, XXHash64 identity, flags.
Artemis finds a specific record by identity — no content scan needed.
The 3+1 geometry provides variable-length support at no extra cost.
Worked example for Zone 1 (ramdrive, 1024 × 1KB = 1MB):
- 1MB ÷ 4KB = 256 groups
- 256 × 3 = **768 data blocks** tracked in Zone 1 Physical BAM
- 256 × 1 = 256 metadata blocks (implicit, not in BAM)
- Zone 1 Physical BAM = 768 bits = **96 bytes**
Zone 2 (USB): same geometry, group count determined at runtime from
claimed device block count, adjusted as devices join/leave.
---
*This document is authoritative. If it conflicts with something in the codebase,
the codebase is wrong.*