Skill: stanestane/game-design-prototyping-companion (version 1.0.1)

--- name: game-design-prototyping-companion description: Track game design prototype ideas, branching outcomes, dead ends, baselines, and next experiments, and optionally generate a simple SVG visualization of prototype evolution. Use when a prototype leads to several possible follow-up paths, when the team needs to backtrack without losing learned branches, when exploring unknowns through multiple iterations, or when you want both a written prototype log and a branch map of how the concept evolved over time. --- # Game Design Prototyping Companion Track prototype evolution, not just prototype results. Use this skill when prototyping produces branching decisions, dead ends, alternative paths, and backtracking. The aim is to preserve learning structure: what was tested, what was learned, what branch it created, which path was followed, and which paths remain available to revisit. This skill can also generate a simple SVG branch map from a lightweight text or JSON structure. ## What to produce Generate one or more of these outputs: 1. **Prototype log** - what was tested and why 2. **Branch record** - what paths emerged from the result 3. **Decision state** - which branch is current, parked, dead, baseline, or promising 4. **Backtrack notes** - what can be revisited later and under what condition 5. **SVG branch map** - a visual map of prototype evolution ## Core principle A prototype is not just a yes/no answer. It often creates a tree: - one branch becomes the current path - another becomes a dead end - another becomes a parked idea worth revisiting later - another reveals a stronger question than the original one The skill should preserve that tree. ## Workflow ### 1. Define the prototype node For each prototype node, record: - **Node ID** - **Prototype name** - **Question being tested** - **What was built or simulated** - **What was learned** - **Result state** Use result states such as: - **baseline** - **promising** - **branch trigger** - **dead end** - **parked** - **production candidate** ### 2. Record branch options When a prototype produces several next moves, capture each branch explicitly. For each branch, record: - **Branch ID** - **Parent node** - **New idea or variation** - **Reason it exists** - **Current status** ### 3. Mark the chosen path without deleting the others Do not treat the selected branch as the only meaningful output. Preserve: - abandoned paths - deferred paths - weird side paths - stronger substitute ideas revealed by the test ### 4. Add backtrack logic If a branch is not chosen now, record: - what would justify revisiting it - what blocker currently prevents it - what later discovery might make it relevant again ### 5. Generate a visual map when useful Use `scripts/branch_map_svg.py` to render a simple SVG from a branch-map JSON file. Read: - `references/branch-map-format.md` for the input structure - `references/example-branch-map.json` for an example ## Response structure Use this structure unless the user asks for something else: ### Prototype Node - Node ID: ... - Question: ... - Built / simulated: ... - Learned: ... - State: ... ### Branches Created 1. ... 2. ... 3. ... ### Current Chosen Path - ... ### Parked / Revisit Later - ... ### Suggested Next Prototype - ... ## Visualization workflow When the user wants a visual branch map: 1. write the branch data to JSON using the format in `references/branch-map-format.md` 2. run `scripts/branch_map_svg.py <input.json> <output.svg>` 3. return the SVG path and summarize what the map shows ## Style rules - Preserve branching history. - Prefer explicit node IDs over vague prose. - Distinguish clearly between what was learned and what was merely assumed. - Do not erase dead ends; label them. - Do not confuse the current path with the best possible path forever. ## References - `references/branch-map-format.md` for the JSON structure - `references/example-branch-map.json` for a starter example - `references/state-labels.md` for recommended branch/node labels ## Working principle Prototype trees are design memory. If you only remember the path you chose, you lose the intelligence of the paths you rejected.

path: references/branch-map-format.md
content: # Branch Map Format

Use a simple JSON object with `nodes` and `edges`.

## Structure

```json
{
  "title": "Prototype branch map",
  "nodes": [
    {
      "id": "P0",
      "label": "Grouped request board",
      "state": "baseline",
      "note": "Initial prototype to test readability"
    },
    {
      "id": "P1",
      "label": "Conflict-based board",
      "state": "promising",
      "note": "Players noticed tradeoffs faster"
    }
  ],
  "edges": [
    {
      "from": "P0",
      "to": "P1",
      "label": "What if requests compete?"
    }
  ]
}
```

