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;
|
||||
}
|
||||
|
||||
@@ -62,7 +62,7 @@ import { ParticlesEditor } from './particles/ParticlesEditor';
|
||||
import { ShaderEditor } from './shader/ShaderEditor';
|
||||
import { StateMachineEditor } from './statemachine/StateMachineEditor';
|
||||
import { installShortcuts } from './shortcuts';
|
||||
import type { PlayFrom } from './settings';
|
||||
import type { PlayFrom } from '../core/model/playSettings';
|
||||
import type { EditorState } from './state';
|
||||
import { PixelArtEditor } from './pixelart/PixelArtEditor';
|
||||
import { VectorArtEditor } from './vectorart/VectorArtEditor';
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
import {
|
||||
RenameProjectCommand,
|
||||
SetCollisionLayersCommand,
|
||||
SetPlayFromCommand,
|
||||
SetSymbolScaleCommand,
|
||||
} from '../core/commands/projectCommands';
|
||||
import type { ProjectStore } from '../core/store/ProjectStore';
|
||||
@@ -142,9 +143,7 @@ export class ProjectSession {
|
||||
|
||||
/**
|
||||
* Name, the scene Play starts, the symbol scale and the collision layers (#152) in one dialog
|
||||
* (#183). Name, symbol scale and layers are undo steps of their own – one each, and only for what
|
||||
* actually changed; which scene Play starts is a preference of this browser, not of the project
|
||||
* (see `settings.ts`).
|
||||
* (#183). Each is an undo step of its own – one each, and only for what actually changed.
|
||||
*/
|
||||
openProjectSettings(): ProjectSettingsDialog {
|
||||
const { project } = this.state;
|
||||
@@ -162,7 +161,7 @@ export class ProjectSession {
|
||||
this.state.execute(new SetCollisionLayersCommand(change.collisionLayers));
|
||||
if (change.symbolScale !== undefined)
|
||||
this.state.execute(new SetSymbolScaleCommand(change.symbolScale));
|
||||
this.state.setPlayFrom(change.playFrom);
|
||||
if (change.playFrom !== undefined) this.state.execute(new SetPlayFromCommand(change.playFrom));
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { layerFallback, MAX_COLLISION_LAYERS } from '../core/model/collisionLayers';
|
||||
import { clampSymbolScale, SYMBOL_SCALE_MAX, SYMBOL_SCALE_MIN } from '../core/model/symbolScale';
|
||||
import { Button, HStack, Label, Menu, NumberField, TextField, Theme, VStack } from '../ui';
|
||||
import type { PlayFrom } from './settings';
|
||||
import type { PlayFrom } from '../core/model/playSettings';
|
||||
import { FormDialog } from './FormDialog';
|
||||
|
||||
/** What the dialog hands back when Apply is pressed. */
|
||||
@@ -14,8 +14,7 @@ export interface ProjectSettings {
|
||||
|
||||
/**
|
||||
* What Apply has to carry out. Only what actually changed, so opening the dialog and closing it
|
||||
* with Apply is not an undo step: a rename and a change of the layers are one command each, while
|
||||
* the start-scene switch is a preference of this browser and no command at all.
|
||||
* with Apply is not an undo step: every setting that did change is one command.
|
||||
*
|
||||
* A blank name is not a change but a fallback to the old one – a project without a name would be
|
||||
* unfindable in the picker.
|
||||
@@ -23,7 +22,7 @@ export interface ProjectSettings {
|
||||
export function settingsChanges(
|
||||
before: ProjectSettings,
|
||||
after: ProjectSettings,
|
||||
): { name?: string; collisionLayers?: string[]; symbolScale?: number; playFrom: PlayFrom } {
|
||||
): { name?: string; collisionLayers?: string[]; symbolScale?: number; playFrom?: PlayFrom } {
|
||||
const name = after.name.trim() || before.name;
|
||||
const symbolScale = clampSymbolScale(after.symbolScale);
|
||||
return {
|
||||
@@ -32,7 +31,7 @@ export function settingsChanges(
|
||||
? {}
|
||||
: { collisionLayers: [...after.collisionLayers] }),
|
||||
...(symbolScale === before.symbolScale ? {} : { symbolScale }),
|
||||
playFrom: after.playFrom,
|
||||
...(after.playFrom === before.playFrom ? {} : { playFrom: after.playFrom }),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
+2
-2
@@ -1,8 +1,8 @@
|
||||
/**
|
||||
* The saves of persistent Variables assets (#166), kept in this browser per project. Played and
|
||||
* editor runs write to separate areas, so trying a behavior in the editor leaves the player's save
|
||||
* alone. Like the editor settings, storage is best effort: a browser that refuses `localStorage`
|
||||
* plays every run from the declared values.
|
||||
* alone. Storage is best effort: a browser that refuses `localStorage` plays every run from the
|
||||
* declared values.
|
||||
*/
|
||||
import type { SaveArea, SavedValues } from '../core/graph';
|
||||
import type { PropertyBag } from '../core/model/Properties';
|
||||
|
||||
@@ -1,52 +0,0 @@
|
||||
/**
|
||||
* The handful of things the editor remembers about *how you work*, as opposed to what you built.
|
||||
* They belong to this browser, not to the project: a second person opening the same project has
|
||||
* their own answer, and none of this may end up in a diff of the project file.
|
||||
*
|
||||
* Storage is best effort. A browser that refuses `localStorage` (private mode, a blocked origin)
|
||||
* gets the defaults and keeps working – a setting is not worth an error.
|
||||
*/
|
||||
|
||||
import { GAME_SCALE_STEPS, type GameScale } from '../viewport/view';
|
||||
|
||||
const KEY = 'game-maker.editor';
|
||||
|
||||
/** Which scene Play starts: the one named as the start scene, or whatever is open right now. */
|
||||
export type PlayFrom = 'start' | 'active';
|
||||
|
||||
export interface EditorSettings {
|
||||
playFrom: PlayFrom;
|
||||
gameScale: GameScale;
|
||||
}
|
||||
|
||||
const DEFAULTS: EditorSettings = { playFrom: 'start', gameScale: 1 };
|
||||
|
||||
let current: EditorSettings | null = null;
|
||||
|
||||
export function settings(): EditorSettings {
|
||||
if (current) return current;
|
||||
current = { ...DEFAULTS };
|
||||
try {
|
||||
const raw = window.localStorage.getItem(KEY);
|
||||
const read: unknown = raw ? JSON.parse(raw) : null;
|
||||
if (read && typeof read === 'object') {
|
||||
const { playFrom, gameScale } = read as Partial<EditorSettings>;
|
||||
if (playFrom === 'start' || playFrom === 'active') current.playFrom = playFrom;
|
||||
if (gameScale === 'auto' || gameScale === 'fit' || GAME_SCALE_STEPS.includes(gameScale as number))
|
||||
current.gameScale = gameScale as GameScale;
|
||||
}
|
||||
} catch {
|
||||
// Unreadable or refused: the defaults stand.
|
||||
}
|
||||
return current;
|
||||
}
|
||||
|
||||
export function updateSettings(change: Partial<EditorSettings>): void {
|
||||
const next = { ...settings(), ...change };
|
||||
current = next;
|
||||
try {
|
||||
window.localStorage.setItem(KEY, JSON.stringify(next));
|
||||
} catch {
|
||||
// Not stored is not a failure worth reporting; the session keeps the value either way.
|
||||
}
|
||||
}
|
||||
+15
-6
@@ -19,7 +19,7 @@ import {
|
||||
import { deserializeProject, serializeProject } from '../core/store/serialize';
|
||||
import { requestRender } from '../ui';
|
||||
import { consoleLog } from './console/log';
|
||||
import { type PlayFrom, settings, updateSettings } from './settings';
|
||||
import type { GameScale, PlayFrom } from '../core/model/playSettings';
|
||||
|
||||
export type Selection =
|
||||
| { kind: 'asset'; assetId: string }
|
||||
@@ -321,13 +321,22 @@ export class EditorState {
|
||||
}
|
||||
|
||||
get playFrom(): PlayFrom {
|
||||
return settings().playFrom;
|
||||
return this.project.playFrom;
|
||||
}
|
||||
|
||||
setPlayFrom(playFrom: PlayFrom): void {
|
||||
if (playFrom === this.playFrom) return;
|
||||
updateSettings({ playFrom });
|
||||
this.emit('mode');
|
||||
get gameScale(): GameScale {
|
||||
return this.project.gameScale;
|
||||
}
|
||||
|
||||
/**
|
||||
* Saved with the project but not an undo step (#226): Ctrl+Z undoes edits, not a look at the game.
|
||||
* While playing it goes into the saved model too, so stopping does not throw it away.
|
||||
*/
|
||||
setGameScale(scale: GameScale): void {
|
||||
if (scale === this.gameScale) return;
|
||||
this.project.gameScale = scale;
|
||||
if (this.edit) this.edit.project.gameScale = scale;
|
||||
this.setSaveStatus('dirty');
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -17,7 +17,6 @@ import {
|
||||
screenToWorld,
|
||||
type View,
|
||||
} from '../../viewport/view';
|
||||
import { settings, updateSettings } from '../settings';
|
||||
import { type Frame, WorldViewport } from './WorldViewport';
|
||||
|
||||
const LETTERBOX_COLOR = 0x000000;
|
||||
@@ -81,12 +80,12 @@ export class GameViewport extends WorldViewport {
|
||||
}
|
||||
|
||||
get scaleMode(): GameScale {
|
||||
return settings().gameScale;
|
||||
return this.state.gameScale;
|
||||
}
|
||||
|
||||
/** Remembered in this browser, like which scene Play starts. */
|
||||
/** Saved with the project, but no undo step (see `EditorState.setGameScale`). */
|
||||
setScaleMode(mode: GameScale): void {
|
||||
updateSettings({ gameScale: mode });
|
||||
this.state.setGameScale(mode);
|
||||
this.redraw();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { GAME_SCALE_STEPS, type GameScale } from '../core/model/playSettings';
|
||||
import type { PropertyBag, Vec2 } from '../core/model/Properties';
|
||||
import { cameraFrame, cameraSize, rectAround, type WorldRect } from '../core/properties/camera';
|
||||
|
||||
@@ -48,10 +49,7 @@ function clampIndex(index: number): number {
|
||||
return Math.max(0, Math.min(FREE_ZOOM_STEPS.length - 1, index));
|
||||
}
|
||||
|
||||
/** 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 { GAME_SCALE_STEPS, type GameScale };
|
||||
|
||||
/**
|
||||
* Device pixels per camera pixel for a camera picture of `sizeW`×`sizeH` in a `w`×`h` view (CSS
|
||||
|
||||
@@ -152,6 +152,8 @@ describe('CommandStack', () => {
|
||||
plugins: [],
|
||||
collisionLayers: ['Default'],
|
||||
symbolScale: 1,
|
||||
playFrom: 'start' as const,
|
||||
gameScale: 1,
|
||||
};
|
||||
const stack = new CommandStack(project);
|
||||
let failing = false;
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
/**
|
||||
* Which scene Play starts and the game view scale (#226): both live in the project now, not in the
|
||||
* browser. The start-scene switch is an undo step like every other project setting; the scale is
|
||||
* saved but never lands on the undo stack.
|
||||
*/
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { SetPlayFromCommand } from '../../src/core/commands/projectCommands';
|
||||
import { gameScaleOf, playFromOf } from '../../src/core/model/playSettings';
|
||||
import { createEmptyProject } from '../../src/core/model/Project';
|
||||
import { deserializeProject, serializeProject } from '../../src/core/store/serialize';
|
||||
import { EditorState } from '../../src/editor/state';
|
||||
|
||||
function loaded() {
|
||||
const state = new EditorState();
|
||||
state.loadProject(createEmptyProject('p', 'P'), 1);
|
||||
return state;
|
||||
}
|
||||
|
||||
describe('play settings in the project', () => {
|
||||
it('start from the start scene at 100% in a new project and in one written before there were any', () => {
|
||||
const fresh = createEmptyProject('p', 'P');
|
||||
const old = deserializeProject({ id: 'p', name: 'P', assets: [] });
|
||||
expect([fresh.playFrom, fresh.gameScale]).toEqual(['start', 1]);
|
||||
expect([old.playFrom, old.gameScale]).toEqual(['start', 1]);
|
||||
});
|
||||
|
||||
it('survive a save and a load', () => {
|
||||
const project = {
|
||||
...createEmptyProject('p', 'P'),
|
||||
playFrom: 'active' as const,
|
||||
gameScale: 'auto' as const,
|
||||
};
|
||||
const back = deserializeProject(serializeProject(project));
|
||||
expect([back.playFrom, back.gameScale]).toEqual(['active', 'auto']);
|
||||
});
|
||||
|
||||
it('read untrusted JSON as the defaults unless it is one of the choices', () => {
|
||||
expect(playFromOf({ playFrom: 'active' })).toBe('active');
|
||||
expect(playFromOf({ playFrom: 'elsewhere' })).toBe('start');
|
||||
expect(playFromOf(null)).toBe('start');
|
||||
expect(gameScaleOf({ gameScale: 'fit' })).toBe('fit');
|
||||
expect(gameScaleOf({ gameScale: 0.25 })).toBe(0.25);
|
||||
expect(gameScaleOf({ gameScale: 3 })).toBe(1);
|
||||
expect(gameScaleOf({ gameScale: '2' })).toBe(1);
|
||||
});
|
||||
|
||||
it('make the start-scene switch one undo step', () => {
|
||||
const state = loaded();
|
||||
state.execute(new SetPlayFromCommand('active'));
|
||||
expect(state.playFrom).toBe('active');
|
||||
expect(state.saveStatus).toBe('dirty');
|
||||
state.undo();
|
||||
expect(state.playFrom).toBe('start');
|
||||
});
|
||||
|
||||
it('save the game view scale without an undo step', () => {
|
||||
const state = loaded();
|
||||
state.setGameScale(4);
|
||||
expect(state.project.gameScale).toBe(4);
|
||||
expect(state.saveStatus).toBe('dirty');
|
||||
expect(state.commands.canUndo).toBe(false);
|
||||
});
|
||||
|
||||
it('keep a scale picked while playing after Stop', () => {
|
||||
const state = loaded();
|
||||
state.enterPlay();
|
||||
state.setGameScale('fit');
|
||||
expect(state.gameScale).toBe('fit');
|
||||
state.exitPlay();
|
||||
expect(state.gameScale).toBe('fit');
|
||||
expect(state.saveStatus).toBe('dirty');
|
||||
});
|
||||
});
|
||||
@@ -14,9 +14,7 @@ const before: ProjectSettings = {
|
||||
|
||||
describe('project settings', () => {
|
||||
it('reports nothing to do when nothing was touched', () => {
|
||||
expect(settingsChanges(before, { ...before, collisionLayers: ['Default', 'Enemies'] })).toEqual({
|
||||
playFrom: 'start',
|
||||
});
|
||||
expect(settingsChanges(before, { ...before, collisionLayers: ['Default', 'Enemies'] })).toEqual({});
|
||||
});
|
||||
|
||||
it('renames only when the name really differs', () => {
|
||||
@@ -55,8 +53,8 @@ describe('project settings', () => {
|
||||
expect(settingsChanges(before, { ...before, symbolScale: Number.NaN }).symbolScale).toBeUndefined();
|
||||
});
|
||||
|
||||
it('always reports the start-scene switch – it is a preference, not an undo step', () => {
|
||||
it('writes the start-scene switch only when it changed (#226)', () => {
|
||||
expect(settingsChanges(before, { ...before, playFrom: 'active' }).playFrom).toBe('active');
|
||||
expect(settingsChanges(before, { ...before, playFrom: 'start' }).playFrom).toBe('start');
|
||||
expect(settingsChanges(before, { ...before, playFrom: 'start' }).playFrom).toBeUndefined();
|
||||
});
|
||||
});
|
||||
@@ -305,6 +305,9 @@ export async function play(t) {
|
||||
const centred = ({ frame, view }) =>
|
||||
Math.abs(frame.x + frame.w / 2 - (view.x + view.w / 2)) <= 1 &&
|
||||
Math.abs(frame.y + frame.h / 2 - (view.y + view.h / 2)) <= 1;
|
||||
const undoSteps = () => json('__gm.state.commands.undoStack.length');
|
||||
await evaluate(`localStorage.removeItem('game-maker.editor')`);
|
||||
const stepsBefore = await undoSteps();
|
||||
let g = await scale();
|
||||
const crateAt100 = (await json(`__gm.gameRect('o1')`)).w;
|
||||
check(
|
||||
@@ -351,9 +354,11 @@ export async function play(t) {
|
||||
JSON.stringify({ g, fit }),
|
||||
);
|
||||
check(
|
||||
'the scale is remembered in this browser, not in the project',
|
||||
(await evaluate(`JSON.parse(localStorage.getItem('game-maker.editor')).gameScale`)) === 'auto' &&
|
||||
!(await evaluate(`JSON.stringify(__gm.state.project).includes('gameScale')`)),
|
||||
'the scale is saved with the project (#226), not in this browser, and is no undo step',
|
||||
(await json('__gm.state.project.gameScale')) === 'auto' &&
|
||||
(await evaluate(`localStorage.getItem('game-maker.editor')`)) === null &&
|
||||
(await undoSteps()) === stepsBefore,
|
||||
JSON.stringify({ steps: await undoSteps(), stepsBefore }),
|
||||
);
|
||||
g = await pickScale('100%');
|
||||
check('and 100% can be picked back', g.mode === 1 && g.scale === 1, JSON.stringify(g));
|
||||
|
||||
@@ -257,8 +257,8 @@ export async function loadScene(t) {
|
||||
);
|
||||
await stop();
|
||||
|
||||
// 35f. The switch is the way to run what you are working on instead – and it is remembered
|
||||
// between runs, because it says how you work rather than what the game does.
|
||||
// 35f. The switch is the way to run what you are working on instead – saved with the project
|
||||
// (#226), so it holds between runs.
|
||||
const chosen = await setPlayFrom('Active scene');
|
||||
await play();
|
||||
const fromActive = await playInfo();
|
||||
@@ -268,6 +268,7 @@ export async function loadScene(t) {
|
||||
(await playFrom()).mode === 'active' &&
|
||||
// The dialog held the new mode before Apply, and Apply left the start scene alone.
|
||||
chosen.playFrom === 'active' &&
|
||||
(await json('__gm.state.project.playFrom')) === 'active' &&
|
||||
(await json('__gm.state.project.startSceneId')) === 'ls-scene',
|
||||
JSON.stringify({ fromActive, chosen, switch: await playFrom() }),
|
||||
);
|
||||
|
||||
Reference in new issue
Block a user