129 lines
9.5 KiB
Markdown
129 lines
9.5 KiB
Markdown
# 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
|
||
|
||
- [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.
|