feat(events): a trigger can start a run (Rust §14.3 stage 9, D370) #216

Merged
whitlocktech merged 1 commits from feat/event-trigger-starts into main 2026-10-16 07:08:02 +00:00
Member

What

Core work from Rust PLAN_REDESIGNS §14.3.12 (D370), stage 9 of §14.3 and the last build stage. A definition's published version may now carry starts, meaning a trigger, a condition and a cooldown. A firing that matches creates a run of it right away. Examples: "when a champion's boss appears, start Champion's Call", or "when Bradley is destroyed on main, start Launch Site Lockdown".

  • Spec: starts: [{ on, where, cooldown }]. It is checked by the phase gate's own validator, so it uses the same grammar and the same dormant rule (a dormant start refuses publish). The cooldown can be 0m to 7d and defaults to 30m. A definition can have at most 10 starts. Core's own event.* triggers are refused.
  • events/starts.js: the emit path's third subscriber, next to engine.dispatch and the gate observer. It is never awaited and never rejects.
    • An unverified version starts nothing (§K: a trigger start runs with nobody watching).
    • "One run at a time" and the cooldown are enforced by one conditional UPDATE on the new event_trigger_starts table.
    • scheduled_for is the firing's time, so the occurrence key turns one firing reported twice into one run.
    • Ignored firings are counted by reason.
  • event_runs.origin: stores the trigger, its label, when it fired, its subject, the condition it matched and its declared variables. It is also written on the run.created audit line.
  • UI: the editor gets a Started by the game panel (the gate's condition builder plus a cooldown) and the line "Last started run #N; since then 2 firings ignored while a run was going". The run console shows "Started because … fired".
  • No module contract change and no MODULE_API bump. Module-uo and Module-Rust need no code.
  • Swagger annotations are updated and the spec is regenerated.

Docs: RunicGateway/docs PR for EVENTS.md §D/§E/API, BACKEND_DESIGN.md and PLAN_REDESIGNS §14.3.21 (branch docs/event-trigger-starts).

Tests

  • Server test/eventStarts.test.js: 21 new tests, all passing.
  • test/eventRunnerSql.test.js: 7 new tests run against a real MariaDB (75/75 pass). They cover the JSON lookup, one winner out of five concurrent claims, the cooldown boundary, a going run blocking the claim, stale claims and origin round-tripping.
  • Client test/eventStarts.test.js: 8 new tests.
  • Full suites: server 2124 pass. The only 2 failures are routeManifest.test.js, which fail the same way on an untouched main on this machine because they read the locally installed modules. Client 430/430, build OK, check:modules and check:hosts OK.
  • Module-uo proof, run against the real core, the real uo module and the dev MariaDB, with a definition starting on uo.champ.boss_up where spawnName is Abyss and the cooldown is 1m:
    • before verify: the firing was held and counted;
    • a Destard firing: nothing;
    • an Abyss firing: one run with its origin;
    • the same firing again: nothing, not counted;
    • while the run was going, and inside the cooldown: each counted;
    • after the cooldown: a second run.
  • Browser check on the local stack: the editor panel renders, saves and reloads (cooldown 30m → 45m stored), the ignored line shows, and the run console shows the origin line.

AI disclosure

  • This PR was written with an AI tool: Claude Code (Claude Opus 5.5).

🤖 Generated with Claude Code

https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY

## What Core work from Rust PLAN_REDESIGNS §14.3.12 (D370), stage 9 of §14.3 and the last build stage. A definition's published version may now carry `starts`, meaning a trigger, a condition and a cooldown. A firing that matches creates a run of it right away. Examples: *"when a champion's boss appears, start Champion's Call"*, or *"when Bradley is destroyed on main, start Launch Site Lockdown"*. - **Spec:** `starts: [{ on, where, cooldown }]`. It is checked by the phase gate's own validator, so it uses the same grammar and the same dormant rule (a dormant start refuses publish). The cooldown can be `0m` to `7d` and defaults to `30m`. A definition can have at most 10 starts. Core's own `event.*` triggers are refused. - **`events/starts.js`:** the emit path's third subscriber, next to `engine.dispatch` and the gate observer. It is never awaited and never rejects. - An unverified version starts nothing (§K: a trigger start runs with nobody watching). - "One run at a time" and the cooldown are enforced by one conditional `UPDATE` on the new `event_trigger_starts` table. - `scheduled_for` is the firing's time, so the occurrence key turns one firing reported twice into one run. - Ignored firings are counted by reason. - **`event_runs.origin`:** stores the trigger, its label, when it fired, its subject, the condition it matched and its declared variables. It is also written on the `run.created` audit line. - **UI:** the editor gets a *Started by the game* panel (the gate's condition builder plus a cooldown) and the line *"Last started run #N; since then 2 firings ignored while a run was going"*. The run console shows *"Started because … fired"*. - **No module contract change and no MODULE_API bump.** Module-uo and Module-Rust need no code. - Swagger annotations are updated and the spec is regenerated. Docs: RunicGateway/docs PR for EVENTS.md §D/§E/API, BACKEND_DESIGN.md and PLAN_REDESIGNS §14.3.21 (branch `docs/event-trigger-starts`). ## Tests - Server `test/eventStarts.test.js`: 21 new tests, all passing. - `test/eventRunnerSql.test.js`: 7 new tests run against a real MariaDB (75/75 pass). They cover the JSON lookup, one winner out of five concurrent claims, the cooldown boundary, a going run blocking the claim, stale claims and `origin` round-tripping. - Client `test/eventStarts.test.js`: 8 new tests. - Full suites: server 2124 pass. The only 2 failures are `routeManifest.test.js`, which fail the same way on an untouched `main` on this machine because they read the locally installed modules. Client 430/430, build OK, `check:modules` and `check:hosts` OK. - **Module-uo proof**, run against the real core, the real `uo` module and the dev MariaDB, with a definition starting on `uo.champ.boss_up` where spawnName is Abyss and the cooldown is 1m: - before verify: the firing was held and counted; - a Destard firing: nothing; - an Abyss firing: one run with its origin; - the same firing again: nothing, not counted; - while the run was going, and inside the cooldown: each counted; - after the cooldown: a second run. - Browser check on the local stack: the editor panel renders, saves and reloads (cooldown 30m → 45m stored), the ignored line shows, and the run console shows the origin line. ## AI disclosure - [x] This PR was written with an AI tool: Claude Code (Claude Opus 5.5). 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
wtclaude added 1 commit 2026-10-16 06:52:41 +00:00
feat(events): a trigger can start a run
All checks were successful
PR Checks / bot-tests (pull_request) Successful in 35s
PR Checks / client-build (pull_request) Successful in 44s
PR Checks / server-tests (pull_request) Successful in 12m45s
c9e992588b
Rust PLAN_REDESIGNS §14.3.12 (D370), stage 9 of §14.3. A definition's
published version may carry `starts`: a trigger, a condition and a
cooldown. A matching firing creates a run of it, now.

- spec: `starts` validated by the phase gate's own function (same
  grammar, same dormant rule; a dormant start refuses publish), a
  cooldown from 0m to 7d (30m default), at most ten, event.* refused.
- events/starts.js: the emit path's third subscriber, beside dispatch
  and the gate observer. An unverified version starts nothing; one run
  at a time per definition and the cooldown are one conditional UPDATE
  on the new event_trigger_starts table; the occurrence key makes one
  firing reported twice one run. Ignored firings are counted by why.
- event_runs.origin: the firing (trigger, label, time, subject, matched
  condition, declared variables), also on the run.created audit line.
- Editor: a "Started by the game" panel using the gate's condition
  builder, with the ignored-firings line. Run console: "Started because
  ... fired".
- No module contract change and no MODULE_API bump.

Tests: eventStarts (server 21, client 8); eventRunnerSql +7 against a
real MariaDB (75/75); Module-uo proof on uo.champ.boss_up against the
dev database.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
whitlocktech approved these changes 2026-10-16 07:02:25 +00:00
whitlocktech scheduled this pull request to auto merge when all checks succeed 2026-10-16 07:02:49 +00:00
whitlocktech merged commit 0f29f0acb2 into main 2026-10-16 07:08:02 +00:00
whitlocktech deleted branch feat/event-trigger-starts 2026-10-16 07:08:03 +00:00
Sign in to join this conversation.
No description provided.