Decisions taken without Tony present, on 2026-09-07, while working REQ-NFR-025 / #410. He asked
that anything I would otherwise have stopped to ask about be answered with my recommendation and
recorded here rather than left blocking.
The measurement that prompted all of this
artifacts/coldstart/coldstart-TG-W11VM-20260907-133247.log — the first valid cold-start figure.
A clean VMware Windows 11 VM, never had OpenVSA, NGen finished, virtual NVMe SSD.
| phase |
cold |
warm (mean of 4) |
cold penalty |
| window (process start → main window) |
2.43 s |
0.60 s |
+1.83 s |
| +menu |
0.64 s |
0.53 s |
+0.11 s |
| +connect |
0.02 s |
0.02 s |
0.00 s |
| +first frame |
0.35 s |
0.28 s |
+0.07 s |
| total |
3.43 s |
1.42 s |
|
NOT MET: 3.43 s against 3.00 s. 91 % of the penalty is before a window exists, so the front-end
registry, ConnectAsync, the capabilities query and the first FFT are all cleared — the cost is
loading assemblies, and the lever is reading fewer bytes.
Decision 1 — split RegisterAccent, so the unused theme assembly never loads (DONE)
Measured, not guessed. Process.Modules cannot answer this — the CLR does not put managed
assemblies in the PEB loader list on .NET Framework, so it reports only OpenVSA.exe and nothing
else. Subscribing to AppDomain.AssemblyLoad and building a ShellWindow the way the test host
does gives the real ordered list. It showed:
0.444 4,470,784 Syncfusion.Themes.FluentLight.WPF
0.445 4,468,224 Syncfusion.Themes.FluentDark.WPF
One millisecond apart, i.e. from one method. ThemeCatalogue.RegisterAccent had both skins as
the two arms of a single if, and the jit resolves every type a method names when it compiles
that method, not when the arm runs. So applying either theme loaded both — 4.47 MB read from cold
disk for a skin the shell was not drawing. The shell opens Dark.
Split into RegisterDarkAccent / RegisterLightAccent, each [MethodImpl(MethodImplOptions.NoInlining)]
— the attribute is load-bearing, not a hint: inlined back into one body, both types are named again
and the loads return. Verified: Syncfusion.Themes.FluentLight.WPF no longer loads at all.
Decision 2 — do NOT gate the configuration system on an embedded key (ROLLED BACK)
ConfigurationManager costs 3.05 MB on the start-up path: System.Configuration (418 KB) then
System.Xml (2.63 MB), at 0.055 s and 0.056 s — and ~10 MB of GAC native images behind them, out
of the ~103 MB of framework native images the path touches.
I implemented and then reverted a change making SyncfusionLicense.ResolveKey prefer the
build-embedded key over appSettings when no local.secrets.config sits beside the exe (a shipped
installation, exactly). It was measured and it does not work: FftProviders.ResolveFromConfiguration
reads ConfigurationManager.AppSettings for the FFT-provider override independently, so the
configuration system loads regardless. The change was complexity plus a documented-precedence
subtlety in exchange for nothing, so it is gone. git checkout on SyncfusionLicense.cs, no trace
left.
Recommendation, for Tony: there are three appSettings overrides on or near the start-up path —
SyncfusionLicense, FftProviders (OpenVSA.Dsp) and VisaConfiguration (OpenVSA.Hal.Visa). All
three are deployment overrides that are absent in a normal install. Skipping the configuration system
when none of them is actually present would remove ~10 MB of cold read, but it needs one shared
mechanism across three layers, and any cheap test for "is the override there?" is a heuristic over
the .config text. That is a design decision, not a performance tweak, and it is his. I did not
take it. Deferring the reads does not help — the first trace is an FFT, so every one of them is on
the measured path wherever it is moved to.
Decision 3 — the harness could not see what it was there to check (DONE)
Measure-ColdStart.ps1 asked ngen display OpenVSA and reported the answer as the installation's
NGen status. It is not. ngen matches a partial assembly name, and a partial name still has to
match the simple name exactly — so OpenVSA says nothing whatever about OpenVSA.Core,
OpenVSA.Dsp, or the 21.7 MB of Syncfusion the start-up path spends most of its time reading.
The v0.1.1 log reads like a clean bill of health and is silent on everything that matters.
Now it walks every assembly beside the shell, asks about each, and prints the ones that were
missed — that list is the difference between a cold start that jits 20 MB and one that does not.
Native (unmanaged) files are counted separately so OpenVSA.Fft.Native.dll is not a false alarm
every run.
Also fixed: the log said installed : 2026-08-08 for an MSI run three weeks later. It was reporting
the shell's CreationTime, which the MSI copies from the build. It now reports the build timestamp
as built, and asks Windows for the actual install date.
What is still open on #410
The single largest unknown is whether the Syncfusion assemblies get native images at all. The
installer hangs one netfx:NativeImage on OpenVSA.exe and relies on ngen install taking the
dependency closure. That is documented behaviour, not measured behaviour, and if it is not happening
then ~20 MB is jitted on the cold launch and that is the whole of the overrun. The patched harness
answers it on the next run; nothing on this machine can, because OpenVSA is not installed here and
installing it to find out would spend a machine's coldness and change Tony's dev box. I did not.
Next step is Tony's, and he is set up for it: roll the VM back to the pre-install snapshot, build
an installer from this commit, and re-run. That gives both the new figure and the NGen coverage list
in one go.
I have not closed #410 and have not touched its acceptance criteria. The 3 s is not moving —
Tony's rule from #421 is to root-cause a failure,
never widen a tolerance.
State
Landed on main as e721257. Solution-wide suite: 2 780 tests, 0 failures — the same count as
the d3bc59f bench-verified baseline, run over the whole solution rather than one project, because
the theming and layering guards only fire that way.
Bench verification of e721257 is reported in a comment below.
Decisions taken without Tony present, on 2026-09-07, while working
REQ-NFR-025/ #410. He askedthat anything I would otherwise have stopped to ask about be answered with my recommendation and
recorded here rather than left blocking.
The measurement that prompted all of this
artifacts/coldstart/coldstart-TG-W11VM-20260907-133247.log— the first valid cold-start figure.A clean VMware Windows 11 VM, never had OpenVSA, NGen finished, virtual NVMe SSD.
NOT MET: 3.43 s against 3.00 s. 91 % of the penalty is before a window exists, so the front-end
registry,
ConnectAsync, the capabilities query and the first FFT are all cleared — the cost isloading assemblies, and the lever is reading fewer bytes.
Decision 1 — split
RegisterAccent, so the unused theme assembly never loads (DONE)Measured, not guessed.
Process.Modulescannot answer this — the CLR does not put managedassemblies in the PEB loader list on .NET Framework, so it reports only
OpenVSA.exeand nothingelse. Subscribing to
AppDomain.AssemblyLoadand building aShellWindowthe way the test hostdoes gives the real ordered list. It showed:
One millisecond apart, i.e. from one method.
ThemeCatalogue.RegisterAccenthad both skins asthe two arms of a single
if, and the jit resolves every type a method names when it compilesthat method, not when the arm runs. So applying either theme loaded both — 4.47 MB read from cold
disk for a skin the shell was not drawing. The shell opens Dark.
Split into
RegisterDarkAccent/RegisterLightAccent, each[MethodImpl(MethodImplOptions.NoInlining)]— the attribute is load-bearing, not a hint: inlined back into one body, both types are named again
and the loads return. Verified:
Syncfusion.Themes.FluentLight.WPFno longer loads at all.Decision 2 — do NOT gate the configuration system on an embedded key (ROLLED BACK)
ConfigurationManagercosts 3.05 MB on the start-up path:System.Configuration(418 KB) thenSystem.Xml(2.63 MB), at 0.055 s and 0.056 s — and ~10 MB of GAC native images behind them, outof the ~103 MB of framework native images the path touches.
I implemented and then reverted a change making
SyncfusionLicense.ResolveKeyprefer thebuild-embedded key over
appSettingswhen nolocal.secrets.configsits beside the exe (a shippedinstallation, exactly). It was measured and it does not work:
FftProviders.ResolveFromConfigurationreads
ConfigurationManager.AppSettingsfor the FFT-provider override independently, so theconfiguration system loads regardless. The change was complexity plus a documented-precedence
subtlety in exchange for nothing, so it is gone.
git checkoutonSyncfusionLicense.cs, no traceleft.
Recommendation, for Tony: there are three
appSettingsoverrides on or near the start-up path —SyncfusionLicense,FftProviders(OpenVSA.Dsp) andVisaConfiguration(OpenVSA.Hal.Visa). Allthree are deployment overrides that are absent in a normal install. Skipping the configuration system
when none of them is actually present would remove ~10 MB of cold read, but it needs one shared
mechanism across three layers, and any cheap test for "is the override there?" is a heuristic over
the
.configtext. That is a design decision, not a performance tweak, and it is his. I did nottake it. Deferring the reads does not help — the first trace is an FFT, so every one of them is on
the measured path wherever it is moved to.
Decision 3 — the harness could not see what it was there to check (DONE)
Measure-ColdStart.ps1askedngen display OpenVSAand reported the answer as the installation'sNGen status. It is not. ngen matches a partial assembly name, and a partial name still has to
match the simple name exactly — so
OpenVSAsays nothing whatever aboutOpenVSA.Core,OpenVSA.Dsp, or the 21.7 MB of Syncfusion the start-up path spends most of its time reading.The v0.1.1 log reads like a clean bill of health and is silent on everything that matters.
Now it walks every assembly beside the shell, asks about each, and prints the ones that were
missed — that list is the difference between a cold start that jits 20 MB and one that does not.
Native (unmanaged) files are counted separately so
OpenVSA.Fft.Native.dllis not a false alarmevery run.
Also fixed: the log said
installed : 2026-08-08for an MSI run three weeks later. It was reportingthe shell's
CreationTime, which the MSI copies from the build. It now reports the build timestampas
built, and asks Windows for the actual install date.What is still open on #410
The single largest unknown is whether the Syncfusion assemblies get native images at all. The
installer hangs one
netfx:NativeImageonOpenVSA.exeand relies onngen installtaking thedependency closure. That is documented behaviour, not measured behaviour, and if it is not happening
then ~20 MB is jitted on the cold launch and that is the whole of the overrun. The patched harness
answers it on the next run; nothing on this machine can, because OpenVSA is not installed here and
installing it to find out would spend a machine's coldness and change Tony's dev box. I did not.
Next step is Tony's, and he is set up for it: roll the VM back to the pre-install snapshot, build
an installer from this commit, and re-run. That gives both the new figure and the NGen coverage list
in one go.
I have not closed #410 and have not touched its acceptance criteria. The 3 s is not moving —
Tony's rule from #421 is to root-cause a failure,
never widen a tolerance.
State
Landed on
mainase721257. Solution-wide suite: 2 780 tests, 0 failures — the same count asthe
d3bc59fbench-verified baseline, run over the whole solution rather than one project, becausethe theming and layering guards only fire that way.
Bench verification of
e721257is reported in a comment below.