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

@@ -145,6 +145,11 @@ test('the classification covers exactly the event kinds the protocol defines, th
'npc.placement.changed',
'plugin.loaded',
'plugin.unloaded',
// Protocol 13, RunicNPC (runicnpc stage 6): a boss's spawn and death are said to
// the whole server and are public; its phases are told only to players near it.
'npc.boss.spawned',
'npc.boss.phase',
'npc.boss.died',
]
assert.deepEqual([...catalogue.ALL_KINDS].sort(), [...PROTOCOL_4].sort())
@@ -154,9 +159,12 @@ test('the classification covers exactly the event kinds the protocol defines, th
}
assert.equal(catalogue.isPublic('config.outcome'), false, 'a server’s log tail must not be public')
for (const kind of ['plugin.loaded', 'plugin.unloaded', 'lease.expired', 'world.expired']) {
for (const kind of ['plugin.loaded', 'plugin.unloaded', 'lease.expired', 'world.expired', 'npc.boss.phase']) {
assert.equal(catalogue.isPublic(kind), false, `${kind} is staff-only`)
}
for (const kind of ['npc.boss.spawned', 'npc.boss.died']) {
assert.equal(catalogue.isPublic(kind), true, `${kind} is said to the whole server, so it is public (D286)`)
}
})
test('every kind that names a player who was on is behind the presence setting', () => {
@@ -169,6 +177,7 @@ test('every kind that names a player who was on is behind the presence setting',
'player.death',
'player.chat',
'player.tally',
'npc.boss.died',
]) {
assert.ok(catalogue.isPresence(kind), `${kind} must be gated as presence`)
assert.ok(!catalogue.kindsFor({}).includes(kind), `${kind} must not reach a default public view`)
@@ -179,5 +188,5 @@ test('every kind that names a player who was on is behind the presence setting',
for (const kind of catalogue.PRESENCE_KINDS) assert.ok(catalogue.PUBLIC_KINDS.includes(kind))
// And what is left names nobody.
assert.deepEqual(catalogue.kindsFor({}).sort(), ['server.initialized', 'server.shutdown', 'server.wipe'])
assert.deepEqual(catalogue.kindsFor({}).sort(), ['npc.boss.spawned', 'server.initialized', 'server.shutdown', 'server.wipe'])
})