2026-09-29 02:04:33 +02:00
|
|
|
"""Generates the shared C0 refusal vectors from the pre-Cut 2 v1 store (written before any
|
|
|
|
|
variant code existed). Every runtime's tests read the committed bytes; nothing regenerates them
|
|
|
|
|
at test time. Run: python generate.py (needs msgpack).
|
|
|
|
|
|
|
|
|
|
unknown-header.msgpack header cultcache.store.v9, otherwise the v1 store
|
|
|
|
|
extra-slot-full-payload.msgpack v1 header, record item:anvil gains slot 4, payload stays full
|
2026-09-29 02:35:57 +02:00
|
|
|
legacy-catalog-extra-slot.msgpack
|
|
|
|
|
v1 header, catalog entry in the older layout (contentHash at slot
|
2026-10-03 02:02:02 +02:00
|
|
|
5), plus the extra-slot record; no C# reader reads the older layout
|
|
|
|
|
legacy-catalog-plain.msgpack v1 header, the older-layout catalog entry, the plain v1 records: refused by
|
|
|
|
|
every C# reader, CultMesh included
|
|
|
|
|
legacy-catalog-trailing.msgpack legacy-catalog-plain plus three bytes after the array
|
2026-09-30 06:54:17 +02:00
|
|
|
current-catalog-bad-canonical.msgpack
|
|
|
|
|
current-catalog-bad-members.msgpack
|
|
|
|
|
current-catalog-bad-compat.msgpack
|
|
|
|
|
v1 header, plain records, and the first catalog entry of the current layout with one slot
|
2026-10-03 02:02:02 +02:00
|
|
|
malformed (canonical schema json an int; members an int; a compatible id an int). Refused
|
|
|
|
|
by every C# reader, CultMesh included
|
2026-09-30 08:17:59 +02:00
|
|
|
current-catalog-bad-slot5-nil.msgpack
|
|
|
|
|
current-catalog-bad-slot5-int.msgpack
|
|
|
|
|
the same, with slot 5 (the compatible ids) nil or an integer: neither is a string, so neither is
|
|
|
|
|
the older layout
|
2026-09-29 02:04:33 +02:00
|
|
|
v1-base.msgpack NOT generated here: written once by the Python runtime at CultLib
|
|
|
|
|
69a21bb (SingleFileMessagePackBackingStore.push of alpha and
|
|
|
|
|
beta, type vectors.item, before any C0 code existed)
|
Variant overrides follow the plain-record outcome; bad writes and shared indexes are refused
F3: an override drifts exactly as the same member would on a plain record. A
slot the type dropped is ignored and reported by the store's schema resolution
(LastSchemaMigrationReports), the one surface a plain record uses; a value that
no longer decodes refuses the load, naming the variant and member. The second
report surface (VariantMigrationReports, the per-variant report map, the
catalog slot-name plumbing) is deleted.
F4: an override for a slot the type does not have, or a value that does not
decode as the member, is refused at write. Drift is for type changes after the
fact, not for bad writes. F5: two overrides of one slot are refused.
F2: a variant that shares an indexed value with another record is refused,
naming both keys and the index (the R6 rule extended to [CultIndex]); plain
record duplicate handling is unchanged.
F7: a flatten now resolves against the same batch, so a base edited in that
batch is in the flattened record; and the directory store refuses to load a
variant page as it refuses to write one.
The C1 vector's big variant now overrides its indexed code, as the unique-index
rule requires.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-29 06:58:51 +02:00
|
|
|
variant-v2.msgpack the wire shape of a variant record, hand-built: header v2, record slot 4 =
|
|
|
|
|
[baseKey, overrides[]], empty payload (override op Set = 0, path steps
|
|
|
|
|
are [slot, elementId]). C1 does not write this store and refuses to load
|
|
|
|
|
it: the variant resolves to its base's name (R6). It exercises readers
|
|
|
|
|
that refuse a variant by version or record.
|
2026-09-29 02:04:33 +02:00
|
|
|
"""
|
|
|
|
|
import pathlib
|
|
|
|
|
|
|
|
|
|
import msgpack
|
|
|
|
|
|
|
|
|
|
here = pathlib.Path(__file__).resolve().parent
|
|
|
|
|
source = here.parent.parent / "GameCult.Caching.Tests" / "Fixtures" / "pre-cut2" / "single-file-v1" / "store.msgpack"
|
|
|
|
|
header, catalog, records = msgpack.unpackb(source.read_bytes(), raw=False)
|
|
|
|
|
assert header == "cultcache.store.v1"
|
|
|
|
|
anvil = records[0]
|
|
|
|
|
assert anvil[0] == "item:anvil"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def write(name, snapshot):
|
|
|
|
|
(here / name).write_bytes(msgpack.packb(snapshot, use_bin_type=True))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
write("unknown-header.msgpack", ["cultcache.store.v9", catalog, records])
|
|
|
|
|
write("extra-slot-full-payload.msgpack",
|
|
|
|
|
[header, catalog, [anvil + [["item:bellows", []]]] + records[1:]])
|
|
|
|
|
variant = ["item:anvil-big", anvil[1], anvil[2], b"", ["item:anvil", [[0, [[1, ""]], "", 99]]]]
|
|
|
|
|
write("variant-v2.msgpack", ["cultcache.store.v2", catalog, records + [variant]])
|
2026-09-30 04:21:23 +02:00
|
|
|
legacy = lambda entry: [entry[0], entry[1], entry[2], entry[4], None, entry[3], []]
|
|
|
|
|
legacy_entry = legacy(catalog[0])
|
2026-09-29 02:35:57 +02:00
|
|
|
write("legacy-catalog-extra-slot.msgpack",
|
|
|
|
|
[header, [legacy_entry], [anvil + [["item:bellows", []]]]])
|
2026-09-30 04:21:23 +02:00
|
|
|
write("legacy-catalog-plain.msgpack", [header, [legacy(entry) for entry in catalog], records])
|
Store readability: one typed refusal, one full reader, one store shape
CultStoreUnreadableException (a NotSupportedException) is the one refusal for a store file this runtime cannot read, thrown
by the single-file reader, the rewriting paths and CultMesh's reads, with the cause as the inner exception. The reader is exactly
one array of three slots (or an empty array): a header alone and an extra top-level slot are refused instead of read as empty or
skipped. CultMesh's older-catalog fallback is the same reader with the older entry reader, so it also requires one array.
Vectors: bad-body, header-only, missing-schema, variant-slot, extra-top-slot in the readability set, and a legacy-catalog store
with and without bytes after it for CultMesh. The persistence contract says what is true.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-09-30 04:06:44 +02:00
|
|
|
(here / "legacy-catalog-trailing.msgpack").write_bytes(
|
2026-09-30 04:21:23 +02:00
|
|
|
msgpack.packb([header, [legacy(entry) for entry in catalog], records], use_bin_type=True) + b"\x01\x02\x03")
|
2026-09-30 06:54:17 +02:00
|
|
|
current = catalog[0]
|
|
|
|
|
for name, entry in (
|
|
|
|
|
("current-catalog-bad-canonical", current[:4] + [5]),
|
|
|
|
|
("current-catalog-bad-members", current[:6] + [5]),
|
|
|
|
|
("current-catalog-bad-compat", current[:5] + [[7]] + current[6:]),
|
2026-09-30 08:17:59 +02:00
|
|
|
("current-catalog-bad-slot5-nil", current[:5] + [None] + current[6:]),
|
|
|
|
|
("current-catalog-bad-slot5-int", current[:5] + [7] + current[6:]),
|
2026-09-30 06:54:17 +02:00
|
|
|
):
|
2026-09-30 07:32:18 +02:00
|
|
|
# One catalog entry and the records that use it: the older-layout reader takes an entry's slot 5 for a string, so a second
|
|
|
|
|
# current-layout entry would make it refuse for that reason, not for the malformed slot under test.
|
|
|
|
|
write(name + ".msgpack", [header, [entry], [record for record in records if record[1] == current[0]]])
|