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
ActivityModandPolicyModrecords, thenSecsRegistry.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.
SecsRegistryandModRegistrymust 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.Replaceas the missing activity/policy runtime path. - Diagnostics stay split by owner. Runtime mod-finalization diagnostics use the
shipped
ModDiagnosticchannel (SECS0602,SECS0603,SECS0611,SECS0800,SECS0801, etc.). Compiler/parser diagnostics for source syntax must use their reserved compiler codes and must not collide with runtimeSECS0801. - Data-only versus host-capable source-set enforcement remains compiler-owned.
The runtime does not gain fictional
SourceSet,HostCapable, orDataOnlyflags. - Activity and policy architectural slots stay replace-only:
activity_actor_scope,activity_target_scope,activity_lane,activity_args_schema,policy_actor_scope, andpolicy_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.