Skip to content

fix(triggers): registration follows every flag and list change; migrator honours Running=false; no nullable value types - #2

Merged
kamronbatman merged 8 commits into
mainfrom
fix/trigger-followups
Sep 12, 2026
Merged

kamronbatman merged 8 commits into
mainfrom
fix/trigger-followups

Conversation

@kamronbatman

@kamronbatman kamronbatman commented Sep 12, 2026 •

Copy link
Copy Markdown
Member

Summary

Closes the trigger-registration follow-ups parked in #1, and removes the last nullable value types from the engine.

  • Registration follows every flag and list change. TriggerActivated is now a property whose setter calls EnsureTriggersActive() (serialized slot unchanged, layout byte-identical). TriggerConfigGump adds and removes through the generated helpers (RemoveFromTriggerDefinitionsAt for the clicked row) and re-registers after each edit; SpawnerJsonImporter clears through ClearTriggerDefinitions() so dirty tracking holds. TriggerSystem.ActivateTriggers still has exactly one caller.
  • Deactivation no longer gated on the flag. OnStopped and OnDelete call DeactivateTriggers unconditionally (a no-op when nothing is registered), so clearing TriggerActivated after registration can no longer leave a deleted spawner in the trigger system.
  • XmlSpawner migrator honours Running="false" on both node forms: the spawner is stopped (it is constructed running) and its triggers registered or not accordingly. Behaviour note: a Running="true" node with zero parsed entries now imports stopped (before, it was left in its constructed running state), which matches D9's "unreproducible encounters import stopped".
  • Perf seed and gump fixes found by review. [ModernSpawnerPerfSeed now activates the proximity trigger it adds, so the proximity-dispatch benchmark measures something. TriggerConfigGump writes the time-window definitions with the trigger type names TriggerSystem actually registers (it wrote walltime:/gametime: before, which parsed as unknown while the gump reported success), and clamps the page after deleting the last trigger on a page.
  • No nullable value types: XmlSpawnerImporter delays use the TimeSpan.Zero sentinel AddModernEntry already understands; SkillTrigger gets a TimeSpan cooldown overload; ScriptContext.LocationOverride becomes HasLocationOverride + Point3D. The remaining Type? members live only in the legacy YAML/JSON export models (ScriptExportModels, SpawnerExportModels), which the SpawnerDto-only format decision (D5/D6) retires; they are left for that work.
  • Docs: CLAUDE.md rule for trigger mutations; dev-docs/architecture.md §2.2; dev-docs/modernuo-prerequisites.md records ModernUO #2636 (SkillEvents.SkillChecked + test-clock IVT) as open; dev-docs/product-spec.md corrects the D3 row: skill: definitions parse and register today but never fire, because nothing calls ModernSpawnerEvents.OnSkillUsed until the wiring PR subscribes to #2636's event.

Test plan

  • New world-backed tests in the sequential collection: toggling TriggerActivated registers/unregisters; adding a definition to an activated running spawner registers immediately; deleting an activated spawner leaves nothing registered; stopping with a stale registration unregisters; deleting a stopped spawner with a stale registration unregisters; migrator Running="false" on both node forms. Each mutation-checked against its reverted behaviour.
  • dotnet build ModernSpawner.slnx clean; full suite green (438). The test fixture now runs EventScheduler.Configure() like production, since window triggers schedule through it.
  • CI

Follow-ups

  • TriggerSystem.OnMobileProximity allocates a TriggerContext class per movement event per spawner; the D2 trigger-state-machine work should make it a readonly struct passed by in.
  • Legacy export models keep their nullable members until the SpawnerDto-only format (D5/D6) retires them.

kamronbatman and others added 8 commits September 11, 2026 20:49
…deactivate unconditionally on stop and delete

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KXJwRhwvHQXV2ABJHicwsr
…unregisters it

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KXJwRhwvHQXV2ABJHicwsr
Both migrator construction paths were constructed running and only ever
called Start(); a "Running=false" node had nothing to stop it, so the
spawner stayed running (and its triggers stayed registered) regardless
of what the file said. Both paths now call Stop() when the file's
Running flag is false, matching the ParseSpawnPointNode form's default
of true when the attribute is absent, then re-derive trigger
registration through EnsureTriggersActive().

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KXJwRhwvHQXV2ABJHicwsr
…script context

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KXJwRhwvHQXV2ABJHicwsr
…UO #2636 recorded

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KXJwRhwvHQXV2ABJHicwsr
…e registered type names; page clamp

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KXJwRhwvHQXV2ABJHicwsr
…d to the engine project

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KXJwRhwvHQXV2ABJHicwsr
…tive doc matches the registry semantics

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KXJwRhwvHQXV2ABJHicwsr
@kamronbatman
kamronbatman merged commit 9710e76 into main Sep 12, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant