unikit-gd-verify · v1.0 · 2026-06-26 · sha256 b4387ab5c6e8d92d

unikit-gd-verify v1.0A

Immutable. This exact content is served forever at /api/v1/blob/b4387ab5c6e8d92d.

---
name: unikit-gd-verify
description: >-
  Mechanical consistency check for game design — answers "is the design consistent with
  itself?" — plus changed-scope impact analysis across all three GDD axes: systems, flows,
  and content types. Offline and deterministic: greps the facts registry against the
  documents (numbers, terms, IDs, duplicate & dangling references, roster↔disk, map
  freshness, Depends 3-way, status/version, AC presence, placeholder leaks, flow & content
  schema), and from a git diff computes which dependent systems, flows, and content a change
  affects. Scope is inferred: a named document checks that document; an unverified diff
  triggers a changed-scope pass; otherwise everything. Use when the user wants a consistency
  or impact check, e.g. "verify the design", "what did this change affect", "find broken
  references in the design". Conflicts triage into four tracks; the entailed fixes hand off
  to /unikit-gd-apply in one pass. This is the mechanical pass — for a subjective "is this
  design good" quality critique use /unikit-gd-review.
argument-hint: "[system name | SYS-slug | question]  (scope inferred; no flags)"
allowed-tools:
  - Read
  - Glob
  - Grep
  - Write
  - Edit
  - Bash(ls *)
  - Bash(find *)
  - Bash(wc *)
  - Bash(date *)
  - Bash(mkdir *)
  - Bash(git diff *)
  - Bash(git status *)
  - AskUserQuestion
disable-model-invocation: false
user-invocable: true
metadata:
  author: unikit
  version: "1.0"
  category: game-design
---

# Game Design — Consistency & Impact Verification

Answer **"is the design consistent with itself?"** — a mechanical, deterministic
check of every document against the facts registry, plus the **changed-scope
impact** of a recent edit. This is the design-side mirror of `unikit-verify` and a
CI-linter to `unikit-gd-review`'s senior reviewer: cheap, binary, run after every
edit. A **verify conflict cannot be declined** (unlike a review finding) — it is
resolved.

Conflicts are **precise, but their fix is not always predetermined**, so each is sorted
into one of **four tracks** — **self-heal** (verify fixes it), **entailed → apply-ready**
(a named fix handed to `unikit-gd-apply`), **direction interview** (the user picks *which*
fix, never *whether*), and **authoring → owner / explore**. The apply-ready set hands off
**in one move** — inline prose (verify writes **no** handoff file, unlike
`unikit-gd-review`'s durable report), recommend-only. The handoff + interview run **only on
a standalone `/unikit-gd-verify`**; when verify is `unikit-gd-apply`'s closing Phase 3 (the
`apply-phase3` sentinel) they are suppressed, so `apply → verify → apply` never loops. The
triage engine is shared with `unikit-gd-review` (`gd-critique` → Handoff Engine).

Verification is **offline and reproducible**: no web research, no expert judgment,
no LLM guessing where a grep will do. The same inputs always yield the same result
(idempotent).

## Language Awareness — BLOCKING PRE-REQUISITE

**BEFORE producing ANY output**, silently read `.unikit/system/LANGUAGE_RULES.md`
and apply it to all output and the report (fall back to English if it is missing) —
including the rule to **translate concepts, not transliterate jargon**.
`gd-principles` → "Language" adds the game-design specifics: which IDs and stored
field values stay English. Do not announce the language setting.

## Phase 0 — Bootstrap

Silently load — do not narrate:

1. **`.unikit/system/gamedesign/gd-principles.md`** (the core) — the **facts
   registry / ID conventions** (registry wins until the user resolves otherwise;
   never delete an ID — deprecate it) and the language rules. Plus, from the same
   `gamedesign/` folder, the shards this skill needs: **`gd-lifecycle.md`** (the
   status/version coherence spine), **`gd-flow-axis.md`** (the flow coherence
   contract — `mode:` ↔ structure, `GOAL → SYS`/`AC`, Win/Lose ↔ terminal GOAL),
   **`gd-content-axis.md`** (the content coherence contract — CU ⊆ CT, `ref<>`
   resolution, `scale` ↔ structure, `belongs_to` 3-way, content-map freshness,
   RES/TRACK/KNOB, cross-axis SYS→CT staleness), and **`gd-critique.md`** (the
   **severity rubric** — a conflict is Critical/Major evidence). This skill
   **applies** them. If missing, warn (`unikit-ai update`) and continue with the
   conventions summarized here.
