# Contributing

The spec is small on purpose. The hard question in every proposal is the same one, so it
is worth answering first.

## Core type, or extension?

**A core type** describes something that exists in every story regardless of medium. A
character is a character in a novel, a screenplay, a game and a comic. So `Character` is a
core type, and `wound / want / need` are core fields, because a screenwriter and a novelist
mean the same thing by them.

**An extension** describes something one medium needs and the others do not. A panel count
is meaningless in prose. A slugline is meaningless in a comic. Those live under
`extensions.graphic` and `extensions.screen`.

The test that actually settles it: **would an overlay-unaware reader be confused by the
object without this field?** If yes, it is core. If they would simply not miss it, it is an
extension.

Two failure modes this is guarding against, both of which look reasonable at the time:

- **Core creep.** Every medium-specific field added to a core type is a field every other
  implementer has to read, ignore and carry forever. Ten of those and the spec is a union
  of four formats rather than a contract between them.
- **Overlay hoarding.** Something genuinely universal hidden in `extensions.novel` because
  that was where the person who needed it happened to be working. Now two tools model the
  same idea two ways and cannot read each other, which is the exact problem this spec
  exists to solve.

When it is genuinely ambiguous, **start it as an extension**. Promoting a field from an
overlay to core is a minor version; demoting one is a major.

## What a change costs

Before proposing, check which of these you are asking for:

| Change | Version |
| --- | --- |
| A new optional field or depth group | minor |
| A new type | minor |
| A new overlay namespace, or a field on one | minor |
| Docs, examples, validator bugfixes that keep old documents valid | patch |
| A new required field | **major** |
| Renaming a field or a depth group | **major** |
| Removing anything | **major** |

A rename is a major even though it feels like a rename. A consumer pinned to the old name
stops seeing the data and gets no error telling it so, which is worse than a break it can
see.

`npm run compat` enforces all of this against the frozen baseline. If it goes red, the
change is bigger than you thought — do not edit `compat/baseline/` to make it green. That
converts a broken promise into a silent one, and `compat:accept` refuses to do it without
an explicit `--major` for exactly that reason.

## Adding a type

1. Add it to the `CATALOG` in [`tools/gen-schemas.mjs`](tools/gen-schemas.mjs) and run
   `npm run schemas`, with a **tiny** required core. If you find yourself requiring four
   fields, you are describing a filled-in object rather than a valid one — a stub has to be
   valid. (Overlay schemas under `schema/v0/overlays/` are hand-maintained JSON, not
   generated.)
2. Give every depth group a documented order. Synthesis fills one group per pass and needs
   to know which one is next.
3. Add a worked example to `examples/tidewardens/` and a stub to `examples/minimal/`.
4. `npm test && npm run compat && npm run validate -- examples/tidewardens`.

## Adding an invariant

An invariant is a rule JSON Schema cannot express. Before adding one, be sure it is a rule
about *the data* and not a rule about *good writing* — this spec has opinions about the
first kind only. "want must differ from need" is structural: if they match, the arc has no
distance and the document is describing a contradiction. "A protagonist should be likeable"
is craft, and belongs in a skill.

Scope it to what is PRESENT. Starting a group applies that group's rules; leaving it out
applies nothing. There is no completeness threshold that turns invariants off.

## Tests

Every rule needs a case that proves it fires and a case that proves it does not fire when
it should not. A check that has never failed and a check that *cannot* fail look identical
from outside, and the second is worse than nothing because it is trusted.

The compatibility checker has seven deliberate sabotage cases for this reason. New rules
deserve the same.

## Filing an issue

Say what you were modelling and what the spec would not let you say. A concrete object you
could not represent is worth more than a proposed field — the field you would have added is
often not the one that gets added.
