Skip to main content

ScaffoldModule

Tsvrc.Editor.ScaffoldModule generates and owns TsGenerated.cs, the project-specific concrete subclass of TsRoot every other module's generated fragment extends via the partial class mechanism. It always writes first and must compile before any other module's Wire() pass can locate the root object, since every other module finds the scene root through ScaffoldModule.FindCompiledType().

What it generates

The scaffold file declares public partial class TsGenerated : TsRoot, a Start() method that calls every subsystem's generated _Ts*Start() method in a fixed order (_TsLogStart, _TsMemoryStart, _TsGlobalStart, _TsPoolStart, _TsConstructStart, _TsInstanceStart), and one generated shadow class per framework type carrying [TsWorldExtensionPoint]. This is the mechanism that produces TsBehaviour, TsInstance, and every other shadow class your own scripts actually extend, discovered entirely by reflecting over that attribute rather than a hardcoded list.

The self-healing scene object

AfterFilesStable() ensures the compiled scaffold type's own UdonSharpProgramAsset exists, creates or repairs the scene root object carrying it (EnsureRootSceneObject), and creates the child TsConfig object (tagged EditorOnly so it's stripped from the actual VRChat build) if it's missing. Finding more than one instance of the compiled type logs a warning naming exactly which one survives and which are destroyed, so a puzzled "where did my duplicate's settings go" moment (say, after a Ctrl+D) has a trail to follow in the console, rather than silently picking one.

Auto-linking the scene

The very first successful bootstrap in a project auto-links TsLinkedScene to the scene it just scaffolded, rather than leaving that corruption-safety net an indefinitely manual opt-in. It's a no-op once anything is already linked, including a second bootstrap attempt within the same pass, since once a scene is linked, the generator's own "linked scene not loaded" guard refuses to touch any other scene at all, so a second scene can never silently grow a competing scaffold of its own.

Deterministic program asset field order

NormalizeProgramAsset sorts an UdonSharpProgramAsset's internal field-definition dictionary into a stable order after every pass. Unity/UdonSharp's own serialization of that dictionary isn't guaranteed to stay in the same order across compilations, which without this normalization would produce a spurious file diff on every single domain reload even when nothing about the actual fields changed. That's noisy for version control and confusing for anyone trying to tell whether a regenerate actually changed anything.