2. **`.unikit/gamedesign/GD-IDS.yaml`** — the single source of truth: pillars,
   systems, flows, entities, formulas, terms. Current values only.
   **Schema guard (clean break — no automatic migration):** it MUST be `version: 2`.
   On a pre-v2 `version: 1` registry, **STOP** and report that the design workspace
   is on the pre-v2 layout (the standalone markdown system-index was dropped; the
   roster now renders into `GAME.md` `## System Map [gen]`) — there is no automatic
   migration; tell the user to upgrade via `/unikit-gd-spec` before verifying.
3. **`.unikit/gamedesign/GAME.md`** — incl. its generated `## System Map [gen]`,
   `## Flow Map [gen]`, and `## Funnel [gen]` renders (the human-readable maps; the
   truth is `GD-IDS`), and its `## Win / Lose Conditions` (for the Win/Lose ↔ terminal
   GOAL check).
4. **`.unikit/gamedesign/systems/*.md`** and **`.unikit/gamedesign/flows/*.md`** — the
   system and flow documents checked against the registry.

**One-way boundary:** never read `.unikit/code/`, project source, or build
artifacts. **Web research is forbidden here** (`gd-principles`) — verification must
stay deterministic. The only `git` use is reading the design-workspace diff.

## Phase 1 — Resolve Scope (no flags)

**Loop-guard sentinel (first).** If the argument is exactly **`apply-phase3`** — the
reserved loop-guard sentinel (`gd-critique` → Handoff Engine), passed by
`unikit-gd-apply` as its closing Phase 3 — this run is the **in-apply gate**, **not** a
scope. Treat it as a bare invocation for scope purposes (derive changed-scope from the
`git diff` exactly as below), but set the **in-apply flag**: the standalone handoff offer
and the direction interview (Phase 4 Track 3 / Phase 6) are **suppressed** this run, so
`apply → verify → apply` cannot loop. A standalone `/unikit-gd-verify` (no sentinel)
leaves the flag off — the handoff is active.

Scope is a function of context:

1. The argument names a system, flow **or content type** → check **that document** and
   its registry facts (a `FLOW-<slug>` runs the flow checks, a `CT-<slug>` the content
   checks below).
2. There is an **unverified diff** under `.unikit/gamedesign/` (`git diff` /
   `git status` shows changed design docs), or the prompt asks "what did this
   change affect" → **changed-scope** pass (Phase 3).
3. Otherwise → **full** check of every system, **every flow**, **every content type**,
   and the whole registry.

Announce the resolved scope in one line.

## Phase 2 — Mechanical Checks (grep-first)

Run every check deterministically; each mismatch is a **CONFLICT** with a citation:

