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
24 changes: 12 additions & 12 deletions .github/workflows/publish-unsigned-preview.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,12 @@ on:
description: New prerelease tag; an existing release is never overwritten
required: true
type: string
default: v0.5.0-beta.14
default: v0.5.0-beta.15
preview_title:
description: Public prerelease title
required: true
type: string
default: Parlyn Engine v0.5.0 Beta 14 (unsigned)
default: Parlyn Engine v0.5.0 Beta 15 (unsigned)

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

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

$portable = "release/Parlyn-Engine-Portable-0.5.0-beta.14-x64.zip"
$portable = "release/Parlyn-Engine-Portable-0.5.0-beta.15-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 @@ -77,12 +77,12 @@ jobs:
shell: pwsh
run: |
@"
# Parlyn Engine v0.5.0 Beta 14
# Parlyn Engine v0.5.0 Beta 15

This unsigned Windows beta adds explicit Local and World orientation for
Move and Rotate gizmos. The selected orientation persists locally and
works with configurable snapping. Scale remains local by design, while
Inspector entry, 2.5D axis limits and scene history remain unchanged.
This unsigned Windows beta adds Ground Placement for the current
selection through a toolbar command and the End key. Placement uses the
rendered object bounds and integrates with multi-selection, Undo/Redo
and scene persistence. Automatic Surface Snap remains a later step.
The portable ZIP creates no Windows installation or uninstall entry and
remains the preferred unsigned test path.

Expand All @@ -99,7 +99,7 @@ jobs:
other public certificate provider.

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

- name: Publish GitHub pre-release
Expand All @@ -117,6 +117,6 @@ jobs:
--notes-file release/PREVIEW-NOTES.md `
--prerelease `
"$($installer[0].FullName)" `
"release/Parlyn-Engine-Portable-0.5.0-beta.14-x64.zip" `
"release/Parlyn-Engine-Portable-0.5.0-beta.15-x64.zip" `
"release/latest.yml" `
"release/SHA256SUMS.txt"
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,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.14 maintainer checklist`](docs/MAINTAINER-ACCEPTANCE-v0.5.0-beta.14.md).
repeatable [`v0.5.0-beta.15 maintainer checklist`](docs/MAINTAINER-ACCEPTANCE-v0.5.0-beta.15.md).

### Windows release integrity

Expand Down
17 changes: 17 additions & 0 deletions docs/MAINTAINER-ACCEPTANCE-v0.5.0-beta.15.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Parlyn Engine v0.5.0 Beta 15 — Maintainer Acceptance

Beta 15 adds the first Ground Placement command. It uses the rendered bounds of
the current selection so objects rest on the editor ground instead of merely
moving their pivot to a fixed Y value.

1. Open a project and move a 3D mesh clearly above the ground.
2. Select it and click **Ground**. Confirm its lowest visible point rests on the grid.
3. Move it upward again and press **End**. Confirm the same result.
4. Rotate and scale the mesh, then repeat; its current visible bounds must be used.
5. Repeat with a 2.5D sprite.
6. Multi-select several nodes at different heights and confirm each is grounded.
7. Undo and Redo the complete operation as one history step.
8. Save and reopen the scene and confirm the grounded positions persist.
9. Confirm Snap and Local/World modes still behave normally.

Automatic Surface Snap remains a later, separate step.
3 changes: 2 additions & 1 deletion docs/ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,8 @@ Completion evidence:
- transform snapping and local/world gizmo modes — complete with persistent,
configurable snapping and explicit Local/World orientation;
- optional Surface/Ground Snap and Grid Snap, including a keyboard command to
place the current selection on the ground;
place the current selection on the ground — initial Ground command and End
shortcut complete; automatic surface placement remains;
- Frame Selected;
- optional scene/world bounds independent from viewport size;
- consistent commands and keyboard behavior.
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.14.md`](MAINTAINER-ACCEPTANCE-v0.5.0-beta.14.md)
[`MAINTAINER-ACCEPTANCE-v0.5.0-beta.15.md`](MAINTAINER-ACCEPTANCE-v0.5.0-beta.15.md)
and record the result in Issue #29. 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.14`; its repeatable checklist is documented in
[`MAINTAINER-ACCEPTANCE-v0.5.0-beta.14.md`](MAINTAINER-ACCEPTANCE-v0.5.0-beta.14.md).
`v0.5.0-beta.15`; its repeatable checklist is documented in
[`MAINTAINER-ACCEPTANCE-v0.5.0-beta.15.md`](MAINTAINER-ACCEPTANCE-v0.5.0-beta.15.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.14",
"version": "0.5.0-beta.15",
"private": true,
"description": "Parlyn Engine - open-source 2.5D-first game engine editor",
"author": "Parlyn Engine contributors",
Expand Down
4 changes: 4 additions & 0 deletions scripts/check-desktop-boundaries.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ const { resolveExistingProjectPath, resolveWritableProjectPath, resolveWritableP
assert.match(html, /id="snap-toggle"[^>]+aria-pressed="false"/);
assert.match(html, /id="snap-settings-dialog"/);
assert.match(html, /id="transform-space"[^>]+aria-pressed="false"/);
assert.match(html, /id="place-on-ground"/);
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 @@ -103,8 +104,11 @@ const { resolveExistingProjectPath, resolveWritableProjectPath, resolveWritableP
assert.match(renderer, /normalizeTransformSpace/);
assert.match(renderer, /renderer\.setTransformSpace\(transformSpace\.space\)/);
assert.match(renderer, /parlyn\.editor\.transform-space/);
assert.match(renderer, /placeSelectionOnGround/);
assert.match(renderer, /key === "end"/);
assert.match(threeRenderer, /setTransformSpace\(space\)/);
assert.match(threeRenderer, /this\.transformMode === 'scale' \? 'local' : this\.transformSpace/);
assert.match(threeRenderer, /getGroundedPosition\(nodeId, groundY = -1\.55\)/);
assert.match(renderer, /parlyn\.editor\.workspace-layout/);
for (const [selector, column] of [['#hierarchy-panel', 1], ['#hierarchy-resizer', 2], ['.center', 3], ['#inspector-resizer', 4], ['#inspector-panel', 5]]) {
assert.match(styles, new RegExp(`${selector.replace('.', '\\.') }\\{grid-column:${column}\\}`), `${selector} must keep a stable workspace grid column.`);
Expand Down
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.14-x64.zip'), 'Portable preview artifact has an unexpected name.');
requireValue(previewWorkflow.includes('Parlyn-Engine-Portable-0.5.0-beta.15-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
3 changes: 2 additions & 1 deletion scripts/check.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ const required = [
'docs/MAINTAINER-ACCEPTANCE-v0.5.0-beta.12.md',
'docs/MAINTAINER-ACCEPTANCE-v0.5.0-beta.13.md',
'docs/MAINTAINER-ACCEPTANCE-v0.5.0-beta.14.md',
'docs/MAINTAINER-ACCEPTANCE-v0.5.0-beta.15.md',
'build/icon.ico',
'.github/workflows/windows-installer.yml',
'scripts/check-smart-systems.mjs',
Expand All @@ -72,7 +73,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.14') throw new Error(`Unexpected package version: ${pkg.version}`);
if (pkg.version !== '0.5.0-beta.15') throw new Error(`Unexpected package version: ${pkg.version}`);

function walk(dir) {
return fs.readdirSync(dir, { withFileTypes: true }).flatMap((entry) => {
Expand Down
1 change: 1 addition & 0 deletions src/engine/render/RendererBackend.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export class RendererBackend {
updateNodeTransform() { throw new Error('updateNodeTransform() not implemented'); }
setTransformSnapping() { throw new Error('setTransformSnapping() not implemented'); }
setTransformSpace() { throw new Error('setTransformSpace() not implemented'); }
getGroundedPosition() { throw new Error('getGroundedPosition() not implemented'); }
setView() { throw new Error('setView() not implemented'); }
dispose() {}
}
9 changes: 9 additions & 0 deletions src/engine/render/ThreeRenderer.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,15 @@ export class ThreeRenderer extends RendererBackend {
this.#attachTransformControls();
}

getGroundedPosition(nodeId, groundY = -1.55) {
const object = this.nodeObjects.get(nodeId);
if (!object || !Number.isFinite(groundY)) return null;
object.updateWorldMatrix(true, true);
const bounds = new THREE.Box3().setFromObject(object);
if (bounds.isEmpty() || !Number.isFinite(bounds.min.y)) return null;
return { x:object.position.x, y:object.position.y + groundY - bounds.min.y, z:object.position.z };
}

#attachTransformControls() {
if (!this.transformControls) return;
this.transformControls.detach();
Expand Down
30 changes: 30 additions & 0 deletions src/renderer/app.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -658,6 +658,30 @@ async function bootstrap() {
selectById(duplicate.id);
status.textContent = `Duplicated: ${duplicate.name}`;
}
function placeSelectionOnGround() {
const nodes = [...selectedIds].map((id) => scene.findById(id)).filter(Boolean);
if (!nodes.length) {
status.textContent = "Select at least one node to place on the ground.";
return;
}
const before = sceneSnapshot();
let placed = 0;
for (const node of nodes) {
const position = renderer.getGroundedPosition(node.id);
if (!position) continue;
node.position = position;
renderer.updateNodeTransform(node);
placed += 1;
}
if (!placed || JSON.stringify(before) === JSON.stringify(sceneSnapshot())) {
status.textContent = placed ? "Selection is already on the ground." : "The selection cannot be placed on the ground.";
return;
}
pushHistory(before, placed === 1 ? `Ground ${nodes[0].name}` : `Ground ${placed} nodes`);
if (selectedIds.size === 1) populateInspector();
setDirty(true);
status.textContent = placed === 1 ? `Placed on ground: ${nodes[0].name}` : `Placed ${placed} nodes on ground`;
}
function nodePath(node) {
const names = [];
for (let current = node; current && current !== scene.root; current = current.parent) names.unshift(current.name);
Expand Down Expand Up @@ -1051,6 +1075,7 @@ async function bootstrap() {
$("tool-scale").addEventListener("click", () => setTransformMode("scale"));
$("snap-toggle").addEventListener("click", toggleTransformSnapping);
$("transform-space").addEventListener("click", toggleTransformSpace);
$("place-on-ground").addEventListener("click", placeSelectionOnGround);
$("snap-settings").addEventListener("click", openTransformSnapSettings);
$("cancel-snap-settings").addEventListener("click", () => $("snap-settings-dialog").close());
$("save-snap-settings").addEventListener("click", saveTransformSnapSettings);
Expand Down Expand Up @@ -1169,6 +1194,11 @@ async function bootstrap() {
setTransformMode("scale");
return;
}
if (key === "end") {
event.preventDefault();
placeSelectionOnGround();
return;
}
}
if (!(event.ctrlKey || event.metaKey)) return;
if (key === "d" && !editing) {
Expand Down
1 change: 1 addition & 0 deletions src/renderer/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@
<button id="snap-toggle" class="tool-button snap-button" type="button" aria-pressed="false" title="Transform Snap: 0.5 units / 15° / 0.1 scale">Snap</button>
<button id="snap-settings" class="icon-button snap-settings-button" type="button" title="Configure Transform Snap" aria-label="Configure Transform Snap">⋯</button>
<button id="transform-space" class="tool-button space-button" type="button" aria-pressed="false" title="Transform orientation: world">World</button>
<button id="place-on-ground" class="tool-button" type="button" title="Place selection on ground (End)">Ground</button>
<span class="toolbar-divider"></span>
<button id="view-25" class="active">2.5D View</button>
<button id="view-3d">3D View</button>
Expand Down
Loading