diff --git a/docs/PROJECT-STRUCTURE.md b/docs/PROJECT-STRUCTURE.md index dd2091e..fe6846e 100644 --- a/docs/PROJECT-STRUCTURE.md +++ b/docs/PROJECT-STRUCTURE.md @@ -28,6 +28,9 @@ S:\Game Hacking\Eushully\Himegari\ ← workspace root (three siblings) │ └── age-reimpl/ ← OUR WORK (everything we made lives here) │ + ├── run-godot.ps1 / run-godot.cmd tracked development launcher + Windows wrapper; + │ resolves Godot/game-root from parameters, environment, + │ PATH, and the conventional sibling install ├── tools/ Python tooling (parser/disassembler + extractors + VM) │ ├── paths.py ★ central path anchor — the ONLY place that knows │ │ where the game / extracted / build dirs are. All diff --git a/docs/tools-reference.md b/docs/tools-reference.md index 7911de3..283badf 100644 --- a/docs/tools-reference.md +++ b/docs/tools-reference.md @@ -311,7 +311,7 @@ at 2.5M lines). All observe-only → parity preserved; all on `run`/`play`/`swee steps / 493k sleeps). Dumped when the scene ends or the window closes. e.g. `godot --path godot -- --scene SC0000 --boot --shot out/p1.png --trace-histogram out/hist.txt`. -**Godot frontend** (`S:/Godot/Godot_v4.7…`; project = `godot/`). Toolchain: `godot --headless --path godot +**Godot frontend** (Godot 4.7 .NET; project = `godot/`). Toolchain: `godot --headless --path godot --import` → `dotnet build godot/Himegari.csproj` → `godot [--headless] --path godot [-- ]`. Plays the real bytecode with call-script execution on (subroutines run live). The game root is selected before catalog loading: `--game-root ` wins, otherwise the directory containing the running @@ -350,9 +350,13 @@ input, or audible output; run those gates on a real Linux desktop. With the exported executable placed in an AGE install, a no-argument launch therefore uses that install and starts the persistent `SYSTEM4.BIN` root naturally. Direct development runs are hosted by the Godot -editor executable, so pass `--game-root ` after Godot's `--` separator; the local -`run-godot.ps1`/`.cmd` launchers pass the local script's directly configured `$gameRoot` value. Edit that -one assignment when testing another installed game. Those launchers make the natural route explicit with +editor executable, so pass `--game-root ` after Godot's `--` separator. The tracked +`run-godot.ps1`/`.cmd` launchers resolve Godot and the game root without machine-specific tracked paths. +Explicit `-GodotConsole`/`-GameRoot` values win, followed by `AGE_GODOT_CONSOLE`/`AGE_GAME_ROOT`; Godot +then falls back to `godot4`, `godot`, or `godot-mono` on `PATH`, while the game root falls back to the +conventional `../Himegari_Game` sibling only when it contains `SYS4INI.BIN`. `run-godot.ps1 -Doctor` +prints the resolved repository, Godot, game-root, .NET, and Python prerequisites without building or +launching. The launchers make the natural route explicit with `--scene SYSTEM4` and pass neither `--boot` nor SC0000 seeds. Examples below focus on their feature-specific arguments and assume the game root is already selected this way. `--headless` can't render texture ops (no GPU context) — run windowed for real scenes. diff --git a/run-godot.cmd b/run-godot.cmd new file mode 100644 index 0000000..8ef125e --- /dev/null +++ b/run-godot.cmd @@ -0,0 +1,5 @@ +@echo off +REM Default windowed launch follows SYSTEM4, exposes TITLE's shipped developer menu, +REM and uses the accepted GPU retained renderer. Performance logging is opt-in again. +REM -StartupDiagnostics overrides the developer-menu flag for a native-faithful capture. +powershell -NoProfile -ExecutionPolicy Bypass -File "%~dp0run-godot.ps1" -NativeDebugMenu %* diff --git a/run-godot.ps1 b/run-godot.ps1 new file mode 100644 index 0000000..509f237 --- /dev/null +++ b/run-godot.ps1 @@ -0,0 +1,184 @@ +# run-godot.ps1 -- build the C# and launch the Himegari Godot project (how we've been running it). +# +# .\run-godot.ps1 build, then run the persistent SYSTEM4 root windowed +# .\run-godot.ps1 -SelfTest build, then headless self-test (asserts the dialogue trace vs vm0) +# .\run-godot.ps1 -Import also (re)import the project first (needed after adding assets) +# .\run-godot.ps1 -NoBuild skip the dotnet build, just launch +# .\run-godot.ps1 -NativeDebugMenu expose TITLE's unreachable shipped developer menu +# .\run-godot.ps1 -StartupDiagnostics capture the natural TITLE -> Game Start -> SC0000 route +# .\run-godot.ps1 -PerfLog write a timestamped frame/compositor CSV under build/perf +# .\run-godot.ps1 -SoftwareRenderer use the retained software correctness oracle +# .\run-godot.ps1 -Doctor resolve and print prerequisites without building or launching +# +# Uses the *console* Godot build so GD.Print / DRAW logs / the selftest result show in the terminal. +# Resolution order is explicit parameter, AGE_* environment variable, then a portable fallback. +param( + [string]$GodotConsole, + [string]$GameRoot, + [switch]$SelfTest, + [switch]$Import, + [switch]$NoBuild, + [switch]$NativeDebugMenu, + [switch]$StartupDiagnostics, + [switch]$PerfLog, + [switch]$SoftwareRenderer, + [switch]$Doctor +) + +$ErrorActionPreference = 'Stop' +$repo = $PSScriptRoot # age-reimpl/ +$project = Join-Path $repo 'godot' +$csproj = Join-Path $project 'Himegari.csproj' + +function Resolve-ConfiguredFile { + param( + [string]$ExplicitValue, + [string]$EnvironmentValue, + [string[]]$PathCommands, + [string]$Description + ) + + $configured = if ($ExplicitValue) { $ExplicitValue } elseif ($EnvironmentValue) { $EnvironmentValue } else { $null } + if ($configured) { + if (-not (Test-Path -LiteralPath $configured -PathType Leaf)) { + throw "$Description not found: $configured" + } + return (Resolve-Path -LiteralPath $configured).Path + } + + foreach ($commandName in $PathCommands) { + $command = Get-Command $commandName -CommandType Application -ErrorAction SilentlyContinue | + Select-Object -First 1 + if ($command) { return $command.Source } + } + + throw "$Description not found. Pass -GodotConsole, set AGE_GODOT_CONSOLE, or add Godot to PATH." +} + +function Resolve-ConfiguredGameRoot { + param( + [string]$ExplicitValue, + [string]$EnvironmentValue, + [string]$ConventionalValue + ) + + $configured = if ($ExplicitValue) { + $ExplicitValue + } elseif ($EnvironmentValue) { + $EnvironmentValue + } else { + $ConventionalValue + } + if (-not (Test-Path -LiteralPath $configured -PathType Container)) { + throw "Game root not found: $configured. Pass -GameRoot or set AGE_GAME_ROOT." + } + $resolved = (Resolve-Path -LiteralPath $configured).Path + if (-not (Test-Path -LiteralPath (Join-Path $resolved 'SYS4INI.BIN') -PathType Leaf)) { + throw "Game root does not contain SYS4INI.BIN: $resolved" + } + return $resolved +} + +$godotArguments = @{ + ExplicitValue = $GodotConsole + EnvironmentValue = $env:AGE_GODOT_CONSOLE + PathCommands = @('godot4', 'godot', 'godot-mono') + Description = 'Godot .NET console executable' +} +$godot = Resolve-ConfiguredFile @godotArguments +$gameRootArguments = @{ + ExplicitValue = $GameRoot + EnvironmentValue = $env:AGE_GAME_ROOT + ConventionalValue = Join-Path (Split-Path $repo -Parent) 'Himegari_Game' +} +$resolvedGameRoot = Resolve-ConfiguredGameRoot @gameRootArguments + +if ($Doctor) { + $dotnet = Get-Command dotnet -CommandType Application -ErrorAction SilentlyContinue | + Select-Object -First 1 + $python = Get-Command py -CommandType Application -ErrorAction SilentlyContinue | + Select-Object -First 1 + if (-not $dotnet) { throw '.NET SDK not found on PATH.' } + if (-not $python) { throw 'Python launcher py.exe not found on PATH.' } + Write-Host "Repository : $repo" + Write-Host "Godot : $godot" + Write-Host "Game root : $resolvedGameRoot" + Write-Host "dotnet : $($dotnet.Source)" + Write-Host "Python : $($python.Source)" + exit 0 +} + +if ($SelfTest -and $StartupDiagnostics) { + throw '-SelfTest and -StartupDiagnostics are mutually exclusive.' +} + +$diagnosticFiles = @() +if ($StartupDiagnostics) { + $diagnosticDir = Join-Path $repo 'build\validation\title-newgame' + New-Item -ItemType Directory -Force -Path $diagnosticDir | Out-Null + $diagnosticFiles = @( + (Join-Path $diagnosticDir 'godot.log'), + (Join-Path $diagnosticDir 'timeline.jsonl'), + (Join-Path $diagnosticDir 'histogram.txt'), + (Join-Path $diagnosticDir 'page-map.jsonl') + ) +} + +$perfLogFile = $null +if ($PerfLog -and -not $SelfTest) { + $perfLogDir = Join-Path $repo 'build\perf' + New-Item -ItemType Directory -Force -Path $perfLogDir | Out-Null + $perfLogFile = Join-Path $perfLogDir ("run-{0}.csv" -f (Get-Date -Format 'yyyyMMdd-HHmmss-fff')) +} + +if (-not $NoBuild) { + Write-Host "==> dotnet build $csproj" -ForegroundColor Cyan + dotnet build $csproj -c Debug --nologo -v q + if ($LASTEXITCODE -ne 0) { throw "build failed" } +} + +if ($Import) { + Write-Host "==> importing project" -ForegroundColor Cyan + & $godot --headless --path $project --import +} + +if ($SelfTest) { + Write-Host "==> headless self-test" -ForegroundColor Cyan + & $godot --headless --path $project -- --selftest --game-root $resolvedGameRoot +} else { + # SYSTEM4 owns the real initializer/title/New Game chain. Do not add --boot or SC0000 + # seeds here: those belong only to explicit direct-scene diagnostics. + Write-Host "==> launching windowed from SYSTEM4" -ForegroundColor Cyan + $userArgs = @('--scene', 'SYSTEM4', '--game-root', $resolvedGameRoot) + if ($perfLogFile) { + Write-Host "==> performance log: $perfLogFile" -ForegroundColor Cyan + $userArgs += @('--perf-log', $perfLogFile) + } + if ($SoftwareRenderer) { $userArgs += @('--render-backend', 'software') } + if ($NativeDebugMenu -and -not $StartupDiagnostics) { $userArgs += '--native-debug-menu' } + if ($StartupDiagnostics) { + Write-Host "==> startup diagnostics enabled (native exit semantics)" -ForegroundColor Cyan + $userArgs += @( + '--timeline-log', $diagnosticFiles[1], + '--trace-histogram', $diagnosticFiles[2], + '--page-map', $diagnosticFiles[3] + ) + & $godot --log-file $diagnosticFiles[0] --path $project -- @userArgs + } else { + & $godot --path $project -- @userArgs + } +} + +$godotExitCode = $LASTEXITCODE +if ($StartupDiagnostics) { + Write-Host "==> startup diagnostic artifacts" -ForegroundColor Cyan + foreach ($file in $diagnosticFiles) { + if (Test-Path -LiteralPath $file) { + $length = (Get-Item -LiteralPath $file).Length + Write-Host (" {0} ({1:N0} bytes)" -f $file, $length) + } else { + Write-Warning "diagnostic artifact was not written: $file" + } + } +} +if ($godotExitCode -ne 0) { exit $godotExitCode }