docs(link): the cliloc table, and why §8.6's recommendation was not implementable #70
Reference in New Issue
Block a user
No description provided.
Delete Branch "docs/cliloc-table"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
What & why
Protocol 3.0 §8.6 — the cliloc dependency, resolved as its own website-only change and landing ahead of §8 so the marketplace ships with real item names. Matching documentation for website #115.
NEW
website/CLILOCS.mdOperator-facing: why the conversion step exists at all, how to convert, how to configure the path, the refresh contract, what gets stored, and how names are applied.
link/v3.md§8.6 — rewritten, because both halves of the recommendation were wrongThe original said:
scripts/buildClilocs.jsreads the client'sCliloc.enu→ committeddb/data/clilocs.json. Neither half survives contact, and both failures generalise.1. The committed artifact predates the Part C corrections (§6.1) and violates both — no committed snapshot of derived content, and nothing EA-derived ever shipped. UO's strings are EA's, exactly as the creature sprites are. Replaced with the §6 pattern: parse on boot from an operator-configured path, hash-gated, output gitignored,
PARSER_VERSIONcounted as drift.2. "reads the UO client's
Cliloc.enu" is not possible. Every current client ships its cliloc files COMPRESSED — all eightCliloc.*files open with a DWORD whose high byte is0x8E, the "Mythic" container. The plain layout is what those files looked like before that change, and parsing one as the other does not fail cleanly: 19,508 "records" with only 1,722 distinct ids, ids into the negatives, one 62 KB "string", and a truncation mid-file.Two facts closed off the alternatives, and the second is the one worth carrying forward:
Ultima.StringListimplements only the plain layout, soVendorSearch.StringListis null andGetItemNamereturnsitem.Name. The work could not be pushed to the plugin — and the in-game Vendor Search gump has exactly the same gap, which retires an open cost in §8.2: the mandatory "never callGetItemNamein the market sweep" costs us nothing we could otherwise have had.⇒ the operator converts once, from their own client, and the site reads the result.
Three traps found by building it, all recorded:
StringList.SaveStringListRE-COMPRESSES on save. It looks exactly like the export path and is not — its output is byte-identical to its compressed input, because its purpose is round-tripping a file back into the client.1005008<TAB>; trimming eats the trailing separator and the line then looks like a header row. 55,994 of 123,490 entries vanished and the import still reported success.Number('')is0, notNaN— a line starting with a separator imports as a bogus cliloc 0.Also updated
website/BACKEND_DESIGN.md—shard_clilocs/shard_cliloc_meta, the three admin routes, and the two "why not" decisions: no staged-approval flow (the atlas escalates facet loss because a half-copied tree and a real map change are indistinguishable; a partial cliloc copy makes the parser fail on a truncated record instead — the ambiguity the atlas must escalate is one this parser detects), and no public route (67k rows would dwarf any page, and the Android client consumes the same resolved JSON).link/INTEGRATION.md— thechar.profilefield note now says explicitly not to expect the shard to resolve clilocs, with the reason, and points at CLILOCS.md.website/CLILOCS.md.How it was tested
Documentation only — no code. Every claim here was written after the corresponding behaviour was observed, not from the plan:
TRUNCATEDbehaviour, theCOMPRESSEDstatus/import responses and the boot log lines are all copied from actual runs in website #115.PROJECT_TREE.mdfiles are deliberately untouched — they are CI-generated by thesync-project-treeworkflow and say so in their header.Checklist
AI-assisted contributions (required)
Claude Code (Opus 5). I have reviewed and understand every change, and take responsibility for it. AI-authored commits are marked with aCo-Authored-Bytrailer.License
Update — shard-added and shard-edited items (second commit)
Review feedback: the table has to be updatable the way the spawn atlas is, because shards edit items and add new ones — those carry cliloc ids no stock client table has.
Reading exactly one converted file meant re-exporting 5 MB every time an operator added one item. That is friction enough that the table goes stale, which is the exact failure Part C was redesigned to avoid.
So it now mirrors
spawnAtlasSource.readSources(): a base (the converted client table) plus every overlay undercustom/, all re-read on every boot and hash-gated as a set. Later sources win, so an overlay both adds ids and overrides stock ones. Adding, editing or removing any overlay counts as drift.custom/is the one convention here that is ours rather than the shard's, deliberately. ServUO has no server-side notion of a custom cliloc — they live in the patched client a shard distributes, and nothing in the tree declares them, so there is nothing to discover. (An operator who does patch their client cliloc needs no overlay: convert the patched file and the edits are in the base.)Scale, measured on the live shard: its script tree references 16,434 cliloc ids and only 37 are absent from stock — tens against a 67k base, which is why this is an overlay and not a second table.
The set brings back the atlas's ambiguity, and gets its answer
A corrupt source fails the parse loudly. But a source that has vanished parses perfectly and imports a table quietly missing everything it contributed — an unmounted volume is indistinguishable from a deliberate deletion. That is precisely what the atlas stages a facet removal for, so it is staged here too:
status: "needsReview", nothing applied,missingSourcesreported by both the import andstatus(), accepted with{"approve": true}.It is a flag rather than the atlas's approve/reject pair because the atlas stores a pending decision so that approving re-parses the tree; here nothing is stored, so re-reading at approval time is automatic. (This replaces the "why there is no staged-approval flow" reasoning in the original description — that argument held for a single file and does not survive multiple sources.)
Import and status also report a per-source breakdown, which is how an operator confirms an overlay took effect —
overrode: 0on a file meant to re-label stock items says it did not.Two bugs this surfaced
Both found by running a shard-style overlay rather than another stock-table fixture:
displayTexttidied punctuation unconditionally, so a custom"Runic Gateway Sigil (v2)"rendered as"(v2". Stripping leftover brackets is right after a placeholder is removed and wrong otherwise — the same condition the%rule already had.CANDIDATE_NAMESomittedclilocs.plain— the exact filename CLILOCS.md and the export tool's README tell operators to write. Pointing at the directory they were told to create failed withNO_FILE.How the update was tested
Full walk against the live MariaDB and a real server boot: base-only import → overlay adding one id and overriding another (breakdown correct:
added: 1, overrode: 1) → unchanged set as a no-op → edited overlay re-importing and withdrawing its override → vanished overlay refused with the table intact →statusreportingmissingSources→approveapplying it → a file-path configuration still finding overlays beside it. TheneedsReviewwarning was also confirmed on a real boot log.Through the running server, all three resolve correctly:
646 server tests pass (16 new in
clilocSource.test.jsdriving real temp directories — which file wins, what a listing yields, what happens when one vanishes are exactly the behaviours a mock would define away; 3 new inclilocParse.test.js). Artifacts regenerated.Protocol 3.0 §8.6 resolved as its own website-only change, landing ahead of §8 so the marketplace ships with real item names. Matching documentation for website #TBD. NEW website/CLILOCS.md — operator-facing: why the conversion step exists, how to convert, how to configure the path, the refresh contract, what gets stored and how names are applied. link/v3.md §8.6 rewritten. Two things in the original recommendation turned out to be wrong, and both are recorded because the reasoning generalises: 1. The committed db/data/clilocs.json artifact predates the Part C corrections (§6.1) and violates both — no committed snapshot of derived content, and nothing EA-derived ever shipped. UO's strings are EA's, exactly as the creature sprites are. 2. "scripts/buildClilocs.js reads the UO client's Cliloc.enu" is not possible. EVERY current client ships its cliloc files compressed (first DWORD's high byte 0x8E, the Mythic container); the plain layout is what those files looked like before that change, and parsing one as the other does not fail cleanly — it yields ~19k records with negative ids, 1,722 distinct keys out of 19,508, one 62 KB "string", and a truncation somewhere in the middle. ServUO's own Ultima.StringList cannot read it either, so VendorSearch.GetItemName is already inert on such a shard and the work could not be pushed to the plugin. That second point also retires an open question in §8.2: the warning never to call GetItemName in the market sweep costs us nothing we could otherwise have had, because the in-game Vendor Search gump has the same gap. Three traps found by building it are recorded: StringList.SaveStringList RE-COMPRESSES on save (its output is byte-identical to its compressed input, because its purpose is round-tripping a file back into the client); trimming a text line before splitting silently drops the ~half of a table that is empty strings; and Number('') is 0, not NaN. Also updated: - Progress and §9 sequencing tables: order 5 split into 5a (this, website only) and 5b (the four-repo wire change). - website/BACKEND_DESIGN.md — shard_clilocs / shard_cliloc_meta, the three admin routes, and why there is no staged-approval flow and no public route. - link/INTEGRATION.md — the char.profile field note now says explicitly not to expect the shard to resolve clilocs, and points at CLILOCS.md. - §10 documentation obligations list CLILOCS.md. Documentation only. Every claim was written after the corresponding behaviour was observed running: the compressed-format finding and the parse failures come from the real client files on this machine, and the counts (123,490 parsed → 67,496 stored) and timings from importing them into the live MariaDB. PROJECT_TREE.md files are deliberately untouched — they are CI-generated by the sync-project-tree workflow and say so in their header. Co-Authored-By: Claude <noreply@anthropic.com>