Skip to main content

SECS — Scripting Engine C Sharp

A game-agnostic scripting and modding engine inspired by Paradox games (CK3, EU4, Victoria 3). SECS uses modified C# as its scripting language — .secs files are real C# with extensions for game scripting. The SECS compiler transpiles them to standard C# that integrates with the engine runtime.

Status: The runtime engine is functional, including the 6-phase HardOverride channel pipeline and activity/policy startup mod finalization. The Roslyn fork is present as secs-roslyn/ with Phase 0 complete. The SECS compiler surface is not yet built — all Valenar generated code is still hand-written. See TASKS.md for current status.

Architecture

src/
SECS.Abstractions/ Shared types (EntityHandle, Command, interfaces) — netstandard2.1
SECS.Engine/ Runtime engine (channel resolution, modifiers, pipeline, events)
SECS.Localization/ YAML-based localization provider

examples/
valenar/ Colony builder reference game (ASP.NET Core SignalR + React/Vite)
character-trainer/ Smaller training-loop example
map/ Map-generation prototypes (mapv10 is the active continent-scale direction)

The engine is game-agnostic. Channels, modifiers, contracts, scopes, and templates are all defined by the game — SECS provides the mechanisms.

Documentation

Start with the Design Reading Guide. That is the entry point for the live SECS docs and sends first-pass readers on to 00 — Overview. Historical, audit, and backlog material remains available under docs/, but the live design path is the authoritative reading route.

For a local docs portal, install the Docusaurus dependencies once:

npm install --prefix docs-site

Live authoring stays on a fixed loopback-only dev server:

npm run dev --prefix docs-site

That serves the portal at http://127.0.0.1:4174/secs/.

The stable personal-portal mode is build-first preview on loopback:

bash scripts/run-docs-portal.sh

That rebuilds the site, serves the built docs at http://127.0.0.1:4173/secs/, and starts a small loopback landing page plus zellij proxy at http://127.0.0.1:4180/. The landing page links to the docs portal and proxies non-root requests to the existing zellij web client on http://127.0.0.1:8082, with /zellij as the stable entry path. The underlying manual commands are npm run build --prefix docs-site, npm run serve --prefix docs-site, and npm run check --prefix docs-site.

The Docusaurus portal is built from generated publish content under docs-site/content/. Do not edit that generated directory directly; edit the source docs in docs/, examples/valenar/docs/, examples/map/mapv10/, .claude/rules/, or .github/, then rerun npm run check --prefix docs-site.

The portal syncs every Markdown file under docs/ and examples/valenar/docs/. For mapv10, it syncs the top-level mapv10 Markdown files plus the documentation-bearing subtrees (docs/, generator/, viewer/, schema/, viewer/src/{data,events,renderer,ui}/, prompts/, and verification/visual-foundation-audit/) without crawling generated map artifacts or dependency folders. npm run dev --prefix docs-site starts a watcher that resyncs source-doc changes while Docusaurus is running, so edits and newly added pages reload without restarting the dev server. npm run build --prefix docs-site and npm run check --prefix docs-site perform a fresh sync before building. For one-off manual syncs, run npm run sync --prefix docs-site.

Sidebars are generated from the synced doc tree by docs-site/sidebars/auto.js. Keep only high-level grouping rules there; do not hand-maintain leaf pages in the sidebars. New docs automatically appear in the left menu under their source-tree group, with unknown paths falling into an Other section until the grouping rules are intentionally refined.

On Linux, install the systemd user service for login-time autostart with:

bash scripts/install-docs-portal-user-service.sh
systemctl --user enable --now secs-docs-portal.service
systemctl --user stop secs-docs-portal.service
systemctl --user status secs-docs-portal.service

Use systemctl --user start secs-docs-portal.service for a one-shot manual start after installation. The installer writes ~/.config/systemd/user/secs-docs-portal.service with absolute paths for this checkout and the current npm and Node binaries; rerun it if the repo path or Node install changes. While the user service is running, its lifecycle hook points the tailnet root / at the repo-owned landing page so the base domain shows two clear options: Zellij and SECS docs. The landing page keeps zellij reachable at /zellij, while the docs portal stays on /secs/. When the user service stops, the lifecycle hook restores the old direct-zellij root route and removes only /secs. If Tailscale is missing or temporarily unavailable, the loopback docs portal still starts normally and only the tailnet route sync is skipped. If you want the user service available without an interactive login, enable linger separately with loginctl enable-linger "$USER".

For private access from another computer on the same tailnet, the user service now manages the /secs mount automatically during start and stop. The helper remains available for manual resync, ad-hoc use without the systemd service, and troubleshooting:

