03. DPI awareness, and the one directory build

Date: 2026-08-09 Verdict: The app was not DPI aware, measured rather than assumed, and a real defect fell out of that. The one directory build starts in a ninth of the time. This pass also corrects startup figures published in both reports before it.

Two measurements that did not fit anywhere else, and one of them turned into a correction of my own earlier numbers.

About this edition. The original record of this pass is an internal working document, in French. This is a full English edition prepared for publication, faithful to it and adding nothing. The original is archived unchanged.


Summary

Measurement Result
Does the app declare DPI awareness No. Nothing in the manifest, and the running window reports itself unaware
Real captures at 100 percent Main window 316 x 579 physical pixels, FAQ 376 x 519
Real captures at 150 percent Not possible here. The sandbox display offers exactly one scaling step
How bad the blur would be at 150 percent Reproduced rather than captured. Edge transitions widen from 0.98 to 1.95 pixels
A DPI defect observable right now Found. The window sits 427 pixels off centre
One directory build Builds and starts. 17.6 MB across 937 files
One directory cold start Median 111.9 ms
Was the expectation of roughly 300 ms right Confirmed and beaten. 111.9 ms, 77 percent faster than one file
Was my earlier extraction figure right No. It was an artefact of measuring from a host mapped folder

Part 1. DPI

What the binary declares

The Windows manifest compiled into the executable was extracted and read in full. It is the build tool's default manifest. It declares long path support and it says nothing whatsoever about DPI.

  dpiAware      absent
  dpiAwareness  absent
  PerMonitor    absent

Confirmed at runtime from a separate observer process that is itself fully DPI aware:

  the app window's awareness : unaware
  DPI reported for it        : 96

Neither the source nor the build configuration calls the API that would opt in. So the claim was correct and is now measured rather than supposed.

Why 150 percent could not be captured here

Three routes were tried and all three are documented.

asking the display what scaling steps it offers
  minimum, current and maximum all report the same value
  the virtual driver advertises exactly one step, 100 percent

asking it to change scale
  access denied

forcing a logical DPI override at five different steps
  the call reports success every time
  but a fresh unaware process and a fresh aware process both read
  the same screen size and the same 96 DPI after every attempt

The virtual display has no physical size, so Windows offers no scaling other than 100 percent. Changing it would mean restarting the sandbox with a different configuration, which destroys the sandbox.

This is a limit of the environment, not a failure of the code. Report 06 later rendered the interface on real displays actually set to 125 and 150 percent.

What turned up instead: a real DPI defect, visible now

While trying to change the scale, I found that this sandbox already presents a 1.5 ratio between what an unaware process sees and the real framebuffer.

  unaware process : reports the screen as 1707 x 938
                    but the true screen is  2560 x 1407
  aware process   : reports 2560 x 1407, correctly
  ratio           : 1.4997 by 1.5000

The toolkit reads the unaware value, so the app believes the screen is 1707 pixels wide. Its centring arithmetic then does the obvious thing with the wrong number.

  the app computes x = 1707 / 2 - 150 = 703
  the window really is at x           = 703
  centred on a 2560 pixel screen it would be = 1130
  horizontal error                    = 427 pixels, 16.7 percent of the screen

Two things need separating here, because they are easy to conflate.

The window is not stretched. 300 logical pixels occupy exactly 300 physical pixels. A sweep of the whole framebuffer finds the window background across precisely 300 pixels. There is no blur in the current state.

The window is badly placed, by 427 pixels. That is a direct symptom of not being DPI aware. The app reasons in a 1707 pixel space while the screen is 2560. On a real machine at 150 percent the same arithmetic produces the same kind of error, and the window would appear noticeably off centre.

This defect was not in my earlier reports because the sandbox display then reported the same number to everyone, so the window measured as correctly centred. The display state changed between sessions, and that divergence is what made the bug visible. Worth saying plainly: it was there the whole time and my environment had been hiding it.

How bad the blur would be, reproduced rather than measured

Since the sandbox cannot switch to 150 percent, I applied to the real 100 percent capture the exact operation the compositor applies to an unaware window: render at 96 DPI, then scale the bitmap up by 1.5 with bilinear interpolation.

This is a faithful reproduction of the transformation, not a capture of a real session at 150 percent. I would rather say that than present a simulation as a measurement.

