fix: game audio persistence and editor perf

- Keep game/campaign audio assets referenced (no prune)
- Flush pending project save on quit/switch/export to avoid losing campaignAudios
- Control: prevent game music restarts on scene changes; allow always-on controls; handle autoplay-after-scene-audio
- Editor: reduce ReactFlow churn with stable scene card map; lazy/async image decode
- Add contract/unit tests and update test script

Made-with: Cursor
This commit is contained in:
Ivan Fontosh
2026-04-22 19:06:16 +08:00
parent f823a7c05f
commit 1d051f8bf9
19 changed files with 1164 additions and 115 deletions
@@ -94,3 +94,38 @@ void test('ControlApp: радиус кисти не в блоке предпро
'Слайдер радиуса должен быть в пульте (файл: выше заголовка предпросмотра)',
);
});
void test('ControlApp: музыка разделена на сцену и кампанию', () => {
const src = readControlApp();
assert.ok(src.includes('МУЗЫКА СЦЕНЫ'));
assert.ok(src.includes('МУЗЫКА ИГРЫ'));
// при музыке сцены — кампанию ставим на паузу
assert.ok(src.includes('allowCampaignAudio'));
assert.ok(
src.includes('campaignAudioSpecKey'),
'кампания: перезагрузка аудио привязана к списку треков, не к смене сцены',
);
assert.match(src, /pause campaign\./i);
});
void test('ControlApp: загрузка камп. аудио — useEffect зависит только от api и campaignAudioSpecKey', () => {
const src = readControlApp();
const re = /\/\/ Campaign elements:[\s\S]*?useEffect\(\(\) => \{[\s\S]*?\}\s*,\s*\[([^\]]*)\]\s*\)\s*;/;
const m = re.exec(src);
assert.ok(m, 'ожидается useEffect загрузки кампании после комментария Campaign elements');
const depList = m[1];
assert.ok(depList !== undefined);
const deps = depList
.split(',')
.map((s) => s.trim())
.filter(Boolean);
assert.deepEqual(
deps,
['api', 'campaignAudioSpecKey'],
'смена сцены / allowCampaignAudio / campaignAudioRefs не должны перезапускать загрузку кампании',
);
assert.ok(!/\ballowCampaignAudio\b/.test(depList));
assert.ok(!/\bcurrentScene\b/.test(depList));
assert.ok(!/\bproject\b/.test(depList));
assert.ok(!/\bcampaignAudioRefs\b/.test(depList));
});