Skip to content

tables: string, bytes and flags defaults, and was on a table declaration (#396) - #576

Merged
gafferongames merged 4 commits into
mainfrom
was-rows-1
Sep 5, 2026
Merged

tables: string, bytes and flags defaults, and was on a table declaration (#396)#576
gafferongames merged 4 commits into
mainfrom
was-rows-1

Conversation

@gafferongames

@gafferongames gafferongames commented Sep 5, 2026

Copy link
Copy Markdown
Contributor

The first of the two was rows (#396, adopt items 2, 3 and 4): defaults for string(N), bytes(N) and flags fields, and was on a table declaration, in the C++ reference, the tool, the baseline and the pages.

What rides

The three defaults. SPEC §4.2's Default production takes a quoted string for string(N) and bytes(N) (at most N bytes, valid UTF-8 for a string) and a brace list of variant names for a flags field (caps Caps = { Jump, Crouch }, {} the zero mask). On the table wire a field at its declared default elides and an absent field reads as it, which is the scalar rule with nothing added, so an EMPTY string rides when the default is not empty. The default is a projection fact, a cook-projection fact and a baseline fact (default=bytes:<hex> for a string or bytes default, the mask for a flags default). The C++ reference and the tool carry the three; every other backend refuses a unit that declares one, naming the follow-on (compiler/valuedefaults.go, TestValueDefaultsAreCppOnly).

was on a table declaration. table Ship | was = "Vessel": the node type id is the hash of the OLD name, so every stored record still reads, and every id derivation reads the wire name: the node record, the cooked node directory, the announced vocabulary, the baseline and the build version. A was rename of a table moves neither id. The refusals are the field's: was naming the table's own name, was = "", an alias colliding with a live table's type id or taking a held-back id, and was on a type declaration, which rides by value and has no node type id to keep.

The baseline keys a table by its wire name and records the declared name beside it (table Vessel name=Ship, on the renamed line only, judged on nothing), so a rename under was regenerates every line byte-identically and the rendering version is unmoved. A second rename aimed at the intermediate spelling is refused naming the first (TestTableWasChainIsRefused). A string, bytes or flags default changed, added or removed is refused as a scalar's is (TestValueDefaultsAreJudgedAsDefaults), with the attribution control (the default rule removed, the edit passes).

The projection. TestTableRenamedUnderWasMovesNeitherId: the rename under was moves neither the protocol id nor the build version, and the discrimination control renames the table bare and watches the build version move. TestValueDefaultsProject: each of the three defaults moves both ids when changed.

The corpus. test/tables/W1.schema / W2.schema are the evolution pair: W1's Vessel is W2's Ship | was = "Vessel", and both carry a string, a bytes and a flags default beside a type a table reaches with a string default of its own. Three pinned instances (w1_fleet, w1_fleet_default, w2_fleet) ride every conformance surface, w2_fleet byte-identical to w1_fleet, type id included, and w1_fleet_default the empty wire. Three report rows pin the silent cross reads:

w1_fleet_as_w2         0,0,0,0,false,read
w1_fleet_default_as_w2 0,0,0,0,false,read
w2_fleet_as_w1         0,0,0,0,false,read

Gates and their negative controls

make tables-was-negative-control compiles test/tables/was_control_main.cpp twice, against the shipped W2 and against a W2 with | was = "Vessel" stripped and regenerated by the shipped compiler, and reads the same w1_fleet golden through both:

unknown=0 kind_mismatch=0 malformed=0 flagship=Aurora escorts=2 home_name=untitled
unknown=2 kind_mismatch=0 malformed=0 flagship=null escorts=2 home_name=untitled
negative control: stripping was from the renamed table turns the cross read RED (unknown counted, the pointers null, the value at its default)

The Go gates, each with the control the file already runs on every case:

  • internal/check/was_test.go: TestValueDefaultAndTableWasRefusals, seventeen refusals by name (a default past the capacity, not UTF-8, a quoted string on a scalar, a brace list on a scalar or an enum, a name that is not a variant, a repeated name, an integer on a flags field, a wstring default, was on a type, a table's was naming itself, empty, unquoted, bare, colliding with a live table, and a valued key a table declaration does not take), and TestValueDefaultsAndTableWasResolve for what each accepted spelling lands in the IR.
  • internal/baseline/was_test.go: the three tests above, with the discrimination control (a table renamed bare warns that Vessel is no longer in the closure under that name) and the attribution control.
  • ir/was_test.go, internal/format/was_test.go (the three defaults and the table attribute through schemafmt, idempotent), compiler/valuedefaults_test.go (eight ports refuse, naming the fields and --lang cpp).
  • test/tables/main.cpp test_was_rows: everything at its default is the empty wire, an absent field reads its declared default, a string cleared to "" rides, and the cross read in both directions with a byte-identical re-save.

The zero-cost law (2026-09-05)

This PR adds no counter, reason or descriptor. The one emitter change on a read or write path is the elision test for a string, bytes or flags field, and for a field with no declared default it emits the text it emitted before: the committed generated/ tree, the bench unit generated/bench/tables/cpp/BenchTableTable.h included, regenerates byte-identically under this compiler (CI's staleness check, and git status clean after make test here).

The sitting anyway, per the law. The table read and write benchmarks over the existing corpus (bench/corpus/BenchTable.schema, corpus_id b51387f36d9b59c4, 2147 wire bytes per record), before at 45888e55 (origin/main) and after at ad7213ee (this branch), the same bench/tables/cpp/table_main.cpp against each tree's generated/bench/tables/cpp/BenchTableTable.h. The two leg binaries are byte-identical (cmp clean, 106408 bytes each), so the sitting is a noise reading of one machine code.

MacBook Air M2, 2026-09-05 12:39, five interleaved rounds (§2.4), --round K each, before then after within a round, one-minute load 2.78 when the sitting started and 3.2 to 3.9 while it ran, with three other builders compiling in sibling clones (no core pinning).

path before, median of 5 (msg/s) after, median of 5 (msg/s) before's own spread after's own spread
write 734103 738857 705082 to 760155 (7.8%) 701664 to 752894 (7.3%)
round_trip 254555 254632 236676 to 255921 (8.1%) 159775 to 255081 (one round under a neighbor's build, the other four within 1.9%)

Unchanged within noise: write 0.6% apart, round_trip 0.03% apart, each side's own spread wider than the gap. No cost measured, none landed.

Pages

docs/SPEC.md §4.2 (the Default production and the three defaults, the attribute vocabulary, the was paragraph), docs/SPEC-TABLES.md §4 (absent reads the declared default), §4.1 (two rows: a table renamed under was, and renamed a second time), §5 (a table's name is identity too), §15 (the compile-time refusals), §18.1 and §18.2 (the rendering and the chain rule), docs/VERSIONING.md promise 3 and the edits table, docs/USAGE.md (defaults, and the table rename), docs/COMPETITION.md (the defaults and rename rows move to 🔶 with their footnotes saying what is built and what is owed).

#366

The row string/bytes/flags defaults and table was (#396) moves to ✅ in the cpp column. Every other column stays ❌ behind the named refusal.

Silences the page left

Gate state

make test whole on this branch, green: the C++ table tests plain and under ASan/UBSan, make tables-wire-fuzz N=20000 on both builds with tblw1.Fleet and tblw2.Fleet registered in the driver, so the six W seeds are live (wire-fuzz: seed 24845619678, 121 seeds over 36 roots, 10 seeds absent (roots the leg has no codec for), 107207 enumerated + 20000 random = 127207 mutants, 0 divergences, and each W golden replayed through both units, 0 divergences), and the oracle keyed by the DECLARED name instead of the wire name goes red on the same run (FAILED after 80727 mutants: LoadMeasure asks for 360 region bytes; the framing commands exactly 200, corpus seed w2_fleet), every wire-fuzz negative control red, tables-was-negative-control as quoted, the conformance leg (wire 63/63, message 11/11, report 53/53, json-read 59/59, json-write 59/59, json-hostile 125/125, cook 6/6, cook-write 92/92, cook-foreign 6/6, block 2/2, block-foreign 2/2, block-dump 2/2, forgery 11/11, cook-forgery 111/111), and go test ./.... MacBook Air, sequential, one-minute load under 3 at the start.

🤖 Generated with Claude Code

gafferongames and others added 3 commits September 5, 2026 12:18
…ion (#396)

The three defaults ride the table wire as a scalar's does: a field holding
its declared default elides, an absent field reads as it, and the default
is a projection, cook-projection and baseline fact. The C++ reference and
the tool carry them; every other target refuses a unit that declares one,
naming the follow-on.

A table declaration takes was. Its node type id is the hash of the old
name, so every stored record still reads, and every id derivation reads
the wire name: the node record, the cooked directory, the announced
vocabulary, the baseline and the build version. The baseline keys a table
by its wire name, records the declared name beside it, and refuses a second
rename aimed at the intermediate spelling.

W1/W2 are the evolution pair, with three pinned instances, three silent
report rows, and tables-was-negative-control, which strips the attribute
and watches unknown count.

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

The versioning table's default-change row reads refuses, the competition
matrix's defaults and rename rows move to partial with their footnotes
saying what is built and what is owed, and the where-they-are-ahead entry
names the table beside the field.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The reference leg compiles W1Table.cpp and W2Table.cpp and includes their
headers, so the generated directories join CONFORMANCE_INCLUDES.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
… W rows join the fuzz

The oracle's storage map was keyed by the DECLARED name's type id, so a
pointer target renamed under `was` commanded no region bytes in the oracle
and wireVerdict refused the seed. It reads st.WireName() now, and the
wire-fuzz driver registers tblw1.Fleet and tblw2.Fleet, so the six W seeds
are live (121 seeds over 36 roots, 10 absent, 0 divergences). With the old
key and the rows registered the fuzz goes red on w2_fleet, LoadMeasure 360
bytes against a framing of 200.

Also from the cold read: SPEC.md no longer says a table declaration takes
no qualification, the held-back-id refusal case in internal/check is named
for what it tests (a valued key a table declaration does not take), and
the added prose in SPEC-TABLES.md, the Makefile, W1.schema, W2.schema and
the valuedefaults refusal carries no em dash.

Co-Authored-By: Claude Fable 5.1 <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