## Node fields
- `id`: short unique ID
- `label`: visible node title
- `state`: baseline | promising | branch-trigger | dead-end | parked | production-candidate
- `note`: optional short explanation

## Edge fields
- `from`: source node ID
- `to`: target node ID
- `label`: short branch reason or question

## Layout note
The bundled script uses a simple top-to-bottom tree-like layout.
It is not a full graph engine. Keep maps small to medium for readability.


path: references/example-branch-map.json
content: {
  "title": "Request Board Prototype Evolution",
  "nodes": [
    {
      "id": "P0",
      "label": "Basic request board",
      "state": "baseline",
      "note": "Clear but emotionally flat"
    },
    {
      "id": "P1",
      "label": "Grouped by urgency",
      "state": "parked",
      "note": "Improved scannability but still low tension"
    },
    {
      "id": "P2",
      "label": "Conflicting requests",
      "state": "promising",
      "note": "Created meaningful tradeoffs"
    },
    {
      "id": "P3",
      "label": "Fully diegetic world requests",
      "state": "branch-trigger",
      "note": "Revealed a stronger direction outside the board itself"
    },
    {
      "id": "P4",
      "label": "Timer-heavy scarcity version",
      "state": "dead-end",
      "note": "Created pressure without meaningful choice"
    }
  ],
  "edges": [
    {
      "from": "P0",
      "to": "P1",
      "label": "Increase readability"
    },
    {
      "from": "P0",
      "to": "P2",
      "label": "Add tradeoffs"
    },
    {
      "from": "P0",
      "to": "P3",
      "label": "What if the board disappears?"
    },
    {
      "from": "P2",
      "to": "P4",
      "label": "Push urgency harder"
    }
  ]
}


path: references/state-labels.md
content: # State Labels

Recommended node and branch states:

- `baseline` - current stable point of reference
- `promising` - worth pursuing soon
- `branch-trigger` - reveals a new path or question
- `dead-end` - failed or not worth continuing
- `parked` - not chosen now, but worth remembering
- `production-candidate` - sufficiently understood to move toward implementation

## Usage guidance

### baseline
Use for the current comparison anchor.

### promising
Use for a path that deserves serious follow-up.

### branch-trigger
Use when a prototype mainly matters because it opened another direction.

### dead-end
Use when the path taught something useful but should not continue.

### parked
Use for good ideas that are deferred, blocked, or out of scope right now.

### production-candidate
Use when a path is understood well enough to stop exploratory prototyping and move into a more concrete implementation phase.


path: scripts/branch_map_svg.py
content: from __future__ import annotations
import json
import sys
from pathlib import Path
from collections import defaultdict, deque

STATE_COLORS = {
    'baseline': '#5B8DEF',
    'promising': '#2BAE66',
    'branch-trigger': '#F2A93B',
    'dead-end': '#D64545',
    'parked': '#8B7CF6',
    'production-candidate': '#1F8EFA',
}

BOX_W = 220
BOX_H = 64
X_GAP = 70
Y_GAP = 90
MARGIN = 40


def esc(text: str) -> str:
    return (text.replace('&', '&amp;')
                .replace('<', '&lt;')
                .replace('>', '&gt;'))


def wrap(text: str, width: int = 26):
    words = text.split()
    lines = []
    cur = []
    cur_len = 0
    for w in words:
        if cur_len + len(w) + (1 if cur else 0) <= width:
            cur.append(w)
            cur_len += len(w) + (1 if cur_len else 0)
        else:
            lines.append(' '.join(cur))
            cur = [w]
            cur_len = len(w)
    if cur:
        lines.append(' '.join(cur))
    return lines[:3]