| Check | Method | Conflict when |
|-------|--------|---------------|
| **Facts** | grep each `GD-IDS` entity/formula value across the docs — match the value on word boundaries, not as a bare substring | a document states a number/name that disagrees with the registry |
| **Terminology drift** | grep each term's `forbidden_aliases` **values** (the listed aliases, not the field name) across the docs | a forbidden alias is used in place of the canonical term |
| **ID validity** | scan every `SYS-`/`ENT-`/`FORM-`/`AC-`/`PIL-`/`FLOW-`/`GOAL-` id for shape (analytics `events` are `snake_case` tokens, not prefixed ids) | an id is malformed — wrong case, bad separator, or an unknown prefix |
| **Duplicate IDs** | group every declared id by value | the same id is declared for two different things |
| **Dangling references** | resolve every referenced id against `GD-IDS` | a referenced id does not exist, or a live (non-deprecated) section points at a `deprecated` entry |
| **Unregistered cross-doc fact** | grep `FORM-`/`ENT-` ids that surface in **two or more** documents | a fact crosses a document boundary yet has no `GD-IDS` entry |
| **Roster ↔ disk** | compare `GD-IDS` `systems` ids to the `systems/*.md` files on disk | a `skeleton`-or-later system has no file, or a `systems/*.md` file has no `GD-IDS` entry (→ route to `unikit-gd-spec` to register it — verify never writes the roster) |
| **Map freshness (3-surface)** | compare the `GAME.md` `## System Map [gen]` rows to `GD-IDS` `systems` — membership, `Status`, `Ver`, `Depends` | the render disagrees with `GD-IDS` — **not a conflict to resolve**: re-render the block (self-heal, announced); see *Map freshness* below |
| **Depends 3-way** | for each `A → B` edge, check it agrees across A's section F, A's `GD-IDS` `depends_on`, and A's `## System Map` Depends cell, and that B carries the reciprocal | the three sources disagree, or the reciprocal edge is missing or contradictory |
| **Status coherence** | a system's `doc_status` agrees across the **two places that must agree** — the header `> Status:` and `GD-IDS` `doc_status` (enum `not-started · skeleton · detailed · reviewed · revised`) | the two disagree for a system — system docs only; see the carve-outs below |
| **Version coherence** | the header `Version` and `GD-IDS` `version` agree; a `not-started` system carries **no** version, a `skeleton`-or-later system carries one | the two disagree, or a `skeleton`+ system is missing a version / a `not-started` system has one |
| **AC presence** | for a `detailed`-or-later system, section H is non-empty and every `AC-<sys>-N` id is unique | a `detailed`+ system has an empty H, or repeats an `AC-<sys>-N` — gaps in the numbering are **not** a conflict (numbering is stable after an AC is removed) |
| **Placeholder leak** | grep `[To be designed]` inside `detailed`-or-later documents (a `<!-- deferred -->` marker is **intentional** — never a leak) | a `detailed`+ document still carries a skeleton `[To be designed]` placeholder |
| **Core-floor coherence** | for a `detailed`/`reviewed`/`revised` document, check that **no core section carries `<!-- deferred -->`** — the core-set per zone (`gd-lifecycle`: system `A/B/C/D/H` · flow `A/B` · content `A/B/C`) | a `detailed`+ document defers a **core** section — the status was raised past the floor (the soft guard in `gd-authoring` was bypassed); route to the owning zone to author the core section or lower the status. A deferred **non-core** section is **not** a conflict (it is the inferred `detailed · partial` — see the render below) |

The registry is authoritative: when a document disagrees with `GD-IDS`, the
registry wins until the user resolves it the other way (`gd-principles`).

**Map freshness — self-heal, not a conflict.** The `## System Map [gen]` block in
`GAME.md` is a deterministic **render** of `GD-IDS` `systems` (`gd-lifecycle` →
Lifecycle & Status), so a disagreement is a *freshness* issue, never a coherence
conflict to resolve. On any of —

- a `GD-IDS` system **missing from the map** (no row);
- a **phantom row** in the map with no matching `GD-IDS` system;
- a row whose **Status / Ver / Depends** differs from `GD-IDS`,

— **re-render** the `## System Map [gen]` block from `GD-IDS` (the same deterministic
render `unikit-gd-spec` writes — group by category, sort by tier then design order,
display-precedence on `deprecated`/`implemented`) and **announce** it
(`re-rendered ## System Map [gen] (freshness)`). The one case verify does **not**
self-heal is a `systems/*.md` file with **no `GD-IDS` entry** (the Roster ↔ disk
check): the roster is `unikit-gd-spec`'s to write, so verify **routes** the user to
`/unikit-gd-spec` to register the system, then the map re-renders.

**Deferred markers, the `· partial (n/m)` render, and the self-check (all three maps).**
A `<!-- deferred -->` marker is an **intentional** omission (`gd-lifecycle` — a section
the author chose to skip at this depth), **never** a placeholder leak. Its only
mechanical consequence at verify time is the **render**: when a `detailed`+ document
carries ≥1 `<!-- deferred -->`, the freshness re-render appends **`· partial (n/m)`** to
that document's row Status in the relevant `## *Map [gen]`, where **n = the count of
deferred non-core sections** (those carrying the marker) and **m = the document's total
deferrable (non-core) sections** (`gd-lifecycle` → the canonical render format + per-zone
core-set). This applies identically to the **System, Flow, and Content** maps; like all
freshness it is a **self-heal re-render, not a coherence conflict**, and the status value
itself stays the read-only enum (`· partial` is a render annotation, not a `doc_status`,
and is **not** part of the two-place status/version coherence). **Self-check (marker ⟺
render):** every document with ≥1 `<!-- deferred -->` must render `· partial (n/m)` in its
map row, **and** every `· partial` suffix in a map must correspond to a document that
actually carries the marker — a mismatch in either direction is a **freshness**
discrepancy, fixed by the re-render. Annotate the announce line with the count, e.g.
`re-rendered ## System Map [gen] (freshness; partial 2/5 on SYS-combat)`.

