Skill: stanestane/game-design-one-page-design-doc (version 1.1.0)

--- name: game-design-one-page-design-doc description: Create a concise one-page game design document and export it as both markdown and PDF. Use when a user wants a one-page design doc, one-pager, pitchable design summary, or compact game concept sheet that captures identity, pillars, summary, features, interface, aesthetic direction, sound, audience, platform, and milestone plan on a single page. --- # Game Design One-Page Design Doc Create a one-page game design document that is readable, decision-useful, and visually polished — exportable as a PDF that looks like a real product. Use this skill when the user wants a compact game design summary rather than a bloated GDD. The output is designed to be passed around in meetings, dropped in Slack, or emailed to publishers without embarrassment. Read `references/family-conventions.md` when you want the shared style and prioritization rules for this game-design skill family. Read `references/source-notes.md` when you want the distilled takeaways from the reference template and the GDC talk overview. ## Core principle A one-page design doc is not a small bad GDD. It is a compressed communication tool. It should: - state what the game is - state why it is interesting - state what feeling or fantasy it is chasing - state the core features and interaction model - state enough production framing to make it actionable It should not: - try to explain every subsystem - drown the reader in lore - use vague slogans instead of specifics - hide the real game behind marketing fluff ## What to produce Produce: 1. **One-page PDF** — the shareable, visually designed version 2. **One-page markdown doc** — the editable text version 3. **Structured source JSON** — the intermediate data used for rendering ## Visual design The PDF renderer (`scripts/render_one_page_gdd.py`) produces a professional dark-theme layout: - **Dark background** — near-black navy `#0F111A`, with a lighter panel for the header - **Teal accent colour** — `#00C8A0` for rules, section headings, pillar tag nubs, and bullet markers - **Pillar tags** — design pillars rendered as pill-shaped labels with a teal left accent in the header - **Two-column body** — left column for mechanics-heavy content, right column for production/aesthetic content - **Section rules** — a teal horizontal rule above each section label - **Poppins typeface** — used when available (falls back to Liberation Sans or DejaVu) - **Branded footer** — document label on the left, game title on the right The renderer uses **ReportLab** (vector PDF — real text, not a raster image), making the output searchable, copy-pasteable, and print-sharp. ### Font requirements The renderer searches for fonts in standard system locations automatically: - Linux: [REDACTED_SECRET], [REDACTED_SECRET] - macOS: `~/Library/Fonts/`, `/Library/Fonts/` - Windows: `C:/Windows/Fonts/` Preferred: Poppins (Bold, Medium, Regular, Light) from fonts.google.com Fallback: Liberation Sans (included in most Linux systems) ### Dependencies ```bash pip install reportlab ``` ## Default section structure Left column: - Genre / Story / Mechanics Summary - Key Features - Interface & Controls Right column: - Art Style - Music & Sound - Roadmap / Launch Criteria (includes platform, audience, milestones, launch date) Header band: - Game title - Identity / Mantra (subtitle) - Design Pillars (as pill tags) ## Process ### 1. Clarify the design input Collect or infer the minimum needed: - game title or working title - game identity / mantra - 2 to 3 design pillars - concise gameplay/story/mechanics summary - 3 to 6 key features - input / interface model - art references or aesthetic direction - sound direction or emotional audio goals - platform - target audience - milestone plan or launch criteria If the user gives messy notes, compress them rather than asking endless questions. ### 2. Write for one-page density Keep each section tight. Guidelines: - prefer short bullets over dense paragraphs - keep pillars punchy (2–4 words each) - keep the summary to a compact paragraph - keep features to the strongest few, not every idea - make roadmap milestones concrete enough to be useful - write like the doc will be skimmed in under two minutes ### 3. Preserve design clarity over style fluff If the concept is fuzzy: - choose clarity over hype - expose contradictions instead of smoothing them over - keep claims grounded in actual mechanics or fantasy ### 4. Build the structured source JSON Write a JSON file matching the renderer schema. Expected fields: - `title` - `identity_mantra` - `design_pillars` (array, 2–4 items) - `summary` - `features` (array, 3–6 items) - `interface` - `art_style` - `music_sound` - `platform` - `audience` - `milestones` (array) - `launch_day` See `references/example-input.json` for a worked example. ### 5. Render markdown and PDF ```bash # Install dependency first if needed: pip install reportlab # Render: python scripts/render_one_page_gdd.py --input <slug>.json --md <slug>.md --pdf <slug>.pdf ``` The renderer auto-discovers fonts. If content is too long, shorten the copy before rerendering — do not try to fit more by cramming. ### 6. Check fit and trim aggressively Before finalizing, verify: - the concept is legible at a glance - the features list is not overstuffed - the PDF still feels like a one-pager (not wall-to-wall text) - the roadmap is realistic enough to be useful - the strongest idea is visible immediately If the PDF looks crowded, shorten the writing. If there is too much whitespace, the document is probably well-scoped — do not pad it. ## Output expectations By default, create files in the current working directory or a user-specified target folder: - `<slug>.json` - `<slug>.md` - `<slug>.pdf` Use a slug based on the game title when naming files. ## Response structure When using this skill, report: - where the JSON, markdown, and PDF files were written - any sections that had to be compressed heavily - any major ambiguities or contradictions you preserved or surfaced ## References Read these when useful: - `references/source-notes.md` for the distilled source principles and section model - `references/example-input.json` for renderer input shape and field conventions ## Working principle If a team cannot explain the game on one page, the design is probably not clear enough yet. Use this skill to produce a one-pager people can actually read, discuss, and pass around.