def main(inp: str, out: str):
    data = json.loads(Path(inp).read_text(encoding='utf-8'))
    nodes = {n['id']: n for n in data.get('nodes', [])}
    edges = data.get('edges', [])
    children = defaultdict(list)
    indeg = defaultdict(int)
    for e in edges:
        children[e['from']].append(e)
        indeg[e['to']] += 1
        indeg[e['from']] += 0

    roots = [nid for nid in nodes if indeg[nid] == 0] or list(nodes.keys())[:1]

    level = {}
    q = deque((r, 0) for r in roots)
    seen = set()
    while q:
        nid, d = q.popleft()
        if nid in seen:
            continue
        seen.add(nid)
        level[nid] = d
        for e in children.get(nid, []):
            q.append((e['to'], d + 1))

    cols = defaultdict(list)
    for nid, d in level.items():
        cols[d].append(nid)
    for d in cols:
        cols[d].sort()

    positions = {}
    max_col = max(cols) if cols else 0
    max_rows = max((len(v) for v in cols.values()), default=1)
    width = MARGIN * 2 + (max_col + 1) * BOX_W + max_col * X_GAP
    height = MARGIN * 2 + max_rows * BOX_H + (max_rows - 1) * Y_GAP + 80

    for d in sorted(cols):
        for r, nid in enumerate(cols[d]):
            x = MARGIN + d * (BOX_W + X_GAP)
            y = MARGIN + r * (BOX_H + Y_GAP) + 40
            positions[nid] = (x, y)

    parts = []
    parts.append(f'<svg xmlns="http://www.w3.org/2000/svg" width="{width}" height="{height}" viewBox="0 0 {width} {height}">')
    parts.append('<style>text{font-family:Segoe UI,Arial,sans-serif}.title{font-size:20px;font-weight:700}.label{font-size:14px;font-weight:600}.note{font-size:11px;fill:#333}.edge{font-size:11px;fill:#555}</style>')
    title = esc(data.get('title', 'Prototype Branch Map'))
    parts.append(f'<text x="{MARGIN}" y="28" class="title">{title}</text>')

    for e in edges:
        if e['from'] not in positions or e['to'] not in positions:
            continue
        x1, y1 = positions[e['from']]
        x2, y2 = positions[e['to']]
        sx = x1 + BOX_W
        sy = y1 + BOX_H / 2
        tx = x2
        ty = y2 + BOX_H / 2
        mx = (sx + tx) / 2
        parts.append(f'<path d="M {sx} {sy} C {mx} {sy}, {mx} {ty}, {tx} {ty}" fill="none" stroke="#999" stroke-width="2"/>')
        label = esc(e.get('label', ''))
        if label:
            parts.append(f'<text x="{mx}" y="{(sy + ty) / 2 - 6}" class="edge" text-anchor="middle">{label}</text>')

    for nid, node in nodes.items():
        if nid not in positions:
            continue
        x, y = positions[nid]
        color = STATE_COLORS.get(node.get('state', ''), '#888')
        parts.append(f'<rect x="{x}" y="{y}" width="{BOX_W}" height="{BOX_H}" rx="10" ry="10" fill="#fff" stroke="{color}" stroke-width="3"/>')
        parts.append(f'<text x="{x + 10}" y="{y + 18}" class="note">{esc(nid)} • {esc(node.get("state", ""))}</text>')
        for i, line in enumerate(wrap(node.get('label', ''), 24)):
            parts.append(f'<text x="{x + 10}" y="{y + 38 + i * 16}" class="label">{esc(line)}</text>')

    parts.append('</svg>')
    Path(out).write_text('\n'.join(parts), encoding='utf-8')


if __name__ == '__main__':
    if len(sys.argv) != 3:
        print('Usage: python branch_map_svg.py <input.json> <output.svg>')
        sys.exit(1)
    main(sys.argv[1], sys.argv[2])


Security scan verdicts — ClawScan: clean | static analysis: clean | VirusTotal: clean | SkillSpector: clean (LOW)