Skip to main content

AD-0001: Runtime Mod Finalization Boundary

Status

Accepted (2026-05-12).

Context

SECS has two related but distinct modding surfaces:

  • The future source-set-aware compiler merger: parse base content plus mods, apply semantic-slot operations in load order, bind the merged source once, and lower one final game DLL.
  • The shipped runtime startup finalizer: generated or hand-written stand-ins register ActivityMod and PolicyMod records, then SecsRegistry.FinalizeModRegistration() merges those records once before the game loop starts.

Older backlog and status text treated all mod-operation support as missing engine work. That is no longer true for activities and policies. The runtime already owns the activity/policy startup-finalization path through src/SECS.Engine/Modding/ModRegistry.cs and the SecsRegistry.RegisterActivityMod, RegisterPolicyMod, and FinalizeModRegistration() entry points in src/SECS.Engine/SecsRegistry.cs.

The remaining source-level work is still real, but it belongs to the compiler: source-set discovery, load-order resolution, deterministic source ordering, AST/IR slot extraction for all declaration kinds, source diagnostics, and lowering to runtime registration calls or already-merged generated code.

Decision

The permanent architecture boundary is:

  • Runtime owns activity/policy startup finalization.
  • Compiler owns the full source-set-aware semantic merger.
  • SecsRegistry and ModRegistry must not grow into a generic runtime merger for templates, modifiers, systems, events, on-actions, scopes, contracts, or arbitrary generated C#.

At startup, hosts may register activity and policy mods in load order and call FinalizeModRegistration(). Finalization produces frozen merged activity and policy maps. After finalization, the ticking game reads only merged declarations; there is no per-mod runtime lookup during execution.

Phase 3 of the compiler plan must target this boundary. For activities and policies, it emits ActivityMod / PolicyMod registration records or an equivalent already-merged output that preserves the same slot semantics. For other declaration kinds, Phase 3 remains a compiler-owned source merger until a specific runtime executable subset is explicitly accepted by a later ADR.

Consequences

  • Backlog rows must distinguish shipped activity/policy runtime finalization from deferred generic source merger work.
  • Docs must not describe SecsRegistry.Replace as the missing activity/policy runtime path.
  • Diagnostics stay split by owner. Runtime mod-finalization diagnostics use the shipped ModDiagnostic channel (SECS0602, SECS0603, SECS0611, SECS0800, SECS0801, etc.). Compiler/parser diagnostics for source syntax must use their reserved compiler codes and must not collide with runtime SECS0801.
  • Data-only versus host-capable source-set enforcement remains compiler-owned. The runtime does not gain fictional SourceSet, HostCapable, or DataOnly flags.
  • Activity and policy architectural slots stay replace-only: activity_actor_scope, activity_target_scope, activity_lane, activity_args_schema, policy_actor_scope, and policy_domain.

References

  • docs/design/00-overview.md — glossary, diagnostic map, and ordering rules.
  • docs/design/04-behavior.md — activity and policy lowering/runtime contract.
  • docs/design/06-overrides-and-modding.md — source operation model and slot schema.
  • docs/design/09-ai-policies-and-activities.md — policy/activity behavior model.
  • docs/design/10-host-secs-execution-boundary.md — host/runtime ownership split.
  • src/SECS.Engine/Modding/ModRegistry.cs — shipped activity/policy finalizer.
  • src/SECS.Engine/SecsRegistry.cs — runtime registration and finalization entry points.
  • tests/SECS.Engine.Tests/ModRegistryTests.cs — runtime finalization coverage.
  • tests/Valenar.Host.Tests/DemoModIntegrationTests.cs — Valenar integration coverage.