Skip to content

save_character.py assumes D&D 5e, so a World Kit with its own stat model can't save a character (and gets silently corrupted) #9

Description

@lordcheetah

Hi — really enjoying the World Kit system, and I think I've found an inconsistency between it and the character-creation path.

lib/world_kit.py lets a campaign define its own stat_schema (attributes + vitals), and the runtime respects that in most places. But features/character-creation/save_character.py is hardcoded to D&D 5e, so a kit that defines its own stat model can't save a character through /create-character. And it doesn't just reject the sheet — given one, it silently rewrites it.

The specifics

Line Behaviour
save_character.py:86 required_fields = ['name', 'race', 'class', 'level', 'stats'] — a kit without races/classes/levels has nothing to put here
:105-106 hp is recomputed via calculate_hp(class, level, con_modifier) — a hit-die lookup keyed on class name, defaulting to d8. It overwrites whatever HP the kit's own rules produced
:110 saves is derived from D&D class proficiencies — a concept many kits don't have
:121 "gold": character_data.get('gold', 0) is injected unconditionally

The gold one is the sharpest example of the mismatch: a kit can legitimately have no currency at all (an honour economy, barter, tripods and cattle), and this adds a gold field to every sheet regardless.

Repro

  1. Create/activate a campaign whose ruleset.json declares its own vitals, e.g.
    "stat_schema": { "attributes": ["bia","podes","thymos","metis","noos","mythos","aidos"], "vitals": ["hp","menos","honour","hubris","miasma","kleos"] }
  2. Build a character for it and try to save:
    bash tools/gm-player.sh save-json "$(cat hero.json)"
    → { "error": "Missing required field: race" }
    
  3. Satisfy the required fields with placeholders to get past the gate, and the saved sheet comes back with a d8-derived HP, D&D saving throws, and a gold field.

Net effect: a non-5e PC has to be written to the campaign's character.json directly, bypassing the supported path entirely.

Suggested fix

Make it kit-aware the same way the rest of the runtime can be — read stat_schema.vitals from the active campaign's ruleset.json:

  • require only name when the kit declares its own vitals; keep the current 5e requirement when it doesn't (so existing D&D campaigns are unchanged);
  • run calculate_hp / calculate_saves only for 5e kits, and otherwise persist the sheet's own vitals untouched;
  • don't inject gold unless the kit actually has it.

I've done the equivalent for player_manager.show_player / show_all_players (which printed Level 1 ... Gold: 0 for every kit) and schemas.validate_character (which required race/class/level of every character) in my fork, gating on stat_schema.vitals and leaving the legacy path byte-for-byte identical when a kit declares none. Happy to open a PR here for save_character.py along the same lines if that'd be useful — just didn't want to send one unsolicited.

Related design question

The /create-character command itself is 5e-shaped end to end (races, classes, spell slots, gold), so fixing the save path alone still leaves the interview assuming D&D. Might be worth deciding whether the command should branch on the active kit, or whether a World Kit should be able to supply its own creation flow.

Environment: Windows 11, Python 3.11, main as of today.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions