modules — mission api 1/5: hello pawns — the module runtime and the missions DSL - #8424
modules — mission api 1/5: hello pawns — the module runtime and the missions DSL#8424keithharvey wants to merge 1 commit into
Conversation
There was a problem hiding this comment.
@sorenmarkert wrote (#8382):
A spec couldn't hurt.
There was a problem hiding this comment.
@keithharvey wrote (#8382):
This is done.
There was a problem hiding this comment.
@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?
There was a problem hiding this comment.
@sorenmarkert wrote (#8375):
It feels like mission related modules ought to be grouped together under modules/missions/ or something.
There was a problem hiding this comment.
@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
There was a problem hiding this comment.
@sorenmarkert wrote (#8382):
Why not end the game immediately instead of waiting until the next frame?
There was a problem hiding this comment.
@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).
There was a problem hiding this comment.
@sorenmarkert wrote (#8382):
Is this available in multiplayer? Or will it need a guard?
There was a problem hiding this comment.
@keithharvey wrote (#8375):
Definite guard, good catch.
There was a problem hiding this comment.
@WatchTheFort wrote (#8382):
This looks like a tool for debugging, not a feature that will stay in
There was a problem hiding this comment.
@keithharvey wrote (#8382):
Yes, that's correct.
There was a problem hiding this comment.
@sorenmarkert wrote (#8382):
Shouldn't this require a manifest making mission module depend on the match flow module?
There was a problem hiding this comment.
@keithharvey wrote (#8375):
Absolutely, nice one.
There was a problem hiding this comment.
@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.
There was a problem hiding this comment.
@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.
There was a problem hiding this comment.
@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.
There was a problem hiding this comment.
@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.
There was a problem hiding this comment.
@sorenmarkert wrote (#8382):
Apart from the pieces added here, how much more is there to the module framework?
There was a problem hiding this comment.
@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.
There was a problem hiding this comment.
@sorenmarkert wrote (#8382):
The dirs in modules/ are generally modules, but modules/types is an inconsistency.
There was a problem hiding this comment.
@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.
There was a problem hiding this comment.
@keithharvey wrote (#8382):
Fixed this one. Good catch.
There was a problem hiding this comment.
@sorenmarkert wrote (#8382):
Where will validation fit in?
E.g. the unitdef and the objective need to exist.
There was a problem hiding this comment.
@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.
There was a problem hiding this comment.
@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?
There was a problem hiding this comment.
@sorenmarkert wrote (#8382):
Also, UnitDef sounds more like a noun than a verb.
There was a problem hiding this comment.
@keithharvey wrote (#8382):
yeah this really isn't a verb, that's a good call out. It's a lookup of a UnitDef
There was a problem hiding this comment.
@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).
There was a problem hiding this comment.
@sorenmarkert wrote (#8382):
Could this have a more accurate name? Policy sounds so middle manager. 😉
There was a problem hiding this comment.
@keithharvey wrote (#8382):
or "strategy", which is less ambiguously noun-verby
There was a problem hiding this comment.
@sorenmarkert wrote (#8382):
The DSL will need a way to name triggers, so they can be referred to for enabling and disabling
There was a problem hiding this comment.
@WatchTheFort wrote (#8382):
These should be MetaUnitAdded and MetaUnitRemoved, to catch every possible reason for a player having / no longer having a unit.
…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.
9e2cd6e to
501045b
Compare
|
Superseded by the twelve-stage stack starting at #8473: the same work re-cut by domain with foundations first, one commit per PR. |
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:
The modules
MatchFlow.Victory/Defeat) enter through the same gate as elimination, so game-over has exactly one owner.The contract with tooling
Any types file declaring
---@meta dslpublishes 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'srequirescontributes 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
.luafile 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 playshello_pawnsend to end through the real verdict path.