**Scope & carve-outs (Status / Version / AC / Placeholder).** These four checks
read the **system** spine only — the header ↔ `GD-IDS` pair of a `systems/*.md`
document (the `## System Map [gen]` render is *not* a coherence surface — its
agreement is handled by *Map freshness* above):

- **Lifecycle-enum scope.** `GAME.md` (`drafted | approved`) and `CONCEPT.md`
  (`exploring | drafted | approved`) keep their **own** lifecycle enums — a
  `Status: approved` there is correct and is **never** a status conflict.
- **Display precedence.** A system with `status: deprecated` legitimately shows
  `deprecated` in the `## System Map [gen]` Status over its underlying `doc_status`;
  the read-only `implemented` value (a code-set display overlay) behaves the same.
  Both are display overlays in the **render** — never part of the two-place header ↔
  `GD-IDS` `doc_status` comparison. The companion **`GD-IDS` `implemented_version`**
  field (also code-set, by `unikit-verify` on all-AC-met) is a code-owned field —
  **not** a `doc_status` and **not** a `version` — so it never participates in Status
  or Version coherence and is never flagged as drift.
- **Non-id metadata (`research:`).** The `GD-IDS` `research:` field — a **path pointer**
  to the explore research that seeded an artifact (written by `unikit-gd-spec`
  add-system on a **system** row, by `unikit-gd-flow` on a **flow** `flows[]` row, and by
  `unikit-gd-content` on a **content type** `content_types[]` row) — is non-semantic
  metadata, **not** a registry id and **not** a `doc_status` / `version`. It is excluded
  from status/version coherence on all three axes, and the id-resolving checks pass it
  over **by construction**: **Dangling references** only resolves id tokens
  (`SYS-`/`ENT-`/`FORM-`/`AC-`/`PIL-`/`FLOW-`/`GOAL-`/`CT-`/`CU-`), and **Unregistered
  cross-doc fact** only greps `FORM-`/`ENT-` ids — a folder path matches neither — so no
  special-case logic is needed.
- **Dependent-lag.** A verify-flagged dependent may transiently carry a header
  `Status` behind its `GD-IDS` `doc_status` (`gd-lifecycle` → Lifecycle & Status);
  that lag is expected, not a conflict. This applies on the **flow** axis too: a
  cross-axis-flagged dependent **flow** (Phase 3) may carry a `FLOW.md` header `Status`
  behind its `GD-IDS` `flows[].doc_status`.

### Axis-aware checks — flow + content (load on demand)

Beyond the system checks above, the registry may carry **flows** and **content
types**, each with its own check family — the mirror of the system checks plus the
axis-specific ones. When `GD-IDS` `flows` and/or `content_types` is **non-empty**, load
the matching family from `{{skills_dir}}/{{self_name}}/references/axis-checks.md` and run
it; an empty `flows: []` / `content_types: []` (or no key) → **skip that family
silently** (not a conflict). The registry-wins rule and the self-heal-vs-conflict
distinction apply exactly as above.

| Registry has | Family (in `references/axis-checks.md`) — coverage |
|--------------|----------------------------------------------------|
| `flows` non-empty | the **flow** family: GOAL-id validity, dangling goal-targets, flow status/version 2-place, flow Depends 3-way, wiring-mode-vs-structure, win/lose-vs-terminal-goal, funnel continuity, flow/funnel map freshness (`Realized` cross-axis derived) |
| `content_types` non-empty | the **content** family: CT/CU-id validity, CU-fields-subset-CT, ref-target resolution, scale-vs-structure, belongs-to 3-way, content status/version 2-place, content map freshness, RES/TRACK/KNOB fact coherence |

The **cross-axis impact** of a system edit on dependent flows / content types is the
reverse-edge walk in **Phase 3** (it stays here — it is impact analysis, not a per-axis
check).

## Phase 3 — Changed-Scope Impact (when a diff is unverified)

Compute the blast radius of a recent edit:

1. **Changed set** = the union of two sources:
   - `git diff HEAD` (and `git status`) restricted to `.unikit/gamedesign/` → the
     systems whose documents changed on disk, **and**
   - every system already carrying `doc_status: revised` in `GD-IDS` — a pending
     edit not yet cleared back to `reviewed`, so a `revised` system is re-checked
     even when its file shows no fresh git diff.
2. Walk `GD-IDS` `depends_on` edges (rendered in the `## System Map` Depends column)
   to the **transitive closure** of systems that depend (directly or indirectly) on
   a changed one.
3. Classify each affected system into an **Affected** table:

   | System / Flow | Relation | Verdict |
   |---------------|----------|---------|
   | SYS-enemy-ai | depends on SYS-combat (changed) | **Needs Review** |
   | SYS-economy | soft dep, no touched interface | **Still Valid** |
   | SYS-loot | uses a removed AC | **Likely Stale** |
   | FLOW-first-session | exercises SYS-combat (changed) via GOAL→AC | **Needs Review** |

   Verdicts: **Still Valid** / **Needs Review** / **Likely Stale**.
4. **Record the impact** (with approval):
   - **Append the `Affected (gd-verify):` line** to the latest changelog block in the
     changed system's section **K** — the line the system's zone owner
     (`unikit-gd-system`) leaves for verify to fill (`gd-authoring` → Delta
     Discipline).
   - For each affected **dependent**, bump it to `doc_status: revised` **only when
     its verdict is `Needs Review` or `Likely Stale`** (a `Still Valid` dependent is
     left untouched). Write the bump in the dependent's `GD-IDS.yaml` `doc_status`
     (the single machine-truth place); the dependent's `SYSTEM.md` header is left to
     catch up on its next authoring touch (full header coherence for flagged
     dependents is a later tier — see gd-lifecycle → Lifecycle & Status), and the
     `## System Map [gen]` re-renders (Map freshness).
   - A `revised` dependent returns to `reviewed` only through `unikit-gd-review`,
     never here.
   - **Idempotent:** re-running on the same diff yields the same Affected table and
     re-bumps nothing already at `revised`.

**Cross-axis impact (system → flow, one-way).** A system edit can stale a **flow** that
exercises it (through `GOAL → SYS` / `GOAL → AC`) — `gd-flow-axis` → Flow Axis. Extend
the pass across the axis (skip when `flows: []`):

- **Changed set** also includes every flow already at `doc_status: revised` (a pending
  cross-axis flag not yet cleared back to `reviewed`).
- **Reverse-edge walk:** for each changed **system**, find every flow whose
  `flows[].depends_on` includes it, or whose `goals[].targets` reference it
  (`GOAL → SYS` / `GOAL → AC`). Those flows join the **Affected** table as **flow rows**
  (Relation: `exercises SYS-x (changed)`), with the same Still Valid / Needs Review /
  Likely Stale verdicts (a flow using a **removed** `AC` is Likely Stale). The reverse
  does **not** hold — editing a flow never stales a system.
- **Record impact (dependent flow):** list affected flows in the changed system's
  `Affected (gd-verify):` line (section K). For each `Needs Review` / `Likely Stale`
  dependent flow, bump it to `doc_status: revised` in the **`GD-IDS` `flows[].doc_status`
  only** (the dependent-lag rule, on the flow axis) — verify does **not** write the
  `FLOW.md` header `> Status:` (it catches up on the next `unikit-gd-flow` touch), and
  the `## Flow Map [gen]` re-renders (freshness). Append a one-line human-readable note
  to the flow's **section F** (Open Questions & Changelog) — the flow analogue of the
  `Affected (gd-verify):` line a system carries in section K — so the next author sees
  why the flow is `revised`. A `revised` flow returns to `reviewed` only through
  `unikit-gd-review`.

**Cross-axis impact (system → content type, one-way).** A system edit can stale a
**content type** that feeds it (through `belongs_to` / a `ref<SYS>` field) —
`gd-content-axis` → Content Axis. Extend the pass across the axis (skip when
`content_types: []`):

- **Changed set** also includes every content type already at `doc_status: revised`
  (a pending cross-axis flag not yet cleared back to `reviewed`).
- **Reverse-edge walk:** for each changed **system**, find every content type whose
  `content_types[].belongs_to` names it, or whose `CT.fields` carry a `ref<SYS>` to it.
  Those types join the **Affected** table as **content rows** (Relation: `feeds SYS-x
  (changed)`), with the same Still Valid / Needs Review / Likely Stale verdicts (a type
  whose consuming system removed a relied-on contract is Likely Stale). The reverse does
  **not** hold — editing a content type never stales a system.
- **Record impact (dependent CT):** list affected types in the changed system's
  `Affected (gd-verify):` line (section K). For each `Needs Review` / `Likely Stale`
  dependent type, bump it to `doc_status: revised` in the **`GD-IDS`
  `content_types[].doc_status` only** (the dependent-lag rule, on the content axis) —
  verify does **not** write the `CONTENT-TYPE.md` header `> Status:` (it catches up on
  the next `unikit-gd-content` touch), and the `## Content Map [gen]` re-renders
  (freshness). Append a one-line human-readable note to the type's **section F** (Open
  Questions & Changelog) — the content analogue of the `Affected (gd-verify):` line — so
  the next author sees why the type is `revised`. A `revised` content type returns to
  `reviewed` only through `unikit-gd-review`.

## Phase 4 — Triage Conflicts (4 tracks)

A verify conflict is **precise** (the grep found a real disagreement) but its **fix is
not always predetermined**, and it can **never be declined** — only resolved or routed.
Using the shared **handoff engine** (`gd-critique` → Handoff Engine), sort every conflict
into one of **four tracks**:

1. **Self-heal** — verify fixes it itself, no question. The `[gen]`-map **freshness**
   re-renders (Phase 2) and the **coherence** conflicts where `GD-IDS` is the machine
   truth: a status / version disagreement is healed by correcting the header to match
   `GD-IDS` (and the `## *Map [gen]` re-renders), unless the user resolves the registry the
   other way. This track is unchanged from before.

2. **Entailed → apply-ready** — the conflict's fix passes the silent **ENTAILED** criterion
   (one concrete target · the correct value already authoritative in `GD-IDS` · exactly one
   local fix · no external knowledge) but needs an **owner write** verify cannot make: a
   **duplicate id** to renumber, a **terminology drift** to swap to the canonical term, a
   **dangling reference** to repoint. Put it in the **apply-ready** set — the named fix
   handed to `unikit-gd-apply` (Phase 6) as a prose delta, authored by the owner, not here.

3. **Direction fork → interview** — the conflict admits **more than one valid direction**:
   a **value conflict** (the document or the registry could each be right), a **dangling
   `GOAL → SYS` / `AC`** fixable three ways across three zones, a **roster ↔ disk** mismatch
   (register vs delete), an **unregistered cross-doc `FORM-`/`ENT-`** (register vs remove).
   The user picks the **direction** — never *whether* to act (decline-vs-direction,
   `gd-critique`). The interview is **direction-only** and batched (`AskUserQuestion`, ≤ 4
   per call); it is **suppressed under the in-apply flag** (Phase 1 sentinel):

   ```
   AskUserQuestion: CONFLICT — <doc/section> vs GD-IDS: <the disagreement>. Which direction?
   Options:
   1. Fix as A — e.g. registry is right → correct the document
   2. Fix as B — e.g. document is right → update GD-IDS to <X> (with approval; record it)
   3. This is authoring / needs a decision → route to the owner or /unikit-gd-explore
   ```

   There is **no "Decline"** — a consistency conflict is a fact, not an opinion. A chosen
   A/B that is a clean local edit becomes an **apply-ready** prose delta (it joins Track 2);
   option 3 routes it to Track 4.

4. **Authoring → owner / explore** — the conflict needs **authoring**, not a mechanical
   fix: an **empty section H** / **missing AC**, a **placeholder leak** in a `detailed`+
   document, a **core section deferred past the floor**. It is neither entailed nor a
   direction pick — route it to the owning zone (`unikit-gd-system` / `unikit-gd-flow` /
   `unikit-gd-content` / `unikit-gd-spec`), or to `/unikit-gd-explore` when it needs a
   design decision first.

A registry change requires explicit approval and never silently overrides an existing
value; a deprecated ID is never deleted. Log every conflict + its track + resolution in
the report (Phase 5). The old coherence-vs-presence split still holds underneath: a
**coherence** conflict (status / version / Depends 3-way) is Track 1 (self-heal to
`GD-IDS`); a **presence** conflict (empty H, duplicate id, placeholder leak, unregistered
cross-doc fact) is Track 2 (entailed → apply-ready) or Track 4 (authoring), resolved by
editing the document, never the registry.

## Phase 5 — Report (only when needed)

