neon-sprawl/docs/plans/NEO-76-implementation-plan.md

129 lines
9.5 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# 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 14 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
- [x] PR gate validates ability JSON against schema.
- [x] Exactly four prototype ability ids; duplicate `id` fails CI.
- [x] Stable id list documented in module doc freeze box.
## Implementation reconciliation (shipped)
- **Schema:** [`ability-def.schema.json`](../../content/schemas/ability-def.schema.json) — required `id`, `displayName`, `baseDamage`, `cooldownSeconds`; optional `abilityKind`.
- **Catalog:** [`prototype_abilities.json`](../../content/abilities/prototype_abilities.json) — four frozen rows per kickoff stat table.
- **CI:** `PROTOTYPE_E5M1_ABILITY_IDS` gate in [`validate_content.py`](../../scripts/validate_content.py).
- **Docs:** E5.M1 freeze table, `content/README.md`, CT.M1, alignment register, E5M1-01 backlog checkboxes.
## 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.