diff --git a/docs/plans/NEO-76-implementation-plan.md b/docs/plans/NEO-76-implementation-plan.md new file mode 100644 index 0000000..527f36a --- /dev/null +++ b/docs/plans/NEO-76-implementation-plan.md @@ -0,0 +1,121 @@ +# NEO-76 — Implementation plan + +## Story reference + +| Field | Value | +|--------|--------| +| **Key** | NEO-76 | +| **Title** | E5M1-01: Prototype AbilityDef catalog + schemas + CI | +| **Linear** | https://linear.app/neon-sprawl/issue/NEO-76/e5m1-01-prototype-abilitydef-catalog-schemas-ci | +| **Module** | [E5.M1 — CombatRulesEngine](../decomposition/modules/E5_M1_CombatRulesEngine.md) · Epic 5 Slice 1 · backlog **E5M1-01** | +| **Branch** | `NEO-76-e5m1-prototype-backlog` | +| **Precursor** | [E1.M4](../decomposition/modules/E1_M4_AbilityInputScaffold.md) — hardcoded allowlist in [`PrototypeAbilityRegistry`](../../server/NeonSprawl.Server/Game/AbilityInput/PrototypeAbilityRegistry.cs) (unchanged this story) | +| **Pattern** | [NEO-50](https://linear.app/neon-sprawl/issue/NEO-50) / [NEO-65](https://linear.app/neon-sprawl/issue/NEO-65) — row JSON Schema + catalog envelope + `scripts/validate_content.py` gate | +| **Blocks** | [NEO-77](https://linear.app/neon-sprawl/issue/NEO-77) — server ability catalog load | + +## Kickoff clarifications + +| Topic | Question | Agent recommendation | Answer | +|--------|----------|----------------------|--------| +| **Prototype stat freeze** | `baseDamage` / `cooldownSeconds` / `abilityKind` per row? | **Recommended table** — pulse 25 dmg / 3.0s; burst 40 / 5.0; guard 0 / 6.0 utility; dash 0 / 4.0 movement; four pulse casts = 100 HP dummy defeat; pulse cooldown matches NEO-32 global 3s. | **User:** recommended table (adopted below). | +| **abilityKind on rows** | Required on all four prototype rows? | **Include on all four** — optional in schema but aids E5M1-04 GET / E5M1-11 HUD without follow-up schema churn. | **Adopted** (bundled with stat freeze). | +| **Catalog envelope** | File shape? | `{ "schemaVersion": 1, "abilities": [ … ] }` in `content/abilities/prototype_abilities.json` (mirror items / recipes). | **Adopted** (NEO-50 / NEO-65 precedent). | +| **CI constant name** | Slice gate identifier? | **`PROTOTYPE_E5M1_ABILITY_IDS`** — Epic 5 Slice 1; avoids collision with economy Slice 1–4 numbering in `validate_content.py`. | **Adopted**. | + +## Goal, scope, and out-of-scope + +**Goal:** Lock content shape and CI validation for **four** frozen `AbilityDef` rows before server load (NEO-77+). + +**In scope (from Linear + [E5M1-01](E5M1-prototype-backlog.md#e5m1-01--prototype-abilitydef-catalog--schemas--ci)):** + +- `content/schemas/ability-def.schema.json`. +- `content/abilities/prototype_abilities.json` — four frozen ids (freeze table below). +- `scripts/validate_content.py` — schema validation, duplicate `id`, exact four-id allowlist, non-empty `displayName`. +- Designer note + **Prototype Slice 1 freeze** table in [E5_M1](../decomposition/modules/E5_M1_CombatRulesEngine.md) + `content/README.md`. +- **CT.M1** PR gate paragraph for ability catalogs. +- `documentation_and_implementation_alignment.md` E5.M1 row → **In Progress** / NEO-76. + +**Out of scope (from Linear):** + +- Server loader, combat engine, HTTP, Godot (NEO-77+). +- Changing [`PrototypeAbilityRegistry`](../../server/NeonSprawl.Server/Game/AbilityInput/PrototypeAbilityRegistry.cs) or global cooldown behavior (NEO-77 / E5M1-07). + +## Acceptance criteria checklist + +- [ ] PR gate validates ability JSON against schema. +- [ ] Exactly four prototype ability ids; duplicate `id` fails CI. +- [ ] Stable id list documented in module doc freeze box. + +## Technical approach + +1. **Row schema (`ability-def.schema.json`):** Draft 2020-12 object; `additionalProperties: false`. Required: `id`, `displayName`, `baseDamage`, `cooldownSeconds`. `id` pattern `^[a-z][a-z0-9_]*$`. `displayName` `minLength: 1`. `baseDamage` integer ≥ 0. `cooldownSeconds` number exclusiveMinimum 0 (positive). Optional: `abilityKind` enum `attack` \| `utility` \| `movement` (present on all four prototype rows). + +2. **Catalog file:** `content/abilities/prototype_abilities.json` with `schemaVersion: 1` and four rows from the freeze table. + +3. **Frozen content values (E5M1-01 — kickoff adopted):** + + | `AbilityDef.id` | `displayName` | `baseDamage` | `cooldownSeconds` | `abilityKind` | + |-----------------|---------------|--------------|-------------------|---------------| + | `prototype_pulse` | Prototype Pulse | 25 | 3.0 | `attack` | + | `prototype_burst` | Prototype Burst | 40 | 5.0 | `attack` | + | `prototype_guard` | Prototype Guard | 0 | 6.0 | `utility` | + | `prototype_dash` | Prototype Dash | 0 | 4.0 | `movement` | + + **Rules:** Do **not** rename `id` values after ship — change `displayName` only, or add a new `id` in a follow-up issue. Relaxing the four-id CI gate requires a new Linear issue. **`prototype_pulse`** at 25 dmg × 4 casts = 100 HP dummy defeat (E5M1 backlog max HP default). **`prototype_pulse`** cooldown 3.0s matches current [`AbilityPrototypeCooldown.GlobalDuration`](../../server/NeonSprawl.Server/Game/AbilityInput/AbilityPrototypeCooldown.cs) until E5M1-07 switches to per-ability catalog cooldowns. + +4. **`validate_content.py`:** + - Add `ABILITY_SCHEMA`, `ABILITIES_DIR`; discover `content/abilities/*_abilities.json`. + - `_validate_ability_catalogs`: envelope `schemaVersion: 1`, top-level `abilities` array; validate each row against `ability-def.schema.json`; track `seen_ids`; reject duplicates across files. + - **`_prototype_e5m1_ability_gate`:** ids must equal `PROTOTYPE_E5M1_ABILITY_IDS` (frozenset of four ids above). + - Run ability validation after existing catalogs succeed (no cross-catalog refs in Slice 1). + - Require ability schema file in `main()` startup checks; extend module docstring + success summary with ability file + id counts. + - Keep `PROTOTYPE_E5M1_ABILITY_IDS` in sync with [E5.M1 freeze table](../decomposition/modules/E5_M1_CombatRulesEngine.md#prototype-slice-1-freeze-e5m1-01) and future NEO-77 server loader. + +5. **Docs:** + - `content/README.md` — add `abilities/` table row + E5 Slice 1 freeze paragraph. + - `E5_M1_CombatRulesEngine.md` — add **Prototype Slice 1 freeze (E5M1-01)** subsection with id + stat table + CI rules line. + - `CT_M1_ContentValidationPipeline.md` — ability catalog PR gate paragraph. + - `documentation_and_implementation_alignment.md` — E5.M1 row → **In Progress** / NEO-76 catalog landed. + - `E5M1-prototype-backlog.md` — E5M1-01 checkboxes when implementation completes. + +6. **No server/C#/client changes** in this story. + +## Files to add + +| Path | Purpose | +|------|---------| +| `content/schemas/ability-def.schema.json` | JSON Schema for a single `AbilityDef` row. | +| `content/abilities/prototype_abilities.json` | Prototype four-ability catalog (`schemaVersion` + `abilities` array). | +| `docs/plans/NEO-76-implementation-plan.md` | This plan. | + +## Files to modify + +| Path | Rationale | +|------|-----------| +| `scripts/validate_content.py` | Load/validate ability catalogs; duplicate `id`; E5M1 four-id gate; success summary. | +| `content/README.md` | Document `content/abilities/`, schema path, E5 Slice 1 freeze (active catalog). | +| `docs/decomposition/modules/E5_M1_CombatRulesEngine.md` | Freeze table + CI enforcement note; link to plan when complete. | +| `docs/decomposition/modules/CT_M1_ContentValidationPipeline.md` | PR gate paragraph for ability catalog validation. | +| `docs/decomposition/modules/documentation_and_implementation_alignment.md` | E5.M1 / NEO-76 status after catalog lands. | +| `docs/plans/E5M1-prototype-backlog.md` | E5M1-01 checkboxes + landed note when complete. | + +## Tests + +| Item | Coverage | +|------|----------| +| **CI / PR gate** | `.github/workflows/pr-gate.yml` already runs `python scripts/validate_content.py` — extended script is the regression signal. | +| **Manual negative cases** | From repo root after implementation: (1) duplicate ability `id` → non-zero exit; (2) remove one frozen id → gate error; (3) break schema (negative `baseDamage` or zero `cooldownSeconds`) → schema error; (4) empty `displayName` → schema error. | +| **Manual happy path** | `pip install -r scripts/requirements-content.txt && python3 scripts/validate_content.py` — reports ability catalog + existing catalogs OK. | + +No dedicated pytest module in-repo today (same as NEO-50 / NEO-65). + +## Open questions / risks + +| Question / risk | Agent recommendation | Status | +|-----------------|----------------------|--------| +| **Catalog growth** | E5M1 gate requires **exactly** the four frozen ids; expanding the roster needs a follow-up issue to change `PROTOTYPE_E5M1_ABILITY_IDS`. | **adopted** | +| **C# vs Python drift** | None until NEO-77; document “keep in sync” constant names in both places when server load lands (mirror NEO-66 / `PrototypeSlice3RecipeCatalogRules`). | **adopted** | +| **Runtime still uses global 3s cooldown** | Expected — content lands before E5M1-07 wires per-ability `cooldownSeconds`; pulse 3.0s pre-aligns catalog with NEO-32 behavior. | **adopted** | +| **Zero-damage guard/dash before E5M1-06** | Catalog values are forward-looking; cast accept path still has no damage until E5M1-07. Zero-damage rows support E5M1-06 unit tests. | **adopted** | + +None blocking.