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
30 changes: 15 additions & 15 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.21
default: v0.5.0-beta.22
preview_title:
description: Public prerelease title
required: true
type: string
default: Parlyn Engine v0.5.0 Beta 21 (unsigned)
default: Parlyn Engine v0.5.0 Beta 22 (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.21' }}
PREVIEW_TITLE: ${{ inputs.preview_title || 'Parlyn Engine v0.5.0 Beta 21 (unsigned)' }}
PREVIEW_TAG: ${{ inputs.preview_tag || 'v0.5.0-beta.22' }}
PREVIEW_TITLE: ${{ inputs.preview_title || 'Parlyn Engine v0.5.0 Beta 22 (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.21-x64.zip"
$portable = "release/Parlyn-Engine-Portable-0.5.0-beta.22-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,14 +72,14 @@ jobs:
shell: pwsh
run: |
@"
# Parlyn Engine v0.5.0 Beta 21

This unsigned Windows beta adds optional Surface placement on Move
gizmo release. Meshes and billboards drop to a lower authored mesh
surface or the editor ground; one Undo reverses movement and placement.
It does not implement runtime physics, slope alignment or imported
terrain. Rotate, Scale, Inspector and Ground/End remain unchanged.
Beta 20 passed maintainer testing; Beta 21 awaits human acceptance.
# Parlyn Engine v0.5.0 Beta 22

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
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.21.md
https://github.com/WebCrew/Parlyn-Engine/blob/main/docs/MAINTAINER-ACCEPTANCE-v0.5.0-beta.22.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.21-x64.zip" `
"release/Parlyn-Engine-Portable-0.5.0-beta.22-x64.zip" `
"release/latest.yml" `
"release/SHA256SUMS.txt"
6 changes: 6 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@

## Unreleased — Foundation Stabilization

- 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
input from accidental editor commands while preserving Ctrl+S save.
- Recorded successful Beta 21 human Windows acceptance.

- Beta 21: optional persistent Surface toggle places the moved primary node
on a lower mesh support or editor ground when the Move gizmo is released.
- Combined placement and movement into one Undo/Redo step; excluded self,
Expand Down
15 changes: 12 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,17 @@

# Parlyn Engine

Beta 21 adds an optional **Surface** toggle in the Placement group: on Move
Beta 22 completes 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.

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
or the existing editor ground. Movement plus placement is one Undo step. It
does not affect Rotate, Scale or Inspector edits and is not physics/collision.
Beta 21 awaits human Windows acceptance; Beta 20 passed maintainer testing.
Beta 21 passed maintainer Windows acceptance on 2026-09-15.

Beta 20 fixes saved per-scene Undo/Redo history after a scene switch. It retains
Beta 19's optional scene/world bounds through **Bounds** in the View command
Expand Down Expand Up @@ -150,6 +156,9 @@ This boundary is intentional: future renderer work should remain possible withou

See [`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md).

Current editor bindings and their context rules are documented in
[`docs/EDITOR-COMMANDS.md`](docs/EDITOR-COMMANDS.md).

## 2.5D-first philosophy

Parlyn does not define 2.5D as "fake 3D". It treats it as a flexible production approach.
Expand Down Expand Up @@ -243,7 +252,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.21 maintainer checklist`](docs/MAINTAINER-ACCEPTANCE-v0.5.0-beta.21.md).
repeatable [`v0.5.0-beta.22 maintainer checklist`](docs/MAINTAINER-ACCEPTANCE-v0.5.0-beta.22.md).

### Windows release integrity

Expand Down
49 changes: 49 additions & 0 deletions docs/EDITOR-COMMANDS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# Editor Commands and Keyboard Behavior

Parlyn routes buttons and keyboard shortcuts to the same editor command
functions. The current bindings are intentionally small and fixed; custom
profiles and rebinding are not part of the Phase 2 foundation.

| Command | Shortcut |
| --- | --- |
| Select | Q / Esc |
| Move | W |
| Rotate | E |
| Scale | R |
| Frame Selected | F |
| Place on Ground | End |
| Delete Selection | Delete |
| Duplicate Node | Ctrl+D |
| Save Scene | Ctrl+S |
| Open Scene | Ctrl+O |
| Open Project | Ctrl+Shift+O |
| Undo | Ctrl+Z |
| Redo | Ctrl+Y / Ctrl+Shift+Z |

## Context rules

- Modal dialogs suspend editor shortcuts so commands cannot affect the scene
behind a dialog. Dialog controls retain their native keyboard behavior.
- Text fields retain typing, Delete and native field Undo/Redo. Ctrl+S commits
the active field and saves the scene.
- Key repeat, input-method composition and Alt/AltGr combinations do not invoke
editor commands.
- Unsupported modifier combinations do nothing; Parlyn does not silently map
them to a different command.

## Reference review

Godot, Unity and Unreal all establish discoverable, editor-wide shortcuts and
context-sensitive commands. Unity's official Shortcuts Manager documentation
explicitly separates global and contextual commands; Unreal exposes a keyboard
shortcut editor. Their mature profile/rebinding systems are useful future
references, but adding one during this audit would exceed the Phase 2 scope.

Parlyn's current improvement is a smaller, predictable contract suited to the
present editor: a single tested resolver, visible bindings inside the editor and
explicit protection for text input and modal work. This is not a claim of broader
capability than those engines.

- Unity: <https://docs.unity3d.com/6000.0/Documentation/Manual/ShortcutsManager.html>
- Unreal Engine: <https://dev.epicgames.com/documentation/en-us/unreal-engine/customizing-keyboard-shortcuts-in-unreal-engine>
- Godot editor documentation: <https://docs.godotengine.org/en/stable/tutorials/editor/index.html>
4 changes: 2 additions & 2 deletions docs/MAINTAINER-ACCEPTANCE-v0.5.0-beta.21.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Parlyn Engine v0.5.0 Beta 21 — Maintainer Acceptance

Status: pending human Windows acceptance. Beta 20 passed the maintainer's
full Bounds/history re-test and additional editor regression tests on 2026-09-13.
Status: passed human Windows acceptance on 2026-09-15. Surface placement and
the wider editor regression scope completed without a reported defect.

Scope: optional downward authoring placement on Move-gizmo release only.
No collision/physics, slope alignment, Inspector snap or asset-import changes.
Expand Down
35 changes: 35 additions & 0 deletions docs/MAINTAINER-ACCEPTANCE-v0.5.0-beta.22.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Parlyn Engine v0.5.0 Beta 22 — Maintainer Acceptance

Status: pending human Windows acceptance. 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
read-only shortcut reference. It does not add shortcut customization.

1. Open **View → Keyboard Shortcuts**. Confirm that all 13 current commands and
keys are readable. Close it with its button and then reopen it and press Esc.
2. With no dialog or Inspector field active, verify Q/Esc, W, E and R switch to
Select, Move, Rotate and Scale and match the toolbar's active states.
3. Select one node and test F, End, Ctrl+D and Delete. Frame, Ground, Duplicate
and Delete must match their buttons. Undo and Redo must restore each change.
4. Multi-select two unrelated nodes and press Delete. Both must be removed as
one Undo step; Undo and Redo must restore/remove both together.
5. Verify Ctrl+S, Ctrl+O and Ctrl+Shift+O. They must match Save Scene, Open Scene
and Open Project, including unsaved-change protection.
6. Verify Ctrl+Z, Ctrl+Y and Ctrl+Shift+Z outside text fields. Both Redo variants
must perform the same Redo command.
7. Focus the Inspector name or a numeric field. Typing Q/W/E/R/F and pressing
Delete must edit the field without activating editor commands. Field-native
Ctrl+Z must not invoke scene Undo. Ctrl+S may commit the field and save.
8. Open Add Node, Bounds, Snap Settings or another modal dialog. Q/W/E/R/F,
Delete and editor Ctrl shortcuts must not affect the scene behind it. Esc
closes dialogs according to their existing behavior.
9. Hold a mapped key long enough to create keyboard repeat and try an Alt/AltGr
combination. A single physical command press must not repeat destructive
actions and Alt combinations must not trigger editor commands.
10. Repeat a short 2.5D/3D regression: selection, Move/Rotate/Scale, Snap,
Surface, Ground, Frame Selected, panels, Bounds, Save/Open and scene switch.

The shortcut reference is descriptive only. User profiles, rebinding and a full
shortcut manager are not part of this Phase 2 audit.
6 changes: 4 additions & 2 deletions docs/ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,11 +65,13 @@ Completion evidence:
- optional Surface/Ground Snap and Grid Snap, including a keyboard command to
place the current selection on the ground — initial Ground command and End
shortcut complete; optional Move-release surface placement implemented in
Beta 21, human acceptance pending;
Beta 21, passed human acceptance on 2026-09-15;
- Frame Selected — initial single- and multi-selection camera framing complete;
- optional scene/world bounds independent from viewport size — initial validated
world-space authoring guides passed human acceptance with Beta 20;
- consistent commands and keyboard behavior.
- consistent commands and keyboard behavior — centralized conflict-safe command
routing, Delete selection and a visible shortcut reference implemented in
Beta 22, human acceptance pending.

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

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.21.md`](MAINTAINER-ACCEPTANCE-v0.5.0-beta.21.md)
[`MAINTAINER-ACCEPTANCE-v0.5.0-beta.22.md`](MAINTAINER-ACCEPTANCE-v0.5.0-beta.22.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 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.21",
"version": "0.5.0-beta.22",
"private": true,
"description": "Parlyn Engine - open-source 2.5D-first game engine editor",
"author": "Parlyn Engine contributors",
Expand Down
7 changes: 5 additions & 2 deletions scripts/check-desktop-boundaries.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,9 @@ const { resolveExistingProjectPath, resolveWritableProjectPath, resolveWritableP
assert.match(html, /id="tool-scale"[^>]+aria-label="Scale"/);
assert.match(html, /id="place-on-ground"[^>]+aria-label="Place selection on ground"/);
assert.match(html, /id="frame-selected"[^>]+aria-label="Frame Selected"[^>]+disabled/);
assert.match(html, /id="shortcuts-dialog"/);
assert.match(html, /id="show-shortcuts"/);
assert.match(html, /id="delete-node"[^>]+title="Delete selected nodes \(Delete\)"/);
assert.match(html, /id="snap-translation"[^>]+min="0\.01"[^>]+max="100"/);
assert.match(html, /id="snap-rotation"[^>]+min="1"[^>]+max="180"/);
assert.match(html, /id="snap-scale"[^>]+min="0\.01"[^>]+max="10"/);
Expand Down Expand Up @@ -122,9 +125,9 @@ const { resolveExistingProjectPath, resolveWritableProjectPath, resolveWritableP
assert.match(renderer, /parlyn\.editor\.transform-space/);
assert.match(renderer, /querySelector\("\.command-label"\)\.textContent/);
assert.match(renderer, /placeSelectionOnGround/);
assert.match(renderer, /key === "end"/);
assert.match(renderer, /renderer\.frameSelection\(\[\.\.\.selectedIds\]\)/);
assert.match(renderer, /key === "f"/);
assert.match(renderer, /resolveEditorShortcut\(event/);
assert.match(renderer, /dialog\[open\]/);
assert.match(threeRenderer, /setTransformSpace\(space\)/);
assert.match(threeRenderer, /this\.transformMode === 'scale' \? 'local' : this\.transformSpace/);
assert.match(threeRenderer, /getGroundedPosition\(nodeId, groundY = -1\.55\)/);
Expand Down
37 changes: 37 additions & 0 deletions scripts/check-editor-shortcuts.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import assert from 'node:assert/strict';
import { EDITOR_SHORTCUTS, isTextEditingTarget, resolveEditorShortcut } from '../src/engine/editor/EditorShortcuts.mjs';

const key = (value, options = {}) => resolveEditorShortcut({ key:value, ...options }, options.context);

for (const [pressed, command] of [['q','select'], ['Escape','select'], ['w','move'], ['e','rotate'], ['r','scale'], ['End','ground'], ['f','frame'], ['Delete','delete']]) {
assert.equal(key(pressed), command);
}
assert.equal(key('d', { ctrlKey:true }), 'duplicate');
assert.equal(key('s', { ctrlKey:true }), 'save');
assert.equal(key('o', { ctrlKey:true }), 'open-scene');
assert.equal(key('o', { ctrlKey:true, shiftKey:true }), 'open-project');
assert.equal(key('z', { ctrlKey:true }), 'undo');
assert.equal(key('y', { ctrlKey:true }), 'redo');
assert.equal(key('z', { ctrlKey:true, shiftKey:true }), 'redo');
assert.equal(key('s', { metaKey:true }), 'save');

for (const blocked of [
{ key:'Delete', repeat:true },
{ key:'w', isComposing:true },
{ key:'w', altKey:true },
{ key:'f', shiftKey:true },
{ key:'s', ctrlKey:true, shiftKey:true },
{ key:'w', context:{ modalOpen:true } },
{ key:'Escape', context:{ modalOpen:true } },
{ key:'z', ctrlKey:true, context:{ editing:true } },
{ key:'Delete', context:{ editing:true } }
]) assert.equal(key(blocked.key, blocked), null);

assert.equal(key('s', { ctrlKey:true, context:{ editing:true } }), 'save');
assert.equal(isTextEditingTarget({ tagName:'INPUT' }), true);
assert.equal(isTextEditingTarget({ tagName:'DIV', isContentEditable:true }), true);
assert.equal(isTextEditingTarget({ tagName:'BUTTON' }), false);
assert.equal(new Set(EDITOR_SHORTCUTS.map((entry) => entry.command)).size, EDITOR_SHORTCUTS.length);
assert.equal(EDITOR_SHORTCUTS.length, 13);

console.log('Editor shortcut routing 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.21-x64.zip'), 'Portable preview artifact has an unexpected name.');
requireValue(previewWorkflow.includes('Parlyn-Engine-Portable-0.5.0-beta.22-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
7 changes: 6 additions & 1 deletion scripts/check.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,11 @@ const required = [
'scripts/check-scene-history-files.mjs',
'scripts/check-surface-placement.mjs',
'src/engine/editor/SurfacePlacement.mjs',
'scripts/check-editor-shortcuts.mjs',
'src/engine/editor/EditorShortcuts.mjs',
'docs/MAINTAINER-ACCEPTANCE-v0.5.0-beta.21.md',
'docs/MAINTAINER-ACCEPTANCE-v0.5.0-beta.22.md',
'docs/EDITOR-COMMANDS.md',
'src/main/sceneHistoryFiles.mjs',
'docs/MAINTAINER-ACCEPTANCE-v0.5.0-beta.20.md',
'src/engine/core/DocumentBounds.mjs',
Expand All @@ -84,7 +88,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.21') throw new Error(`Unexpected package version: ${pkg.version}`);
if (pkg.version !== '0.5.0-beta.22') throw new Error(`Unexpected package version: ${pkg.version}`);

function walk(dir) {
return fs.readdirSync(dir, { withFileTypes: true }).flatMap((entry) => {
Expand Down Expand Up @@ -113,6 +117,7 @@ cp.execFileSync(process.execPath, [path.join(root, 'scripts/check-frame-selected
cp.execFileSync(process.execPath, [path.join(root, 'scripts/check-document-bounds.mjs')], { stdio: 'inherit' });
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-asset-files.cjs')], { stdio: 'inherit' });
cp.execFileSync(process.execPath, [path.join(root, 'scripts/check-windows-distribution.cjs')], { stdio: 'inherit' });

Expand Down
Loading
Loading