fix(rust): send reward notices, and keep a downed line's credit rule (W21, W4)
All checks were successful
PR Checks / client-build (pull_request) Successful in 21s
PR Checks / frozen-manifest (pull_request) Successful in 48s
PR Checks / server-tests (pull_request) Successful in 7m16s

W21: the permission push named its fields one by one and left out
`notices`, so no kit reward, lend or give ever told its player
anything (D333). The body is now built by `pushBody`, which carries
every field the payload has; a test fails if a payload field is
dropped again.

W4: a downed patrol helicopter, Chinook or Bradley is stored with the
credit rule in force when it arrived, and the feed reads that stamp.
Before, the line re-credited past rows with the server's current rule
while the Rotorbane/Tankbreaker counts kept the old one. Rows stored
before this change keep today's reading.

PLAN_LAST_FIXES.md stage 1.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
This commit is contained in:
2026-10-21 06:16:32 -05:00
parent 5decd5cc05
commit 8aff9b33a6
7 changed files with 92 additions and 15 deletions

View File

@@ -28,6 +28,7 @@ const rewards = require('../eventRewards')
const leases = require('../eventLeases')
const templates = require('../eventTemplates')
const model = require('../model/permissions/permissions.model')
const reconcile = require('../model/permissions/reconcile')
const entitle = rewards.ACTIONS.find((a) => a.id === 'rust.kit.entitle')
const ok = (data) => ({ ok: true, status: 'ok', data })
@@ -163,6 +164,19 @@ test('the sync carries each untold message to the account the reward picked', ()
])
})
test('the push carries the notices, and every other field the payload built (W21)', () => {
const permSync = require('../permSync')
const desired = model.buildDesired('main', authored([grantRow({ notice: 'You earned kit vip', noticeDelivery: 'chat' })]))
const held = reconcile.withHold(desired, new Set())
const body = permSync.pushBody(desired.hash, held.payload, [], [])
assert.deepStrictEqual(body.notices, [
{ key: '7:3:11', steamId: '76561190000000001', message: 'You earned kit vip', delivery: 'chat' },
])
// A field the model adds and the push forgets is how W21 happened.
for (const field of Object.keys(held.payload)) assert.ok(field in body, `the push drops payload.${field}`)
assert.strictEqual(body.setId, desired.hash)
})
test('a notice moves the digest, so a new one is pushed and a told one is dropped', () => {
const none = model.buildDesired('main', authored([grantRow({})]))
const one = model.buildDesired('main', authored([grantRow({ notice: 'hi', noticeDelivery: 'chat' })]))

View File

@@ -216,6 +216,19 @@ test('a Bradley counts toward Tankbreaker, and an arrival counts nothing', async
assert.strictEqual(rec2.touching('rust_player_wipe_stats').length, 0)
})
test('a downed one is stored with the rule that credited it; an arrival is stored as sent (W4)', async () => {
const { apply } = require('../ingest')
const storedRaw = async (frame) => {
const rec = withRecorder()
await apply('main', { id: 1, kind: 'world.event', frame }, { id: 'main', creditRule: 'all' })
const insert = rec.touching('rust_events').find((s) => /INSERT/i.test(s.sql))
return JSON.parse(insert.params.find((p) => typeof p === 'string' && p.startsWith('{')))
}
assert.strictEqual((await storedRaw(downed())).creditRule, 'all')
assert.strictEqual((await storedRaw(world('heli.arrived', { x: 1, z: 1 }))).creditRule, undefined)
})
test('the tally’s crates hacked and drops looted are added up (D356)', async () => {
const rec = withRecorder()
const { apply } = require('../ingest')
@@ -251,6 +264,21 @@ test('the feed reads each world event for its viewer, with the server’s rule a
assert.deepStrictEqual(member.frame.credited, [{ name: 'Bob' }])
})
test('a line keeps the rule it was stored with after the server’s rule changes (W4)', async () => {
const stamped = downed({ creditRule: 'all' })
withRecorder((sql) => {
if (sql.includes('FROM rust_events')) return [{ id: 9, kind: 'world.event', t: NOW, wipeId: 'w1', steamId: null, raw: JSON.stringify(stamped) }]
if (sql.includes('FROM rust_servers')) return [{ id: 'main', name: 'Main', creditRule: 'most', enabled: 1 }]
return []
})
const events = require('../model/events/events.model')
const [member] = await events.recent({ serverId: 'main', presence: true })
assert.strictEqual(member.frame.creditRule, 'all')
assert.strictEqual(member.frame.creditedCount, 2)
assert.deepStrictEqual(member.frame.credited.map((c) => c.name).sort(), ['Alice', 'Bob'])
})
// ── Triggers (D355) ────────────────────────────────────────────────────────
test('one public trigger per type, none of them declaring a Steam id', () => {