docs(modules): the presence fix - nothing names who is online by default

modules/rust/PLAN.md gains §23: the org lead's rule (2026-09-22) that
nothing names who is online by default, the five decisions it needed
(D42-D46), the fourth surface the rule reached (the leaderboard's
lastSeen), the viewer re-read from the users row, and a defect four
phases old that the walk found - every empty state in module-rust
rendered as a blank box, because core's EmptyState renders children and
the Integration Kit's template teaches title/message.

rust-link/PROTOCOL.md reclassifies six §8.4 kinds from public to
presence - public with an operator-chosen audience - with no wire change.

android/PLAN.md records the app's half as an amendment to M14.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
This commit is contained in:
2026-09-23 00:32:17 -05:00
parent eb4a8bcafc
commit 3b7b9cca4f
3 changed files with 134 additions and 7 deletions

View File

@@ -445,12 +445,12 @@ Every kind protocol 2 defines, and the hook behind it. **`class` is not a field
| `kind` | Hook | `class` | Carries |
|---|---|---|---|
| `player.connected` | `OnPlayerConnected` | public | steamId, name |
| `player.disconnected` | `OnPlayerDisconnected` | public | steamId, name, reason, sessionSec |
| `player.respawned` | `OnPlayerRespawned` | public | steamId |
| `player.death` | `OnPlayerDeath` | public | victim, attacker, attackerType, weapon, distance, grid |
| `player.chat` | `OnPlayerChat` | public | steamId, name, channel, message |
| `player.tally` | *aggregate* — see §8.6 | public | steamId, gathered{}, npcKills, structures |
| `player.connected` | `OnPlayerConnected` | **presence** | steamId, name |
| `player.disconnected` | `OnPlayerDisconnected` | **presence** | steamId, name, reason, sessionSec |
| `player.respawned` | `OnPlayerRespawned` | **presence** | steamId |
| `player.death` | `OnPlayerDeath` | **presence** | victim, attacker, attackerType, weapon, distance, grid |
| `player.chat` | `OnPlayerChat` | **presence** | steamId, name, channel, message |
| `player.tally` | *aggregate* — see §8.6 | **presence** | steamId, gathered{}, npcKills, structures |
| `entity.destroyed` | `OnEntityDeath` on owned building blocks | **staff** | ownerId, prefab, grid, attacker |
| `player.reported` | `OnPlayerReported` | **staff** | reporter, target, subject, message, type |
| `player.banned` / `player.unbanned` | `OnUserBanned` / `OnUserUnbanned` | **staff** | id, name, **ip**, reason |
@@ -486,6 +486,14 @@ So: the table in §8.4 is the specification, `module-rust` holds the allowlist,
its allowlist against this document, so adding a kind here without classifying it there fails a
build rather than shipping an IP address to a public page.
**`presence` is `public` with an audience an operator chooses** (added 2026-09-22, [`PLAN.md`](../modules/rust/PLAN.md) §23).
The six kinds marked so each say that a *named* player was on the server at a given moment, and the
org lead's rule is that nothing names who is online by default: `module-rust` serves them only to
viewers inside an operator-chosen audience — staff unless widened, fleet-wide with a per-server
override. Below it the public feed carries only what names nobody (a wipe, a start, a shutdown).
Nothing on the wire changed: the class is still the module's to enforce, which is why the rule could
be added without a protocol bump.
`player.login.attempt`, `player.approved` and `player.banned` carry **IP addresses**, and
`player.reported` carries the text of one player's complaint about another. They are stored because
an operator chasing ban evasion needs them and because the sidecar persists what it is told; they