Carwars Bundle — Widget Vocabulary Extensions¶
Bundle id: carwars
Vocab spec base: STORYTANGL_WIDGET_VOCAB.md v1.5
Status: draft v0.4 · aligned to v1.5 core vocabulary
Audience: authors writing carwars-genre bundles; port implementers covering the carwars profile suite
This document is a Tier P3 genre extension (per main spec §8). It does not modify Tier S/P1/P2 contract surfaces. It defines:
Typed sub-shapes that carwars bundles populate on
UIHintsandRollFragment.inputs(extending open dicts; no new top-level types).The bundle’s conventional usage of
PieceFragment.realized,ZoneConstraints, andRollFragmentfor its game patterns.A worked end-to-end example (the Garage turn).
Test patterns and port parity for these affordances.
Anything in this document that requires more than an extension to an existing open dict is a candidate for promotion to Tier P2. Such candidates go through the CLI Floor Rule (main spec §0.2).
The genre-specific enrichments here are advisory render hints. A client
that ignores ui_hints.stat_check, ui_hints.drag, or vehicle silhouette
layout still conforms when it renders the underlying choice, piece,
zone, kv, and roll surfaces with the click-pick fallbacks described
in the main spec.
0 · Genre summary¶
Car Wars Adventure Gamebooks combine RPG-style stat-check resolution, inventory management, and vehicle outfitting. The patterns generalize beyond the carwars-specific framing — equip-an-adventurer, build-a-deck, ship-loadout, crafting bench, RNG skill checks, salvage / shopping. This extension covers them once, named after the motivating bundle but reusable.
Three orthogonal patterns the carwars bundle exercises:
Pattern |
Main spec mechanism |
Genre layer adds |
|---|---|---|
Slot-style equipment zones |
|
Conventions for which capacity kinds are used; layout hints for vehicle silhouettes |
Catalog-style shopping/salvage |
|
Conventions for |
RNG stat checks |
|
|
1 · Slot-zone conventions¶
A slot is a zone with constraints.capacity set, used to model an
equipment receptacle (vehicle weapon mount, adventurer hand slot, ship
hardpoint, kitchen prep station, deck synergy slot).
// fragment_type: "group", group_type: "zone", zone_role: "slot"
{
uid: "z-front-mount",
fragment_type: "group",
group_type: "zone",
member_ids: ["pc-rocket-launcher"],
constraints: {
accepts_kind: ["weapon"],
accepts_tags: ["mounted"],
capacity: [
{ kind: "count", max: 1, unit: "weapon" },
{ kind: "weight", max: 3, unit: "stone", sum_property: "weight",
ledger_key: "vehicle.front.weight" }
]
},
layout_hints: {
orientation: "row",
reveal: "all",
// genre-extension subkey for the silhouette renderer:
silhouette: { region: "front", x: 0.5, y: 0.1 }
},
hints: { label_text: "front mount" }
}
Conventional capacity kinds for carwars¶
Capacity kind |
Used for |
|
Notes |
|---|---|---|---|
|
Per-mount weapon limits |
(n/a) |
Most common; |
|
Per-mount stone-weight limits |
|
Mirrors a |
|
Per-mount engine-load limits |
|
Same shape as weight, different ledger |
|
Vehicle-total limits |
(multiple entries) |
Multiple |
Empty slots are visible¶
Per main spec §5.1 (Decision Legibility), an empty slot referenced by an
open place choice MUST render as a placeholder showing its label and
constraint summary. CLI port renders [ front_mount: empty (weapon, cap 1, 0/3 stone) ].
The empty slot is the click-target for placement.
Inverse: removing a placed piece (unmount)¶
Two conforming patterns. Bundle authors pick one per situation:
Per-slot pick choice. Each occupied slot exposes its own
pickchoice (e.g.,e-unmount-front). Simple, accessible, matches CLI floor.Pieces choice over the slot’s contents. A single
accepts.kind="pieces"choice withtarget_zone_ref="z-front-mount"andmin:1, max:1. Lets the player select which to remove when slots hold multiples.
Both are conforming. Bundles MAY mix.
Swap as compose¶
A swap is a compose choice with two parts:
{
edge_id: "e-swap-front",
text: "Replace front weapon",
accepts: {
kind: "compose",
parts: [
{ role: "remove", accepts: {
kind: "pieces",
constraints: { target_zone_ref: "z-front-mount" },
min: 1, max: 1 } },
{ role: "install", accepts: {
kind: "place",
source_zone_ref: "z-vehicle-loose",
target_zone_ref: "z-front-mount" } }
]
}
}
Optional and rare; most bundles handle swap as two consecutive turns.
2 · Catalog conventions¶
A catalog is a zone whose members are PieceFragment instances with
realized=False (offers). A buy/take action commits an
accepts.kind="pieces" choice with target_zone_ref pointing at the
catalog and target_kind: ["piece"] (or genre-specific kind names).
// PieceFragment with realized=False — an offer
{
uid: "pc-flamethrower",
fragment_type: "piece",
piece_id: "flamethrower",
kind: "weapon",
realized: false,
zone_ref: "z-murphs-catalog",
properties: {
name: "Flamethrower",
weight: 3,
power_draw: 1,
ammo: 4,
description: "Splash damage. Burns 1 fuel per shot."
},
cost: [{ ledger_key: "wallet", delta: -400, unit: "credit" }],
available: true,
hints: { label_text: "Flamethrower" }
}
Use-case patterns (same shape, different framing)¶
Framing |
|
Choice constraints |
|---|---|---|
Shop |
present (negative |
|
Salvage |
absent |
|
Quest reward |
absent |
|
Crafting output |
absent (cost is in the recipe’s compose) |
emerges from a recipe |
Race conditions on availability¶
If a shop item runs out of stock between turns, the next envelope sends a
control fragment with update mutating the offer’s available: false
and adding unavailable_reason: "Out of stock until next session.". The
offer stays visible (decision legibility); the choice referencing it
either dims that row or removes it from selectable members.
Backend-issued realized UIDs¶
When a player commits to buy pc-flamethrower, the next envelope contains:
A new
PieceFragmentwithrealized=Trueand a backend-issuedpiece_id(e.g.,pc-flamethrower-#a3for the third flamethrower minted in this session) — placed in the player’s inventory zone.A
controlfragment removing the offer from the catalog zone (or flipping itsavailableto false if the catalog represents stock).
Bundles document their realized-id naming convention. Carwars uses
<offer-piece-id>-#<session-counter> for stackable items; for unique
items the offer’s id carries forward unchanged.
3 · Pre-roll choice → RollFragment flow¶
The carwars bundle uses RollFragment (main spec §7.3) for stat checks.
The pattern is two envelopes:
Envelope N carries the triggering ChoiceFragment with a
stat_check ui_hint. Its accepts.kind is pick — the player commits to
attempt the check; the backend rolls.
Envelope N+1 carries the resolved RollFragment (with canonical
outcome) and a new choice list reflecting whichever branch the roll
selected.
ui_hints.stat_check typed sub-shape¶
# Pseudo-Pydantic; lives in genre extension code, not main spec types
class StatCheckHint(BaseModel):
label: str # "Driving check" / "Lockpicking"
dice: str # "2d6", "1d20", "3d10"
target: int # difficulty value
against: dict[str, str] | None = None # {"piece_id": "you", "property": "driving"}
modifier: int = 0
success_text: str | None = None # "5/6 chance" — pre-computed by backend
When ui_hints.stat_check is present on a choice:
The widget MUST surface the difficulty BEFORE commit so the player understands the wager. CLI:
Driving 12 (2d6, +0). Web: a small badge with the dice + target + advisory probability.The choice’s
accepts.kindMUST bepick(orcomposewith a pick part). Anything more interactive should generate the check after the interactive part resolves.success_textis advisory; if absent, the port may compute its own preview (“5/6 chance” for 2d6 vs 12). The client MUST NOT use this preview to gate commit; it’s purely informational.
RollFragment.inputs for kind: "dice" rolls¶
The carwars bundle uses kind: "dice" exclusively. The inputs shape:
{
"dice": "2d6",
"rolled": [4, 5],
"modifier": 0,
"total": 9,
"target": 12
}
RollFragment.outcome is one of "success", "fail", "crit_success",
"crit_fail". Bundles MAY define additional outcome strings for genres
with finer gradation ("partial", "with_cost").
CLI floor rendering¶
> drive
Driving check (2d6 vs 12, modifier +0).
rolled: 4 + 5 = 9.
outcome: fail.
The wheel jerks under you.
The outcome word is not optional — every roll renders its outcome verbatim so transcripts are auditable. Per main spec §5.2, the visual ritual on the web port is skippable to this CLI-equivalent rendering.
4 · ui_hints.drag typed sub-shape¶
Drag-and-drop is a presentation enhancement of accepts.kind="place".
Per main spec §5.3 (Input Parity), every drag interaction MUST also work
as a two-step click-pick. The drag hint structure tells the web port how
to wire the drag affordance; ports that don’t support drag (CLI, tkinter)
ignore the hint entirely.
# Genre-extension typed sub-shape on UIHints
class DragHint(BaseModel):
enabled: bool = True
grab_zone_ref: str # uid of the source zone
drop_zone_refs: list[str] # uids of valid target zones
preview: Literal["capacity", "blocker", "ghost"] | None = None
fallback_label: str | None = None # "Drag to mount, or click each step"
Floor rule (mandatory)¶
Drag-and-drop is a presentation enhancement of a click-pick choice. Every choice with
ui_hints.dragMUST have an equivalent click-pick interaction reachable in the same turn. The CLI port ignoresui_hints.dragentirely.
This is just §5.3 applied to drag specifically. A web port test asserts
that for every place-accepting choice with ui_hints.drag, a click-pick
sequence (click the source piece, click the target zone) produces an
identical commit payload.
Capacity preview during drag¶
When preview = "capacity", the slot tile updates its capacity bar live
as the user drags a candidate piece over it. The client computes the
projected overflow by reading:
The target zone’s projected capacity (from
ProjectedState.kv_listrow keyed bytarget_zone.constraints.capacity[].ledger_key).The dragged piece’s
properties.<sum_property>value.
If the sum exceeds the projected max, the slot tile turns red. This is
purely advisory; the backend re-validates on drop.
Reduced motion¶
With prefers-reduced-motion, no drag ghost, no preview animation. The
slot tile turns its border green/red statically as the candidate piece is
held over it (via keyboard navigation or screen-reader-friendly focus
rings), and the click-pick model is preferred in any conflict.
5 · Worked example — Garage turn¶
The classic outfitting scenario: the player is at Murph’s Auto, deciding which weapons to mount on the Beast (their interceptor) before heading back into combat. Six fragments per envelope, demonstrating slot zones, catalog with offers, place + pieces accepts, capacity ledger, and optional drag affordance.
Turn N — entering the Garage¶
fragments: [
// Scene-setting prose
{ uid: "f-prose-1", fragment_type: "content", content_format: "md",
content: "Murph's Auto, end of the strip. Your **Beast** sits on the "
+ "lift, hood up, oil pan dripping. Murph wipes his hands on a "
+ "rag and looks up. \"What'll it be today?\"" },
// Scene group
{ uid: "f-scene", fragment_type: "group", group_type: "scene",
member_ids: [ ... ] },
// Vehicle status zone
{ uid: "z-vehicle-status", fragment_type: "group", group_type: "zone",
member_ids: ["pc-the-beast"],
layout_hints: { orientation: "row" },
hints: { label_text: "Beast — your interceptor" } },
// Slot zones — the vehicle's mount points
{ uid: "z-front-mount", fragment_type: "group", group_type: "zone",
member_ids: ["pc-rocket-launcher"],
constraints: {
accepts_kind: ["weapon"],
capacity: [
{ kind: "count", max: 1 },
{ kind: "weight", max: 3, sum_property: "weight",
ledger_key: "vehicle.front.weight" }
]
},
layout_hints: { orientation: "stack",
silhouette: { region: "front" } },
hints: { label_text: "front mount" } },
{ uid: "z-turret", fragment_type: "group", group_type: "zone",
member_ids: [],
constraints: {
accepts_kind: ["weapon"],
capacity: [
{ kind: "count", max: 1 },
{ kind: "weight", max: 4, sum_property: "weight",
ledger_key: "vehicle.turret.weight" }
]
},
layout_hints: { silhouette: { region: "top" } },
hints: { label_text: "turret" } },
{ uid: "z-back-mount", fragment_type: "group", group_type: "zone",
member_ids: [],
constraints: {
accepts_kind: ["weapon"],
capacity: [
{ kind: "count", max: 1 },
{ kind: "weight", max: 2, sum_property: "weight",
ledger_key: "vehicle.back.weight" }
]
},
layout_hints: { silhouette: { region: "back" } },
hints: { label_text: "back mount" } },
// Catalog zone — Murph's wares (offers)
{ uid: "z-murphs-catalog", fragment_type: "group", group_type: "zone",
member_ids: ["pc-flamethrower", "pc-armor-plate", "pc-vulcan",
"pc-rl-mk2"],
layout_hints: { orientation: "grid", reveal: "all" },
hints: { label_text: "Murph's wares" } },
// Loose-parts zone — owned but not mounted
{ uid: "z-vehicle-loose", fragment_type: "group", group_type: "zone",
member_ids: ["pc-spare-armor", "pc-spare-tire"],
layout_hints: { orientation: "row" },
hints: { label_text: "parts on hand" } },
// Realized pieces (mounted weapons + spares)
{ uid: "pc-rocket-launcher", fragment_type: "piece", piece_id: "rl-1",
kind: "weapon", realized: true,
zone_ref: "z-front-mount",
properties: { name: "Rocket Launcher", weight: 3, power_draw: 1, ammo: 4 },
hints: { label_text: "Rocket Launcher" } },
{ uid: "pc-spare-armor", fragment_type: "piece", piece_id: "sa-1",
kind: "armor", realized: true,
zone_ref: "z-vehicle-loose",
properties: { name: "Spare armor", weight: 2 },
hints: { label_text: "Spare armor" } },
// Offers (realized: false)
{ uid: "pc-flamethrower", fragment_type: "piece", piece_id: "flamethrower",
kind: "weapon", realized: false,
zone_ref: "z-murphs-catalog",
properties: { name: "Flamethrower", weight: 3, power_draw: 1, ammo: 4 },
cost: [{ ledger_key: "wallet", delta: -400, unit: "credit" }],
available: true,
hints: { label_text: "Flamethrower",
description_text: "Splash damage. Burns 1 fuel per shot." } },
{ uid: "pc-rl-mk2", fragment_type: "piece", piece_id: "rl-mk2",
kind: "weapon", realized: false,
zone_ref: "z-murphs-catalog",
properties: { name: "Rocket Launcher Mk II", weight: 3, ammo: 6 },
cost: [{ ledger_key: "wallet", delta: -650, unit: "credit" }],
available: false,
unavailable_reason: "Out of stock until next session.",
hints: { label_text: "Rocket Launcher Mk II" } },
// Choices
{ uid: "f-choice-mount", fragment_type: "choice",
edge_id: "e-mount", text: "Mount a weapon.",
accepts: {
kind: "place",
source_zone_ref: "z-vehicle-loose",
predicate_ref: "is_open_weapon_slot"
},
ui_hints: {
hotkey: "1",
drag: { // genre-specific
enabled: true,
grab_zone_ref: "z-vehicle-loose",
drop_zone_refs: ["z-front-mount", "z-turret", "z-back-mount"],
preview: "capacity"
}
} },
{ uid: "f-choice-unmount-front", fragment_type: "choice",
edge_id: "e-unmount-front", text: "Remove front weapon.",
accepts: {
kind: "pieces",
min: 1, max: 1,
constraints: { target_zone_ref: "z-front-mount" }
},
available: true,
ui_hints: { hotkey: "2" } },
{ uid: "f-choice-buy", fragment_type: "choice",
edge_id: "e-buy", text: "Buy from Murph's.",
accepts: {
kind: "pieces",
min: 0, max: 5,
constraints: { target_zone_ref: "z-murphs-catalog",
target_kind: ["weapon", "armor"] }
},
ui_hints: { hotkey: "3" } },
{ uid: "f-choice-leave", fragment_type: "choice",
edge_id: "e-leave", text: "Hit the road.",
accepts: { kind: "pick" },
ui_hints: { hotkey: "4" } },
{ uid: "f-interpret-command", fragment_type: "choice",
edge_id: "interpret_command", text: "Try a command.",
accepts: { kind: "raw_command" },
ui_hints: { hotkey: ">" } }
]
Projected state — the ledger¶
sections: [
{ section_id: "wallet", title: "Wallet", kind: "wallet",
value: {
value_type: "kv_list",
items: [
{ key: "credits", value: 1240, unit: "cr" }
]
} },
{ section_id: "vehicle_load", title: "Vehicle load", kind: "score",
value: {
value_type: "kv_list",
items: [
{ key: "total_weight", value: 4, max: 12, unit: "stone",
hint: "bar", emphasis: "warn" },
{ key: "front", value: 3, max: 3, unit: "stone",
hint: "bar", emphasis: "warn" },
{ key: "turret", value: 0, max: 4, unit: "stone", hint: "bar" },
{ key: "back", value: 0, max: 2, unit: "stone", hint: "bar" }
]
} }
]
Two paths through this turn¶
Click flow. Click front mount in the vehicle silhouette → the
e-unmount-front choice expands inline → click Rocket Launcher →
commit. The control fragment from the next envelope moves the piece’s
zone_ref from z-front-mount to z-vehicle-loose. Click Buy → the
catalog zone becomes selectable → click Flamethrower → click the buy
button. Backend mints pc-flamethrower-#a3, places it in
z-vehicle-loose, debits the wallet. Click front mount → pick
Flamethrower, commit.
Drag flow. Drag Flamethrower from the catalog onto front mount.
The web port posts two commits: e-buy for the offer first, waits for
the realized piece’s UID, then e-mount with that UID as the
piece_id. Two commits, one gesture. The bundle’s drag handler is
responsible for the choreography; the contract is unchanged.
In both flows, the CLI port sees the same envelopes and renders them identically — just without the silhouette graphics. Per §5.3 (Input Parity), the click flow IS the CLI floor.
CLI port rendering of this turn¶
Murph's Auto, end of the strip. Your Beast sits on the lift, hood up,
oil pan dripping. Murph wipes his hands on a rag and looks up. "What'll
it be today?"
[Beast — your interceptor]
front mount: Rocket Launcher (3 stone) [cap 1, 3/3 stone]
turret: empty [cap 1, 0/4 stone]
back mount: empty [cap 1, 0/2 stone]
[parts on hand]
- Spare armor (2 stone)
- Spare tire (1 stone)
[Murph's wares]
- Flamethrower 400 cr (3 stone, 4 ammo)
- Armor plate 200 cr (2 stone, +2 armor)
- Vulcan gun 300 cr (2 stone, 10 ammo)
- Rocket Launcher Mk II 650 cr (out of stock)
-- ledger --
credits: 1240 cr
vehicle load:
total: 4/12 stone (warn)
front: 3/3 stone (warn)
turret: 0/4 stone
back: 0/2 stone
1) Mount a weapon.
2) Remove front weapon.
3) Buy from Murph's.
4) Hit the road.
>
6 · Combat-resolution patterns¶
Carwars combat uses RollFragment for hit/miss/damage resolution.
A typical exchange:
Turn N — attack choice¶
{ uid: "f-choice-attack", fragment_type: "choice",
edge_id: "e-attack-thug", text: "Fire the rocket launcher at the thug.",
accepts: { kind: "pick" },
ui_hints: {
hotkey: "1",
stat_check: {
label: "Gunner check",
dice: "2d6",
target: 8,
against: { piece_id: "you", property: "gunnery" },
modifier: 1,
success_text: "10/12 chance to hit"
}
}
}
Turn N+1 — the roll¶
{ uid: "f-roll-attack", fragment_type: "roll",
label: "Gunner check",
kind: "dice",
inputs: {
dice: "2d6",
rolled: [3, 6],
modifier: 1,
total: 10,
target: 8
},
outcome: "success",
narrative: "The rocket streaks across the highway and detonates "
+ "against the thug's quarter panel. Sparks fly.",
against: { piece_id: "you", property: "gunnery" },
ritual_hints: {
skip_label: "Skip the roll",
auto_skip_after_seen: false, // first roll always shown
allow_replay: true,
duration_ms: 1800
}
}
Damage as a follow-on roll¶
If hit, a damage roll follows in the same envelope (or the next):
{ uid: "f-roll-damage", fragment_type: "roll",
label: "Damage",
kind: "dice",
inputs: { dice: "1d6", rolled: [4], modifier: 0, total: 4 },
outcome: "success",
narrative: "4 points of damage.",
ritual_hints: { duration_ms: 800, auto_skip_after_seen: true }
}
The auto_skip_after_seen: true lets later instances of the same
fragment-type-shape play instantly on replay — so the first damage roll
of a session is shown in full but the tenth is instant. Per §5.2, the
player can override either way.
Combat as multi-actor rounds¶
For multi-actor exchanges (you vs three thugs), the bundle currently emits
one RollFragment per actor. A future genre extension could introduce a
typed CombatReport fragment that summarizes the round; for now the
prose content fragments between rolls carry the narration. This is
a deferred Tier P3 candidate.
7 · Edge cases — canonical responses¶
Situation |
Where caught |
Player sees |
|---|---|---|
Slot kind mismatch (try to mount engine in weapon slot) |
client may pre-empt via |
inline transcript, slot tile flashes red |
Capacity overflow (place exceeds weight max) |
client previews via projected |
slot bar shows projected over-fill before commit; hard error after |
Empty catalog (shop sold out) |
runtime emits |
catalog zone renders as |
Salvage min not met (must pick at least 1) |
client validator on |
“pick at least one” reason chip on the commit button |
Stat-check fail-branch (rolled below target) |
next envelope’s |
transcript shows roll + outcome; choice list updates |
Stat-check crit failure |
|
same as fail; bundles MAY add a one-shot animation, but per §5.2 still skippable |
Repair partial (paid but not enough) |
backend’s commit response carries an |
transcript clarifies what was actually repaired |
Offer no longer available (race) |
next envelope updates |
catalog row dimmed with |
8 · Testing patterns¶
Vue component tests (Vitest + @vue/test-utils)¶
SlotZone— render empty / occupied / over-capacity (preview); kind-mismatch rejection; click-pick path; drag-drop path withui_hints.drag; reduced-motion fallback.Catalog— renderzone_role: "catalog"with mix of available and unavailable offers; cost stripe; selection model; cart-roundtrip.RollWidget— render everyoutcomevalue the bundle uses; against- stat callout; reduced-motion (no shake); CLI fallback (narrative + inputs summary + outcome word).AnnotatedKvRow— bar / fraction / delta variants; emphasis states; fallback whenvalueis plain scalar.
Integration tests (Vitest + JSDOM)¶
Catalog with one
available: falseoffer + one whose cost exceeds the ledger ⇒ both rendered as disabled with reason.A
placecommit synthesized from click flow vs drag flow ⇒ payload identical (modulo timestamp).RollFragmentwith eachoutcome⇒ transcript line includes literal outcome word; visual ritual respects reduced-motion preference.Skip on a
RollFragmentwithduration_ms: 1800⇒ canonical-instant rendering reaches the next-turn choices in < 100ms.
E2E (Playwright)¶
Garage happy path. Boot → unmount RL from front → buy Flamethrower from catalog → mount in front → ledger updates (weight, wallet) match expected.
Capacity overflow. Try to add a 4-stone weapon to a 3-stone-cap slot ⇒ client preview shows red BEFORE commit; if the preview is bypassed (e.g. CLI port), backend rejects with
interpretation.result = "blocked"and the slot stays unchanged.Stat-check fork. Pick a
stat_checkchoice → next envelope contains aRollFragmentAND a different choice list (fail branch verified).Sold-out offer. Catalog with one
available: falseoffer ⇒ button disabled with reason text,pc-rl-mk2row dimmed.Drag fallback. Disable JS drag events ⇒ click-pick path still works end-to-end.
Time parity check. Time-to-canonical-outcome on web port (skip invoked) ≤ CLI-port time-to-canonical-outcome + 100ms tolerance for every fixture in
bundles/carwars/fixtures/.
Conformance smoke (CI)¶
For every fixture with a
zone_role: "slot", assert each slot has ahints.label_textand aconstraints.capacitydeclaration.For every offer (
PieceFragmentwithrealized: false), assertproperties.nameis set andcostis either present or intentionally absent (salvage/quest framing).For every
RollFragment, assertoutcomeis in the canonical set for the bundle ({success, fail, crit_success, crit_fail}) andnarrativeis present.For every choice with
ui_hints.drag, assert there’s an equivalent click-pick path producing the same commit payload (Input Parity).For every
RollFragmentwithritual_hints.duration_ms > 0, assert the web port’s skip key produces the canonical-instant rendering.
9 · Port parity addendum (carwars-specific widgets)¶
Widget |
Web (Vue) |
CLI |
Hypothetical tk |
Hypothetical map renderer |
|---|---|---|---|---|
|
tile in vehicle silhouette; capacity bar; drag target |
|
|
nodes on the vehicle silhouette |
|
card grid with cost stripes |
numbered list with cost column |
listbox + per-row buttons |
n/a (use list view) |
|
inline transcript card; one-shot tumble anim |
|
inline |
inline transcript card |
Annotated kv row |
bar / fraction / delta |
|
|
sidebar |
|
combined click-or-drag in silhouette |
numbered two-step pick |
listbox + |
click-on-silhouette |
|
per-slot |
|
listbox + |
click-on-silhouette |
Plural cost previews |
inline |
|
text label |
tooltip |
|
small badge on choice button |
|
|
overlay on map node |
|
drag-drop interactions |
(ignored) |
(ignored) |
drag on silhouette |
Appendix — BGG mechanism coverage¶
The carwars bundle exercises the following BGG-top-20 game mechanisms via the patterns above. This is informational; not contract.
BGG mechanism |
Carwars realization |
Vocab elements used |
|---|---|---|
Variable Player Powers |
Vehicle stats and weapon templates |
|
Action Points |
Period-based turn budgets |
|
Dice Rolling |
Stat checks, damage |
|
Hand Management |
Inventory zones |
|
Set Collection |
Outfitting matched weapon systems |
|
Press Your Luck |
Combat continuation choices |
branching choices after |
Resource Management |
Wallet, weight, ammo |
annotated |
Variable Phase Order |
Combat initiative |
per-turn |
Modular Board |
Map-zone variants |
|
Income |
Per-turn credit grants |
projected |
Mechanisms requiring more than the above (bidding, area control, pattern
building) are open candidates for genre-extension work; some depend on
predicate_ref registration (main spec §7.4).
End of carwars EXTENSIONS v0.4.