feat(rust): the world verbs, their budgets and the reconcile watch (phase 13a, protocol 9)
- registerEventActions: rust.zone.open and rust.prefab.place, both reversible 'ledger' with revert() and reconcile(), budgetMs 15000 above the client's 12 s. A location is a monument (kind + instance, carrying its server) or raw coordinates, exactly one (D87, D93); bounds mirrored from the plugin so a bad step is refused on the form (D95); zone minutes required and held by the game (D96). - registerEventBudgets: rust.prefabs, rust.npcs and rust.zone.minutes, each beside the verb that spends it (D79, D89). - Option sources rust.options.monuments (live, searchable) and rust.options.prefabs (mirrored, answers with every server off), registered in the one batch core accepts alongside the lease sources. - Refs are <serverId>:<id>, since revert and reconcile get no params. The undo sends no idempotency key; a lost answer is reverted by key on every server. reconcile asks the plugin, and a server that cannot be asked keeps its rows. - The refresh's bootId/wipeId watch calls ctx.events.reconcile() on a restart or a wipe, never on a first sighting or a reconnect (§11.1). - The permission mirror keeps the plugin's new notLanded grants out of what it records as pushed, and the admin page says so (D85). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
This commit is contained in:
@@ -142,10 +142,41 @@ test('nothing is registered that has nothing behind it yet', () => {
|
||||
// NOT register (D62) moved into the assertions below. Phase 12 deleted the
|
||||
// leases and option sources, and kept budgets here on purpose (D79): a lease
|
||||
// spends none, and a dimension nothing spends is a dial that does nothing.
|
||||
// Phase 13a deleted the budgets and actions lines, and registered each budget
|
||||
// beside the verb that spends it (below). The announce leg is 13b's.
|
||||
assert.deepStrictEqual(api.record.legs, [])
|
||||
assert.strictEqual(api.record.hooks.post, undefined)
|
||||
assert.strictEqual(api.record.eventBudgets, null)
|
||||
assert.strictEqual(api.record.eventActions, null)
|
||||
})
|
||||
|
||||
test('the world verbs are registered, and every budget has a verb that spends it (phase 13a)', () => {
|
||||
const { api } = register()
|
||||
|
||||
const actions = api.record.eventActions
|
||||
const budgets = api.record.eventBudgets
|
||||
assert.deepStrictEqual(actions.map((a) => a.id).sort(), ['rust.prefab.place', 'rust.zone.open'])
|
||||
assert.deepStrictEqual(budgets.map((b) => b.id).sort(), ['rust.npcs', 'rust.prefabs', 'rust.zone.minutes'])
|
||||
|
||||
// D79/D89: no dimension without a verb that spends it. A crate step and an
|
||||
// NPC step are priced on different dials, and a zone on its minutes.
|
||||
const spent = new Set()
|
||||
const place = actions.find((a) => a.id === 'rust.prefab.place')
|
||||
for (const cost of [
|
||||
place.cost({ prefab: 'crate.elite', count: 3 }),
|
||||
place.cost({ prefab: 'npc.scientist', count: 2 }),
|
||||
actions.find((a) => a.id === 'rust.zone.open').cost({ minutes: 90 }),
|
||||
]) {
|
||||
for (const id of Object.keys(cost)) spent.add(id)
|
||||
}
|
||||
assert.deepStrictEqual([...spent].sort(), budgets.map((b) => b.id).sort())
|
||||
|
||||
for (const a of actions) {
|
||||
assert.strictEqual(a.reversible, 'ledger')
|
||||
assert.strictEqual(typeof a.revert, 'function')
|
||||
assert.strictEqual(typeof a.reconcile, 'function')
|
||||
// Every param source is one this module registers.
|
||||
const sources = new Set(api.record.eventOptionSources.map((s) => s.id))
|
||||
for (const p of a.params) if (p.source) assert.ok(sources.has(p.source), `${a.id}.${p.name} names ${p.source}`)
|
||||
}
|
||||
})
|
||||
|
||||
test('the leases and their option sources are registered, every source a lease reads (phase 12)', () => {
|
||||
@@ -158,8 +189,10 @@ test('the leases and their option sources are registered, every source a lease r
|
||||
['rust.decay.scale', 'rust.group.permission', 'rust.population', 'rust.spawn.scalar'],
|
||||
)
|
||||
|
||||
// D78: exactly the sources the leases' targets name — none without a reader.
|
||||
// D78: exactly the sources the leases' targets name, plus those the world
|
||||
// verbs' params name (phase 13a) — none without a reader.
|
||||
const read = new Set(leases.map((l) => l.target.source))
|
||||
for (const a of api.record.eventActions) for (const p of a.params) if (p.source) read.add(p.source)
|
||||
assert.deepStrictEqual([...read].sort(), sources.map((s) => s.id).sort())
|
||||
|
||||
for (const l of leases) {
|
||||
|
||||
Reference in New Issue
Block a user