From a8661eaaa5966e07e9c14fa7e79167228d13126c Mon Sep 17 00:00:00 2001 From: fd-shockwave-bot Date: Fri, 25 Sep 2026 22:50:17 +0100 Subject: [PATCH 1/3] M1 baseline: Windows build record and lifecycle checks tools/build-windows.sh writes BUILD-INFO.txt beside the binaries: the source commit, whether the build inputs had uncommitted changes, the build image, the Rust and MinGW versions, and the SHA-256 of eech_dc.dll, eech-world.exe, lua.dll and the Lua scripts. EECH_WIN_TARGET selects the cargo target volume, so a from-scratch build can be compared with an incremental one. lua/lifecycle.lua exercises the module's lifecycle and failure behaviour through the public path (require ("eech_dc")), one case per process; tools/lifecycle-windows.ps1 runs the cases and checks them against the behaviour observed at 5f07697b. A missing map directory crashes the host (access violation in msvcrt.dll): it is checked as a known defect, not approved behaviour. No engine or host code changes. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01C64rJFJLY8BTJbC1QimZDK --- .../crates/eech-world/lua/lifecycle.lua | 101 ++++++++++++++++++ eech-campaign/tools/build-windows.sh | 27 ++++- eech-campaign/tools/lifecycle-windows.ps1 | 88 +++++++++++++++ 3 files changed, 212 insertions(+), 4 deletions(-) create mode 100644 eech-campaign/crates/eech-world/lua/lifecycle.lua create mode 100644 eech-campaign/tools/lifecycle-windows.ps1 diff --git a/eech-campaign/crates/eech-world/lua/lifecycle.lua b/eech-campaign/crates/eech-world/lua/lifecycle.lua new file mode 100644 index 00000000..7180b398 --- /dev/null +++ b/eech-campaign/crates/eech-world/lua/lifecycle.lua @@ -0,0 +1,101 @@ +-- The module's lifecycle and failure behaviour, observed through the public +-- path (host Lua -> require ("eech_dc")). One case per process: EECH is global +-- state, booted at most once per process, so every case starts from a fresh +-- process (tools/lifecycle-windows.ps1 runs them all and checks the results). +-- +-- host.args: root (a prepared retail installation), scenario (lebanon_retail or +-- georgia_retail), case (below) +-- +-- Each step prints one line to stdout: TAB ok|error TAB + +local args = host.args +local root = assert (args.root, "root=") +-- the retail campaigns, as campaign.lua boots them +local scenarios = { + lebanon_retail = { map = "..\\common\\maps\\map5", campaign = "LEBANON.CHC" }, + georgia_retail = { map = "..\\common\\maps\\map3", campaign = "GEORGIA.CHC" }, +} +local scenario = assert (scenarios[args.scenario or "lebanon_retail"], "scenario=lebanon_retail|georgia_retail") + +local function config (overrides) + local c = { install_root = root, map = scenario.map, campaign_directory = "camp01", campaign = scenario.campaign, gunship = "apache", seed = 1 } + for k, v in pairs (overrides or {}) do + if v == false then c[k] = nil else c[k] = v end + end + return c +end + +local function step (name, f, ...) + local ok, result = pcall (f, ...) + local message = ok and "" or (tostring (result):match ("^[^\n]*") or "") + print (string.format ("%s\t%s\t%s", name, ok and "ok" or "error", message)) + io.stdout:flush () + return ok, result +end + +local dc = require ("eech_dc") +step ("load", function () assert (type (dc.boot) == "function") end) +step ("prepare", dc.prepare_installation, root) + +local function frames (engine, n) + for _ = 1, n do engine:frame (100) end +end + +local cases = {} + +-- one boot per process: a second boot is refused and the first engine carries on +function cases.boot_twice () + local _, engine = step ("boot", dc.boot, config ()) + step ("frames", frames, engine, 50) + step ("boot_again", dc.boot, config ()) + step ("frames_after", frames, engine, 50) + step ("clock", function () return engine:clock () end) +end + +-- the binding checks its arguments before the engine sees them +function cases.bad_arguments () + step ("boot_unknown_gunship", dc.boot, config { gunship = "tiger" }) + step ("boot_no_install_root", dc.boot, config { install_root = false }) + step ("boot_no_map", dc.boot, config { map = false }) + step ("boot_no_campaign", dc.boot, config { campaign = false }) + step ("boot_not_a_table", dc.boot, "lebanon") + local _, engine = step ("boot", dc.boot, config ()) + step ("frame_negative", function () engine:frame (-1) end) + step ("frame_not_a_number", function () engine:frame ("soon") end) + step ("frame_zero", function () engine:frame (0) end) + step ("frames_after", frames, engine, 50) +end + +-- inputs the engine itself cannot use +function cases.missing_root () + step ("boot_missing_root", dc.boot, config { install_root = root .. "/does-not-exist" }) + step ("boot_after", dc.boot, config ()) +end + +function cases.missing_map () + step ("boot_missing_map", dc.boot, config { map = "..\\common\\maps\\map99" }) + step ("boot_after", dc.boot, config ()) +end + +function cases.missing_campaign () + step ("boot_missing_campaign", dc.boot, config { campaign = "NOSUCH.CHC" }) + step ("boot_after", dc.boot, config ()) +end + +-- no shutdown or reuse: dropping the engine does not allow another boot, and +-- neither does loading the module again +function cases.no_reuse () + local _, engine = step ("boot", dc.boot, config ()) + step ("frames", frames, engine, 50) + engine = nil + collectgarbage () + collectgarbage () + step ("boot_after_collect", dc.boot, config ()) + package.loaded.eech_dc = nil + local _, reloaded = step ("require_again", require, "eech_dc") + step ("boot_after_require", reloaded.boot, config ()) +end + +local case = assert (cases[args.case], "case=boot_twice|bad_arguments|missing_root|missing_map|missing_campaign|no_reuse") +case () +print ("end") diff --git a/eech-campaign/tools/build-windows.sh b/eech-campaign/tools/build-windows.sh index d80be7ef..9dbf3250 100644 --- a/eech-campaign/tools/build-windows.sh +++ b/eech-campaign/tools/build-windows.sh @@ -6,7 +6,8 @@ # eech-world.exe the host: embeds nothing, loads lua.dll and requires eech_dc # eech_dc.dll the whole headless engine as a Lua 5.1 module # lua.dll Lua 5.1.5, built as DCS World names its Lua DLL -# lua/ campaign.lua and metrics.lua +# lua/ campaign.lua, metrics.lua and lifecycle.lua +# BUILD-INFO.txt the source commit, the toolchain and the SHA-256 of each file above # # Usage (Git Bash or any Docker host): tools/build-windows.sh [] # (running binaries are locked by Windows: build into another directory while campaigns run) @@ -16,12 +17,18 @@ out=${1:-target/windows} export MSYS_NO_PATHCONV=1 here=$(cd "$(dirname "$0")/.." && pwd) repo=$(cd "$here/.." && pwd) +commit=$(cd "$here" && git rev-parse HEAD) +# uncommitted changes to anything the build reads make the commit an incomplete identity +dirty=$(cd "$here" && git status --porcelain -- . ../aphavoc ../modules | wc -l | tr -d ' ') case $repo in /[a-zA-Z]/*) repo=$(echo "$repo" | sed -E 's|^/([a-zA-Z])/|\1:/|') ;; esac docker build -q -t eech-build - < "$here/tools/Dockerfile" > /dev/null -docker run --rm -v "$repo:/src" -v eech-win-target:/target -v eech-cargo:/usr/local/cargo/registry \ +image=$(docker image inspect --format '{{.Id}}' eech-build) +# EECH_WIN_TARGET: the cargo target volume (a fresh name builds from scratch) +docker run --rm -v "$repo:/src" -v "${EECH_WIN_TARGET:-eech-win-target}:/target" -v eech-cargo:/usr/local/cargo/registry \ -e CARGO_TARGET_DIR=/target -e LUA_LIB=/opt/lua-mingw -e LUA_LIB_NAME=lua \ - -e OUT="/src/eech-campaign/$out" -w /src/eech-campaign eech-build sh -c ' + -e OUT="/src/eech-campaign/$out" -e COMMIT="$commit" -e DIRTY="$dirty" -e IMAGE="$image" \ + -w /src/eech-campaign eech-build sh -c ' set -e cargo build --release --target x86_64-pc-windows-gnu -p eech-dc -p eech-world > /target/build.log 2>&1 || { grep -E "error" /target/build.log | tail -20; exit 1; } tail -1 /target/build.log @@ -29,4 +36,16 @@ docker run --rm -v "$repo:/src" -v eech-win-target:/target -v eech-cargo:/usr/lo mkdir -p $out/lua cp /target/x86_64-pc-windows-gnu/release/eech_dc.dll /target/x86_64-pc-windows-gnu/release/eech-world.exe /opt/lua-mingw/lua.dll $out/ cp crates/eech-world/lua/*.lua $out/lua/ - ls -la $out' + { + echo "commit: $COMMIT" + echo "uncommitted changes in the build inputs: $DIRTY files" + echo "image: $IMAGE" + echo "rustc: $(rustc -V)" + echo "cargo: $(cargo -V)" + echo "mingw: gcc $(x86_64-w64-mingw32-gcc -dumpfullversion), gcc-mingw-w64-x86-64-posix $(dpkg-query -W -f "\${Version}" gcc-mingw-w64-x86-64-posix)" + echo "lua.dll: Lua 5.1.5 (lua-5.1.5.tar.gz, SHA-256 checked in tools/Dockerfile)" + echo "target: x86_64-pc-windows-gnu, release" + echo "sha256:" + cd $out && sha256sum eech_dc.dll eech-world.exe lua.dll lua/*.lua | sed "s/^/ /" + } > $out/BUILD-INFO.txt + cat $out/BUILD-INFO.txt' diff --git a/eech-campaign/tools/lifecycle-windows.ps1 b/eech-campaign/tools/lifecycle-windows.ps1 new file mode 100644 index 00000000..b68a584f --- /dev/null +++ b/eech-campaign/tools/lifecycle-windows.ps1 @@ -0,0 +1,88 @@ +# The module's lifecycle and failure behaviour on Windows, through the public +# path: eech-world.exe runs lua/lifecycle.lua, which requires eech_dc and boots +# it. One process per case (EECH boots at most once per process). Each case's +# steps are checked against the behaviour characterised at the M1 baseline +# (docs/m1-baseline.md). A case marked as a known defect is expected to fail +# that way; if it starts behaving differently, the check says so, so a fix or +# a regression is noticed and its expectation is reviewed. +# +# Usage (PowerShell): tools\lifecycle-windows.ps1 -Root [-Bin ] +# binaries: tools/build-windows.sh [dir] (default target\windows) +# the root: tools/retail-cvh.sh; boots take a few seconds, no campaign is run +param( + [Parameter(Mandatory = $true)] [string] $Root, + [string] $Bin, + [int] $TimeoutSeconds = 300 +) +$ErrorActionPreference = 'Stop' +$here = Split-Path -Parent $PSScriptRoot +$bin = if ($Bin) { $Bin } else { Join-Path $here 'target\windows' } +$out = Join-Path $here 'target\lifecycle-windows' +New-Item -ItemType Directory -Force $out | Out-Null +$world = Join-Path $bin 'eech-world.exe' +if (-not (Test-Path $world)) { throw "$world not found: run tools/build-windows.sh" } + +# per case: the expected exit (0, or the Windows exception code of a crash) and +# each step as " " +$load = @('load ok', 'prepare ok') +$notAgain = "not valid in the engine's state" +$cases = [ordered]@{ + boot_twice = @{ exit = 0; steps = $load + @( + 'boot ok', 'frames ok', "boot_again error $notAgain", 'frames_after ok', 'clock ok') } + bad_arguments = @{ exit = 0; steps = $load + @( + 'boot_unknown_gunship error unknown gunship "tiger"', + 'boot_no_install_root error error converting Lua nil to String', + 'boot_no_map error error converting Lua nil to String', + 'boot_no_campaign error error converting Lua nil to String', + 'boot_not_a_table error error converting Lua string to table', + 'boot ok', + 'frame_negative error out of range', + 'frame_not_a_number error error converting Lua string to u32', + 'frame_zero ok', 'frames_after ok') } + missing_root = @{ exit = 0; steps = $load + @( + 'boot_missing_root error bad argument: rejected by the engine', "boot_after error $notAgain") } + missing_campaign = @{ exit = 0; steps = $load + @( + 'boot_missing_campaign error EECH fatal error: Error opening file for reading', "boot_after error $notAgain") } + no_reuse = @{ exit = 0; steps = $load + @( + 'boot ok', 'frames ok', "boot_after_collect error $notAgain", 'require_again ok', "boot_after_require error $notAgain") } + # known defect: a missing map directory crashes the host process during boot + # (access violation in msvcrt.dll) instead of returning an error to Lua + missing_map = @{ exit = -1073741819; known_defect = $true; steps = $load } +} + +$status = 0 +foreach ($name in $cases.Keys) { + $case = $cases[$name] + $stdout = Join-Path $out "$name.out" + $stderr = Join-Path $out "$name.log" + $p = Start-Process -FilePath $world -NoNewWindow -PassThru -RedirectStandardOutput $stdout -RedirectStandardError $stderr -ArgumentList @( + (Join-Path $bin 'lua\lifecycle.lua'), "root=$($Root -replace '\\', '/')", "case=$name") + # read the handle now: Start-Process -PassThru reports no ExitCode otherwise + $null = $p.Handle + if (-not $p.WaitForExit($TimeoutSeconds * 1000)) { $p.Kill(); $p.WaitForExit() } + $lines = @(Get-Content $stdout | Where-Object { $_ -ne '' }) + $problems = @() + if ($p.ExitCode -ne $case.exit) { $problems += ('exit {0} (0x{0:X8}), expected {1} (0x{1:X8})' -f $p.ExitCode, $case.exit) } + $expected = @($case.steps) + if ($case.exit -eq 0) { $expected += 'end' } + for ($i = 0; $i -lt [Math]::Max($expected.Count, $lines.Count); $i++) { + $want = if ($i -lt $expected.Count) { $expected[$i] } else { $null } + $got = if ($i -lt $lines.Count) { $lines[$i] } else { $null } + if ($null -eq $want) { $problems += "unexpected step: $got"; continue } + if ($null -eq $got) { $problems += "missing step: $want"; continue } + $step, $result, $text = $want -split ' ', 3 + $fields = $got -split "`t", 3 + $ok = $fields[0] -eq $step -and ($want -eq 'end' -or $fields[1] -eq $result) + if ($ok -and $text) { $ok = $fields.Count -ge 3 -and $fields[2].Contains($text) } + if (-not $ok) { $problems += "step $($i + 1): got [$($got -replace "`t", ' | ')], expected [$want]" } + } + if ($problems.Count -eq 0) { + if ($case.known_defect) { Write-Host "${name}: KNOWN DEFECT (unchanged)" } else { Write-Host "${name}: PASS" } + } else { + Write-Host "${name}: FAIL$(if ($case.known_defect) { ' (known defect behaves differently: review its expectation)' })" + $problems | ForEach-Object { Write-Host " $_" } + $status = 1 + } +} +Write-Host "outputs: $out" +exit $status From b6ee8c8085c26fe83f9e3c408bf17df54a7204f8 Mon Sep 17 00:00:00 2001 From: fd-shockwave-bot Date: Fri, 25 Sep 2026 23:11:01 +0100 Subject: [PATCH 2/3] M1 baseline: the evidence record for the Windows eech_dc candidate docs/m1-baseline.md identifies the candidate (a8661eaa, the build image and toolchain, SHA-256 of eech_dc.dll, eech-world.exe and lua.dll), records what was run at it and the actual results, and states what that evidence does and does not establish, with the remaining M1 gaps: - Windows public path (eech-world.exe -> require ("eech_dc")): retail Georgia and Lebanon, 3 simulated hours each: expectations 30/30 and 39/39, both IDENTICAL to regression/windows/ - lifecycle and failure (tools/lifecycle-windows.ps1): 5 cases pass; a missing map directory crashes the host (known defect) - a from-scratch rebuild: code and data byte-identical; file hashes differ (link timestamps, header checksum, COFF symbol table) - kernel (supporting, Linux x86-64): 20 tests, 1,401/1,401 corpus scenarios identical to the C reference regression/README.md: the lifecycle checks; Lebanon has 39 expectations, not 37, since 12e8f692. README.md points to the record. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01C64rJFJLY8BTJbC1QimZDK --- eech-campaign/README.md | 4 +- eech-campaign/docs/m1-baseline.md | 187 +++++++++++++++++++++++++++++ eech-campaign/regression/README.md | 12 +- 3 files changed, 201 insertions(+), 2 deletions(-) create mode 100644 eech-campaign/docs/m1-baseline.md diff --git a/eech-campaign/README.md b/eech-campaign/README.md index fce8b927..babfaaf4 100644 --- a/eech-campaign/README.md +++ b/eech-campaign/README.md @@ -34,7 +34,9 @@ target/release/eech-world crates/eech-world/lua/campaign.lua root=/tmp/georgia s A twelve-hour Georgia campaign recorded this way is in [`recordings/georgia-12h.zip.acmi`](recordings/) (Tacview opens it directly). -Details, findings and limits: [`docs/engine.md`](docs/engine.md). +Details, findings and limits: [`docs/engine.md`](docs/engine.md). The M1 +candidate (Windows), what its evidence establishes and what it does not: +[`docs/m1-baseline.md`](docs/m1-baseline.md). ## The campaign kernel spike diff --git a/eech-campaign/docs/m1-baseline.md b/eech-campaign/docs/m1-baseline.md new file mode 100644 index 00000000..f2ef62c3 --- /dev/null +++ b/eech-campaign/docs/m1-baseline.md @@ -0,0 +1,187 @@ +# M1 baseline: the Windows `eech_dc` candidate + +The evidence record for M1 ([#19](https://github.com/flying-dice/eech_source_code/issues/19)). +It identifies the candidate, shows what it does through the public Lua path, +and states what that evidence establishes and what it does not. Its structure +follows [`BASELINE_RECORD.md`](../../docs/governance/BASELINE_RECORD.md). + +M1 prepares a candidate. The candidate is the whole engine as a Lua module, +which #19 allows as migration scaffolding. It is not a campaign-only module. + +## The candidate + +| | | +|---|---| +| Source | commit `a8661eaa`. That is the integration branch at `5f07697b` plus this record's checks (`lua/lifecycle.lua`, `tools/lifecycle-windows.ps1`) and the build record in `tools/build-windows.sh`. No engine or host code differs from `5f07697b`. | +| Build | `tools/build-windows.sh target/m1-candidate` (Git Bash, Docker): `x86_64-pc-windows-gnu`, release | +| Build image | `eech-build` from `tools/Dockerfile`, image `sha256:0c60c75db7b035e830c4834b1e15e096483631915bafc22c1af6aae02032bab3` | +| Toolchain | rustc 1.98.1 (48a229cea 2026-09-01); cargo 1.98.1; MinGW-w64 GCC 12 (`gcc-mingw-w64-x86-64-posix` 12.2.0-14+deb12u1+25.2+b1); Lua 5.1.5 as `lua.dll` | + +The build writes `BUILD-INFO.txt` beside the binaries. At `a8661eaa` it reports no uncommitted changes and these hashes: + +| File | SHA-256 | +|---|---| +| `eech_dc.dll` | `dd8c65687cde48f49fff49685cd09c480bae0b481f0b1af81bd1bb4d19f3a0e4` | +| `eech-world.exe` | `314fde3d4f7959bbf4bc27d5055c8198f8c59ef6c40f8f3cdd66684daa3eda35` | +| `lua.dll` | `d0c799fe4e1e615d335d18504d28a7b0d7c78d7f97d1754bb885b1097ac60350` | +| `lua/campaign.lua` | `3835b94fe0353e4658f6d5d7d5dcc2ffa7e8291eaee7663df59288a7c180fd03` | +| `lua/metrics.lua` | `dd28274e374e2698a84c0725e1e5ab3d5a564f13ecc0457bfce765df634d3053` | +| `lua/lifecycle.lua` | `dbce9000c176e8563c0443d4b82942b4aef332393fb95da4c1fab738f3c3f30c` | + +**Rebuild check.** A second build of `a8661eaa`, from scratch in a fresh cargo target volume (`EECH_WIN_TARGET=eech-win-target-m1-clean tools/build-windows.sh target/m1-clean`), gave: + +- different SHA-256 for `eech_dc.dll` (`82403592…`) and `eech-world.exe` (`e1e1e630…`), with the same sizes; +- identical `lua.dll` and scripts. + +A byte comparison locates every difference: + +- the link timestamps, in the PE header and in `eech_dc.dll`'s export directory; +- the header checksum; +- the COFF symbol table after the last section, which the loader doesn't map. + +The code, data, read-only data and relocations are byte-identical. So the hashes above identify these particular files, not every build of the commit. A bit-reproducible build would need the timestamps and symbols removed; that isn't done here. + +`tools/Dockerfile` starts from the floating tag `rust:bookworm`. Rebuilding the +image later can therefore change the toolchain. The image ID above identifies +the one used here. + +## The public path + +``` +eech-world.exe host process: owns the Lua 5.1 state (lua.dll) + lua/campaign.lua advances time, observes, records metrics; makes no campaign decisions + require ("eech_dc") eech_dc.dll: boot {...}, engine:frame (ms), engine:objects (), engine:clock () + original EECH C campaign, AI, tasking, logistics, and the physical world +``` + +The script only chooses how far to advance time and when to stop. Every +tasking, supply, combat and capture decision is made inside `eech_dc.dll`. +The physical world is inside the same DLL (see "What this does not establish"). + +## Evidence produced for this record + +Every command was run on Windows 11 (x64), except the kernel tests. The retail +installations are local and not in the repository: `tools/retail-cvh.sh` builds +Lebanon and `tools/retail-map3-installs.sh` builds Georgia. + +| Check | Command | Result | +|---|---|---| +| Campaign regression | `tools\regress-windows.ps1 -Georgia -Lebanon -Bin target\m1-candidate -Exact` | PASS for both. Campaign expectations: Georgia 30/30, Lebanon 39/39. Both runs IDENTICAL to `regression/windows/`. The two runs took 1,140 s in parallel. | +| Lifecycle and failure | `tools\lifecycle-windows.ps1 -Root -Bin target\m1-candidate` | 5 cases PASS; 1 KNOWN DEFECT unchanged (`missing_map`) | +| Lifecycle check sensitivity | the same with a root that has no Lebanon map | 4 cases FAIL (the boots crash), so the check detects changed behaviour | +| Kernel (supporting; Linux x86-64, in the `eech-build` image) | `cargo test --locked -p eech-campaign -p eech-harness -p eech-sys` | 20 tests pass (see below) | + +### The campaign regression + +Retail Georgia and Lebanon ran for 3 simulated hours each (seed 1, 100 ms frames, no recording), in parallel, on 2026-09-25. The table shows the figures the runner reported. The full metrics equal `regression/windows/*.json`. + +| After 3 h | Georgia blue | Georgia red | Lebanon blue | Lebanon red | +|---|---|---|---|---| +| Task sorties (task types) | 120 (15) | 227 (18) | 163 (18) | 227 (19) | +| Aircraft sorties | 147 | 271 | 247 | 281 | +| Weapons launched | 1,179 | 526 | 3,370 | 1,917 | +| Units lost | 196 | 198 | 293 | 254 | +| Units regenerated or spawned | 46 (infantry) | 96 (infantry) | 108 | 148 | +| Keysites captured | 1 FARP | 1 FARP | 1 FARP | none | +| Airbase ammo / fuel (from 100) | 64 / 28 | 55.3 / 28.2 | 49.9 / 31.1 | 45.5 / 38.3 | + +What the expectations check (`tools/campaign-expectations.py`): + +- **Both campaigns:** the task types flown by each side; combat and attrition. +- **Lebanon:** regen from reserves, production at factories and refineries, and airbases resupplied with ammo and fuel. +- **Georgia:** keysite captures. Its regen interval (16.7 h) is longer than the run, and it has no producers. + +The Windows baselines (`regression/windows/`) and the expectations were last +written at `12e8f692`. The candidate's identical result shows that `5f07697b` +(N3, H1, U1) left these 3-hour results unchanged. + +Both the baselines and the expectations were recorded from runs of this code +base and checked against the known EECH mechanics. They were not independently +derived. An identical result shows that this build repeats that behaviour. It +does not prove fidelity to historical EECH. + +## Lifecycle and failure behaviour observed + +Observed through `require ("eech_dc")`, one fresh `eech-world.exe` process per +case, on retail Lebanon (`lua/lifecycle.lua`). + +| Behaviour | Observed | +|---|---| +| **One boot per process** | Enforced. A second `boot` raises `eech: not valid in the engine's state`, and the first engine keeps running. | +| **Shutdown, reset, reuse** | Unsupported: no such operation exists. Setting the engine variable to nil and collecting garbage, or clearing `package.loaded` and requiring the module again, doesn't allow another boot (the same error). The limit is process-wide. | +| **A failed boot uses up the process's boot** | After an engine-rejected install root or a fatal missing campaign file, every later `boot` raises the same state error. The host has to restart the process. | +| **Argument errors in the binding** | An unknown gunship (`eech: bad argument: unknown gunship "tiger"`), a missing `install_root`, `map` or `campaign`, or a non-table config are Lua errors raised before the engine is touched. A valid `boot` afterwards succeeds. Missing fields give mlua's generic conversion message (`error converting Lua nil to String`), not an `eech:` message. | +| **Input the engine rejects** | A missing install root: `eech: bad argument: rejected by the engine`. The reason ("has no cohokum directory") goes only to stderr. | +| **An EECH fatal error** | A missing campaign file: `eech: EECH fatal error: Error opening file for reading: …`. `debug_fatal` comes back as a Lua error, not a crash. | +| **`frame` arguments** | A negative or non-numeric step is rejected by the binding, `frame (0)` is accepted, and the engine keeps running afterwards. | +| **Known defect: a missing map directory crashes the host** | An access violation in `msvcrt.dll` (reading address `0x134`) during `boot`. The process dies with `0xC0000005`, and no Lua error is raised. Reproduced with two installations. The crash reporter's call chain is invalid here: it can't walk frames through `msvcrt.dll`. | + +**Not exercised:** + +- A fatal error during `frame`. The engine would then be poisoned (`EngineError::Poisoned`: every later call fails), but no public input that triggers one was found. +- Threading and reentrancy: unsupported and unverified. The Rust `Engine` type isn't `Send`, which constrains Rust callers only. +- Host callbacks: none exist on this path, so there is no callback contract to characterise. + +**Risks for running in-process in DCS** (inferred, not tested): + +- The module imports `lua.dll` by name. +- A crash in the module ends the host process, as the missing-map case shows. +- With one boot per process, a mission restart can't boot the campaign again in the same process. +- The MinGW build uses `msvcrt.dll`, not the C runtime DCS uses. + +## Kernel and reference evidence (supporting only) + +This evidence belongs to the smaller campaign kernel: `crates/eech-campaign`, `eech-sys` and `eech-harness`. It exercises the kernel's Rust API, not `eech_dc` or the Lua path, so it corroborates the original C running natively but does not stand in for the candidate's evidence. + +- **What it covers:** the supply-chain slice (keysite supply, the low-on-supplies response, supply task creation and assignment, up to `assign_primary_task_to_group`), replayed against the eech-core-ts corpus: 1,401 scenarios and the float fixtures (`docs/conformance.md`). The corpus's expected output comes from the 32-bit C reference, with the TSTL port's verdict recorded alongside. +- **At this candidate:** the kernel's 20 tests pass on Linux x86-64 (GCC 12.2.0, `eech-build` image): public-API behaviour (11), the corpus (3), semantic scenarios (1), findings (2), smoke (2) and a doctest. The kernel's code last changed at `f6ecff99`, so this repeats the spike's result rather than covering anything new. + - The recorded corpus: all 1,401 scenarios give output identical to the C reference in every family: assess-group-supplies 291, entity-lifecycle 194, force-low-on-supplies 187, keysite-cargo 182, supply-task-assignment 186, supply-task-construction 189, update-timeline 172. + - The float fixtures (float32-rtz, double-sum-rtz, crate-row, aircraft-database) also match. + - The fresh-corpus test passes without checking anything: no fresh corpus was generated. +- **Not rerun here:** the kernel's i686 and Windows (Wine) runs, and fresh random corpora (`README.md`, "Commands"). + +## What this establishes + +- **Identity:** the candidate is an exact commit, build configuration and set of binary hashes. A from-scratch rebuild reproduces its code and data byte for byte. The file hashes differ (timestamps, symbol table), so they identify these particular files. +- **Campaign behaviour:** through the public Lua path on Windows, the original EECH campaign ran retail Lebanon and Georgia for 3 simulated hours each. Every mechanic the expectations check occurred: + - in both campaigns: task types flown, combat and attrition; + - in Lebanon: regen, production, and airbase ammo and fuel resupply; + - in Georgia: captures. + + The results were identical to the retained Windows baselines. +- **Lifecycle:** the module's lifecycle and failure behaviour as it exists, including one crash on bad input. +- **Kernel:** its conformance still holds at this commit, on Linux x86-64. + +## What this does not establish + +- **A campaign-only module.** The whole engine, physical world included, runs inside `eech_dc.dll`, and `eech-world.exe` only drives time and observes. That hosts can replace the world is unproven (M5). +- **Fidelity to historical EECH.** The expectations and baselines come from this code base's own runs (see above). +- **Longer runs.** Only 3 simulated hours per campaign ran at this candidate. The following were reported in the commit messages of `12e8f692` and `5f07697b` and in issue comments. They were not reproduced here and have no retained artifact in the repository: + - retail Lebanon to a conclusion; + - 195- and 14-hour Lebanon runs; + - 24-hour AddressSanitizer runs. +- **Review of the corrections.** The corrections to original EECH behaviour are part of the candidate: S2, S3, N1–N3, H1 and U1, and earlier X1, S1, C1, T1, T2, B2, W1 and E1–E3 (`docs/engine.md`). They are not classified or reviewed here. +- **Tacview.** No recording was made at this candidate. The recordings in `recordings/` come from earlier commits. +- **Linux.** The Linux baselines (`regression/*.json`) predate S2–U1 and weren't run. +- **CI.** The repository workflow currently fails before any test runs: `eech-world` can't link `lua5.1` on the runner. It gives no evidence for this candidate. Campaign runs stay local (retail data, and cost). + +## Remaining M1 gaps + +1. Classify and review the corrections to original EECH behaviour (defect correction or intentional semantic change). +2. Decide the contract for bad installation input. The missing-map crash would take down a host such as DCS. A failed boot using up the process's one boot may or may not be intended. +3. Give errors from the Lua binding the same classification as the engine's (`eech:` messages), and return the engine's rejection reason to Lua, not only to stderr. +4. Exercise a fatal error during `frame` (poisoning), or record it as untestable through the public path. +5. Reconcile the capability claims in the older documents (`feasibility.md`, `engine.md`, `handover.md`) against this candidate. +6. Rerun the kernel on Windows, and fix CI. +7. The M1 acceptance review on #19. + +## Reproduce + +```sh +# Git Bash, with Docker +tools/build-windows.sh target/m1-candidate # compare target/m1-candidate/BUILD-INFO.txt with the hashes above +``` +```powershell +tools\lifecycle-windows.ps1 -Root -Bin target\m1-candidate +tools\regress-windows.ps1 -Georgia -Lebanon -Bin target\m1-candidate -Exact +``` diff --git a/eech-campaign/regression/README.md b/eech-campaign/regression/README.md index b33d81a4..2e970eeb 100644 --- a/eech-campaign/regression/README.md +++ b/eech-campaign/regression/README.md @@ -84,12 +84,22 @@ the comparison in its description. ## How the test was checked -- **Expectations hold:** the Windows baselines meet every campaign expectation, 30 of 30 for Georgia and 37 of 37 for Lebanon. +- **Expectations hold:** the Windows baselines meet every campaign expectation, 30 of 30 for Georgia and 39 of 39 for Lebanon. Lebanon had 37 checks before `12e8f692` added its airbase resupply checks. - **A broken mechanic fails:** with Lebanon's regen disabled (`REGEN_FREQUENCY 60000`), both checks fail. - The expectations fail on all four regen items (blue and red jets and helicopters rebuilt: 0). - The baseline comparison fails on every spawned count and on the forces alive: blue jets 39 → 14, red helicopters 96 → 66. - **A different seed is a different war:** `seed=2` has 42 of 244 changed metrics outside tolerance, while every campaign expectation still holds. +## Lifecycle and failure checks + +`tools\lifecycle-windows.ps1 -Root [-Bin ]` takes a few seconds and runs no campaign. It boots the module through the same public path, one process per case, and checks: + +- one boot per process; +- argument errors and installation errors; +- that the engine can't be reused. + +The expected results are the behaviour characterised in [`docs/m1-baseline.md`](../docs/m1-baseline.md). The missing-map crash is checked as a known defect, so the check reports it if that behaviour changes. + ## The Docker/Linux runner `tools/regress-docker.sh ` and `tools/regress.sh` run the same From da9fde4453c643190f743181d9ea48ae1a708919 Mon Sep 17 00:00:00 2001 From: fd-shockwave-bot Date: Fri, 25 Sep 2026 23:37:20 +0100 Subject: [PATCH 3/3] M1 baseline: classify the remaining work against #19's exit question Review feedback on #60: not every limitation found is M1 work. The record now separates the M1 blocker (reviewing the behavioural corrections in the candidate), older claims to correct only where they contradict this baseline, the #19 acceptance review, and known limitations kept as follow-up candidates (bad installation input, binding error messages, unverified poisoning, the kernel on Windows, CI, bit-reproducibility). Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01C64rJFJLY8BTJbC1QimZDK --- eech-campaign/docs/m1-baseline.md | 33 ++++++++++++++++++++++--------- 1 file changed, 24 insertions(+), 9 deletions(-) diff --git a/eech-campaign/docs/m1-baseline.md b/eech-campaign/docs/m1-baseline.md index f2ef62c3..2c58759f 100644 --- a/eech-campaign/docs/m1-baseline.md +++ b/eech-campaign/docs/m1-baseline.md @@ -165,15 +165,30 @@ This evidence belongs to the smaller campaign kernel: `crates/eech-campaign`, `e - **Linux.** The Linux baselines (`regression/*.json`) predate S2–U1 and weren't run. - **CI.** The repository workflow currently fails before any test runs: `eech-world` can't link `lua5.1` on the runner. It gives no evidence for this candidate. Campaign runs stay local (retail data, and cost). -## Remaining M1 gaps - -1. Classify and review the corrections to original EECH behaviour (defect correction or intentional semantic change). -2. Decide the contract for bad installation input. The missing-map crash would take down a host such as DCS. A failed boot using up the process's one boot may or may not be intended. -3. Give errors from the Lua binding the same classification as the engine's (`eech:` messages), and return the engine's rejection reason to Lua, not only to stderr. -4. Exercise a fatal error during `frame` (poisoning), or record it as untestable through the public path. -5. Reconcile the capability claims in the older documents (`feasibility.md`, `engine.md`, `handover.md`) against this candidate. -6. Rerun the kernel on Windows, and fix CI. -7. The M1 acceptance review on #19. +## Remaining M1 work and known limitations + +This work is classified against #19's exit question: *can we use this candidate to develop a host while knowing exactly what it does and does not establish?* M1 can keep unsupported or unverified behaviour, as long as it is explicit. + +**M1 blocker:** + +1. Review and classify the behavioural corrections already in the candidate. These are S2, S3, N1–N3, H1 and U1, and the earlier X1, S1, C1, T1, T2, B2, W1 and E1–E3. Each is either a defect correction or an intentional semantic change. #19 requires them to be visible and reviewed rather than silently inherited. + +**A blocker only where an older claim contradicts this baseline:** + +2. Correct claims in the older documents (`feasibility.md`, `engine.md`, `handover.md`, `recordings/README.md`) only where they materially conflict with this record. Wholesale documentation cleanup is not M1 work. + +**M1 acceptance step:** + +3. The explicit milestone acceptance review on #19. + +**Known limitations and follow-up candidates** (recorded above, and not required for M1 to exit unless the acceptance review finds that one stops a host from safely respecting the candidate): + +- **Bad installation input:** a missing map directory crashes the host, and a failed boot uses up the process's one boot. +- **Error messages:** Lua-binding errors aren't classified as `eech:` messages, and the engine's rejection reason reaches only stderr. +- **Poisoning:** after a fatal error during `frame`, it stays unverified. No such fatal was manufactured just to test it. +- **Kernel on Windows:** the supporting kernel tests weren't run on Windows. +- **CI:** the repository workflow fails before any test runs. +- **Bit-reproducibility:** a rebuild reproduces the code and data but not the file hashes (see "Rebuild check"). ## Reproduce