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

11 KiB
Raw Blame History

NEO-100 — Implementation plan

Story reference

Field Value
Key NEO-100
Title E5M3-01: Prototype EncounterDef + RewardTable catalog + schemas + CI
Linear https://linear.app/neon-sprawl/issue/NEO-100/e5m3-01-prototype-encounterdef-rewardtable-catalog-schemas-ci
Module E5.M3 — EncounterAndRewardTables · Epic 5 Slice 3 · backlog E5M3-01
Branch NEO-100-e5m3-encounter-reward-catalog-schemas-ci
Precursor E5.M2 Ready (three NPC instance ids) · E3.M3 Ready (frozen item catalog incl. contract_handoff_token)
Pattern NEO-87 — row JSON Schema + catalog envelope + scripts/validate_content.py gate
Blocks NEO-101 — server encounter + reward catalog load

Kickoff clarifications

No clarifications needed. Scope, frozen ids, loot grants, completion rule, and cross-ref targets are fully specified in E5M3 kickoff decisions, E5.M3 freeze table, and content/README.md (NEO-100 paragraph already present from E5M3 decomposition). Catalog envelope and CI constant naming follow NEO-87 / NEO-76 precedent without ambiguity.

Topic Question Agent recommendation Answer
No blocking decisions (see above).

Goal, scope, and out-of-scope

Goal: Lock content shape and CI validation for one frozen EncounterDef + one frozen RewardTable before server load (NEO-101+).

In scope (from Linear + E5M3-01):

  • content/schemas/encounter-def.schema.json, content/schemas/reward-table.schema.json, content/schemas/reward-grant-row.schema.json.
  • content/encounters/prototype_encounters.json — one row prototype_combat_pocket.
  • content/reward-tables/prototype_reward_tables.json — one row prototype_combat_pocket_clear.
  • scripts/validate_content.py — schema validation, duplicate id, one-encounter / one-table allowlists, cross-ref itemId → frozen item catalog, requiredNpcInstanceIds → three E5.M2 NPC instance ids, rewardTableId → reward table ids.
  • Designer note / CI rules line in E5_M3 + CT.M1 PR gate paragraph.
  • documentation_and_implementation_alignment.md E5.M3 row → note NEO-100 catalog in progress / landed.

Out of scope (from Linear):

  • Server loader, runtime engine, HTTP, Godot (NEO-101+).
  • IEncounterProgressStore, completion events, inventory grant (E5M3-04+).
  • Client counterpart: none — server-only content + CI; player-visible work starts at E5M3-08+ / NEO-110 / NEO-111.

Acceptance criteria checklist

  • PR gate validates encounter + reward-table JSON against schema.
  • Exactly one prototype encounter id and one reward table id; duplicate id fails CI.
  • Stable id list documented in module doc freeze box.

