Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,9 @@ versioned release tag yet, so entries are organized as pre-alpha snapshots.
- Local Windows health check helper for Git alignment, Docker, Nakama
containers, Nakama HTTP, Unity process ownership, recent Editor.log
compile/package patterns, and Unity MCP port readiness.
- Local Windows health check reports now include a summary verdict and
per-component next-action hints, and they filter empty AI decision `error=`
fields from the generic Unity log warning path.
- Play Mode smoke checklist for NPC dialogue, ambient society behavior, quest
tracker, memory and relationship writes, PromptTrace, fallback display,
combat safety, MCP, Unity console, and backend evidence.
Expand Down
5 changes: 3 additions & 2 deletions docs/setup/agent-handoff.md
Original file line number Diff line number Diff line change
Expand Up @@ -105,8 +105,9 @@ Do not touch:
`tools/windows/check-local-health.ps1`. It reports Git alignment, Docker,
`second-spawn-postgres`, `second-spawn-nakama`, Nakama HTTP, Unity process,
recent Editor.log compile/package patterns, and Unity MCP ports without
killing or restarting anything. Use `-Strict` only when an automation should
fail on hard failures.
killing or restarting anything. The report includes a summary verdict and
per-component next-action hints so agents can fix the failing lane instead of
guessing. Use `-Strict` only when an automation should fail on hard failures.
- Play Mode verification uses
[Play Mode Smoke Checklist](play-mode-smoke-checklist.md) for NPC dialogue,
quest tracker, PromptTrace, memory, relationship, fallback display, combat
Expand Down
5 changes: 5 additions & 0 deletions docs/setup/play-mode-smoke-checklist.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,11 @@ Run the local health check first:
powershell -NoProfile -ExecutionPolicy Bypass -File tools/windows/check-local-health.ps1
```

Use `-Json` when an agent or CI-style helper needs machine-readable evidence.
The JSON output includes a summary verdict and per-component `next_action`
guidance. `WARN` means Play Mode can often continue with a note, while `FAIL`
means fix the named component before claiming the smoke is clean.

When a previous Play Mode run just stopped, wait at least 10-15 seconds before
clearing the Unity console and starting the next smoke. Unity `6000.5.0b9` can
finish async domain-reload cleanup after the stop command returns, and clearing
Expand Down
60 changes: 37 additions & 23 deletions tools/windows/check-local-health.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -31,14 +31,16 @@ function Add-Result {
[string]$Component,
[Parameter(Mandatory = $true)]
[string]$Message,
[string]$Detail = ""
[string]$Detail = "",
[string]$NextAction = ""
)

$script:results.Add([PSCustomObject]@{
status = $Status
component = $Component
message = $Message
detail = $Detail
next_action = $NextAction
})
}

Expand Down Expand Up @@ -170,7 +172,7 @@ if (Test-Path $UnityProjectPath) {
Add-Result -Status "OK" -Component "Unity Project" -Message "Unity project folder exists." -Detail $UnityProjectPath
}
else {
Add-Result -Status "FAIL" -Component "Unity Project" -Message "Unity project folder is missing." -Detail $UnityProjectPath
Add-Result -Status "FAIL" -Component "Unity Project" -Message "Unity project folder is missing." -Detail $UnityProjectPath -NextAction "Open the repository root or pass -UnityProjectPath."
}

if (Test-CommandAvailable "git") {
Expand All @@ -183,15 +185,15 @@ if (Test-CommandAvailable "git") {
Add-Result -Status "OK" -Component "Git" -Message "Branch is aligned with upstream." -Detail $detail
}
else {
Add-Result -Status "WARN" -Component "Git" -Message "Branch is not aligned with upstream." -Detail $detail
Add-Result -Status "WARN" -Component "Git" -Message "Branch is not aligned with upstream." -Detail $detail -NextAction "Fetch and rebase or fast-forward before starting new work."
}
}
else {
Add-Result -Status "WARN" -Component "Git" -Message "No upstream branch detected." -Detail "branch=$branch"
Add-Result -Status "WARN" -Component "Git" -Message "No upstream branch detected." -Detail "branch=$branch" -NextAction "Set an upstream branch or use a repo worktree created from origin/dev."
}
}
else {
Add-Result -Status "WARN" -Component "Git" -Message "Git CLI not found."
Add-Result -Status "WARN" -Component "Git" -Message "Git CLI not found." -NextAction "Install Git or run this from a shell where Git is on PATH."
}

if (Test-CommandAvailable "docker") {
Expand All @@ -201,27 +203,27 @@ if (Test-CommandAvailable "docker") {
Add-Result -Status "OK" -Component "Docker" -Message "Docker engine is reachable."
}
else {
Add-Result -Status "FAIL" -Component "Docker" -Message "Docker engine is not reachable."
Add-Result -Status "FAIL" -Component "Docker" -Message "Docker engine is not reachable." -NextAction "Start Docker Desktop, then rerun tools/windows/start-local-nakama.ps1 if containers are stopped."
}
}
catch {
Add-Result -Status "FAIL" -Component "Docker" -Message "Docker engine is not reachable." -Detail $_.Exception.Message
Add-Result -Status "FAIL" -Component "Docker" -Message "Docker engine is not reachable." -Detail $_.Exception.Message -NextAction "Start Docker Desktop, then rerun tools/windows/start-local-nakama.ps1 if containers are stopped."
}
}
else {
Add-Result -Status "FAIL" -Component "Docker" -Message "Docker CLI not found."
Add-Result -Status "FAIL" -Component "Docker" -Message "Docker CLI not found." -NextAction "Install Docker Desktop or open a shell where Docker is on PATH."
}

foreach ($containerName in @("second-spawn-postgres", "second-spawn-nakama")) {
$state = Get-ContainerState -Name $containerName
if (-not $state.exists) {
Add-Result -Status "FAIL" -Component "Docker Container" -Message "$containerName is missing." -Detail $state.detail
Add-Result -Status "FAIL" -Component "Docker Container" -Message "$containerName is missing." -Detail $state.detail -NextAction "Run tools/windows/start-local-nakama.ps1 to create the local backend containers."
}
elseif ($state.running) {
Add-Result -Status "OK" -Component "Docker Container" -Message "$containerName is running." -Detail "restart=$($state.restartPolicy) status=$($state.detail)"
}
else {
Add-Result -Status "FAIL" -Component "Docker Container" -Message "$containerName is not running." -Detail "restart=$($state.restartPolicy) status=$($state.detail)"
Add-Result -Status "FAIL" -Component "Docker Container" -Message "$containerName is not running." -Detail "restart=$($state.restartPolicy) status=$($state.detail)" -NextAction "Run tools/windows/start-local-nakama.ps1 or docker start $containerName."
}
}

Expand All @@ -231,75 +233,76 @@ try {
$nakamaPort = if ($nakamaUri.Port -gt 0) { $nakamaUri.Port } else { 7350 }
}
catch {
Add-Result -Status "WARN" -Component "Nakama" -Message "Nakama URL is not a valid URI." -Detail $NakamaUrl
Add-Result -Status "WARN" -Component "Nakama" -Message "Nakama URL is not a valid URI." -Detail $NakamaUrl -NextAction "Pass a valid -NakamaUrl, for example http://127.0.0.1:7350."
}

if (Test-TcpPort -HostName "127.0.0.1" -Port $nakamaPort) {
Add-Result -Status "OK" -Component "Nakama" -Message "Nakama TCP port is open." -Detail $NakamaUrl
}
else {
Add-Result -Status "FAIL" -Component "Nakama" -Message "Nakama TCP port is closed." -Detail $NakamaUrl
Add-Result -Status "FAIL" -Component "Nakama" -Message "Nakama TCP port is closed." -Detail $NakamaUrl -NextAction "Start the local Nakama container and confirm port 7350 is not occupied by another service."
}

$nakamaHttp = Test-Http -Url $NakamaUrl
if ($nakamaHttp.ok) {
Add-Result -Status "OK" -Component "Nakama HTTP" -Message "Nakama HTTP endpoint responded." -Detail "http=$($nakamaHttp.code) $($nakamaHttp.detail)"
}
elseif ($nakamaHttp.code -ge 400 -and $nakamaHttp.code -lt 500) {
Add-Result -Status "WARN" -Component "Nakama HTTP" -Message "Nakama HTTP endpoint responded with client status." -Detail "http=$($nakamaHttp.code) $($nakamaHttp.detail)"
Add-Result -Status "WARN" -Component "Nakama HTTP" -Message "Nakama HTTP endpoint responded with client status." -Detail "http=$($nakamaHttp.code) $($nakamaHttp.detail)" -NextAction "Confirm the URL points at Nakama and not the console or another service."
}
else {
Add-Result -Status "FAIL" -Component "Nakama HTTP" -Message "Nakama HTTP endpoint did not respond cleanly." -Detail $nakamaHttp.detail
Add-Result -Status "FAIL" -Component "Nakama HTTP" -Message "Nakama HTTP endpoint did not respond cleanly." -Detail $nakamaHttp.detail -NextAction "Check second-spawn-nakama logs and restart the local backend if needed."
}

$unityProcesses = @(Get-UnityProcesses)
if ($unityProcesses.Count -eq 0) {
Add-Result -Status "WARN" -Component "Unity Editor" -Message "No Unity.exe process detected."
Add-Result -Status "WARN" -Component "Unity Editor" -Message "No Unity.exe process detected." -NextAction "Open Unity with the Unity project before Play Mode smoke."
}
else {
$matching = $unityProcesses | Where-Object { $_.CommandLine -like "*$UnityProjectPath*" }
if ($matching) {
Add-Result -Status "OK" -Component "Unity Editor" -Message "Unity is open for this project." -Detail (($matching | ForEach-Object { "pid=$($_.ProcessId)" }) -join ", ")
}
else {
Add-Result -Status "WARN" -Component "Unity Editor" -Message "Unity is running, but not clearly for this project." -Detail (($unityProcesses | ForEach-Object { "pid=$($_.ProcessId)" }) -join ", ")
Add-Result -Status "WARN" -Component "Unity Editor" -Message "Unity is running, but not clearly for this project." -Detail (($unityProcesses | ForEach-Object { "pid=$($_.ProcessId)" }) -join ", ") -NextAction "Focus the SECOND SPAWN Unity project or pass -UnityProjectPath."
}
}

$logTail = @(Read-EditorLogTail -Path $EditorLogPath -TailLines $LogTailLines)
if ($logTail.Count -eq 0) {
Add-Result -Status "WARN" -Component "Unity Editor Log" -Message "Editor log was not found or is empty." -Detail $EditorLogPath
Add-Result -Status "WARN" -Component "Unity Editor Log" -Message "Editor log was not found or is empty." -Detail $EditorLogPath -NextAction "Open Unity once so Editor.log exists, or pass -EditorLogPath."
}
else {
$packageManagerMatches = @($logTail | Select-String -Pattern 'The "path" argument must be of type string|Failed to resolve packages|Package Manager' -SimpleMatch:$false)
$compileMatches = @($logTail | Select-String -Pattern "Assets\\.*\\.cs\\(.*\\): error CS|Compilation failed|Compiler errors")
$compileMatches = @($logTail | Select-String -Pattern "Assets[\\/].*\.cs\([0-9]+,[0-9]+\): error CS|Compilation failed|Compiler errors")
$errorMatches = @($logTail | Select-String -Pattern "Exception|Error|Failed" | Where-Object {
$line = $_.Line.Trim()
$line -notmatch "The referenced script .* is missing" -and
$line -notmatch "System\.Exception" -and
$line -notmatch "^at " -and
$line -notmatch "^[A-Za-z0-9_.<>/]+:.*\(.*\)\s+\(at " -and
$line -notmatch "^\[PrototypeAgentBrain\] Decision result .* error=\s*$" -and
$line -notmatch "Unity\.AI\.Tracing\.(ConsoleSink|TraceLogger|TraceWriter)" -and
$line -notmatch "Unity\.AI\.MCP\.Editor\.Helpers\.McpLog"
})
$warningMatches = @($logTail | Select-String -Pattern "Warning|WARN")

if ($compileMatches.Count -gt 0) {
Add-Result -Status "FAIL" -Component "Unity Compile" -Message "Compile errors found in recent Editor.log." -Detail (($compileMatches | Select-Object -Last 3 | ForEach-Object { $_.Line.Trim() }) -join " | ")
Add-Result -Status "FAIL" -Component "Unity Compile" -Message "Compile errors found in recent Editor.log." -Detail (($compileMatches | Select-Object -Last 3 | ForEach-Object { $_.Line.Trim() }) -join " | ") -NextAction "Fix the reported C# compile errors before entering Play Mode."
}
else {
Add-Result -Status "OK" -Component "Unity Compile" -Message "No compile error pattern found in recent Editor.log."
}

if ($packageManagerMatches.Count -gt 0) {
Add-Result -Status "WARN" -Component "Unity Package Manager" -Message "Package Manager warning pattern found in recent Editor.log." -Detail (($packageManagerMatches | Select-Object -Last 3 | ForEach-Object { $_.Line.Trim() }) -join " | ")
Add-Result -Status "WARN" -Component "Unity Package Manager" -Message "Package Manager warning pattern found in recent Editor.log." -Detail (($packageManagerMatches | Select-Object -Last 3 | ForEach-Object { $_.Line.Trim() }) -join " | ") -NextAction "Open Package Manager or restart Unity if it is stuck resolving packages."
}
else {
Add-Result -Status "OK" -Component "Unity Package Manager" -Message "No known package blocker pattern found in recent Editor.log."
}

if ($errorMatches.Count -gt 0) {
Add-Result -Status "WARN" -Component "Unity Editor Log" -Message "Recent log contains error or exception text." -Detail (($errorMatches | Select-Object -Last 3 | ForEach-Object { $_.Line.Trim() }) -join " | ")
Add-Result -Status "WARN" -Component "Unity Editor Log" -Message "Recent log contains error or exception text." -Detail (($errorMatches | Select-Object -Last 3 | ForEach-Object { $_.Line.Trim() }) -join " | ") -NextAction "Inspect the recent Unity console. Compile errors are already reported separately as FAIL."
}
else {
Add-Result -Status "OK" -Component "Unity Editor Log" -Message "No generic error or exception text found in recent log tail."
Expand All @@ -315,16 +318,26 @@ foreach ($port in $McpPorts) {
Add-Result -Status "OK" -Component "Unity MCP" -Message "Port $port is listening." -Detail $detail
}
else {
Add-Result -Status "WARN" -Component "Unity MCP" -Message "Port $port is not listening."
Add-Result -Status "WARN" -Component "Unity MCP" -Message "Port $port is not listening." -NextAction "Start or re-enable the matching Unity MCP bridge only if this bridge is needed for the smoke."
}
}

$failCount = @($results | Where-Object { $_.status -eq "FAIL" }).Count
$warnCount = @($results | Where-Object { $_.status -eq "WARN" }).Count
$okCount = @($results | Where-Object { $_.status -eq "OK" }).Count

$summary = [PSCustomObject]@{
generated_at = (Get-Date).ToString("o")
repo_root = $RepoRoot
unity_project_path = $UnityProjectPath
nakama_url = $NakamaUrl
strict = [bool]$Strict
summary = [PSCustomObject]@{
ok = $okCount
warn = $warnCount
fail = $failCount
verdict = if ($failCount -gt 0) { "fail" } elseif ($warnCount -gt 0) { "warn" } else { "ok" }
}
results = $results
}

Expand All @@ -337,8 +350,9 @@ else {
Write-Host "Repo: $RepoRoot"
Write-Host "Unity: $UnityProjectPath"
Write-Host "Nakama: $NakamaUrl"
Write-Host "Verdict: $($summary.summary.verdict.ToUpperInvariant()) ($okCount OK, $warnCount WARN, $failCount FAIL)"
Write-Host ""
$results | Format-Table status, component, message, detail -AutoSize
$results | Format-Table status, component, message, detail, next_action -AutoSize
}

$hasFailure = [bool]($results | Where-Object { $_.status -eq "FAIL" })
Expand Down
Loading