SECS Core Concepts
The engine is game-agnostic. It provides mechanisms; the game (via .secs content + Generated/ code) defines the data. These seven concepts together form the SECS programming model — read docs/design/00-overview.md as the current entry point, then follow the numbered design docs for the live contract.
The seven definitions
| Concept | What it is | Lifecycle |
|---|---|---|
| Template | Defines what entities ARE. Static. Registers own-root intrinsic channel sources, implements contract queries (CanBuild, CanEquip, etc.), and implements contract void methods (OnBuilt, OnEquipped, etc.). Contract lifecycle bindings choose which void methods the runtime calls automatically. | Registered at startup. Looked up by FNV-1a-64 hash of the template's canonical id string (valenar:template/<owner>/<name> colon-slash; see docs/design/01-world-shape.md § "Canonical id string format"). The matching hash lives as H.<TemplateName> in Generated/Hashes.cs. When a name is dual-use (Wood, Stone, Gold are both Settlement scalar channels AND Resource templates), Wave 5b splits the constant into H.<Name>_Channel and H.<Name>_Template so the channel and template hashes are distinct and the engine no longer needs to disambiguate by usage site. |
| System | Defines what HAPPENS each tick. Non-static (per-tick instance). Frequency-gated, phased. Iterates entities via AllByContractForTick (zero-alloc ref struct). | Runs in pipeline order during TickContext.Tick(). |
| Event | Defines what happens WHEN. Pulse-triggered, on_action-triggered, or player-choice-triggered. | EventDispatcher evaluates triggers per-tick; player choices park on PendingChoice. |
| Modifier | Reusable effect bundle. Bindings + triggers + duration + stacking policy + decay. Can be ReApplyMode.Refresh, Stack, or Reject. | Attached to entities by host or by OnAction. Decay handled by ModifierBindingStore. |
| Formula | Dynamic value used by an intrinsic channel source or modifier effect at resolution time with owner context. Has access to scope walks. | Evaluated lazily during channel resolution; result cached in ChannelCache until invalidation. |
| Scope | Hierarchy node. Walked via ISecsHostReads.WalkScope. Host defines parent-child relationships (e.g., Empire → County → Province → Building). | Static hierarchy maintained by host; engine never mutates scope graph. |
| Mod operation / slot merge | Layered content change expressed with inject, replace, try inject, try replace, inject_or_create, or replace_or_create. Operations target compiler-owned semantic slots; later load-order writes win for the same slot. | Resolved by the compiler pre-binding merge pass, with activity/policy runtime finalization as the current executable subset. |
Entity & channel flow (the resolution pipeline)
- Registration. Host calls
SecsModule.Initialize(registry)at startup — templates, systems, modifiers, contracts, scopes, on_actions, activities, and policies all land inSecsRegistry. - Activation. Host calls
Activate(handle, contractId, templateId)for each entity. Engine looks up the template, attaches channel sources, then invokes the contract'sContractLifecycleIds.Activationbinding if that binding points at an implemented void method. Method names such asOnBuiltare game vocabulary, not hard-coded engine hooks. - Resolution on demand. When the host or a system reads a channel,
ChannelResolutionruns the 6-phase pipeline documented indocs/design/03-channels-and-modifiers.md:- base — read host base for
Base/Accumulative, or start at zero and add own-root intrinsic channel sources forContributed - additive — sum additive contributions (
+5,-2) - multiplicative — apply multiplicative contributions (
*1.2,*0.85) - HardOverride — last-applied override replaces the post-multiply value
- clamp — apply min/max clamps from declarations
- return — final value, written into
ChannelCachefor the current tick
- base — read host base for
- Dynamic formulas re-evaluate every read using owner's current state (their other resolved channels, scope walks, etc.) — they bypass the cache where appropriate.
- Dirty tracking. When a channel changes,
DirtySetrecords it. At end of tick,SyncDirtywrites back to host viaISecsHostWrites. - Tick pipeline. Systems run in phase order (Pre → Main → Post). Each system iterates its contract's entities. Load distribution spreads expensive per-entity work across multiple ticks via
AllByContractForTick.
Critical pattern: Templates are data
Host data and engine channels are deliberately separate. Base and Accumulative channels read host-owned fields as their source of truth. Contributed channels start at zero; the host field, when present, is only a cache that SyncDirty writes after resolution.
Template-body channel declarations lower to compiler-emitted channel sources on the template activation root. They are not a generic anonymous contribution mechanism, and they never push into a different scope. Cross-scope influence is expressed through named modifiers so ownership, teardown, tooltips, and mod operations all have a stable target.
Where to look for examples
Terminology split: keep
statfor player-facing UI concepts (the Stats rail tab, stat tooltips, "No pinned stats" empty states, stat-category filters, file names likeStatsExpansion.tsx/statCategories.ts); usechannelfor engine/runtime plumbing AND the data layer (ChannelResolver,ChannelCache,ChannelSourceStore,RegisterChannelSource, thechannelkeyword in.secssource, DTOchannel: stringfields, internal tip-IDs likechannel:Food). Do not blindly global-rename — the prior global Stats→Channels sweep over UI labels was the wrong direction and had to be reverted; UI says "Stats", the data/engine layer says "channel".
src/SECS.Engine/Resolution/ChannelResolution.cs— the current six-phase channel pipelinesrc/SECS.Engine/Pipeline/TickContext.cs— system execution ordersrc/SECS.Engine/Events/EventDispatcher.cs— event evaluationsrc/SECS.Engine/SecsRegistry.cs— definition registration, validation, and shipped activity/policy mod finalizationexamples/valenar/Generated/Templates/— concrete template patterns (the spec for what the SECS compiler will eventually emit)examples/valenar/Generated/SecsModule.cs— registration entry point pattern