path: references/example-input.json
content: {
  "title": "Sky Bastion",
  "identity_mantra": "A tactical co-op defense game about holding a floating fortress together while fending off impossible weather and worse enemies.",
  "design_pillars": [
    "desperate cooperation",
    "visible escalation",
    "heroic last stands"
  ],
  "summary": "Players defend and maintain a flying fortress while surviving enemy raids and environmental disasters. Short combat-defense phases alternate with urgent repair, positioning, and upgrade decisions that determine whether the bastion survives the next assault.",
  "features": [
    "real-time co-op fortress defense",
    "repair and reroute damaged ship systems",
    "crew roles with complementary abilities",
    "storm events that reshape each encounter",
    "persistent upgrades between missions"
  ],
  "interface": "Keyboard/mouse on PC. Players move, aim, repair, activate devices, and issue quick pings. Gamepad support optional after core PC controls are stable.",
  "art_style": "Stylized painted skies, chunky readable silhouettes, warm ship interiors contrasted against violent storm fronts. Reference feel: Airships, Pyre, and animated adventure concept art.",
  "music_sound": "Orchestral tension with mechanical creaks, storm pressure, and triumphant swells during survival peaks. Audio should support urgency, teamwork, and relief after successful defense.",
  "platform": "PC first (Steam)",
  "audience": "Players who enjoy co-op survival pressure, team roles, and cinematic defense fantasies.",
  "milestones": [
    "Core movement, combat, and repair loop playable",
    "One full mission with escalating storm phases",
    "Progression and upgrade layer functional",
    "Polish pass on readability, VFX, and onboarding"
  ],
  "launch_day": "TBD"
}


path: references/family-conventions.md
content: # Family Conventions

Use this file to keep this game-design skill family tonally and structurally aligned.

## Core style

- Be direct, concrete, and design-facing.
- Prefer clear diagnosis over academic display.
- Name the exact moment, system, or interaction where the issue appears.
- Avoid generic advice like "improve balance" or "add polish."
- When a tradeoff exists, state the tradeoff plainly.
- Favor actionable language over abstract theory recap.

## Analysis rules

