Skip to content

modules — mission api 1/5: hello pawns — the module runtime and the missions DSL - #8424

Closed
keithharvey wants to merge 1 commit into
masterfrom
hello_pawns
Closed

modules — mission api 1/5: hello pawns — the module runtime and the missions DSL#8424
keithharvey wants to merge 1 commit into
masterfrom
hello_pawns

Conversation

@keithharvey

@keithharvey keithharvey commented Jul 23, 2026

Copy link
Copy Markdown
Collaborator

One commit, one runtime: the mission/campaign API as encapsulated game modules, with a served editor over it. Playable today: start a skirmish and run /luarules mission hello_pawns.

This is 1/5 of the modules stack — mission api: this (the foundation) → #8464 matchflow → #8465 the served editor → #8460 combat → #8461 CM8 Ashfall; then multiplayer: #8462 the mode grammar → #8463 sharing v2. This PR's diff is the foundation; the editor and tooling sections below describe what the stack builds toward.

The authoring surface

Mission files are Lua in a sandbox whose injected environment IS the API — dot-only, closure-free, terminator-free:

When(Team.Player.Has(UnitDef("armpw"), 3))
	.Do(Objective("build_pawns").Complete())

When(Objective("build_pawns").IsComplete())
	.Do(MatchFlow.Victory(Team.Player))
  • Conditions declare their inputs (a closed, typed event vocabulary): the engine hooks only watched callins, evaluates on events plus a cadence for pollers.
  • Trigger progress lives in the engine's serializable state table, never in closures (the savegame rule); identity is filename + declaration order, which is also the hot-reload key.

The modules

  • module runtime — discovery, per-state contracts, auto-loaded subdirectories, module-owned modoptions; the shim for engine-native module loading.
  • matchflow — game_end's decision extracted behind a policy pipeline; scripted verdicts (MatchFlow.Victory/Defeat) enter through the same gate as elimination, so game-over has exactly one owner.
  • missions — the trigger engine, DSL builder, and loader.

The contract with tooling

Any types file declaring ---@meta dsl publishes the DSL surface as ordinary LuaCATS annotations. The editor tooling (BAR-Devtools: bar-mission-kit) derives its entire grammar from these files — statement heads, chain verbs, slot semantics from parameter type aliases, editor enums from literal unions. Adding vocabulary is a game-side annotation edit; the language server does not change. Vocabulary lives with the module that injects it: a module named in the missions manifest's requires contributes its verbs and their types — the dependency list is the whitelist, for the sandbox and the tooling alike. What these files declare is the only Lua that works in a mission file, and the tooling marks that edge explicitly.

Editing

The served editor renders missions as editable sentence cards in-game (RmlUi), in VS Code, and in a browser — all blind terminals of one server-rendered form. Every edit round-trips through the recognizer: a change that leaves the subset is rejected and the .lua file stays the source of truth.

Testing

Pure-Lua specs under busted (spec/modules/{missions,matchflow}) cover the engine, DSL, verbs, and policies. The headless integration test plays hello_pawns end to end through the real verdict path.

@github-actions

github-actions Bot commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

Integration Test Results

15 tests  +15   7 ✅ +7   5s ⏱️ +5s
 1 suites + 1   8 💤 +8 
 1 files   + 1   0 ❌ ±0 

Results for commit 9539424. ± Comparison against base commit a04d911.

♻️ This comment has been updated with latest results.

