From 35bddaab74a09ca0fabd204836471642d7b84c03 Mon Sep 17 00:00:00 2001 From: Alan Tse Date: Tue, 2 Jun 2026 22:32:48 -0700 Subject: [PATCH 1/4] build(tools): add shader-refactor bytecode verifier verify-shader-refactor.ps1 (with a bash wrapper) compiles a shader from a base git ref and from the working tree across the VR x HDR_OUTPUT permutations, then compares the compiled DXBC. Identical SHA-256 proves a refactor is behavior-preserving; on mismatch it diffs the /Fc assembly. Exit 0 = all identical, 2 = differs, 1 = compile error. Tested both ways: detects the TAA VR fix as DIFFERS (VR perms only) and the TAA constants/alpha-loop refactor as IDENTICAL across all permutations. Co-Authored-By: Claude Opus 4.8 (1M context) --- docs/development/shader-workflow.md | 19 +++ tools/verify-shader-refactor.ps1 | 189 ++++++++++++++++++++++++++++ tools/verify-shader-refactor.sh | 19 +++ 3 files changed, 227 insertions(+) create mode 100644 tools/verify-shader-refactor.ps1 create mode 100644 tools/verify-shader-refactor.sh diff --git a/docs/development/shader-workflow.md b/docs/development/shader-workflow.md index f32233a73e..280541fcde 100644 --- a/docs/development/shader-workflow.md +++ b/docs/development/shader-workflow.md @@ -8,8 +8,27 @@ cmake --build build/ALL --target COPY_SHADERS # Full deployment (DLL + tests + shaders) cmake --build build/ALL --target DEPLOY_ALL + +# Prove an HLSL refactor changed no behavior (compares compiled DXBC vs a git ref) +pwsh tools/verify-shader-refactor.ps1 package/Shaders/Foo.hlsl # or tools/verify-shader-refactor.sh ``` +## Verifying refactors + +`tools/verify-shader-refactor.ps1` (bash wrapper: `tools/verify-shader-refactor.sh`) +compiles a shader from a base git ref and from the working tree across the +`VR` × `HDR_OUTPUT` permutations, then compares the compiled bytecode: + +- **IDENTICAL** SHA-256 of the `.cso` ⇒ the refactor is a provable no-op (fxc emits + no timestamps without `/Zi`, so identical source ⇒ identical bytes). +- **DIFFERS** ⇒ it dumps and diffs the `/Fc` assembly so a legitimate-but-non-identical + change can be reviewed. + +Exit codes: `0` all identical, `2` some differ, `1` compile error. Defaults to comparing +the working tree against `merge-base(HEAD, origin/dev)`; pass `-BaseRef ` to override. +Requires `fxc.exe` from the Windows SDK. The permutation sweep is strong evidence, not the +full `shader-validation.yaml` matrix — pass `-Permutations` for exotic define combos. + ## Overview Two deployment targets for different workflows: diff --git a/tools/verify-shader-refactor.ps1 b/tools/verify-shader-refactor.ps1 new file mode 100644 index 0000000000..a652e99cc8 --- /dev/null +++ b/tools/verify-shader-refactor.ps1 @@ -0,0 +1,189 @@ +<# +.SYNOPSIS + Prove an HLSL refactor is behavior-preserving by comparing compiled bytecode. + +.DESCRIPTION + Compiles a shader from a base git revision and from the current working tree + across a set of preprocessor permutations, then compares the resulting DXBC. + + Tier 1 (this script): identical SHA-256 of the compiled .cso == provably identical + GPU program. fxc emits no timestamps without /Zi, so same source -> same bytes. + + Tier 2 (this script, on mismatch): dumps /Fc assembly for both revisions and prints + a unified diff, so a legitimate-but-non-identical refactor (e.g. register reorder) + can be eyeballed. + + A refactor that is Tier-1 IDENTICAL on the swept permutations needs no further proof. + Note: the default sweep (VR x HDR_OUTPUT) is strong evidence, not the full build + matrix from shader-validation.yaml. Pass -Permutations for exotic define combos. + +.PARAMETER Shader + Path to the .hlsl file (repo-relative or absolute). + +.PARAMETER BaseRef + Git ref to treat as "before". Default: merge-base of HEAD and origin/dev. + +.PARAMETER IncludeDir + Shader include root passed to fxc /I. Default: package/Shaders. + +.PARAMETER Permutations + Optional explicit permutation list; each entry is a space-separated define set, + e.g. -Permutations "PSHADER","PSHADER VR". Overrides the auto sweep. + +.PARAMETER Entry + Shader entry point. Default: main. + +.PARAMETER Profile + fxc target profile. Default: auto (cs_5_0 for *CS.hlsl, else ps_5_0). + +.EXAMPLE + pwsh tools/verify-shader-refactor.ps1 package/Shaders/ISTemporalAA.hlsl + +.EXAMPLE + pwsh tools/verify-shader-refactor.ps1 package/Shaders/Foo.hlsl -BaseRef HEAD~1 +#> +[CmdletBinding()] +param( + [Parameter(Mandatory = $true, Position = 0)] + [string]$Shader, + [string]$BaseRef, + [string]$IncludeDir = "package/Shaders", + [string[]]$Permutations, + [string]$Entry = "main", + [string]$Profile, + [string]$Fxc +) + +# Continue (not Stop): native git calls write warnings to stderr that would otherwise +# abort the run; control flow keys off explicit $LASTEXITCODE checks and `throw`. +$ErrorActionPreference = "Continue" + +function Resolve-Fxc { + if ($Fxc -and (Test-Path $Fxc)) { return $Fxc } + $cmd = Get-Command fxc.exe -ErrorAction SilentlyContinue + if ($cmd) { return $cmd.Source } + $roots = @("${env:ProgramFiles(x86)}\Windows Kits\10\bin", "${env:ProgramFiles}\Windows Kits\10\bin") + $found = foreach ($r in $roots) { + if (Test-Path $r) { + Get-ChildItem -Path $r -Recurse -Filter fxc.exe -ErrorAction SilentlyContinue | + Where-Object { $_.FullName -match "x64" } + } + } + $pick = $found | Sort-Object FullName -Descending | Select-Object -First 1 + if (-not $pick) { throw "fxc.exe not found. Install the Windows 10/11 SDK or pass -Fxc." } + return $pick.FullName +} + +# Resolve repo root so the script works from any cwd. +$repoRoot = (git rev-parse --show-toplevel 2>$null) +if (-not $repoRoot) { throw "Not inside a git repository." } +Push-Location $repoRoot +try { + $fxcPath = Resolve-Fxc + + # Normalize the shader path to repo-relative (forward slashes) for `git show`. + # (Path.GetRelativePath is unavailable in Windows PowerShell 5.1 / .NET Framework.) + $shaderFull = (Resolve-Path $Shader).Path + $rootFull = (Resolve-Path $repoRoot).Path + $relPath = $shaderFull.Substring($rootFull.Length).TrimStart('\', '/').Replace('\', '/') + + if (-not $BaseRef) { + $BaseRef = (git merge-base HEAD origin/dev 2>$null) + if (-not $BaseRef) { $BaseRef = "HEAD" } + } + + if (-not $Profile) { + $Profile = if ($relPath -match 'CS\.hlsl$') { "cs_5_0" } else { "ps_5_0" } + } + $stageDefine = switch -Wildcard ($Profile) { + "cs_*" { "CSHADER" } + "vs_*" { "VSHADER" } + default { "PSHADER" } + } + + if (-not $Permutations -or $Permutations.Count -eq 0) { + $Permutations = @( + "$stageDefine", + "$stageDefine VR", + "$stageDefine HDR_OUTPUT", + "$stageDefine VR HDR_OUTPUT" + ) + } + + # Materialize the base revision of the file. + $work = Join-Path ([IO.Path]::GetTempPath()) ("shaderverify_" + [Guid]::NewGuid().ToString("N")) + New-Item -ItemType Directory -Force $work | Out-Null + $baseFile = Join-Path $work "base.hlsl" + $baseBlob = "${BaseRef}:${relPath}" + # Capture and write as UTF-8 (no BOM); PS 5.1 `>` redirection emits UTF-16, which fxc misreads. + $baseContent = git show $baseBlob 2>$null + if ($LASTEXITCODE -ne 0 -or -not $baseContent) { + throw "Could not read '$relPath' at '$BaseRef' (git show $baseBlob)." + } + [IO.File]::WriteAllLines($baseFile, $baseContent) + + function Compile([string]$src, [string]$defs, [string]$outFile, [switch]$Asm) { + $defArgs = @(); foreach ($d in ($defs -split '\s+' | Where-Object { $_ })) { $defArgs += "/D"; $defArgs += "$d=1" } + $fmt = if ($Asm) { "/Fc" } else { "/Fo" } + $out = & $fxcPath /nologo /T $Profile /E $Entry @defArgs /I $IncludeDir $src $fmt $outFile 2>&1 + return @{ Code = $LASTEXITCODE; Out = $out } + } + + Write-Host "Shader : $relPath" + Write-Host "Base ref : $BaseRef" + Write-Host "Profile : $Profile (entry $Entry)" + Write-Host "Include : $IncludeDir" + Write-Host ("-" * 60) + + $allIdentical = $true + $anyError = $false + + foreach ($perm in $Permutations) { + $tag = $perm + $baseCso = Join-Path $work "base.cso" + $workCso = Join-Path $work "work.cso" + $rb = Compile $baseFile $perm $baseCso + $rw = Compile $shaderFull $perm $workCso + + if ($rb.Code -ne 0 -or $rw.Code -ne 0) { + $anyError = $true + $which = if ($rb.Code -ne 0) { "BASE" } else { "WORK" } + Write-Host "[$tag] COMPILE-ERROR ($which)" -ForegroundColor Red + ($(if ($rb.Code -ne 0) { $rb.Out } else { $rw.Out }) | Where-Object { $_ -match 'error|warning' } | Select-Object -First 6) | + ForEach-Object { Write-Host " $_" } + continue + } + + $hb = (Get-FileHash $baseCso -Algorithm SHA256).Hash + $hw = (Get-FileHash $workCso -Algorithm SHA256).Hash + if ($hb -eq $hw) { + Write-Host "[$tag] IDENTICAL" -ForegroundColor Green + } else { + $allIdentical = $false + Write-Host "[$tag] DIFFERS base=$($hb.Substring(0,12)) work=$($hw.Substring(0,12))" -ForegroundColor Yellow + # Tier 2: assembly diff for inspection (Compare-Object avoids git's CRLF/exit noise). + $baseAsm = Join-Path $work "base.asm"; $workAsm = Join-Path $work "work.asm" + Compile $baseFile $perm $baseAsm -Asm | Out-Null + Compile $shaderFull $perm $workAsm -Asm | Out-Null + $d = Compare-Object (Get-Content $baseAsm) (Get-Content $workAsm) + if ($d) { + $d | Select-Object -First 40 | ForEach-Object { + $mark = if ($_.SideIndicator -eq '=>') { 'work' } else { 'base' } + Write-Host (" [{0}] {1}" -f $mark, $_.InputObject) + } + if (@($d).Count -gt 40) { Write-Host (" ... (+{0} more asm lines)" -f (@($d).Count - 40)) } + } + } + } + + Write-Host ("-" * 60) + if ($anyError) { Write-Host "RESULT: compile error" -ForegroundColor Red; $exit = 1 } + elseif ($allIdentical) { Write-Host "RESULT: behavior-preserving (all permutations identical)" -ForegroundColor Green; $exit = 0 } + else { Write-Host "RESULT: bytecode differs - inspect asm diff above" -ForegroundColor Yellow; $exit = 2 } + + Remove-Item -Recurse -Force $work -ErrorAction SilentlyContinue + exit $exit +} +finally { + Pop-Location +} diff --git a/tools/verify-shader-refactor.sh b/tools/verify-shader-refactor.sh new file mode 100644 index 0000000000..6f87588956 --- /dev/null +++ b/tools/verify-shader-refactor.sh @@ -0,0 +1,19 @@ +#!/usr/bin/env bash +# Thin wrapper around verify-shader-refactor.ps1 for bash/WSL/git-bash users. +# fxc.exe is Windows-only and MSYS mangles its /switches, so the real work lives +# in PowerShell. This just forwards arguments verbatim. +# +# Usage: tools/verify-shader-refactor.sh package/Shaders/Foo.hlsl [-BaseRef HEAD~1] ... +set -euo pipefail + +here="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +ps1="$here/verify-shader-refactor.ps1" + +if command -v pwsh >/dev/null 2>&1; then + exec pwsh -NoProfile -ExecutionPolicy Bypass -File "$ps1" "$@" +elif command -v powershell.exe >/dev/null 2>&1; then + exec powershell.exe -NoProfile -ExecutionPolicy Bypass -File "$ps1" "$@" +else + echo "Need PowerShell (pwsh or powershell.exe) on PATH." >&2 + exit 1 +fi From 89f4d6120fb55191e4e704ba5984e59b63c2dff2 Mon Sep 17 00:00:00 2001 From: Alan Tse Date: Tue, 2 Jun 2026 22:41:43 -0700 Subject: [PATCH 2/4] fix(tools): address review on shader-refactor verifier - Preserve explicit-valued -Permutations defines (e.g. SHADOWFILTER=0); the builder no longer appends =1 to them (was producing FOO=0=1). - Move temp-dir cleanup into finally so it never leaks on a thrown error. - Correct help text: Tier-2 lists differing asm lines, not a unified diff. Co-Authored-By: Claude Opus 4.8 (1M context) --- tools/verify-shader-refactor.ps1 | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/tools/verify-shader-refactor.ps1 b/tools/verify-shader-refactor.ps1 index a652e99cc8..c8271154c3 100644 --- a/tools/verify-shader-refactor.ps1 +++ b/tools/verify-shader-refactor.ps1 @@ -9,9 +9,9 @@ Tier 1 (this script): identical SHA-256 of the compiled .cso == provably identical GPU program. fxc emits no timestamps without /Zi, so same source -> same bytes. - Tier 2 (this script, on mismatch): dumps /Fc assembly for both revisions and prints - a unified diff, so a legitimate-but-non-identical refactor (e.g. register reorder) - can be eyeballed. + Tier 2 (this script, on mismatch): dumps /Fc assembly for both revisions and lists + the differing lines (base/work markers), so a legitimate-but-non-identical refactor + (e.g. register reorder) can be eyeballed. A refactor that is Tier-1 IDENTICAL on the swept permutations needs no further proof. Note: the default sweep (VR x HDR_OUTPUT) is strong evidence, not the full build @@ -78,6 +78,7 @@ function Resolve-Fxc { $repoRoot = (git rev-parse --show-toplevel 2>$null) if (-not $repoRoot) { throw "Not inside a git repository." } Push-Location $repoRoot +$work = $null try { $fxcPath = Resolve-Fxc @@ -123,7 +124,12 @@ try { [IO.File]::WriteAllLines($baseFile, $baseContent) function Compile([string]$src, [string]$defs, [string]$outFile, [switch]$Asm) { - $defArgs = @(); foreach ($d in ($defs -split '\s+' | Where-Object { $_ })) { $defArgs += "/D"; $defArgs += "$d=1" } + # Preserve explicit-valued defines (e.g. SHADOWFILTER=0); only bare names get =1. + $defArgs = @() + foreach ($d in ($defs -split '\s+' | Where-Object { $_ })) { + $defArgs += "/D" + $defArgs += $(if ($d -like '*=*') { $d } else { "$d=1" }) + } $fmt = if ($Asm) { "/Fc" } else { "/Fo" } $out = & $fxcPath /nologo /T $Profile /E $Entry @defArgs /I $IncludeDir $src $fmt $outFile 2>&1 return @{ Code = $LASTEXITCODE; Out = $out } @@ -181,9 +187,10 @@ try { elseif ($allIdentical) { Write-Host "RESULT: behavior-preserving (all permutations identical)" -ForegroundColor Green; $exit = 0 } else { Write-Host "RESULT: bytecode differs - inspect asm diff above" -ForegroundColor Yellow; $exit = 2 } - Remove-Item -Recurse -Force $work -ErrorAction SilentlyContinue exit $exit } finally { + # Runs on normal exit and on throw, so the temp dir never leaks. + if ($work -and (Test-Path $work)) { Remove-Item -Recurse -Force $work -ErrorAction SilentlyContinue } Pop-Location } From 7dfbb0ecadf50673180b3a3c2b86a5024e726929 Mon Sep 17 00:00:00 2001 From: Alan Tse Date: Tue, 2 Jun 2026 22:44:15 -0700 Subject: [PATCH 3/4] docs: note shader-refactor verifier in CLAUDE.md --- .claude/CLAUDE.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.claude/CLAUDE.md b/.claude/CLAUDE.md index 77a3bc3c0e..318e7d3e32 100644 --- a/.claude/CLAUDE.md +++ b/.claude/CLAUDE.md @@ -99,8 +99,17 @@ hlslkit-generate-defines --log CommunityShaders.log # Scan for buffer conflicts across features hlslkit-buffer-scan --features-dir features/ + +# Prove a shader refactor changed no behavior (compiles base ref vs working tree, +# compares DXBC across VR x HDR_OUTPUT permutations; exit 0 identical / 2 differs) +pwsh tools/verify-shader-refactor.ps1 package/Shaders/Foo.hlsl # bash: tools/verify-shader-refactor.sh ``` +When refactoring an existing shader (especially the decompile-transcription shaders like +`ISTemporalAA.hlsl`), use `tools/verify-shader-refactor.ps1` to prove the change is +behavior-preserving: identical compiled bytecode means a provable no-op. See +`docs/development/shader-workflow.md` for details. + ### Custom CMake Targets **Package and Deployment Targets**: From 26ef2b5ce4705df3fc1c46f74eb143d5df654cb8 Mon Sep 17 00:00:00 2001 From: Alan Tse Date: Tue, 2 Jun 2026 23:16:11 -0700 Subject: [PATCH 4/4] fix(tools): compile base ref's full include tree in verifier Addresses review: the verifier only materialized the target .hlsl from the base ref while reading included .hlsli from the working tree for BOTH the base and work compiles, so a refactor that also edited a shared header was masked (both sides saw the new header) and the run could still claim "behavior- preserving". Now `git archive` materializes the base ref's whole -IncludeDir tree; base compiles against base-ref headers, work against working headers. Also guard against a shader path resolving outside the repo root. Co-Authored-By: Claude Opus 4.8 (1M context) --- docs/development/shader-workflow.md | 5 +++- tools/verify-shader-refactor.ps1 | 46 ++++++++++++++++++----------- 2 files changed, 33 insertions(+), 18 deletions(-) diff --git a/docs/development/shader-workflow.md b/docs/development/shader-workflow.md index 280541fcde..bf58da9431 100644 --- a/docs/development/shader-workflow.md +++ b/docs/development/shader-workflow.md @@ -17,7 +17,10 @@ pwsh tools/verify-shader-refactor.ps1 package/Shaders/Foo.hlsl # or tools/veri `tools/verify-shader-refactor.ps1` (bash wrapper: `tools/verify-shader-refactor.sh`) compiles a shader from a base git ref and from the working tree across the -`VR` × `HDR_OUTPUT` permutations, then compares the compiled bytecode: +`VR` × `HDR_OUTPUT` permutations, then compares the compiled bytecode. The base +ref's whole include tree is materialized (via `git archive`), so the base compiles +against base-ref `.hlsli` headers and the working tree against working headers — a +refactor that also edits a shared header is compared correctly, not masked: - **IDENTICAL** SHA-256 of the `.cso` ⇒ the refactor is a provable no-op (fxc emits no timestamps without `/Zi`, so identical source ⇒ identical bytes). diff --git a/tools/verify-shader-refactor.ps1 b/tools/verify-shader-refactor.ps1 index c8271154c3..c18954f977 100644 --- a/tools/verify-shader-refactor.ps1 +++ b/tools/verify-shader-refactor.ps1 @@ -5,6 +5,9 @@ .DESCRIPTION Compiles a shader from a base git revision and from the current working tree across a set of preprocessor permutations, then compares the resulting DXBC. + The base ref's entire include tree (-IncludeDir) is materialized via git archive, + so the base compiles against base-ref headers and the working tree against working + headers -- a refactor that also edits a shared .hlsli is therefore compared correctly. Tier 1 (this script): identical SHA-256 of the compiled .cso == provably identical GPU program. fxc emits no timestamps without /Zi, so same source -> same bytes. @@ -82,10 +85,13 @@ $work = $null try { $fxcPath = Resolve-Fxc - # Normalize the shader path to repo-relative (forward slashes) for `git show`. + # Normalize the shader path to repo-relative (forward slashes) for git. # (Path.GetRelativePath is unavailable in Windows PowerShell 5.1 / .NET Framework.) $shaderFull = (Resolve-Path $Shader).Path $rootFull = (Resolve-Path $repoRoot).Path + if (-not $shaderFull.StartsWith($rootFull, [StringComparison]::OrdinalIgnoreCase)) { + throw "Shader '$Shader' resolves outside the repo root '$rootFull'." + } $relPath = $shaderFull.Substring($rootFull.Length).TrimStart('\', '/').Replace('\', '/') if (-not $BaseRef) { @@ -111,19 +117,25 @@ try { ) } - # Materialize the base revision of the file. + # Materialize the base revision's FULL include tree (not just the target shader) so a + # refactor that also touches a shared .hlsli is compared correctly: base compiles against + # base-ref headers, work compiles against working-tree headers. git archive -> tar (via a + # file, never a PS pipeline, which would corrupt the binary tar). $work = Join-Path ([IO.Path]::GetTempPath()) ("shaderverify_" + [Guid]::NewGuid().ToString("N")) - New-Item -ItemType Directory -Force $work | Out-Null - $baseFile = Join-Path $work "base.hlsl" - $baseBlob = "${BaseRef}:${relPath}" - # Capture and write as UTF-8 (no BOM); PS 5.1 `>` redirection emits UTF-16, which fxc misreads. - $baseContent = git show $baseBlob 2>$null - if ($LASTEXITCODE -ne 0 -or -not $baseContent) { - throw "Could not read '$relPath' at '$BaseRef' (git show $baseBlob)." + $baseRoot = Join-Path $work "base" + New-Item -ItemType Directory -Force $baseRoot | Out-Null + $tar = Join-Path $work "base.tar" + git archive --format=tar -o $tar $BaseRef -- $IncludeDir $relPath 2>$null + if ($LASTEXITCODE -ne 0 -or -not (Test-Path $tar)) { + throw "git archive failed for '$BaseRef' (paths: $IncludeDir, $relPath)." } - [IO.File]::WriteAllLines($baseFile, $baseContent) + tar -xf $tar -C $baseRoot + if ($LASTEXITCODE -ne 0) { throw "Failed to extract base archive." } + $baseFile = Join-Path $baseRoot $relPath + $baseInclude = Join-Path $baseRoot $IncludeDir + if (-not (Test-Path $baseFile)) { throw "'$relPath' not found at '$BaseRef'." } - function Compile([string]$src, [string]$defs, [string]$outFile, [switch]$Asm) { + function Compile([string]$src, [string]$incDir, [string]$defs, [string]$outFile, [switch]$Asm) { # Preserve explicit-valued defines (e.g. SHADOWFILTER=0); only bare names get =1. $defArgs = @() foreach ($d in ($defs -split '\s+' | Where-Object { $_ })) { @@ -131,12 +143,12 @@ try { $defArgs += $(if ($d -like '*=*') { $d } else { "$d=1" }) } $fmt = if ($Asm) { "/Fc" } else { "/Fo" } - $out = & $fxcPath /nologo /T $Profile /E $Entry @defArgs /I $IncludeDir $src $fmt $outFile 2>&1 + $out = & $fxcPath /nologo /T $Profile /E $Entry @defArgs /I $incDir $src $fmt $outFile 2>&1 return @{ Code = $LASTEXITCODE; Out = $out } } Write-Host "Shader : $relPath" - Write-Host "Base ref : $BaseRef" + Write-Host "Base ref : $BaseRef (full include tree materialized)" Write-Host "Profile : $Profile (entry $Entry)" Write-Host "Include : $IncludeDir" Write-Host ("-" * 60) @@ -148,8 +160,8 @@ try { $tag = $perm $baseCso = Join-Path $work "base.cso" $workCso = Join-Path $work "work.cso" - $rb = Compile $baseFile $perm $baseCso - $rw = Compile $shaderFull $perm $workCso + $rb = Compile $baseFile $baseInclude $perm $baseCso + $rw = Compile $shaderFull $IncludeDir $perm $workCso if ($rb.Code -ne 0 -or $rw.Code -ne 0) { $anyError = $true @@ -169,8 +181,8 @@ try { Write-Host "[$tag] DIFFERS base=$($hb.Substring(0,12)) work=$($hw.Substring(0,12))" -ForegroundColor Yellow # Tier 2: assembly diff for inspection (Compare-Object avoids git's CRLF/exit noise). $baseAsm = Join-Path $work "base.asm"; $workAsm = Join-Path $work "work.asm" - Compile $baseFile $perm $baseAsm -Asm | Out-Null - Compile $shaderFull $perm $workAsm -Asm | Out-Null + Compile $baseFile $baseInclude $perm $baseAsm -Asm | Out-Null + Compile $shaderFull $IncludeDir $perm $workAsm -Asm | Out-Null $d = Compare-Object (Get-Content $baseAsm) (Get-Content $workAsm) if ($d) { $d | Select-Object -First 40 | ForEach-Object {