- Audit player experience, not designer intention.
- Separate what is mechanically true from what players are likely to perceive.
- Distinguish local issues from structural issues.
- Call out contradictions instead of smoothing them over.
- Prefer 2 to 5 strong findings over a long bag of weak observations.
- If input is incomplete, infer cautiously and state assumptions.

## Recommendation rules

- Prioritize the highest-leverage fix first.
- State why the issue happens, not just what to change.
- When possible, provide one "minimal fix" and a broader strategic direction.
- If the right answer is removal, simplification, or reduction, say so.
- Separate must-fix issues from optional improvements when useful.

## Response conventions

Default sections should stay close to this order when applicable:
1. Target or framing
2. Diagnosis or profile
3. Key risks or contradictions
4. Recommendations
5. Minimal fix

## Voice guardrails

- Sound like a sharp design partner, not a textbook.
- Do not pad with praise or softening filler.
- Do not confuse length with rigor.
- If the design is muddled, say it is muddled.
- If the design has a strong core, say that too.

path: references/source-notes.md
content: # Source Notes

Distilled from:
- Game Dev Underground one-page game design document template
- GDC Vault overview for **One-Page Designs**

## Core takeaways

### 1. One page is a communication constraint
The point is not novelty. The point is forcing clarity and brevity.

### 2. People skim
The GDC overview makes the blunt point: if people often only read the first page, then make the first page do the real work.

### 3. A one-pager should carry the idea, not the whole production bible
It should communicate:
- what the game is
- what makes it distinct
- what kind of experience it promises
- enough implementation direction to align the team

### 4. Use explicit sections
The template suggests this compact structure:
- Game Identity / Mantra
- Design Pillars
- Genre / Story / Mechanics Summary
- Features
- Interface
- Art Style
- Music / Sound
- Development Roadmap / Launch Criteria

### 5. Good one-pagers trade completeness for readability
Omit detail that does not help the reader grasp the design quickly.

### 6. Production framing still matters
A one-page design doc is not just creative fluff. Platform, audience, milestones, and launch criteria help make it actionable.

## Practical guidance for this skill

- Make the mantra visible early.
- Keep pillars few and sharp.
- Use a short summary paragraph.
- Keep features selective.
- Treat art and sound as directional references, not exhaustive style bibles.
- Keep roadmap milestones coarse and readable.
- If the page is crowded, rewrite shorter; do not shrink forever.


path: scripts/render_one_page_gdd.py
content: #!/usr/bin/env python3
"""
render_one_page_gdd.py
Renders a one-page game design document to Markdown and a visually designed PDF.

Usage:
    python render_one_page_gdd.py --input game.json --md game.md --pdf game.pdf

Requires: reportlab  (pip install reportlab)
"""

import argparse
import json
import os
import re
import sys
from pathlib import Path

# ── Colour palette ─────────────────────────────────────────────────────────
DARK_BG    = (15,  17,  26)
PANEL_BG   = (24,  27,  40)
ACCENT     = (0,  200, 160)
ACCENT_DIM = (0,  130, 105)
HEADING_FG = (0,  200, 160)
TITLE_FG   = (255, 255, 255)
BODY_FG    = (210, 215, 230)
TAG_BG     = (38,  46,  66)
DIVIDER    = (48,  56,  78)
FOOTER_FG  = (120, 130, 155)

# ── Font discovery ─────────────────────────────────────────────────────────
FONT_SEARCH_DIRS = [
    "/usr/share/fonts/truetype/google-fonts",
    "/usr/share/fonts/truetype/liberation",
    "/usr/share/fonts/truetype/dejavu",
    "C:/Windows/Fonts",
    os.path.expanduser("~/Library/Fonts"),
    "/Library/Fonts",
    "/System/Library/Fonts",
]
FONT_CANDIDATES = {
    "regular": ["Poppins-Regular.ttf", "LiberationSans-Regular.ttf", "DejaVuSans.ttf", "arial.ttf"],
    "bold":    ["Poppins-Bold.ttf",    "LiberationSans-Bold.ttf",    "DejaVuSans-Bold.ttf", "arialbd.ttf"],
    "light":   ["Poppins-Light.ttf",   "Poppins-Regular.ttf",        "LiberationSans-Regular.ttf", "arial.ttf"],
    "medium":  ["Poppins-Medium.ttf",  "Poppins-Bold.ttf",           "LiberationSans-Bold.ttf", "arialbd.ttf"],
}

def find_font(variant):
    for candidate in FONT_CANDIDATES[variant]:
        for d in FONT_SEARCH_DIRS:
            p = os.path.join(d, candidate)
            if os.path.exists(p):
                return p
    raise FileNotFoundError(
        f"No font found for variant '{variant}'. "
        "Install Poppins from fonts.google.com or Liberation Sans."
    )

def slugify(text):
    text = text.lower().strip()
    text = re.sub(r"[^a-z0-9]+", "-", text)
    return re.sub(r"-+", "-", text).strip("-") or "one-page-gdd"

def rgb01(t):
    return tuple(c / 255.0 for c in t)

def load_json(path):
    with path.open("r", encoding="utf-8") as f:
        data = json.load(f)
    for k, v in {
        "title": "Untitled Game", "identity_mantra": "",
        "design_pillars": [], "summary": "", "features": [],
        "interface": "", "art_style": "", "music_sound": "",
        "platform": "", "audience": "", "milestones": [], "launch_day": "TBD",
    }.items():
        data.setdefault(k, v)
    return data

# ── Markdown ──────────────────────────────────────────────────────────────
def make_markdown(data):
    lines = [f"# {data['title']}", ""]
    lines += ["## Game Identity / Mantra", data["identity_mantra"], ""]
    lines += ["## Design Pillars"]
    for p in data["design_pillars"]:
        lines.append(f"- {p}")
    lines.append("")
    lines += ["## Genre / Story / Mechanics Summary", data["summary"], ""]
    lines += ["## Features"]
    for f in data["features"]:
        lines.append(f"- {f}")
    lines.append("")
    lines += ["## Interface", data["interface"], ""]
    lines += ["## Art Style", data["art_style"], ""]
    lines += ["## Music / Sound", data["music_sound"], ""]
    lines += ["## Development Roadmap / Launch Criteria"]
    lines.append(f"- Platform: {data['platform']}")
    lines.append(f"- Audience: {data['audience']}")
    for i, m in enumerate(data["milestones"], start=1):
        lines.append(f"- Milestone {i}: {m}")
    lines.append(f"- Launch Day: {data['launch_day']}")
    lines.append("")
    return "\n".join(lines)

