Talos docs

Breaking changes: what we promise about your project

Since 0.7

Draft for sign-off, 25 Oct 2026. The commander signs off the engine rules.

Plain text

Draft for sign-off, 25 Oct 2026. The commander signs off the engine rules. Sean decides the support window and the other open questions at the end. Until then nothing here is in force, except what the Today section says is already built.

This page says what may change between engine versions, what the engine does about it, and what you do about it. It covers the files of your project that you or the editor wrote:

  • argus.project.json;
  • scenes, prefabs and materials;
  • scripts against the script API.

It does not cover:

  • .argus/: the engine's own state beside a project (caches, the migration backups), which an engine may rebuild at any time;
  • features you opted into under experimental: they may change or go with no compatibility promise (docs/PROJECT-MANIFEST.md, experimental);
  • exported games: they carry their own engine and never change after export.

The promise

  1. A newer engine opens your project, or tells you exactly how to upgrade it. It never silently drops a field, rewrites a file you did not ask it to, or opens a project it only half understands.
  2. Upgrading is one command, and so is undoing it.
    • argus project migrate --dry-run <project> shows what would change.
    • --apply changes it and first keeps a backup of every file it writes.
    • --restore puts the backup back.
  3. An older engine never guesses at a newer project's format. It refuses and says a newer engine saved the project. There is no downgrade.
  4. Every format change ships with its migration and a release note in the same release, and only in a new series (0.N.0), never in a patch.

What counts as a breaking change

changebreaking?what ships with it
a new optional field, component, rule warning or API membernoa release note (added)
a fix that changes behaviour only where it was wrongnoa release note (fixed); allowed in a patch
a new default for a field you did not set, which changes how a scene looks or playsyes, behavioura release note (changed) saying what changes and how to keep the old behaviour, if there is a way
a field renamed, moved, split or removed; a value's meaning changed; a file layout changedyes, formata schemaVersion step (manifest step and/or file steps in the migration registry), a release note (breaking) with its migration: line, and a fixture project at the old version the step is tested on
a rule that turns a warning into an erroryes, validationa release note (breaking); in the release before, a warning that names the coming change
a built-in resource removed, so a builtin: path a project names stops resolvingyes, contenta release note (breaking) naming the paths and their replacement; until projects have had a series to move, the engine runs on a placeholder and warns, and only an export refuses
a script API member removed or changedyes, APIa deprecation in the types in an earlier release (it still works, and typecheck flags it), then the removal with a release note (breaking)

The version rules already say:

  • a patch carries fixes only, nothing a passing project could notice except that a bug is gone;
  • a minor release (0.5 to 0.6) is, while we are 0.x, where breaking changes go.

This page says what each kind of break must bring with it.

How a format change is made (for engine developers)

  1. schemaVersion goes up by exactly one in a release that changes the format: N to N + 1. No release skips a number, and no step reads two versions.
  2. The step is pure. The same input always gives the same bytes; no clock, no randomness, no network. A step that would need information the old file lacks refuses and says what to add. It never invents it (no generated ids).
  3. The step is in the registry: The upgrade report, --apply and --restore pick it up with no other code.
    • the manifest step goes in PROJECT_MIGRATIONS (Rust and its TypeScript twin, kept identical);
    • file steps go in PROJECT_FILE_MIGRATIONS.
  4. It is tested on a fixture project at the old version: apply, validate with the new engine, open, restore, byte-compare. crates/contract/tests/project_migrate.rs does this with a test-only step.
  5. Every project an earlier release shipped is run through it: node scripts/migrate/old-projects.mjs (results: docs/MIGRATE-OLD-PROJECTS.md). Every row must still pass.
  6. It has a release note: node scripts/release/changelog.mjs new <slug> --kind breaking --area <area> --text "...", then add a migration: line to the entry. The changelog check refuses a breaking entry without one, and finalize refuses a release page whose Migrating section leaves it out. Write it for someone carrying a real project across.
  7. The commander signs off every schemaVersion bump: it is a contract change.

Upgrading your project (for users)

  1. Commit or copy your project first. --apply refuses to write a file that has uncommitted changes in git (--allow-dirty overrides), and it keeps its own backup anyway.
  2. argus project migrate --dry-run <project>. It lists every file it would write and why, and every rule finding. If it reports an error, nothing can be applied until that is fixed.
  3. argus project migrate --apply <project>. Its steps: A second --apply reports "already current".
    • it writes the backup to <project>/.argus/migrations/<UTC time>/ before touching anything;
    • it writes each file;
    • if anything fails part-way, it puts back what it wrote, so the project is byte-identical to before.
  4. Open the project and check it. If the report lists placeholder sounds (a 0.5 project's retired builtin:sfx/… sounds), put a sound of your own over each file it names.
  5. To undo: argus project migrate --restore <project>. It puts back the newest backup (--backup <id> names another one).
    • If you have changed a migrated file since, it refuses and lists the file.
    • --force restores anyway, and first saves your newer bytes in another backup.
  6. Backups are never deleted for you. Delete .argus/migrations/<id>/ yourself once you are sure.

An apply also moves the project's engineVersion to this engine's series and stamps savedWith. That is what "Upgrade to Talos X.Y" means in the launcher: the project now opens with this series.

Support window (proposed)

  • An engine migrates projects from every earlier schema version: the registry is a chain from the oldest version this engine reads.
  • Proposed: the chain is kept whole for at least the last two release series. Older steps may be retired, with a release note. A project from before the window upgrades through an intermediate engine (engines install side by side, so the older one is still there). Sean decides.
  • Behaviour and API deprecations last at least one release series before removal.

Today (built and tested)

  • schemaVersion is 1 and stays frozen until this page is signed off.
  • The registry holds one step, 0 to 1. Version 0 is a synthetic draft that no release wrote. The step keeps the migration path exercised end to end.
  • The commands work (stranger-ready plan, phase C):
    • --dry-run, --apply with backup and rollback, and --restore with --force;
    • launch refuses an older project and names project migrate; it refuses a newer one and says there is no downgrade.
  • Every project in 0.5.0, 0.5.1, 0.6.0 and 0.7.0 has been run through dry run, apply, validate, open, apply again and restore with today's engine. The table is docs/MIGRATE-OLD-PROJECTS.md.
  • One break has shipped without its migration note (found by that run): It now has a migration (the commander, #14909; decision 5): --apply points each such sound at a placeholder file it generates beside the scene (assets/audio/retired-builtin/<shelf>/<name>.wav: a short beep, or silence for music and ambient), lists every one for the user to replace, and --restore removes them. Both 0.5 projects in this repository that hit it now apply and open. The missing note is Release's (0.7.x notes and a 0.6.0 erratum).
    • 0.6.0 removed the starter sounds;
    • a 0.5 project that names builtin:sfx/…, builtin:music/…, builtin:ambient/… or builtin:voice/… fails asset-builtin-unknown (an error), so 0.6.0 and later refuse to open it;
    • the 0.6.0 notes say such a path "now plays a placeholder tone", and their Migrating section says "No breaking changes".
  • Not built yet:
    • the launcher's Upgrade dialog still says converting files "comes in a later version"; it is to be wired to --apply --json;
    • an apply rewrites the manifest (and any file a schema file step changes) in the engine's own JSON form, so hand formatting in those files is not kept. The retired-sounds step is the exception: it edits only the path strings in a scene's text.

Decisions for sign-off

#questionproposed
1Support windowthe last two release series; older projects step through an intermediate engine
2Should the editor offer --apply when it opens an older project, after asking, or stay a command plus a launcher button?a command and a launcher button only, for now; the refusal already names the command
3Behaviour changes to defaults: is a release note enough, or must each such change sit behind a flag for one series first?a release note, plus the old behaviour as an opt-in value where one is cheap to keep
4Does the old-projects run (scripts/migrate/old-projects.mjs) join the release checklist?yes, for every series release, with the new release's projects added as a ref
5The 0.6.0 starter-sound break: (a) only add the missing note (the 0.6.x/0.7 Migrating section and docs/MIGRATE-OLD-PROJECTS.md); (b) also do what the 0.6.0 note promised, so a retired builtin: sound plays the placeholder tone and warns while export still refuses it by name (a rule-severity change, so a contract change); (c) a migration step that rewrites those pathsdecided (the commander, #14909): (c), with each path pointed at a generated, clearly named placeholder file in the project that the report lists for the user to replace; built. (a) goes to Release, pointing users at project migrate --apply