Skip to content

Build missions and acts

Build the acts and missions your dimension’s single-player campaign draws from, in the CAMPAIGN tab’s ACTS and MISSIONS sub-tabs.

An act manifest picks the decision pool an act draws its persistent ADRs from.

  1. Build a decision pool first, in the POOLS sub-tab (Write decisions and decision pools) — an act can’t be created without one to point at.
  2. In the ACTS sub-tab, click + ADD (or UPLOAD JSON for several at once).
  3. Pick the Track (domain) and Act (1, 2 or 3).
  4. Pick the Act Pool — only pools on-domain for this act’s track and scoped act or mixed are usable.
  5. Set Act Draw Size — how many decisions this act draws from the pool. The pool needs enough matching decisions to cover it, four times over.
  6. Optionally pin specific decisions from this track, scoped act, so they always appear rather than being drawn.
  7. Click CREATE. Your dimension gets one manifest per (track, act) pair. To change one, edit the existing manifest — creating the same pair again from the form is rejected, while a bulk JSON upload replaces it in place.
  1. In the MISSIONS sub-tab, click + ADD (or UPLOAD JSON for several at once).
  2. Fill in Track, Act, Mission Index, Name, Narrative, Grading Accuracy Star, Question Budget, Difficulty Tier, and optionally Is Mastery and Shared With.
  3. Fill in the three raw-JSON fields: MAP, AI and QUESTIONS — see the worked example and field notes below. Each flags invalid JSON syntax as you type; a schema error only appears once you click CREATE.
  4. Optionally fill MODIFIERS (buffs, scheduled effects, or caffeine stations).
  5. Toggle on the ADR block and fill layoutId (a map built in the MAPS tab), missionPoolId (a decision pool scoped mission or mixed), and missionDrawSize. This is how every platform mission is built. With it on, the ADR map is what’s actually played — any MAP JSON you filled in above becomes advisory only — and you don’t author the objective: it’s capture-the-HQ if the map has an Enemy HQ marker, otherwise a mode rolled each attempt (see Build a map).
  6. Click CREATE.

An ADR mission — the map referenced by adr.layoutId is what’s actually played; the map below is the advisory fallback.

{
"track": "SECURITY",
"act": 1,
"missionIndex": 1,
"name": "Phishing Trip",
"narrative": "An email just promised a gift card for your login. Reach the mail gateway before muscle memory clicks \"Enable Content\".",
"gradingAccuracyStar": 70,
"questionBudget": 8,
"map": { "kind": "authored", "layout": {
"hexes": [
{ "q": -2, "r": 0, "s": 2, "elevation": 0, "domain": "SECURITY" },
{ "q": -1, "r": 0, "s": 1, "elevation": 0, "domain": "SECURITY" },
{ "q": 0, "r": 0, "s": 0, "elevation": 0, "domain": "SECURITY" },
{ "q": 1, "r": 0, "s": -1, "elevation": 0, "domain": "SECURITY" },
{ "q": 2, "r": 0, "s": -2, "elevation": 0, "domain": "SECURITY" }
],
"units": [{ "playerId": "player-0", "domain": "SECURITY", "position": { "q": -2, "r": 0, "s": 2 }, "hp": 3 }],
"bases": [], "pois": [], "playerCount": 1, "playerNames": ["You"], "initialBP": 3
} },
"ai": { "policy": "none", "roleName": "No Adversary", "unitCount": 0, "hideBase": true },
"questions": { "domain": "SECURITY", "tierRange": [1, 2], "targetSubtopics": ["owasp_top10"] },
"difficultyTier": "standard",
"adr": { "layoutId": "phishing-trip-map", "missionPoolId": "security-act-1-pool", "missionDrawSize": 1 }
}

An ADR mission’s objective isn’t authored — it comes from the map, as described in step 5.

A non-ADR mission has no OBJECTIVES field in the ADD MISSION form; objectives can only be set through bulk JSON upload. Left unset, the mission defaults to capturing the enemy base if the map has one, otherwise to eliminating every enemy unit. Validation REJECTS a mission with no objectives and no safe default to fall back on: ai.policy is none or invisible, ai.hideBase is set, or the map has no enemy base or units at all (the default would then be an instant win or impossible to complete). Author objectives explicitly in any of those cases.

Every objective needs a type and a description. The type sets what else it needs:

Type Extra fields
reach target (a hex), optional unitDomain
capture count, optional poiType
capture-hq —
eliminate —
survive turns
time-pressure maxTurns — a fail condition, not a win condition
no-loss optional maxLosses (defaults to 0)
constraint allowedDomains (at least one)

bonusObjective is also JSON-only, on any mission — ADR included. An ADR mission’s bonus must be board-agnostic (no-loss, survive, time-pressure, constraint): a reach/capture/capture-hq bonus targets a specific authored hex, which doesn’t exist once the board is rolled, and validation warns on it.

kind: "authored" hand-builds the layout inline (hexes, units, bases, points of interest). kind: "seeded" instead gives a radius, seed and mask to generate one — but match creation doesn’t yet honour the seed or mask, so don’t rely on a seeded map being reproducible.

policy is one of inert, active, invisible or none. none means no opponent at all — it requires an authored map with no AI units and unitCount at 0 or absent. The rest (difficulty, personality, roleName, unitCount, unitDomains, hideBase) are optional.

domain, a tierRange ([min, max]), and targetSubtopics — every target subtopic must be valid for the domain.

  • questionBudget should sit inside its act’s range — Act 1: 8–12, Act 2: 12–16, Act 3: 15–18, or 16–22 for a mastery mission. Going outside it warns rather than blocks.
  • An authored opposing unit should start within 4 hexes of the player’s unit — placing it further away warns rather than blocks.

The dimension’s owner and admins. For a dimension owned by an organisation, that organisation’s owner and admins too.