From 42029734ad91d77f2e454725ec38ed9b63fbc292 Mon Sep 17 00:00:00 2001 From: wtclaude Date: Thu, 24 Sep 2026 07:47:01 -0500 Subject: [PATCH] fix(rust): lowercase the reward option-source ids, and test the grammar The first boot against real core refused the whole module at register: `rust.options.runZones` fails core's EVENT_ID grammar, which is lowercase dotted segments only. The fake api validates none of it, so 310 green tests said nothing. The four fixed-choice sources and the chat-server source are renamed, and entry.test now holds every action, budget, lease and option-source id against a copy of the grammar. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY --- server/eventRewards.js | 20 ++++++++++---------- server/test/entry.test.js | 18 ++++++++++++++++++ 2 files changed, 28 insertions(+), 10 deletions(-) diff --git a/server/eventRewards.js b/server/eventRewards.js index 8c0beb2..938c366 100644 --- a/server/eventRewards.js +++ b/server/eventRewards.js @@ -293,11 +293,11 @@ const participationOpen = { params: [ { name: 'server', type: 'string', required: true, example: 'main', source: 'rust.options.servers', description: 'Which server counts.' }, - { name: 'zone', type: 'string', required: false, example: 'Airfield brawl', source: 'rust.options.runZones', + { name: 'zone', type: 'string', required: false, example: 'Airfield brawl', source: 'rust.options.runzones', description: 'The name an earlier "Open a zone" step of this run gave its zone. Left blank, the whole server counts (D100).' }, - { name: 'score', type: 'string', required: true, example: 'both', source: 'rust.options.scoreModes', + { name: 'score', type: 'string', required: true, example: 'both', source: 'rust.options.scoremodes', description: 'What earns a place: seconds present, kills, or both.' }, - { name: 'killsOf', type: 'string', required: false, example: 'npcs', source: 'rust.options.killsOf', + { name: 'killsOf', type: 'string', required: false, example: 'npcs', source: 'rust.options.killsof', description: 'Whose deaths count as a kill: players, NPCs (animals included), or both. The last hit gets it. Needed unless the score is seconds.' }, { name: 'killWeight', type: 'float', required: false, example: DEFAULT_KILL_WEIGHT, description: `For a score of both: how many minutes one kill is worth. Left blank, ${DEFAULT_KILL_WEIGHT}.` }, @@ -477,7 +477,7 @@ const kitEntitle = { params: [ { name: 'kit', type: 'string', required: true, example: 'main/vip-starter', source: 'rust.options.kits', description: 'The kit, as server/kit. The reward reaches only that server (D102).' }, - { name: 'recipients', type: 'string', required: true, example: 'top', source: 'rust.options.recipientModes', + { name: 'recipients', type: 'string', required: true, example: 'top', source: 'rust.options.recipientmodes', description: 'Who gets it: everyone who scored, the top N, a score of at least X, N drawn at random, or the top X per cent.' }, { name: 'count', type: 'float', required: false, example: 3, description: 'N for top and random, X for a minimum score, the percentage for top per cent. Not used for everyone.' }, @@ -641,7 +641,7 @@ const announce = { version: 1, budgetMs: BUDGET_MS, params: [ - { name: 'server', type: 'string', required: true, example: 'main', source: 'rust.options.chatServers', + { name: 'server', type: 'string', required: true, example: 'main', source: 'rust.options.chatservers', description: 'Which server, or * for every server (D105).' }, { name: 'message', type: 'string', required: true, example: 'The airfield brawl starts in five minutes!', description: `The line, up to ${MAX_CHAT} characters.` }, @@ -805,18 +805,18 @@ const OPTION_SOURCES = [ // Free text: the zones a run will open do not exist when it is authored, and // the name is checked when the step runs (D100). Declared so the field is // documented rather than a bare box, and answers nothing. - fixed('rust.options.runZones', 'Zones this run opens', 'The name an earlier "Open a zone" step of the same run gave its zone. Type it; it is checked when the step runs.', []), - fixed('rust.options.scoreModes', 'Score', 'What earns a place in a tally.', [ + fixed('rust.options.runzones', 'Zones this run opens', 'The name an earlier "Open a zone" step of the same run gave its zone. Type it; it is checked when the step runs.', []), + fixed('rust.options.scoremodes', 'Score', 'What earns a place in a tally.', [ { value: 'seconds', label: 'Seconds present' }, { value: 'kills', label: 'Kills' }, { value: 'both', label: 'Both — minutes plus a weight per kill' }, ]), - fixed('rust.options.killsOf', 'Kills of', 'Whose deaths count as a kill.', [ + fixed('rust.options.killsof', 'Kills of', 'Whose deaths count as a kill.', [ { value: 'players', label: 'Players' }, { value: 'npcs', label: 'NPCs, animals included' }, { value: 'both', label: 'Players and NPCs' }, ]), - fixed('rust.options.recipientModes', 'Recipients', 'Who a reward goes to (D101).', [ + fixed('rust.options.recipientmodes', 'Recipients', 'Who a reward goes to (D101).', [ { value: 'everyone', label: 'Everyone who scored' }, { value: 'top', label: 'The top N (ties in)' }, { value: 'minScore', label: 'A score of at least X' }, @@ -824,7 +824,7 @@ const OPTION_SOURCES = [ { value: 'topPercent', label: 'The top X per cent (ties in)' }, ]), { - id: 'rust.options.chatServers', + id: 'rust.options.chatservers', label: 'Chat servers', description: 'Every enabled server, or * for all of them.', async resolve() { diff --git a/server/test/entry.test.js b/server/test/entry.test.js index 934deb0..0653a0e 100644 --- a/server/test/entry.test.js +++ b/server/test/entry.test.js @@ -203,6 +203,24 @@ test('the event verbs are registered, and every budget has a verb that spends it } }) +test('every event id is one the core grammar accepts (phase 13b)', () => { + const { api } = register() + + // Core's EVENT_ID (modules/registries.js), copied rather than imported: this + // module cannot reach core's tree. The fake api validates none of it, and + // phase 13b's first boot against real core refused the WHOLE module over one + // camelCase source id (`rust.options.runZones`). Lowercase dotted segments only. + const EVENT_ID = /^[a-z][a-z0-9_]*(\.[a-z][a-z0-9_]*)+$/ + const ids = [ + ...api.record.eventActions.map((a) => a.id), + ...api.record.eventBudgets.map((b) => b.id), + ...api.record.eventOptionSources.map((s) => s.id), + ...api.record.eventLeases.map((l) => l.id), + ] + for (const a of api.record.eventActions) for (const p of a.params) if (p.source) ids.push(p.source) + for (const id of ids) assert.match(id, EVENT_ID, id) +}) + test('the leases and their option sources are registered, every source a lease reads (phase 12)', () => { const { api } = register()