Window Size Distinct grey levels Edge transition width
Main 100 percent 316 x 579 217 0.98 px
Main 150 percent 474 x 868 255 1.95 px
FAQ 100 percent 376 x 519 196 1.12 px
FAQ 150 percent 564 x 778 255 1.97 px

Side by side magnifications show it plainly. Text keeps its apparent size but loses its crisp edges, and the sub pixel colour fringing of the native rendering, which is clean and deliberate, dissolves into a diffuse coloured halo. That is the ugliest part of the result.

What the fix would cost

One call at startup, before any window exists, opting the process into per monitor awareness. Or the same thing declared in the manifest, with no code at all.

Either way the hard coded sizes would then have to be multiplied by the real scale factor, otherwise the window becomes physically tiny at 150 percent. Not testable here, because a DPI fix cannot be validated on a display that offers one scaling step.

That work was done afterwards, and report 06 verified it on displays actually set to 125 and 150 percent.


Part 2. The one directory build

The bias that had to be removed first

The first measurement, with the executables left where they were built:

median
one directory 957.4 ms
one file 1,308.5 ms

That location is the folder the sandbox maps in from the host machine, confirmed by looking at its owner. Reading 17.6 MB spread over 937 files across that share dominates the measurement completely.

So both builds were copied to a local disk inside the sandbox and everything was measured again. This is the point where my earlier numbers turned out to be wrong.

The clean measurement

Ten iterations each, identical method: launch the process, poll every two milliseconds for the first visible window belonging to it, and separately record the gap between the launcher starting and its child appearing.

Build median mean min max deviation self extraction
one directory 111.9 ms 112.6 109.6 117.0 2.2 none
one file, hardened 490.0 ms 489.7 473.8 501.4 9.3 286.0 ms
one file, plain 535.2 ms 551.1 522.2 640.0 39.2 319.8 ms
running from source 114.7 ms 116.1 111.6 126.7 4.4 none
  one directory against one file : 378.2 ms faster, 77.2 percent
  one directory against source   : 2.9 ms slower
  one file self extraction       : 286.0 ms, 58 percent of its startup

One directory is statistically indistinguishable from running the source. A 2.9 millisecond gap against deviations of 2.2 and 4.4 means the packaging adds no measurable startup cost. The entire one file penalty is self extraction.

Correcting my own published figures

The premise I had been working from was wrong, and that is my fault. The 988 millisecond extraction figure in my retest report, and the 1,076 milliseconds in the first audit, were both measured from the host mapped folder. On a local disk extraction costs 286 milliseconds, three and a half times less.

Previously published Real context Corrected, local disk
one file hardened: 1,297.6 ms host mapped folder 490.0 ms
one file plain: 1,427.1 ms host mapped folder 535.2 ms
extraction: 988.0 ms host mapped folder 286.0 ms

The relative comparisons in those reports still hold, because both builds carried the same handicap. The hardened build is still faster than the plain one, by 8.4 percent against the 9.1 percent I had reported, which is consistent.

But the absolute startup values are overstated by roughly a factor of 2.7 and should not be quoted. Somebody launching the app from a local disk sees 490 milliseconds, not 1.3 seconds.

The trade off

one file one directory
Startup, local disk 490.0 ms 111.9 ms
Disk footprint 7.2 MB 17.6 MB
Files to distribute 1 937
Processes at launch 2 1
Written to the temporary folder every launch 23.6 MB, then deleted nothing

For a tool launched between rounds of a game, 378 milliseconds is the number that matters, and one directory also stops writing and deleting 23.6 MB on every single launch. The cost is losing the single file, which is exactly the project's distribution argument.

This is a product decision rather than a defect, so it is documented without recommending which way to go. It was later settled by the Store package, which is already a container and makes the single file pointless.


Limits of these two measurements

  1. 150 percent was not testable. The blur figures are a reproduction of the compositor's operation, not a capture.
  2. The 427 pixel offset is real and measured, but its magnitude depends on the ratio this sandbox imposes. On a real machine at 150 percent the faulty arithmetic is the same and the exact offset depends on the resolution.
  3. Startup times are specific to this hardware. It is the ratios between the four variants that carry the information, not the absolute values.
  4. The one directory build has not had a full security pass. It shares the exclusion list and the same source, but the network, child process and bytecode checks were not replayed against it. That should happen before it is distributed. Report 05 did exactly that.