Repository navigation
CI Reproducible Builds
Audience: Programmers
Thread Context: N/A (development/process reference)
Platform/Backend Scope: Linux (GCC/Clang, sanitizers, MinGW-Wine), Windows (MSVC), macOS
Exact build commands to reproduce each CI job locally. The GitHub Actions workflow (.github/workflows/build.yml) runs these jobs on every PR. Use these when a CI check fails and you need to reproduce locally.
When a job fails, use gh run view <RUN_ID> --log-failed to get logs (see GitHub API and PR Checks), then reproduce locally using the matching command below.
The CI test binary is SparkTests (built into build/bin/). CI runs it directly (./bin/SparkTests) and, for the standard GCC/Clang/Windows/macOS matrix jobs, via ctest. The Linux GCC job uses GCC 14 (gcc-14/g++-14).
The Windows Shipping producer and the Linux trusted verifier must compute the same parity report. Recorded Windows paths are data: use Windows lexical path rules for their basename, absolute-path, containment, and provenance comparisons, regardless of the verifier host. Native filesystem checks remain appropriate only when inspecting files on the current host. A mismatch such as producer=3 trusted=19 can therefore indicate a checker portability defect even when the Windows build succeeded; do not waive report equality or promote the readiness gates to resolve it.
Build Matrix Verifier accepts only push and workflow_dispatch builds on Working. Its workflow trigger filters the branch and its job conditions filter those event types. Pull-request and scheduled builds remain ordinary CI evidence and must not invoke the publication verifier as if they were accepted release sources. The verifier still independently checks repository, workflow, commit, run attempt, artifact, and receipt identity before publishing a status.
.github/scripts/qualify-installed-native.py captures source_sha in
identity.json during its build phase. Later SDK, ABI and dependency-closure
phases require that same source identity, qualification mode and image hashes
before starting a process. Compiler-free fixtures in
Tests/Tools/test_installed_native_runner.py must include the captured source
identity to reach the intended host-binding, timeout and closure checks. Missing
or changed identities remain failures; do not relax the runner to accommodate
an incomplete fixture.
Reproduce the fixture checks with:
python3 -B Tests/Tools/test_installed_native_runner.pyThe ABI10 weather workflow's source-contract job gates its Windows native job.
Run every source-contract step in
.github/workflows/installed-weather-qualification.yml before requesting native
evidence; CMake, Ninja and PyYAML are required for those source checks. Passing
them qualifies the orchestration checks only. Native product results must retain
their separately pinned product and workflow commits, and do not certify Windows
11, physical graphics, installer signing or release readiness.
FPSWeatherIntegration_RejectsWrongThreadWithoutMutation covers both inactive
and active weather integration. Worker calls must return refusal before reading
game-thread state, and must leave the bound port and activation state unchanged.
The owned save decoder's missing-fixture skip is classified in
Tests/test-warning-waivers.json; its dedicated qualification runner still
requires a real copied primary and rejects skipped decoding as evidence.
The native decoder builds its canonical profile JSON object one string entry at
a time. The vendored JSON API does not support implicit std::unordered_map
conversion; keep the persisted profile keys and values as strings in the receipt.
Cast its byte count to std::uint64_t explicitly: on macOS, size_t does not
select the vendored JSON integer constructor unambiguously.
Reflected scene rejection fixtures rebuild filtered objects and arrays instead
of calling unsupported JSON erase overloads; retain every other field/entity.
Installed lineage command binding accepts Windows executable path case aliases
only when they identify the same physical file. PATHEXT can return cmake.EXE
while CMake reports cmake.exe. Source pins, configuration, argument order and
flag spelling remain exact; different or unresolvable tools must fail binding.
Verify the complete configured command before compiling as well as afterward,
so a binding mismatch fails before the expensive native build.
Installed reflected-scene failures retain their specific reason on stderr when
static engine code has an uninitialized module-local logger. An initialized
logger still uses its configured structured sinks and filtering. Runtime camera
FOV checks allow the existing 1e-6 relative numeric tolerance for the float
degrees/radians round trip; authored scene fields remain exact, and missing,
duplicate, non-finite or materially changed runtime values still fail.
SparkFPSGameCameraReloadReal separately loads two authored legacy scenes through
the exported FPS DLL Game, checks the live camera's changed FOV and other state,
and verifies that a malformed reload retains the committed state. Its isolated
staged assets and CPU initialization require no graphics device. Required Windows
CTest executes it; compiling it in native qualification alone is not runtime proof.
Documentation health allocates its results file with Python under the safe job
TMPDIR; Apple's bare mktemp can choose /var/folders across a symlink and
make every generator result unreadable under the strict no-reparse contract.
The asset cooker fixture verifies invalid native filename bytes before claiming
its UTF-8 rejection was exercised. Apple filesystems that refuse creation with
an encoding error report that case as unexercised; unexpected I/O errors fail.
Held-fire network tests wait for each input's applied sequence without advancing
simulation time during socket dispatch, retaining the separate flood-limit test.
ModuleLifecycleRecordHost selects all six production lifecycle-record cases.
The Linux symbolication probe uses -O0 -fno-omit-frame-pointer so the actual
signal-safe frame-chain walker can retain its caller. The canary still requires
the exact fault location and a return frame in main; this is a fixture option,
not a change to production crash capture or optimization.
Fatal stderr diagnostics are best effort so an unread or closed pipe cannot
block the crash report or its watchdog. Linux prepares a separate nonblocking
open-file description during handler installation, preserving inherited flags.
Other POSIX hosts emit these optional diagnostics only when stderr is already
nonblocking. The private report and original fatal termination remain canonical;
ordinary nonfatal diagnostics keep their existing behavior. The Linux canary
checks full and closed stderr pipes, including the stalled-report timeout.
Non-MSVC Debug builds optimize the pinned crypto dependency with -O2 while
retaining symbols, security flags and the ISA floor. The password verifier keeps
its 600,000 rounds and the authenticator keeps its two-second guard. The Linux
component regression checks actual compile commands and runs the existing hash
cases. ASan explicitly uses -g -Og for Debug C/C++ code while keeping assertions,
sanitizer instrumentation and the full-suite 1,800-second limit.
Qualification workflows also participate in readiness ownership validation.
The stdlib-only workflow parser accepts literal blocks (| / |-) for multiline
strings; use those instead of wrapped plain or quoted scalars. When converting
existing workflows, compare the complete parsed YAML values before and after,
including command newlines, then check the strict parser against those values.
Source pins, permissions, deadlines and triggers must retain their meanings.
The mainline Windows Release CPU-floor gate uses
.github/scripts/extract_sde_archive.py after verifying the pinned Intel SDE
archive hash. Its single streaming XZ pass avoids the native Windows tar
extraction timeout observed in Build run 37196542008. Keep the five-minute
setup limit and propagate extractor failures; the below-floor process test
remains required. Extraction measurements are printed into the job log.
The fuzz-policy structural gate requires every detected parser to have an
accurate classification. SceneManagerReflected.cpp owns a second JSON read
and gameplay adaptation beyond DeserializeInto; neither the reflected-world
nor text-reader harness executes it. Its reflected-gameplay-scene inventory
entry therefore remains blocked under SEC-120. A structural pass does not
prove this adapter is fuzz covered or close the release gate.
find SparkEngine/Source GameModules SparkEditor/Source SparkConsole/src SparkShaderCompiler/src \
-not -path '*/Metal/*' \
\( -name '*.h' -o -name '*.hpp' -o -name '*.cpp' \) | \
xargs clang-format --dry-run --Werror 2>&1
# Fix: pipe the same file list to clang-format -icmake -B build \
-DCMAKE_BUILD_TYPE=Release \
-DBUILD_TESTS=ON \
-DCMAKE_C_COMPILER=gcc \
-DCMAKE_CXX_COMPILER=g++
cmake --build build --parallel $(nproc)
cd build && ./bin/SparkTests && cd ..CI also runs CTest on the Windows/macOS matrix jobs; for a fail-closed local equivalent, use ctest --test-dir build --output-on-failure --parallel --no-tests=error. On the Linux GCC/Clang jobs it invokes ./bin/SparkTests directly. Either is fine locally — CTest gives per-test isolation, ./bin/SparkTests gives a single combined run.
cmake -B build \
-DCMAKE_BUILD_TYPE=Release \
-DBUILD_TESTS=ON \
-DCMAKE_C_COMPILER=clang \
-DCMAKE_CXX_COMPILER=clang++
cmake --build build --parallel $(nproc)
cd build && ./bin/SparkTests && cd ..cmake -B build \
-DCMAKE_BUILD_TYPE=Debug \
-DBUILD_TESTS=ON \
-DCMAKE_CXX_FLAGS="-fsanitize=address,undefined -fno-omit-frame-pointer" \
-DCMAKE_C_FLAGS="-fsanitize=address,undefined -fno-omit-frame-pointer" \
-DCMAKE_EXE_LINKER_FLAGS="-fsanitize=address,undefined" \
-DCMAKE_SHARED_LINKER_FLAGS="-fsanitize=address,undefined"
cmake --build build --parallel $(nproc)
cd build
ASAN_OPTIONS=detect_leaks=1:halt_on_error=0 \
LSAN_OPTIONS=suppressions=../Tests/lsan_suppressions.txt \
./bin/SparkTests --output-file asan-ubsan-lsan-results.txt
cd ..There is also a ci-linux-asan CMake preset that bundles these flags if you prefer cmake --preset ci-linux-asan.
The --output-file name above is only a local convenience. CI does not write asan-ubsan-lsan-results.txt. It runs the suite through .github/scripts/run-sanitizer-tests.sh, which writes junit.xml, metadata.json, console.txt, report.txt and process-footer.txt into $SANITIZER_EVIDENCE_DIR, and uploads that directory as test-results-linux-asan. The module-evidence job (RDY-010) downloads it to build/module-evidence/sanitizer-asan/ and runs verify-sanitizer-evidence.py verify-published on it. tools/module-evidence/validate_manifest.py then consumes it as SparkGameFPS's required sanitizer-report evidence: the metadata must name the exact commit, carry an origin directory that matches that commit and its recorded run id/attempt, be a clean run with zero failures, and match the junit.xml digest; the junit.xml must record at least the 6900-testcase SparkTests floor (SANITIZER_MIN_JUNIT_TESTCASES, pinned by a test to the step's --minimum-tests); and it must record exactly the FPSRespawn_* tests that production-source files define (derived by Tools/test_source_census.py, pinned to the SPARK_TEST_EXPECT_COUNT of FPSSinglePlayerSlice_RespawnProductionSource), each once and passed. The same exact-set rule applies to SparkGameFPS's junit-xml binding (build/SparkTests-junit.xml), so a filtered, stale or merged run cannot stand in for the module's production tests. Only the preceding verify-published step binds the directory to the current workflow run id and attempt, so keep it ahead of the consumer.
cmake -B build \
-DCMAKE_BUILD_TYPE=Debug \
-DBUILD_TESTS=ON \
-DCMAKE_CXX_FLAGS="-fsanitize=thread -fno-omit-frame-pointer" \
-DCMAKE_C_FLAGS="-fsanitize=thread -fno-omit-frame-pointer" \
-DCMAKE_EXE_LINKER_FLAGS="-fsanitize=thread" \
-DCMAKE_SHARED_LINKER_FLAGS="-fsanitize=thread"
cmake --build build --parallel $(nproc)
cd build
TSAN_OPTIONS=halt_on_error=0 ./bin/SparkTests --output-file tsan-results.txt
cd ..A ci-linux-tsan CMake preset bundles these flags as well.
MSan requires every linked C++ object, including the runtime, to be instrumented. CI therefore first builds an MSan-instrumented libc++/libc++abi from the LLVM 18.1.3 release tarball (hash-pinned in build.yml) with the recipe .github/msan/libcxx-runtime.cmake, caches that prefix by recipe hash and runner clang version, and then builds only the SparkTests target against it (cmake --build build --parallel $(nproc) --target SparkTests).
# 1. Instrumented runtime (once; CI caches the prefix). llvm-project-18.1.3.src.tar.xz from the llvmorg-18.1.3 release.
PREFIX=$PWD/msan-libcxx
cmake -G Ninja -S llvm-project-18.1.3.src/runtimes -B build-msan \
-C .github/msan/libcxx-runtime.cmake \
-DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ \
-DCMAKE_INSTALL_PREFIX=$PREFIX
cmake --build build-msan --target install-cxx install-cxxabi
# 2. SparkTests against it (no distro libc++-dev installed on the runner)
cmake -B build \
-DCMAKE_BUILD_TYPE=Debug \
-DBUILD_TESTS=ON \
-DENABLE_SDL2=OFF \
-DENABLE_VULKAN=OFF \
-DSPARK_IMGUI_ENABLE_FREETYPE=OFF \
-DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ \
-DCMAKE_CXX_FLAGS="-fsanitize=memory -fsanitize-memory-track-origins=2 -fsanitize-recover=memory -fno-omit-frame-pointer -stdlib=libc++ -nostdinc++ -isystem $PREFIX/include/c++/v1 -fsanitize-ignorelist=$(pwd)/Tests/msan_ignorelist.txt" \
-DCMAKE_C_FLAGS="-fsanitize=memory -fsanitize-memory-track-origins=2 -fsanitize-recover=memory -fno-omit-frame-pointer -fsanitize-ignorelist=$(pwd)/Tests/msan_ignorelist.txt" \
-DCMAKE_EXE_LINKER_FLAGS="-fsanitize=memory -stdlib=libc++ -L$PREFIX/lib -Wl,-rpath,$PREFIX/lib -lc++abi" \
-DCMAKE_SHARED_LINKER_FLAGS="-fsanitize=memory -stdlib=libc++ -L$PREFIX/lib -Wl,-rpath,$PREFIX/lib -lc++abi"
cmake --build build --parallel $(nproc) --target SparkTests
cd build
MSAN_OPTIONS=halt_on_error=1 ./bin/SparkTests --warn-is-error --shuffle 123
cd ..The job stays continue-on-error (advisory) until a run classifies clean with the instrumented runtime; Tests/TestMSanCanary.cpp is designed to fail RED if the linked libc++ is not instrumented (RED proof pending the first run), and a failure before the tests run is labelled infrastructure stage '<stage>' failed in ci-errors-linux-msan. Practically, reproduce it only when CI flags a real MSan finding.
Every library linked into the MSan binary must be instrumented, and the distro libfreetype is not, so the job disables ImGui's optional FreeType rasterizer (SPARK_IMGUI_ENABLE_FREETYPE=OFF; cmake/BuildImGui.cmake then skips find_package(Freetype) and falls back to stb_truetype). With FreeType linked, every editor font-atlas build aborted the run with a use-of-uninitialized-value report in FT_New_Memory_Face. Wall-clock budgets in Tests/TestEngineLoadTest.cpp are likewise skipped under SPARK_TEST_SANITIZER_BUILD, because the instrumentation alone exceeds them.
Run from an "x64 Native Tools Command Prompt for VS 2022" (or after Enter-VsDevShell -VsInstallPath <vs> -SkipAutomaticLocation -DevCmdArguments "-arch=x64 -host_arch=x64"): Ninja needs cl/rc on PATH and INCLUDE/LIB set, and the SDK fxc on PATH is what lets the foliage shader validations run.
cmake --fresh -B build -G "Ninja Multi-Config" \
-DCMAKE_C_COMPILER=cl -DCMAKE_CXX_COMPILER=cl \
-DCMAKE_C_COMPILER_LAUNCHER=sccache -DCMAKE_CXX_COMPILER_LAUNCHER=sccache \
-DCMAKE_MSVC_DEBUG_INFORMATION_FORMAT=Embedded \
-DCMAKE_DISABLE_PRECOMPILE_HEADERS=ON \
-DCMAKE_CXX_SCAN_FOR_MODULES=OFF \
-DGENERATE_DEBUG_SYMBOLS=OFF \
-DBUILD_TESTS=ON -DBUILD_GAME_MODULES=ON
cmake --build build --config Release --parallel
ctest --test-dir build -C Release --output-on-failure --parallel --no-tests=errorWithout sccache installed, omit the two _LAUNCHER flags. With sccache, keep -DGENERATE_DEBUG_SYMBOLS=OFF: Jolt's own /Zi would otherwise make sccache expect a PDB that cl (honouring the engine's later /Z7) never writes, and every Jolt translation unit fails with sccache: encountered fatal error. PCH is off because sccache cannot cache /Yc / /Fp; module scanning is off because no module units exist.
Use a fresh build directory for each CMake executable, generator, compiler, or
toolset. Do not run two configure processes against the same directory. A
partially written compiler-information file can retain the compiler identity
while losing the ABI and CMAKE_CXX_COMPILE_FEATURES results; the later
target_compile_features diagnostic is then a stale-state symptom, not proof
that MSVC lacks C++23 support. Re-run with cmake --fresh (or remove the
directory) after changing the CMake installation or recovering an interrupted
configure.
CI installs sccache v0.17.0 from the GitHub release verified against a SHA-256 literal, keeps the cache in SCCACHE_DIR under runner.temp (split actions/cache/restore / actions/cache/save steps keyed like the Linux ccache steps; the save runs even when the job went red; build/ is not restored), starts the server explicitly before configure, and reports the numbers in a Print sccache stats step. The engine selects embedded MSVC debug information (/Z7) through CMake policy CMP0141 before project(), which is what lets the configure-time try-compiles pass under the launcher.
The advisory build-windows-vs2026 job follows the same recipe inside the VS 2026 developer environment (vswhere [18.0,19.0), default v145 toolset) without -DBUILD_GAME_MODULES=ON; it fails visibly when that toolchain is absent instead of reporting a green no-op. build-windows-shipping still configures through the windows-shipping preset (Visual Studio 17 2022 generator, v143) and uses no compiler cache.
The build-matrix provenance checker records the absolute C++ compiler path in
addition to the Visual Studio generator instance, archiver, and linker. It
accepts only an absolute Windows x64 MSVC cl.exe path whose concrete toolset
version agrees with the other recorded tools. This is provenance validation,
not hosted reproducibility evidence: BLD-100 still requires two clean Windows
Shipping builds and an externally verified comparison.
STRIP_DEBUG_SYMBOLS=ON (both Shipping presets) keeps private symbols out of
the runtime package. It no longer stops them from being produced:
-
MSVC: every image links with
/DEBUG(objects already compile with/Z7), so it carries a CodeView RSDS record, the PDB GUID and age that identify its PDB. Outside Debug,/PDBALTPATH:<name>.pdbrecords only the PDB file name; the name comes from$<TARGET_PDB_FILE_NAME:...>, not%_PDB%, because the Visual Studio generator escapes%to%%in link options and link.exe then recorded%%%probe.pdb%%%(reproduced 2026-09-27 with a probe project; Ninja passed%_PDB%through intact)./OPT:REFand/OPT:ICFare restated for every non-Debug configuration, because/DEBUGalone would switch them off.tools/check_shipping_configuration.pyreads a configured tree's File API codemodel and fails when MinSizeRel is not a distinct Shipping configuration: Shipping defines only in MinSizeRel, a per-configuration artifact directory, and/DEBUG,/Breproand a bare-name/PDBALTPATHon the Shipping link. build.yml runs it onbuild/windows-shipping; theShippingConfiguration_DistinctCTest runs it on Visual Studio test trees. -
ELF (GCC/Clang): every image links with
-Wl,--build-id=sha1. WithSTRIP_DEBUG_SYMBOLS=ON, every target compiles with-g(the static libraries hold most shipped code), and each shipped image target getscmake/SparkSplitDebugLink.cmakeas its C/C++LINKER_LAUNCHER. It runs the link, then writes<image>.debug(objcopy --only-keep-debug) and strips the image with a.gnu_debuglink. It runs inside the link step, so the module.sparkabihash and everyPOST_BUILDcopy see the stripped image. Every other image (SparkTests, test probes) links outside Debug with-g0and--strip-alland gets no.debug; under LTO, link-time-g0keeps the LTRANS stage from generating debug info (checked with GCC 13). MinGW keeps-s, and Apple has no strip step.
The PDBs and .debug files of the shipped image targets
(SPARK_SHIPPED_IMAGE_TARGETS in the root CMakeLists.txt) install only into
the symbols component. CPACK_COMPONENTS_ALL leaves that component out, so no
package carries symbols:
cmake --install <build> --component runtime --prefix stage # also tools, samples
cmake --install <build> --component symbols --prefix symbols-stage
python3 tools/shipping_symbol_manifest.py --images stage \
--symbols symbols-stage/symbols --output shipping-symbol-manifest.jsonThe manifest tool uses only the standard library. It maps each ELF image by
build-id, .gnu_debuglink name and CRC-32 to one DWARF .debug file, and each
PE image by RSDS GUID and age to one PDB: the GUID comes from the PDB info
stream and the age from the DBI stream. It writes a closed
spark.shipping-symbol-manifest/1 JSON and writes nothing (exit 1) when any of
these hold:
- an image has no build ID, or still has DWARF or
.symtab; - an image has no matching symbol file, or more than one;
- a debuglink name or CRC does not match;
- an RSDS record holds a path instead of a bare PDB name;
- a symbol file sits in the runtime tree;
- a symbol file maps to no image.
build-windows-shipping runs it over the staged runtime/tools/samples
components and uploads the PDBs and manifest as the separate
shipping-symbols-<sha> artifact. Symbol-server hosting belongs to OPS-100.
CTests: ShippingManifest_SymbolManifestTool runs
Tests/Tools/test_shipping_symbol_manifest.py. It builds real gcc fixtures
through the production launcher, and clang/lld-link PE+PDB fixtures
cross-checked with llvm-readobj and llvm-pdbutil. It also checks that
SPARK_SHIPPED_IMAGE_TARGETS names every image target installed by an
install(TARGETS) rule in the root, Spark*/, GameModules/ and cmake/
CMake files, and that no CPACK_COMPONENTS_ALL list (root or
cmake/SparkCPackOptions.cmake) names the symbols component.
ShippingManifest_PrivateSymbols is registered in a STRIP_DEBUG_SYMBOLS ELF
tree and in every MSVC tree with tests enabled (MSVC: Release, RelWithDebInfo
and MinSizeRel only, because Debug links keep the absolute PDB path). Its body,
cmake/SparkStagePrivateSymbols.cmake, installs the runtime/tools/samples and
symbols components to separate roots under <build>/shipping-symbol-stage and
maps every installed image, so it needs every installed target built. The local
linux-shipping evidence is with ENABLE_LTO=OFF; a full LTO (preset default)
build with -g, and its disk, memory and time on hosted runners, has not been
measured. MSVC PDB output under /Brepro and the hosted job have not yet been
observed.
cmake/SparkReproducibleBuild.cmake maps both roots out of optimized GCC/Clang
outputs: -ffile-prefix-map=<source>/= and then -ffile-prefix-map=<build>=.
(the later map wins, so it also covers a build tree inside the source tree).
Without the build-root map every DWARF comp_dir (each target's binary
directory) carried the build path, so builds made in different directories
differed in .debug_info and, through the GNU build-id, in the stripped image.
Under GCC LTO the LTRANS units compile at link time, so GCC also gets both maps
as link options, and every compile gets -frandom-seed=<OBJECT>: without a
seed GCC names the LTO IR sections of an object from the clock and pid, so even
two builds in one directory produced different static libraries.
Known limit: GCC's LTO IR (the members of static libraries such as
libSparkAssetPipelineCore.a) still records the build directory; no prefix map
rewrites it. Those members differ between trees in different directories,
although the linked images and .debug files are equivalent.
tools/compare_build_outputs.py compares builds with the standard library only:
python3 tools/compare_build_outputs.py manifest <root> --output a.json # one tree
python3 tools/compare_build_outputs.py compare a.json b.json --report r.json
python3 tools/compare_build_outputs.py trees <root-a> <root-b> # both at onceThe spark.build-output-manifest/2 manifest lists every ELF, PE, COFF object and ar file
under the root: relative path, size, SHA-256, the identity (GNU build-id; COFF
timestamp, RSDS GUID/age/PDB name and whether the image carries the /Brepro
REPRO debug entry) and a SHA-256 per section or archive
member. It holds no absolute path, so equivalent trees give byte-identical
manifests. Regenerate older manifests: version 2 records normalization metadata
and hashes archive members with their actual bytes. PDBs are not compared:
the RSDS record in the image identifies them.
The comparison exits 1 on any missing, extra or differing output and names the
first differing section that is a cause (headers, the build-id note and the
debuglink CRC only follow other changes). A PE image linked without /Brepro
whose COFF timestamp differs is reported at <pe-headers> (COFF timestamp):
there the timestamp is the link time and the debug directory that repeats it
is derived. Under /Brepro the timestamp is a content hash, so the content
section is named instead. An empty or malformed tree exits 2.
CTests (label reproducibility):
-
ReproducibleBuild_CompareToolrunsTests/Tools/test_compare_build_outputs.pyon gcc ELF, crafted PE andarfixtures. -
ReproducibleBuild_LinuxToolTargets(ELF trees with objcopy;RUN_SERIAL, about 80 s) runscompare_build_outputs.py two-tree. It copies the source tree (no.git, nobuild/) to<build>/reproducible-build-trees/a/srcand.../tree-b/nested/src, configures each with the linux-shipping settings (MinSizeRel, LTO,STRIP_DEBUG_SYMBOLS=ON;SPARK_STRICT_DEPSstays OFF because the copies have no.gitfor the third-party audit) and this tree's compiler, with no compiler launcher and noCFLAGS/CXXFLAGS/LDFLAGS, buildsSparkCooker, and comparesbin/: the stripped image and its.debug. Before the build-root map both differed (.debug_info, and the image's build-id). It has passed locally only with GCC 13.3. The inner build uses the defaults (ENABLE_LTO=ON, the compiler's default standard library), not the Clang lane'sENABLE_LTO=OFF/libc++ flags, and no hosted GCC 14 or Clang lane has run it yet; one manual Clang two-tree run outside the CTest was equivalent.
The reproducibility-windows job checks the repository out twice (tree-a and
tree-b), builds and installs the windows-shipping preset in each with no
compiler cache, and compares the two install trees. The checkout names have
equal byte lengths. Each manifest receives its native Windows build-root
string through --build-root.
Owner decision OD-24 permits one normalization: the exact build-root string
inside .debug$S CodeView sections of COFF members of .lib archives is
replaced with a fixed placeholder of the same length. The manifest records
the affected members. Archive headers, member lengths, section layouts,
other sections, image bytes and paths outside .debug$S remain exact.
Different-length roots are rejected. Standalone COFF objects and archives
with other extensions do not receive this normalization. This does not
permit stripping debug sections or ignoring SDK libraries.
The job remains continue-on-error and outside required-ci-gate until a
hosted run proves equivalent trees. Local parser fixtures do not establish
Windows Shipping reproducibility. A local MSVC /Z7 /Brepro /bigobj probe
still differed in the COFF content-derived timestamp and .debug$T bytes
after the permitted replacement. Those differences remain failures under
OD-24; broadening the comparator to hide them is not permitted.
tools/check_isa_baseline.py scans Windows PE images when each is given its
matching PDB (--pdb IMAGE=PDB) and LLVM's llvm-objdump and llvm-pdbutil
are available; a missing or mismatched PDB is a tool error. On Windows MSVC,
cmake/SparkIsaBaseline.cmake registers CpuFloor_IsaBaseline from the root
SPARK_SHIPPED_IMAGE_TARGETS inventory when the distribution CPU floor applies.
It pairs each configured image with that configuration's TARGET_PDB_FILE.
The custom target works with BUILD_TESTS=OFF; Windows Shipping CI invokes it
after building the products. With tests enabled it also registers a CTest.
Missing tools, missing files and remaining findings fail the scan. Existing ELF
registration remains in Tests/CMakeLists.txt.
On a local windows-shipping MinSizeRel build (LTO off, MSVC 14.44.35207,
LLVM 22.1.8) all 12 configured first-party images (each scanned with its own
build PDB) pass. The Microsoft runtime DLLs packaged under redist/ have no
build PDB and are not scanned. The earlier residual classes were
resolved as follows:
- libsodium's AVX2/AES-NI/AVX-512 variants and
sodium_init's XGETBV: no longer compiled on MSVC (cmake/SparkLibsodium.cmake). - EVEX loops the auto-vectorizer adds behind
__isa_available >= 6(cgltf_calc_index_bound, the STL's__std_minmax_*) and AVX2 loops behind__isa_available >= 5(Sha256State::Finalize): reviewed guard rules. - The out-of-line
std::_Countl_zero_lzcnt<unsigned __int64>, reached only by_Checked_x86_x64_countl_zero's guarded tail jump: a caller-guarded entry, re-proven on every scan. - MSVC x64 switch tables inside
.text(all undecodable records, and bytes that decoded as bogus VEX/EVEX instructions): proven data bytools/isa_code_map.py.
Per-image measurements are in docs/readiness/BLD-100-PROGRESS.md.
The scanner binds each PE to its PDB by RSDS GUID and age. Every exemption is scoped to named ISA families and to reviewed provenance:
-
Section contributions. The DBI section contributions of the STL's
vector_algorithms.objare exempt for AVX, AVX2 and LZCNT only. The module must carry its exact Microsoft build path, and the PDB must say it came from an MSVC 14.44.35207 library. The whole 5382-line source was reviewed. Every above-floor intrinsic in it runs only behind_Use_avx2(), and the line numbers are cited in the checker. This covers the code that has noS_GPROC32record. -
Exact procedure/module pairs.
memcpy/memsetare exempt for AVX/AVX2. vcruntime's__isa_available_initandSpark::Detail::ReadXcr0are exempt for XGETBV only; other XSAVE-family instructions still fail. -
Guard dominance. This covers CRT/STL code the headers inline into
arbitrary procedures, and auto-vectorized loops: UCRT
wmemchr/wmemcmpbehind_Avx2WmemEnabled(AVX/AVX2),__isa_available >= 5(AVX/AVX2 and LZCNT) and__isa_available >= 6(onlyvpmaxuq/vpminuq, AVX512F/VL). The vcruntime__isa_available_initdisassembly was reviewed for what each level proves. Such an instruction is exempt when the guard edge is the only way to reach it from its procedure's entry. The analysis builds the procedure's control-flow graph from the disassembly. It fails closed on indirect jumps, guards in callers, unmatched compare shapes, undecodable bytes and code with no procedure record. Guard storage must fit inside the section's virtual data extent, including the loader's zero-filled tail. -
Caller-guarded procedures.
REVIEWED_CALLER_GUARDED_PROCEDURESnames out-of-line procedures that only guarded callers reach. An entry holds only when every code reference is a direct branch or call that a guard rule dominates, the address is never taken, and no data section contains it.
Data in code. Before classification, tools/isa_code_map.py rebuilds each
procedure's instruction stream around the MSVC switch tables it can prove. The
dispatch must match MSVC's lea image base; [movzbl index table]; movl RVA(%base,%idx,4); add %base; jmp *reg idiom, with the image base reaching
every use. The extent comes from the bound check. Where MSVC omits the bound
(std::variant), it comes from the run of entries that name instruction
starts. Every entry must land on an instruction of the rebuilt stream. No
branch may target a table, and no reachable instruction may fall into one. The
table set must be a fixed point of re-reading the rebuilt stream. The stream
is re-decoded where the sweep fell out of step. Bytes nothing explains stay
undecodable and fail.
A table is also rejected, and a guarded block's exemption withdrawn, when the
bytes are reached as code from outside the procedure. The scanner collects
image-wide code entry points -- direct branch/call targets (from the rebuilt
streams, so table bytes do not masquerade as branches), exception handlers,
exports, base-relocation pointees and the guard-CF table -- and treats an entry
inside a table, or strictly inside a guarded procedure, as an alternate entry.
.pdata BeginAddress is excluded (MSVC gives a jump table its own
RUNTIME_FUNCTION). A computed indirect target is resolved by basic-block
dataflow -- an immediate image address materialized into a register
(movabs/mov/lea, constant add/sub/inc/dec, and the mov-RVA-plus-image-base
idiom) that reaches jmp *reg / call *reg, or is stored while the procedure
has an indirect branch -- and counts as an entry (rejecting a table, seeding a
guard root, or withdrawing the caller-guard exemption). A bare data constant
that merely coincides with a code address is not an entry; separately, if such a
constant lands inside proven-table bytes, those bytes are decoded and the table
is rejected only when they form an above-floor instruction (undecodable table
data is kept; a computed jump into it is caught as an entry). Finally,
_verify_coverage requires the classified instructions, proven tables and
padding to tile every executable file-backed byte with no gap, so a record
without raw bytes (unknown size) fails rather than being skipped.
The reviewed CRT/STL exemptions (vector_algorithms, memcpy/memset,
__isa_available_init XGETBV, the inline guards and the caller-guarded LZCNT
helper) are granted only when the PDB's sole observed MSVC toolset is the
reviewed 14.44.35207; otherwise they become violations and the report prints a
"re-review for this toolset" note. The scanner's threat model is
compiler-generated MSVC code from this repository's sources: direct branches,
structural pointers and basic-block-local computed targets are resolved;
interprocedural or memory-carried computed targets in a fixed-base image are
not.
The scanner reads raw instruction bytes, so an EVEX-encoded xmm instruction
counts as AVX-512 and never as AVX. A VEX instruction is classified by an
explicit allow-list of the AVX/AVX2 mnemonics; anything outside it fails closed
as its own feature, so AVX-VNNI (vpdpbusd), AVX-IFMA, AVX-NE-CONVERT, VAES,
VPCLMULQDQ, GFNI and XOP cannot inherit an AVX/AVX2 exemption.
Mask-register instructions include k0.
LLVM's separate prefix records are joined only to contiguous instruction bytes.
Undecodable bytes remain failures and are reported separately from identified
above-floor instructions. FMA, unreviewed AVX-512, BMI, other extensions and
unknown runtime functions remain failures under these PE mechanisms. --allow-symbol affects ELF only. New toolchain or
runtime variants need a new review.
CpuFloor_IsaBaselineChecker covers linked PE fixtures, the PDB contribution
and procedure parsers, the guard analysis, the switch-table proofs and the
caller-guarded entries, using synthetic llvm-objdump and llvm-pdbutil text. Native runtime fixture passes establish local scanner
behavior. They do not replace an engine image scan or execution on below-floor
hardware.
cmake --preset macos-release
cmake --build build/macos-release --parallel $(sysctl -n hw.logicalcpu)
./build/macos-release/bin/SparkTestsbuild-macos configures by hand, so this job is the only one that proves the
shipped macOS configuration (MinSizeRel, arm64, no editor or dev tools, no
tests) still configures, builds and installs. It then checks that the staged
engine records SPARK_MACOS_MIN_VERSION as its minimum OS:
cmake --preset macos-shipping
cmake --build --preset macos-shipping --parallel $(sysctl -n hw.logicalcpu)
cmake --install build/macos-shipping --prefix stage --component runtime
python3 tools/check_macos_min_version.py --binary stage/bin/SparkEngineIt is advisory and not a required-ci-gate dependency until a hosted run is
green; macos_shipping_leg_errors() in
.github/scripts/test-workflow-failure-propagation.py keeps it present,
preset-driven and advisory.
This experimental lane runs only on manual workflow_dispatch. It is advisory
and targets CPU rendering for GPU-less servers and agents in both the engine
and editor. The last documented hosted run built but failed Wine tests; the
new runtime smokes have no current local or exact-commit hosted pass.
# From the Linux/WSL checkout root; initialize the recorded submodules first.
git submodule update --init --recursive
cmake --preset linux-mingw-release -DBUILD_TESTS=ON -DENABLE_EDITOR=ON
cmake --build build/linux-mingw-release --target SparkEngine SparkEditor --parallel 2
cmake --build build/linux-mingw-release --parallel 2
# Use a dedicated prefix. --dxvk-only verifies the archive and copies its x64 DLLs here.
export WINEPREFIX="$PWD/build/linux-mingw-release/.wineprefix-mingw"
xvfb-run -a bash tools/wine-run.sh --setup-only
bash tools/setup-mingw-wine.sh --dxvk-only
# Each command invokes xvfb-run + Wine + pinned DXVK + Lavapipe internally.
python3 .github/scripts/mingw-wine-smoke.py engine
python3 .github/scripts/mingw-wine-smoke.py editor
export SPARK_TEST_EXCLUDE="$(python3 .github/scripts/mingw-wine-smoke.py exclusions)"
python3 .github/scripts/mingw-wine-smoke.py tests
python3 .github/scripts/mingw-wine-smoke.py summaryThe runner requires fresh captures/command audits, editor successful presentation
counts, pinned DXVK CPU-device logs, clean exits and the passing-test floor (MINIMUM_TESTS=7500)
with --warn-is-error. The job emits an always-on stage summary and artifact.
The MinGW guide lists prerequisites and every
Wine-only exclusion. Native Windows tests are unchanged. No nightly trigger is
added without three consecutive local passes.
./tools/validate-prompts.sh --ci-
check-thirdparty-manifest—./tools/check-thirdparty-manifest-sync.sh -
coverage— GCC Debug with--coverage+ lcov, per-subsystem thresholds -
clang-tidy— Clang Debug static analysis over every shipped-product translation unit; configure/compile failures block, and the diagnostic budget ratchet (below) blocks any change in per-check diagnostic counts -
todo-count— fails if TODO count exceeds threshold (20) -
build-installer— builds theSparkInstallertarget -
report-ci-errors— aggregatesci-errors-*artifacts from failed jobs; findings from advisory lanes (job-levelcontinue-on-error, e.g.build-linux-msan) are listed but do not fail the report. The reporter is loaded from the trustedWorkingcheckout, so a change to it takes effect only after it lands there
To verify CI-100's required-job failure propagation on the Working ref, run
gh workflow run build.yml --ref Working -f simulate_required_job_failure=true. The manual-only input deliberately fails
the required validate-ci-tools job; Required CI Gate must then fail as well.
This is a red control run and cannot qualify a release commit.
The blocking inventory includes SparkAssetPipelineCore/src and
SparkCrashReporter/src; the workflow failure-propagation contract pins both
roots. Measure newly covered translation units with Ubuntu 24.04 clang-tidy 18,
the lane's configure flags and compile database, and the repository .clang-tidy.
Prefer behavior-preserving fixes, add only measured new-file budget counts,
never raise existing entries, and record provenance in measuredAt. A focused
log checked against a projected budget proves those files only; the complete
inventory remains the full static-analysis gate.
--warnings-as-errors="" keeps a single diagnostic from failing the analysis,
so Tools/clang_tidy_budget.py turns the diagnostic stream into a ratchet
against the committed Tools/clang-tidy-budget.json. The run step writes one
log per translation unit (parallel workers never interleave partial lines),
checks that there is a log for every translation unit, and concatenates them in
sorted order. The budget step then deduplicates diagnostics by
repository-relative path, line, column, check and message (a header reported by
many translation units counts once), counts them per file and check, and fails
when:
- a (file, check) entry reports more diagnostics than its budget (a regression; the error names the file and lists its diagnostics);
- a (file, check) pair that is not in the budget reports anything;
- an entry reports fewer than its budget (the budget is stale: lower it in the same change so the cleanup cannot be spent by a later regression elsewhere);
- the clang-tidy major version differs from the budget's
clangTidyMajorVersion(18 on ubuntu-24.04).
Reproduce the lane locally (ubuntu-24.04 packages: clang-tidy, libc++-dev,
libc++abi-dev), with the CI configure line and -B <dir> outside the repo:
cmake -S . -B /tmp/tidy-build -DCMAKE_BUILD_TYPE=Debug \
-DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ \
-DCMAKE_CXX_FLAGS="-stdlib=libc++" -DCMAKE_EXE_LINKER_FLAGS="-stdlib=libc++ -lc++abi" \
-DCMAKE_SHARED_LINKER_FLAGS="-stdlib=libc++" \
-DCMAKE_DISABLE_PRECOMPILE_HEADERS=ON -DCMAKE_EXPORT_COMPILE_COMMANDS=ON
# Build the file list and per-TU logs exactly as the "Run clang-tidy" step does
# (with -p /tmp/tidy-build in place of -p build), concatenate them into
# clang-tidy-output.log, then:
clang-tidy --version > clang-tidy-version.txt
python3 Tools/clang_tidy_budget.py check --log clang-tidy-output.log \
--clang-tidy-version-file clang-tidy-version.txtTo accept a reviewed change (lowering after a cleanup, or a deliberate raise),
download the lane's clang-tidy-output artifact (the log plus
clang-tidy-version.txt) and regenerate; --repo-root is the checkout the log
was produced in:
python3 Tools/clang_tidy_budget.py update --log clang-tidy-output.log \
--clang-tidy-version-file clang-tidy-version.txt \
--repo-root /home/runner/work/SparkEngine/SparkEngine --measured-at <commit-or-run>A local measurement is only valid when it runs over the exact tree CI will
check out. Never measure the shared working tree: uncommitted edits by other
agents change counts, and an untracked source file becomes a budget entry that
a clean checkout reports as stale. Export the commit (git archive <sha> | tar -x -C <dir>), link the submodule directories in, point a copy of
compile_commands.json at the export, run clang-tidy there, and pass the
export as --repo-root. Every commit that changes C++ under the analyzed roots
must carry the matching budget update, because an exact ratchet reports both
new and removed diagnostics.
Tests/Tools/test_clang_tidy_budget.py (run by validate-ci-tools) covers the
regression, unbudgeted-check, stale-budget, toolchain-mismatch, empty-log and
malformed-budget paths, rejects budget entries for files git does not track,
and pins the workflow wiring. A full local run of the
955 translation units takes hours on a loaded 4-core machine.
- For Windows-only failures that cannot be reproduced on Linux, inspect CI logs carefully and fix based on MSVC-specific diagnostics (e.g.,
/W4warnings, MSVC type-conversion rules, Windows SDK headers). -
$(nproc)is Linux/bash-specific; on macOS use$(sysctl -n hw.logicalcpu).
- Original entry:
CI Reproducible Builds — Local Reproduction Commands, last updated 2026-03-30. - Verified against codebase 2026-06-08.
- Updated / found stale:
-
Fixed a broken shell construct in the ASan/TSan/MSan run lines: the source wrote
ENV=... cd build && ./bin/SparkTestswhich applies the env var tocd, not to the test binary. Rewritten ascd buildthen the env-prefixed./bin/SparkTestsrun, matching how CI actually invokes it. - Aligned the LSan suppressions path to
../Tests/lsan_suppressions.txt(relative tobuild/); verifiedTests/lsan_suppressions.txtandTests/msan_ignorelist.txtexist. - Removed the source's prior CTest-plus-
SparkTestscombo from the GCC/Clang jobs — CI runs./bin/SparkTestsdirectly there; clarified where CTest actually runs (Windows/macOS matrix). - Output filenames updated to match current CI (
asan-ubsan-lsan-results.txt, etc.). Corrected 2026-09-24: CI's ASan evidence is therun-sanitizer-tests.shdirectory (junit.xml+metadata.json), not a results.txtfile. - Added the new
ci-linux-asan/ci-linux-tsanpresets as alternatives. - Noted MSan builds only the
SparkTeststarget in CI; added|| trueto match CI (2026-09-06: the recipe now builds the MSan-instrumented libc++ first and runs withhalt_on_error=1, so the|| truewas dropped again). - Added sccache/
continue-on-errornotes for the Windows jobs and the v145 VS 2026 variant. - Windows VS 2022 / VS 2026 recipes switched to Ninja Multi-Config + sccache (2026-09-06); the Visual Studio-generator configure now applies only to
build-windows-shipping's preset. - Added the jobs that did not exist in the source:
check-thirdparty-manifest,coverage,clang-tidy,todo-count,build-installer,report-ci-errors, plus the macOS and MinGW-Wine reproduction recipes. - Noted the Linux GCC job uses gcc-14/g++-14.
- 2026-09-29: Windows CPU instruction checks now describe the vector_algorithms section-contribution ranges, the guard-dominance analysis and the XSAVE exemptions (BLD-100). Measured on local MSVC 14.44.35207 Release images, not on a hosted run.
- 2026-09-27: added the advisory
build-macos-shippingjob (PLT-220), the first lane that configures themacos-shippingpreset; authored and structurally tested only, with no hosted macOS run yet. - 2026-09-26: added the CI-110 clang-tidy diagnostic budget ratchet (per-TU logs,
Tools/clang_tidy_budget.py,Tools/clang-tidy-budget.json); the committed budget was measured locally with Ubuntu clang-tidy 18.1.3 and the lane's configure line, not yet on a hosted run. - 2026-09-25: added build-output reproducibility (BLD-100): the build-root prefix map, the GCC LTO seed,
tools/compare_build_outputs.py, theReproducibleBuild_*CTests and the advisoryreproducibility-windowsjob, measured locally with GCC 13.3.
-
Fixed a broken shell construct in the ASan/TSan/MSan run lines: the source wrote
- GitHub API and PR Checks — fetching the failing logs
- Build Optimizations — CI workflow speedups
- Workflow Patterns — pre-push checklist
- Project conventions (CLAUDE.md) — "CI jobs summary" table
Versioned publication requires its vMAJOR.MINOR.PATCH tag to equal the single SPARK_ENGINE_VERSION default in CMakeLists.txt. CHANGELOG.md must contain exactly one matching ## [MAJOR.MINOR.PATCH] heading, optionally followed by - YYYY-MM-DD. Missing, duplicate, or mismatched metadata fails preparation before release outputs are emitted. Nightly publication continues to use the source default without requiring a versioned changelog section. This contract does not certify release notes, signing, or Windows qualification; the stable readiness gate remains mandatory.
Portable POSIX CPack archives keep SDL2's ABI-visible library names but
dereference its SONAME/development symlinks during install staging. Installed
executables search lib/ through $ORIGIN/../lib on Linux and
@executable_path/../lib on macOS. The archive preflight therefore sees regular
files while the runtime loader and exported CMake targets retain their expected
names.
Installed-template live smokes also set SPARK_TEMPLATE_LIVE_SMOKE_EVIDENCE=1
inside a disposable project copy. The shared template bridge and FPSStarter's
compatible bridge write .spark-template-live-smoke.log; the verifier reads it
alongside process and FileSink output before removing the copy. This keeps the
scene-ownership gate reliable for GUI-subsystem Windows hosts whose module DLLs
have DLL-local logger state.
Versioned Windows publication stages only runtime, tools, and samples into a fresh root before CPack. The package validator's explicit runtime layout checks that SDK-free tree against the trusted build's generated game-module inventory and the validator checkout's canonical SDK ABI header. It retains the complete required executable and runtime-content lists, every configured module's sidecar/schema/ABI/SHA-256 checks, and executable help/version smokes. The default sdk layout retains its installed inventory/header checks. Unknown layouts, runtime use outside stable-v1, or reference files inside the installed package are rejected.
The workflow retains runtime-layout.log on success and failure. Local fixtures test orchestration and rejection behavior; POSIX script stand-ins are not engine binaries. This preflight does not execute an MSI or NSIS installer and does not certify Windows 11, code signing, upgrades, rollback, or uninstall. Native installation qualification remains required.
The existing Windows Shipping CI job packages its already-built MinSizeRel tree with WiX and runs .github/scripts/qualify-windows-msi.py; versioned publication invokes the same gate after CPack and before artifact upload. The gate requires exactly one expected MSI filename and matching read-only database identity, refuses an already registered product or any registered product sharing its UpgradeCode, hashes the selected artifact, and installs into a unique directory under the runner temporary directory. After installation, it requires Windows Installer to report INSTALLSTATE_DEFAULT (5) for the selected product; absent, advertised, or broken registration fails before runtime execution, with uninstall still attempted. It validates the actual installed runtime layout and runs a five-frame installed SparkGameFPS headless smoke with module initialization required and a positive SPARK_MODULE_READY marker. This verifies startup/lifecycle, not interactive gameplay or an authored-scene acceptance test.
Uninstall is attempted in finally with the same MSI unless its bytes changed, preserving the original error if cleanup also fails. Reboot-required and other nonzero installer exits fail. Remaining product registration or install-root residue also fail; the script never conceals failure by recursively deleting installed files. Logs and a commit/artifact-bound result are retained separately from build-matrix evidence. Local injected-process fixtures establish orchestration behavior only. Actual hosted native results remain pending until CI runs; Windows Server evidence does not certify Windows 11, D3D11, signing, NSIS, upgrade, rollback, or user-data retention.
The install-directory override is INSTALL_ROOT, matching CMake's WiX template. The package's Directory table is checked before use. Microsoft documents quiet install/uninstall, no-restart, logging, and public properties and read-only MSI database access.
Versioned Windows publication runs .github/scripts/verify-windows-package-signatures.py after CPack and before native MSI installation or package upload. It requires exactly the expected versioned MinSizeRel Runtime EXE and MSI at the package directory's top level, rejects links/nonregular files and extra EXE/MSI entries, and uses the system Windows PowerShell Get-AuthenticodeSignature command. Microsoft documents that catalog signatures take precedence when present; this gate rejects that result. Both installers must report Valid, an embedded Authenticode signature (catalog signatures are insufficient), a timestamp certificate, and the explicit publisher certificate thumbprint configured in the repository variable SPARK_RELEASE_SIGNER_THUMBPRINT (40 hexadecimal characters). Certificate thumbprints identify the approved certificate; artifact content is bound separately with SHA-256.
Owners must provision the trusted publisher certificate identity and a secure signing pipeline for the final CPack outputs before this gate can pass. The workflow does not sign artifacts, invent a publisher identity, or accept an empty configuration. Current unsigned CPack outputs therefore block stable publication. Nightly packaging and ordinary Shipping CI installer qualification are unchanged.
The diagnostic JSON records the exact source commit, artifact SHA-256 values, publisher and timestamp certificate subjects/thumbprints, native signature status, and pass/failure. Each file is hashed before and after the native signature query; a second workflow check requires the same bytes after MSI qualification and immediately before package upload. Diagnostics are retained on failure as well as success. Injected-process tests cover rejected status, catalog signatures, mismatched signer, absent timestamp, malformed responses, process failures, file substitution, and selection defects. Windows-only unsigned-fixture subtests exercise the real PowerShell command independently for EXE and MSI in ordinary VS 2022 Release PR CI before compilation and early in the stable release job; local Linux runs skip them. A second-artifact-only rejection fixture also proves that a valid EXE result cannot hide an unsigned MSI.
This is a bounded first slice of REL-110, which remains open. It verifies only the outer NSIS EXE and MSI; it does not verify ZIP contents or internal PE payload signatures, prove NSIS installation behavior, provide independent consumer verification, or complete signing, scanning, protected approval, or Windows 11 qualification. Native signed-artifact success evidence remains pending the owner's signing setup and hosted run.
Windows SDE installation has a five-minute step limit and logs download, digest verification, extraction and executable-validation boundaries. The pinned URL/hash and exactly-one-executable requirement remain. Timeout is a failure, not CPU-floor evidence; these markers do not repair an unknown installation hang.
The comparator cleans up the actual a/b layout for permitted COFF build-root
normalization and a/tree-b in ordinary mode only after an equivalent result.
Failed comparisons retain both trees. This corrects successful cleanup without
changing byte comparison, OD-24 normalization or unresolved archive differences.
Windows Shipping also retains coff-diagnostics.json, capped at 1 MiB, alongside
the unchanged comparison report. It inspects one differing ordinary member per
static library and reports section hashes, header hashes and remaining bytes.
Unavailable inspection is explicit and preserves the comparison result. These
hashes locate differences; they do not prove their cause or permit additional
normalization. The cap applies to this JSON, not the complete hosted artifact.
Shipping retains a separate diagnostic-only JSON bundle for parity findings, inventory and the pending receipt when present. Its provenance records missing inputs explicitly; missing receipts are never synthesized. The bundle caps JSON at 12 MiB expanded and its inner ZIP at 960 KiB. Retrieval still requires checking the hosted outer artifact against the approved 1 MiB download cap and accounting for both archive layers. Existing full products, validators and failures remain. Unreviewed findings are labelled FINDINGS; only an exact reviewed baseline earns REVIEWED FINDINGS. No diagnostic bundle is release acceptance evidence.
Published from 614bca17b41e. Edit the canonical source in wiki/.
- Documentation
- Docs route
- Wiki index
- Guides
- Tutorials
- Samples
- Examples
- API Reference
- API route
- Reference
- Build Guide
- Dependencies
- FAQ
- Changelog
- Roadmap
- Contributing
- Code of Conduct
- Home
- FAQ
- Getting Started
- Quick-Start Tutorial
- Making Your First Game
- Making Your First Multiplayer Game
- Artist Workflow Guide
- Editor Walkthrough
- Migration Guide
- How SparkEngine Works
- Architecture Overview
- Engine Architecture Flowchart
- Creating a Game Module
- Game Modules (catalog)
- Entity Component System
- Rendering and Graphics
- Physics
- Cloth Simulation
- Audio
- Input System
- Camera System
- Scripting with AngelScript
- Visual Scripting
- AI and Navigation
- Animation
- 2D Systems
- Networking
- Dedicated Server
- Multiplayer Quick Start
- Area Server Architecture
- Scene Management
- Large World Support
- Collaborative Editing
- Coroutine System
- Event System
- Event Response System
- Job System
- UI System
- UI Layout Extensions
- Localization
- Dialogue System
- Destruction System
- Replay System
- Achievement System
- Loading System
- Mod System
- Content Delivery
- Tween System
- Memory Integrity
- Gameplay Systems
- Terrain and Procedural Generation
- Save System
- Persistence System
- Day Night Cycle and Weather
- Cinematic Sequencer
- Runtime Prefabs
- SparkEditor
- Editor Tutorials
- SparkConsole
- SparkDaemon
- Shader Pipeline
- Asset Pipeline
- Asset Validation
- Asset Migration
- Game Packaging
- Online Services
- DataTable System
- Loot and Crafting System
- CSG System
- Font System
- Timer Manager
- Movie Render Pipeline
- HLOD and World Partition
- Remote Debug System
- Selection Manager
- Asset Dependency Graph
- Editor Automation
- File Watcher
- Project Templates
- System Requirements
- VR Support
- Mobile Platform
- Accessibility
- Platform Input
- Platform Certification
- Cross-Compilation: Wine Testing
- RHI Abstraction Layer
- D3D11 Backend
- D3D12 Backend
- Vulkan Backend
- OpenGL Backend
- Metal Backend
- DXR Raytracing
- Hybrid Ray Tracing
- Upscaling (DLSS/FSR)
- Render Graph
- Shader Graph
- GPU Particles
- GPU-Driven Rendering
- Volumetric Fog
- Volumetric Clouds
- Global Illumination
- Virtual Texturing
- Water Rendering
- Clustered Lighting
- Material System
- Post-Processing
- Shadow System
- Particle System
- Decal System
- Sky and Atmosphere
- Foliage System
- Mesh Shaders
- Neural Rendering
- Configuration Reference
- Performance Tips
- Benchmark Framework
- Threading Model
- Fuzz Policy and Parser Security
- Memory Safety
- Memory Management Patterns
- Build System and CMake Modules
- Profiler and Debugging
- Performance Profiling Guide
- Telemetry System
- Crash Reporting
- Golden Image Testing
- Utilities
- Testing
- Fuzz Policy and Parser Security
- Codebase Statistics
- Codebase Health
- Error Handling Patterns
- Hot Reload Overview
- Troubleshooting
- Contributing
- Workflow Patterns
- Build Optimizations
- CI Reproducible Builds
- CI Controlled-Failure Rehearsal
- Release Publication Stages
- Session Handoff 2026-09-27
- GitHub API and PR Checks
- Git Rebase Conflicts
- Clang-Format
- Code Quality Violations
- AI Bloat Pattern
- MinGW + Wine Cross-Compilation
- Live Editor Testing
- Engine & Renderer Landscape
- DuetOS Portability Catalog
- Five-Engine Analysis
- Eleven-Engine Analysis
- ThorVG / Unity Graphics Analysis
- Advanced Techniques Catalog
- Third-Party Library Evaluation
- Engine Viability Evaluation
- Engine Feature Recommendations
- Project Recommendations
- Mac Compatibility Analysis
- Codebase Observations
- Codebase Bloat Audit
- Test Suite Audit
- Documentation Coverage Audit
- ThirdParty Dependencies Audit
- Load Test Baseline
- Gameplay Systems Status
- SparkGame Module Status
- Stub and Abandoned Features
- Memory Integrity System
- Memory Safety Evaluation
- Hardware Acceleration Systems
- Jolt Physics Integration
- GPU/CPU Separation Plan
- Daemon Services Architecture
- Online Service Boundary
- Server Operations Runbook
- Reflection & Polymorphism Refactoring Plan
- SparkBuild In-Tree
- Wine No-JobSystem Breakthrough
- Wine Role and Fallback Tiers