fix(rust): hold the reconcile until the world is loaded, and never read a refusal as a revert
Two defects the phase 13a walk found by restarting the rig mid-run: - The watch asked core to reconcile the moment a new boot id appeared, which is before the game has loaded its save — every crate looked gone and was orphaned. It now waits for the plugin's hello to say `worldReady`; an older plugin that never says is taken as ready. - revert() read any 200 as success. On this bridge a refusal is a 200 carrying world.error (`not-ready` while loading), so every row would have been marked reverted with the game still holding every crate. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
This commit is contained in:
@@ -256,6 +256,7 @@ async function revert({ runId, resources, idempotencyKey }) {
|
||||
for (const server of await servers.listForPolling()) {
|
||||
const result = await client.worldRevert(server, { runId: String(runId), key: idempotencyKey })
|
||||
if (!result.ok) errors.push(transportError(server, result, 'revert'))
|
||||
else if (!result.data || result.data.kind !== 'world.ok') errors.push(pluginError(result.data, `${server.name || server.id} refused the revert`))
|
||||
}
|
||||
return errors.length ? { ok: false, error: errors.join('; ') } : { ok: true }
|
||||
}
|
||||
@@ -279,6 +280,16 @@ async function revert({ runId, resources, idempotencyKey }) {
|
||||
continue
|
||||
}
|
||||
|
||||
// **A 200 is not a success on this bridge** — a refusal comes back as one,
|
||||
// carrying `world.error` (`not-ready` while the world is still loading).
|
||||
// Read as success it would mark every row reverted while the game still
|
||||
// held every crate.
|
||||
if (!result.data || result.data.kind !== 'world.ok') {
|
||||
failed.push(...group.map((r) => r.ref))
|
||||
errors.push(pluginError(result.data, `${found.server.name || found.server.id} refused the revert`))
|
||||
continue
|
||||
}
|
||||
|
||||
// `gone` is not reported: a crate a player looted is the point of having
|
||||
// placed it. `refused` IS — the plugin found something there that this run
|
||||
// did not make, and nothing will ever remove it through this path.
|
||||
@@ -593,12 +604,19 @@ const lastSeen = new Map()
|
||||
|
||||
/**
|
||||
* Note a server's identity as the refresh saw it, and ask core to reconcile when
|
||||
* it moved. The first sighting after this module boots is a baseline, not a
|
||||
* it moved — once its world is loaded. The first sighting after this module boots is a baseline, not a
|
||||
* change: core's own boot reconcile already covered it.
|
||||
*/
|
||||
function observeServer(serverId, { bootId, wipeId } = {}) {
|
||||
function observeServer(serverId, { bootId, wipeId, worldReady } = {}) {
|
||||
if (!serverId || (!bootId && !wipeId)) return false
|
||||
|
||||
// **Not until the world is loaded** (§28.6). The plugin connects before the
|
||||
// save loads, so the new boot id arrives while every crate still looks gone;
|
||||
// asked then, reconcile would orphan the lot. The plugin says when it is
|
||||
// ready, and the change is noticed on that hello instead. An older plugin
|
||||
// that never says is taken as ready, as it always was.
|
||||
if (worldReady === false) return false
|
||||
|
||||
const previous = lastSeen.get(serverId)
|
||||
lastSeen.set(serverId, { bootId: bootId || null, wipeId: wipeId || null })
|
||||
if (!previous) return false
|
||||
|
||||
Reference in New Issue
Block a user