Generated Code Authority
Valenar compiler-owned schema output lives under
examples/valenar-v2/Generated/obj/**/SecsGenerated; Endgate's generated
project uses examples/endgate/Generated/ for the same authority model. Their
project builds invoke secsc over the paired Content/ tree and compile
generated C# from obj/**/SecsGenerated.
Tracked source files under those Generated/ directories are retained
checked-in non-schema surfaces only. They may reference compiler-emitted IDs,
expose explicit host runtime metadata, or carry temporary retained debt pending
an ABI closure, but they must not construct schema row families, emit Hashes,
or replace *.Generated.SecsModule.BuildSchema(). Do not hand-edit emitted
files or reintroduce checked-in schema authority.
Why both Content/ and Generated/ exist
examples/valenar-v2/Content/*.secs— the compiler-consumed source of truth for Valenar schema-owned rows.Valenar.V2.Generated.csprojin the Valenar generated project — invokessecsc build --generated-namespace Valenar.V2.Generatedand compiles emitted C# fromexamples/valenar-v2/Generated/obj/**/SecsGenerated.examples/valenar-v2/Generated/ValenarHostIds.cs— the only retained checked-in Valenar.csfile. Today it contains onlyFailureReasons.StaminaExhaustedas a host runtime failure-reason ID; it is true host-owned and not schema/catalog authority. Scoped collection IDs are compiler-owned schema rows emitted fromContent/. Valenar catalog/config/helper outputs such asActivityDefinitions.g.cs,ResourceDefinitions.g.cs,RecipeDefinitions.g.cs,ItemDefinitions.g.cs,SkillProfiles.g.cs,FeatureDefinitions.g.cs,TravelEncounterDefinitions.g.cs, andValenarConfigDefaults.g.csare compiler-emitted from metadata inContent/.examples/endgate/Content/*.secs— the compiler-consumed source of truth for Endgate schema-owned rows.examples/endgate/Generated/Endgate.Generated.csproj— invokessecsc build --generated-namespace Endgate.Generatedand compiles emitted C# fromobj/**/SecsGenerated.examples/endgate/Generated/ItemDefinitions.csandWaveScripts.cs— retained non-schema catalog helpers. They are not fallback schema authority.
When you add or change schema-owned game content, edit the .secs source or the
compiler, then build the generated project. Do not patch emitted schema rows by
hand. When you add or change a retained checked-in surface, keep its authority
explicit: true host-owned metadata stays narrowly scoped, temporary retained
debt must say that it is debt, and nothing there may become hidden schema or
catalog authority.
What "matches the compiler" looks like
The emitted patterns in obj/**/SecsGenerated are not arbitrary — they are the
contract the compiler must satisfy. For Valenar and Endgate schema-owned rows,
change Content/ or the compiler, then regenerate by building the generated
project.
- Templates lower to compiler-emitted
TemplateRowentries. Template rows carry the template id, contract id, and dense range metadata; behavior is reached through contract/activity host handlers rather than v1TemplateEntryself-registration. - Channel declarations lower to compiler-emitted
ChannelRowentries with id, scalar type, mode, source field id, default/clamp metadata, contract id, andtrack_prev. - Modifier declarations lower to compiler-emitted
ModifierRowplusModifierEffectRowdense ranges. Effects carry channel id, phase, operation, static delta, or formula id. - Hashes live in compiler-emitted
Hashes.g.csas typed-id static fields created from canonical id strings (which hash through FNV-1a-64). Every named entity (template id, channel id, modifier id, system id, event id, activity id, policy id, need id, selector id, rule id, scope id, contract id) gets a stable typed id. The canonical id format the compiler emits is owner-less colon-slash with the declared name reproduced verbatim:<namespace>:<kind>/<Name>(seedocs/design/01-world-shape.md § "Canonical id string format",docs/design/06-overrides-and-modding.md § 4.2, anddocs/decisions/ADR-0003-canonical-id-grammar.md). Sub-ids are emitted as dotted-suffix forms (valenar:scope/Building.scope-field:JobsFilled,valenar:template/MainCharacter.channel-modifier:Attack) or kind-prefixed forms (contract-method:valenar:contract/Building.OnBuilt():void,event-option:valenar:event/RoadsideEncounter.OptionInvestigate). Type ids are namespace-less (type/FeatureDiscoveryState). Theon_actionname is the one segment emitted in snake_case (valenar:on_action/endurance_drill_completed). Owner-segmented snake_case top-level forms (valenar:channel/settlement/x), bare-name, and dot-form (valenar.channel.x) hashes are not live identity targets; the owner-segmented form was never implemented (ADR-0003). Hash collisions are caught at registration.- Dual-use commodity split. When a name is used as BOTH a Settlement scalar channel AND a Resource template id (Wood, Stone, Gold), the compiler emits the two as distinct constants from distinct canonical strings:
H.Channel<Name>fromvalenar:channel/<Name>andH.Template<Name>fromvalenar:template/<Name>(for exampleH.ChannelWoodfromvalenar:channel/WoodandH.TemplateWoodfromvalenar:template/Wood). The constant name carries the kind prefix; there is no_Channel/_Templatesuffix and no owner segment. Every callsite picks the role explicitly through the kind-prefixed constant. Channel-only commodities (Food, Metal, Population) emit only theH.Channel<Name>constant (e.g.H.ChannelFoodfromvalenar:channel/Food). - Mod-local rule / need / selector ids. Rules and other policy-substructure ids defined inside a mod live in the compiler-emitted mod file under
obj/**/SecsGenerated/Mods/<Name>Mod.cs(not inHashes.g.cs), use the mod's source-set namespace, and are emitted as kind-prefixed sub-ids that name the owning policy in the owner segment:rule:<source_set>:policy/<PolicyName>.<RuleName>(e.g.rule:cautious_survival:policy/CharacterSurvival.FleeOnLowHp). Collisions between mods are impossible by construction.
- Dual-use commodity split. When a name is used as BOTH a Settlement scalar channel AND a Resource template id (Wood, Stone, Gold), the compiler emits the two as distinct constants from distinct canonical strings:
- Scopes and contracts lower to compiler-emitted schema rows plus dense field/walk/method ranges.
- Systems lower to compiler-emitted
SystemRowdeclarations plus compiler-emittedSystems.Execute*delegate bodies;RuntimeModule.g.csregisters eachSystems.Execute*handler viaSecsRuntimeBuilder.WithSystemHandlerkeyed by system id. Systems whose emittedExecute*body is an empty stub get their live behavior from host-registered handlers (examples/valenar-v2/Host/*HostLogic.cs) that the host wires withWithSystemHandlerin place of the stub. - Events lower to compiler-emitted
EventRowplusEventOptionRowdense ranges. - Activities, on_actions, and policies lower to compiler-emitted row families and sidecar metadata where authored.
- The registration entry point is compiler-emitted
SecsModule.g.cs. It builds one deterministicWorldSchemafor the host/runtime. New schema row families must be wired intoBuildSchema()and into the second-passWith*copy if the row family is split across first/second pass arrays.
Hard rules
- Do not invent patterns. If a new construct does not exist in emitted generated C# yet, look at the corresponding
.secssyntax inContent/, look at the spec indocs/, and implement the compiler lowering that matches the committed runtime/schema semantics. - Do not bypass
SecsModule. Every generated schema row goes throughBuildSchema(). The host should not construct generated row arrays behind a parallel facade. - Do not use reflection in Generated/. The compiler emits static, AOT-friendly code. Reflection at startup would break Unity IL2CPP and would not match what the compiler can do.
- Field hashes use FNV-1a-64 over the canonical id string. Computed identically to
secs-roslyn's hash function. The compiler emits top-level canonical id strings as<namespace>:<kind>/<Name>colon-slash with the declared name reproduced verbatim (no owner/category segment), and scope-field ids as the dotted-suffix sub-id form<scope-id>.scope-field:<FieldName>. Compiler-emittedHashesnames are the public symbol authority; do not preserve old names through aliases, wrapper constants, or duplicate fields. RegisterTemplateIdentifierregisters ONLY the canonical string (Wave 5b). The dual-alias bridge that registered both the canonical id and the bare C# name (nameof(BareLocation) = "BareLocation") was collapsed; every WorldData JSON file, MapGenerationSystem fallback, test fixture, andTryGetTemplateconsumer must quote the canonical id directly. TheTemplatesByNamedictionary onSecsModuleis a SEPARATE designer-facing display-name lookup ("Great Hall" → TemplateId) and remains in place — do NOT collapse it; do NOT register its keys viaRegisterTemplateIdentifier.- Scope-field hashes are per-scope, emitted from the dotted-suffix canonical string. A field name reused across scopes (
OwnerId,MapCellId,LastCapitalChangedDay) is emitted as a distinct constant per owning scope from a distinct canonical string:valenar:scope/Territory.scope-field:OwnerId,valenar:scope/Camp.scope-field:OwnerId, andvalenar:scope/Skill.scope-field:OwnerIdeach hash to their own value and emit their ownH.ScopeField<Scope><Field>_H...constant. The engine still distinguishes by(ScopeId, FieldId); theFieldIddiffers per scope because the canonical string differs. Do not collapse these back to a single shared field-name constant — that disagrees with the compiler-emitted hash table.
When deleting generated content
If you delete schema-owned content, delete the .secs source or compiler
support and rebuild the generated project. Remove any host/test references to
the compiler-emitted IDs that disappear. Do not restore deleted Hashes,
SecsModule, row-builder files, aliases, fallback builders, or override tables.
Retained host/catalog marker
When a tracked Generated/*.cs file is retained because it is a checked-in
non-schema surface rather than compiler-emitted schema output, it must carry
this exact header comment if it has no .secs source citation:
// Retained host/catalog surface: no schema rows.
This is the canonical text that
tests/SECS.Standalone.Tests/Guards/V2GeneratedProvenanceGuardTests.cs
recognises for retained Valenar checked-in non-schema files. Do not invent a
paraphrase.
The legal retained-file header forms are:
// Source: <relative-path-to-.secs-file>— real committed source exists.// Retained host/catalog surface: no schema rows.— explicit retained non-schema surface with no schema row construction.
The scripts/check-secs-source-contract.sh CI guard enforces three additional patterns beyond provenance: bare phase/frequency slot syntax in .secs files, bare TemplateId strings in .secs files, and the absence of existing override/fire/prev_tick violations. See that script for the authoritative regex patterns.
Banned source-form syntax
The following tokens must not appear in .secs files under examples/valenar-v2/Content/, or in any Generated // Source: comment pointing to a hypothetical .secs file:
| Banned form | Why banned | Reference |
|---|---|---|
candidate_builder Name { ... } | Invented keyword not in committed list | docs/WAVE_REFACTOR_AUDIT.md § 1; docs/decisions/ADR-0002-behavior-vocabulary.md |
slot X.Y of Type; inside candidate_builder | Sub-clause of an invented keyword | same |
activity Y; inside candidate_builder body | Body clause of an invented keyword | same |
method ActivityRequest[] Build(...) inside candidate_builder | Body method of an invented keyword | same |
from collection X with builder Y | Invented selector source clause | same |
with builder X as a selector source clause | Sub-form of above | same |
The canonical runtime/compiler-lowering shapes that remain are CandidateBuilderId, CandidateBuilderDelegate, SelectorSource.FromCollection(...) — these are C# types, not .secs source syntax. Plain C# with these types is allowed in retained checked-in non-schema files and engine code.
The consider activity X from all_registered selector clause is COMMITTED source syntax and is not banned.
If you would have written candidate_builder in a .secs file, there is no committed source equivalent. Express the selection logic as an explicit retained checked-in non-schema surface or registered host delegate, and leave the source syntax for a future ADR.
Fabricated source files
Never create a .secs file to satisfy tests/SECS.Standalone.Tests/Guards/V2GeneratedProvenanceGuardTests.cs or any other provenance check. If a retained host/catalog file has no committed source citation, use the retained host/catalog marker. Creating a fabricated .secs file with invented syntax as a workaround is a governance violation that will be caught by the source-contract guard and the generated-source provenance tests, and must be reverted.
Source hygiene in Content/
Live .secs files under examples/valenar-v2/Content/ are subject to a separate
source-hygiene rule (.claude/rules/secs-source-hygiene.md). That rule governs
what comments must not appear in .secs source — wave labels, prototype status,
temporary-output markers, old keyword compatibility notes, future compiler commentary,
and deferral notes. This generated-codegen rule governs what headers and
provenance markers must appear in Generated/; the source-hygiene rule governs
what must not appear in Content/. The two are complementary.