bash scripts/docs-portal-tailscale.sh enable
bash scripts/docs-portal-tailscale.sh url
bash scripts/docs-portal-tailscale.sh status
bash scripts/docs-portal-tailscale.sh disable

Those manual helper commands still manage only /secs. They do not change the root / route, which means users can continue to use enable and disable as the existing /secs-only control surface. The systemd service lifecycle hooks are the only place that swaps the tailnet root between the landing page and direct zellij. The resulting private URL shape for docs remains https://<host>.ts.net/secs/. This remains tailnet-only private access; it does not enable Funnel, public hosting, Caddy, nginx, or a broader reverse-proxy layer.

#DocumentWhat it covers
00OverviewLowering-contract entry point, glossary, reading order, diagnostic map
01World ShapeScope declarations, contracts, top-level channel declarations, the H.* hash convention
02Templatestemplate<Contract> Name { }, fields, intrinsic/dynamic channels in templates, CanBuild, lifecycle methods, bare templates
03Channels and ModifiersModifiers, formulas, triggers, 6-phase target resolution recap, reads {}, ValidateDependencies()
04BehaviorSystems, events (pulse + player choices), on-actions, activities, policies
05ExpressionsScope sigils (@this/@root/…), resolve/increment/add_modifier/fire/save_scope_as, foreach, query keywords (any/every/count/random/first)
06Mod Operations and Moddinginject / replace operation model, load order, typed identity, FNV-1a identity, slot schema, AST-level merger, conflict detection
07Structured Template Data and CallablesC# source type binding, generated SecsTypeRef metadata, structured template fields, typed query returns, feature placement query contract
08Collections and PropagationScopedList<T>, ScopedDictionary<TKey,T>, TemplateId, tags, modifier propagation, previous-tick bridge reads, aggregate channels, system phase/frequency slots, registry-only recipes
09AI Policies and ActivitiesPolicies, utility-AI scoring, activities, runtime player slots, mod injection examples
10Host <-> SECS execution boundaryBoundary classification + assembly placement
Fork DecisionRoslyn fork vs source generator strategy
ADRs under docs/decisions/ and docs/adr/Architectural decisions that explain why the live design contract looks the way it does. Use alongside the numbered design docs when you need rationale rather than lowering detail.

Key Concepts

  • Templates define what entities ARE (static, contribute channels, lifecycle methods)
  • Systems define what HAPPENS each tick (non-static, frequency-gated, phased)
  • Events define what happens WHEN (pulse triggers, on_action hooks, player choices)
  • Modifiers are reusable effect bundles that modify channel values
  • Channels resolve through the implemented 6-phase target pipeline: base, additive, multiplicative, HardOverride, clamp, return.
  • Formulas are dynamic channel contributions and modifier effects evaluated at resolution time
  • Mod operations let layered content use inject, replace, try inject, try replace, inject_or_create, and replace_or_create against semantic slots. Activity/policy mods finalize at runtime startup; the full source-set merger for all declaration kinds is Phase 3 compiler work. Later writes win by load order, with conflicts reported for tooling.

Valenar Example

The primary example game is Valenar — a colony builder where a single MC explores an Estonia-sized region while the settlement survives demonic raids. It demonstrates:

  • Region / Area / Province / Location scopes plus settlement and MC entities
  • 11 building templates with costs, CanBuild checks, and formula-driven channels
  • 3 systems: TaxCollection, FoodConsumption, PopulationGrowth (phased, load-distributed)
  • 3 events: DemonRaid, PlagueSpreads, CelebrationBonus
  • Dynamic formula channels (Farm scales with Fertility, Watchtower scales with Garrison)
  • Modifier stacking policies (VictoryRally stacks x3, DemonDread is unique)
  • ASP.NET Core SignalR server plus React/Vite client with Paradox-style tooltip work in progress

Building

dotnet build SECS.sln

Targets: netstandard2.1 (engine libraries, Unity-compatible), net8.0 (examples). Three test projects exist: tests/SECS.Engine.Tests/, tests/Valenar.Host.Tests/, tests/Valenar.Server.Tests/. Together they currently green ~580 tests.

Running Valenar

The Valenar example has a .NET server (SignalR) and a React client (Vite). During development, run both in separate terminals.

Server (SignalR hub on port 5062):

dotnet run --project examples/valenar/Server --urls http://localhost:5062

Client (Vite dev server on port 5173, proxies /gamehub to the server):

npm run dev --prefix examples/valenar/Client

Open http://localhost:5173 in your browser.