fix(rust): the last fixes' small ones (W3 W6 W7/W11 W8 W10 W13-W18)
Stage 2 of PLAN_LAST_FIXES (docs#366). - W7/W11: an "is it loaded" refusal is confirmed live before it is given. New liveStatus.js asks the sidecar once (boot.refreshOne, as a poll tick would), at most once per server per 5 s and waiting at most 3 s, then reads the stored status again. Used by Economics (step and staff page), Backpacks, Dangerous Treasures, Guarded Crate, RaidableBases, ZoneDomes, TruePVE and RunicNPC. - W3: a world call answered `already-out` (the bridge half comes in stage 3) succeeds as called 0, saying a Travelling Vendor is already out. - W6: "A locked crate dropped by a Chinook is being hacked." - W8: a RaidableBases profile's audit row names RaidableBases, not "@data". - W10: the backpack sizes picker lists each size once, naming its servers. - W13: a RaidableBases name both servers describe differently shows each server's own description. - W14: each RaidableBases note after the first starts its own sentence. - W15: a staff deposit refused at Economics' limit answers 409. - W16: a payment's failedNote explains only the reasons that happened. - W17: the sync state counts player grants that did not land (waitingToLand), and the Permissions page says "in sync · 1 waits for their player to join" instead of plain "in sync". - W18: Guarded Crate's kept note drops the "30 minutes" it could not know. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
This commit is contained in:
@@ -47,6 +47,7 @@ const linksDb = require('./model/links/links.db')
|
||||
const emit = require('./engagement/emit')
|
||||
const economics = require('./model/economics/economics.model')
|
||||
const backpacks = require('./model/backpacks/backpacks.model')
|
||||
const live = require('./liveStatus')
|
||||
const voice = require('./model/permissions/voice')
|
||||
const { serverFor, transportError, pluginError, perServer, bounded } = require('./eventLeases')
|
||||
const { BUDGET_MS } = require('./eventWorld')
|
||||
@@ -741,6 +742,22 @@ function coinsNotice(params, amount) {
|
||||
return { ok: true, text, delivery }
|
||||
}
|
||||
|
||||
/** What each reason in a payment's `failed` list means, in the plugin's order. */
|
||||
const FAILED_REASONS = Object.freeze({
|
||||
'at-limit': "the balance is at Economics' limit",
|
||||
refused: 'Economics did not take the deposit',
|
||||
unmeasured: 'Economics did not report the deposit',
|
||||
})
|
||||
|
||||
/** A payment's `failedNote`: only the reasons that happened, each explained (W16). */
|
||||
function failedNote(failed) {
|
||||
const seen = new Set(failed.map((row) => row.reason))
|
||||
return [...Object.keys(FAILED_REASONS), ...[...seen].filter((r) => !FAILED_REASONS[r])]
|
||||
.filter((reason) => seen.has(reason))
|
||||
.map((reason) => (FAILED_REASONS[reason] ? `${reason}: ${FAILED_REASONS[reason]}` : `${reason}: Economics' own reason`))
|
||||
.join('; ')
|
||||
}
|
||||
|
||||
/** The key the plugin remembers a payment by: core's, or a digest of it past the plugin's bound. */
|
||||
function paymentKey(idempotencyKey, runId, stepId) {
|
||||
const key = String(idempotencyKey || `coins:${runId}:${stepId}`)
|
||||
@@ -806,7 +823,8 @@ const coinsPay = {
|
||||
|
||||
// D199: refused with the reason where Economics is absent, in the dry run
|
||||
// too — the author learns it on the form, not at the run's reward.
|
||||
const econ = await economics.forServer(server.id)
|
||||
// Confirmed live before it refuses: the stored status lags a restart (W7).
|
||||
const econ = await live.confirmed(server, () => economics.forServer(server.id), (e) => !e.loaded)
|
||||
if (!econ.loaded) return { ok: false, retry: false, error: `${economics.MISSING} (${name})` }
|
||||
|
||||
if ((mode === 'top' || mode === 'random') && count.value > MAX_RECIPIENTS) {
|
||||
@@ -906,7 +924,7 @@ const coinsPay = {
|
||||
paid: paid.length,
|
||||
total,
|
||||
received: paid.slice(0, 50),
|
||||
...(failed.length ? { failed: failed.slice(0, 50), failedNote: 'at-limit: the balance is at Economics\' limit; refused or unmeasured: Economics did not take or report the deposit' } : {}),
|
||||
...(failed.length ? { failed: failed.slice(0, 50), failedNote: failedNote(failed) } : {}),
|
||||
...(notice.text && paid.length ? { told: `${paid.length} will be told in game, by ${notice.delivery}` } : {}),
|
||||
...(data.repeat ? { repeat: true, repeatNote: 'answered from the first attempt; nobody was paid twice' } : {}),
|
||||
...missedDetail,
|
||||
@@ -995,7 +1013,8 @@ const backpackSize = {
|
||||
|
||||
// D199: refused with the reason where Backpacks is absent, in the dry run
|
||||
// too, and a size the server does not offer is refused by name.
|
||||
const report = await backpacks.forServer(server.id)
|
||||
// Confirmed live before it refuses: the stored status lags a restart (W7).
|
||||
const report = await live.confirmed(server, () => backpacks.forServer(server.id), (r) => !r.loaded)
|
||||
if (!report.loaded) return { ok: false, retry: false, error: `${backpacks.MISSING} (${name})` }
|
||||
const offered = backpacks.leasable(report)
|
||||
if (!offered.includes(size)) {
|
||||
@@ -1356,17 +1375,23 @@ const OPTION_SOURCES = [
|
||||
]),
|
||||
{
|
||||
// From each server's last hello: the sizes its Backpacks offers above the
|
||||
// smallest, grouped by server (D337). The step checks the pair it is given.
|
||||
// smallest (D337). Each size once, naming every server that offers it, as
|
||||
// the other pickers merge a value (W10). The step checks the pair it is given.
|
||||
id: 'rust.options.backpacksizes',
|
||||
label: 'Backpack sizes',
|
||||
description: "Each server's Backpacks sizes above its smallest — its backpacks.size.N permissions.",
|
||||
async resolve() {
|
||||
const [list, reports] = await Promise.all([servers.listForPolling(), backpacks.byServer()])
|
||||
const rows = []
|
||||
const bySize = new Map()
|
||||
for (const s of list) {
|
||||
for (const n of backpacks.leasable(reports.get(s.id))) rows.push({ value: n, label: `${n} slots`, group: s.name || s.id })
|
||||
for (const n of backpacks.leasable(reports.get(s.id))) {
|
||||
if (!bySize.has(n)) bySize.set(n, [])
|
||||
bySize.get(n).push(s.name || s.id)
|
||||
}
|
||||
}
|
||||
return rows
|
||||
return [...bySize.keys()]
|
||||
.sort((x, y) => x - y)
|
||||
.map((n) => ({ value: n, label: `${n} slots — ${bySize.get(n).join(', ')}` }))
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -1398,6 +1423,7 @@ module.exports = {
|
||||
usersFor,
|
||||
readTally,
|
||||
entitlementRef,
|
||||
failedNote,
|
||||
pickRecipients,
|
||||
checkCount,
|
||||
splitKit,
|
||||
|
||||
Reference in New Issue
Block a user