**Write a report file only on `CONFLICTS FOUND` or a changed-scope pass.** A clean
full PASS is a single chat line — no file (decision 2026-06-12). When a file is
written, it is `.unikit/gamedesign/reviews/<date>_verify-<scope>.md` (`mkdir -p`):

```markdown
# Verify: <scope> — <YYYY-MM-DD>
> Result: <PASS | CONFLICTS FOUND>  ·  Scope: <SYS-slug | changed | all>

## Conflicts
| Severity | Document / Section | Check | Conflict (evidence) | Resolution |
|----------|--------------------|-------|---------------------|------------|

## Affected (changed-scope only)
| System / Flow | Relation | Verdict |
|---------------|----------|---------|

## Status changes
<rows bumped to `revised`, with approval>
```

**This file is the conflict report, not the handoff.** verify's handoff to
`unikit-gd-apply` is **inline prose from this session** (Phase 6) — verify writes **no**
handoff/bucket file (the asymmetry with `unikit-gd-review`, which leaves a durable
two-bucket report; a verify pass is ephemeral and cheap to re-run). The conflict-report
file here is unchanged — the cross-session rework checklist, written on `CONFLICTS FOUND`
or a changed-scope pass only.

Why keep the file: it is the only cross-session memory and the rework checklist.
When the same conflict recurs across systems, record it as a `gamedesign` `library`
rule via `/unikit-memory --module gamedesign` — durable domain knowledge, not
re-discovered each pass.

## Phase 6 — Handoff (standalone only; inline prose, recommend-only)

> **LOOP-GUARD.** This phase runs **only on a standalone `/unikit-gd-verify`**. Under the
> **in-apply flag** (the `apply-phase3` sentinel resolved in Phase 1), **skip this phase
> entirely** — verify self-heals (Track 1) and reports (Phase 5), but never offers a
> handoff back to `unikit-gd-apply`. That is the silent gate that stops
> `apply → verify → apply` from looping.

When Tracks 2 and 3 produced **apply-ready deltas** (the entailed fixes + the
direction-resolved edits), hand them to `unikit-gd-apply` **in one move** rather than
routing each conflict into an owner skill by hand. verify's handoff is **inline prose from
this session** — it writes **no** file (Phase 5): the prose deltas go straight into
`unikit-gd-apply`'s prose input path.

**Hand off — recommend-only (never a `Skill` call).** Neither `unikit-gd-review` nor
`unikit-gd-verify` carries `Skill` in `allowed-tools`; each **prints** the
`/unikit-gd-apply` recommendation for the user to run, and on Codex the auto-invoke nudge
runs it — the two handoff tails are identical by contract.

<!-- unikit:agents codex -->
### Auto-invoke the handoff — BLOCKING PRE-REQUISITE

When this phase prints the `/unikit-gd-apply` recommendation, automatically invoke it
(`$unikit-gd-apply "<the prose deltas>"`) rather than asking the user to run it by hand —
the printed command is the recommend-only fallback for agents without auto-invocation.
This is a single handoff: invoke `unikit-gd-apply` once with the prose deltas and let it
route them to the owners (it closes with its own `apply-phase3` verify — no loop).
<!-- unikit:end -->

- **apply-ready non-empty:** print the apply command with the deltas as prose —
  ```
  /unikit-gd-apply "<the entailed + direction-resolved deltas, each citing its conflict>"
  ```
  `unikit-gd-apply` takes the prose deltas (no file — verify writes none), dispatches them
  to the owning zones, and closes with its own `apply-phase3` verify pass. A single-zone
  set is bounced to its owner by apply's GATE 2.
- **apply-ready empty** (only self-heal + authoring / research routes): **no apply
  handoff** — list the Track-4 routes instead (`/unikit-gd-system` / `/unikit-gd-flow` /
  `/unikit-gd-content` / `/unikit-gd-spec`, or `/unikit-gd-explore` for the ones needing a
  design decision).

## Final: Compact Report

Follow the **Handoff Tail contract** (`gd-critique` → Handoff Engine): the runnable
command is the **last block** on screen — the report and any route note go above it,
nothing prints after it. Under the `apply-phase3` sentinel the handoff is
**suppressed**, so there is no command tail (the loop-guard).

Print the compact report:

