Breaking changes: what we promise about your project
Draft for sign-off, 25 Oct 2026. The commander signs off the engine rules.
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
- 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.
- Upgrading is one command, and so is undoing it.
argus project migrate --dry-run <project>shows what would change.--applychanges it and first keeps a backup of every file it writes.--restoreputs the backup back.
- 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.
- 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
| change | breaking? | what ships with it |
|---|---|---|
| a new optional field, component, rule warning or API member | no | a release note (added) |
| a fix that changes behaviour only where it was wrong | no | a release note (fixed); allowed in a patch |
| a new default for a field you did not set, which changes how a scene looks or plays | yes, behaviour | a 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 changed | yes, format | a 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 error | yes, validation | a 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 resolving | yes, content | a 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 changed | yes, API | a 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 (docs/PLAN-RELEASES.md §2.1) 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)
schemaVersiongoes 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.- 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).
- The step is in the registry: The upgrade report,
--applyand--restorepick 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.
- the manifest step goes in
- 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.rsdoes this with a test-only step. - 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. - It has a release note:
node scripts/release/changelog.mjs new <slug> --kind breaking --area <area> --text "...", then add amigration:line to the entry. The changelog check refuses a breaking entry without one, andfinalizerefuses a release page whose Migrating section leaves it out. Write it for someone carrying a real project across. - The commander signs off every
schemaVersionbump: it is a contract change.
Upgrading your project (for users)
- Commit or copy your project first.
--applyrefuses to write a file that has uncommitted changes in git (--allow-dirtyoverrides), and it keeps its own backup anyway. 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.argus project migrate --apply <project>. Its steps: A second--applyreports "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.
- it writes the backup to
- 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. - 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.
--forcerestores anyway, and first saves your newer bytes in another backup.
- 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. It never moves either one down: a project a later engine series saved is refused (project-engine-saved-with-newer), a later patch's savedWith is kept, and so is an engineVersion of a later 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)
schemaVersionis 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,--applywith backup and rollback, and--restorewith--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):
--applypoints 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--restoreremoves 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/…orbuiltin:voice/…failsasset-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.
- the launcher's Upgrade dialog still says converting files "comes in a later version"; it is to be wired to
Decisions for sign-off
| # | question | proposed |
|---|---|---|
| 1 | Support window | the last two release series; older projects step through an intermediate engine |
| 2 | Should 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 |
| 3 | Behaviour 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 |
| 4 | Does 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 |
| 5 | The 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 paths | decided (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 |