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
10 changes: 0 additions & 10 deletions .codacy.yml

This file was deleted.

33 changes: 0 additions & 33 deletions .deepsource.toml

This file was deleted.

20 changes: 20 additions & 0 deletions .github/codeql/codeql-config.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
name: "SWFOC-Mod-Menu lean CodeQL config"

# Scope CodeQL to FIRST-PARTY code only. The incorporated reverse-engineering
# editor trees are vendored / reproduced-for-provenance material, not the
# actively-maintained first-party trainer (src/, tools/, scripts/, native/).
# They are ALREADY out of scope for every other lean gate — ruff + basedpyright
# exclude them (ruff.toml extend-exclude / pyrightconfig.json exclude), gitleaks
# allowlists the vendored deps, osv ignores them. CodeQL is scoped consistently
# here so the lean "0 code-scanning alerts" bar reflects first-party code, not
# third-party libraries (Dear ImGui, MinHook) or one-shot RE
# tooling reproduced verbatim. This is a reasoned, greppable exclusion — not a
# suppression of a first-party finding.
paths-ignore:
- bridge
- swfoc_lua_bridge
- swfoc_overlay
- swfoc_toolkit
- ghidra_scripts
- re-findings
- docs
48 changes: 5 additions & 43 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -152,49 +152,11 @@ jobs:
run: |
pwsh ./tools/lua-harness/run-lua-harness.ps1 -Strict

