Skip to content

Tutorial Per Individual Fields

johnjohto edited this page Jul 28, 2026 · 2 revisions

Tutorial: Per-Individual Fields (ext)

Give each individual monster its own extra state -- a grade, a variant type, a tint, a mark -- beyond what its species defines. You declare the fields once; the engine validates every producer against your declaration, carries the values through saves, trades, and link battles, and feeds them to your stat and palette hooks.

1. Declare the fields in data/ruleset.json:

{"mon_fields": {
  "grade":     {"type": "integer", "minimum": 0, "maximum": 5},
  "delta_type": {"type": "string"},
  "shiny_mark": {"type": "boolean"}
}}

Each field is a schema fragment. Everything below is checked against it -- a typo'd field name or an out-of-range value refuses at --validate, again at load, and the engine never half-applies.

2. Author it on content. Any surface that produces an individual takes an ext block:

{"cmd": "give_mon", "species": "species:cindercub", "level": 5,
 "ext": {"grade": 3}}

The same block works on wild_battle commands, on encounter-group entries (every wild rolled from that entry carries it -- grass rolls and visible overworld spawns alike), and on trainer party members, which also accept pinned dvs and sexp bags (and ivs/evs/nature if your project declares mon_wire: 2):

{"species": "species:galewisp", "level": 18,
 "dvs": {"atk": 15, "spd": 15}, "ext": {"grade": 4}}

3. Write it from a story. The set_mon_ext command (or the same call inside a HatchScript event) changes a party member's field -- the appraisal booth, the fusion altar, the grading ceremony:

let g = get_mon_ext(0, "grade")
if g < 5 { set_mon_ext(0, "grade", g + 1) }

Reads answer the field's type default (0/false/"") until something writes it. Writes queue with the event's other commands, so a failing script never half-writes, and stats recompute immediately -- a graded mon grows the moment the ceremony ends.

4. Roll it at birth. Bind the on_mon_created formula kernel and every new individual -- wild, gift, trainer's, starter, traded-in -- rolls your fields when it's created:

{"formula_scripts": {"on_mon_created": "script:birth_roll"}}
# script:birth_roll — a rare mark, a graded wild
if rand_int(64) == 0 { out("shiny_mark", true) }
if provenance == "wild" { out("grade", rand_int(3)) }

The script sees species, level, provenance, and the rolled dv_* values; rand_int(n) draws from a dedicated stream seeded off the run seed -- the same run rolls the same individuals, and your rolls never disturb battles, spawns, or anything else. An authored ext block on the same surface beats the roll.

5. Consume it. stat_base_scale reads every declared field as ext_<name> (Modern Individuals shows verifying it with --statread), the sprite-variant ladder reads ext.palette (Sprite Variants), and the values ride the mon record through saves, trades, and link play -- both peers always compute the same stats from the same individual.

Clone this wiki locally