Keep play start and game view scale in the project (#226)
Both settings lived in the browser's localStorage, so they did not travel with the project and differed per machine. They now belong to the project like the symbol scale. There is no migration, old projects load with the defaults. - New module src/core/model/playSettings.ts reads playFrom and gameScale on load. Unknown or missing values fall back to the start scene and 100%. - src/editor/settings.ts is gone. - Play starts in the project settings dialog is an undo step (SetPlayFromCommand). It is only recorded on a real change. - The game view scale is saved with the project but is no undo step, so Ctrl+Z never undoes a zoom. A scale picked during play is written to the stored project and survives Stop. - Save states of persistent Variables assets (saves.ts) stay in the browser on purpose. They are runtime data of the game, not settings. Tests: - 6 new unit tests in tests/editor/play-settings.test.ts: defaults, save and load, broken JSON, undo of Play starts, scale without undo step, scale picked during play survives Stop. - e2e 03g checks the value in the project, nothing in localStorage and no new undo step. 35f also checks project.playFrom. No new checks, the sum stays at 1375. - Counter checks: no write to the stored project in play, no read on load, and an undo step for the scale each fail exactly their test. - ./check.sh: 158 files, 2332 tests, CHECK EXIT 0 - e2e 1-17,28,30,35,38-40,46: ALL PASSED, 752 checks Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
5afb70a92e
commit
4b7e8614a8
17 files changed
+180
-88
No files matched your search
@@ -1,4 +1,5 @@
|
||||
import { collisionLayersOf } from '../model/collisionLayers';
|
||||
import type { PlayFrom } from '../model/playSettings';
|
||||
import type { Project } from '../model/Project';
|
||||
import { clampSymbolScale } from '../model/symbolScale';
|
||||
import type { Command } from './Command';
|
||||
@@ -56,3 +57,20 @@ export class SetSymbolScaleCommand implements Command {
|
||||
project.symbolScale = this.previous;
|
||||
}
|
||||
}
|
||||
|
||||
/** Which scene Play starts (#226): saved with the project like the rest of its settings. */
|
||||
export class SetPlayFromCommand implements Command {
|
||||
readonly label = 'Play starts';
|
||||
private previous: PlayFrom = 'start';
|
||||
|
||||
constructor(private readonly playFrom: PlayFrom) {}
|
||||
|
||||
execute(project: Project): void {
|
||||
this.previous = project.playFrom;
|
||||
project.playFrom = this.playFrom;
|
||||
}
|
||||
|
||||
undo(project: Project): void {
|
||||
project.playFrom = this.previous;
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
type SceneAsset,
|
||||
} from './Asset';
|
||||
import { DEFAULT_LAYER_NAME } from './collisionLayers';
|
||||
import { DEFAULT_GAME_SCALE, DEFAULT_PLAY_FROM, type GameScale, type PlayFrom } from './playSettings';
|
||||
import { DEFAULT_SYMBOL_SCALE } from './symbolScale';
|
||||
|
||||
/** 2: a placed container carries no copy of its template. */
|
||||
@@ -42,6 +43,10 @@ export interface Project {
|
||||
* written before there was one reads as 1, the size the icons always had.
|
||||
*/
|
||||
symbolScale: number;
|
||||
/** Which scene Play starts (see `playSettings.ts`). */
|
||||
playFrom: PlayFrom;
|
||||
/** The scale the game view draws the camera at. Saved, but not an undo step: it is how you look. */
|
||||
gameScale: GameScale;
|
||||
}
|
||||
|
||||
export function createEmptyProject(id: string, name: string): Project {
|
||||
@@ -55,6 +60,8 @@ export function createEmptyProject(id: string, name: string): Project {
|
||||
plugins: [],
|
||||
collisionLayers: [DEFAULT_LAYER_NAME],
|
||||
symbolScale: DEFAULT_SYMBOL_SCALE,
|
||||
playFrom: DEFAULT_PLAY_FROM,
|
||||
gameScale: DEFAULT_GAME_SCALE,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
/**
|
||||
* How the project is played in the editor (#226): which scene Play starts and the scale of the
|
||||
* game view. Both are saved with the project, so whoever opens it gets the same answer, and a
|
||||
* project written before they were saved reads as the defaults.
|
||||
*/
|
||||
|
||||
/** Which scene Play starts: the one named as the start scene, or whatever is open right now. */
|
||||
export type PlayFrom = 'start' | 'active';
|
||||
|
||||
export const DEFAULT_PLAY_FROM: PlayFrom = 'start';
|
||||
|
||||
/** How the game view sizes the camera frame: a fixed factor, the largest step that fits, or fitted. */
|
||||
export type GameScale = 'auto' | 'fit' | number;
|
||||
|
||||
export const GAME_SCALE_STEPS = [0.25, 0.5, 1, 2, 4];
|
||||
|
||||
export const DEFAULT_GAME_SCALE: GameScale = 1;
|
||||
|
||||
export function isGameScale(value: unknown): value is GameScale {
|
||||
return value === 'auto' || value === 'fit' || GAME_SCALE_STEPS.includes(value as number);
|
||||
}
|
||||
|
||||
/** Read off untrusted JSON; anything else is the default. */
|
||||
export function playFromOf(raw: unknown): PlayFrom {
|
||||
const value = (raw as { playFrom?: unknown } | null)?.playFrom;
|
||||
return value === 'start' || value === 'active' ? value : DEFAULT_PLAY_FROM;
|
||||
}
|
||||
|
||||
/** Read off untrusted JSON; anything that is not one of the choices is the default. */
|
||||
export function gameScaleOf(raw: unknown): GameScale {
|
||||
const value = (raw as { gameScale?: unknown } | null)?.gameScale;
|
||||
return isGameScale(value) ? value : DEFAULT_GAME_SCALE;
|
||||
}
|
||||
@@ -39,6 +39,7 @@ import type {
|
||||
import { parseCellKey } from '../model/tilemap';
|
||||
import { pluginsOf } from './projectPlugins';
|
||||
import { collisionLayersOf } from '../model/collisionLayers';
|
||||
import { gameScaleOf, playFromOf } from '../model/playSettings';
|
||||
import { symbolScaleOf } from '../model/symbolScale';
|
||||
import { decodeCells, encodeCells } from './tilemapCells';
|
||||
import { isAssetType } from '../model/AssetTypes';
|
||||
@@ -250,6 +251,8 @@ export function deserializeProject(raw: unknown, fallbackId = ''): Project {
|
||||
plugins: pluginsOf(r),
|
||||
collisionLayers: collisionLayersOf(r),
|
||||
symbolScale: symbolScaleOf(r),
|
||||
playFrom: playFromOf(r),
|
||||
gameScale: gameScaleOf(r),
|
||||
};
|
||||
return project;
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user