Theme, objectives & visibility
Three top-level blocks shape what players see and know. All three are first-match-wins: the engine evaluates entries top-down and uses the first that matches, so order them most-specific-first with a catch-all last.
interface GameTheme { playerMarkers?: MarkerColorRule[]; playerHeadings?: { others?: HeadingVisibilityRule[] };}
interface MarkerColorRule { when?: { key: string; value: unknown }; color: string }interface HeadingVisibilityRule { when?: { key: string; value: unknown }; visible: boolean }playerMarkers
Section titled “playerMarkers”Colors each player’s map marker from their state. First match wins; an entry
with no when is the catch-all default.
theme: playerMarkers: - when: { key: it, value: true } color: "#dc2626" # red — the it player - color: "#16a34a" # green — everyone elseSource: apps/wage-engine/src/games/tag/game.yaml.
when matches against the player being colored. A richer example layers
several states (winner > carrier > dead > default):
playerMarkers: - { when: { key: won, value: true }, color: "#f59e0b" } # gold - { when: { key: inHill, value: true }, color: "#eab308" } # yellowSource: apps/wage-engine/src/games/king_of_the_hill/game.yaml.
playerHeadings.others
Section titled “playerHeadings.others”Controls whether other players’ facing direction is shown to a viewer. Evaluated against the target player’s state; if no rule matches, that player’s heading is hidden. Players always see their own heading.
theme: playerHeadings: others: - { when: { key: it, value: true }, visible: true } # you can see which way the tagger faces - { visible: false } # everyone else's heading hiddenUse this to hide directional info that would give away a hider or a runner.
objectives
Section titled “objectives”interface ObjectiveRule { when?: { key: string; value: unknown }; text: string; navigation?: { target: NavigationTarget; mode?: "suggested" | "required" };}
type NavigationTarget = | { kind: "point"; pointId: string } | { kind: "zone"; zoneId: string } | { kind: "itemKind"; itemKind: string };Sets each player’s objective — the short goal text on their phone — from their
state, every tick. First match wins; the no-when entry is the default.
objectives: - { when: { key: it, value: true }, text: "Tag someone!" } - { text: "Don't get tagged." }Source: apps/wage-engine/src/games/tag/game.yaml.
Navigation hints
Section titled “Navigation hints”An objective can attach a navigation target — a point, a zone, or “the nearest
item of a kind” — that the phone surfaces as a waypoint. mode: "suggested" lets
the player override it; mode: "required" keeps it pinned (gameplay enforcement
is still your rules’ job).
objectives: - when: { key: carrying, value: true } text: "Get it home!" navigation: { target: { kind: zone, zoneId: red_ship }, mode: suggested } - text: "Grab some loot." navigation: { target: { kind: itemKind, itemKind: treasure_chest }, mode: suggested }visibility
Section titled “visibility”interface VisibilityRule { when?: { key: string; value: unknown }; // on the VIEWER's state canSeeOthers: "any" | { key: string; value: unknown }[];}Per-role position visibility. For each viewer, the engine evaluates rules
top-down and applies the first matching rule’s canSeeOthers to strip the
positions of non-matching players from that viewer’s world snapshot. A rule with
no when is a catch-all. Viewers always see themselves; the GM always sees
everyone unfiltered.
canSeeOthers: "any"— the viewer sees all positions.canSeeOthers: [{ key, value }, …]— the viewer sees only players matching all those predicates. Keys areteamIdorstate.<name>.
# Sardines-style: seekers only see other seekers; hiders see everyone.visibility: - when: { key: seeker, value: true } # if the viewer is a seeker… canSeeOthers: [{ key: state.seeker, value: true }] # …they see only seekers - canSeeOthers: any # everyone else (hiders) sees allThis is what makes hide-and-seek work: hiders are invisible to the people hunting them, but can watch the hunters close in.