Comment thread luarules/gadgets.lua

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@sorenmarkert wrote (#8382):

A spec couldn't hurt.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@keithharvey wrote (#8382):

This is done.

Comment thread modules/matchflow/api.lua

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@sorenmarkert wrote (#8382):

This module is not placed under modules/missions. Is it supposed to able to be used outside the Mission API as well?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@sorenmarkert wrote (#8375):

It feels like mission related modules ought to be grouped together under modules/missions/ or something.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@keithharvey wrote (#8375):

The modules are general purpose, so they handle all behavior for their category. In this case match flow can do both multiplayer and sp victory conditions. This is nice architecturally because then missions are just a thin wrapper around base behavior and it's all one system

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@sorenmarkert wrote (#8382):

Why not end the game immediately instead of waiting until the next frame?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@keithharvey wrote (#8375):

This is mostly about collapsing state. Because triggers can trigger Victory or Defeat, which run inside GameFrame - calling Spring.GameOver reentrantly from mid-callin is asking for undefined orderng, and deferring gives one idempotent application point (double victory in one frame collapses to one verdict).

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@sorenmarkert wrote (#8382):

Is this available in multiplayer? Or will it need a guard?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@keithharvey wrote (#8375):

Definite guard, good catch.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@WatchTheFort wrote (#8382):

This looks like a tool for debugging, not a feature that will stay in

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@keithharvey wrote (#8382):

Yes, that's correct.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@sorenmarkert wrote (#8382):

Shouldn't this require a manifest making mission module depend on the match flow module?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@keithharvey wrote (#8375):

Absolutely, nice one.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@sorenmarkert wrote (#8382):

Trigger conditions will need to specify which call ins they should use. Some will require more than one call in, unless someone extends the engine.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@keithharvey wrote (#8375):

yeah this makes a ton of sense. Right now we're polling but this should absolutely be thought about in this prototype stage, because like the DSL etc. it's just much easier to build out the event connections as we add trigger types than it is to do it when we have 15 of them. "I watch UnitFinished + UnitDestroyed" is absolutely information we need attached to the trigger model.

  • Verbs declare their own inputs Team.Player.Has -> { UnitFinished, UnitDestroyed, UnitGiven, UnitTaken }
  • The engine indexes at register input -> list of watching triggers. An event marks its watchers dirty
  • The gadget subscribes only to callins some registered trigger actually watches. the don't-hook-what-you-don't-user rule from the performance model applies
  • The wrinkle: not every input is an engine callin Objective("x").IsComplete() watches a rulesparam, and rulesparam changes have no callin - but the mission module is the thing that completes objectives, so it can emit its own event. So inputs names events on a small internal bus, where engine callins are one producer and modules are another.
  • Nothing touches the save story: inputs is configuration (reloads from source), dirty flags are derived (never serialized), and the editor's live state display reads the same metadata plus a derived last-result table.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@sorenmarkert wrote (#8382):

I'd really like to see Register removed from the DSL as it doesn't tell anything about the trigger's behaviour.
Removing T would also be nice.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@keithharvey wrote (#8375):

T we can kill, no problem, though that namespace is serving as an intellisense entry point menu for users, is the only downside to killing it. Press T, see options. As the number of globals grow that becomes a problem not to have. I'm going to sit on this one for a second and think about it.

Register looks superfluous but that one is actually pretty important. The terminator gives us a decisive way to know the statement is over (it's a transaction commit basically) and catch half-completed statements. If we just let them run together (especially if we kill T), consumers could hit VERY subtle bugs when the parser doesn't comprehend the statement is over.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@sorenmarkert wrote (#8382):

Apart from the pieces added here, how much more is there to the module framework?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@keithharvey wrote (#8375):

This is pretty much it. This adds the policy engine that sharing also uses but it's basically just what you see: discovery, contracts, the loaders.

Comment thread types/modules.lua

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@sorenmarkert wrote (#8382):

The dirs in modules/ are generally modules, but modules/types is an inconsistency.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@keithharvey wrote (#8375):

True, I like the modules' own types being subdirectoried (along with specs also bucking our normal conventions in that way), but this one should go in root types/modules for sure.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@keithharvey wrote (#8382):

Fixed this one. Good catch.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@sorenmarkert wrote (#8382):

Where will validation fit in?
E.g. the unitdef and the objective need to exist.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@keithharvey wrote (#8382):

The type system check should handle most of that for us for free. We will use the same data the parser/enricher provide to validate the constants.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@sorenmarkert wrote (#8382):

If this module depends on all the feature modules, and those all need the parameter types, they will need to move to a shared module, no?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@sorenmarkert wrote (#8382):

Also, UnitDef sounds more like a noun than a verb.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@keithharvey wrote (#8382):

yeah this really isn't a verb, that's a good call out. It's a lookup of a UnitDef

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@sorenmarkert wrote (#8382):

This is available in the DSL, no? Yet it's calculated on load time (and will eventually need to come from the lobby).

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@sorenmarkert wrote (#8382):

Could this have a more accurate name? Policy sounds so middle manager. 😉

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@efrec wrote (#8382):

It is a policy pattern, so I like the name being obvious. What would you name this?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@keithharvey wrote (#8382):

or "strategy", which is less ambiguously noun-verby

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@sorenmarkert wrote (#8382):

The DSL will need a way to name triggers, so they can be referred to for enabling and disabling

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@WatchTheFort wrote (#8382):

These should be MetaUnitAdded and MetaUnitRemoved, to catch every possible reason for a player having / no longer having a unit.

@keithharvey

Copy link
Copy Markdown
Collaborator Author

Review discussion from the previous incarnations of this PR (#8375, #8382) has been ported below as file-anchored threads, with original authors and sources noted — no need to re-post anything this time.

…pawns

All additive; nothing existing changes. The module layout and its loader
bootstrap; the missions module — closure-free When-chain DSL (dot-only,
Finalize is the commit point), trigger engine with declared inputs,
loader/sandbox, chat guard, a mission bus closed by type; framework-shared
types in root types/; hello_pawns as the walking skeleton, with an
integration test.

matchflow's decision logic (liveness, ceremony, the game_over policy) lands
here with specs, but nothing loads it yet — the surrogate matchflow_verdict
carries the mission verdict until the next layer's handover.
@keithharvey keithharvey changed the title mission api 1/3: hello pawn mission api: the campaign runtime, its DSL, and the served editor Jul 26, 2026
@keithharvey
keithharvey force-pushed the hello_pawns branch 2 times, most recently from 9e2cd6e to 501045b Compare July 26, 2026 12:19
@keithharvey keithharvey changed the title mission api: the campaign runtime, its DSL, and the served editor mission api 1/3: the campaign runtime, its DSL, and the served editor Jul 26, 2026
@keithharvey keithharvey mentioned this pull request Jul 26, 2026
@keithharvey keithharvey changed the title mission api 1/3: the campaign runtime, its DSL, and the served editor modules — mission api 1/5: the campaign runtime, its DSL, and the served editor Jul 26, 2026
@keithharvey keithharvey changed the title modules — mission api 1/5: the campaign runtime, its DSL, and the served editor modules — mission api 1/5: hello pawns — the module runtime and the missions DSL Jul 26, 2026
@keithharvey

Copy link
Copy Markdown
Collaborator Author

Superseded by the twelve-stage stack starting at #8473: the same work re-cut by domain with foundations first, one commit per PR.

@keithharvey
keithharvey deleted the hello_pawns branch July 27, 2026 04:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant