fix(rust): a zone resource carries its deadline as until (W1)
Stage 4 of docs/modules/rust/PLAN_LAST_FIXES.md, the module's half of W1. A placed zone now reports `until`, reckoned from the answer as now plus holdMs, so it lands a round trip after the game's own deadline and never before it. Core's reconcile then files a zone gone past that deadline as expired, where a reconnect used to log resource.orphaned five seconds before the game's world.expired arrived. Crates and NPCs send no holdMs, so they carry no until. No core pin change: `until` has been in the resource contract all along, and a core without the reconcile half only shows it on the console. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
This commit is contained in:
@@ -324,11 +324,19 @@ async function place(server, send, body, what) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const placed = Array.isArray(data.placed) ? data.placed : []
|
const placed = Array.isArray(data.placed) ? data.placed : []
|
||||||
|
// A zone's deadline, as core's `until` (W1, PLAN_LAST_FIXES). The game erases
|
||||||
|
// the zone `holdMs` after it placed it; reckoned from the answer, on this
|
||||||
|
// clock, it lands a round trip after the game's, never before. With it core's
|
||||||
|
// reconcile files a zone gone past its deadline as expired instead of
|
||||||
|
// orphaned, which a reconnect used to show five seconds before the game's own
|
||||||
|
// `world.expired` arrived. Crates and NPCs have no deadline and send none.
|
||||||
|
const until = Number(body.holdMs) > 0 ? new Date(Date.now() + Number(body.holdMs)).toISOString() : null
|
||||||
return {
|
return {
|
||||||
ok: true,
|
ok: true,
|
||||||
resources: placed.map((row) => ({
|
resources: placed.map((row) => ({
|
||||||
kind: OWNED_KIND,
|
kind: OWNED_KIND,
|
||||||
ref: refOf(server.id, row.id),
|
ref: refOf(server.id, row.id),
|
||||||
|
...(until ? { until } : {}),
|
||||||
payload: {
|
payload: {
|
||||||
serverId: server.id,
|
serverId: server.id,
|
||||||
what: row.kind,
|
what: row.kind,
|
||||||
|
|||||||
@@ -178,10 +178,13 @@ test('a zone crosses with its key, its duration, and the monument without the se
|
|||||||
server: 'main',
|
server: 'main',
|
||||||
body: { runId: '7', key: 'k1', monument: 'airfield_1', offsetX: 0, offsetZ: -20, radius: 40, holdMs: 5400000, name: 'Brawl' },
|
body: { runId: '7', key: 'k1', monument: 'airfield_1', offsetX: 0, offsetZ: -20, radius: 40, holdMs: 5400000, name: 'Brawl' },
|
||||||
})
|
})
|
||||||
// The ref carries the server: revert and reconcile are handed no params.
|
// The ref carries the server: revert and reconcile are handed no params. The
|
||||||
assert.deepStrictEqual(result.resources, [
|
// zone's deadline rides as `until` (W1); zones.test.js checks its value.
|
||||||
{ kind: 'world', ref: 'main:rg-7-1-1', payload: { serverId: 'main', what: 'zone', name: 'Z' } },
|
const [resource] = result.resources
|
||||||
])
|
assert.strictEqual(typeof resource.until, 'string')
|
||||||
|
const { until, ...rest } = resource
|
||||||
|
assert.deepStrictEqual(rest, { kind: 'world', ref: 'main:rg-7-1-1', payload: { serverId: 'main', what: 'zone', name: 'Z' } })
|
||||||
|
assert.strictEqual(result.resources.length, 1)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('one resource per thing placed, and a repeated key is said to be one', async (t) => {
|
test('one resource per thing placed, and a repeated key is said to be one', async (t) => {
|
||||||
|
|||||||
@@ -232,6 +232,29 @@ test('a zone crosses with its flags, settings, messages in the chat voice, and i
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('a zone resource carries its deadline as until, a crate none (W1)', async (t) => {
|
||||||
|
// Core's reconcile reads `until`: a zone gone past it is expired, not
|
||||||
|
// orphaned. Reckoned from the answer, so it never falls before the game's own.
|
||||||
|
stubZones(t)
|
||||||
|
stubWorld(t)
|
||||||
|
const before = Date.now()
|
||||||
|
const result = await action('rust.zone.open').perform({
|
||||||
|
runId: 7, idempotencyKey: 'k', params: { monument: 'main/a', radius: 40, minutes: 30 },
|
||||||
|
})
|
||||||
|
assert.strictEqual(result.ok, true, result.error)
|
||||||
|
const until = Date.parse(result.resources[0].until)
|
||||||
|
assert.ok(until >= before + 1800000 && until <= Date.now() + 1800000, result.resources[0].until)
|
||||||
|
|
||||||
|
const saved = client.worldPlace
|
||||||
|
client.worldPlace = async () => ({ ok: true, status: 'ok', data: { kind: 'world.ok', placed: [{ id: 'rg-7-1-2', kind: 'crate' }] } })
|
||||||
|
t.after(() => { client.worldPlace = saved })
|
||||||
|
const crate = await action('rust.crate.place').perform({
|
||||||
|
runId: 7, idempotencyKey: 'k2', params: { monument: 'main/a', prefab: 'crate.elite', count: 1 },
|
||||||
|
})
|
||||||
|
assert.strictEqual(crate.ok, true, crate.error)
|
||||||
|
assert.strictEqual('until' in crate.resources[0], false)
|
||||||
|
})
|
||||||
|
|
||||||
test('a dome with its stack left blank stacks 3 spheres (D326)', async (t) => {
|
test('a dome with its stack left blank stacks 3 spheres (D326)', async (t) => {
|
||||||
stubZones(t, { servers: [server('main', { domes: PATCHED })] })
|
stubZones(t, { servers: [server('main', { domes: PATCHED })] })
|
||||||
const calls = stubWorld(t)
|
const calls = stubWorld(t)
|
||||||
|
|||||||
Reference in New Issue
Block a user