Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
85 commits
Select commit Hold shift + click to select a range
d9d6c2a
feat(vision): make continuous live vision and real input authoritative
venom3-byte Sep 30, 2026
5673011
fix(vision): initialize page before screencast and isolate CI vision …
venom3-byte Sep 30, 2026
c8911eb
fix(vision): normalize action endpoint response envelope
venom3-byte Sep 30, 2026
2d393bf
feat(vision): expose flexible live element discovery
venom3-byte Sep 30, 2026
96bea9b
fix(ci): make glTF validation deterministic with bundled fixture
venom3-byte Sep 30, 2026
941de40
feat(forge): add real raster 2D paint tool with Vision QA proof
venom3-byte Sep 30, 2026
2738079
fix(draw): await real 2D asset import before Vision verification
venom3-byte Sep 30, 2026
aef1307
fix(draw): bind paint dialog directly and wait for visible canvas
venom3-byte Sep 30, 2026
2c9991b
fix(draw): make paint dialog part of deterministic UI bootstrap
venom3-byte Sep 30, 2026
9fcea86
test(vision): read evaluate payload from action envelope
venom3-byte Sep 30, 2026
458cf5f
fix(vision): report real click target and harden 2D dialog opening
venom3-byte Sep 30, 2026
1808b13
test(vision): avoid duplicate click response binding
venom3-byte Sep 30, 2026
d2daf19
test(vision): capture full hit-target mismatch diagnostics
venom3-byte Sep 30, 2026
54df171
test(vision): use real wheel navigation before offscreen UI click
venom3-byte Sep 30, 2026
bd0a2e2
fix(vision): target wheel events at real screen coordinates
venom3-byte Sep 30, 2026
4949fe3
test(vision): verify asynchronous 2D import and save visual proof frames
venom3-byte Sep 30, 2026
5034b1f
test(vision): preserve visual proof images as CI artifacts
venom3-byte Sep 30, 2026
9def5a5
fix(ci): keep vision proof path inside artifact upload block
venom3-byte Sep 30, 2026
039304c
feat(audio): import real audio assets into sound components with list…
venom3-byte Sep 30, 2026
88dc845
feat(assets): recognize common browser audio formats consistently
venom3-byte Sep 30, 2026
1a6f8eb
fix(audio): load blob audio through filename-aware PlayCanvas handler
venom3-byte Sep 30, 2026
c1e057a
test(vision): create persistent proof directory before frame capture
venom3-byte Sep 30, 2026
eb78f42
feat(packaging): add real Android and Windows build pipeline
venom3-byte Sep 30, 2026
8555cd1
fix(android): pin stable Capacitor 8.5.2 dependencies
venom3-byte Sep 30, 2026
a905f14
fix(android): add TypeScript for Capacitor config
venom3-byte Sep 30, 2026
4fb2e09
fix(android): pin Capacitor to stable 8.5.2
venom3-byte Sep 30, 2026
dc37c64
test(packaging): boot packaged runtime in real Chromium
venom3-byte Sep 30, 2026
c26ab5e
feat(editor): align Forge workspace with professional asset-editor la…
venom3-byte Sep 30, 2026
4769d15
fix(editor): remove legacy overlay and harden visual chrome tests
venom3-byte Sep 30, 2026
ec35c90
fix(runtime): remove game controls from editor DOM
venom3-byte Sep 30, 2026
0ea6073
fix(import): preserve textured model previews and enforce visual mate…
venom3-byte Sep 30, 2026
a84c0fc
fix(viewport): make textured model preview visibly emissive-safe
venom3-byte Sep 30, 2026
37a4f7f
test(vision): capture imported model material diagnostics
venom3-byte Sep 30, 2026
5504bf0
fix(test): write material diagnostics with imported fs helper
venom3-byte Sep 30, 2026
11e11f9
fix(asset): normalize imported models and improve viewport material p…
venom3-byte Sep 30, 2026
e7b4d32
test(asset): require visible normalized 2D and 3D asset presentation
venom3-byte Sep 30, 2026
33d29bf
feat(import): add browser legacy 3D conversion and clearer 2D present…
venom3-byte Sep 30, 2026
743cc51
feat(import): bundle legacy model dependencies during import
venom3-byte Sep 30, 2026
672f63b
test(import): validate 2D inspection and real OBJ conversion
venom3-byte Sep 30, 2026
70df8c2
fix(import): preserve final selection after 3D asset load
venom3-byte Sep 30, 2026
da1138b
fix(test): use binary Buffer for real OBJ file injection
venom3-byte Sep 30, 2026
0dbc5c8
fix(viewport): improve 2D framing and untextured model presentation
venom3-byte Sep 30, 2026
a718596
test(viewport): require visible 2D framing and nonblack legacy materials
venom3-byte Sep 30, 2026
ed27a86
test(import): count nested render meshes for converted models
venom3-byte Sep 30, 2026
0660477
fix(2d): correct imported image front-face orientation
venom3-byte Sep 30, 2026
f8b22b9
ui(asset): add realistic car import entry point
venom3-byte Sep 30, 2026
58009bc
feat(asset): add real high-quality car remote asset importer
venom3-byte Sep 30, 2026
8276d99
test(asset): validate realistic car catalog asset end-to-end
venom3-byte Sep 30, 2026
5c1ee66
ui(asset): relabel built-in high-quality car
venom3-byte Sep 30, 2026
d43aa36
fix(asset): use Khronos high-quality PBR concept car source
venom3-byte Sep 30, 2026
f9aa37f
docs(asset): record Car Concept attribution license correctly
venom3-byte Sep 30, 2026
837d902
test(asset): validate high-quality Khronos concept car import
venom3-byte Sep 30, 2026
e575beb
feat(import): sanitize unsupported glTF preview extensions before vie…
venom3-byte Sep 30, 2026
1ba52c6
feat(asset): switch built-in demo to high-quality realistic car model
venom3-byte Sep 30, 2026
b68f816
test(asset): validate high-quality realistic external car import
venom3-byte Sep 30, 2026
8c02d0e
fix(asset): use compact realistic supercar source with verified CC-BY…
venom3-byte Sep 30, 2026
5f6d86c
ui(asset): label built-in realistic supercar importer
venom3-byte Sep 30, 2026
04a13e3
test(asset): validate compact realistic supercar source
venom3-byte Sep 30, 2026
d65ae24
feat(asset): add real Three.js fallback viewer for high-quality car a…
venom3-byte Sep 30, 2026
5f1103c
ui(asset): add integrated real 3D asset preview surface
venom3-byte Sep 30, 2026
78abb5a
feat(asset): render imported supercar through real Three.js preview f…
venom3-byte Sep 30, 2026
274c785
test(asset): validate real supercar GLB through integrated Three.js p…
venom3-byte Sep 30, 2026
7ff5f4a
fix(asset): bind realistic supercar importer to editor button
venom3-byte Sep 30, 2026
39b99ec
fix(asset): switch car fixture to smaller realistic BMW GLB
venom3-byte Sep 30, 2026
f4b2f48
test(asset): use smaller real BMW GLB fixture for car acceptance
venom3-byte Sep 30, 2026
c882230
feat(car): import high-fidelity Khronos car natively into Forge viewport
venom3-byte Sep 30, 2026
7863940
test(car): verify native high-fidelity car import and rendered viewpo…
venom3-byte Sep 30, 2026
4034923
ui(car): label built-in asset as high-fidelity and document attribution
venom3-byte Sep 30, 2026
83c0783
ui(provenance): show asset license and source state in inspector
venom3-byte Sep 30, 2026
286c184
fix(car): remove stale supercar error label
venom3-byte Sep 30, 2026
b428f0d
ci: prefer hosted Chrome and keep Chromium as fallback
venom3-byte Sep 30, 2026
b4a1510
ci: allow Playwright to use hosted Chrome channel
venom3-byte Sep 30, 2026
91d908b
feat(import): add universal asset import matrix and PBR-preserving fa…
venom3-byte Oct 1, 2026
1aa01dc
fix(import): unify model dispatch and preserve source metadata
venom3-byte Oct 1, 2026
29c0646
feat(import): route all multi-file assets through universal importer
venom3-byte Oct 1, 2026
dfddf1b
feat(ui): expose universal asset import coverage
venom3-byte Oct 1, 2026
90e725a
test(import): cover universal format matrix and PBR preservation
venom3-byte Oct 1, 2026
faed6c2
feat(import): add Three.js compatibility bridge for modern glTF and USD
venom3-byte Oct 1, 2026
5cc082f
fix(import): harden compatibility bridge fallback metadata
venom3-byte Oct 1, 2026
f1c1cb8
feat(ui): include USD family formats in universal import
venom3-byte Oct 1, 2026
fa25eb1
fix(ui): add explicit web manifest reference target
venom3-byte Oct 1, 2026
74e069f
fix(ui): declare Forge favicon and eliminate startup 404
venom3-byte Oct 1, 2026
b2e5a8f
fix(import): preserve converted asset registry references
venom3-byte Oct 1, 2026
0e0aaa3
test(car): wait for complete import provenance metadata
venom3-byte Oct 1, 2026
ccdde6a
fix(render): keep textured PBR assets visible in editor preview
venom3-byte Oct 1, 2026
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
92 changes: 92 additions & 0 deletions .github/workflows/platform-build.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
name: Forge Platform Builds
on:
push:
branches: [vision-live-direct-fix2-2026-09-30]
workflow_dispatch:
permissions:
contents: read
concurrency:
group: forge-platform-${{ github.ref }}
cancel-in-progress: true
jobs:
android:
runs-on: ubuntu-latest
timeout-minutes: 35
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
- uses: actions/setup-java@v4
with:
distribution: temurin
java-version: "21"
- name: Prepare Web runtime
run: node scripts/package-web-project.mjs --project packaging/sample-project.forge.json
- name: Install Capacitor
working-directory: packaging/android
run: npm install
- name: Create Android project
working-directory: packaging/android
run: npx cap add android
- name: Sync Web runtime
working-directory: packaging/android
run: npx cap sync android
- name: Build Debug APK
working-directory: packaging/android/android
shell: bash
run: ./gradlew assembleDebug
- name: Upload Android APK
uses: actions/upload-artifact@v4
with:
name: forge-android-debug-apk
path: packaging/android/android/app/build/outputs/apk/debug/*.apk
if-no-files-found: error

windows:
runs-on: windows-latest
timeout-minutes: 25
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
- name: Prepare Web runtime
run: node scripts/package-web-project.mjs --project packaging/sample-project.forge.json
- name: Install Electron Forge
working-directory: packaging/windows
run: npm install
- name: Build Windows package
working-directory: packaging/windows
run: npm run make
- name: Upload Windows package
uses: actions/upload-artifact@v4
with:
name: forge-windows-package
path: packaging/windows/out/make/**
if-no-files-found: error

packaging-proof:
runs-on: ubuntu-latest
needs: [android, windows]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
- name: Install test dependencies
run: npm install --no-audit --no-fund
- name: Install Chromium
run: npx playwright install --with-deps chromium
- name: Prepare Web runtime
run: node scripts/package-web-project.mjs --project packaging/sample-project.forge.json
- name: Validate packaged runtime manifest
run: node -e "const fs=require('fs'); const p=JSON.parse(fs.readFileSync('dist/forge-web/project.forge.json','utf8')); if(p.format!=='forge-project'||!fs.existsSync('dist/forge-web/index.html')||!fs.existsSync('dist/forge-web/build-info.json')) process.exit(1); console.log('Packaging manifest OK')"
- name: Browser boot proof for packaged runtime
run: npx playwright test tests/packaging-runtime.spec.mjs --project=desktop
- name: Upload packaged runtime proof
uses: actions/upload-artifact@v4
with:
name: forge-packaged-runtime-proof
path: test-results/packaged-runtime-proof.png
if-no-files-found: error
13 changes: 10 additions & 3 deletions .github/workflows/proof.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,15 @@ jobs:
with:
node-version: 22
- run: npm install
- run: npx playwright install --with-deps chromium
- name: Prepare browser runtime
shell: bash
run: |
if command -v google-chrome >/dev/null 2>&1; then
echo "PLAYWRIGHT_CHANNEL=chrome" >> "$GITHUB_ENV"
google-chrome --version
else
npx playwright install --with-deps chromium
fi
- run: node scripts/validate-syntax.mjs
- name: Upload syntax report
if: always()
Expand All @@ -35,8 +43,6 @@ jobs:
run: npm run test:professional
- name: Live vision + real input acceptance
run: npm run test:vision
- name: Live vision + real input acceptance
run: npm run test:vision
- name: Khronos glTF validation
run: npm run validate:gltf
- name: Full regression suite
Expand All @@ -50,4 +56,5 @@ jobs:
playwright-report
test-results
gltf-validation-report.json
vision-proof
if-no-files-found: ignore
223 changes: 223 additions & 0 deletions forge-draw2d.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,223 @@
(() => {
const $ = (id) => document.getElementById(id);
const state = {
canvas: null,
ctx: null,
drawing: false,
last: null,
erasing: false,
brush: 18,
color: "#ffffff",
history: [],
redo: [],
maxHistory: 24,
ready: false,
lastPngDataUrl: null,
importPromise: null
};

function dialog(){ return $("draw2dDialog"); }

function status(text){
const el = $("draw2dStatus");
if(el) el.textContent = text;
}

function capture(){
if(!state.ctx || !state.canvas) return;
state.history.push(state.ctx.getImageData(0,0,state.canvas.width,state.canvas.height));
if(state.history.length > state.maxHistory) state.history.shift();
state.redo.length = 0;
syncButtons();
}

function syncButtons(){
$("drawUndo")?.toggleAttribute("disabled", state.history.length < 2);
$("drawRedo")?.toggleAttribute("disabled", state.redo.length === 0);
$("drawEraser")?.classList.toggle("active", state.erasing);
}

function point(event){
const r=state.canvas.getBoundingClientRect();
return {
x:(event.clientX-r.left)*state.canvas.width/r.width,
y:(event.clientY-r.top)*state.canvas.height/r.height
};
}

function begin(event){
if(!state.ctx) return;
event.preventDefault();
capture();
state.drawing=true;
state.last=point(event);
state.canvas.setPointerCapture?.(event.pointerId);
state.ctx.save();
state.ctx.globalCompositeOperation=state.erasing?"destination-out":"source-over";
state.ctx.strokeStyle=state.color;
state.ctx.lineWidth=state.brush;
state.ctx.lineCap="round";
state.ctx.lineJoin="round";
state.ctx.beginPath();
state.ctx.arc(state.last.x,state.last.y,state.brush/2,0,Math.PI*2);
state.ctx.fillStyle=state.color;
if(state.erasing) state.ctx.fillStyle="rgba(0,0,0,1)";
state.ctx.fill();
state.ctx.restore();
status("Drawing…");
}

function move(event){
if(!state.drawing || !state.ctx) return;
event.preventDefault();
const p=point(event),a=state.last;
state.ctx.save();
state.ctx.globalCompositeOperation=state.erasing?"destination-out":"source-over";
state.ctx.strokeStyle=state.color;
state.ctx.lineWidth=state.brush;
state.ctx.lineCap="round";
state.ctx.lineJoin="round";
state.ctx.beginPath();
state.ctx.moveTo(a.x,a.y);
state.ctx.lineTo(p.x,p.y);
state.ctx.stroke();
state.ctx.restore();
state.last=p;
}

function end(event){
if(!state.drawing) return;
event?.preventDefault?.();
state.drawing=false;
state.last=null;
try{state.canvas.releasePointerCapture?.(event?.pointerId)}catch{}
status("Ready");
}

function clear(){
if(!state.ctx) return;
capture();
state.ctx.clearRect(0,0,state.canvas.width,state.canvas.height);
status("Canvas cleared");
}

function undo(){
if(state.history.length<=1 || !state.ctx)return;
const current=state.ctx.getImageData(0,0,state.canvas.width,state.canvas.height);
state.redo.push(current);
const previous=state.history.pop();
state.ctx.putImageData(previous,0,0);
syncButtons();
status("Undo");
}

function redo(){
if(!state.redo.length || !state.ctx)return;
const current=state.ctx.getImageData(0,0,state.canvas.width,state.canvas.height);
state.history.push(current);
const next=state.redo.pop();
state.ctx.putImageData(next,0,0);
syncButtons();
status("Redo");
}

function dataUrl(){
if(!state.canvas) return null;
return state.canvas.toDataURL("image/png");
}

function download(){
const url=dataUrl();
if(!url)return;
state.lastPngDataUrl=url;
const a=document.createElement("a");
a.href=url;
a.download="forge-drawing.png";
a.click();
status("PNG exported");
}

async function addToScene(){
if(!state.canvas || !window.Forge?.importFile) throw new Error("Forge runtime is not ready");
const blob=await new Promise(resolve=>state.canvas.toBlob(resolve,"image/png"));
if(!blob)throw new Error("PNG encoding failed");
const file=new File([blob],"forge-drawing.png",{type:"image/png"});
state.lastPngDataUrl=await new Promise((resolve,reject)=>{
const reader=new FileReader();
reader.onload=()=>resolve(String(reader.result||""));
reader.onerror=()=>reject(reader.error||new Error("PNG preview read failed"));
reader.readAsDataURL(blob);
});
await window.ForgeProduction?.assets?.storeFile?.(file);
const result=await window.Forge.importFile(file);
window.ForgeRefreshUI?.();
if(result?.record) window.Forge.select?.(result.record.id);
dialog()?.close();
status("Imported into Forge");
window.dispatchEvent(new CustomEvent("forge-drawing-imported",{detail:{name:file.name,bytes:file.size}}));
return {name:file.name,bytes:file.size,type:file.type};
}

function open(){
const d=dialog();
if(!d)throw new Error("2D paint dialog is missing");
try{
if(!d.open)d.showModal();
}catch{
d.setAttribute("open","");
d.style.display="block";
}
status("Ready");
syncButtons();
return{open:!!d.open,ready:state.ready};
}

function init(){
if(state.ready)return;
state.canvas=$("drawCanvas");
state.ctx=state.canvas?.getContext("2d");
if(!state.canvas||!state.ctx)return;
state.canvas.style.touchAction="none";
state.canvas.addEventListener("pointerdown",begin);
state.canvas.addEventListener("pointermove",move);
state.canvas.addEventListener("pointerup",end);
state.canvas.addEventListener("pointercancel",end);
state.canvas.addEventListener("pointerleave",(e)=>{if(state.drawing)move(e)});
$("draw2dOpen")?.addEventListener("click",open);
$("drawClose")?.addEventListener("click",()=>dialog()?.close());
$("drawClear")?.addEventListener("click",clear);
$("drawUndo")?.addEventListener("click",undo);
$("drawRedo")?.addEventListener("click",redo);
$("drawDownload")?.addEventListener("click",download);
$("drawAddToScene")?.addEventListener("click",()=>{
state.importPromise=addToScene().catch(e=>{
status("Import failed: "+(e?.message||String(e)));
throw e;
}).finally(()=>{state.importPromise=null});
});
$("drawColor")?.addEventListener("input",e=>{state.color=e.target.value||"#ffffff"});
$("drawSize")?.addEventListener("input",e=>{state.brush=Math.max(1,Math.min(120,Number(e.target.value)||18));$("drawSizeValue").textContent=state.brush+" px"});
$("drawEraser")?.addEventListener("click",()=>{state.erasing=!state.erasing;syncButtons()});
$("drawReset")?.addEventListener("click",()=>{state.color="#ffffff";state.brush=18;state.erasing=false;$("drawColor").value="#ffffff";$("drawSize").value="18";$("drawSizeValue").textContent="18 px";syncButtons()});
state.ctx.clearRect(0,0,state.canvas.width,state.canvas.height);
state.ctx.fillStyle="rgba(8,20,35,0)";
capture();
state.ready=true;
syncButtons();
}

window.ForgeDraw2D={
open,
clear,
undo,
redo,
download,
addToScene,
exportDataUrl:dataUrl,
status:()=>({ready:state.ready,drawing:state.drawing,brush:state.brush,color:state.color,erasing:state.erasing,history:state.history.length,redo:state.redo.length}),
get lastPngDataUrl(){ return state.lastPngDataUrl; },
waitForImport:()=>state.importPromise||Promise.resolve(true)
};

init();
})();
Loading
Loading