campaign api: hello pawn - #33
Closed
keithharvey wants to merge 5 commits into
Closed
Conversation
Integration Test Results15 tests +1 6 ✅ +1 4s ⏱️ +2s For more details on these failures, see this check. Results for commit 9bff5ec. ± Comparison against base commit 4f8b1da. ♻️ This comment has been updated with latest results. |
keithharvey
commented
Jul 21, 2026
| "UnitDef", | ||
| "Objective", | ||
| "MatchFlow", | ||
| "describe", |
Owner
Author
There was a problem hiding this comment.
these busted globals are unfortunately correct because transforms hasn't merged yet.
module_handler + policy_builder + modules/types, the gadgets.lua module-gadget shim, the dbg_test_runner sandbox exposures (setmetatable/getmetatable/getfenv), busted helper wiring, and emmyrc globals for the mission authoring surface. Borrow footprint beyond the three planned files: gadgets.lua hunk (9 lines), test-runner sandbox (3 lines), .busted helper, emmyrc globals — small, as matchflow_module_plan.md hoped. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Pure-Lua core of the mission runtime: TriggerEngine (registration, cadenced evaluation, once semantics, unregister-by-identity for hot reload, progress in plain state tables per the savegame rule), the chain builder (T.When(cond).Then(fn).Register(), dot-only — no colon calls, no metatables on the mission-facing surface), and the pure halves of the verbs (UnitDef refs, Team.Has reading counts from ctx). Trigger identity = filename + declaration order, stamped at Register. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… mission matchflow (demo-minimal, scripted-verdict only): api.lua forwards Victory/Defeat to the verdict gadget through GG.MatchFlow (one owner of the pending verdict; contract includes are per-consumer), and the gadget applies it via Spring.GameOver on the next GameFrame. Coexists with game_end the way missions do today — demo-only, called out. mission_loader: /luarules mission <name> loads modules/missions/<name>/triggers/*.lua into the trigger engine with the injected environment — T, Team, UnitDef, Objective, MatchFlow and nothing else; the sandbox is the API surface. Unregister-by-identity before each file include makes loading idempotent and is the hot-reload path (/luarules mission reload). Evaluation every 15 frames; Team.Player = first human team (demo rule). hello_pawns: the six-line mission, dot-only chain. emmyrc: busted globals (describe/it/...) — drops the repo-wide spec error noise; new files check clean. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Plays the mission for real in the headless harness: arm via /luarules mission hello_pawns, cheat-spawn to 3 Pawns, expect the GameOver callin with the player's ally team, and assert the objective rulesparam flipped first. Passes under the headless integration suite; the scripted GameOver does not disturb the rest of the suite (deathmode=neverend startscript). Gotchas encoded here: the runner wants bare global test()/setup()/ skip(), and SyncedRun ships the caller's stack locals, not upvalues. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The demo bug: the mission was won the moment the third Pawn's blueprint appeared in the factory. GetTeamUnitDefCount includes under-construction units, so the ctx now filters through GetUnitIsBeingBuilt — Has(UnitDef, n) means n FINISHED units, as the type annotation already promised. The integration test now proves it: two finished Pawns plus one nanoframe must not win across two evaluation cadences; completing the frame (SetUnitHealth build=1) must. Test archaeology encoded in comments: the nanoframe needs a live builder passed to CreateUnit or unit_prevent_lab_hax2 destroys it a frame later, and it needs real build progress or abandoned-frame decay would kill it anyway. Headless suite: 0 failures. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Owner
Author
|
Moving to a cross-repo draft against upstream. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The runnable Hello Pawns demo from the campaign-API plan: six lines of mission Lua win a skirmish, authored in the trigger DSL, loaded by chat command, verified end to end in the headless harness.
The whole mission (
modules/missions/hello_pawns/triggers/win.lua):What's here
module_handler.lua,policy_builder.lua,modules/types/, plus minimal wiring (a 9-line module-gadget shim ingadgets.lua, 3 test-sandbox lines indbg_test_runner). The borrow stayed small — this doubles as the master bootstrap step of the matchflow module plan.modules/missions/lib/trigger_engine.lua) — pure Lua, busted-spec'd. Trigger identity = filename + declaration order; fired flags live in the engine's plain state tables (GetState/SetState), never in closures — the savegame rule from commit one.UnregisterFileis the hot-reload path.lib/dsl.lua,lib/verbs.lua) — every chain step is a plain call with parens, no colon calls, no metatables on the mission-facing surface. Chain → descriptor → sink, the policy_builder idiom.MatchFlow.Victory/Defeat→ verdict gadget →Spring.GameOver). The call shape is the contract; the real module (game_end extraction) lands underneath it later. Coexists with game_end the way missions do today — demo-only, not multiplayer-safe./luarules mission hello_pawnsloadstriggers/*.luawith an injected env of exactlyT, Team, UnitDef, Objective, MatchFlow; the sandbox is the API surface. Evaluates every 15 frames.Team.Player= first human team (demo rule)./luarules mission reloadexists but is untested.Team.Player.Has(unitDef, n)(n completed units — nanoframes don't count, with an integration-test case proving a factory blueprint can't win the mission),Objective(name).Complete()(echo + rulesparam, no UI),MatchFlow.Victory/Defeat.Testing
spec/modules/missions/), suite green.luaui/Tests/hello_pawns/test_win.luaplays the mission for real under the headless harness (just bar::integrations): arms it via the chat command, spawns two Pawns plus a nanoframe, asserts no verdict across two cadences, completes the frame, expectsGameOverwith the player's ally team. Suite: 0 failures.🤖 Generated with Claude Code