SECS Future Work — Backlog Index
A durable punch-list aggregating every deferred item, open engine task, stand-in bug,
and unresolved design topic across the design docs. Each item cites the doc section
or audit finding that generated it. Items are not closed by editing the source;
they move to the ## Completed section at the bottom when addressed.
Last regenerated: 2026-05-13.
How this doc is maintained
- When a new design commit creates engine tasks, compiler diagnostics, deferred features, or stand-in bugs, add rows here at the same time the docs are updated.
- When an item lands (compiler implements a diagnostic, engine catches up with spec,
stand-in gets refactored), move the row to
## Completedwith a short "landed" note. Do NOT delete — the history matters. - Re-sweep this doc once per quarter (or after any major design pass) against the canonical docs to catch drift.
- Entries cite committed docs (
docs/design/*.md) first, thenaudit-findings.mdandmod-coverage-audit.mdby finding number. If a backlog item survives only from deleted legacy docs, restate the needed semantics here in concise live wording instead of citing a removed source. - Mod-coverage findings (from
mod-coverage-audit.mdor future audits of the same class) land in section § 7 below. Each finding admits via the same row schema used elsewhere — citation, blocks, notes — plus a "mod scenario" summary line so the reader can trace the originating concern without re-reading the audit.
1. Engine implementation tasks
Runtime catch-up work driven by committed spec. Each row is work the engine must absorb before the compiler can emit the committed shape without the engine silently dropping or mis-handling the output.
| Task | Source doc § | Engine file:line | Blocks | Notes |
|---|---|---|---|---|
Implement scope-walk declared-edge validation at registry-init time — reject WalkScope(from, to) when to is not in from's walks_to list (server-side safety net for SECS0111) | 01-world-shape.md § "Scope-walk correctness" (L222-291) | src/SECS.Engine/SecsRegistry.cs (new method) + walk dispatcher | Compiler SECS0111 emits at build time; engine catches mod-injected walks | |
Implement runtime formula read-set validation — debug-build check on ChannelResolver that checks every resolve(ChannelId) against GetFormulaDependencies(formulaId); throw SecsFormulaReadSetViolation on mismatch | 03-channels-and-modifiers.md § "Runtime read-set validation" (L947-959) | src/SECS.Engine/Resolution/ChannelResolution.cs; new exception type under src/SECS.Abstractions/Exceptions/ | D-Hybrid rule; debug-build cycle-miss catch | Compiled-out under #if DEBUG or equivalent |
Auto-extract formula readsChannels from the expression tree (compiler-side task; engine provides the registration surface) | 03-channels-and-modifiers.md § "reads { } — D-Hybrid rule" (L873-943) | Compiler AST walker feeds SecsRegistry.RegisterFormula (src/SECS.Engine/SecsRegistry.cs:110-115) | D-Hybrid case 1 (static literal) and case 3 (mixed) | Engine side is already built; compiler pass is the missing piece |
Populate captured-entity slots (captured0, captured1) at add_modifier call sites when the trigger body references root or prev | 03-channels-and-modifiers.md § "Trigger lowering" (L814-871), 05-expressions.md § "Built-in sigils" / "add_modifier" (L66-67, L355-356) | src/SECS.Abstractions/Commands/CommandBufferExtensions.cs:57-75 (API exists); compiler call-site emission is the gap | Full root/prev semantics in triggers | All Generated/ sites currently pass EntityHandle.Null |
Implement scope-stack capture in ChannelResolver for root / prev in dynamic-effect re-evaluation (captured1 slot on Formula delegates) | 03-channels-and-modifiers.md § "Formula lowering" (L802-812) | src/SECS.Engine/Resolution/ChannelResolution.cs | Dynamic modifier effects that read cross-scope through root/prev | Slot is reserved; both captured args currently pass EntityHandle.Null |
Implement DestroyEntityWithChildren command in CommandBuffer / CommandProcessor | 04-behavior.md § "activity Name — targeted form" (L1106-1108) | src/SECS.Abstractions/Commands/CommandBuffer.cs, CommandProcessor dispatch branch | scope.destroy_with_children() in activity effect bodies | Used by raze_location activity |
| Implement a SECS log command if script-authored logging returns to current fixtures | 04-behavior.md § "End-to-end: PlagueSpreadsEvent" | src/SECS.Engine/Pipeline/TickContext.cs or a host-observer bridge | Optional source-authored gameplay/UI logging | Current .secs fixtures avoid @log(...); earlier stand-ins dropped it because no command surface existed |
Implement arbitrary TickRate system cadence support beyond the named Daily/Weekly/Monthly/Yearly/Once helpers | 04-behavior.md § "Frequency gating" (L180), superseded source shape in 08-collections-and-propagation.md § Part 5 | src/SECS.Engine/Pipeline/ITickSystem.cs already accepts int; compiler binding/emission for frequency = <TickRate expression>; is the gap | Non-standard cadences | Engine accepts any positive int today |
Implement distribute false (opt-out of per-entity load distribution) on Monthly/Weekly systems | 04-behavior.md § "Frequency gating" (L178) | src/SECS.Engine/Pipeline/ITickSystem.cs (possibly new Distributed prop) | Monthly global recalculation systems | Current live behavior docs commit automatic distribution for these cadences; this row tracks the explicit opt-out surface if we add it. |
Register EventSource discriminant on EventEntry (analog of SystemSource for mod-operation provenance/dispatch) | 04-behavior.md § "Event registration" (L580-581) | src/SECS.Engine/Events/EventEntry.cs | Event provenance symmetry with systems | Not yet a field; open question whether needed |
Implement ResourceProductionSystem frequency (currently missing; runs every tick at O(settlements × locations)) | audit-findings.md § 8 / Systems (L291-292); 05-expressions.md per-entity flush idiom (L617) | examples/valenar/Generated/Systems/Economy/ResourceProductionSystem.cs:34-42 | Load distribution at map-generation scale | No Frequency property declared — uses interface default of 1 |
Canonicalise TemplateEntry.Methods dictionary-init syntax — Buildings use brace-brace ({ { H.X, X } }), Features use indexer ([H.X] = X). Compiler must pick one. | audit-findings.md § 8 / Templates/Buildings (L257), 02-templates.md § "Lifecycle methods" (L493-494) | Generated/ output uniformity (compiler emission rule) | Diff cleanliness post-compiler bring-up | Both forms compile today |
Emit generated C# value types and SecsTypeDeclaration[] from the future compiler for structs, records, enums, arrays, and chosen C# collection-shaped values | 07-structured-template-data-and-callables.md § "C# Source Types" | Compiler structural prepass + Generated/ stand-ins | Phase 1 structural prepass; typed fields/callables | Runtime SecsTypeRef/type registry support landed 2026-05-02; compiler emission is still pending |
| Implement structured template-field storage/accessors — scalar fast accessors exist; structured immutable accessors and exact-type value validation still need generated/runtime support | 07-structured-template-data-and-callables.md § "Structured template fields"; 01-world-shape.md § "field declarations" | src/SECS.Engine/Instances/TemplateEntry.cs, generated template classes, TemplateValueResolver | FeaturePlacementProfile and other structured designer data | TemplateFieldDeclaration.ValueType = SecsTypeRef landed 2026-05-02; additive/multiplicative runtime effects remain numeric-scalar only |
Implement registry_only end-to-end instead of documenting it as shipped | 00-overview.md § registry_only, 08-collections-and-propagation.md § Summary of new primitives | src/SECS.Abstractions/Contracts/ContractDeclaration.cs, src/SECS.Engine/SecsRegistry.cs, TemplateActivator, entity creation helpers, generated declarations/tests | Recipe/metadata-only template contracts | Required shape: ContractDeclaration.IsRegistryOnly, registry validation, activation skip, create_entity rejection, generated output, diagnostics/tests, and docs. Until all land, registry_only remains specified/future/current gap. |
Add/confirm a typed TickContext.PrevTick(...) helper or document the existing bridge read as the sole committed API | 08-collections-and-propagation.md § Part 4, SECS-Compiler-Plan.md § Phase 4 | src/SECS.Engine/Pipeline/TickContext.cs, prev-tick snapshot store | Analyzer target for SECS0830; source examples using bridge/runtime call pattern | Do not introduce standalone prev_tick(...) source syntax. |
1.1 Behavior layer — deferred policy infrastructure
Wave 3 of the behavior refactor shipped PolicyDispatcher plus the
no-fallback slot model. The remaining policy plumbing is split across
the next behavior waves and tracked here so it does not get lost.
| Task | Source doc § | Engine file:line | Blocks | Notes |
|---|---|---|---|---|
Per-actor active policy stack (Wave 6) — currently the host invokes PolicyDispatcher.Tick(policy, actor, tick) directly with hardcoded policy ids; Wave 6 introduces the attach_policy event hook and the per-actor active-policy list described in 09-ai-policies-and-activities.md § Attaching a policy to an actor. | 04-behavior.md § Open questions / Policy attachment, 09-ai-policies-and-activities.md § Attaching a policy to an actor | src/SECS.Engine/Policies/PolicyDispatcher.cs, host bootstrap that currently calls Tick directly | attach_policy(...) runtime semantics; player-edit panels that need to know which policies are active | Today the dispatcher tracks at most one child run per (actor, policyId) pair; the Wave 6 stack will replace the hardcoded host invocation with an event-driven attach/detach surface. |
Multi-domain arbitration with priority rules — when multiple policies in different domains both want to dispatch call_best, the host orchestrates today. A future wave introduces a per-actor priority stack so the engine arbitrates deterministically. | 04-behavior.md § Open questions / Concurrency across domains | New per-actor policy-priority structure (TBD) | Multi-policy decision determinism across domains | Listed as an open question on doc 04 prior to this wave; promoted here to a tracked row. |
Activity catalog snapshot wrapper type — Wave 8 deferred a typed ActivityCatalogSnapshot that mirrors AllActivityDeclarations() with player-facing display fields baked in. Hosts can build the catalog from AllActivityDeclarations() today (the projection is straightforward); a typed wrapper would let SecsRegistry cache the projection across mod-finalize boundaries. | 04-behavior.md § Activity request and candidate | src/SECS.Engine/SecsRegistry.cs, src/SECS.Engine/Activities/ | Catalog projection caching across save/load and re-finalize | Raw AllActivityDeclarations() is sufficient for Wave 8; deferred until catalog allocation pressure justifies the abstraction. |
ActivityRunStore.GetByLane(actor, lane) filter — currently hosts iterate RunStore.ActiveRuns and filter manually when they need lane-specific lookups beyond TryGetActive. A typed GetByLane would short-circuit the iteration. | 04-behavior.md § Save shape | src/SECS.Engine/Activities/ActivityRunStore.cs | Lane-scoped lookups on actors with many concurrent runs | Filtering via ActiveRuns is sufficient for Wave 8; TryGetActive covers the single-active-per-lane case. |
1.2 Tag fixture backlog
The tag contract is committed and runtime-supported. Current committed Valenar
executable tag provenance covers template tags only; this backlog does not
claim current Valenar source/generated fixtures for activity tags,
CharacterSurvival.FoodSelector, or policy from tags selectors. Those
fixtures belong to a scoped tag-fixture wave that lands source, Generated
stand-ins, and tests together. The rows below are deferred fixture gaps only;
do not invent Valenar gameplay to close them.
| Task | Source doc § | Engine file:line | Blocks | Notes |
|---|---|---|---|---|
Add a live Valenar activity tags = ... source/generated/test fixture only when a real activity family needs a tag shared by selector or UI behavior | 04-behavior.md § Selectors and SelectorSource, SECS-Compiler-Plan.md § Phase 2 tag binding | Runtime support exists in src/SECS.Abstractions/Activities/SecsActivity.cs and src/SECS.Engine/SecsRegistry.cs | Compiler-readiness provenance for activity tag lowering | Must be owned by the tag-fixture wave; Wave D mod-finalization docs must not rely on staged or unmerged Valenar fixture edits |
Add a live Valenar policy consider activities from tags = ... source/generated/test fixture only when it selects over real tagged activities | 04-behavior.md § Selectors and SelectorSource, 09-ai-policies-and-activities.md § Utility AI selector | Runtime support exists in src/SECS.Engine/Policies/PolicyExecutor.cs via SelectorSource.FromTags | Compiler-readiness provenance for policy selector tag lowering | Must land with or after the activity-tag fixture; do not invent a selector or tag solely to satisfy provenance |
Add a live Valenar propagates_to children where has_tag ... source/generated fixture only when a real modifier needs tag-filtered child propagation | 08-collections-and-propagation.md § Part 2, SECS-Compiler-Plan.md § Phase 2 tag binding | Runtime support exists in src/SECS.Abstractions/Modifiers/PropagationFilter.cs, src/SECS.Engine/StructuralPredicate.cs, and src/SECS.Engine/PropagationDispatcher.cs | Compiler-readiness provenance for modifier propagation has_tag | Generic engine tests already cover PropagationFilter.ForTag; no current Valenar mechanic needs this source shape |
Add a live Valenar Children.Where(has_tag ...).Sum(...) aggregate source/generated fixture only when a real aggregate channel needs tag-filtered children | 08-collections-and-propagation.md § Part 1, SECS-Compiler-Plan.md § Phase 4 aggregate channels | Runtime support exists in src/SECS.Abstractions/Channels/AggregateChannelSource.cs and src/SECS.Engine/Resolution/ChannelResolution.cs | Compiler-readiness provenance for aggregate .Where(has_tag ...) | Generic engine tests already cover aggregate tag predicates; no current Valenar aggregate can be changed without inventing behavior |
1.3 Save/load durability backlog
The runtime currently has partial store-level primitives only:
ScopeSlotStore.Snapshot/Restore, ActivityRun.ToState, and
ActivityRunStore.Restore. This subsection tracks the future unified
payload and the gaps that must close before SECS can claim a complete
durable save/load contract.
| Task | Source doc § | Engine file:line | Blocks | Notes |
|---|---|---|---|---|
Design and implement a future unified SecsSavePayload / SaveSnapshot / RestoreSnapshot boundary without taking over the host's byte format or world container. | 10-host-secs-execution-boundary.md § 20.2 | Future save/load surface under src/SECS.Engine/SaveLoad/ | One coherent SECS runtime payload for activity runs, slots, modifier bindings, pending choices, and policy children | Current committed APIs remain the partial store-level primitives; do not treat the future payload record as shipped. |
Add ModifierBindingStore persistence for real source bindings, including stable creation-order / source-binding metadata so virtual bindings can be reconstructed as derived runtime state. | 03-channels-and-modifiers.md § Modifier propagation, 08-collections-and-propagation.md § Save/load: virtual bindings are derived state, 10-host-secs-execution-boundary.md § 20.1 | src/SECS.Engine/ModifierBindingStore.cs, src/SECS.Engine/PropagationDispatcher.cs | Durable modifier effects, propagation reload, HardOverride order determinism | Virtual bindings themselves stay out of the payload. Restore must rebuild them from restored real bindings and current children. |
| Define the pending-choice key protocol before persisting player-choice events. | 04-behavior.md § Player-choice events, 10-host-secs-execution-boundary.md § 19.5 and § 20.3 | src/SECS.Engine/Events/EventOption.cs, src/SECS.Engine/Events/EventDispatcher.cs future changes | Safe save/load across event-option reorder/add/remove patches | Current pending choices are UI-projected only and resolved by array index; GameSnapshotBuilder DTOs are not durable deserialization. |
Persist PolicyDispatcher.activeChildren after activity runs restore. | 09-ai-policies-and-activities.md § Save / load and migration, 10-host-secs-execution-boundary.md § 20.1 | src/SECS.Engine/Policies/PolicyDispatcher.cs future snapshot/restore surface | cancel_child, wait/resume, and parent-policy bookkeeping across load | Current dictionary is in-memory only; future records must validate referenced ActivityRunIds instead of fabricating missing runs. |
Design a no-lifecycle InstanceStore rehydrate path. | 10-host-secs-execution-boundary.md § 11.1 A8, § 20.1, § 20.2 | src/SECS.Engine/Instances/InstanceStore.cs, src/SECS.Engine/TemplateActivator.cs | Durable entity restore without replaying activation/lifecycle side effects | Current TemplateActivator.Create / Activate fires activation behavior and is not a valid restore primitive for already-created entities. |
Build Valenar durable world save/load beyond GameTimeBank. | 10-host-secs-execution-boundary.md § 3.1 Host-owned, examples/valenar/Server/Services/GameTimeBank.cs | examples/valenar/Host/GameWorld.cs, examples/valenar/Server/Services/GameSessionStore.cs | End-to-end playable saves that combine host world state with SECS runtime records | GameSnapshotBuilder and read-model DTOs are UI projections, not durable save deserialization. GameTimeBank persists elapsed/offline time only. |
2. Compiler diagnostics to implement
Diagnostics specified in the design docs but not yet wired into the (future) SECS
compiler. Codes follow the SECS<doc><code> convention — first two digits = home doc
number.
| Code | What it enforces | Doc source | Blocks |
|---|---|---|---|
SECS0101 | kind = clause missing on top-level channel declaration | 01-world-shape.md § "ChannelKind decision table" L696 | Phase 1 — every channel must state its kind explicitly |
SECS0104 | Unknown channel identifier in resolve(X) or template-body channel X usage (channel not declared in top-level Channels[]) | 01-world-shape.md § "Channel with no source"; 02-templates.md § "Historical stand-in bugs under the current channel model" | Catches future undeclared-channel drift; Valenar feature channels are now declared in locations/features/channels.secs |
SECS0105 | kind = Base without source = clause | 01-world-shape.md § "ChannelKind" L697 | Use kind = Contributed for channels with no host field |
SECS0106 | kind = Accumulative without source = clause | 01-world-shape.md § "ChannelKind" L698 | Accumulative requires host-owned storage field |
SECS0107 | Two identifiers collide under case-insensitive FNV-1a-64 lowering | 01-world-shape.md § "Case-collision rule" L699, L765-770 | Rename one of the colliding identifiers |
SECS0108 (warning) | Cross-assembly redeclaration of an identifier without an inject / replace / try / or_create mod operation | 01-world-shape.md § "Cross-assembly same-name rule" L700, L772-774; current operation model in 06-overrides-and-modding.md § 3 | Steer mod authors toward an explicit mod operation when intentional |
SECS0110 | Clamp literal type does not match channel type (or bool channel has min/max) | 01-world-shape.md § "Clamp literal types" L701, L620-672 | Surfaces declaration-type drift |
SECS0111 | WalkScope(from, to) / to.X targets a scope not declared in from's walks_to list | 01-world-shape.md § "Scope-walk correctness" L702, L222-291 | Catches undeclared host-walk bug class |
SECS0201 | Template-body channel declaration on a scope that differs from the template's root_scope | 02-templates.md § "Per-instance flat channels" / "Per-instance derived channels" L167, L220 | Enforces the own-root intrinsic channel-source contract |
SECS0301 | Modifier effect on bool channel uses += or *= instead of = (HardOverride) | 03-channels-and-modifiers.md § "bool-channel effect-mode rule" L240-268, L1243 | Hard compile error for bool-channel modifiers |
SECS0302 | Dynamic resolve(<expr>) used without an explicit reads { } block, or reads { } missing a channel the body reads | 03-channels-and-modifiers.md § "reads { } — D-Hybrid rule" L878 | D-Hybrid case 2 (dynamic) |
SECS0303 (warning) | Explicit reads { } declares channels not auto-extracted (over-approximation warning) | 03-channels-and-modifiers.md § "reads { } — D-Hybrid rule" L877 | Keeps author intent and auto-extraction in sync |
SECS0601 | Hash collision during mod-operation merge — two distinct identifier strings hashing to the same FNV-1a value | 06-overrides-and-modding.md § "Identity scheme" L149, § "The merge-pass pipeline" L591 | Phase 3 merger must abort, not warn |
SECS0602 | Duplicate declaration in mod without an inject / replace / try / or_create operation (collides with existing registry entry) | 06-overrides-and-modding.md § "The merge-pass pipeline" L593 | Phase 3 merger aborts on ambiguous redeclaration |
SECS0701 | Unknown type name in a field, callable signature, struct/record field, array element, collection generic argument, or template/scope reference | 07-structured-template-data-and-callables.md § "Diagnostics" | C# source type binding |
SECS0702 | Value initializer does not match the declared SecsTypeRef | 07-structured-template-data-and-callables.md § "Diagnostics" | Structured template field validation |
SECS0703 | method declaration has a non-void return type; use query for read-only typed returns | 07-structured-template-data-and-callables.md § "Diagnostics" | Preserves query/method read/write split |
SECS0704 | Query body performs a command-producing operation | 07-structured-template-data-and-callables.md § "Diagnostics" | Keeps typed query returns read-only |
SECS0705 | Callable body replacement changes parameter or return SecsTypeRef | 07-structured-template-data-and-callables.md § "Diagnostics"; 06-overrides-and-modding.md § "What's NOT overridable" | Signature-stable body replacement |
SECS0706 | Runtime template-field modifier attempts additive/multiplicative effect on a non-numeric structured field | 07-structured-template-data-and-callables.md § "Diagnostics" | Structured field effect discipline |
SECS0032 | Source keyword whitelist/analyzer guard rejects uncommitted SECS keywords (formula top-level declarations, old override, top-level tag, top-level phase/tick_rate, arbitrary direct fire EventName) while permitting ordinary C# collected for metadata | SECS-Compiler-Plan.md § Source keyword whitelist and grammar status, docs/design/README.md | Prevents stale docs/fixtures from silently expanding source syntax |
SECS0803 | Command-producing call appears in a read-only body such as event Condition, activity IsVisible, or policy rule decision evaluation | 10-host-secs-execution-boundary.md § 13.3 | Analyzer/binder target for the host execution-boundary taxonomy |
SECS0804 | Generated code reaches ISecsHostCommands without a command-producing context | 10-host-secs-execution-boundary.md § 13.3 | Analyzer/binder target for command-surface misuse outside command-producing bodies |
SECS0830 | Prev-tick bridge/API read such as ctx.PrevTick(S) targets channel S without track_prev = true | 08-collections-and-propagation.md § Part 4, SECS-Compiler-Plan.md § Diagnostic Code Catalog | Analyzer/binder target once helper/API shape is finalized |
Note on code convention (resolved 2026-04-24): Earlier drafts of doc 06 reused
SECS0301 / SECS0302, colliding with doc 03's HardOverride and D-Hybrid codes.
Per the XX = doc number convention in 00-overview.md § "Diagnostic code convention", doc 06's merger codes are now SECS0601 / SECS0602. The FUTURE_WORK
sweep caught the drift; fix landed the same day.
3. Valenar stand-in bugs
Backlog only — these files must not be edited until the compiler is ready to emit correct output. Each entry cites file:line, what's wrong, the fix shape, and whether anything blocks remediation beyond compiler bring-up.
3.1 Cross-scope template-body channel bugs — resolved 2026-04-26
The own-root channel-source violations where location-rooted Building templates directly
declared settlement channels have been rewritten in both .secs source and
hand-written Generated/ output. Buildings now attach always-on modifiers to
settlement in OnBuilt:
- House, Great Hall, Temple, Training Grounds, Walls, Watchtower, Forge,
Barracks, Housing, and Tavern now use named modifier declarations such as
HouseMoralePresence,WatchtowerDefense, andBarracksGarrison. - Dynamic settlement effects (
HousePopulationCapStack,WatchtowerDefense) are modifier-effect formulas, so their formula output side is discovered fromModifierDeclaration.Effectsinstead ofRegisterFormulaContribution(...). - Farm/IrrigatedFarm/TerracedFarm remain template dynamic channels because
FoodOutputis a location channel and their templateroot_scopeis Location.
Storehouse's old channel int Food = 100/250/500 rows were removed instead of
being converted to modifiers. Food is Accumulative, and modifiers
intentionally do not apply to accumulative stockpile channels. The generic
resource model is now documented in 01-world-shape.md § "Resource amount, capacity, and flow" and 03-channels-and-modifiers.md § "The 6-phase resolution pipeline": Storehouse-style storage targets a separate capacity channel
(FoodCapacity, GoldCapacity, etc.), while systems clamp amount writes
against resolved capacity.
Valenar status: Implemented for food storage on 2026-04-26. Valenar now has
settlement.FoodCapacity, channel int FoodCapacity { kind = Contributed; ... },
Storehouse capacity modifiers, and a production-system clamp against resolved
food capacity. Additional resources can follow the same amount/capacity pair
only when that resource actually needs storage gameplay.
3.2 Modifier lifecycle leaks — resolved 2026-04-26
Source: audit-findings.md § 2 Lifecycle pairing L84-124. The original audit
correctly spotted that permanent modifier bindings needed a lifecycle story.
The committed design is owner/target cleanup, not required source-level
attach/detach pairs:
owneris the lifetime source.targetis the effect anchor.TemplateActivator.Destroyruns the contract's deactivation lifecycle binding, then queuesRemoveAllChannelSources(owner),RemoveAllBindings(owner), andRemoveAllBindingsOnTarget(owner).
The 12-template "missing OnDestroyed" table is now historical. A template
that attaches settlement.add_modifier HouseMoralePresence in OnBuilt
does not leak because the building instance owns the binding. remove_modifier
is reserved for explicit early/custom removal, but it no longer
blocks ordinary template lifetime cleanup.
3.3 Undeclared scope walk — resolved 2026-04-26
location -> settlement is now a declared Valenar edge (walks_to Settlement;
on scope Location) and generated scope declarations use ParentScopeIds.
Farm.CanBuild now returns false for an unowned location instead of using a
permissive fallback.
3.4 Missing frequency in ResourceProductionSystem
Source: audit-findings.md § 8 Systems L291-292. Runs every tick at
O(settlements × locations); no Frequency declared, uses AllByContract (no
distribution). File: examples/valenar/Generated/Systems/Economy/ResourceProductionSystem.cs:34-42.
Also the corpus's only ctx.Host.ReadInt call from a system (:55). Decide
whether direct host reads are idiomatic for ownership-by-field scopes, or whether
this should be factored through host.WalkScope.
3.5 Lower-severity audit findings (6 active items)
Sourced from audit-findings.md. Each is cosmetic or latent; none blocks
compiler bring-up.
GreatHall.cs:30—GetField => 0;switch-free implementation; every otherGetFieldis a switch expression (audit § 8 L255)- 12 Tier-variants never declare a
CanBuildquery — intent is "tier transition bypasses the BuildingCount query" but undocumented (audit § 8 L256) - Dictionary-init syntax inconsistent — Buildings use brace-brace, Features use indexer (audit § 8 L257) — also tracked in Engine Task 19
CelebrationBonusEvent.cs— omitsConditionoverride (relies onSecsEventbase defaulttrue); every other Valenar event with a real gate declares it (audit § 8 L302)Trigger_MoraleGte60.cs:10-11— only trigger that branches oncaptured0.IsNull; silent mode switch (audit § 8 L328)FamineStartsEvent.cs:29— only event that explicitly setsPriority = 0(audit § 8 L303)
3.6 Sync-drift between .secs sources and Generated/ output
Hashes.cshand-picked sentinels — resolved 2026-04-25. The generated stand-ins now use FNV-1a-64ulongconstants instead of hand-picked 32-bit sentinels.MainCharacter.cs:15carriesName = "Hero"— violates the narrative rule that MC must not be called "Hero" (audit § 8 Characters, L282; user memoryfeedback_valenar_genre). Content bug, not engine bug
3.7 Deferred spell-authoring seam
Source: examples/valenar/docs/implementation/pr-docs-codegen-rules.md § Live Behavior Vocabulary.
SpellRoot remains an unresolved Valenar/example design choice, and
KnownSpellsCandidateBuilder remains an internal runtime/lowering bridge.
Revisit both only when a committed future spell-authoring model exists.
Until then, neither should be generalized into live .secs syntax or treated
as the settled long-term shape.
4. Deferred language features
Features explicitly marked not-in-Phase-1 across the design docs. Each entry lists the feature, source, rationale for deferral, and criteria for revisiting.
-
range = 0..100clamp sugar —01-world-shape.md § "range = sugar status"L674-679. Sugar overmin/max; core syntax is committed complete form. Deferred indefinitely; no keyword reserved. Reconsider if author surveys show the two-line form is consistently clunky. Desugars at parse time — purely additive. -
<= Nclamp operator in modifier effects —03-channels-and-modifiers.md § "Effect modes"L172. Compiler rejects. Use channel-declarationmin/maxinstead. Reconsider when a use case demands a modifier-scoped upper cap distinct from the channel-global cap. -
priority Non modifier declarations —03-channels-and-modifiers.md § "Effect modes"L173. Historical proposal retained as a backlog label; the field is reserved but not implemented. Reconsider when HardOverride ordering or stacking-tie-breaker determinism surfaces as a real author need. SECS03XX code to be assigned when it lands. -
decimalchannel type —01-world-shape.md § "Numeric type set"L393. Niche (accounting-grade 28-digit fixed-point). Add as a sixthSecsScalarTypeentry when a real use case emerges. Single-enum-entry addition; no restructuring. -
BigIntarbitrary-precision channel type —01-world-shape.mdL394. Out of scope for Phase 1 and Phase 2 — breaks theO(1)fast-path model and the value-union discriminant. Reconsider only if a shipped game needs it; multi- month project. -
uint/ulong/byte/shortchannel types —01-world-shape.md § "Explicitly rejected"L391-392. Rejected, not deferred.uint/ulongoverlap with clamped signed types (channel int X { min = 0; }).byte/shortare premature optimisation. Listed here only to record the rejection. -
upgradeblock in templates —02-templates.md § "upgrade block (NOT YET IMPLEMENTED)"L694-727. Engine already supportsTemplateActivator.Upgradeas C# API;.secssurface syntax and compiler lowering are missing. Reconsider after Farm / IrrigatedFarm / TerracedFarm tier content is settled. Open shape questions: cost-per-resource vs cost-expression, condition expression vs precondition block,TemplateEntryfield vs standalone registration. -
save_temporary_scope_as(statement-scoped vs chain-scoped) —05-expressions.md § "save_scope_as"L505-506. No runtime plumbing yet; the current runtime only supports chain-scoped saved scopes. Reconsider when nested on-action chains need per-statement scope cleanup. -
every_in_list/random_in_listquery keywords — historical saved-scope-list query proposals. Scope-list storage would need a separateDictionary<ulong, List<EntityHandle>>alongside the flat scopes dict. Reconsider when a content pattern demands lists of saved scopes. -
Interprocedural analysis for formula helpers —
03-channels-and-modifiers.md § "reads { } — D-Hybrid rule"open question L962. A formula that calls a helper method currently treats the helper'sresolve()calls as dynamic (requiring explicitreads { }). Future compiler pass could inline helper bodies or build per-helper read-set summaries. D-Hybrid's "dynamic → explicit" branch covers the interim. Upgrade-compatible — extras inreads { }stay legal once helpers are analysed. -
Singleton constraint on templates — historical proposal retained as a backlog label. Not supported in Phase 1. Reconsider if content needs a compile-time single- instance guarantee.
-
Inline modifier declaration in method bodies — historical proposal retained as a backlog label. Not supported. Use template-level modifier members.
-
expects channelon contracts — historical proposal retained as a backlog label. Contract-level channel-presence declarations. Not supported. -
Aggregate channel declarations (
scope = aggregate) — historical proposal retained as a backlog label. Not supported; content uses dynamic methods with foreach. -
resolve()call-site restrictions — historical proposal retained as a backlog label. Not enforced; revisit when dependency issues arise. -
Compile-time channel-cycle detection (strong) — historical proposal retained as a backlog label. Runtime re-entry check is mandatory; compile-time is advisory-warning-only. D-Hybrid
readsChannelsmetadata is the input a stronger compile-time check could use. -
Dependency-aware channel caching — historical proposal retained as a backlog label. Dynamic sources re-evaluate eagerly; no caching. Host may add dirty tracking on scope-field mutations as an optimisation.
-
distribute falseopt-out — see Engine Task 16 above..secssyntax and runtime handling both missing. Current live docs do not commit an opt-out surface yet. -
Contract inheritance / mixins (
extends) —01-world-shape.md § "contract declarations"L355. Not present in .secs or Generated/. If introduced must flatten at lowering time. -
Event option description localization —
04-behavior.md § "Player-choice events"L543. Strings hand-written in Generated/; the live docs still need a committed localization contract for option descriptions. Option display names are now explicit source strings passed tooptions.Add("Name", Handler); descriptions still need either derived localization keys or a method-based metadata surface. -
Event option timeout / auto-pick —
04-behavior.md § "Player-choice events"L544. Not specified. -
Chained-on-action source syntax —
04-behavior.md § "End-to-end: OnActionDeclarations.cs". Runtime fields exist, but no committed.secssource syntax exists. The current runtime stores these fields as inert metadata only; dispatcher execution and registry link validation remain disabled until the syntax and semantics are deliberately recommitted. If this lands, it must preserve metadata-only on-actions and avoid reintroducing on-action effect bodies. -
Fallback on-action source syntax —
04-behavior.md. Runtime field exists, but no committed.secssource syntax exists. The current runtime stores it as inert metadata only; dispatcher execution and registry link validation remain disabled until the syntax and semantics are deliberately recommitted. Same metadata-only constraint as chained on-actions. -
Old
on_action effectbody migration — resolved for source syntax and generated stand-ins by moving guaranteed behavior into normal subscriber events with early priority (for exampleBuildingCompleteMorale). RuntimeOnActionDeclarationno longer has anEffectdelegate; fallback/chaining fields remain reserved inert metadata with no committed source syntax. -
.secscost X N;authoring syntax — the runtimeactivity_costsslot is committed and live (SecsActivity.Costs,ActivityExecutor.CanPayCosts/DeductCosts,ActivityModPatch.Costs). The.secssource syntax for declaring costs in anactivityblock (e.g.cost stamina 1;) is reserved for a future content-source wave; today, costs are authored by settingCostson the generated activity class directly. See04-behavior.md § "activity Name". The runtime side already supports full-list replacement at registration; only the source-syntax parser and lowering remain to be built. Phase 3 mod parsing must accept the full-list-replacement merge rule for theactivity_costsslot when the source syntax lands. -
Generic non-activity/policy declaration replacement in Phase 3 —
06-overrides-and-modding.md § 8anddocs/adr/ad-0001-runtime-mod-finalization-boundary.md. The shipped runtime finalizer handles activity and policy mods only. The remaining source-level merger work is compiler-owned: parse source sets, extract semantic slots for templates/modifiers/systems/events/on-actions and other non-activity/policy declarations, apply load-order writes, and lower the resulting merged declaration set. Do not implement this as a genericSecsRegistry/ModRegistryruntime merger. -
Compiler lowering for scope methods —
01-world-shape.md § "Methods declared on scopes",04-behavior.md § "Targeted actions", and05-expressions.md § "Host-exposed scope methods". Runtime/generated C# now has theISecsHostCommandsbridge plus generated typed scope command and query facades. Compiler work still needs parsing, declaration emission, type-checking, and lowering for read-only and command-producing scope method calls; known generated call sites should use concrete C# source types, withSecsValuereserved for command payload storage or explicitly erased dynamic/tooling adapters. -
Raw C# helper replacement surface declared inside
.secs—06-overrides-and-modding.md § 10leaves namespace-scoped raw C# helper replacement unsettled. Phase 3 commits only to SECS mod operations (inject,replace,try inject,try replace,inject_or_create,replace_or_create) and namespace isolation for ordinary helper C#. -
Static → dynamic channel conversions through mod operations —
06-overrides-and-modding.md § "Overriding dynamic channels"L287. Base haschannel int X = 5; a laterinject/replacebody haschannel int X { return ...; }. Requires synthesizing / deletingFormula_*.cs. Marked "Phase 3 stretch". -
--strict-conflictsCLI flag —06-overrides-and-modding.md § "Conflict detection"L560. CI-only; fail build on mod-operation slot conflict. Not Phase 3. -
scope:Xcompile-time validation against on-actionprovides—04- behavior.md § "provides scope:X"L765-766. Compiler should check every subscriber event's saved-scope reads match the on-action'sprovideslist. Unchecked today. -
fire on_actioncall-site provides-coverage check —04-behavior.mdL766. Every firing site must save the scopes declared by the on-action'sprovideslist. Runtime dispatch now throws when a declared provided-scope key is absent, but the compiler diagnostic is still required.
4a. Deferred doc hygiene
Cosmetic / labeling cleanup that does not block any commit, scheduled for future doc-hygiene waves. Each row cites the originating wave and lists the docs requiring follow-up.
| Task | Affected docs | Notes |
|---|---|---|
| Per-doc Valenar-illustrative labeling cleanup | docs/design/01-world-shape.md, 04-behavior.md, 05-expressions.md, 08-collections-and-propagation.md, 09-ai-policies-and-activities.md | Per § 7 of docs/design/10-host-secs-execution-boundary.md (Wave B1 added the convention; one caveat added to 01-world-shape.md:5). Full retrofit of per-section "(Valenar example)" caveats is non-blocking and cosmetic; deferred. (OQ-6 routing — see docs/design/10-host-secs-execution-boundary.md § 22.5.) |
5. Design topics still to discuss
Open design questions that are not yet committed. Cluster E and post-cluster topics, system and contract shape decisions, and compiler-internal concerns.
5.1 Modifier lifecycle pattern — resolved owner cleanup
Status: Resolved 2026-04-26; retained here until the next backlog re-index.
Trigger: 12-template leak audit finding (§ 3.2). The finding forced a proper lifecycle decision for permanent modifier bindings created from template lifecycle methods.
Commitment: Owner/target meaning is fixed in 05-expressions.md § "add_modifier": owner is the lifetime source and target is the effect
anchor. Every persistent binding has a non-null owner; global effects use a
real World / Realm / law entity rather than a magic null owner. Template
destruction performs owner/target cleanup in the engine:
RemoveAllChannelSources(owner), RemoveAllBindings(owner), and
RemoveAllBindingsOnTarget(owner).
Result: Source authors attach permanent template-owned modifiers once,
usually in OnBuilt / OnSpawned / OnDiscovered. They do not write paired
OnDestroyed removal just to prevent leaks. remove_modifier remains useful
for early/custom removal, but not for ordinary template
lifetime cleanup.
Mod impact: This is the most mod-friendly option from the old ranking: modifying the attach site cannot accidentally omit the hidden cleanup path because cleanup is keyed by binding owner, not by a second source method the mod author must remember to update.
5.2 ChannelKind.Internal — fourth ChannelKind for compiler-internal channels
Trigger: 01-world-shape.md § "Channel with no source" L618 — "Internal
ChannelKind for purely compiler-internal channels remains a potential future
addition (deferred — no current use case in Valenar)."
Question: Should the compiler be able to declare channels that are not author- facing (intermediate calculations, profiler/debug values, engine-internal caches)?
Referenced: 01-world-shape.md § "Lowering summary" L859 — "Internal flag
is not usable from .secs yet."
Scope: Small. Add the enum entry, specify that authors cannot use it directly, specify the host / compiler-side surface that does.
5.3 Channel-array ordering in Declarations.Channels
Trigger: Cluster E (carried forward). Not explicitly cited in the committed design docs but flagged for decision in the task prompt.
Question: Should ChannelDeclaration[] Channels be ordered alphabetically, in
declaration order (.secs source order), or grouped by ChannelKind?
Scope: Affects diff cleanliness and cache locality at registration time. Pick one at compiler bring-up.
5.4 System declaration topic — frequency required / AllByContractForTick default
Trigger: audit-findings.md § 3 summary finding L396 — ResourceProductionSystem.cs
ships without a Frequency declaration, runs O(settlements × locations) every
tick. Every other system uses AllByContractForTick.
Question:
- Should
frequencybe required on everysystem { ... }declaration? - Should
foreach <entity> in <Contract>in a system body auto-distribute by default (useAllByContractForTick)?
Referenced: 04-behavior.md § "Frequency gating" L180 open question on
arbitrary cadence values, with current source shape
frequency = <TickRate expression>; per 08-collections-and-propagation.md § Part 5.
Scope: Language-level default. Affects every system in Valenar. Commitment.
Mod-friendliness weighting: Mods load untrusted system code. Defaults
that fail-safe under mod use (required frequency, auto-distribute on)
should weight higher than defaults that fail-noisy only for base. A
careless mod-added system without frequency could flood the tick loop;
a mod-added foreach without distribution could spike per-tick cost
to O(entities). The commitment should treat untrusted-mod safety as a
first-class dimension alongside base-author ergonomics. Cross-link
mod-coverage Finding 64.
5.5 Contract-declared methods with zero implementers
Trigger: audit-findings.md § 2 Lifecycle pairing L112-124. Eight methods
are declared on contracts but never implemented by any template:
OnDestroyed, OnDayTick, OnSeasonTick, OnCreated, OnLoaded,
OnClaimed, OnDesignated, OnInteract.
Question: Either the host never invokes these methods (in which case the contract declarations are lies), or it does and every target silently no-ops (in which case the compiler should warn on zero-implementer contract methods).
Scope: Likely a SECS02XX warning diagnostic plus a sweep of the Valenar
contracts to prune unused method names.
2026-04-29 design refinement: Contract method names are game/developer
vocabulary. Lifecycle slot ids are engine vocabulary. activation OnBuilt;
means "invoke the method bound to ContractLifecycleIds.Activation"; it does
not make OnBuilt a reserved engine hook. The same contract mechanism should
support activation OnEquipped;, activation OnCrafted;, or any other declared
void method in another game.
2026-04-29 implementation: Activation and deactivation now both follow this
binding shape. TemplateEntry.Deactivate was removed; destruction resolves
ContractLifecycleIds.Deactivation -> <method id> and invokes the method through
TemplateEntry.Methods, so every lifecycle slot uses one generic
method-dispatch path.
2026-05-01 implementation: Contract callable signature metadata now exists
as ContractMethodDeclaration[]. Registration validates that query rows are
listed in ContractDeclaration.QueryMethodIds and command rows are listed in
ContractDeclaration.MethodIds; this resolves compiler-facing signature drift.
The zero-implementer warning remains open.
5.6 CommandBufferExtensions follow-up surface
Trigger: audit-findings.md § 9 Unused engine APIs L332-342. Seven
CommandBufferExtensions methods had zero call sites in Generated/ at the time
of the audit. The scalar channel-source helper family is now runtime-complete
(RegisterChannelSourceFloat, RegisterChannelSourceLong,
RegisterChannelSourceDouble, RegisterChannelSourceBool) even though current
Valenar content only exercises int sources. Removal helpers remain compiler
targets for source keywords and runtime lifecycle plumbing.
Question: Are these compiler-phase-later targets (required once the
remove_modifiers_by_tag keywords land) or dead engine surface to cull?
Scope: Audit each. RemoveAllBindings is now used by template destruction;
RemoveModifier is compiler-required for remove_modifier and explicit
early/custom removal. RemoveModifiersByTag* tie to a deferred batch-removal
source surface. The typed RegisterChannelSource* helpers are part
of the 5-type scalar surface and are kept even without content-side consumers. The others
need a decision.
5.7 root / prev scope-stack capture semantics
Trigger: 05-expressions.md § "Built-in sigils" L66-67, 03-channels-and- modifiers.md § "Trigger lowering" L871, plus the still-open runtime gap for
captured outer-scope entities
"[NOT YET IMPLEMENTED]". All current Trigger_*.Evaluate signatures accept
captured0 / captured1, but every add_modifier call in Generated/ passes
EntityHandle.Null.
Question: What's the canonical shape for populating the captured slots?
Compiler detects root / prev references in the trigger body and emits
the corresponding captured0: / captured1: arguments at the add_modifier
call site. The concrete rule for which enclosing foreach iteration
variables land in which slot needs spec-level commitment.
Referenced: Trigger_MoraleGte60.cs:9-11 uses the captured0.IsNull ? target : captured0 fallback — silent mode switch that audit § 8 L328 flags
as a bug class.
Scope: Depth-aware rule. At depth 1 prev == root; deeper nesting
needs clearer semantics. Archive docs describe the goal; compiler and
engine both need the rule spelled out before implementation.
5.8 Multi-scope capture for deeper scope expressions
Trigger: 03-channels-and-modifiers.md § "Trigger lowering" open question
L871. The two captured slots cover root + prev at depth 1. Expressions
like root.owner.HasLaw("foo") need a third or fourth entity reference.
Question: Fixed-size captured slots (expand to Captured0/1/2/3) or variable-length array? The first option preserves the struct-based binding layout; the second complicates ModifierBinding but scales further.
Scope: Engine-side struct layout change. Defer until a concrete .secs
body exercises the need.
5.9 provides scope:X type discipline
Trigger: 04-behavior.md § "provides scope:X" L765-766. ProvidedScopes
today is Dictionary<ulong, ulong> mapping scope-name-hash → scope-type-hash,
but the runtime does not interpret the type for non-handle payloads (e.g.,
scope:WasVictory uses .Value == 1 encoding).
Question: Does the language want a first-class bool/int-valued saved
scope, or should EntityHandle.Value continue doubling as a flag carrier?
Scope: Type system decision. Affects SaveScope / GetSavedScope API
shape and every scope:X == literal condition lowering.
5.10 Formula keyword/source form clarification
Trigger: None explicit, but 03-channels-and-modifiers.md § "Formula lowering"
L756 shows formulas come from dynamic template channels or dynamic modifier
effects. Worth confirming.
Question: Can a .secs file declare a top-level formula independently
of any template or modifier (e.g., a shared calculation referenced from two
places)? Current Wave L contract says no: formula is not a committed top-level
source keyword, and every formula delegate is lifted from a specific dynamic
template channel or dynamic modifier effect body.
Scope: Likely no — the Formula_{TemplateName}{ChannelName} naming
convention in 02-templates.md L292 treats formulas as derivative. But worth
deciding explicitly.
5.11 Template Name-vs-identifier rule for identity-only templates
Trigger: 02-templates.md § "Identity-only templates" L793 — can name
accept an expression? Currently rejected. What about other metadata fields
(icon, description, category)?
Question: Fixed set of metadata init fields on TemplateEntry, or a
string/string dictionary for forward compatibility?
Scope: Small. Each new metadata field adds one init field.
5.12 Collection type inference (of <ScopeType>)
Trigger: 01-world-shape.md § "collection" L151-152. .secs sources
elide of <ScopeType>; compiler could require it (typed
CollectionDeclaration row) or continue to infer. Bidirectional consistency
pass between Settlement.collection Provinces and province.walks_to Area
is unchecked.
Scope: Parse-time decision. Affects Declarations.cs — whether
CollectionDeclaration[] is a new top-level array or elided into
Hashes.cs alone.
5.13 walks_to storage — resolved 2026-04-26
ScopeDeclaration now stores ParentScopeIds : ulong[], and Valenar generated
declarations emit every declared walks_to edge including self-walks and the
game-specific location -> settlement ownership edge.
5.14 Scope-level method lowering
Trigger: 01-world-shape.md § "Methods declared on scopes" — scope method
signatures now include typed read-only methods and typed void command methods.
Open compiler catch-up: emit the method declaration table and type-check call
sites against { ScopeId, MethodId, ReturnType: SecsTypeRef, ParameterTypes: SecsTypeRef[] }.
Scope: Compiler parsing/declaration emission, one compiler type-check pass,
and lowering. Existing corpus read sites are the CallIntMethod(…, H.Scope_Location_BuildingCount_TemplateId_Int, …)
calls; Valenar activity source adds DrainStamina, GainXp, AdvanceLead, and
Reveal as command-producing method calls.
5.15 The root_scope = name vs root_scope name grammar — resolved 2026-04-29
The Valenar source now uses the canonical root_scope Character; spelling.
The compiler should reject the equals-sign variant unless a future migration
requires accepting legacy content.
5.16 Raw C# helper replacement attributes
Trigger: Earlier drafts asked whether override accepted trailing
attributes ([Deprecated]). The current design has no single override
operation for SECS constructs; raw C# helper replacement remains unsettled.
Scope: Out of scope for Phase 3 unless 06-overrides-and-modding.md
commits a raw C# helper replacement surface.
5.17 Compiler command flush cadence — resolved 2026-05-01
The compiler-emitted rule is now specified in
05-expressions.md § "Generated command flush contract": generated system,
event, activity, and template contract method bodies flush with
FlushCommands() after every command-producing source statement. Runtime
dispatchers flush on normal body return and discard still-pending commands on
exceptions so command buffers do not leak across system/event/activity/template
method boundaries. Template activation remains activation-only intrinsic
channel-source registration and is flushed at the runtime boundary.
5.18 Event source discriminant
Trigger: 04-behavior.md § "Event registration" L580-581 — "There is no
SystemSource-analog EventSource field on EventEntry. Host-registered
events are not distinguished from SECS-generated ones. Whether that matters
for mod-operation dispatch/provenance is unresolved."
Scope: One new struct field plus the corresponding event dispatch / provenance branch.
5.19 The hot-path 5-minute cache issue doesn't apply here (removed)
(Sanity filter — the prompt's guidance about cache windows in loop tools is not a design topic.)
5.20 Identity-only Name handling for BareX templates
Trigger: 02-templates.md § "Bare templates" L570-571 — BareLocation
registers intrinsic Slots = 4 but the .secs has no declaration for it. "Hand-
written-only artifact — the compiler needs the source of that default,
likely as a scope-level declaration in 01-world-shape."
Scope: Decide whether scope-field defaults live in scope declarations
or in Bare template bodies. Small.
5.21 CanBuild failure-mode convention — resolved 2026-04-26
Undeclared scope walks are SECS0111; generated CanBuild queries do not silently
fall back to true. A declared walk may still return null for this entity's
runtime state, and the query handles that explicitly.
5.22 [CompilerGenerated] attributes on emitted files
Trigger: 02-templates.md § "template<Contract> Name" L85 — "Should the
compiler emit [CompilerGenerated] attributes? Not currently done."
Scope: Adds one attribute to every emitted class. Noise vs signal in diffs / analyzers.
5.23 Source-range metadata (.map sidecar files)
Trigger: 02-templates.md § "Source-comment and docstring conventions"
L640 — "Should the compiler embed source-range metadata (line/col) in a
sidecar .map file? Likely yes; not yet decided. The Roslyn fork already
has infrastructure for pdb-style source maps."
Scope: Debug-trace quality. Roslyn-fork feature; may land naturally as a byproduct of the fork's PDB story.
5.24 Active-time duration variant
Trigger: 03-channels-and-modifiers.md § "Lifetime clauses: owned_by, duration, when, while" commits that
duration + while counts total elapsed binding lifetime even while inactive.
Question: Should SECS eventually support an explicit "active time only"
variant, e.g. duration_active 30 or duration 30 active_only, where the timer
pauses while a while trigger is false?
Scope: Deferred until a concrete content pattern needs it. Do not overload
plain duration; its current meaning is total elapsed binding lifetime.
5.25 Event option source syntax
Trigger: 06-overrides-and-modding.md § 8.4 needs slot-level event option
merge semantics, while doc 04's live lowering shape is BuildOptions(EventOptions options, EventContext context) plus explicit handler methods.
Question: Commit a first-class option { ... } source syntax, or keep
BuildOptions/handler methods as the source model and define option slots by
analyzing options.Add("Label", Handler) under strict rules?
Scope: Compiler parser/binder/lowering, event-option mod slots, generated
provenance, and tests. Until resolved, option {} examples are proposed/future,
not current .secs vocabulary.
6. Audit-findings follow-ups
Cross-reference of every item in docs/design/audit-findings.md against the
categories above. Items already folded into sections § 1–5 are noted; only
lower-severity residuals appear here.
- § 1 API call frequency — informational table, no action items
- § 2 Lifecycle pairing — twelve templates folded into § 3.2; contract methods without implementers folded into § 5.5
- § 3 Scope-walk correctness —
Farm.cs:32folded into § 3.3 - § 4 Command-flush convention — minor inconsistency folded into § 5.17
- § 5 Reflection / dynamic dispatch — zero hits (clean)
- § 6 Non-determinism — zero hits (clean); note on
EventDispatcher.randomfield is engine-side, not Generated-side; out of audit scope - § 7 Host-side leaks — zero hits (clean)
- § 8 Pattern divergence — cosmetic divergences folded into § 3.5 and
§ 5.6; specific call-outs:
House.cs:15explicit-null — resolved by the 2026-04-26 building channel sweepHousing.csnoGetFieldInt— resolved by the 2026-04-26 cost-field sweep; Workshop/AdvancedWorkshop intentionally have no build-cost fields yetGreatHall.cs:30switch-free — § 3.5- 12 tier-variants no
CanBuildquery — § 3.5 - Dictionary-init syntax — § 1 / § 3.5
StorehouseTier2/3cross-scopeH.Foodregistration — resolved by § 3.1 and the 2026-04-26FoodCapacityValenar implementationGranary.csuniqueness — § 3.1 (noted as exemplar)MainCharacter.cs:15Name = "Hero" — § 3.6ResourceProductionSystemno frequency + direct host read — § 3.4 / § 1CelebrationBonusEvent.csnoConditionoverride — § 3.5Trigger_MoraleGte60.cscaptured0 branch — § 3.5 / § 5.7FamineStartsEvent.cs:29Priority = 0 — § 3.5
- § 9 Unused engine APIs — folded into § 5.6 (CommandBufferExtensions
completeness). Candidates for deprecation (
RandomFloat,ReadFloat/WriteFloat/ReadBool/WriteBool,ReadEntity/GetChildren) are also tied to the 5-typeSecsScalarTypeexpansion in § 1. The type set is now expanded; the remaining question is which typed readers earn content-side consumers.
No audit finding stands alone. Every row in audit-findings.md is either
already folded into sections § 1–5, or called out above as a lower-severity
residual.
7. Mod-coverage follow-ups
Findings from docs/design/mod-coverage-audit.md (and any future audits of the
same class — re-qualifying base-scoped rules under the base ∪ mods union).
Each row cites the audit finding number, the affected doc(s), and the fix
status. Findings absorbed inline into the source docs in the 2026-04-24 fix
pass appear with landed 2026-04-24; remaining findings list the doc(s) they
target and the residual decision they require.
| Finding | Severity | Doc(s) | Subject | Status / Fix |
|---|---|---|---|---|
| 1 | DRIFT | 00 | "Override" glossary entry was the only mod concept | landed 2026-04-24 — added "Mod extension" sibling glossary entry |
| 2 | DRIFT | 00 | Active-diagnostics list was silent on cross-source-set firing | landed 2026-04-24 — table re-qualified per code |
| 3 | POLISH | 00 | Status flags didn't separate lowering-contract state from mod lifecycle | landed 2026-04-24 — added clarifying paragraph |
| 4 | BLOCKING | 01, 06 | walks_to storage design unresolved under mods | landed 2026-04-24 — committed multi-edge design, mod scope note added |
| 5 | BLOCKING | 01, 06 | Scope-extension semantics unspecified | landed 2026-04-24 — added "Mod extension of scopes" section |
| 6 | DRIFT | 01 | single-parent scope storage not mod-composable | landed 2026-04-24 — resolved by Finding 4 multi-edge commit |
| 7 | BLOCKING | 01, 06 | Scope methods had no mod extensibility story | landed 2026-04-24 — covered in Finding 5 "Method additions" subsection; host-bridge replacement/extensibility left as open question |
| 8 | DRIFT | 01, 06 | Contract extensibility unspecified | landed 2026-04-24 — added "contracts are not extensible by mods" mod scope note |
| 9 | BLOCKING | 01, 06 | SecsScalarType enum closure unspecified | landed 2026-04-24 — added closed-surface mod scope note; reserves SECS0102 for the diagnostic |
| 10 | BLOCKING | 01, 06 | kind = requirement / mod channel operations | landed 2026-04-24 — added cross-source-set qualifier; clarified channel metadata is not replaceable by mods |
| 11 | DRIFT | 01 | Internal = false on user channels — base-only | landed 2026-04-24 — added mod scope note |
| 12 | BLOCKING | 01, 06, 00 | FNV-1a-32 → FNV-1a-64 commit | design landed 2026-04-24; runtime/generated code migrated 2026-04-25 |
| 13 | DRIFT | 01 | SECS0107 cross-mod scope | landed 2026-04-24 — added mod scope note |
| 14 | DRIFT | 01, 06 | SECS0108 cross-assembly three-case split | landed 2026-04-24 — added three-case mod scope note |
| 15 | BLOCKING | 01 | identifier width for mod-added entity ids | design landed 2026-04-24; runtime/generated code migrated 2026-04-25 |
| 16 | DRIFT | 01 | Hand-picked hash sentinels block mod detection | landed 2026-04-24 (prior pass) |
| 17 | BLOCKING | 01, 06 | Registry collision policy underspecified | landed 2026-04-24 — committed compile-time-merger-only policy; runtime DLL sideload out of scope |
| 18 | DRIFT | 01 | Invariant list base-only | landed 2026-04-24 — added per-invariant mod scope notes |
| 19 | BLOCKING | 02, 06 | Modifier auto-detach under mod operations | resolved 2026-04-26 — owner/target cleanup is the committed lifecycle contract |
| 20 | DRIFT | 02, 06 | Formula hash naming under mod-added templates | open — tied to own-root channel-source enforcement |
| 21 | BLOCKING | 02 | Template-method dictionary-init canonicalisation | open — already tracked in § 1 row 19 / § 3.5 |
| 22 | DRIFT | 02, 06 | CanBuild permissive fallback under mod walks | resolved 2026-04-26 — undeclared walks are SECS0111; null declared walks are explicit query logic |
| 23 | BLOCKING | 02, 06 | Bare templates under mods | landed 2026-04-30 — doc 02 specifies synthesized Bare templates; doc 06 exposes bare_template_defaults slots |
| 24 | DRIFT | 02, 06 | name = "X" template metadata under mod operations | landed 2026-04-30 — template_name_metadata is a per-field replacement slot in doc 06 |
| 25 | DRIFT | 02, 06 | reads { } auto-extraction under mod operations | open — merger must rewrite RegisterFormula readsChannels: |
| 26 | BLOCKING | 02, 06 | upgrade block mod-operation granularity | open — pending upgrade keyword landing |
| 27 | DRIFT | 02, 06 | TemplateEntry.Methods mod-added contract methods | resolved 2026-05-01 — contracts are not extensible, and ContractMethodDeclaration[] must match QueryMethodIds/MethodIds |
| 28 | BLOCKING | 03, 06 | Modifier declarations duplicate across mods | open — needs SECS0602 enumeration in doc 06 |
| 29 | DRIFT | 03, 06 | EffectMode enum closure unspecified | landed 2026-04-24 — doc 03 now defines EffectMode as a closed enum and doc 06 lists it as non-overridable |
| 30 | BLOCKING | 03, 06 | Stacking policy under mod-added attachers / operation changes | open — max_stacks change rule unspecified |
| 31 | DRIFT | 03 | Plain C# TagId constants and short-name resolution under mods | open — exported SDK / alias-resolution rule unspecified |
| 32 | BLOCKING | 03, 06 | Template-scoped modifier mod-operation identity | open — needs cross-mod walk-through |
| 33 | BLOCKING | 03, 06 | reads { } D-Hybrid under mod formulas | open — ValidateDependencies cross-mod re-run |
| 34 | DRIFT | 03 | SecsFormulaReadSetViolation cross-mod | open — --strict-readsets flag |
| 35 | BLOCKING | 03 | 6-phase HardOverride ordering under mods | landed 2026-04-24 — last-applied wins by modifier-binding-registration order; explicit priority remains deferred |
| 36 | DRIFT | 03 | translation = localization under mods | open — mod-localization YAML directory convention |
| 37 | BLOCKING | 04, 06 | Phase-extension story under mods | open — reject or spec mod.json phase_order |
| 38 | DRIFT | 04 | SystemSource.Host mod dispatch | open — mod scope note needed |
| 39 | BLOCKING | 04 | Event trigger on mod on-actions / load order | open — mod.json load_after interaction |
| 40 | DRIFT | 04, 06 | Event condition under mod operation changes | landed 2026-04-30 — condition is now a query slot: query bool Condition() { ... } |
| 41 | BLOCKING | 04, 06 | Player-choice event option replacement | landed 2026-04-30 — options are explicit-name registrations in BuildOptions; handler methods are per-option slots |
| 42 | DRIFT | 04 | Option descriptions localization under mods | open — tied to Finding 36 |
| 43 | BLOCKING | 04 | EventEntry array index identity | open — sort-by-EventId rule |
| 44 | DRIFT | 04 | No EventSource discriminant | open — already in § 1 row 22 |
| 45 | BLOCKING | 04, 06 | On-action mode priority under mod events | landed 2026-04-30 — lower priority values run earlier; 0-99 ordinary content, negative reserved for early platform/compatibility subscribers |
| 46 | BLOCKING | 04, 06 | provides scope:X mod extensibility | open — tie to fire-site provides-coverage check |
| 47 | DRIFT | 04 | Chained / fallback on-actions under mods | open — pending syntax commit |
| 48 | DRIFT | 04, 06 | Activity mod-operation per-slot granularity | landed 2026-04-30 — class-based activity slots are enumerated in doc 06 (IsVisible, IsTargetValid, CanStart, lifecycle methods, cooldown/AI fields) |
| 49 | BLOCKING | 04, 06 | Targeted-activity candidate validation replacement | superseded 2026-04-30 — old candidate-filter block wording is replaced by the IsTargetValid activity slot |
| 50 | DRIFT | 05 | <scope> sigil from mod-declared scope | open — SECS0501 proposed |
| 51 | DRIFT | 05 | add_modifier cross-mod identity | open — error-message provenance |
| 52 | BLOCKING | 05, 06 | foreach entity in Contract under mod templates | landed 2026-04-30 — doc 05 states contract iteration includes mod-authored templates implementing the contract |
| 53 | DRIFT | 05 | Query keywords under mod scope-graph extensions | open — tied to Finding 5 |
| 54 | BLOCKING | 06 | "What's NOT overridable" enumeration incomplete | open — extension to doc 06 list |
| 55 | BLOCKING | 06 | FNV-1a-32 vs 64 mod-scale (Doc 06 side) | design landed 2026-04-24; runtime/generated code migrated 2026-04-25 |
| 56 | BLOCKING | 06 | Conflict detection slot enumeration | open — half-day extension to doc 06's table |
| 57 | BLOCKING | 06 | Merge-pass pipeline missing add-extension case | open — extend step 3 of pipeline |
| 58 | BLOCKING | 06 | Hash collision case table cross-mod additions | open — explicit four-case table |
| 59 | DRIFT | 06 | Load-order resolution cycle policy | open — launcher manifest validation |
| 60 | DRIFT | 06 | Base as load order 0 semantics | open — explicit reservation rule |
| 61 | DRIFT | FUTURE_WORK | "Not a plan" metadata + mod-item admission | landed 2026-04-24 — added § 7 (this section) and updated maintenance preamble |
| 62 | BLOCKING | FUTURE_WORK | 3.2 Modifier lifecycle leaks — mod inheritance | superseded 2026-04-26 — owner/target cleanup makes paired source teardown unnecessary |
| 63 | BLOCKING | FUTURE_WORK | 5.1 Modifier lifecycle pattern — mod-friendliness ranking | resolved 2026-04-26 — chosen pattern is engine owner/target cleanup |
| 64 | DRIFT | FUTURE_WORK | 5.4 System declaration defaults — mod audit weighting | landed 2026-04-24 — added mod-friendliness weighting to § 5.4 |
The 2026-04-24 fix pass landed 22 findings inline (1, 2, 3 in doc 00; 4, 5, 6,
7, 8, 9, 10, 11, 13, 14, 17, 18 in doc 01; 61, 62, 63, 64 in this file; plus
12, 15, 16, 55 from the prior cascade pass). Later passes also closed 19, 23,
24, 40, 41, 45, 48, 49, and 52. Rows still marked open remain to be absorbed
into docs 02-06 by separate fix passes.
Completed
| Task | Landed | Notes |
|---|---|---|
Expanded SecsScalarType from 3 entries (Int, Float, Bool) to 5 entries (Int, Long, Float, Double, Bool) | 2026-04-25 | Shared scalar enum now covers channels, scope fields, template fields, commands, formulas, and modifier effects. |
Added ResolveLongFast / ResolveDoubleFast and matching slow result paths to ChannelResolver | 2026-04-25 | Numeric resolver paths use target-typed additive/override slots and DoubleValue multiplicative factors. |
Added typed ReadLong / WriteLong / ReadDouble / WriteDouble and channel re-entry methods to host interfaces | 2026-04-25 | Valenar, character-trainer, benchmarks, and test hosts implement the expanded interface. |
Expanded Command and ModifierEffect payload slots to IntValue, LongValue, FloatValue, DoubleValue, BoolValue | 2026-04-25 | Multiplicative modifier effects always use DoubleValue; FloatValue is only an actual float value. |
| Expanded channel/template-field clamp slots to cover int, long, float, and double | 2026-04-25 | Bool remains unclamped by design. |
Added EffectMode.HardOverride and inserted the HardOverride phase into channel and template-field resolution | 2026-04-25 | Accumulative channels skip additive, multiplicative, and HardOverride phases. |
| Added typed scope-field increment helpers for long, float, and double | 2026-04-25 | increment is now helper-backed for all numeric scope-field scalar types; bool remains invalid. |
| Committed modifier owner/target lifecycle cleanup | 2026-04-26 | owner is lifetime source, target is effect anchor; template destruction removes owned bindings and bindings targeted at the destroyed entity. |
| Removed special template deactivation delegate path | 2026-04-29 | Deactivation now uses ContractLifecycleIds.Deactivation -> MethodId through TemplateEntry.Methods, matching activation and preserving developer-owned method names. |
| Replaced flat saved scopes with dispatch frames | 2026-05-01 | FireOnAction pushes/pops frames, nested dispatches inherit snapshots without leaking writes, deferred choices execute inside their captured snapshot, and runtime validates declared provides keys before subscriber dispatch. |
| Specified generated command flush contract | 2026-05-01 | Systems, events, activities, and template contract methods flush after every command-producing source statement via FlushCommands(); runtime boundaries prevent pending command leakage. Template activation remains activation-only intrinsic channel-source registration. |
Added SecsTypeRef, SecsTypeDeclaration, type-reference validation, and SecsTypeRef callable metadata | 2026-05-02 | Scope methods, contract methods, template fields, and typed query registration now describe first-class SECS types instead of scalar-only SecsValueKind metadata. |
| Added typed template-query registration and dispatch | 2026-05-02 | Templates register concrete TemplateQuery<TArgs,TResult> delegates; query returns no longer use SecsValue. |
| Refactored Valenar content/host feature generation around structured placement profiles and placement results | 2026-05-02 | Feature templates author FeaturePlacementProfile, expose EvaluatePlacement(Location candidate, FeaturePlacementInput input), and host placement applies global budgets, spacing, and conflicts after local scoring. |
| Completed Valenar Generated feature-placement query/profile lowering stand-ins | 2026-05-02 | Generated feature templates expose placement profiles and register Feature.EvaluatePlacement(Location, FeaturePlacementInput):FeaturePlacementResult through typed query adapters; the host consumes the generated evaluator and no longer carries duplicate placement model definitions. |
| Activity/policy startup mod finalization runtime | 2026-05-12 doc sweep | Shipped before this sweep: ModRegistry plus SecsRegistry.RegisterActivityMod, RegisterPolicyMod, and FinalizeModRegistration() merge activity/policy mods once at startup. The remaining generic source-set merger is compiler-owned Phase 3 work. |
Closed Wave 8 — save/load durability for ScopeSlotStore and ActivityRunStore, migration discipline, and tooling hardening | 2026-05-08 | Shipped APIs: ScopeSlotStore.Snapshot() returns IReadOnlyList<SlotSnapshot>; ScopeSlotStore.Restore(snapshots) rehydrates a fresh store with hard validation (SECS0820 unknown kind / non-empty store, SECS0821 element type mismatch). ScopeSlotStore.EnumerateSlotKeys() lists all (scope, kind) pairs without materialising payloads. ActivityRunStore.Restore now validates schema id (SECS0811) and lane id (SECS0812) in addition to activity id (SECS0810). PolicyDispatcher adds opt-in EnableTrace / LastTrace / PolicyRuleTrace / PolicyRuleTraceEntry / PolicyDispatchAction for debug tooling. Diagnostic codes SECS0810-0812 and SECS0820-0821 reserved on RegistryDiagnosticCode; the legacy SECS0820 (Phase 2 binder, ScopedList<T> indexed access) renumbered to SECS0822. Docs 04, 06 § 8.10, 09 updated; SECS-Compiler-Plan diagnostic catalog updated. |
| Closed Wave D/C — runtime activity provenance and save/load durability | 2026-05-13 | ActivityRun and ActivityRunState now carry ActivityRequestOrigin; ToState() / Restore(...) round-trip it. Player queue starts build player-origin requests while preserving explicit activity-clock ticks. PolicyDispatcher stamps direct calls as Policy and normalizes CallBest candidates to Policy at the dispatch boundary. PolicyExecutor static selector producers emit policy-origin requests. Origin remains runtime/save provenance, not a required UI projection. |