Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 13 additions & 13 deletions .github/workflows/publish-unsigned-preview.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,12 @@ on:
description: New prerelease tag; an existing release is never overwritten
required: true
type: string
default: v0.5.0-beta.22
default: v0.5.0-beta.23
preview_title:
description: Public prerelease title
required: true
type: string
default: Parlyn Engine v0.5.0 Beta 22 (unsigned)
default: Parlyn Engine v0.5.0 Beta 23 (unsigned)

permissions:
contents: write
Expand All @@ -24,8 +24,8 @@ jobs:
env:
CSC_IDENTITY_AUTO_DISCOVERY: false
GH_TOKEN: ${{ github.token }}
PREVIEW_TAG: ${{ inputs.preview_tag || 'v0.5.0-beta.22' }}
PREVIEW_TITLE: ${{ inputs.preview_title || 'Parlyn Engine v0.5.0 Beta 22 (unsigned)' }}
PREVIEW_TAG: ${{ inputs.preview_tag || 'v0.5.0-beta.23' }}
PREVIEW_TITLE: ${{ inputs.preview_title || 'Parlyn Engine v0.5.0 Beta 23 (unsigned)' }}

steps:
- name: Checkout
Expand Down Expand Up @@ -55,7 +55,7 @@ jobs:
throw "Expected exactly one Parlyn installer."
}

$portable = "release/Parlyn-Engine-Portable-0.5.0-beta.22-x64.zip"
$portable = "release/Parlyn-Engine-Portable-0.5.0-beta.23-x64.zip"
if (-not (Test-Path -LiteralPath "release/win-unpacked/Parlyn Engine.exe")) {
throw "Packaged Parlyn executable was not found for the portable build."
}
Expand All @@ -72,13 +72,13 @@ jobs:
shell: pwsh
run: |
@"
# Parlyn Engine v0.5.0 Beta 22
# Parlyn Engine v0.5.0 Beta 23

This unsigned Windows beta completes the Phase 2 editor command and
keyboard audit. It centralizes shortcut routing, adds Delete selection
and provides a read-only shortcut reference under View. Text fields,
modal dialogs, key repeat and Alt/AltGr input are protected from
accidental editor commands. Beta 21 passed maintainer testing; Beta 22
This unsigned Windows beta safely reopens the last saved project scene
or standalone scene after a normal application close. It restores the
project context, editor camera, 2.5D/3D view and valid selection. Missing
or moved files fall back to normal startup. Unsaved edits are never
restored by this feature. Beta 22 passed maintainer testing; Beta 23
awaits human acceptance.
The portable ZIP creates no Windows installation or uninstall entry and
remains the preferred unsigned test path.
Expand All @@ -96,7 +96,7 @@ jobs:
other public certificate provider.

Maintainer acceptance instructions:
https://github.com/WebCrew/Parlyn-Engine/blob/main/docs/MAINTAINER-ACCEPTANCE-v0.5.0-beta.22.md
https://github.com/WebCrew/Parlyn-Engine/blob/main/docs/MAINTAINER-ACCEPTANCE-v0.5.0-beta.23.md
"@ | Set-Content -LiteralPath release/PREVIEW-NOTES.md -Encoding utf8

- name: Publish GitHub pre-release
Expand All @@ -114,6 +114,6 @@ jobs:
--notes-file release/PREVIEW-NOTES.md `
--prerelease `
"$($installer[0].FullName)" `
"release/Parlyn-Engine-Portable-0.5.0-beta.22-x64.zip" `
"release/Parlyn-Engine-Portable-0.5.0-beta.23-x64.zip" `
"release/latest.yml" `
"release/SHA256SUMS.txt"
8 changes: 8 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,14 @@

## Unreleased — Foundation Stabilization

- Beta 23: reopen the last saved project scene or standalone scene after a
normal application close, including camera, 2.5D/3D view and valid selection.
- Store only a small validated local session descriptor; unavailable or invalid
files fall back to normal startup and clear the stale entry.
- Preserve Save/Discard/Cancel semantics and keep unsaved recovery outside this
step. Explicitly closing or deleting a project prevents automatic reopening.
- Recorded successful Beta 22 human Windows acceptance.

- Beta 22: centralized editor keyboard routing, added Delete selection and a
visible read-only shortcut reference under View.
- Protected text editing, modal dialogs, key repeat, composition and Alt/AltGr
Expand Down
15 changes: 11 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,17 @@

# Parlyn Engine

Beta 22 completes the Phase 2 command/keyboard audit with centralized shortcut
Beta 23 safely reopens the last saved project scene or standalone scene after a
normal application close. It restores the saved scene, project context, camera,
2.5D/3D view and valid selection while falling back to normal startup if files
were moved or removed. It never restores unsaved edits; separate autosave drafts
remain the final Phase 2 follow-up. Beta 23 awaits human Windows acceptance.

Beta 22 completed the Phase 2 command/keyboard audit with centralized shortcut
routing, Delete selection and a visible reference under **View → Keyboard
Shortcuts**. Text entry, dialogs, key repeat and Alt/AltGr combinations are
protected from accidental editor commands. Beta 22 awaits human Windows
acceptance.
protected from accidental editor commands. Beta 22 passed human Windows
acceptance on 2026-09-15.

Beta 21 added an optional **Surface** toggle in the Placement group: on Move
release, a mesh or billboard drops onto the nearest suitable lower Mesh3D face
Expand Down Expand Up @@ -68,6 +74,7 @@ The long-term direction includes:
- Frame Selected for single and multiple nodes with the F shortcut
- live Inspector synchronization during gizmo edits
- editor camera orbit, pan and zoom
- safe reopening of the last saved project scene or standalone scene
- undo/redo history foundation
- cycle-safe scene hierarchy and isolated, bounded Undo/Redo snapshots
- native scene open/save dialogs
Expand Down Expand Up @@ -252,7 +259,7 @@ See [`docs/WINDOWS-DISTRIBUTION.md`](docs/WINDOWS-DISTRIBUTION.md) for signing,
verification and the maintainer acceptance test.

Phase 1 acceptance is complete. The current Phase 2 editor candidate uses the
repeatable [`v0.5.0-beta.22 maintainer checklist`](docs/MAINTAINER-ACCEPTANCE-v0.5.0-beta.22.md).
repeatable [`v0.5.0-beta.23 maintainer checklist`](docs/MAINTAINER-ACCEPTANCE-v0.5.0-beta.23.md).

### Windows release integrity

Expand Down
37 changes: 37 additions & 0 deletions docs/EDITOR-SESSION.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# Editor Session Reopening

Beta 23 reopens the last saved working context after a normal Parlyn shutdown.
The local descriptor contains only the active project or standalone scene path,
project-relative scene path, 2.5D/3D mode, bounded camera values and up to 100
node IDs. It is stored below Electron's per-user application-data directory, not
inside the project and not in scene documents.

## Safety contract

- Scene content is always loaded from the normal saved scene file.
- Unsaved content is never placed in the session descriptor.
- Save, Discard and Cancel retain their existing meanings.
- Explicit Close Project and Delete Project clear the remembered context.
- Missing, moved, corrupt or incompatible files clear the stale descriptor and
fall back to normal startup instead of creating a retry loop.
- Selection IDs are restored only when they still exist in the loaded scene.
- Camera values, payload size and session format are validated and bounded.
- Failure to write optional session metadata must not block Save or application
shutdown.

Autosave is deliberately separate. Beta 23 cannot recover work after a crash or
power loss and does not claim to do so.

## Reference review

