<# .SYNOPSIS Build multiple ISR frequency / command budget variants for A/B testing. Each variant produces a separate tsconf_*.img disk image. .DESCRIPTION Generates inc\variant_cfg.h for each variant, runs build.bat, copies the output image. Restores the original variant_cfg.h afterward. NO source files are regex-patched — only variant_cfg.h is overwritten. .NOTES C compilation is SLOW (~60 sec per variant). Do NOT interrupt (Ctrl-C). #> Set-StrictMode -Version Latest $ErrorActionPreference = 'Stop' $root = Split-Path -Parent $MyInvocation.MyCommand.Path # src_sdcc $projRoot = Split-Path -Parent $root # project root $cfgFile = "$root\inc\variant_cfg.h" # ── Frequency tiers ────────────────────────────────────────────────── # ISR_FREQ = 3 500 000 / Step; Entries = 71 680 / Step; TPF = Entries # Shift = log2(44100 / Freq) rounded down $freqTiers = @{ '2734' = @{ Tag=''; Freq=2734; Step=1280; Entries=56; Shift=4; Mask='0x0F'; TPF=56; Budgets=@(8,16) } '1367' = @{ Tag='hf'; Freq=1367; Step=2560; Entries=28; Shift=5; Mask='0x1F'; TPF=28; Budgets=@(8,16,32) } '683' = @{ Tag='qf'; Freq=683; Step=5120; Entries=14; Shift=6; Mask='0x3F'; TPF=14; Budgets=@(16,32,64) } } # ── Generate variants: 2+3+3 = 8 base × 2 NS = 16 total ───────────── $baseVariants = @() foreach ($ft in $freqTiers.Values | Sort-Object Freq -Descending) { foreach ($b in $ft.Budgets) { $suffix = if ($ft.Tag) { "_$($ft.Tag)" } else { '' } $name = "b${b}${suffix}" $baseVariants += @{ Name = $name Budget = $b Entries = $ft.Entries Step = $ft.Step Freq = $ft.Freq Shift = $ft.Shift Mask = $ft.Mask TPF = $ft.TPF DataLoc = '0xB600' NS = $false } } } # Auto-generate _ns (no short waits) clones $nsVariants = foreach ($bv in $baseVariants) { $nv = $bv.Clone() $nv.Name = $bv.Name + '_ns' $nv.NS = $true $nv } $variants = $baseVariants + $nsVariants # ── Save original variant_cfg.h ───────────────────────────────────── $cfgBackup = Get-Content $cfgFile -Raw function Restore-Cfg { Set-Content $script:cfgFile -Value $script:cfgBackup -NoNewline } # ── Helper: generate variant_cfg.h ────────────────────────────────── function Write-VariantCfg($v) { $nsLine = if ($v.NS) { "#define VGM_NO_SHORT_WAITS" } else { "/* #define VGM_NO_SHORT_WAITS */" } $content = @" /* variant_cfg.h -- AUTO-GENERATED by build_variants.ps1 * Variant: $($v.Name) Freq=$($v.Freq)Hz Budget=$($v.Budget) NS=$($v.NS) */ #ifndef VARIANT_CFG_H #define VARIANT_CFG_H #define ISR_FREQ $($v.Freq) #define ISR_TICKS_PER_FRAME $($v.TPF) #define VGM_SAMPLE_SHIFT $($v.Shift) #define VGM_SAMPLE_MASK $($v.Mask) #define VGM_FILL_CMD_BUDGET $($v.Budget) #define VARIANT_POS_ENTRIES $($v.Entries) #define VARIANT_POS_STEP $($v.Step) #define VARIANT_DATA_LOC $($v.DataLoc) $nsLine #endif /* VARIANT_CFG_H */ "@ Set-Content $script:cfgFile -Value $content -NoNewline } # ── Output directory ───────────────────────────────────────────────── $outDir = "$projRoot\variants" if (-not (Test-Path $outDir)) { New-Item -ItemType Directory -Path $outDir | Out-Null } # ── Build loop ────────────────────────────────────────────────────── $total = $variants.Count $idx = 0 $results = @() foreach ($v in $variants) { $idx++ $name = $v.Name Write-Host "`n========== [$idx/$total] Building $name ==========" -ForegroundColor Cyan Write-Host " Budget=$($v.Budget) Freq=$($v.Freq)Hz Entries=$($v.Entries) Step=$($v.Step) Shift=$($v.Shift) TPF=$($v.TPF) NS=$($v.NS)" # --- Generate variant_cfg.h --- Write-VariantCfg $v # --- Clean .rel files to force recompile --- Remove-Item "$root\build\*.rel" -ErrorAction SilentlyContinue # --- Run build --- Write-Host " Building... (this takes ~60 sec)" -ForegroundColor Yellow $buildOut = cmd /c "cd /d $root && build.bat 2>&1" $buildOk = $buildOut | Where-Object { $_ -match 'BUILD OK' } $buildFail = $buildOut | Where-Object { $_ -match 'FAIL|ERROR|overflow' } if ($buildFail -or -not $buildOk) { Write-Host " *** BUILD FAILED for $name ***" -ForegroundColor Red $buildOut | Where-Object { $_ -match 'FAIL|error|warning|_CODE|_DATA|Last byte|overflow|variant_cfg' } | ForEach-Object { Write-Host " $_" } $results += [pscustomobject]@{ Name=$name; Status='FAIL'; Code=0; Free=0 } continue } # --- Extract sizes from output --- $codeLine = $buildOut | Where-Object { $_ -match '_CODE' } | Select-Object -First 1 $freeLine = $buildOut | Where-Object { $_ -match 'Free before' } | Select-Object -First 1 $codeSize = if ($codeLine -match '(\d+)\s+bytes') { [int]$Matches[1] } else { 0 } $freeSize = if ($freeLine -match 'Free before C000:\s*(\d+)') { [int]$Matches[1] } else { 0 } # --- Show map --- $buildOut | Where-Object { $_ -match 'variant_cfg:|Section|_CODE|_DATA|_GSINIT|_HOME|_INITIAL|Last byte|Code:|Written.*WMF' } | ForEach-Object { Write-Host " $_" } # --- Copy output image --- $srcImg = "$projRoot\tsconf.img" $dstImg = "$outDir\tsconf_$name.img" if (Test-Path $srcImg) { Copy-Item $srcImg $dstImg -Force Write-Host " -> $dstImg" -ForegroundColor Green } else { Write-Host " WARNING: tsconf.img not found" -ForegroundColor Yellow } # Also copy WMF $srcWmf = "$projRoot\DiskRef\WC\VGMPLAY.WMF" $dstWmf = "$outDir\VGMPLAY_$name.WMF" if (Test-Path $srcWmf) { Copy-Item $srcWmf $dstWmf -Force } $results += [pscustomobject]@{ Name=$name; Status='OK'; Code=$codeSize; Free=$freeSize } } # ── Restore original variant_cfg.h ────────────────────────────────── Write-Host "`n========== Restoring original variant_cfg.h ==========" -ForegroundColor Cyan Restore-Cfg # Rebuild baseline to leave workspace clean Remove-Item "$root\build\*.rel" -ErrorAction SilentlyContinue Write-Host " Rebuilding baseline..." -ForegroundColor Yellow $null = cmd /c "cd /d $root && build.bat 2>&1" Write-Host " Done." -ForegroundColor Green # ── Summary ────────────────────────────────────────────────────────── Write-Host "`n========== VARIANT SUMMARY ==========" -ForegroundColor Cyan $fmt = "{0,-18} {1,6} {2,6} {3,6} {4,3} {5,5} {6,5}" Write-Host ($fmt -f 'Name','Budget','Freq','Code','NS','Free','St') Write-Host ($fmt -f '----','------','----','----','--','----','--') foreach ($r in $results) { $v = $variants | Where-Object { $_.Name -eq $r.Name } $nsTag = if ($v.NS) { 'yes' } else { ' - ' } $color = if ($r.Status -eq 'OK') { 'Green' } else { 'Red' } Write-Host ($fmt -f $r.Name, $v.Budget, $v.Freq, $r.Code, $nsTag, $r.Free, $r.Status) -ForegroundColor $color } Write-Host "`nImages in: $outDir" # ── Generate README.txt ────────────────────────────────────────────── $readme = @" Variants of VGM player for A/B testing ======================================= Parameters stored in inc\variant_cfg.h (single config file). build_variants.ps1 regenerates variant_cfg.h for each variant. Frequency tiers: qf - quarter freq (ISR 683 Hz, step 5120, 14 ticks/frame) (none) - base freq (ISR 2734 Hz, step 1280, 56 ticks/frame) hf - half freq (ISR 1367 Hz, step 2560, 28 ticks/frame) Other tags: b - command budget (max commands between forced pauses) ns - no short waits (0x70-0x7F pauses skipped) "@ $readme += "{0,-30} {1,6} {2,8} {3,3} {4,6} {5,5} {6}`n" -f 'Image','Budget','Freq','NS','Code','Free','Status' $readme += "{0,-30} {1,6} {2,8} {3,3} {4,6} {5,5} {6}`n" -f '-----','------','----','--','----','----','------' foreach ($r in $results) { $v = $variants | Where-Object { $_.Name -eq $r.Name } $nsTag = if ($v.NS) { 'yes' } else { ' - ' } $freq = "$($v.Freq)Hz" $readme += "{0,-30} {1,6} {2,8} {3,3} {4,6} {5,5} {6}`n" -f "tsconf_$($r.Name).img", $v.Budget, $freq, $nsTag, $r.Code, $r.Free, $r.Status } $readme += @" What to test ------------ 1. Higher budget helps (b32/b48/b64 vs b8/b16) -> Increase VGM_FILL_CMD_BUDGET. 2. Lower freq helps (hf or qf better than base) -> ISR overhead is the bottleneck, reduce frequency. 3. Higher freq helps (base better than hf/qf) -> More granular timing needed, keep higher ISR. 4. _ns variants better -> short waits 0x70-0x7F are the problem. 5. Nothing helps -> something else (OPL3 write delay, VGM parsing, etc.) "@ Set-Content "$outDir\README.txt" -Value $readme -NoNewline -Encoding UTF8 Write-Host "`nREADME.txt written to $outDir" -ForegroundColor Green Write-Host "`nLegend:" -ForegroundColor DarkGray Write-Host " qf = quarter freq (683 Hz)" -ForegroundColor DarkGray Write-Host " hf = half freq (1367 Hz)" -ForegroundColor DarkGray Write-Host " (no suffix) = base freq (2734 Hz)" -ForegroundColor DarkGray Write-Host " bN = budget N commands per ISR tick" -ForegroundColor DarkGray Write-Host " ns = no short waits (0x70-0x7F skipped)" -ForegroundColor DarkGray