docs: the prose corrections the whole-standard read and the tutorial found (#460) - #482
Merged
Conversation
gafferongames
force-pushed
the
docs-fix-460
branch
from
September 4, 2026 02:19
0283889 to
d8e2031
Compare
…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
force-pushed
the
docs-fix-460
branch
from
September 4, 2026 02:27
d8e2031 to
0c18d99
Compare
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>
This was referenced Sep 4, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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
mainafter #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 runningbin/schema; where a claim needed a fresh tree,schema generatewas run for all nine targets overtables/blockandtables/pointers.Findings closed
docs/SPEC.md:68,docs/USAGE.md:473generated/{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<Base>Flat.jsunmentioned, and<Name>Rownamed in two Rust modules. §8 named six table emitters whereinternal/codegenholds ninedocs/SPEC.md:1370intro,:1394C++,:1446Go,:1460Rust,:1482C#,:1510Java,:1848§8NODE_ENV; every<Base>Flat.jsforks onprocess.env.NODE_ENVat module load (generated/js/ClausesFlat.js:34)docs/SPEC.md:1551cmd/schema/main.go's usage prints thirteen, plushelp. Thepack/unpackformatting exception (loadTree) is stated with themdocs/SPEC.md:1647docs/SPEC.md:1820?absent from §4.1's punctuation list though the grammar andinternal/scannercarry itdocs/SPEC.md:233docs/SPEC-TABLES.md:164,:549,:1058,:3468(cookOpen, all nine),:4286(§7.5),:5275/:5279(§11),:6556(§15),:6935(§16),:8799/:8811(§20);docs/USAGE.md:614,:2243,:2517?Trow said a present key sets presence "whatever its value";internal/tabletext/read.go:564resets the field onnull, which the section's ownnullparagraph already saiddocs/SPEC-TABLES.md:7089byteorderrides the projection and no target varies it — verified withschema build-version --facts), and the cook's content address is the triple(asset hash, build version, byte order), moved everywhere the pair was spelleddocs/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,:291table Node { value int32 next *Node }and the comma form did not parse, in five SPEC-TABLES blocks and one USAGE block; two inlinetable Pair { ... }fragments were the same spelling in running prosedocs/SPEC-TABLES.md:1198,:1306,:2463,:4098,:7286,:1438,:2986;docs/USAGE.md:1499string(N)'s UTF-8 writer contract and which three targets assert itdocs/USAGE.md:382I + Fmust equal a storage width; the rule was learned by refusaldocs/USAGE.md:335,:360docs/VERSIONING.mdalready carries since #465docs/SPEC-TABLES.md:2837PB-dosURL now redirectsdocs/COMPARISON-TABLES.md:121,:163,:185,:265,:273,:274,:301,:354docs/USAGE.md:2344?Tand zero-copy without scoping them to the packet wire; "do I need the serialize runtimes? Yes" where three of nine are self-containeddocs/FAQ.md:39,:57,:178,:287,:314cpptable/block.goandctable/block.gocarry the builder half, and every other block emitter's own header says the READ sidedocs/SPEC-TABLES.md:509SourceCodeInfowithdescriptor.protoadded to the source listdocs/COMPARISON-TABLES.md:295,:313,:224,:355The 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:checkokcheckokpackage demo)checkokcheckokcheckokcheckok§2.8's maps block is not in the gate:
map[K]Vis designed and unbuilt, so the block cannot parse today by construction. Its twotabledeclarations were respelled anyway.Found by the gate
docs/USAGE.mdhad an unclosed```rustfence — 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
internal/codegenand moves the checked-in generated tree, which is outside this PR's stated scope; it wants the emitter round.<Package>Viewpair; no backend emits one) sits inside the output layout K5 rewrote, but it is its own finding and Whole-standard read at max effort: 15 contradictions, 6 narrowings, 6 pattern breaks, 13 prose-without-code, 11 versioning seams — and the one-design verdict #439 routes it to C++ consolidation review before the sweep: implementation vs standard, dead code, scaffolding, weird stuff #431's fix wave. The View sentences are untouched.gotablenames the text form's home<Home>TableJson.goafter the protocol-id home file) is not in The docs-fix PR before the sweep: every prose correction from #439 and #447 that no in-flight wire PR owns #460's list. SPEC.md's new Go bullet states what the tree emits; §19.2's claim is left for Runtime home with a file-order control: carry to cpp, c, rust, go, java, dart, elixir #422.Palettewithout declaring it. Pre-existing, not a block this PR changed, and the gate is a parse gate.Gates
make check,go test ./...,bin/schema checkover every corpus unit (examples,examples128,tables/{examples,block,blockhome,messages,stream,pointers,scalars}), andTestDocExamplesParsewith both negative controls.🤖 Generated with Claude Code