Alternative displays
Game authors declare optional render modes in gameinfo.displays. The front end uses group for radio choices (mutually exclusive, e.g. default 3D vs flat) and omits group for independent checkbox toggles (e.g. hide overlays). Declarative constraint fields match variants so combinations can be validated and sanitized in one place.
Display preferences are per-user client settings (not stored in game state). See Game object for IRenderOpts passed into render().
Overview
Each entry in gameinfo.displays is an AlternativeDisplay object (see gameinfo.json):
| Field | Purpose |
|---|---|
uid |
Stable identifier in user settings and IRenderOpts.altDisplays |
group |
When present, mutually exclusive with other displays in the same group (radio UI) |
#group sentinel |
{ uid: "#projection" } marks the implicit default when no member uid is active |
experimental |
Reserved for production filtering (not applied in v1) |
Radio vs checkbox: a group field → one active uid per group. No group → optional checkbox; multiple checkboxes may be active together.
Submitted arrays: clients send active display uids as string[] (empty = default renderer). Do not send #group sentinels in saved settings.
Who consumes constraints:
| Layer | Behaviour |
|---|---|
| Front display picker (Phase 4) | Disables invalid controls; sanitizes selection |
GameBase.resolveActiveDisplays() |
Sanitizes before hasDisplay() checks in render() |
Legacy IRenderOpts.altDisplay |
Single string; coalesced to one uid until callers migrate |
Declaring constraints
Same semantics as variants: enabledWhen, conflictsWith, requires, implies, impliesLock.
Evaluation rule:
selectable(uid) =
enabledWhen gates pass
AND requires ⊆ active
AND conflictsWith ∩ active = ∅
Game render() helpers
Prefer hasDisplay(opts, uid) over comparing opts.altDisplay to a single string:
const showThreatened = !this.hasDisplay(opts, "hide-threatened");
const vertexStyle = this.hasDisplay(opts, "vertex-style");
| Method | Use |
|---|---|
resolveActiveDisplays(opts) |
Sanitized uid list for this game |
hasDisplay(opts, uid) |
Whether a toggle is active |
displaySelectionState(opts) |
{ active, groupChoice } for radio groups (e.g. flat vs isometric) |
Pass altDisplays: string[] from the front when multiple toggles are active. altDisplay remains supported for one uid (including legacy composite uids expanded during sanitize).
Legacy composite uids
Before combinable toggles, some games used shortcut uids such as hide-both. LEGACY_DISPLAY_EXPAND and gameinfo implies map these to multiple active uids during sanitize:
| Legacy uid | Expands to |
|---|---|
hide-both |
hide-threatened, hide-influence |
Prefer independent checkbox uids in new games instead of encoding every combination.
FAB cycle (simple display games)
isSimpleDisplayCycleGame() is true when the FAB can cycle without combining independent toggles:
- One ungrouped display (e.g. Asli
swap-prison): cycles off → on → off. - One radio group with default + at least one member (e.g. default isometric vs
flat).
The board FAB uses displayCycleSteps(). Games with two or more ungrouped checkbox displays (or a mix of checkboxes and groups) hide the FAB (settings modal only).
Add group: "projection" on flat (and rely on #projection sentinel) so Druid/Carnac qualify once metadata is updated (Phase 5b).
Shared library API
Exported from @abstractplay/gameslib (mirrors variant exports):
sanitizeDisplaySelection,validateDisplaySelection,resolveIncomingDisplaysevaluateDisplayAvailability,isDisplaySelectable,resolveDisplayGroupsisSimpleDisplayCycleGame,displayCycleSteps,nextDisplayCycleStep
Example — overlay toggles (Tumbleweed-shaped)
displays: [
{ uid: "hide-threatened" },
{ uid: "hide-influence" },
{
uid: "hide-both",
implies: ["hide-threatened", "hide-influence"],
impliesLock: true,
},
],
In render(), use hasDisplay(opts, "hide-threatened") rather than a dedicated hide-both branch.
Example — projection radio (Druid-shaped)
displays: [{ uid: "flat", group: "projection" }],
Default renderer when resolveActiveDisplays(opts) is empty; flat when hasDisplay(opts, "flat").