- name: SonarCloud scan (with coverage)
if: always()
uses: SonarSource/sonarcloud-github-action@ffc3010689be73b8e5ae0c57ce35968afd7909e8 # v5
env:
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
args: >
-Dsonar.cs.opencover.reportsPaths=TestResults/coverage/opencover.opencover.xml
-Dsonar.coverage.exclusions=tests/**,scripts/**,tools/**,native/**
continue-on-error: true

- name: Upload coverage to Codecov
if: always()
uses: codecov/codecov-action@75cd11691c0faa626561e295848008c8a7dddffe # v5
with:
token: ${{ secrets.CODECOV_TOKEN }}
files: TestResults/coverage/opencover.opencover.xml
flags: dotnet
fail_ci_if_error: false

- name: Transform coverage for QLTY
if: always()
run: |
dotnet tool install -g dotnet-reportgenerator-globaltool 2>$null
reportgenerator -reports:TestResults/coverage/opencover.opencover.xml -targetdir:TestResults/coverage -reporttypes:Cobertura 2>$null
shell: pwsh
continue-on-error: true

- name: Upload coverage to QLTY
if: always()
shell: pwsh
env:
QLTY_COVERAGE_TOKEN: ${{ secrets.QLTY_COVERAGE_TOKEN }}
run: |
if (Test-Path TestResults/coverage/Cobertura.xml) {
Invoke-WebRequest -Uri "https://qlty.sh/install.ps1" -OutFile install-qlty.ps1
pwsh ./install-qlty.ps1
qlty coverage publish --tag dotnet TestResults/coverage/Cobertura.xml
} else {
Write-Output "Coverage file not found, skipping QLTY upload"
}
continue-on-error: true
# Retired 2026-06-27: SonarCloud / Codecov / QLTY coverage uploaders removed
# as part of the lean-gate migration (SaaS reporters are RETIRED machinery;
# the lean 6-gate quality workflow in .github/workflows/quality.yml is the
# authoritative gate). These were already non-blocking (continue-on-error),
# so their removal does not change build-test pass/fail.

- name: Upload Test Results
if: always()
Expand Down
5 changes: 4 additions & 1 deletion .github/workflows/codeql.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,13 @@ jobs:
actions: read
contents: read
security-events: write
uses: Prekzursil/quality-zero-platform/.github/workflows/reusable-codeql.yml@fbb2255753788b93cc79ec917c764d0fc4f52146
uses: Prekzursil/quality-zero-platform/.github/workflows/reusable-codeql.yml@3e05256353a3da48308fceaafbd5389fb9188ae2
with:
repo_slug: ${{ github.repository }}
event_name: ${{ github.event_name }}
sha: ${{ github.event.pull_request.head.sha || github.sha }}
platform_repository: Prekzursil/quality-zero-platform
platform_ref: main
# Scope analysis to first-party code; exclude vendored / provenance RE
# editor trees (consistent with ruff/basedpyright/gitleaks/osv exclusions).
codeql_config_file: .github/codeql/codeql-config.yml
11 changes: 11 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -93,3 +93,14 @@ tmp_codacy_*.json

# Cheat Engine tables (local RE workspace)
*.ct

# Incorporated RE editor source trees: ignore local build artifacts
*.o
*.obj
*.exe
*.dll
*.lib
*.exp
*.ilk
*.pdb
bridge/test_runs/
8 changes: 8 additions & 0 deletions .gitleaks.toml
Original file line number Diff line number Diff line change
Expand Up @@ -34,4 +34,12 @@ paths = [
# game data carry high-entropy bytes that are not credentials.
'''.*\.CT$''',
'''.*\.(meg|dat|bin|dll|exe|pdb|so)$''',
# Vendored third-party RE editor dependencies reproduced verbatim for
# provenance (2026-06-27 incorporation). These carry only upstream-example /
# high-entropy library constants, never our credentials:
# - Dear ImGui (swfoc_overlay/imgui): generic-api-key FP on a base64-looking
# lookup-table literal in imgui.cpp.
# - MinHook (swfoc_lua_bridge/minhook): vendored hooking library.
'''(^|/)swfoc_overlay/imgui/''',
'''(^|/)swfoc_lua_bridge/minhook/''',
]
33 changes: 0 additions & 33 deletions .jscpd.json

This file was deleted.

19 changes: 0 additions & 19 deletions .qlty/qlty.toml

This file was deleted.

181 changes: 181 additions & 0 deletions bridge/autonomous_live_test.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
# autonomous_live_test.ps1
# Hits the SWFOC bridge with a battery of pre-baked probes and writes a
# structured pass/fail/expected-error report. Does NOT require any in-game
# input — fires every probe over the named pipe, classifies the response,
# logs everything to a timestamped file.

$ErrorActionPreference = "Continue"
$logPath = "C:\Users\Prekzursil\Downloads\swfoc_memory\.remember\autonomous_live_test_$(Get-Date -Format yyyy-MM-dd_HHmmss).log"

function Send-LuaCmd {
param([string]$cmd, [int]$readMs = 350)
# NamedPipeClientStream performs the full pipe handshake synchronously
# whereas File.Open returns a handle before the server has acknowledged.
$pipe = New-Object System.IO.Pipes.NamedPipeClientStream(".", "swfoc_bridge", "InOut")
try {
$pipe.Connect(3000)
} catch {
$pipe.Dispose()
return "CONNECT_FAIL: $_"
}
try {
$b = [System.Text.Encoding]::UTF8.GetBytes($cmd) + [byte[]]@(0)
$pipe.Write($b, 0, $b.Length)
$pipe.Flush()
Start-Sleep -Milliseconds $readMs
$buf = New-Object byte[] 65536
$n = $pipe.Read($buf, 0, 65536)
return [System.Text.Encoding]::UTF8.GetString($buf, 0, $n).TrimEnd()
} finally {
$pipe.Dispose()
}
}

function Classify {
param([string]$response, [string]$expectedPattern = $null)
if ($response -match "^CONNECT_FAIL") { return "CONNECT_FAIL" }
if ($response -match "^ERR: .*Phase 2") { return "P2-PENDING (honest)" }
if ($response -match "Phase 2 hook pending") { return "P2-PENDING (honest)" }
if ($response -match "non-tactical state") { return "TACTICAL-ONLY (expected in galactic)" }
if ($response -match "engine error rc=") { return "ENGINE-ERR (helper raised)" }
if ($response -match "^ERR: .*expected ") { return "NEEDS-ARGS" }
if ($response -match "^ERR:") { return "ERR" }
if ($expectedPattern -and -not ($response -match $expectedPattern)) { return "UNEXPECTED" }
return "PASS"
}

# ---- TEST BATTERY ----
# Each test: name, cmd, category, optional expected-pattern regex
$tests = @(
# === Meta / version / state ===
@{ Cat="META"; Name="GetVersion"; Cmd="return SWFOC_GetVersion()"; Expect="v1\.5-dev" },
@{ Cat="META"; Name="GetBuildInfo"; Cmd="return SWFOC_GetBuildInfo()"; Expect="\d{4}" },
@{ Cat="META"; Name="StateInfo"; Cmd="return SWFOC_StateInfo()"; Expect="Game states" },
@{ Cat="META"; Name="DiagPipeStats"; Cmd="return SWFOC_DiagPipeStats()"; Expect="received=\d+" },
@{ Cat="META"; Name="DiagSelfTest"; Cmd="return SWFOC_DiagSelfTest()"; Expect="passed=" },
@{ Cat="META"; Name="DiagGameTick"; Cmd="return SWFOC_DiagGameTick()" },
@{ Cat="META"; Name="DiagSelection"; Cmd="return SWFOC_DiagSelection()" },
@{ Cat="META"; Name="GetGameModeLua"; Cmd="return SWFOC_GetGameModeLua()" },
@{ Cat="META"; Name="ListMods"; Cmd="return SWFOC_ListMods()" },
@{ Cat="META"; Name="GetCurrentMod"; Cmd="return SWFOC_GetCurrentMod()" },

# === Player + faction reads ===
@{ Cat="READ"; Name="GetLocalPlayerLua"; Cmd="return SWFOC_GetLocalPlayerLua()" },
@{ Cat="READ"; Name="GetAllPlayers"; Cmd="return SWFOC_GetAllPlayers()"; Expect="count=" },
@{ Cat="READ"; Name="GetCredits"; Cmd="return SWFOC_GetCredits()"; Expect="\d+" },
@{ Cat="READ"; Name="GetCreditsForSlot(6)"; Cmd="return SWFOC_GetCreditsForSlot(6)"; Expect="\d+" },
@{ Cat="READ"; Name="GetTechForSlot(6)"; Cmd="return SWFOC_GetTechForSlot(6)"; Expect="^\d+$" },
@{ Cat="READ"; Name="GetMaxCredits"; Cmd="return SWFOC_GetMaxCredits()"; Expect="\d+" },
@{ Cat="READ"; Name="GetPlayerKills(6)"; Cmd="return SWFOC_GetPlayerKills(6)" },
@{ Cat="READ"; Name="GetPlayerDeaths(6)"; Cmd="return SWFOC_GetPlayerDeaths(6)" },
@{ Cat="READ"; Name="ListFactions"; Cmd="return SWFOC_ListFactions()" },

# === Multipliers + freezes (READ side) ===
@{ Cat="MULT-READ"; Name="GetCreditsMultiplierGlobal"; Cmd="return SWFOC_GetCreditsMultiplierGlobal()" },
@{ Cat="MULT-READ"; Name="GetCreditsFreezeGlobal"; Cmd="return SWFOC_GetCreditsFreezeGlobal()" },
@{ Cat="MULT-READ"; Name="GetDamageMultiplierGlobal"; Cmd="return SWFOC_GetDamageMultiplierGlobal()" },
@{ Cat="MULT-READ"; Name="GetFireRateMultiplierGlobal";Cmd="return SWFOC_GetFireRateMultiplierGlobal()" },

# === World queries ===
@{ Cat="WORLD"; Name="GetPlanets"; Cmd="return SWFOC_GetPlanets()" },
@{ Cat="WORLD"; Name="ListHeroes"; Cmd="return SWFOC_ListHeroes()" },
@{ Cat="WORLD"; Name="ListAbilities"; Cmd="return SWFOC_ListAbilities()" },
@{ Cat="WORLD"; Name="ListTacticalUnits"; Cmd="return string.sub(SWFOC_ListTacticalUnits(), 1, 100)" },
@{ Cat="WORLD"; Name="GetTotalUnitsAlive"; Cmd="return SWFOC_GetTotalUnitsAlive()" },
@{ Cat="WORLD"; Name="GetSelectedUnit"; Cmd="return SWFOC_GetSelectedUnit()" },
@{ Cat="WORLD"; Name="GetSelectedUnits"; Cmd="return SWFOC_GetSelectedUnits()" },
@{ Cat="WORLD"; Name="GetFactionRoster(6)"; Cmd="return string.sub(SWFOC_GetFactionRoster(6), 1, 200)" },

# === Camera reads (galactic returns 0,0,0; tactical returns 3D) ===
@{ Cat="CAMERA"; Name="GetCameraPos"; Cmd="return SWFOC_GetCameraPos()" },

# === Tactical-only (expected to fail in galactic with mode message) ===
@{ Cat="TACTICAL-ONLY"; Name="BatchTypeExists";Cmd="return SWFOC_BatchTypeExists('TIE_Fighter|Vengeance_Frigate|NotARealUnit')" },

# === Write-and-readback cycles ===
@{ Cat="WRITE-CYCLE"; Name="SetCredits cycle"; Cmd="local v0=SWFOC_GetCreditsForSlot(6); SWFOC_SetCreditsForSlot(6,77777); local v1=SWFOC_GetCreditsForSlot(6); SWFOC_SetCreditsForSlot(6,v0); local v2=SWFOC_GetCreditsForSlot(6); return 'before='..v0..' mid='..v1..' after='..v2"; Expect="before=12000 mid=77777 after=12000" },
@{ Cat="WRITE-CYCLE"; Name="SetTech cycle"; Cmd="local v0=SWFOC_GetTechForSlot(6); SWFOC_SetTechForSlot(6,4); local v1=SWFOC_GetTechForSlot(6); SWFOC_SetTechForSlot(6,v0); local v2=SWFOC_GetTechForSlot(6); return 'before='..v0..' mid='..v1..' after='..v2"; Expect="before=2 mid=4 after=2" },
@{ Cat="WRITE-CYCLE"; Name="CreditsMult cycle";Cmd="local v0=SWFOC_GetCreditsMultiplierGlobal(); SWFOC_SetCreditsMultiplierGlobal(3.5); local v1=SWFOC_GetCreditsMultiplierGlobal(); SWFOC_SetCreditsMultiplierGlobal(v0); local v2=SWFOC_GetCreditsMultiplierGlobal(); return 'before='..v0..' mid='..v1..' after='..v2"; Expect="mid=3.5" },
@{ Cat="WRITE-CYCLE"; Name="DamageMult cycle"; Cmd="local v0=SWFOC_GetDamageMultiplierGlobal(); SWFOC_SetDamageMultiplierGlobal(2.0); local v1=SWFOC_GetDamageMultiplierGlobal(); SWFOC_SetDamageMultiplierGlobal(v0); local v2=SWFOC_GetDamageMultiplierGlobal(); return 'before='..v0..' mid='..v1..' after='..v2"; Expect="mid=2" },
@{ Cat="WRITE-CYCLE"; Name="FireRateMult cycle"; Cmd="local v0=SWFOC_GetFireRateMultiplierGlobal(); SWFOC_SetFireRateMultiplierGlobal(0.5); local v1=SWFOC_GetFireRateMultiplierGlobal(); SWFOC_SetFireRateMultiplierGlobal(v0); local v2=SWFOC_GetFireRateMultiplierGlobal(); return 'before='..v0..' mid='..v1..' after='..v2"; Expect="mid=0.5" },
@{ Cat="WRITE-CYCLE"; Name="CreditsFreeze cycle"; Cmd="local v0=SWFOC_GetCreditsFreezeGlobal(); SWFOC_SetCreditsFreezeGlobal(1); local v1=SWFOC_GetCreditsFreezeGlobal(); SWFOC_SetCreditsFreezeGlobal(0); local v2=SWFOC_GetCreditsFreezeGlobal(); return 'before='..v0..' mid='..v1..' after='..v2"; Expect="mid=1 after=0" },

# === P2-PENDING wires (catalog says these will return honest pending text) ===
@{ Cat="P2-PENDING"; Name="FreezeAI(true)"; Cmd="return SWFOC_FreezeAI(1)" },
@{ Cat="P2-PENDING"; Name="SetGameSpeed(2.0)"; Cmd="return SWFOC_SetGameSpeed(2.0)" },
@{ Cat="P2-PENDING"; Name="TriggerVictory"; Cmd="return SWFOC_TriggerVictory('Galactic_Conquer')" },
@{ Cat="P2-PENDING"; Name="FreeCam(true)"; Cmd="return SWFOC_FreeCam(1)" },

# === Galactic-only writes (revertible) ===
@{ Cat="GALACTIC"; Name="UncapCredits"; Cmd="return SWFOC_UncapCredits(true)" },
@{ Cat="GALACTIC"; Name="UncapCredits revert"; Cmd="return SWFOC_UncapCredits(false)" },

# === Hooks / abilities / lookup ===
@{ Cat="LOOKUP"; Name="DiagListRegisteredFn (head)"; Cmd="return string.sub(SWFOC_DiagListRegisteredFunctions(), 1, 100)" },
@{ Cat="LOOKUP"; Name="DoString round-trip"; Cmd='return SWFOC_DoString("return 1+2+3")'; Expect="6" },
@{ Cat="LOOKUP"; Name="DoString global probe"; Cmd='return SWFOC_DoString("return _G[\"GameMode\"] or \"unknown\"")' }
)

# ---- Run ----
$results = @()
$counts = @{ PASS=0; "P2-PENDING (honest)"=0; "TACTICAL-ONLY (expected in galactic)"=0; "ENGINE-ERR (helper raised)"=0; "NEEDS-ARGS"=0; ERR=0; UNEXPECTED=0; CONNECT_FAIL=0 }

$startTime = Get-Date
Add-Content -LiteralPath $logPath -Value "=== SWFOC Bridge Autonomous Live Test ==="
Add-Content -LiteralPath $logPath -Value "Started: $startTime"
Add-Content -LiteralPath $logPath -Value ""

foreach ($t in $tests) {
$response = Send-LuaCmd $t.Cmd
$verdict = Classify $response $t.Expect
$counts[$verdict]++
$results += [PSCustomObject]@{ Cat=$t.Cat; Name=$t.Name; Verdict=$verdict; Response=$response }
Add-Content -LiteralPath $logPath -Value ("[{0,-30}] {1,-25} {2,-30} -> {3}" -f $t.Cat, $t.Name, $verdict, ($response.Replace("`n"," | ").Substring(0, [Math]::Min(200, $response.Length))))
}

$endTime = Get-Date
$elapsed = [int]($endTime - $startTime).TotalSeconds

# ---- Summary ----
Add-Content -LiteralPath $logPath -Value ""
Add-Content -LiteralPath $logPath -Value "=== SUMMARY ==="
Add-Content -LiteralPath $logPath -Value "Elapsed: ${elapsed}s"
Add-Content -LiteralPath $logPath -Value "Total tests: $($results.Count)"
foreach ($k in $counts.Keys | Sort-Object) {
$line = " {0,-40} {1}" -f $k, $counts[$k]
Add-Content -LiteralPath $logPath -Value $line
}
Add-Content -LiteralPath $logPath -Value ""

# Console output: condensed table by category
Write-Output ""
Write-Output "=== Autonomous live test (${elapsed}s, $($results.Count) probes) ==="
foreach ($cat in ($results | Group-Object Cat | Sort-Object Name)) {
Write-Output ""
Write-Output "--- $($cat.Name) ---"
foreach ($r in $cat.Group) {
$status = switch ($r.Verdict) {
"PASS" { "OK" }
"P2-PENDING (honest)" { "P2" }
"TACTICAL-ONLY (expected in galactic)" { "TC" }
"ENGINE-ERR (helper raised)" { "EN" }
"NEEDS-ARGS" { "AR" }
"UNEXPECTED" { "??" }
"CONNECT_FAIL" { "XX" }
default { "ER" }
}
$preview = $r.Response.Replace("`n", " | ")
if ($preview.Length -gt 100) { $preview = $preview.Substring(0, 100) + "..." }
Write-Output (" [{0}] {1,-32} {2}" -f $status, $r.Name, $preview)
}
}

Write-Output ""
Write-Output "=== TOTALS ==="
foreach ($k in $counts.Keys | Sort-Object) {
if ($counts[$k] -gt 0) {
Write-Output (" {0,-40} {1}" -f $k, $counts[$k])
}
}
Write-Output ""
Write-Output "Full log: $logPath"
Loading
Loading