- [Godot EditorSettings](https://docs.godotengine.org/en/stable/classes/class_editorsettings.html)
provides explicit restoration settings for scene tabs, scripts and windows.
- [Unity workspace customization](https://docs.unity3d.com/6000.0/Documentation/Manual/CustomizingYourWorkspace.html)
provides saved/restored editor layouts.
- [Unreal layout customization](https://dev.epicgames.com/documentation/en-us/unreal-engine/layout-customization)
persists editor tabs and layout between sessions.

Parlyn adopts the familiar continuity goal but keeps this first implementation
smaller and easier to reason about: one last saved scene context, strict fallback
and no coupling between session convenience and project data. A future multi-tab
workspace can extend the versioned descriptor without changing scene files.
4 changes: 2 additions & 2 deletions docs/MAINTAINER-ACCEPTANCE-v0.5.0-beta.22.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Parlyn Engine v0.5.0 Beta 22 — Maintainer Acceptance

Status: pending human Windows acceptance. Beta 21 passed maintainer testing on
2026-09-15.
Status: passed human Windows acceptance on 2026-09-15. Beta 21 passed maintainer
testing on 2026-09-15.

Scope: final Phase 2 command and keyboard consistency audit. This release adds
Delete for the current selection, centralizes existing shortcuts and displays a
Expand Down
33 changes: 33 additions & 0 deletions docs/MAINTAINER-ACCEPTANCE-v0.5.0-beta.23.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Parlyn Engine v0.5.0 Beta 23 — Maintainer Acceptance

Status: pending human Windows acceptance. Beta 22 passed maintainer testing on
2026-09-15.

Scope: reopen the last saved project scene or standalone scene after a normal
application close. This includes the editor camera, 2.5D/3D view and valid node
selection. This release does not add autosave or recovery of unsaved edits.

1. Open a project with at least two scenes and switch to its non-startup scene.
Save it, select two nodes, choose 3D view and noticeably orbit, pan and zoom.
2. Close Parlyn normally and reopen it. The same project and scene, saved scene
contents, 3D mode, camera position and both selected nodes must return.
3. Confirm the restored scene is clean (no unsaved marker) and its saved
Undo/Redo history still behaves as before.
4. Change a node, close Parlyn and choose **Save and Continue**. Reopen Parlyn;
the saved change and last workspace state must return.
5. Change a node again, close Parlyn and choose **Discard**. Reopen Parlyn; the
previous saved value must return and the discarded value must stay gone.
6. Change a node, close Parlyn and choose **Cancel**. Parlyn must remain open
with the unsaved change intact. Save or discard it before continuing.
7. Use **Close Project**, then close and reopen Parlyn. It must show the normal
empty startup workspace and must not reopen the deliberately closed project.
8. Open and save a standalone scene outside a project. Close and reopen Parlyn;
that saved scene and its camera/view/selection must return without a project.
9. Using a disposable project copy, close Parlyn and then rename or move that
project folder in Explorer. Parlyn must fall back to normal startup without a
startup loop. A second restart must remain normal.
10. Run a short regression for scene switching, selection, Move/Rotate/Scale,
Snap, Surface, Ground, Frame Selected, Bounds, panels and Save/Open.

Only saved scene data is reopened. Unsaved recovery and separate drafts belong
to the next independently tested Phase 2 step.
5 changes: 3 additions & 2 deletions docs/ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,13 +71,14 @@ Completion evidence:
world-space authoring guides passed human acceptance with Beta 20;
- consistent commands and keyboard behavior — centralized conflict-safe command
routing, Delete selection and a visible shortcut reference implemented in
Beta 22, human acceptance pending.
Beta 22, passed human acceptance on 2026-09-15.

Explicitly accepted Phase 2 workspace follow-ups (planned, not parallel work):

1. Optional **Reopen Last Session** after the remaining placement and command
checks: reopen the last project/scene's saved state, then restore editor
view/camera/selection safely; unavailable files must fall back to normal startup.
view/camera/selection safely; unavailable files must fall back to normal
startup. Implemented in Beta 23; human acceptance pending.
2. Optional autosave to separate drafts, implemented and tested as its own step.
Normal Save commits scene data; Discard must discard the draft too. Autosave
must not silently overwrite scene files or restore intentionally discarded
Expand Down
2 changes: 1 addition & 1 deletion docs/TESTING-v0.5.0.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Parlyn Engine v0.5.0 Test Guide

For the current Phase 2 editor acceptance pass, use the shorter artifact-specific
[`MAINTAINER-ACCEPTANCE-v0.5.0-beta.22.md`](MAINTAINER-ACCEPTANCE-v0.5.0-beta.22.md)
[`MAINTAINER-ACCEPTANCE-v0.5.0-beta.23.md`](MAINTAINER-ACCEPTANCE-v0.5.0-beta.23.md)
The guide below remains the detailed feature reference.

This test focuses on the two new foundations introduced in v0.5.0: viewport transform gizmos and the module lifecycle.
Expand Down
4 changes: 2 additions & 2 deletions docs/WINDOWS-DISTRIBUTION.md
Original file line number Diff line number Diff line change
Expand Up @@ -129,8 +129,8 @@ blocker.

Phase 1 functional acceptance completed with the unsigned `v0.5.0-beta.4`
prerelease and Issue #23. The current Phase 2 editor candidate is
`v0.5.0-beta.18`; its repeatable checklist is documented in
[`MAINTAINER-ACCEPTANCE-v0.5.0-beta.18.md`](MAINTAINER-ACCEPTANCE-v0.5.0-beta.18.md).
`v0.5.0-beta.23`; its repeatable checklist is documented in
[`MAINTAINER-ACCEPTANCE-v0.5.0-beta.23.md`](MAINTAINER-ACCEPTANCE-v0.5.0-beta.23.md).

Unsigned preview releases also provide a portable ZIP for functional testing.
It creates no installation or uninstall registration and is the preferred test
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "parlyn-engine",
"version": "0.5.0-beta.22",
"version": "0.5.0-beta.23",
"private": true,
"description": "Parlyn Engine - open-source 2.5D-first game engine editor",
"author": "Parlyn Engine contributors",
Expand Down
33 changes: 33 additions & 0 deletions scripts/check-editor-session.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import assert from 'node:assert/strict';
import fs from 'node:fs/promises';
import os from 'node:os';
import path from 'node:path';
import { normalizeEditorSessionState, normalizeEditorViewState } from '../src/engine/editor/EditorSessionState.mjs';
import { clearLastSession, readLastSession, writeLastSession } from '../src/main/lastSessionFiles.mjs';

const view = normalizeEditorViewState({ mode:'3d', camera:{ target:{ x:3, y:4, z:5 }, orbit:{ yaw:1, pitch:99, distance:500 } }, selectionIds:['one', 'one', '', 7, 'two'] });
assert.deepEqual(view, { mode:'3d', camera:{ target:{ x:3, y:4, z:5 }, orbit:{ yaw:1, pitch:1.25, distance:40 } }, selectionIds:['one', 'two'] });
assert.equal(normalizeEditorSessionState(null), null);
assert.equal(normalizeEditorSessionState({ version:1, kind:'project', projectRoot:'C:\\Game' }), null);

const state = normalizeEditorSessionState({ version:1, kind:'project', projectRoot:'C:\\Game', scenePath:'scenes/Level.parlyn-scene.json', view });
assert.equal(state.kind, 'project');
assert.equal(state.scenePath, 'scenes/Level.parlyn-scene.json');

const directory = await fs.mkdtemp(path.join(os.tmpdir(), 'parlyn-session-'));
const filePath = path.join(directory, 'last-session.json');
try {
assert.equal(await readLastSession(filePath), null);
await writeLastSession(filePath, state);
assert.deepEqual(await readLastSession(filePath), state);
await fs.writeFile(filePath, '{broken', 'utf8');
await assert.rejects(readLastSession(filePath));
assert.equal(await readLastSession(filePath), null);
await writeLastSession(filePath, { version:1, kind:'scene', filePath:'C:\\Scene.json', view:{} });
await clearLastSession(filePath);
assert.equal(await readLastSession(filePath), null);
} finally {
await fs.rm(directory, { recursive:true, force:true });
}

console.log('Editor last-session validation check passed.');
3 changes: 3 additions & 0 deletions scripts/check-electron-preload.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ const methods = [
'editorReady',
'onAppCloseRequested',
'confirmAppClose',
'rememberLastSession',
'restoreLastSession',
'clearLastSession',
'createProject',
'openProject',
'openProjectScene',
Expand Down
3 changes: 3 additions & 0 deletions scripts/check-frame-selected.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,7 @@ assert.equal(renderer.frameSelection(["left", "right"]), true);
assert.deepEqual(renderer.cameraTarget.toArray().map((value) => Number(value.toFixed(6))), [0, 3, 0]);
assert.ok(renderer.orbit.distance > 5, "Multiple separated nodes should require a wider frame.");

renderer.restoreEditorCameraState({ target:{ x:7, y:8, z:9 }, orbit:{ yaw:0.8, pitch:0.4, distance:14 } });
assert.deepEqual(renderer.getEditorCameraState(), { target:{ x:7, y:8, z:9 }, orbit:{ yaw:0.8, pitch:0.4, distance:14 } });

console.log("Frame Selected camera contract check passed.");
2 changes: 1 addition & 1 deletion scripts/check-windows-distribution.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ requireValue(workflow.includes('Verify signed Windows artifacts'), 'Signed outpu
requireValue(workflow.includes('require_signing'), 'Windows workflow must distinguish signed and unsigned preflight builds.');
requireValue(!workflow.includes('BEGIN PRIVATE KEY'), 'Signing material must never be embedded in the workflow.');
requireValue(previewWorkflow.includes('Compress-Archive'), 'Unsigned previews must create a portable ZIP test artifact.');
requireValue(previewWorkflow.includes('Parlyn-Engine-Portable-0.5.0-beta.22-x64.zip'), 'Portable preview artifact has an unexpected name.');
requireValue(previewWorkflow.includes('Parlyn-Engine-Portable-0.5.0-beta.23-x64.zip'), 'Portable preview artifact has an unexpected name.');
requireValue(previewWorkflow.includes('release/win-unpacked/Parlyn Engine.exe'), 'Portable preview must verify its packaged executable.');

const gitignore = fs.readFileSync(path.join(root, '.gitignore'), 'utf8');
Expand Down
8 changes: 7 additions & 1 deletion scripts/check.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -69,9 +69,14 @@ const required = [
'src/engine/editor/SurfacePlacement.mjs',
'scripts/check-editor-shortcuts.mjs',
'src/engine/editor/EditorShortcuts.mjs',
'src/engine/editor/EditorSessionState.mjs',
'src/main/lastSessionFiles.mjs',
'scripts/check-editor-session.mjs',
'docs/MAINTAINER-ACCEPTANCE-v0.5.0-beta.21.md',
'docs/MAINTAINER-ACCEPTANCE-v0.5.0-beta.22.md',
'docs/EDITOR-COMMANDS.md',
'docs/EDITOR-SESSION.md',
'docs/MAINTAINER-ACCEPTANCE-v0.5.0-beta.23.md',
'src/main/sceneHistoryFiles.mjs',
'docs/MAINTAINER-ACCEPTANCE-v0.5.0-beta.20.md',
'src/engine/core/DocumentBounds.mjs',
Expand All @@ -88,7 +93,7 @@ for (const rel of required) {

const pkg = JSON.parse(fs.readFileSync(path.join(root, 'package.json'), 'utf8'));
if (pkg.name !== 'parlyn-engine') throw new Error('Unexpected package name.');
if (pkg.version !== '0.5.0-beta.22') throw new Error(`Unexpected package version: ${pkg.version}`);
if (pkg.version !== '0.5.0-beta.23') throw new Error(`Unexpected package version: ${pkg.version}`);

function walk(dir) {
return fs.readdirSync(dir, { withFileTypes: true }).flatMap((entry) => {
Expand Down Expand Up @@ -118,6 +123,7 @@ cp.execFileSync(process.execPath, [path.join(root, 'scripts/check-document-bound
cp.execFileSync(process.execPath, [path.join(root, 'scripts/check-scene-history-files.mjs')], { stdio: 'inherit' });
cp.execFileSync(process.execPath, [path.join(root, 'scripts/check-surface-placement.mjs')], { stdio: 'inherit' });
cp.execFileSync(process.execPath, [path.join(root, 'scripts/check-editor-shortcuts.mjs')], { stdio: 'inherit' });
cp.execFileSync(process.execPath, [path.join(root, 'scripts/check-editor-session.mjs')], { stdio: 'inherit' });
cp.execFileSync(process.execPath, [path.join(root, 'scripts/check-asset-files.cjs')], { stdio: 'inherit' });
cp.execFileSync(process.execPath, [path.join(root, 'scripts/check-windows-distribution.cjs')], { stdio: 'inherit' });

Expand Down
Loading
Loading