```
Scope: <SYS-slug | changed | all>
Result: <PASS | CONFLICTS FOUND (<n>)>
Checks: facts · terms · IDs · dup-IDs · dangling · unregistered-fact · roster↔disk · map-freshness · Depends-3way · status · version · AC-presence · placeholder · core-floor — <pass/fail each>
Flow checks (when flows exist): GOAL-ids · dangling-GOAL · flow-status/version · flow-Depends-3way · mode↔structure · win/lose↔terminal-GOAL · funnel-continuity · flow/funnel-freshness — <pass/fail each>
Content checks (when content_types exist): CT/CU-ids · CU⊆CT · ref<>-resolve · scale↔structure · belongs_to-3way · content-status/version · content-map-freshness · RES/TRACK/KNOB · cross-axis SYS→CT — <pass/fail each>
Affected (changed-scope): <k systems + flows + content types — Needs Review: …, Likely Stale: …>
Freshness: <re-rendered ## System Map [gen] (· partial n/m where a section is deferred) | up to date>
Tracks: <self-heal: N · apply-ready: N · direction-interview: N · authoring/explore: N>
Handoff: <printed /unikit-gd-apply "<prose deltas>" | apply-ready empty → routes only | in-apply (apply-phase3) → suppressed>
Report: <path | none (clean PASS)>
```

The actionable routes when apply-ready is empty live in the `Tracks` line above —
read them there. No summary document beyond the conditional report file.

**Then end with the handoff as the LAST block** — one command, nothing after it:

- **apply-ready non-empty** → `🛠️ /unikit-gd-apply "<apply-ready deltas>"`
- **apply-ready empty, a conflict needs a design call** → `🔎 /unikit-gd-explore`
- **apply-ready empty, authoring only** → the matching owner: `🔧 /unikit-gd-system`
  / `/unikit-gd-flow` / `/unikit-gd-content` / `/unikit-gd-spec`
- **clean PASS** → `🔍 /unikit-gd-review <system>` (fresh session)
- **in-apply (`apply-phase3`)** → suppressed — no command tail.

Nothing prints after the command — no recap, no description of what the called
skill does next.

## Ownership Boundaries

- **Owns:** the `Affected (gd-verify):` changelog line (system section K) and the flow /
  content cross-axis note (flow / content type section F); verify report files; the
  `GD-IDS.yaml` `doc_status: revised` bump for a flagged dependent system, **flow, or
  content type** (with approval); and the **freshness re-render** of the `GAME.md`
  `## System Map [gen]`, `## Flow Map [gen]`, `## Funnel [gen]`, and `## Content Map
  [gen]` blocks (deterministic re-renders of `GD-IDS`, never an authored change — the
  Flow Map `Realized` column recomputed from the depended-on systems'
  `implemented_version`).
- **Read-only:** every design document and (except the `Affected` line / flow & content
  section-F notes, approved conflict resolutions, a flagged dependent's
  `doc_status: revised` bump, and the `[gen]`-map freshness re-renders) `GD-IDS.yaml`,
  `GAME.md`.
- **Not this skill:** quality judgment → `unikit-gd-review`; applying the apply-ready set
  in one pass → `unikit-gd-apply` (the handoff is **recommend-only** — verify prints the
  command, never invokes it); authoring → `unikit-gd-system` (systems) / `unikit-gd-flow`
  (flows) / `unikit-gd-content` (content types) / `unikit-gd-spec` (`GAME.md` + the roster);
  a conflict needing a design decision → `unikit-gd-explore`.
- **Never:** use web research; guess where a grep settles it; change a `GD-IDS`
  value silently or without approval; delete or renumber an ID; write the roster
  (route to `unikit-gd-spec`); carry `Skill`/`Agent` in `allowed-tools` or invoke
  `unikit-gd-apply` itself (the handoff is a printed prose recommendation); write a
  handoff/bucket file (the handoff is inline prose — the conflict report is the only file);
  offer the handoff or run the direction interview when invoked as apply's Phase 3 (the
  `apply-phase3` loop-guard); read the code workspace or project source.

## Quick Reference

```
/unikit-gd-verify SYS-combat        → check one system → triage conflicts (4 tracks) → handoff apply-ready to /unikit-gd-apply
/unikit-gd-verify                   → unverified diff → changed-scope impact; else full check
/unikit-gd-verify what did that change affect  → changed-scope impact from git diff
/unikit-gd-verify all               → full registry + roster + map-freshness + Depends pass
# internal: /unikit-gd-apply passes apply-phase3 as its Phase 3 — verify suppresses the handoff/interview (loop-guard)
```