# ── PDF ───────────────────────────────────────────────────────────────────
def render_pdf(data, pdf_path):
    try:
        from reportlab.pdfgen import canvas as rl_canvas
        from reportlab.lib.pagesizes import A4
        from reportlab.lib.units import mm
        from reportlab.pdfbase import pdfmetrics
        from reportlab.pdfbase.ttfonts import TTFont
    except ImportError:
        print("ERROR: reportlab required. Run: pip install reportlab", file=sys.stderr)
        sys.exit(1)

    pdfmetrics.registerFont(TTFont("GDD-Regular", find_font("regular")))
    pdfmetrics.registerFont(TTFont("GDD-Bold",    find_font("bold")))
    pdfmetrics.registerFont(TTFont("GDD-Light",   find_font("light")))
    pdfmetrics.registerFont(TTFont("GDD-Medium",  find_font("medium")))

    PAGE_W, PAGE_H = A4
    MX  = 22 * mm
    MB  = 16 * mm
    G   = 7 * mm
    HH  = 50 * mm

    c = rl_canvas.Canvas(str(pdf_path), pagesize=A4)

    # Background
    c.setFillColorRGB(*rgb01(DARK_BG))
    c.rect(0, 0, PAGE_W, PAGE_H, fill=1, stroke=0)

    # Header band
    c.setFillColorRGB(*rgb01(PANEL_BG))
    c.rect(0, PAGE_H - HH, PAGE_W, HH, fill=1, stroke=0)

    # Top accent stripe
    c.setFillColorRGB(*rgb01(ACCENT))
    c.rect(0, PAGE_H - 3 * mm, PAGE_W, 3 * mm, fill=1, stroke=0)

    # Left accent block in header
    c.setFillColorRGB(*rgb01(ACCENT))
    c.rect(0, PAGE_H - HH, 5 * mm, HH - 3 * mm, fill=1, stroke=0)

    # Title
    title_y = PAGE_H - HH + 24 * mm
    c.setFillColorRGB(*rgb01(TITLE_FG))
    c.setFont("GDD-Bold", 28)
    c.drawString(MX + 2 * mm, title_y, data["title"])

    # Mantra
    mantra = str(data.get("identity_mantra", "")).strip()
    if mantra:
        max_w = PAGE_W - 2 * MX
        c.setFont("GDD-Light", 9)
        while c.stringWidth(mantra, "GDD-Light", 9) > max_w and " " in mantra:
            mantra = mantra[:mantra.rfind(" ")] + "…"
        c.setFillColorRGB(*rgb01(BODY_FG))
        c.drawString(MX + 2 * mm, title_y - 9 * mm, mantra)

    # Pillar tags
    pillars = data.get("design_pillars", [])
    if pillars:
        tag_x = MX + 2 * mm
        tag_y = title_y - 20 * mm
        TAG_H = 5.5 * mm
        TAG_PX = 4.5 * mm
        TAG_R = 2.5 * mm
        c.setFont("GDD-Medium", 7)
        for p in pillars[:6]:
            label = str(p).upper()
            tw = c.stringWidth(label, "GDD-Medium", 7) + 2 * TAG_PX
            if tag_x + tw > PAGE_W - MX:
                break
            c.setFillColorRGB(*rgb01(TAG_BG))
            c.roundRect(tag_x, tag_y, tw, TAG_H, TAG_R, fill=1, stroke=0)
            c.setFillColorRGB(*rgb01(ACCENT))
            c.rect(tag_x, tag_y, 2 * mm, TAG_H, fill=1, stroke=0)
            c.roundRect(tag_x, tag_y, 2 * TAG_R, TAG_H, TAG_R, fill=1, stroke=0)
            c.setFillColorRGB(*rgb01(BODY_FG))
            c.drawString(tag_x + TAG_PX, tag_y + 1.7 * mm, label)
            tag_x += tw + 3 * mm

    # Body layout
    body_top = PAGE_H - HH - 4 * mm
    body_bot = MB + 8 * mm
    col_w    = (PAGE_W - 2 * MX - G) / 2
    left_x   = MX
    right_x  = MX + col_w + G

    # Column divider
    div_x = MX + col_w + G / 2
    c.setStrokeColorRGB(*rgb01(DIVIDER))
    c.setLineWidth(0.5)
    c.line(div_x, body_bot, div_x, body_top - 2 * mm)

    SL  = 7
    BS  = 8.5
    LHB = 4.5 * mm
    LHU = 4.3 * mm
    SG  = 6 * mm
    LG  = 2.8 * mm

    def _wrap(text, fname, fsize, max_w):
        words = str(text).split()
        if not words:
            return []
        result, cur = [], words[0]
        for w in words[1:]:
            trial = cur + " " + w
            if c.stringWidth(trial, fname, fsize) <= max_w:
                cur = trial
            else:
                result.append(cur)
                cur = w
        result.append(cur)
        return result

    def draw_section(heading, value, x, y, width):
        y -= SG
        c.setFillColorRGB(*rgb01(ACCENT))
        c.rect(x, y, width, 0.7 * mm, fill=1, stroke=0)
        y -= 4 * mm
        c.setFont("GDD-Medium", SL)
        c.setFillColorRGB(*rgb01(HEADING_FG))
        c.drawString(x, y, heading.upper())
        y -= LG
        c.setFillColorRGB(*rgb01(BODY_FG))
        if isinstance(value, list):
            for item in value:
                s = str(item).strip()
                if not s:
                    continue
                lines = _wrap(s, "GDD-Regular", BS, width - 5.5 * mm)
                first = True
                for line in lines:
                    y -= LHU
                    if first:
                        c.setFont("GDD-Medium", BS)
                        c.setFillColorRGB(*rgb01(ACCENT_DIM))
                        c.drawString(x, y, "›")
                        c.setFont("GDD-Regular", BS)
                        c.setFillColorRGB(*rgb01(BODY_FG))
                        c.drawString(x + 5 * mm, y, line)
                        first = False
                    else:
                        c.setFont("GDD-Regular", BS)
                        c.drawString(x + 5 * mm, y, line)
        else:
            text = str(value).strip()
            if text:
                lines = _wrap(text, "GDD-Regular", BS, width)
                c.setFont("GDD-Regular", BS)
                for line in lines:
                    y -= LHB
                    c.drawString(x, y, line)
        return y

    # Left column
    y_l = body_top
    y_l = draw_section("Genre / Story / Mechanics", data.get("summary", ""),   left_x, y_l, col_w)
    y_l = draw_section("Key Features",              data.get("features", []),  left_x, y_l, col_w)
    y_l = draw_section("Interface & Controls",      data.get("interface", ""), left_x, y_l, col_w)

    # Right column
    y_r = body_top
    y_r = draw_section("Art Style",     data.get("art_style",   ""), right_x, y_r, col_w)
    y_r = draw_section("Music & Sound", data.get("music_sound", ""), right_x, y_r, col_w)

    road = []
    if data.get("platform"):  road.append(f"Platform: {data['platform']}")
    if data.get("audience"):  road.append(f"Audience: {data['audience']}")
    for i, m in enumerate(data.get("milestones", []), start=1):
        road.append(f"M{i}: {m}")
    road.append(f"Launch: {data.get('launch_day', 'TBD')}")
    y_r = draw_section("Roadmap / Launch Criteria", road, right_x, y_r, col_w)

    # Footer
    c.setFillColorRGB(*rgb01(PANEL_BG))
    c.rect(0, 0, PAGE_W, MB + 4 * mm, fill=1, stroke=0)
    c.setFillColorRGB(*rgb01(ACCENT_DIM))
    c.rect(0, MB + 3.5 * mm, PAGE_W, 0.4 * mm, fill=1, stroke=0)
    c.setFont("GDD-Light", 6.5)
    c.setFillColorRGB(*rgb01(HEADING_FG))
    c.drawString(MX, MB, "ONE-PAGE GAME DESIGN DOCUMENT")
    c.setFillColorRGB(*rgb01(FOOTER_FG))
    c.drawRightString(PAGE_W - MX, MB, data["title"].upper())

    c.save()

# ── CLI ───────────────────────────────────────────────────────────────────
def main():
    parser = argparse.ArgumentParser(description="Render one-page GDD to Markdown and PDF")
    parser.add_argument("--input", required=True)
    parser.add_argument("--md")
    parser.add_argument("--pdf")
    args = parser.parse_args()

    data = load_json(Path(args.input))

    if args.md:
        Path(args.md).write_text(make_markdown(data), encoding="utf-8")
        print(f"Markdown → {args.md}")

    if args.pdf:
        render_pdf(data, Path(args.pdf))
        print(f"PDF → {args.pdf}")

    print(json.dumps({
        "input": args.input, "markdown": args.md, "pdf": args.pdf,
        "slug": slugify(data.get("title", "one-page-gdd")),
    }, ensure_ascii=False))

if __name__ == "__main__":
    main()


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