feat(rust): bosses and passive NPCs on the profile form, and public boss triggers (runicnpc stage 6)
All checks were successful
PR Checks / client-build (pull_request) Successful in 20s
PR Checks / server-tests (pull_request) Successful in 24s
PR Checks / frozen-manifest (pull_request) Successful in 46s

The profile form's boss box (D273-D277): the bar's distance, where its
lines are said, the spawn and death lines, and phases at health
percentages, each with optional damage, aim, speed, ranges, a kit swap,
adds of another profile and a line. The passive role (D278, D279,
D288): press E answers with a chat line or a window, it moves as its
profile says (now including `stand`), and it starts unhurtable.

The site checks both as RunicNPC does, in its words; a phase's kit is
checked on each server like the profile's own, and its adds must name a
site profile that is not a boss.

Public triggers rust.boss.spawned, rust.boss.phase and rust.boss.killed
(D277) from the bridge's new frames. In the feed, a boss appearing is
public; its death names the killer, so it sits behind the presence
setting; its phases are staff's, as the game tells them only to players
near it (D286). engagement-triggers.json regenerated.

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-05 10:54:28 -05:00
parent 620ea63b54
commit f2ca314d10
11 changed files with 855 additions and 15 deletions

View File

@@ -40,6 +40,10 @@ export const FEED_KINDS = Object.freeze([
'server.wipe',
'server.initialized',
'server.shutdown',
// RunicNPC bosses (runicnpc stage 6): the game says both to the whole server.
// The death names its killer, so the server shows it only where presence is on.
'npc.boss.spawned',
'npc.boss.died',
])
/** The filters the feed offers, and the kinds each one asks the API for. */
@@ -53,6 +57,7 @@ export const FILTERS = Object.freeze([
kinds: ['player.connected', 'player.disconnected', 'player.respawned'],
},
{ id: 'server', label: 'Server', kinds: ['server.wipe', 'server.initialized', 'server.shutdown'] },
{ id: 'bosses', label: 'Bosses', kinds: ['npc.boss.spawned', 'npc.boss.died'] },
])
export function kindsFor(filterId) {
@@ -128,6 +133,17 @@ export function describe(row) {
case 'server.shutdown':
return { tone: 'server', actor: null, verb: 'The server went down', subject: null, detail: '' }
// A boss's name is the profile's, as typed (`name`), never a prefab's.
case 'npc.boss.spawned':
return { tone: 'server', actor: name, verb: 'has appeared', subject: null, detail: 'a boss' }
case 'npc.boss.died': {
const fought = Number(frame.damagers) > 1 ? `${frame.damagers} players fought it` : ''
return frame.killerName
? { tone: 'kill', actor: frame.killerName, verb: 'killed', subject: name, detail: fought }
: { tone: 'death', actor: name, verb: 'was killed', subject: null, detail: fought }
}
default:
return { tone: 'other', actor: name, verb: String((row && row.kind) || 'unknown'), subject: null, detail: '' }
}