diff --git a/src/config/sidebar.mjs b/src/config/sidebar.mjs index e91818e..b7baadd 100644 --- a/src/config/sidebar.mjs +++ b/src/config/sidebar.mjs @@ -36,6 +36,7 @@ export const docsSidebar = [ { label: 'Users and roles', slug: 'docs/administration/users-and-roles' }, { label: 'Authentication', slug: 'docs/administration/authentication' }, { label: 'Teams', slug: 'docs/administration/teams' }, + { label: 'Scheduled events', slug: 'docs/administration/events' }, { label: 'Moderation', slug: 'docs/administration/moderation' }, { label: 'Notifications and email', slug: 'docs/administration/notifications-and-email' }, { label: 'Engagement rules', slug: 'docs/administration/engagement-rules' }, @@ -66,6 +67,7 @@ export const docsSidebar = [ { label: 'The bridge', slug: 'docs/architecture/the-bridge' }, { label: 'Authentication architecture', slug: 'docs/architecture/authentication-architecture' }, { label: 'Teams architecture', slug: 'docs/architecture/teams-architecture' }, + { label: 'Events architecture', slug: 'docs/architecture/events-architecture' }, { label: 'Protocol versions', slug: 'docs/architecture/protocol-versions' }, ], }, @@ -77,7 +79,7 @@ export const docsSidebar = [ { label: 'sidecar.toml', slug: 'docs/reference/sidecar-toml' }, { label: 'Bridge.cfg', slug: 'docs/reference/bridge-cfg' }, { label: 'HTTP API', slug: 'docs/reference/http-api' }, - { label: 'Event catalog', slug: 'docs/reference/event-catalog' }, + { label: 'Shard event catalog', slug: 'docs/reference/event-catalog' }, { label: 'Canonical documents', slug: 'docs/reference/canonical-documents' }, ], }, @@ -113,6 +115,7 @@ export const plannedSidebar = { 'Users and roles', 'Authentication', 'Teams', + 'Scheduled events', 'Moderation', 'Notifications and email', 'Engagement rules', @@ -137,6 +140,7 @@ export const plannedSidebar = { 'The bridge', 'Authentication architecture', 'Teams architecture', + 'Events architecture', 'Protocol versions', ], Reference: [ @@ -145,7 +149,7 @@ export const plannedSidebar = { 'sidecar.toml', 'Bridge.cfg', 'HTTP API', - 'Event catalog', + 'Shard event catalog', 'Canonical documents', ], }; diff --git a/src/content/docs/docs/administration/events.mdx b/src/content/docs/docs/administration/events.mdx new file mode 100644 index 0000000..fc1aef3 --- /dev/null +++ b/src/content/docs/docs/administration/events.mdx @@ -0,0 +1,191 @@ +--- +title: Scheduled events +description: Author an event as phases and steps, price it against this deployment's caps before it runs, and let it change a live game world unattended — with a ledger that makes the undo automatic. +--- + +import { Aside } from '@astrojs/starlight/components'; + +An **event** is a scheduled, bounded, audited change to a live game world. You write it once +as a sequence of phases, publish a version of it, put it on the calendar, and it runs — at +four in the morning if that is when you scheduled it, with nobody watching. + +That last clause is the whole reason this feature is shaped the way it is. Everything below +that looks like extra ceremony — the switchboard, the caps, the dry run, the ledger — is +there because the thing being automated is somebody's game world, and the person who +authored the change is asleep when it happens. + +**Core owns the engine; the installed module owns the meaning.** Core decides whether an +action is permitted, when it runs, in what order, how many times, within what budget, what +it created and who is told. The module says which verbs exist and performs them. Core never +learns a game word: every label you see in the step editor came from the module that +registered it. + +## Where it is + +**Admin → Events**, its own group in the sidebar: + +| Row | Who sees it | +|---|---| +| **Events** — the definitions, and their runs | Admin, editor, moderator | +| **Calendar** — month and list view, with series | Admin, editor, moderator | +| **Actions** — what this deployment permits, and the caps | **Admin only** | +| **My participation** — your own attendance | Everyone | + +Reading is staff-wide on purpose. A moderator's power over this feature is the **run +console** — the screen you open when an event is doing something wrong at two in the +morning — and hiding it from the one role that exists for incident response would be a +strange way to build an incident tool. The narrower gates are on the actions, not the rows: +authoring is admin and editor, publishing a version and starting a run are admin only, and +all of it is enforced on the server rather than by hiding a button. + +## Authoring + +A **definition** is the thing that gets listed, searched, scheduled and audited: a title, a +slug, a storyline, a schedule, and an ordered list of **phases**. Each phase holds **steps**, +and a step is one action with its parameters. + +A phase advances on a condition — after a duration, or when something happens in the game a +given number of times. The vocabulary of "something that happens" is the trigger catalog the +installed module already ships, so a module gains phase conditions by declaring one more +entry in a list it already had. + + + +### Versions are immutable, and a run pins one + +Publishing takes a snapshot. The run that starts on Saturday holds the version that was +published, not the one you edited on Friday — which is what makes a run reproducible and an +audit answerable after a change. **A running event cannot be edited**; you edit the +definition, publish a new version, and the next run picks it up. + +## Nothing is enabled until you enable it + +**Admin → Events → Actions** lists every action the installed modules registered, and +**everything above a notification arrives switched off.** Installing a module must never +start doing things to your world. + +Each row has two controls: whether the action is permitted on this deployment at all, and its +**per-run caps** — how much of a budget dimension one run may consume. Dimensions are +declared by the module (`uo.creatures`, `uo.bosses`, `uo.rewards` and so on), and consumption +is counted in the database with a conditional update, not checked in application code. + +That distinction matters more than it sounds. A stolen admin session has already passed every +role check there is; it still cannot exceed the cap, because the cap is a condition on the +`UPDATE` that spends the budget. + + + +## Dry run before anything unattended + +**Verify** materialises the whole plan without touching the world: every step is dispatched +with a verify flag, and you get back what *would* happen and what it *would* cost against the +caps, in the module's own words. Refusals show up here, before the calendar entry exists. + +A definition that has never been verified is exactly the one worth not scheduling. Verifying +is cheap, and it is the last point a human sees the plan. + +## Running one + +Runs start on the schedule, or by hand. A **series** groups definitions into an arc, so a +three-part story reads as one thing on the calendar rather than three unrelated entries. + +The **run console** shows live status, the steps and their attempts, the budget consumed +against each cap, any failures, and the cleanup. Its controls are: + +- **Pause** and **resume** — resume carries a run past any settled step, including one that + failed or was refused. +- **Skip**, **retry** and **confirm** a single step. *Confirm* is how a human-cue step + advances: the run posts the instruction, waits, and moves on when somebody says they did it. +- **Advance** a phase by hand. +- **Cancel**, with or without cleanup. + +Every one of those is logged with the person who did it. + +## What an event does to a world, and how it is undone + +Two different things, and the difference is the whole safety story. + +**What it owns.** Creatures, bosses, oracle NPCs, decoration, a temporary gate — things the +run created. Each one is written to a **resource ledger** as it is made, with the run and +step that made it. + +**What it borrows.** A spawner's respawn timer, a starting skill cap, a seasonal flag — values +that already existed and are being changed for the duration. Those are **leases**: the game +keeps the original, the site records both halves, and the lease carries its own deadline. + + + +## The game server has its own switches + +They live on the shard host, outside the site's reach, and the site cannot turn them on. + +**`EventsEnabled` is off by default, and it is a different switch from `AdminWriteEnabled`.** +Turning the admin plane on is consenting to staff moderation driven from a screen somebody is +looking at. Turning this on is consenting to the site changing and watching your world +unattended. One switch could not honestly express both. + +Beside it sit the game's own ceilings — how many creatures one call may spawn, how long a gate +may stand, how much one run may own in total, how often the world may be saved. **They refuse +rather than clamp**, for the same reason the caps do: a quietly shortened request leaves the +two halves disagreeing about what actually happened. See +[Bridge.cfg](/docs/reference/bridge-cfg/) for every key. + +## What players see + +The public calendar at `/site/events` carries what is scheduled, what is happening now, what +finished recently, and published results. A run that was cancelled says so — *"Did not +happen"* — rather than quietly disappearing. + +**Listing is separate from publishing.** A definition has its own *listed* switch, because +publishing is what makes an event runnable and a surprise invasion should not have to be +advertised a fortnight in advance in order to be allowed to happen. Unlisting hides the +definition, its runs and its results from the public pages and from a participant's own +history; it hides nothing from staff. + +Where a module can tell who took part, a run can keep a **participation ledger** — scores and +ranks, published as a results table when the run finishes. Ranks are computed at publication +and stored, so somebody added afterwards does not silently renumber a table people have +already read. A signed-in person sees their own attendance under their account, and staff see +theirs on the same screen. + +## When something goes wrong + +- **`degraded` is not `failed`.** If the game server disappears mid-run, the run degrades, + world-changing steps park unattempted, and it recovers when the connection does. The public + page does not say so — that is operator information. +- **`refused` means a bound said no**, and it is reported with the numbers. +- **The run log answers "why did phase 3 not start?"** as a query, not by reading a wall of + text. It is kept for 90 days after a run reaches a terminal state — and a run still in + flight keeps every line it has, however old, because the question it answers is still open. +- **Cleanup can be re-run** from the run console if a teardown was interrupted. + +## Where the record is + +[`website/EVENTS.md`](https://gitea.whitlocktech.com/RunicGateway/docs/src/branch/main/website/EVENTS.md) +is the design of record — the model, the data, the security argument and what was deliberately +left out. +[`website/MODULE_API.md`](https://gitea.whitlocktech.com/RunicGateway/docs/src/branch/main/website/MODULE_API.md) +is the contract a module registers its verbs against, and +[`link/ADMIN_CONTROLS.md`](https://gitea.whitlocktech.com/RunicGateway/docs/src/branch/main/link/ADMIN_CONTROLS.md) +is what the site may ask a game to do at all. + +For how the engine is put together, see +[Events architecture](/docs/architecture/events-architecture/). diff --git a/src/content/docs/docs/architecture/events-architecture.mdx b/src/content/docs/docs/architecture/events-architecture.mdx new file mode 100644 index 0000000..6362ee8 --- /dev/null +++ b/src/content/docs/docs/architecture/events-architecture.mdx @@ -0,0 +1,163 @@ +--- +title: Events architecture +description: An event does not edit the world — it holds a lease. How a game-agnostic engine schedules changes to a live game world it cannot name. +--- + +import { Aside } from '@astrojs/starlight/components'; + +The Event System is a game-agnostic engine for **scheduled, bounded, audited** changes to a +live game world. Core runs it and cannot name a single thing in your game. + +The administrator's view is [Scheduled events](/docs/administration/events/). + +## The two sentences the design turns on + +**An event does not edit the world. It holds a lease.** + +Anything an event changes that already existed is borrowed, not set: the game keeps the +baseline, the site records both halves, and the lease carries its own deadline. When the +deadline passes the game restores the value — whether or not it ever hears from the site +again. A lease is never written to the game's save file either, so a server restart also +puts every borrowed value back. That is the difference between automating a change and +handing an unattended process a `[set` command. + +**The module declares; core dispatches.** A module says a verb exists, what it costs and what +it needs; core decides whether it is permitted, when it runs, in what order, how many times, +within what budget, what it created and who is told. Nothing crosses that line as a string +core interprets — the browser posts an action *id* and a params object, both validated +against the registry before anything is dispatched. There is no passthrough field and no +place a request body can name a game command. + +## What is a table, and what deliberately is not + +Eleven core tables, no ORM, and no migration system — which makes every table a permanent +commitment. The rule applied was: **a table is for what must be queried, claimed or joined.** + +| Kind | Where it lives | +|---|---| +| Definitions, series, versions, runs, steps, budget, resources, participants, gates, settings, log | Tables | +| Phases | Configuration inside an immutable version snapshot. A phase has no identity a query needs; a step does | +| Actions, budget dimensions, conditions | Registry entries a module declares at load. A stored one would outlive the module that can perform it | +| A reward catalog | Neither. A reward is an ordinary action, so a granted reward is an ordinary ledger row | + +**The step is the unit of execution, and it is a row** — one action invocation with a due +time, a status, an attempt count and a claim. Retries, timeouts, duplicate execution and +resumption after a crash are then all properties of that row rather than of a process's +memory, which is what lets the runner be killed mid-run and pick up where it stopped. + +**One run per occurrence, guaranteed by a unique index** on the definition, the scope and the +scheduled instant — not by the claim. Two application instances cannot both start the same +occurrence, because the second insert fails. + +## Budgets are enforced in SQL + +Consumption is spent with a conditional update: + +```sql +UPDATE event_run_budget + SET consumed = consumed + ? + WHERE run_id = ? AND dimension = ? AND consumed + ? <= cap +``` + +No transaction, no read-then-write, and no way for two concurrent steps to both squeeze past +the same ceiling. + + + +## The ledger, and why cleanup is generated + +Every world write appends to a resource ledger before it is confirmed: the run, the step, the +owning module, an opaque kind and reference, and — for a borrowed value — the baseline +alongside what was applied. + +Teardown is then **derived from the ledger**, never authored, and runs on every terminal path: +completion, cancellation and abort alike. An operator cannot be relied on to write the undo, +and an aborted run never reaches the phase they wrote it in. + +Two rules make that hold up: + +- **A unique index across non-reverted rows** stops two events leasing the same target. The + second one is refused rather than layered on top of the first. +- **A restore is a compare-and-set.** If the current value is not what the lease applied, + somebody else changed it since; the row is marked `drifted` rather than stamped over. The + ledger would rather say "I do not know what happened here" than lie about having undone it. + +## At-most-once, on a wire that can lose an answer + +Every command the site sends the game carries an **idempotency key**, and the game executes a +given key at most once — a repeat is answered with the original reply rather than re-run. + +Without it, a lost acknowledgement is indistinguishable from a command that never applied, so +every world write has to be declared un-retryable and one has to be *lost* rather than risk +*doubling* it. The key is what makes a world-changing step an ordinary retried row like any +other. + + + +## Three layers, and the role check is only one of them + +1. **Declaration** — a module says a verb exists. That is code the operator installed; it is + not a permission. +2. **Enablement** — an admin turns an action on for this deployment and sets its caps. + Nothing above a notification is on by default. +3. **Invocation** — the role check, then the cap, then the game's own switches. Admin routes + are re-validated against the database on every request, so a demotion takes effect on the + next click. + +The game's switches are the layer the site cannot reach: `EventsEnabled` and +`AdminWriteEnabled` live in a file on the shard host and are off out of the box, and the +game's own ceilings **refuse rather than clamp** — because a silently shortened request leaves +the two halves disagreeing about what happened. + + + +## Where it meets everything else + +- **[Engagement](/docs/administration/engagement-rules/)** — core registers `event.` triggers + and owns none of the delivery. Who is told about a run is an ordinary rule. +- **[The bridge](/docs/architecture/the-bridge/)** — every world verb becomes a command on the + same versioned wire the game already speaks, through the same sidecar. Core still holds no + game connection. +- **[Teams](/docs/architecture/teams-architecture/)** — "this Team's members" is already a + registered audience, so a guild-scoped event needs no event-side feature at all. +- **News** — an event does not write posts. A core action links an *existing* post to a run and + enqueues it through the announcement pipeline, so the in-game town crier and Discord arrive + as legs that already exist. + +## What it deliberately does not do + +- **No branching.** The condition grammar is `and` / `or` / `not` over comparisons, and the + phase editor is a timeline rather than a canvas, because a canvas would promise power the + engine has not got. +- **No delegation, grants or proposal queue.** Permissions gate on the admin roles that + already exist. The whole authorisation decision lives behind one function, which is what + keeps a coordinator model a later option rather than a redesign. +- **No event invoking another event.** It already works by composition — a second event's + condition can be the first one completing. +- **No mutation of game-owned content without a baseline.** If it cannot be restored, it + cannot be leased, and it is out. + +## Canonical documents + +[`website/EVENTS.md`](https://gitea.whitlocktech.com/RunicGateway/docs/src/branch/main/website/EVENTS.md) +is the design of record; +[`website/MODULE_API.md`](https://gitea.whitlocktech.com/RunicGateway/docs/src/branch/main/website/MODULE_API.md) +is the contract a module registers against; and +[`link/v7.md`](https://gitea.whitlocktech.com/RunicGateway/docs/src/branch/main/link/v7.md) +is the wire protocol the world verbs travel on. diff --git a/src/content/docs/docs/architecture/protocol-versions.mdx b/src/content/docs/docs/architecture/protocol-versions.mdx index dbaf1ae..893b863 100644 --- a/src/content/docs/docs/architecture/protocol-versions.mdx +++ b/src/content/docs/docs/architecture/protocol-versions.mdx @@ -46,18 +46,27 @@ allowed to be chosen independently. ## What a bump obliges Changing a message shape means editing every side plus the specification. The most recent -bump touched: +bump touched five repositories: | Repository | What had to change | |---|---| -| `servuo-plugins` | The emitters, the config keys, and `overlay.toml` | -| `link` | `PROTOCOL_VERSION`, and the projections | -| `module-uo` | The tables, the ingest, and the kind-to-feature map | +| `servuo-plugins` | The handlers, the caps and switches in `Bridge.cfg`, and `overlay.toml` | +| `link` | `PROTOCOL_VERSION`, and the endpoints that carry the new commands | +| `module-uo` | The verbs it declares, their option sources, and the ingest | +| `website` | Core learned a shape it had not had — a lease aimed at one named target | | `docs` | The protocol document and the integration guide | +The `website` row is the one worth noticing. Core holds no game connection and names no +game noun, so most protocol bumps do not reach it at all — the two before this one did not. +This one did, because what changed was not a game *noun* but the shape of a thing core owns +the ledger for. + **A protocol bump can also require a store migration**, because the sidecar persists what it -forwards. That is not automatic — version 4 needed one and version 5 did not, because -version 5 only widened frames the store already keeps whole. +forwards. That is not automatic, and it has happened once: version 4 added a column to a +table that already existed. Versions 5, 6 and 7 needed none, because every frame is +persisted whole — a bump that only widens a frame, or adds a kind, or adds a guarantee about +how a command is executed, asks nothing of a store that defines no schema for a frame's +contents. That is the dumb-forwarder property paying for itself. ## This is not the module API version @@ -93,9 +102,9 @@ What is worth inheriting is the **shape**: ## Canonical documents -[`link/v5.md`](https://gitea.whitlocktech.com/RunicGateway/docs/src/branch/main/link/v5.md) +[`link/v7.md`](https://gitea.whitlocktech.com/RunicGateway/docs/src/branch/main/link/v7.md) is the current protocol's record, including its cross-repository obligations, and -[`link/v4.md`](https://gitea.whitlocktech.com/RunicGateway/docs/src/branch/main/link/v4.md) +[`link/v6.md`](https://gitea.whitlocktech.com/RunicGateway/docs/src/branch/main/link/v6.md) the one before it; [`link/PLAN.md`](https://gitea.whitlocktech.com/RunicGateway/docs/src/branch/main/link/PLAN.md) §7 is the wire protocol, and diff --git a/src/content/docs/docs/architecture/the-bridge.mdx b/src/content/docs/docs/architecture/the-bridge.mdx index 4d8773d..dcafcf3 100644 --- a/src/content/docs/docs/architecture/the-bridge.mdx +++ b/src/content/docs/docs/architecture/the-bridge.mdx @@ -95,7 +95,7 @@ A representative line looks like: "price":75000,"commission":3750} ``` -The full catalog is [Event catalog](/docs/reference/event-catalog/). +The full catalog is the [Shard event catalog](/docs/reference/event-catalog/). ## Two design details worth stealing diff --git a/src/content/docs/docs/index.mdx b/src/content/docs/docs/index.mdx index f44712d..29b1ffb 100644 --- a/src/content/docs/docs/index.mdx +++ b/src/content/docs/docs/index.mdx @@ -43,8 +43,9 @@ They meet at four values pasted into the module's shard screen, and at protocol {platform.protocol}, which both sides check before they will pair. You can stop after the first one. A site with no game server attached is a complete -community website — news, wiki, pages, Teams, forums, accounts and moderation are all core, -and none of them knows a game exists. The second install is what fills the game screens. +community website — news, wiki, pages, Teams, forums, accounts, moderation and the event +calendar are all core, and none of them knows a game exists. The second install is what +fills the game screens, and what lets an event reach into a world. ## Start here @@ -59,8 +60,8 @@ you should expect to see before you move on: 6. [Verify the whole stack](/docs/getting-started/verify-the-whole-stack/) — proving it works, rather than assuming Then **Administration** covers running it: configuration, branding, content, users, -authentication, Teams, moderation, notifications, modules, the shard connection, upgrades, -and what to do when something is wrong. +authentication, Teams, scheduled events, moderation, notifications, modules, the shard +connection, upgrades, and what to do when something is wrong. ## Where the truth lives diff --git a/src/content/docs/docs/reference/canonical-documents.mdx b/src/content/docs/docs/reference/canonical-documents.mdx index 9d34fa8..14be822 100644 --- a/src/content/docs/docs/reference/canonical-documents.mdx +++ b/src/content/docs/docs/reference/canonical-documents.mdx @@ -45,7 +45,10 @@ moved turns this page red rather than leaving a dead link. - **"Why is the module system like this?"** → `MODULE_SYSTEM.md`. - **"What does this API return?"** → your own deployment's `/api/docs`, then `BACKEND_DESIGN.md` §4. -- **"What can the shard send?"** → `link/PLAN.md` §5, and `v4.md` for the current protocol. +- **"What can the shard send?"** → `link/PLAN.md` §5, and `v7.md` for the current protocol. +- **"What may a scheduled event do to the world?"** → `website/EVENTS.md` for the model, + `MODULE_API.md` for the verbs a module may declare, and `link/ADMIN_CONTROLS.md` for what + the site may ask a game to do at all. - **"Who may see this?"** → `SHARD_VISIBILITY.md` for the administrator's view, `modules/uo/API.md` §4 for the specification. - **"How do I set a shard up?"** → `installer/INSTALL.md`. diff --git a/src/content/docs/docs/reference/event-catalog.mdx b/src/content/docs/docs/reference/event-catalog.mdx index 8ae00db..097f785 100644 --- a/src/content/docs/docs/reference/event-catalog.mdx +++ b/src/content/docs/docs/reference/event-catalog.mdx @@ -1,5 +1,5 @@ --- -title: Event catalog +title: Shard event catalog description: What a game server can tell the website, how those events are grouped, and the five-rung ladder that decides who may see each one. --- @@ -8,6 +8,12 @@ import { visibilityLadder } from '../../../../data/reference.mjs'; The events a shard emits, and the mechanism that decides who may see them. +**Not to be confused with a scheduled event.** This page is about what the game tells the +site, unprompted, as things happen in the world. An *event* in the sense of a thing you put +on the calendar and run — phases, steps, a boss at eight o'clock — is +[Scheduled events](/docs/administration/events/). The two do meet: a kind listed below is +exactly what a scheduled event's phase can wait for. + The exact wire shapes are in the protocol specification and are **not** restated here — a copy of a wire format is a copy that will be wrong after the next bump. This page is the map and the security model. diff --git a/src/data/capabilities.mjs b/src/data/capabilities.mjs index 2df9c24..c0c83c2 100644 --- a/src/data/capabilities.mjs +++ b/src/data/capabilities.mjs @@ -143,6 +143,16 @@ const community = { 'than per person by you. The on-site inbox arrives by default and can be switched ' + 'off; push and email only ever arrive if they were asked for.', }, + { + label: 'Event calendar', + demoPath: '/site/events', + detail: + 'What is scheduled, what is happening now, what finished recently, and the ' + + 'results afterwards — with arcs, so a three-part story reads as one thing rather ' + + 'than three unrelated entries. A run that was cancelled says so instead of ' + + 'quietly vanishing. Core owns the whole calendar and can run an event on its ' + + 'own; what an event may do inside a game world comes from the installed module.', + }, { label: 'Wiki', demoPath: '/wiki', @@ -306,6 +316,17 @@ const administration = { 'view with an emergency unban, deliberately — it is not somewhere to tune a ' + 'threshold at three in the morning.', }, + { + label: 'Scheduled world events', + detail: + 'Author an event as phases and steps, publish a version, put it on the calendar ' + + 'and let it run unattended — with a dry run first that prices the whole plan ' + + 'against this deployment’s caps. Every action arrives switched off, every run ' + + 'has a per-run budget enforced in the database rather than in a role check, and ' + + 'everything an event creates or borrows is written to a ledger so the undo is ' + + 'generated rather than authored. Pause, resume, skip a step or cancel with ' + + 'cleanup, all logged with the person who did it.', + }, { label: 'Module management', detail: diff --git a/src/data/collection.mjs b/src/data/collection.mjs index 2af9fb3..ee69496 100644 --- a/src/data/collection.mjs +++ b/src/data/collection.mjs @@ -471,6 +471,35 @@ export const collected = [ 'website server/db/schema.sql — engagement_sends, engagement_suppressions, ' + 'notification_channel_prefs; server/src/utils/engagementRetentionPrune.js', }, + { + id: 'deploy-events', + scope: 'deployment', + title: 'Who took part in a scheduled event', + body: + 'Where the operator runs scheduled events, a run can count who took part — kept as ' + + 'the name the game module knows a participant by, a score, and a rank, linked to a ' + + 'site account where one is linked and left unlinked where it is not. That is what ' + + 'the published results table renders, and what a signed-in person sees as their own ' + + 'event history. Beside it the site records what each run did: which step ran, what ' + + 'it created or borrowed in the game world, whether the undo succeeded, and which ' + + 'staff account started, paused or cancelled it.', + retention: { + summary: + "A run's diagnostic log is swept after 90 days; the run itself and its participants " + + 'are kept until the operator removes them', + detail: + 'The log that answers "why did this run stall" is deleted 90 days after a run ' + + 'reaches a terminal state, and only then — a run still in flight keeps every line ' + + 'it has, however old, because the question it answers is still open. The run, its ' + + 'steps, what it created and its participant list are not swept: they are the ' + + 'record of what was done to a shared world, and deleting one silently would ' + + 'unmake an audit. Deleting an account detaches its participation rows rather than ' + + 'removing them — the result table keeps the score and stops naming a person.', + }, + source: + 'website server/db/schema.sql — event_runs, event_run_participants, ' + + 'event_run_resources, event_run_log; server/src/utils/eventRunner.js', + }, { id: 'deploy-game-data', scope: 'deployment', @@ -481,7 +510,7 @@ export const collected = [ 'Which of it is visible to the public is the operator’s decision, made in the ' + 'admin panel — the bridge itself forwards, and the site decides.', retention: { summary: 'Operator-configured' }, - source: 'docs/link/v4.md — the visibility framework', + source: 'docs/link/v7.md — the visibility framework', }, ]; diff --git a/src/data/platform.json b/src/data/platform.json index 062facc..ea52ca2 100644 --- a/src/data/platform.json +++ b/src/data/platform.json @@ -12,23 +12,23 @@ "wrong protocol number in the first place." ], - "verifiedOn": "2026-09-01", + "verifiedOn": "2026-09-09", - "protocol": 5, + "protocol": 7, - "moduleApi": "1.9.0", + "moduleApi": "1.10.0", "bundle": { - "tag": "2026.09.01", - "sidecar": "v2.1.0", - "overlay": "v1.1.0", + "tag": "2026.09.10", + "sidecar": "v2.2.0", + "overlay": "v1.2.0", "servuoMin": "57.4" }, "releases": { - "link": "v2.1.0", + "link": "v2.2.0", "installer": "v0.1.1", - "Module-uo": "v1.1.0", + "Module-uo": "v1.2.2", "Android-app": "v0.5.0" }, diff --git a/src/data/reference.mjs b/src/data/reference.mjs index 4164ff5..69f3bcb 100644 --- a/src/data/reference.mjs +++ b/src/data/reference.mjs @@ -151,6 +151,49 @@ export const bridgeCfg = { AdminReasonMaxLength: 'Reason field cap', AdminBanMaxDurationSec: 'Longest ban the site may set', }, + 'Scheduled events': { + EventsEnabled: + 'Whether the website may run scheduled events against this world at all. Off by ' + + 'default, and deliberately a separate switch from AdminWriteEnabled', + EventsSweepSeconds: 'How often expired gates are collected and lost objects pruned', + EventsMinSaveIntervalSec: + 'Shortest gap between world saves, counted from the last save by anyone. A save ' + + 'asked for too soon is refused rather than queued', + }, + 'Event caps': { + EventsMaxCreatures: 'Creatures one call may spawn', + EventsMaxBosses: 'Enhanced "boss" variants one call may spawn', + EventsMaxNpcs: 'Oracle NPCs one call may place', + EventsMaxDecor: 'Decoration items one call may place', + EventsMaxGateMinutes: 'Longest a temporary gate may stand', + EventsMaxOwnedPerRun: 'Objects one run may own across every verb — the runaway bound', + EventsMaxSpread: 'How far from the chosen spot things may be scattered', + EventsMaxBossMultiplier: 'How much harder than normal a boss may be made', + EventsMaxGrantPerRun: 'How many characters one item grant may reach', + EventsMaxGrantStack: 'How large one granted stack may be', + }, + 'The oracle NPC': { + EventsOracleMaxLines: 'Keyword lines it will answer to', + EventsOracleGreetRange: 'How close a player must be to be greeted', + EventsOracleSpeechRange: 'How close a player must be to be heard', + EventsOracleGreetCooldownSec: 'How often it greets the same player', + EventsOracleAnswerCooldownSec: 'How often it answers the same player', + }, + Leases: { + LeaseMaxDurationSec: + 'Longest lease this shard will hold, whatever the site asks for. A longer request ' + + 'is refused rather than shortened', + LeaseGraceSec: 'How long a restored lease stays listed, so a late teardown still gets a verdict', + }, + Participation: { + ParticipationSweepSeconds: "How often everyone standing in a run's area is credited", + ParticipationKillWeight: 'What one kill inside the area is worth against one minute in it', + ParticipationMaxRuns: 'Runs counted at once', + ParticipationMaxMembers: 'Members counted per run', + ParticipationMaxRadius: 'Widest area an event may declare', + ParticipationGraceSec: "How long a closed run's tally stays readable", + ParticipationSnapshotChunk: "Members resolved per yield of the game's core thread", + }, Accounts: { SignupMode: 'How game accounts may be created', AccountCreateEnabled: 'Allow creation at all', @@ -175,12 +218,13 @@ export const canonicalDocs = { 'website/MODULE_SYSTEM.md': 'Why the module system is shaped this way', 'website/MODULE_API.md': 'Everything a module may do — the contract', 'website/TEAMS.md': 'Teams as a platform primitive', + 'website/EVENTS.md': 'The Event System — leases, the ledger, and the module seam', 'website/SHARD_VISIBILITY.md': 'The audience ladder, for administrators', 'website/THEMING_AND_NAV.md': 'Admin-configurable theme, assets and navigation', 'website/TRUSTED_DEVICES_MFA.md': 'Trusted devices and the second factor', 'link/PLAN.md': 'The sidecar design of record, the data catalog and the wire protocol', 'link/INTEGRATION.md': 'Integrating with the sidecar', - 'link/v4.md': 'Protocol 4, and its cross-repository obligations', + 'link/v7.md': 'The current protocol, and its cross-repository obligations', 'link/ADMIN_CONTROLS.md': 'What the site may command the game to do', 'installer/INSTALL.md': 'The operator guide for setting a shard up', 'installer/PLAN.md': "The installer's design of record",