Skip to content

docs: the prose corrections the whole-standard read and the tutorial found (#460) - #482

Merged
gafferongames merged 2 commits into
mainfrom
docs-fix-460
Sep 4, 2026
Merged

docs: the prose corrections the whole-standard read and the tutorial found (#460)#482
gafferongames merged 2 commits into
mainfrom
docs-fix-460

Conversation

@gafferongames

@gafferongames gafferongames commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

Closes the prose half of #460: every correction from the whole-standard read (#439) and the tutorial read (#447) that no in-flight wire PR owns, plus the competitor gather's rows, the second versioning cold read's three, and the competition page's FAQ and comparison items. Prose only — no wire, no emitter, no golden moves — so it lands beside the open wire PRs. Rebased on main after #465, #429 and #483.

Every correction is made where the code is right and the page is wrong. Each was verified by reading the generated tree under generated/ or by running bin/schema; where a claim needed a fresh tree, schema generate was run for all nine targets over tables/block and tables/pointers.

Findings closed

id what was wrong where it changed
C12 §1's non-goal denied the table wire outright ("data that must outlive builds is out of its scope entirely") docs/SPEC.md:68, docs/USAGE.md:473
C1 / K7 §5's writer-misuse list, "exhaustive at all nine", is wrong on five: C, C#, Go, JavaScript and Rust return failure; nothing panics or throws. Read out of generated/{c,cpp,cs,dart,elixir,go,java,js,rust}. Its "costlier contracts assert in DEBUG ONLY everywhere" is narrowed to §4.7's own list (C, C++, Rust) docs/SPEC.md:1220
K5 §6.1's output layout did not describe the tree: the C++ Block pair missing, C# stale at one file, Go carrying no table output, no Java bullet at all, JS's <Base>Flat.js unmentioned, and <Name>Row named in two Rust modules. §8 named six table emitters where internal/codegen holds nine docs/SPEC.md:1370 intro, :1394 C++, :1446 Go, :1460 Rust, :1482 C#, :1510 Java, :1848 §8
C15 §6.1 said generated JS never reads NODE_ENV; every <Base>Flat.js forks on process.env.NODE_ENV at module load (generated/js/ClausesFlat.js:34) docs/SPEC.md:1551
K6 §7 listed six CLI commands; cmd/schema/main.go's usage prints thirteen, plus help. The pack/unpack formatting exception (loadTree) is stated with them docs/SPEC.md:1647
C11 §7.4 rule 9 said union tags sort by name; §4.8 and the code say declared order docs/SPEC.md:1820
N6 ? absent from §4.1's punctuation list though the grammar and internal/scanner carry it docs/SPEC.md:233
C13 / K13 / F-13 backend counts that still said two, five, six or seven where all nine carry the table wire docs/SPEC-TABLES.md:164, :549, :1058, :3468 (cook Open, all nine), :4286 (§7.5), :5275/:5279 (§11), :6556 (§15), :6935 (§16), :8799/:8811 (§20); docs/USAGE.md:614, :2243, :2517
C14 §16.2's ?T row said a present key sets presence "whatever its value"; internal/tabletext/read.go:564 resets the field on null, which the section's own null paragraph already said docs/SPEC-TABLES.md:7089
V4 / F-22 the byte-order seam under #432: the build version is target-neutral in effect (byteorder rides the projection and no target varies it — verified with schema build-version --facts), and the cook's content address is the triple (asset hash, build version, byte order), moved everywhere the pair was spelled docs/SPEC-TABLES.md:3512, :3520, :3911, :4090, :4482, :8879 (§20.1), :9256 (§20.6), :9339 (§20.4); docs/USAGE.md:2131, :2260, :2276; docs/COMPARISON-TABLES.md:164, :291
K8 table Node { value int32 next *Node } and the comma form did not parse, in five SPEC-TABLES blocks and one USAGE block; two inline table Pair { ... } fragments were the same spelling in running prose docs/SPEC-TABLES.md:1198, :1306, :2463, :4098, :7286, :1438, :2986; docs/USAGE.md:1499
F-10 USAGE omitted string(N)'s UTF-8 writer contract and which three targets assert it docs/USAGE.md:382
F-11 USAGE never said I + F must equal a storage width; the rule was learned by refusal docs/USAGE.md:335, :360
#464 §4.1's declaration-rename row was half right: a table's name is its node's type id on the wire (§5), so renaming a POINTER TARGET leaves every node unnameable and every pointer to it null. This is the row docs/VERSIONING.md already carries since #465 docs/SPEC-TABLES.md:2837
#460's comment (competitor gather) "exactly three" silent edits where §4.1 says four; "both competitors' own guidance says not to" where only Protobuf's does; FlatBuffers' verifier is C++, C and Swift and it is the reflection row that reads basic in C; the Protobuf enum cell omitted protobuf.dev's own nonconformance list; the PB-dos URL now redirects docs/COMPARISON-TABLES.md:121, :163, :185, :265, :273, :274, :301, :354
USAGE's silent-edit count (second versioning cold read) "Exactly three edits are different" where §4.1 says four; the fifth, a retired name reused, is named as schema#441 docs/USAGE.md:2344
FAQ (competition page cold read) the mmap answer said that case "is deliberately not schema's job" — the cook is exactly that job; two answers denied evolution outright; the NOT-have list denied maps, ?T and zero-copy without scoping them to the packet wire; "do I need the serialize runtimes? Yes" where three of nine are self-contained docs/FAQ.md:39, :57, :178, :287, :314
§19's block-form status (competition page rework) said the block form is "live in C++, C, C#, Go, Rust, Java and Elixir, and READ by JavaScript and Dart"; only cpptable/block.go and ctable/block.go carry the builder half, and every other block emitter's own header says the READ side docs/SPEC-TABLES.md:509
Three competitor cells (same rework) "header-only runtime" is claimed by no FlatBuffers page; "ProtoJSON in every runtime" ignores protobuf.dev's own nonconformance list; doc-comment source info was cited to nothing, and now names SourceCodeInfo with descriptor.proto added to the source list docs/COMPARISON-TABLES.md:295, :313, :224, :355

The K8 gate

compiler/docs_test.go (TestDocExamplesParse) names each fixed block by an anchor line, pulls the fenced block that holds it out of the page, and parses it. Both failure modes were planted and observed red: a block edited back to the one-line form fails on the anchor ("this gate names an example that has moved"), and a block that keeps its anchor but breaks the grammar fails on the parse, printing the block.

Beyond the parse gate, every changed block was extracted into a scratch unit and run through bin/schema check — the full typecheck, not just the parse:

block verdict
SPEC-TABLES §2.6, union arms as tables check ok
SPEC-TABLES §3.1, framing worked check ok
SPEC-TABLES §7.3, a cook worked to the byte (package demo) check ok
SPEC-TABLES §16.7, one declaration and two texts check ok
USAGE, messages: a union whose arms are tables check ok
SPEC.md §4.9, the complete example check ok

§2.8's maps block is not in the gate: map[K]V is designed and unbuilt, so the block cannot parse today by construction. Its two table declarations were respelled anyway.

Found by the gate

docs/USAGE.md had an unclosed ```rust fence — the Rust table-surface example was truncated mid-if, and every renderer swallowed the paragraph that followed it into the code block. Closed with the tail its Go twin twenty lines below already carries (docs/USAGE.md:692).

Not done, and why

Gates

make check, go test ./..., bin/schema check over every corpus unit (examples, examples128, tables/{examples,block,blockhome,messages,stream,pointers,scalars}), and TestDocExamplesParse with both negative controls.

🤖 Generated with Claude Code

gafferongames and others added 2 commits September 4, 2026 12:26
…found (#460)

Prose only — no wire, no emitter, no golden moves — so it lands beside the
open wire PRs. Every correction is made where the code is right and the page
is wrong, each verified by reading the generated tree or running the binary.

**The two-wire front door.** SPEC.md §1's non-goal denied the half of the
product SPEC-TABLES.md specifies ("data that must outlive builds is out of
its scope entirely"); it now scopes itself to the type wire and names the
table wire as the other answer (C12). USAGE.md's "The wire" section and three
FAQ answers — "isn't this just Protobuf", "what happens when I change a
schema", and the NOT-have list — carried the same denial and are rewritten
the same way, keeping the honest half: what schema does not offer is one wire
that does both.

**Writer misuse, per target, from the generated code.** SPEC.md §5 named a
list "exhaustive at all nine" that is wrong on five of them. Read out of
generated/: three debug-assert (C++ `serialize_assert`, Dart and Java
`assert`), Elixir raises `ArgumentError` in every build, and C, C#, Go,
JavaScript and Rust return failure — `0`, `false`, `serialize.ErrValueOutOfRange`,
`false`, `Err(ValueOutOfRange)`. Nothing panics and nothing throws. The
paragraph's "costlier contracts assert in DEBUG ONLY everywhere" is narrowed
to §4.7's own exhaustive list (C1, K7).

**Output layout and the CLI, from the tree and from main.go.** §6.1's bullets
are rewritten against `schema generate` run over a table-bearing unit for all
nine: the C++ Block pair, the C# per-file Block/Cook and three package
runtime homes, Go's three table files plus `<Home>TableJson.go`, Rust's
`<Name>Cook` where `<Name>Row` was named twice, JavaScript's `<Base>Flat.js`,
and a Java bullet where there was none. §7's six commands become the
binary's own thirteen plus `help`, with the `pack`/`unpack` formatting
exception stated. §8's "six table emitters" becomes the nine that exist (K5,
K6, K13).

**NODE_ENV.** §6.1 said generated JS never reads it; every `<Base>Flat.js`
forks on `process.env.NODE_ENV` at module load. The claim is now the
runtime tier's, and the flat tier's fork is stated where the module is (C15).

**The byte order, under #432's ruling.** The build version is target-neutral
in effect — `byteorder` rides its projection and no target varies it — so
§7's "the byte order is a fact of the build version", §7's pair, §19's "a
matching build version already means a matching byte order", USAGE's cook and
build-version sections and §20.4's inclusions list all say the same thing
now: the cook's header refuses a foreign order, and the content address is
the triple `(asset hash, build version, byte order)`, moved everywhere the
pair was spelled (V4, F-22).

**Counts and rows.** §4.8 against §7.4 rule 9 on union tag order (C11);
§16.2's optional row, where `null` is the one key value that reads as absence
(C14); `?` added to §4.1's punctuation list (N6); the backend counts that
still said two, five, six or seven where nine carry the table wire, across
the SPEC-TABLES preamble, §7, §7.5, §11, §15, §16, §20 and USAGE
(C13, K13, F-13). §4.1's declaration-rename row gains the
qualification it was missing: silent when the table is held by value, but a
table's name is its node's type id on the wire, so renaming a POINTER TARGET
leaves every node unnameable and every pointer to it null — the row
VERSIONING.md already carries since #465 (#464).

**USAGE gains two rules a learner met only as a refusal**: `string(N)`'s
UTF-8 writer contract and which three targets assert it (F-10), and that
`I + F` must equal a storage width (F-11). Its silent-edit count becomes four
per §4.1, with the retired-name reuse named as the fifth that the baseline
cannot see yet (schema#441).

**The examples parse now.** `table Node { value int32  next *Node }` and the
comma form appear in five SPEC-TABLES blocks and one USAGE block and none of
them parsed; each is expanded to the canonical multi-line form, and the two
inline `table Pair { ... }` fragments are respelled as prose. A truncated
```rust fence in USAGE — an unclosed block that swallowed the paragraph after
it — is closed. compiler/docs_test.go is the gate: it names each block by an
anchor line, pulls the fenced block out of the page and parses it, so an
example that stops parsing goes red and an anchor that stops existing goes
red with the page and the line to look at. Both failure modes were planted
and observed (K8).

**docs/COMPARISON-TABLES.md**, from the competitor gather: the silent-edit
count takes §4.1's four; the required-fields claim narrows to Protobuf, whose
own guidance says "Never add a required field" where FlatBuffers describes
`required` neutrally; FlatBuffers' verifier is C++, C and Swift and it is the
reflection row that reads basic in C; the Protobuf enum cell gains
protobuf.dev's own nonconformance list; and the `PB-dos` source URL follows
its redirect.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
… producers, and three competitor cells (#460)

**§19's block-form status said seven backends carry it live.** Only
`cpptable/block.go` and `ctable/block.go` carry the builder half; every other
block emitter's own header says the READ half or the READ side, and §19's
Elixir paragraph already said that backend never produces a block or a cook.
The sentence now says what the tree does: built by C++ and C, read by the
other seven, and none of those seven emits a fill path.

**Three COMPARISON-TABLES cells cited more than their sources say.**
"Generated code dependencies" called FlatBuffers a header-only runtime, which
no FlatBuffers page claims — FB-cpp has generated code needing
`flatbuffers/flatbuffers.h` on the include path and the text and schema
parsers linking further sources — so the cell states that instead. "Text
form" said ProtoJSON is in every runtime; protobuf.dev's JSON page names its
own nonconformant implementations, and the cell now carries them. "Doc
comments" attributed source-info preservation to nothing on protobuf.dev; it
now names `SourceCodeInfo` and its comment fields, with `descriptor.proto`
added to the source list as PB-descriptor. The provenance line says which
rows were re-read on 2026-09-04.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@gafferongames
gafferongames merged commit d945abf into main Sep 4, 2026
20 checks passed
@gafferongames
gafferongames deleted the docs-fix-460 branch September 4, 2026 02:31
gafferongames added a commit that referenced this pull request Sep 4, 2026
#482 excluded the block because maps did not parse. They do now, so the block
is anchored like every other declaration example and the header comment says
what the gate actually is: the PARSER's, so a block enters the moment its
construct has a grammar, whether or not a backend carries the codec.

Negative control: respelling the example to the REJECTED map<K, V> form turns
the gate red, naming the page, the anchor and the line.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
gafferongames added a commit that referenced this pull request Sep 4, 2026
#482 excluded the block because maps did not parse. They do now, so the block
is anchored like every other declaration example and the header comment says
what the gate actually is: the PARSER's, so a block enters the moment its
construct has a grammar, whether or not a backend carries the codec.

Negative control: respelling the example to the REJECTED map<K, V> form turns
the gate red, naming the page, the anchor and the line.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
gafferongames added a commit that referenced this pull request Sep 4, 2026
…nerated entry (#380) (#487)

* tables: the map front end — the `map[K]V` spelling, its key rules and the generated entry (#380)

The scanner takes `map` as a keyword; the AST carries the value as a whole
FIELD spelling, so a map of arrays, of optionals and of maps are one
production; the parser reads `map[K]V` wherever a field type stands.

The IR gains ir.TMap and Field.MapEntry — the generated `{ key, value }`
table, a real table of the closure with its own record, Reset and descriptor.
ir/tablemap.go carries the derived facts: the entry's name, its two constant
ids as ordinary hashes of two ordinary names, the census a refusal names.
VariableTables gains §2.8's clause (a map is a variable edge whatever its key
and value are) and TableClosure reaches through the entry.

The checker resolves the value through the ordinary field path and holds every
rule §2.8 states, each refused by name with the page's own reason: a map in a
`type` body, an enum key (naming [E]T), bool, float, flags, bits(N),
bytes(N), fixed, a type, a table, a pointer, an optional and a union keys, a
bound on the map, ?map, a default, an attribute, the by-value cycle through
the entry, and a declared table under the claimed <Table><Field>Entry name.

Every other backend refuses a map-bearing unit by name, through
compiler/tablesmaps.go per the registry split.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* tables: the map's wire kinds, record piece and every backend's refusal (#380)

The IR facts a map's codec will read, and the gate that keeps a half-codec
from being emitted before one exists.

- ir/tablekind.go: a map rides as kind 14 over element kind 13 — an array of
  the generated entry, no new kind and no new framing (§2.8, §3) — and
  TableTypeSpelling renders `map[K]V` so a diagnostic and a descriptor name
  the declaration the author wrote.
- ir/blocklayout.go: a map field is ONE sixteen-byte piece at eight (§2.8,
  §7.2) — an int64 self-relative reference and a uint32 count, then padding —
  one piece and not two for the reason the block form's out-of-line triple is
  one: both backends spell it as one member of a TableMap type, and a port
  that walked two would account for twelve bytes where sixteen are written.
- Every backend refuses a map-bearing unit by name, the C++ reference
  included, until its codec lands (compiler/tablesmaps.go). The refusal names
  the fields and schema#380, and target_cpp drops its call and registers
  through registerMapCarrier when its half lands.
- The zero-cost gate's header scan gains the map symbols (§2.2, §2.8): every
  unit it scans is map-free by construction, and the symbols say so
  mechanically rather than by inspection.
- The parser takes `?` on a key so the checker's OPTIONAL-key diagnostic lands
  where the spelling is, rather than a parse error naming a bracket.

Held by: compiler/tablesmaps_test.go (the derived facts at three depths, the
two constant ids, every target's refusal, and a map-free unit untouched) and
24 new rows in internal/check's refusal suite.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* tables: the carrier registry's second refusal shape, held by a test (#380)

refuseMaps has two shapes — no carrier yet, and a carrier named — and only the
first had a call site. The test registers cpp through registerMapCarrier and
holds the second, so the day target_cpp.go registers for real the message it
produces is already pinned. It also keeps the registry function from being
dead code between the two PRs.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* tables: §2.8's map example joins the doc-examples gate (#380, #460)

#482 excluded the block because maps did not parse. They do now, so the block
is anchored like every other declaration example and the header comment says
what the gate actually is: the PARSER's, so a block enters the moment its
construct has a grammar, whether or not a backend carries the codec.

Negative control: respelling the example to the REJECTED map<K, V> form turns
the gate red, naming the page, the anchor and the line.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* tables: a map's spelling names the value's star (#380)

ir.TableTypeSpelling rendered map[uint32]*ShipConfig as map[uint32]ShipConfig,
so a diagnostic and a descriptor would have named a declaration nobody wrote —
and map[K]T against map[K]*T is exactly the edit §3.1 spends a kind to keep
from being silent. Pinned by TestMapTypeSpelling over the three shapes.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* tables: schemafmt keeps the map spelling, and the pointer star binds to its type (#380)

Every command formats a source in place before it processes it, so a spelling
the formatter does not know is a spelling it silently rewrites. `needSpace` had
no case for the `map` keyword and none for the `]` that closes a map key, so
§2.8's own example came back as `map [string(32)]ShipConfig`,
`map [uint32] * ShipConfig` and `map [uint32] ?[..4]int32`: idempotent, and
away from the page for good.

The `] * ` half is not new. `[..2]*Chunk` in the stream corpus has been stored
as `[..2] * Chunk` since pointers landed, because type position was recognized
only at index 1. It is the same defect one line away, so it is fixed here.
Type position is index 1, the `]` that closes an array bound or a map key, and
the optional `?`. No expression in this grammar ends in either, so the rule
takes no case away from multiplication.

The formatter's SAFETY CHECK was blind to the construct too. Format re-parses
its own output and compares the two ASTs structurally, and the fingerprint
rendered no map at all, so two different maps and a map against a bare field
all fingerprinted alike. It renders the whole field type now: the key, the
bound, the star and a nested map included.

The gate reads §2.8's example OFF THE PAGE and compares the formatted bytes,
with the mangled spellings as its controls. Each is an input schemafmt itself
used to produce, and each must come back as the page states it.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* tables: the key takes no qualification, the map claims eight names, and SPEC.md carries the grammar (#380)

The FRONT END's half of §2.8 and §11, and the SPEC.md lines the construct was
missing.

- A QUALIFICATION OR A DEFAULT ON THE KEY is a refusal with the page's own
  sentence, not a parse error about a bracket. The grammar takes
  `map[uint32 | max = 4]int32` and `map[uint32 = 5]int32` on the terms it
  already takes `map[?uint32]`, so the diagnostic names the real problem: a key
  is an identity, and clamping an identity merges two entries.
- THE ENUM-KEY SUGGESTION never names a spelling the next compile refuses, and
  it keeps the value's array bound. `map[E][..4]int32` suggested `[E]int32`, a
  different declaration; `map[E]*V` suggested `[E]*V`, which the checker
  refuses as a named follow-on. The replacement clause now asks whether `[E]T`
  takes the value at all, and says what to do instead when it does not.
- THE MAP CLAIMS EIGHT NAMES against its field, on the rule §11 already states
  for the block form's row accessors: `<Table>` plus the field's PascalCase,
  then Entry, Insert, Find, Erase, Each, IndexMeasure, Index and IndexFind.
  The runtime names TableMap and TableMapIndex join internal/tablenames on
  TableKeyed's terms, claimed with the construct rather than with the codec,
  because a name freed now is a collision the day the codec lands.
- THE ENUM-KEY TEST fixture is renamed. It was an enum literally named `E`
  against a diagnostic that prints `[E]T`, so it could not tell a substitution
  from a hardcoded string. It is `ShipType` now.
- ir.MapEntryStructs, ir.MapKeyIsString and ir.HasMap go: scaffolding with no
  caller outside the tests. ir.MapFields lists the fields an AUTHOR WROTE, so
  a target's refusal no longer names `FleetLoadoutsEntry.value`, a generated
  name in no source file.

SPEC.md gains `map` in the reserved words and a `Map` production in the `Type`
grammar, with the same TABLE BODIES ONLY note the `?` and `*` productions
carry. #479 is SPEC-TABLES.md only, so nothing else covered it.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* tables: a map's entries are never rows, and neither projection sees the entry's name (#380)

The DERIVED FACTS half, and the cook projection's form version with it.

THE BLOCK FORM. §2.8 states that a map's entries are never rows, and it was
not so. `blockFormable` asked only whether a table is variable-length, and
`FleetShipsEntry`, a `string(32)` key and a by-value `ShipConfig`, is fixed,
so it took a block form, its counts, its storage and its projection asserts.
The rule is categorical now, because the entry can look perfectly fixed. And
the reason a table has no block form NAMES THE EDGE THE WALK FOUND, a pointer,
a map, or the member that reaches one, instead of the hardcoded sentence about
a pointer, which was emitted verbatim over a table whose only variable edge was
a map.

THE TWO PROJECTIONS. A generated entry is ANONYMOUS in both (§18.1, §20.2):
its record line carries the holder's wire id and the map field's wire id
joined by a dot, in place of a name, and both projections SORT over that text.
The entry's generated name is derived from the field's source spelling, so a
`was` rename moved a line, moved the record order and invalidated every cooked
file, under an edit that moves no byte. A test pins that the rename now moves
neither the projection nor the build version.

The map field's line is an array line: `kind=14`, `array=map`, `elem=` the
generated entry's own storage size, which is the pitch its entries lie at and
what `elem=` means on every other array line this projection writes, and no
`bound=`, because a map declares no extent. The KEY's kind and capacity ride on
the entry's own `key` line, which is where a key edit moves the id.

THE BASELINE gains the map shape token and the key's two facts beside it, and
the two policy rows that judge them. A key kind is fixed, because a key
arriving under a kind the reader does not declare resets the whole map to
empty; a key bound is an extent, because an entry whose key does not fit is
skipped whole and counted. The `[..N]Pair` to `map[K]V` edit is one WARN with
the page's reason, and it is one finding rather than four: the tokens the
construct owns describe the same edit, so judging them beside the shape would
say it three times and say it harsher. Two new judged tokens bump the baseline
rendering version to 5, and the two committed baselines are regenerated with
their history intact.

THE COOK PROJECTION'S FORM VERSION moves to 2, because this rendering emits
tokens the last one did not. Every unit's build version moves with it, map-free
ones included, which is what a form version is for. §20.2's worked number,
compiler's pin, docs/USAGE.md's printed number, the build-version goldens, the
generated headers' BuildVersion constants and every cooked conformance
artifact are re-pinned in this commit.

THE ENTRY IS NOT A ROOT. §2.8 makes it the one exception to §7's "a root is
any table": it is reached only through the map that generates it, so the C++
reference emits it no Open, no Cook, no Save and no Load, and its walk, its
layout and its cook body are the whole of what it carries.

§11's Maps list gains the two keys the checker already refuses and the page
did not name: a 128-bit integer and a fixed-point key. The map slot's count is
an int32 like every companion, and the IR says so.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant