From 74b9c606613c0f5c99381868722f26a4ac716b70 Mon Sep 17 00:00:00 2001 From: MelekTaus Date: Sat, 26 Sep 2026 15:05:10 +0200 Subject: [PATCH] Stop marking older sprites as changed after loading (#232) Loading filled every missing asset property with its `default`. For Sort with parent that is `false`, while the gold mark and Reset compare against `initial`, which is `true`. So every sprite, text and particle source saved before #232 counted as changed once loaded, and each save wrote the `false` into the project file. - normalizeBag leaves a missing key missing when it has its own `initial`. It still reads as `false`, so older projects draw as before, but they no longer count as changed. - e2e module 4 failed in CI on three checks since 8b44f97. Its Reset click hit the extra Reset of Sort with parent instead of Height. - The #232 test deleted the key on the object instead of going through the load path. It now checks that loading keeps the key missing and marks nothing. serialize.test.ts no longer expects the filled value. Tests: - ./check.sh: 167 files, 2435 tests, CHECK EXIT 0 - e2e 1-5 chained: ALL PASSED, 141 checks (before the fix the same three module 4 checks failed locally, as in CI) - Counter-check: without the fix the extended test fails Co-Authored-By: Claude Opus 5.5 --- src/core/store/serialize.ts | 5 ++++- tests/core/serialize.test.ts | 1 - tests/viewport/sort-with-parent.test.ts | 3 +++ 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/core/store/serialize.ts b/src/core/store/serialize.ts index 8adc27c6..450468d2 100644 --- a/src/core/store/serialize.ts +++ b/src/core/store/serialize.ts @@ -84,6 +84,7 @@ import { import { type FlatItem, makeFlatItem } from '../properties/listItems'; import { coerceValue, + initialOf, isValidFieldName, isValidVariableName, type PropertyDef, @@ -771,7 +772,9 @@ function normalizeBag(raw: unknown, schema: PropertySchema, sparse: boolean): Pr for (const def of schema) { const value = r[def.key]; if (value === undefined || value === null) { - if (!sparse) bag[def.key] = def.default; + // A key with its own `initial` stays missing: written out, the old reading would count as + // a deviation from what a new asset starts with, and mark every older asset gold. + if (!sparse && initialOf(def) === def.default) bag[def.key] = def.default; continue; } bag[def.key] = coerceValue(def, value as PropertyValue); diff --git a/tests/core/serialize.test.ts b/tests/core/serialize.test.ts index 1c0f1e1d..fd08acb3 100644 --- a/tests/core/serialize.test.ts +++ b/tests/core/serialize.test.ts @@ -177,7 +177,6 @@ describe('serialize', () => { rotation: 0, mirrorX: false, mirrorY: false, - sortWithParent: false, width: 10, height: 100, color: 0xffffff, diff --git a/tests/viewport/sort-with-parent.test.ts b/tests/viewport/sort-with-parent.test.ts index ca613f09..aa079ab0 100644 --- a/tests/viewport/sort-with-parent.test.ts +++ b/tests/viewport/sort-with-parent.test.ts @@ -65,6 +65,9 @@ describe('a child sorted with its parent (#232)', () => { delete weapon.props[SORT_WITH_PARENT_KEY]; const loaded = findAsset(roundTrip(s.f.project), s.weaponId)!; expect(resolveAssetProps(s.f.project, loaded)[SORT_WITH_PARENT_KEY]).toBe(false); + // Loading must not write the old reading out, or every older asset would be marked. + expect(loaded.props[SORT_WITH_PARENT_KEY]).toBeUndefined(); + expect(isModified(s.f.project, loaded, SORT_WITH_PARENT_KEY)).toBe(false); }); it('marks an asset only once it is switched off, and Reset switches it on again', () => {