Technical approach

  1. reward-grant-row.schema.json: Mirror recipe-io-row.schema.json — required itemId (pattern ^[a-z][a-z0-9_]*$), quantity integer ≥ 1.

  2. reward-table.schema.json: Draft 2020-12 object; additionalProperties: false. Required: id, displayName, fixedGrants (non-empty array of $ref reward-grant-row). id pattern same as other catalogs.

  3. encounter-def.schema.json: Required: id, displayName, completionCriteria, requiredNpcInstanceIds, rewardTableId. completionCriteria: object with required kind enum defeat_all_targets only (prototype). requiredNpcInstanceIds: array of 3 strings (pattern), minItems/maxItems: 3. rewardTableId: string pattern.

  4. Catalog files:

    content/reward-tables/prototype_reward_tables.json

    {
      "schemaVersion": 1,
      "rewardTables": [
        {
          "id": "prototype_combat_pocket_clear",
          "displayName": "Prototype Combat Pocket Clear",
          "fixedGrants": [
            { "itemId": "scrap_metal_bulk", "quantity": 10 },
            { "itemId": "contract_handoff_token", "quantity": 1 }
          ]
        }
      ]
    }
    

    content/encounters/prototype_encounters.json

    {
      "schemaVersion": 1,
      "encounters": [
        {
          "id": "prototype_combat_pocket",
          "displayName": "Prototype Combat Pocket",
          "completionCriteria": { "kind": "defeat_all_targets" },
          "requiredNpcInstanceIds": [
            "prototype_npc_melee",
            "prototype_npc_ranged",
            "prototype_npc_elite"
          ],
          "rewardTableId": "prototype_combat_pocket_clear"
        }
      ]
    }
    

    requiredNpcInstanceIds order matches E5.M3 freeze table (melee → ranged → elite); CI enforces set equality with PROTOTYPE_E5M2_NPC_INSTANCE_IDS, not order-sensitive beyond schema min/max 3.

  5. validate_content.py constants (keep in sync with E5.M3 module doc + future NEO-101 loader):

    Constant Value
    PROTOTYPE_E5M3_ENCOUNTER_IDS { "prototype_combat_pocket" }
    PROTOTYPE_E5M3_REWARD_TABLE_IDS { "prototype_combat_pocket_clear" }
    PROTOTYPE_E5M2_NPC_INSTANCE_IDS { "prototype_npc_melee", "prototype_npc_ranged", "prototype_npc_elite" }
  6. validate_content.py validators:

    • Add schema paths + dirs: ENCOUNTER_DEF_SCHEMA, REWARD_TABLE_SCHEMA, REWARD_GRANT_ROW_SCHEMA, ENCOUNTERS_DIR, REWARD_TABLES_DIR.
    • _validate_reward_table_catalogs: envelope schemaVersion: 1, top-level rewardTables array; validate each row; track seen_ids; reject duplicates across files.
    • _prototype_e5m3_reward_table_gate: ids must equal PROTOTYPE_E5M3_REWARD_TABLE_IDS.
    • _prototype_e5m3_reward_grant_item_gate: each fixedGrants[].itemIdPROTOTYPE_SLICE1_ITEM_IDS; quantities ≥ 1 (schema) — spot-check scrap_metal_bulk ×10 and contract_handoff_token ×1 in gate or integration-style assert in docstring test notes.
    • _validate_encounter_catalogs: envelope schemaVersion: 1, top-level encounters array; validate rows; duplicate id rejection.
    • _prototype_e5m3_encounter_gate: ids must equal PROTOTYPE_E5M3_ENCOUNTER_IDS.
    • _prototype_e5m3_encounter_cross_ref_gate: each row's requiredNpcInstanceIds set must equal PROTOTYPE_E5M2_NPC_INSTANCE_IDS; rewardTableId must exist in reward-table seen_ids.
    • Run reward tables before encounters in main() (encounters reference reward table ids).
    • Extend module docstring, startup schema file checks, success summary with encounter + reward-table file/id counts.
  7. Docs:

    • E5_M3_EncounterAndRewardTables.md — add CI enforcement line under freeze table (ids + cross-ref rules); link to this plan when complete.
    • CT_M1_ContentValidationPipeline.md — encounter + reward-table PR gate paragraph.
    • documentation_and_implementation_alignment.md — E5.M3 row notes NEO-100 catalog when complete.
    • content/README.md — table rows for encounters/ and reward-tables/ already present; verify paragraph matches shipped constants after implementation.
    • E5M3-prototype-backlog.md — E5M3-01 checkboxes when implementation completes.
  8. No server/C#/client changes in this story.

Files to add

Path Purpose
content/schemas/reward-grant-row.schema.json JSON Schema for a single fixed-grant row on a RewardTable.
content/schemas/reward-table.schema.json JSON Schema for a single RewardTable row (uses reward-grant-row).
content/schemas/encounter-def.schema.json JSON Schema for a single EncounterDef row.
content/reward-tables/prototype_reward_tables.json Prototype one-table catalog (schemaVersion + rewardTables array).
content/encounters/prototype_encounters.json Prototype one-encounter catalog (schemaVersion + encounters array).
docs/plans/NEO-100-implementation-plan.md This plan.

Files to modify

Path Rationale
scripts/validate_content.py Load/validate encounter + reward-table catalogs; duplicate id; E5M3 one-id gates; NPC instance + item + rewardTableId cross-refs; success summary.
docs/decomposition/modules/E5_M3_EncounterAndRewardTables.md CI enforcement note under freeze table; link to plan when complete.
docs/decomposition/modules/CT_M1_ContentValidationPipeline.md PR gate paragraph for encounter + reward-table catalog validation.
docs/decomposition/modules/documentation_and_implementation_alignment.md E5.M3 / NEO-100 status after catalog lands.

Tests

Item Coverage
CI / PR gate .github/workflows/pr-gate.yml runs python scripts/validate_content.py — extended script is the primary regression signal.
Manual negative cases From repo root after implementation: (1) duplicate encounter or reward-table id → non-zero exit; (2) remove frozen id → gate error; (3) unknown itemId in fixedGrants → cross-ref error; (4) wrong NPC instance id set → cross-ref error; (5) broken rewardTableId on encounter → cross-ref error; (6) empty displayName or invalid completionCriteria.kind → schema error.
Manual happy path pip install -r scripts/requirements-content.txt && python3 scripts/validate_content.py — reports encounter + reward-table catalogs + existing catalogs OK.

No dedicated pytest module in-repo today (same as NEO-87 / NEO-76).

Open questions / risks

Question / risk Agent recommendation Status
NPC instance ids are server-registry constants, not content rows PROTOTYPE_E5M2_NPC_INSTANCE_IDS frozenset in CI — mirror server PrototypeNpcRegistry; document sync obligation in module doc (same pattern as behavior ids without a content file). adopted
content/README.md pre-documents NEO-100 before catalogs land Keep paragraph; verify constants match after ship — decomposition already merged. adopted
Cursor markdown #anchor deep links Unrelated to this story; GitHub + stable ids remain source of truth. deferred