@pix3/cli 1.6.3 → 2.0.0-alpha.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -201
- package/README.md +722 -788
- package/dist/index.js +557 -421
- package/dist/runtime-types.json +1 -1
- package/dist/smoke/prebuilt/{build-7MIXIMSZ.mjs → build-7REMSA2S.mjs} +52 -52
- package/dist/smoke/prebuilt/chunk-4DGG2BKR.mjs +2 -0
- package/dist/smoke/prebuilt/chunk-YLBMUJ2H.mjs +238 -0
- package/dist/smoke/prebuilt/runtime.mjs +1 -1
- package/dist/smoke/prebuilt/smoke-worker.mjs +211 -2
- package/dist/smoke/prebuilt/tree-defaults.mjs +1 -1
- package/dist/validate/prebuilt/{build-AUGKXER3.js → build-ADF5EEE2.js} +52 -52
- package/dist/validate/prebuilt/bundle-main.js +223 -18
- package/dist/validate/prebuilt/chunk-VIHIF4G2.js +238 -0
- package/dist/validate/prebuilt/runtime.js +1 -1
- package/kit/files/.claude/skills/pix3-editor/SKILL.md +200 -0
- package/kit/files/.claude/skills/pix3-nodes/SKILL.md +13 -15
- package/kit/files/.claude/skills/pix3-nodes/reference.md +77 -71
- package/kit/files/.claude/skills/pix3-scene-format/SKILL.md +38 -41
- package/kit/files/.claude/skills/pix3-scene-format/pix3anim.md +185 -0
- package/kit/files/.claude/skills/pix3-scene-format/project-files.md +244 -0
- package/kit/files/.claude/skills/pix3-scene-format/reference.md +28 -16
- package/kit/files/.claude/skills/pix3-scripts/SKILL.md +34 -24
- package/kit/files/.claude/skills/pix3-scripts/reference.md +68 -141
- package/kit/files/.claude/skills/pix3-verify/SKILL.md +65 -326
- package/kit/files/.claude/skills/pix3-verify/reference.md +2 -14
- package/kit/files/AGENTS.md +76 -84
- package/kit/files/design/tests/bots/pix3-test-bot.d.ts +89 -0
- package/kit/kit.json +8 -5
- package/package.json +62 -54
- package/templates/2d/files/scenes/main.pix3scene +31 -0
- package/templates/2d/template.yaml +11 -0
- package/templates/3d/files/scenes/main.pix3scene +50 -0
- package/templates/3d/template.yaml +11 -0
- package/templates/base/files/README.md +39 -0
- package/templates/base/files/gitignore +11 -0
- package/templates/base/files/index.html +23 -0
- package/templates/base/files/package.json +28 -0
- package/templates/base/files/src/main.ts +3 -0
- package/templates/base/files/tsconfig.json +17 -0
- package/templates/base/files/vite.config.ts +10 -0
- package/dist/smoke/prebuilt/chunk-CBFYKDS7.mjs +0 -2
- package/dist/smoke/prebuilt/chunk-VJHJ5BCO.mjs +0 -239
- package/dist/validate/prebuilt/chunk-CFQFEI52.js +0 -239
- package/templates/empty-2d/files/README.md +0 -20
- package/templates/empty-2d/files/scenes/main.pix3scene +0 -68
- package/templates/empty-2d/files/sprites/pix3-logo.png +0 -0
- package/templates/empty-2d/template.yaml +0 -7
- package/templates/empty-3d/files/README.md +0 -20
- package/templates/empty-3d/files/scenes/main.pix3scene +0 -108
- package/templates/empty-3d/files/sprites/pix3-logo.png +0 -0
- package/templates/empty-3d/template.yaml +0 -7
- package/templates/idea-blank/files/README.md +0 -22
- package/templates/idea-blank/files/scenes/main.pix3scene +0 -37
- package/templates/idea-blank/template.yaml +0 -10
- package/templates/minigame-2d/files/README.md +0 -44
- package/templates/minigame-2d/files/design/tests/reachability.json +0 -1
- package/templates/minigame-2d/files/design/tests/routines/mute-music.json +0 -44
- package/templates/minigame-2d/files/scenes/main.pix3scene +0 -84
- package/templates/minigame-2d/files/scenes/menu.pix3scene +0 -118
- package/templates/minigame-2d/files/scenes/ui/settings-window.pix3scene +0 -138
- package/templates/minigame-2d/files/scripts/GameFlow.ts +0 -133
- package/templates/minigame-2d/files/scripts/MenuFlow.ts +0 -217
- package/templates/minigame-2d/files/scripts/SettingsWindow.ts +0 -198
- package/templates/minigame-2d/template.yaml +0 -10
- package/templates/playable-2d/files/README.md +0 -30
- package/templates/playable-2d/files/design/recipe.md +0 -83
- package/templates/playable-2d/files/design/tests/reachability.json +0 -4
- package/templates/playable-2d/files/design/tests/routines/intro-to-cta.json +0 -32
- package/templates/playable-2d/files/scenes/main.pix3scene +0 -111
- package/templates/playable-2d/files/scenes/ui/end-screen.pix3scene +0 -91
- package/templates/playable-2d/files/scenes/ui/intro.pix3scene +0 -65
- package/templates/playable-2d/files/scripts/CtaButton.ts +0 -55
- package/templates/playable-2d/files/scripts/GameFlow.ts +0 -256
- package/templates/playable-2d/files/sprites/pix3-logo.png +0 -0
- package/templates/playable-2d/template.yaml +0 -9
- package/templates/playable-3d/files/README.md +0 -32
- package/templates/playable-3d/files/design/recipe.md +0 -127
- package/templates/playable-3d/files/design/tests/reachability.json +0 -4
- package/templates/playable-3d/files/design/tests/routines/intro-to-cta.json +0 -32
- package/templates/playable-3d/files/scenes/main.pix3scene +0 -230
- package/templates/playable-3d/files/scenes/ui/end-screen.pix3scene +0 -91
- package/templates/playable-3d/files/scenes/ui/intro.pix3scene +0 -65
- package/templates/playable-3d/files/scripts/CtaButton.ts +0 -55
- package/templates/playable-3d/files/scripts/GameFlow.ts +0 -309
- package/templates/playable-3d/files/scripts/GameRules.ts +0 -290
- package/templates/playable-3d/files/scripts/ScoreHud.ts +0 -113
- package/templates/playable-3d/template.yaml +0 -9
- package/templates/recipe-arena-2d/files/README.md +0 -48
- package/templates/recipe-arena-2d/files/design/recipe.md +0 -127
- package/templates/recipe-arena-2d/files/design/tests/reachability.json +0 -4
- package/templates/recipe-arena-2d/files/design/tests/routines/terminal-retry.json +0 -23
- package/templates/recipe-arena-2d/files/scenes/main.pix3scene +0 -292
- package/templates/recipe-arena-2d/files/scenes/menu.pix3scene +0 -100
- package/templates/recipe-arena-2d/files/scenes/prefabs/hazard.pix3scene +0 -42
- package/templates/recipe-arena-2d/files/scenes/prefabs/pickup.pix3scene +0 -45
- package/templates/recipe-arena-2d/files/scenes/ui/result.pix3scene +0 -85
- package/templates/recipe-arena-2d/files/scripts/GameRules.ts +0 -368
- package/templates/recipe-arena-2d/files/scripts/MenuFlow.ts +0 -106
- package/templates/recipe-arena-2d/files/scripts/PlayerController.ts +0 -151
- package/templates/recipe-arena-2d/files/scripts/ScoreHud.ts +0 -94
- package/templates/recipe-arena-2d/files/scripts/Spawner.ts +0 -214
- package/templates/recipe-arena-2d/files/scripts/TouchRules.ts +0 -174
- package/templates/recipe-arena-2d/files/sprites/ph-avatar.png +0 -0
- package/templates/recipe-arena-2d/files/sprites/ph-hazard.png +0 -0
- package/templates/recipe-arena-2d/files/sprites/ph-pickup.png +0 -0
- package/templates/recipe-arena-2d/template.yaml +0 -10
- package/templates/recipe-blank-2d/files/README.md +0 -61
- package/templates/recipe-blank-2d/files/design/recipe.md +0 -148
- package/templates/recipe-blank-2d/files/design/tests/reachability.json +0 -4
- package/templates/recipe-blank-2d/files/design/tests/routines/restart.json +0 -27
- package/templates/recipe-blank-2d/files/design/tests/routines/terminal-retry.json +0 -23
- package/templates/recipe-blank-2d/files/scenes/main.pix3scene +0 -224
- package/templates/recipe-blank-2d/files/scenes/ui/result.pix3scene +0 -85
- package/templates/recipe-blank-2d/files/scripts/GameRules.ts +0 -350
- package/templates/recipe-blank-2d/files/scripts/ScoreHud.ts +0 -110
- package/templates/recipe-blank-2d/files/sprites/ph-bg.png +0 -0
- package/templates/recipe-blank-2d/files/sprites/ph-capsule.png +0 -0
- package/templates/recipe-blank-2d/files/sprites/ph-circle.png +0 -0
- package/templates/recipe-blank-2d/files/sprites/ph-orb.png +0 -0
- package/templates/recipe-blank-2d/files/sprites/ph-ring.png +0 -0
- package/templates/recipe-blank-2d/files/sprites/ph-star.png +0 -0
- package/templates/recipe-blank-2d/template.yaml +0 -10
- package/templates/recipe-bouncer-2d/files/README.md +0 -81
- package/templates/recipe-bouncer-2d/files/design/recipe.md +0 -146
- package/templates/recipe-bouncer-2d/files/design/tests/reachability.json +0 -4
- package/templates/recipe-bouncer-2d/files/design/tests/routines/terminal-retry.json +0 -23
- package/templates/recipe-bouncer-2d/files/scenes/main.pix3scene +0 -615
- package/templates/recipe-bouncer-2d/files/scenes/menu.pix3scene +0 -191
- package/templates/recipe-bouncer-2d/files/scenes/ui/result.pix3scene +0 -87
- package/templates/recipe-bouncer-2d/files/scripts/BallBody.ts +0 -298
- package/templates/recipe-bouncer-2d/files/scripts/GameRules.ts +0 -377
- package/templates/recipe-bouncer-2d/files/scripts/MenuFlow.ts +0 -109
- package/templates/recipe-bouncer-2d/files/scripts/PaddleController.ts +0 -112
- package/templates/recipe-bouncer-2d/files/scripts/ScoreHud.ts +0 -94
- package/templates/recipe-bouncer-2d/files/scripts/TouchRules.ts +0 -263
- package/templates/recipe-bouncer-2d/files/scripts/ball-collision.ts +0 -307
- package/templates/recipe-bouncer-2d/files/sprites/ph-ball.png +0 -0
- package/templates/recipe-bouncer-2d/files/sprites/ph-bg.png +0 -0
- package/templates/recipe-bouncer-2d/files/sprites/ph-bumper.png +0 -0
- package/templates/recipe-bouncer-2d/files/sprites/ph-paddle.png +0 -0
- package/templates/recipe-bouncer-2d/template.yaml +0 -10
- package/templates/recipe-grid-3d/files/README.md +0 -22
- package/templates/recipe-grid-3d/files/design/recipe.md +0 -110
- package/templates/recipe-grid-3d/files/scenes/main.pix3scene +0 -169
- package/templates/recipe-grid-3d/files/scenes/menu.pix3scene +0 -100
- package/templates/recipe-grid-3d/files/scenes/ui/result.pix3scene +0 -105
- package/templates/recipe-grid-3d/files/scripts/GridBoard.ts +0 -319
- package/templates/recipe-grid-3d/files/scripts/GridRules.ts +0 -278
- package/templates/recipe-grid-3d/files/scripts/MenuFlow.ts +0 -106
- package/templates/recipe-grid-3d/files/scripts/ScoreHud.ts +0 -94
- package/templates/recipe-grid-3d/template.yaml +0 -10
- package/templates/recipe-tapper-2d/files/README.md +0 -39
- package/templates/recipe-tapper-2d/files/design/recipe.md +0 -132
- package/templates/recipe-tapper-2d/files/design/tests/reachability.json +0 -4
- package/templates/recipe-tapper-2d/files/design/tests/routines/terminal-retry.json +0 -23
- package/templates/recipe-tapper-2d/files/scenes/main.pix3scene +0 -269
- package/templates/recipe-tapper-2d/files/scenes/menu.pix3scene +0 -100
- package/templates/recipe-tapper-2d/files/scenes/prefabs/hazard.pix3scene +0 -42
- package/templates/recipe-tapper-2d/files/scenes/prefabs/target.pix3scene +0 -45
- package/templates/recipe-tapper-2d/files/scenes/ui/result.pix3scene +0 -85
- package/templates/recipe-tapper-2d/files/scripts/GameRules.ts +0 -368
- package/templates/recipe-tapper-2d/files/scripts/MenuFlow.ts +0 -106
- package/templates/recipe-tapper-2d/files/scripts/ScoreHud.ts +0 -94
- package/templates/recipe-tapper-2d/files/scripts/Spawner.ts +0 -214
- package/templates/recipe-tapper-2d/files/scripts/TouchRules.ts +0 -192
- package/templates/recipe-tapper-2d/files/sprites/ph-hazard.png +0 -0
- package/templates/recipe-tapper-2d/files/sprites/ph-target.png +0 -0
- package/templates/recipe-tapper-2d/template.yaml +0 -10
|
@@ -1,24 +1,229 @@
|
|
|
1
1
|
import { createRequire as __pix3CreateRequire } from 'node:module'; const require = __pix3CreateRequire(import.meta.url);
|
|
2
|
-
import{
|
|
3
|
-
`)[0]??s.message,line:s.linePos?.[0]?.line}))};let r=s=>s===void 0?void 0:e.linePos(s).line;return{data:t.toJS({maxAliasCount:1e3}),lineOf(s){let o=t.contents,i=r(o?.range?.[0]);for(let a of s)if((0,_.isMap)(o)){let c=o.items.find(l=>(0,_.isScalar)(l.key)?l.key.value===a:l.key===a);if(!c)return i;i=r((0,_.isScalar)(c.key)?c.key.range?.[0]:void 0)??i,o=c.value}else if((0,_.isSeq)(o)&&typeof a=="number"){let c=o.items[a];if(!c)return i;i=r(c.range?.[0])??i,o=c}else return i;return i}}},p=n=>typeof n=="object"&&n!==null&&!Array.isArray(n);var xt=new Set(["node_modules",".yalc",".git",".pix3","dist",".vite",".cache"]),C=".pix3scene",k=class{root;files;fileSet;textCache=new Map;constructor(e){this.root=Oe(e),this.files=Ce(this.root,this.root).sort(),this.fileSet=new Set(this.files)}has(e){return this.fileSet.has(e)}absolute(e){return Q(this.root,e)}relativeOf(e){let t=Ae(this.root,Oe(e));return t.startsWith("..")||St(t)?null:t.split(De).join("/")}readText(e){let t=this.textCache.get(e);if(t!==void 0)return t;let r=Te(this.absolute(e),"utf8");return this.textCache.set(e,r),r}scenes(){return this.files.filter(e=>e.endsWith(C))}},Ce=(n,e)=>{let t=[],r;try{r=Et(e,{withFileTypes:!0})}catch{return t}for(let s of r)if(s.isDirectory()){if(xt.has(s.name))continue;t.push(...Ce(n,Q(e,s.name)))}else s.isFile()&&t.push(Ae(n,Q(e,s.name)).split(De).join("/"));return t},T=n=>{let e=n.trim();if(e.length===0)return null;let t=/^([a-z][a-z0-9+.-]*):/i.exec(e)?.[1]?.toLowerCase();if(t&&t!=="res")return null;let s=(t==="res"?e.replace(/^res:\/\//i,""):e).replace(/[?#].*$/,""),o=[];for(let i of s.replace(/\\/g,"/").split("/"))if(!(i===""||i===".")){if(i===".."){if(o.length===0)return null;o.pop();continue}o.push(_t(i))}return o.length===0?null:o.join("/")},_t=n=>{try{return decodeURIComponent(n)}catch{return n}};var je=(n,e)=>yt("sha256").update(Te(n.absolute(e))).digest("hex"),Le=n=>{if(!n.has("pix3project.yaml"))return{};try{let e=(0,Ie.parse)(n.readText("pix3project.yaml"));return p(e)?typeof e.targetPlatform=="string"?{targetPlatform:e.targetPlatform}:{}:{}}catch{return{}}};var wt=(n,e)=>{if(n===e)return 0;if(n.length===0)return e.length;if(e.length===0)return n.length;let t=Array.from({length:e.length+1},(r,s)=>s);for(let r=1;r<=n.length;r++){let s=[r];for(let o=1;o<=e.length;o++){let i=t[o-1]+(n[r-1]===e[o-1]?0:1);s[o]=Math.min(s[o-1]+1,t[o]+1,i)}t=s}return t[e.length]},b=(n,e)=>{let t=n.toLowerCase(),r,s=Number.POSITIVE_INFINITY;for(let i of e){if(i===n)continue;if(i.toLowerCase()===t)return i;let a=wt(t,i.toLowerCase());a<s&&(s=a,r=i)}let o=n.length>=10?3:2;return s<=o?r:void 0};var We="http://www.w3.org/2000/svg",Pt=/^\s*(\d+(?:\.\d+)?|\.\d+)(?:e[+-]?\d+)?\s*(px|pt|pc|mm|cm|in|q|em|ex)?\s*$/i,Rt=n=>{let e=n.replace(/^\uFEFF/,"");for(;;)if(e=e.replace(/^\s+/,""),e.startsWith("<?")){let r=e.indexOf("?>");if(r<0)return null;e=e.slice(r+2)}else if(e.startsWith("<!--")){let r=e.indexOf("-->");if(r<0)return null;e=e.slice(r+3)}else if(/^<!DOCTYPE/i.test(e)){let r=/^<!DOCTYPE[^>[]*(\[[\s\S]*?\])?[^>]*>/i.exec(e);if(!r)return null;e=e.slice(r[0].length)}else break;let t=/^<((?:[A-Za-z_][\w.-]*:)?svg)(?=[\s/>])[^>]*>/.exec(e);return t?t[0]:null},j=(n,e)=>{let r=new RegExp(`\\s${e.replace(":","\\:")}\\s*=\\s*(?:"([^"]*)"|'([^']*)')`,"i").exec(n);return r?r[1]??r[2]:void 0},Ye=n=>{if(n===void 0)return!1;let e=Pt.exec(n);return e!==null&&Number(e[1])>0},vt=n=>{if(n===void 0)return!1;let e=n.trim().split(/[\s,]+/).map(Number);return e.length===4&&e.every(Number.isFinite)&&e[2]>0&&e[3]>0},bt=n=>{let e=[],t=r=>{let s=r.trim();return s!==""&&!s.startsWith("#")&&!/^data:/i.test(s)};for(let r of n.matchAll(/\s(?:xlink:)?href\s*=\s*(?:"([^"]*)"|'([^']*)')/gi)){let s=r[1]??r[2]??"";t(s)&&e.push(`href="${s.trim()}"`)}for(let r of n.matchAll(/url\(\s*(['"]?)([^)'"]*)\1\s*\)/gi))t(r[2])&&e.push(`url(${r[2].trim()})`);for(let r of n.matchAll(/@import\s+([^;]+);?/gi))e.push(`@import ${r[1].trim()}`);return[...new Set(e)]},Fe='start from <svg xmlns="http://www.w3.org/2000/svg" width="W" height="H" viewBox="0 0 W H">',$t=n=>{let e=Rt(n);if(!e)return[{code:"E_SVG_INVALID",message:"the file does not start with an <svg> root element, so no browser decodes it.",fix:Fe}];let t=[],r=/^<([A-Za-z_][\w.-]*):svg/.exec(e)?.[1],s=j(e,r?`xmlns:${r}`:"xmlns");s!==We&&t.push({code:"E_SVG_INVALID",message:`the <svg> root has ${s===void 0?"no xmlns":`xmlns="${s}"`}; as an image (which every texture load is) the browser refuses to decode it.`,fix:`add xmlns="${We}" to the <svg> tag`});let o=Ye(j(e,"width")),i=Ye(j(e,"height")),a=vt(j(e,"viewBox"));!o&&!i?t.push(a?{code:"W_SVG_VIEWBOX_ONLY",message:"the <svg> root has a viewBox but no width/height (or only % / style sizes): browsers give it no natural size \u2014 Chrome rasterises it into a 300x150 box with the art letterboxed inside, so the sprite has the wrong size and aspect.",fix:"add width/height in px to the <svg> tag (the viewBox size, or the size the node draws it at)"}:{code:"E_SVG_NO_SIZE",message:"the <svg> root has no width/height and no viewBox: it rasterises into a default 300x150 box and the art is cropped to it.",fix:`add width/height in px and a matching viewBox \u2014 ${Fe}`}):o!==i&&!a&&t.push({code:"E_SVG_NO_SIZE",message:`the <svg> root has only ${o?"width":"height"} and no viewBox: the missing dimension defaults to ${o?"150":"300"}px.`,fix:`add ${o?"height":"width"} in px (and a viewBox) to the <svg> tag`});let c=bt(n);return c.length>0&&t.push({code:"W_SVG_EXTERNAL_REF",message:`references outside the file (${c.slice(0,3).join(", ")}${c.length>3?", \u2026":""}) never load when an SVG is drawn as an image \u2014 not in the editor, not in play mode, not in the export.`,fix:"inline the shapes or embed the image as a data: URI; use generic font families (sans-serif), no webfonts"}),t},Ue=new WeakMap,Ve=(n,e)=>{let t=Ue.get(n);t||(t=new Map,Ue.set(n,t));let r=t.get(e);if(!r){let s;try{s=n.readText(e)}catch{s=""}r=$t(s),t.set(e,r)}return r},Me=n=>/\.svg$/i.test(n);var y=n=>n===null?"null":Array.isArray(n)?`an array of ${n.length}`:typeof n=="string"?`the string ${JSON.stringify(n)}`:typeof n=="object"?"a mapping":`${typeof n} ${String(n)}`,te=n=>typeof n=="number"&&Number.isFinite(n),kt=/^(#([0-9a-f]{3}|[0-9a-f]{4}|[0-9a-f]{6}|[0-9a-f]{8})|(rgb|rgba|hsl|hsla)\([^)]*\)|[a-z]+)$/i,Nt=n=>{let e=n.ui?.options;return Array.isArray(e)?e:p(e)?Object.values(e):[]},ee=(n,e)=>{if(Array.isArray(n)){if(n.length<e.length)return`expected ${e.length} numbers [${e.join(", ")}], got ${y(n)}`;let t=n.slice(0,e.length).findIndex(r=>!te(r));return t===-1?null:`component ${t} is ${y(n[t])}, not a number`}if(p(n)){let t=e.find(r=>r in n&&!te(n[r]));return t?`${t} is ${y(n[t])}, not a number`:e.some(r=>r in n)?null:`expected {${e.join(", ")}}`}return`expected [${e.join(", ")}], got ${y(n)}`},S=(n,e)=>({code:"E_PROPERTY_TYPE",message:n,fix:e}),Ge=n=>n.ui?.editor?.endsWith("-resource")===!0||n.ui?.resourceType!==void 0,re=(n,e,t=n.name)=>{switch(n.type){case"number":{if(!te(e)){let i=typeof e=="string"&&e.trim()!==""&&Number.isFinite(Number(e));return S(`${t} must be a number, got ${y(e)}.`,i?`write ${t}: ${Number(e)} (no quotes)`:void 0)}let r=n.validation?.validate(e);if(r===!1||typeof r=="string")return{code:"E_PROPERTY_RANGE",message:`${t} is ${e}: ${typeof r=="string"?r:"rejected by the property's validation"}.`};let s=n.ui?.min,o=n.ui?.max;return s!==void 0&&e<s||o!==void 0&&e>o?{code:"W_PROPERTY_RANGE",message:`${t} is ${e}, outside the inspector's ${s??"-\u221E"}..${o??"\u221E"} range; it loads as written, but the inspector cannot produce it.`}:null}case"boolean":return typeof e=="boolean"?null:S(`${t} must be true or false, got ${y(e)}.`,e==="true"||e==="false"?`write ${t}: ${e} (no quotes)`:void 0);case"string":return e===null||typeof e=="string"?null:S(`${t} must be a string, got ${y(e)}.`,typeof e=="number"||typeof e=="boolean"?`quote it: ${t}: "${String(e)}"`:void 0);case"color":return e===null?null:typeof e!="string"?S(`${t} must be a colour string like "#ff8800", got ${y(e)}.`,typeof e=="number"?`write ${t}: "#${e.toString(16).padStart(6,"0")}"`:void 0):kt.test(e.trim())?null:S(`${t} is not a colour: ${JSON.stringify(e)}.`,'use "#rrggbb"');case"enum":case"select":{let r=Nt(n);if(r.length===0||r.includes(e))return null;let s=typeof e=="string"?r.find(o=>typeof o=="string"&&o.toLowerCase()===e.toLowerCase()):void 0;return{code:"E_PROPERTY_ENUM",message:`${t} is ${y(e)}; allowed: ${r.map(o=>JSON.stringify(o)).join(", ")}.`,fix:s!==void 0?`write ${t}: ${String(s)}`:void 0}}case"vector2":{let r=ee(e,["x","y"]);return r?S(`${t}: ${r}.`,`write ${t}: [x, y]`):null}case"vector3":case"euler":{let r=ee(e,["x","y","z"]);return r?S(`${t}: ${r}.`,`write ${t}: [x, y, z]`):null}case"vector4":{let r=ee(e,["x","y","z","w"]);return r?S(`${t}: ${r}.`,`write ${t}: [x, y, z, w]`):null}case"node":return e===null||typeof e=="string"?null:S(`${t} must be a node id (string), got ${y(e)}.`);case"object":return Ge(n)?ne("texture",e,t):null;default:return null}},ne=(n,e,t,r)=>{switch(n){case"texture":return e===null||typeof e=="string"||p(e)&&typeof e.url=="string"?null:S(`${t} must be a texture: { type: texture, url: res://\u2026 } or a path string, got ${y(e)}.`);case"resource-path":return e===null||typeof e=="string"?null:S(`${t} must be a path string (res://\u2026), got ${y(e)}.`);case"array":return Array.isArray(e)?null:S(`${t} must be a list, got ${y(e)}.`);case"record":return p(e)?null:S(`${t} must be a mapping, got ${y(e)}.`);case"enum":return r===void 0||r.includes(e)?null:{code:"E_PROPERTY_ENUM",message:`${t} is ${y(e)}; allowed: ${r.join(", ")}.`};default:return re({name:t,type:n,getValue:()=>{},setValue:()=>{}},e,t)}},Be=Ge;var Ot=ve,He=new Map,se=n=>{let e=He.get(n.type);if(e)return e;let t=Ot[n.classExport],r=typeof t?.getPropertySchema=="function"?t.getPropertySchema().properties:[];return He.set(n.type,r),r},L=class{constructor(e){this.project=e}cache=new Map;get(e){let t=this.cache.get(e);if(t)return t;let r;if(!this.project.has(e))r={ok:!1,reason:"missing"};else{let s=Z(this.project.readText(e));if("errors"in s||!p(s.data))r={ok:!1,reason:"unparsable"};else{let o=s.data.root;r={ok:!0,roots:Array.isArray(o)?o:[]}}}return this.cache.set(e,r),r}instanceTargets(e){let t=this.get(e);if(!t.ok)return[];let r=[],s=o=>{if(p(o)){if(typeof o.instance=="string"){let i=T(o.instance);i&&r.push(i)}Array.isArray(o.children)&&o.children.forEach(s)}};return t.roots.forEach(s),r}findCycle(e,t){if(t.includes(e))return[...t.slice(t.indexOf(e)),e];let r=[...t,e];for(let s of this.instanceTargets(e)){let o=this.findCycle(s,r);if(o)return o}return null}effectiveDefinition(e,t=new Set){if(typeof e.instance!="string")return e;let r=T(e.instance);if(!r||t.has(r))return null;let s=this.get(r);return!s.ok||s.roots.length!==1||!p(s.roots[0])?null:this.effectiveDefinition(s.roots[0],new Set([...t,r]))}localIdOf(e){let t=this.effectiveDefinition(e);return t&&typeof t.id=="string"?t.id:null}},Ke=/^res:\/\//i,W=class{constructor(e,t,r){this.env=e;this.file=t;this.parsed=r}diagnostics=[];references=new Set;usesUserComponents=!1;svgChecked=new Set;seenIds=new Map;report(e){let{at:t,...r}=e;this.diagnostics.push(m({...r,file:this.file,path:t?J(t):r.path,line:t?this.parsed.lineOf(t):void 0}))}run(){let e=this.parsed.data;if(!p(e)){this.report({code:"E_SHAPE",message:`A scene must be a YAML mapping with a root: list, got ${e===null?"an empty document":typeof e}.`,fix:"start the file with `version: 1.0.0` and `root:`",at:[]});return}for(let t of Object.keys(e))B.includes(t)||this.report({code:"E_SHAPE",message:`"${t}" is not a scene document key (allowed: ${B.join(", ")}); the loader ignores it.`,fix:t==="nodes"||t==="children"?"rename it to root:":void 0,at:[t]});if(e.version===void 0?this.report({code:"W_LEGACY_VERSION",message:`The scene has no version:; the current format is ${v}.`,fix:`add version: ${v}`,at:[]}):String(e.version)!==v&&this.report({code:"W_LEGACY_VERSION",message:`Scene format version ${String(e.version)} is not the current ${v}; the loader has no migrations and reads it as ${v}.`,fix:`set version: ${v}`,at:["version"]}),e.metadata!==void 0&&e.metadata!==null&&!p(e.metadata)&&this.report({code:"E_SHAPE",message:"metadata: must be a mapping.",at:["metadata"]}),e.root===void 0||e.root===null){this.report({code:"E_SHAPE",message:"The scene has no root: list \u2014 it would load as an empty scene.",fix:"add root: with at least one node",at:[]});return}if(!Array.isArray(e.root)){this.report({code:"E_SHAPE",message:`root: must be a list of nodes, got ${p(e.root)?"a mapping":typeof e.root}.`,fix:"write each root node as a list item: root:\\n - id: \u2026",at:["root"]});return}e.root.forEach((t,r)=>this.checkNode(t,["root",r]))}checkNode(e,t){if(!p(e)){this.report({code:"E_SHAPE",message:`A node must be a mapping with at least id: and type:, got ${e===null?"null":typeof e}.`,at:t});return}let r=typeof e.id=="string"&&e.id.trim().length>0?e.id:void 0;if(r===void 0)this.report({code:"E_SHAPE",message:e.id===void 0?"The node has no id: \u2014 the loader accepts it with an undefined id, and a second one collides.":`id: must be a non-empty string, got ${JSON.stringify(e.id)}.`,fix:"give every node a unique string id:",at:e.id===void 0?t:[...t,"id"]});else{let a=this.seenIds.get(r);a!==void 0?this.report({code:"E_DUPLICATE_ID",nodeId:r,message:`Duplicate node id "${r}" (first used at ${a}); the loader rejects the scene.`,fix:"rename one of them",at:[...t,"id"]}):this.seenIds.set(r,J(t))}let s=r;for(let a of Object.keys(e))H.includes(a)||this.report({code:"E_SHAPE",nodeId:s,message:`"${a}" is not a node key (allowed: ${H.join(", ")}); the loader ignores it.`,fix:"if it is a property, move it under properties:",at:[...t,a]});for(let a of["name","type"])e[a]!==void 0&&typeof e[a]!="string"&&this.report({code:"E_SHAPE",nodeId:s,message:`${a}: must be a string.`,at:[...t,a]});e.groups!==void 0&&!(Array.isArray(e.groups)&&e.groups.every(a=>typeof a=="string"))&&this.report({code:"E_SHAPE",nodeId:s,message:"groups: must be a list of strings.",at:[...t,"groups"]}),e.metadata!==void 0&&e.metadata!==null&&!p(e.metadata)&&this.report({code:"E_SHAPE",nodeId:s,message:"metadata: must be a mapping.",at:[...t,"metadata"]});let o=e.properties;o!=null&&!p(o)&&this.report({code:"E_SHAPE",nodeId:s,message:`properties: must be a mapping, got ${Array.isArray(o)?"a list":typeof o}.`,at:[...t,"properties"]});let i=p(o)?o:{};if(e.instance!==void 0){this.checkInstanceNode(e,s,i,t);return}this.checkNodeType(e,s,i,t),this.checkComponents(e.components,s,t),e.children!==void 0&&e.children!==null&&(Array.isArray(e.children)?e.children.forEach((a,c)=>this.checkNode(a,[...t,"children",c])):this.report({code:"E_SHAPE",nodeId:s,message:`children: must be a list of nodes, got ${p(e.children)?"a mapping":typeof e.children}.`,at:[...t,"children"]}))}checkNodeType(e,t,r,s){let o=typeof e.type=="string"?e.type:void 0,i=o===void 0?"Node3D":G(o);if(i==="Layout2D"){this.report({code:"E_UNKNOWN_NODE_TYPE",nodeId:t,message:"Layout2D is no longer supported; the loader rejects the scene.",fix:"use a Group2D (or Node2D) with layout: anchors",at:[...s,"type"]});return}if(i===null){let c=o?ge(o):void 0;this.report({code:"E_UNKNOWN_NODE_TYPE",nodeId:t,message:he(String(e.name??t??"?"),String(o)),fix:c?`type: ${c}`:void 0,at:[...s,"type"]});return}if(o!==void 0&&o!==i){let c=M(o)===M(i);this.report({code:"W_TYPE_CASE",nodeId:t,message:c?`type: ${o} loads as ${i}, but only because the loader forgives case and separators; the editor rewrites it on save.`:`type: ${o} is a read-compat alias of ${i}; the editor rewrites it on save.`,fix:`type: ${i}`,at:[...s,"type"]})}let a=z(i);a&&this.checkNodeProperties(a,r,t,[...s,"properties"])}checkNodeProperties(e,t,r,s){let o=se(e);for(let[i,a]of Object.entries(t)){let c=[...s,i],l=q(e,o,i);switch(l.kind){case"schema":this.checkSchemaValue(l.property,a,r,c,i);break;case"write-only":this.report({code:"W_WRITE_ONLY_PROPERTY",nodeId:r,message:`${e.type}.${i} is saved by the editor but not read back by the loader, so it has no effect after a reload.`,at:c});break;case"extra":this.checkRule(e,l.rule,a,r,c,i);break;case"relocated":this.report({code:"E_UNKNOWN_PROPERTY",nodeId:r,message:`${e.type} does not read "${i}" here \u2014 on disk it lives at ${l.diskPath}. As written it loads, is saved back, and does nothing.`,fix:`move it to properties.${l.diskPath}`,at:c});break;case"not-stored":this.report({code:"E_UNKNOWN_PROPERTY",nodeId:r,message:`"${i}" is not stored under properties: \u2014 ${l.reason}.`,fix:i==="id"||i==="name"||i==="type"||i==="groups"?`move it up to the node entry (${i}:)`:"remove it",at:c});break;case"unknown":{let d=[...o.filter(f=>q(e,o,f.name).kind==="schema").map(f=>f.name),...Object.keys(e.extras)],u=b(i,d);this.report({code:"E_UNKNOWN_PROPERTY",nodeId:r,message:`${e.type} has no property "${i}"; the loader keeps it, saves it back, and nothing reads it.`,fix:u?`did you mean ${u}?`:void 0,at:c});break}}this.checkStrings(a,c,r)}}checkRule(e,t,r,s,o,i){t.preferred&&this.report({code:"W_LEGACY_KEY",nodeId:s,message:`${i} is a read-compat spelling; the editor saves it as ${t.preferred}.`,fix:`rename to ${t.preferred}`,at:o});let a=se(e),c=t.schemaName?a.find(l=>l.name===t.schemaName):void 0;if(c)this.checkSchemaValue(c,r,s,o,i);else if(t.kind){let l=ne(t.kind,r,i,t.options);l&&this.report({...l,nodeId:s,at:o}),!l&&(t.kind==="texture"||t.kind==="resource-path")&&this.checkResourceValue(r,s,o)}if(t.nested&&p(r))for(let[l,d]of Object.entries(r)){let u=t.nested[l],f=[...o,l];if(!u){let h=b(l,Object.keys(t.nested));this.report({code:"E_UNKNOWN_PROPERTY",nodeId:s,message:`${i}.${l} is not read by the loader (known: ${Object.keys(t.nested).join(", ")}).`,fix:h?`did you mean ${i}.${h}?`:void 0,at:f});continue}this.checkRule(e,u,d,s,f,`${i}.${l}`)}}checkSchemaValue(e,t,r,s,o){let i=re(e,t,o);if(i){this.report({...i,nodeId:r,at:s});return}Be(e)&&this.checkResourceValue(t,r,s)}checkResourceValue(e,t,r){let s=typeof e=="string"?e:p(e)&&typeof e.url=="string"?e.url:null;!s||Ke.test(s)||s.trim()===""||this.checkReference(s,t,p(e)?[...r,"url"]:r)}checkReference(e,t,r){let s=T(e);if(!s)return;if(this.references.add(s),this.env.project.has(s)){Me(s)&&this.checkSvg(s,e,t,r);return}let o=this.env.project.files.find(i=>i.toLowerCase()===s.toLowerCase());this.report({code:"E_MISSING_RESOURCE",nodeId:t,message:`${e} does not exist in the project.`,fix:o?`the file is res://${o} (case differs)`:void 0,at:r})}checkSvg(e,t,r,s){if(!this.svgChecked.has(e)){this.svgChecked.add(e);for(let o of Ve(this.env.project,e))this.report({code:o.code,nodeId:r,message:`${t}: ${o.message}`,fix:o.fix,at:s})}}checkStrings(e,t,r){if(typeof e=="string"){Ke.test(e)&&this.checkReference(e,r,t);let s=[...t].reverse().find(o=>typeof o=="string");s&&xe(s)&&Se(e)&&this.report({code:"E_EMOJI_AS_ART",nodeId:r,message:`${s} is ${e.trim()} \u2014 nothing but emoji, i.e. a picture standing in for art. It draws differently on every platform, cannot be recoloured, atlased or animated, and is a hollow box where the font lacks it.`,fix:"use a Sprite2D with a generated sprite (or a ColorRect2D placeholder); an emoji inside a sentence is fine",at:t});return}if(Array.isArray(e))e.forEach((s,o)=>this.checkStrings(s,[...t,o],r));else if(p(e))for(let[s,o]of Object.entries(e))this.checkStrings(o,[...t,s],r)}checkComponents(e,t,r){if(e==null)return;let s=[...r,"components"];if(!Array.isArray(e)){this.report({code:"E_SHAPE",nodeId:t,message:`components: must be a list, got ${p(e)?"a mapping":typeof e}.`,fix:"write each component as a list item: components:\\n - type: core:Rotate",at:s});return}e.forEach((o,i)=>this.checkComponent(o,t,[...s,i]))}checkComponent(e,t,r){if(!p(e)){this.report({code:"E_SHAPE",nodeId:t,message:"A component must be a mapping with type:.",at:r});return}for(let a of Object.keys(e))K.includes(a)||this.report({code:"E_SHAPE",nodeId:t,message:`"${a}" is not a component key (allowed: ${K.join(", ")}).`,fix:"component settings go under config:",at:[...r,a]});let s=e.type;if(typeof s!="string"||s.trim()===""){this.report({code:"E_SHAPE",nodeId:t,message:"The component has no type: \u2014 the loader parks it as an unregistered component.",fix:"add type: core:<Name> or user:<ExportedClass>",at:r});return}e.enabled!==void 0&&typeof e.enabled!="boolean"&&this.report({code:"E_SHAPE",nodeId:t,message:"enabled: must be true or false.",at:[...r,"enabled"]}),e.id!==void 0&&typeof e.id!="string"&&this.report({code:"E_SHAPE",nodeId:t,message:"A component id: must be a string.",at:[...r,"id"]});let o=e.config;o!=null&&!p(o)&&this.report({code:"E_SHAPE",nodeId:t,message:"config: must be a mapping.",at:[...r,"config"]});let i=p(o)?o:{};if(this.checkStrings(i,[...r,"config"],t),s.startsWith("core:")){if(!this.env.registry.getComponentType(s)){let l=b(s,this.env.registry.getAllComponentTypes().map(d=>d.id));this.report({code:"E_UNKNOWN_COMPONENT",nodeId:t,message:`${s} is not a built-in component; the loader parks it and it never runs.`,fix:l?`type: ${l}`:void 0,at:[...r,"type"]});return}let c=this.env.registry.getComponentPropertySchema(s)?.properties??[];this.checkConfig(s,c,i,t,[...r,"config"]);return}if(s.startsWith("user:")){if(this.usesUserComponents=!0,this.env.userScripts.ids.has(s)||this.env.userScripts.opaqueEntries.length>0)return;let a=b(s,this.env.userScripts.ids.keys()),c=s.slice(5);this.report({code:"E_USER_SCRIPT_NOT_FOUND",nodeId:t,message:`No script under scripts/ (or src/scripts/) exports a Script class named ${c}; the component would stay unregistered. The id is the EXPORT name, and the file must contain "extends Script".`,fix:a?`type: ${a}`:`create scripts/${c}.ts with export class ${c} extends Script`,at:[...r,"type"]});return}this.report({code:"E_UNKNOWN_COMPONENT",nodeId:t,message:`Component type "${s}" has no namespace: built-ins are core:<Name>, project scripts user:<ExportedClass>.`,fix:this.env.registry.getComponentType(`core:${s}`)?`type: core:${s}`:`type: user:${s}`,at:[...r,"type"]})}checkConfig(e,t,r,s,o){for(let[i,a]of Object.entries(r)){let c=t.find(l=>l.name===i);if(!c){let l=b(i,t.map(d=>d.name));this.report({code:"E_UNKNOWN_CONFIG_KEY",nodeId:s,message:`${e} has no setting "${i}" (declared: ${t.map(d=>d.name).join(", ")||"none"}); it is kept and ignored.`,fix:l?`did you mean ${l}?`:void 0,at:[...o,i]});continue}this.checkSchemaValue(c,a,s,[...o,i],i)}}checkInstanceNode(e,t,r,s){for(let u of["children","components"])e[u]!==void 0&&this.report({code:"E_SHAPE",nodeId:t,message:`${u}: on an instance node is ignored \u2014 the node's content comes from the prefab.`,fix:"move them into the prefab, or wrap the instance in a parent node",at:[...s,u]});if(typeof e.instance!="string"||e.instance.trim()===""){this.report({code:"E_SHAPE",nodeId:t,message:"instance: must be a res:// path to a .pix3scene prefab.",at:[...s,"instance"]});return}let o=T(e.instance),i=[...s,"instance"];if(!o||!this.env.project.has(o)){this.report({code:"E_MISSING_PREFAB",nodeId:t,message:`Prefab ${e.instance} does not exist; the loader fails the whole scene.`,at:i});return}this.references.add(o);let a=this.env.prefabs.findCycle(o,[this.file]);if(a){this.report({code:"E_PREFAB_CYCLE",nodeId:t,message:`Prefab instances form a cycle: ${a.join(" -> ")}.`,at:i});return}let c=this.env.prefabs.get(o);if(!c.ok)return;if(c.roots.length!==1){this.report({code:"E_PREFAB_ROOT",nodeId:t,message:`Prefab ${e.instance} has ${c.roots.length} root nodes; an instanced prefab must have exactly one.`,at:i});return}let l=p(c.roots[0])?c.roots[0]:null,d=l?this.env.prefabs.effectiveDefinition(l):null;d&&this.checkInstanceProperties(d,r,t,[...s,"properties"]),this.checkOverrides(e.overrides,l,t,[...s,"overrides"])}checkInstanceProperties(e,t,r,s){let o=typeof e.type=="string"?G(e.type):"Node3D",i=o?z(o):null;if(!i)return;let a=se(i);for(let[c,l]of Object.entries(t)){let d=[...s,c];if(this.checkStrings(l,d,r),c==="transform"){if(!p(l)){this.report({code:"E_PROPERTY_TYPE",nodeId:r,message:"transform must be a mapping.",at:d});continue}for(let[f,h]of Object.entries(l)){let g=X[f],E=g?a.find(x=>x.name===g):void 0;if(!E){this.report({code:"E_UNKNOWN_PROPERTY",nodeId:r,message:`transform.${f} is not applied to an instance (known: ${Object.keys(X).join(", ")}).`,at:[...d,f]});continue}this.checkSchemaValue(E,h,r,[...d,f],`transform.${f}`)}continue}let u=a.find(f=>f.name===c);if(!u){let f=b(c,a.map(h=>h.name));this.report({code:"E_UNKNOWN_PROPERTY",nodeId:r,message:`The prefab's ${i.type} has no property "${c}"; on an instance (and in overrides) properties are the node's schema names.`,fix:f?`did you mean ${f}?`:void 0,at:d});continue}this.checkSchemaValue(u,l,r,d,c)}}checkOverrides(e,t,r,s){if(e==null)return;if(!p(e)||e.byLocalId!==void 0&&!p(e.byLocalId)){this.report({code:"E_SHAPE",nodeId:r,message:"overrides: must be a mapping of the form { byLocalId: { <node path>: { properties: \u2026 } } }.",at:s});return}let o=p(e.byLocalId)?e.byLocalId:{};for(let[i,a]of Object.entries(o)){let c=[...s,"byLocalId",i];if(!p(a)||a.properties!==void 0&&!p(a.properties)){this.report({code:"E_SHAPE",nodeId:r,message:"An override entry must be { properties: { \u2026 } }.",at:c});continue}if(!t)continue;let l=this.resolveOverrideTarget(t,i);if(l!==void 0){if(l===null){this.report({code:"E_UNKNOWN_OVERRIDE_TARGET",nodeId:r,message:`The prefab has no node at "${i}"; the loader warns and drops this override.`,fix:"override keys are node ids from the prefab root, joined with / for nested nodes",at:c});continue}this.checkInstanceProperties(l,p(a.properties)?a.properties:{},r,[...c,"properties"])}}}resolveOverrideTarget(e,t){let r=this.env.prefabs,s=r.localIdOf(e),o=r.effectiveDefinition(e);if(!s||!o)return;let i=t.replace(/\\/g,"/").replace(/^\/+/,"").trim(),a=[i.split("/")];if(i===s)return o;i.startsWith(`${s}/`)&&a.push(i.slice(s.length+1).split("/"));for(let c of a){let l=o;for(let d of c){let u=Array.isArray(l?.children)?l.children:[],f=null;for(let h of u){if(!p(h))continue;let g=r.localIdOf(h);if(g===null&&typeof h.instance=="string")return;if(g===d){f=r.effectiveDefinition(h);break}}if(l=f,!l)break}if(l)return l}return null}},ze=(n,e,t)=>{let r=Z(t);if("errors"in r)return{diagnostics:r.errors.map(o=>m({code:"E_YAML",file:e,line:o.line,message:`YAML does not parse: ${o.message}`})),references:new Set,parsed:null,usesUserComponents:!1};let s=new W(n,e,r);return s.run(),{diagnostics:s.diagnostics,references:s.references,parsed:r,usesUserComponents:s.usesUserComponents}},qe=(n,e,t,r,s,o,i,a)=>{let c=new W(n,e,t);return c.checkConfig(r,s,o,i,a),c.diagnostics};import{mkdtempSync as Wt,rmSync as Yt,writeFileSync as Ft}from"node:fs";import{tmpdir as Ut}from"node:os";import{join as Y}from"node:path";import{pathToFileURL as Vt}from"node:url";var Xe=()=>{let n={addColorStop:()=>{}};return new Proxy({},{get(e,t){return t==="measureText"?r=>({width:r.length*10,actualBoundingBoxAscent:10,actualBoundingBoxDescent:3,actualBoundingBoxLeft:0,actualBoundingBoxRight:r.length*10}):t==="getImageData"?()=>({data:new Uint8ClampedArray(4),width:1,height:1}):typeof t=="string"&&t.startsWith("create")?()=>n:()=>{}},set:()=>!0})},Tt=()=>({style:{},src:"",crossOrigin:null,complete:!1,width:0,height:0,addEventListener:()=>{},removeEventListener:()=>{}}),Je=()=>({createElement(n){if(n!=="canvas")throw new Error(`canvas-only document shim: createElement('${n}') is not provided`);return{width:1,height:1,style:{},getContext:e=>e==="2d"?Xe():null,addEventListener:()=>{},removeEventListener:()=>{}}},createElementNS(n,e){if(e!=="img")throw new Error(`canvas-only document shim: createElementNS('${e}') is not provided`);return Tt()}}),oe=()=>typeof Reflect.get(globalThis,"document")<"u"?()=>{}:(Reflect.set(globalThis,"document",Je()),()=>{Reflect.deleteProperty(globalThis,"document")});import{existsSync as Ze,readFileSync as Qe,statSync as At}from"node:fs";import{isAbsolute as Dt,join as It,relative as Ct,resolve as et}from"node:path";var A=class extends de{projectDir;transformText;constructor(e,t={}){super("/"),this.projectDir=et(e),this.transformText=t.transformText}pathOf(e){let t=this.normalize(e).replace(/^\/+/,"");return this.containedPath(t)}isProjectResource(e){let t=this.getScheme(e);return t===""||t==="res"}exists(e){let t=this.pathOf(e);if(!t||!Ze(t))return!1;try{return At(t).isFile()}catch{return!1}}async fetchText(e){let t=this.requirePath(e),r=Qe(t,"utf8");return this.transformText?this.transformText(r,t):r}async fetchBlob(e){return new Blob([new Uint8Array(Qe(this.requirePath(e)))])}containedPath(e){let t=decodeURIComponent(e.replace(/^\/+/,"")),r=et(It(this.projectDir,t)),s=Ct(this.projectDir,r);return s.startsWith("..")||Dt(s)?null:r}requirePath(e){let t=`res://${e.replace(/^\/+/,"")}`,r=this.containedPath(e);if(!r)throw new w(t,"resolves outside the project folder");if(!Ze(r))throw new w(t);return r}},w=class extends Error{resource;constructor(e,t="file not found"){super(`Missing resource ${e} (${t})`),this.name="MissingResourceError",this.resource=e}},$=class extends Error{resource;constructor(e){super(`Resource ${e} exists but is not decoded outside the browser`),this.name="ResourceNotDecodedError",this.resource=e}},D=class extends fe{missingSet=new Set;disk;constructor(e){super(e),this.disk=e}get missing(){return[...this.missingSet]}async loadTexture(e,t){return this.require(e),new pe}async loadAsset(e){throw this.require(e),new $(e)}async loadSpineAsset(e){throw this.require(e.skeletonPath),this.require(e.atlasPath),e.texturePath&&this.require(e.texturePath),new $(e.skeletonPath)}require(e){if(this.disk.isProjectResource(e)&&!this.disk.exists(e))throw this.missingSet.add(e),new w(e)}};var jt=[/^Lit is in dev mode/],Lt=n=>n instanceof Error?n.message:typeof n=="string"?n:String(n);async function ie(n){let e=[],t={warn:console.warn,debug:console.debug,info:console.info,log:console.log};console.warn=(...r)=>{let s=r.find(i=>i instanceof Error);if(s instanceof $)return;let o=r.map(Lt).join(" ");jt.some(i=>i.test(o))||e.push({message:o,error:s})},console.debug=()=>{},console.info=()=>{},console.log=()=>{};try{return{result:await n(),warnings:e}}finally{console.warn=t.warn,console.debug=t.debug,console.info=t.info,console.log=t.log}}var tt={runtime:"@pix3/runtime",three:"three"},Mt=async n=>{try{return await import(n)}catch{return null}},Gt=(n,e)=>({name:"pix3-validate-host-imports",setup(t){t.onResolve({filter:/^@pix3\/runtime$/},()=>({path:n.runtime,external:!0})),t.onResolve({filter:/^three$/},()=>({path:n.three,external:!0})),t.onResolve({filter:/\?(raw|url)$/},r=>{let[s,o]=r.path.split("?");return{path:Y(r.resolveDir,s),namespace:o==="raw"?"pix3-raw":"pix3-url"}}),t.onResolve({filter:/^[^./]/},r=>r.kind==="entry-point"?void 0:{path:r.path,namespace:"pix3-stub"}),t.onLoad({filter:/.*/,namespace:"pix3-stub"},()=>({contents:"module.exports = {};",loader:"js"})),t.onLoad({filter:/.*/,namespace:"pix3-raw"},async r=>{let{readFile:s}=await import("node:fs/promises");return{contents:`export default ${JSON.stringify(await s(r.path,"utf8"))};`,loader:"js"}}),t.onLoad({filter:/.*/,namespace:"pix3-url"},r=>({contents:`export default ${JSON.stringify(`res://${e.relativeOf(r.path)??r.path}`)};`,loader:"js"}))}}),rt=async(n,e,t,r,s="esbuild")=>{if(e.length===0)return{status:"none"};let o=await Mt(s);if(!o)return{status:"unavailable",reason:"esbuild is not installed (it is an optional dependency of @pix3/cli), so project scripts cannot be compiled"};let i;try{i=(await o.build({stdin:{contents:e.map((d,u)=>`export * as __pix3_entry_${u} from ${JSON.stringify(`./${d}`)};`).join(`
|
|
4
|
-
`),resolveDir:n.root,sourcefile:"pix3-validate-scripts.ts",loader:"ts"},absWorkingDir:n.root,bundle:!0,write:!1,format:"esm",platform:"neutral",target:"es2022",logLevel:"silent",loader:{".glsl":"text",".vert":"text",".frag":"text",".css":"empty"},define:{"import.meta.env.BASE_URL":JSON.stringify("/"),"import.meta.env.DEV":"true","import.meta.env.PROD":"false"},plugins:[Gt(r,n)]})).outputFiles[0]?.text??""}catch(l){let d=l.errors??[];return{status:"failed",diagnostics:(d.length>0?d:[{text:String(l),location:null}]).map(f=>m({code:"E_SCRIPT_COMPILE",file:f.location?.file?n.relativeOf(Y(n.root,f.location.file))??f.location.file:"scripts",line:f.location?.line,message:`Project scripts do not compile: ${f.text}. The editor registers none of them, so every user: component stays unregistered.`}))}}let a=Wt(Y(Ut(),"pix3-validate-")),c=Y(a,"scripts.mjs");try{Ft(c,i);let l=await import(Vt(c).href),d=[];return e.forEach((u,f)=>{let h=l[`__pix3_entry_${f}`];p(h)&&d.push(...Pe(t,h,u))}),{status:"loaded",registered:d}}catch(l){return{status:"failed",diagnostics:[m({code:"W_SCRIPT_IMPORT",file:"scripts",message:`The compiled project scripts threw when imported in Node (${l instanceof Error?l.message:String(l)}) \u2014 probably a browser global used at module top level. user: component properties not checked.`})]}}finally{Yt(a,{recursive:!0,force:!0})}},Bt=/Component type "(.*?)" is not registered yet — kept as pending on node "(.*?)"/,Ht=/Override target "(.*?)" not found/,nt=async n=>{let{env:e,registry:t,file:r}=n,s=[],o=new A(e.project.root),i=new ye(new D(o),t,o),a=oe();try{let{result:c,warnings:l}=await ie(()=>i.parseScene(n.text,{filePath:`res://${r}`}));for(let d of l){let u=Bt.exec(d.message);u?s.push(m({code:"E_PENDING_COMPONENT",file:r,nodeId:u[2],message:`Component ${u[1]} on node "${u[2]}" is still unregistered after compiling the project scripts; it never runs.`})):d.error instanceof w?s.push(m({code:"E_MISSING_RESOURCE",file:r,message:`${d.error.resource} does not exist (the loader warned: ${d.message.split(":")[0]}).`})):Ht.test(d.message)?s.push(m({code:"E_UNKNOWN_OVERRIDE_TARGET",file:r,message:`${d.message.replace(/^\[SceneLoader\]\s*/,"")} The override is dropped.`})):s.push(m({code:"E_LOAD",file:r,message:`The loader warned: ${d.message.replace(/^\[SceneLoader\]\s*/,"")}`}))}if(n.checkUserConfig&&Kt(n.parsed.data,(d,u,f,h)=>{if(!d.startsWith("user:"))return;let g=t.getComponentPropertySchema(d)?.properties;g&&s.push(...qe(e,r,n.parsed,d,g,u,f,h))}),!n.isPrefab)for(let d of Ee(c.rootNodes,{targetPlatform:n.targetPlatform}))d.code!=="inert-nodes"&&s.push(m({code:`W_RENDERABILITY_${d.code.toUpperCase().replace(/-/g,"_")}`,file:r,nodeId:d.nodeIds[0],message:d.message}))}catch(c){c instanceof me?s.push(m({code:"E_LOAD",file:r,message:`The loader rejects the scene: ${c.message}${c.details.length>0?` (${c.details.join("; ")})`:""}`})):c instanceof w?s.push(m({code:"E_MISSING_RESOURCE",file:r,message:`${c.resource} does not exist; the loader fails the whole scene.`})):s.push(m({code:"E_LOAD",file:r,message:`The loader threw ${c instanceof Error?`${c.name}: ${c.message}`:String(c)}.`}))}finally{a()}return s},Kt=(n,e)=>{let t=(r,s)=>{if(!p(r))return;let o=typeof r.id=="string"?r.id:void 0;Array.isArray(r.components)&&r.components.forEach((i,a)=>{p(i)&&typeof i.type=="string"&&e(i.type,p(i.config)?i.config:{},o,[...s,"components",a,"config"])}),Array.isArray(r.children)&&r.children.forEach((i,a)=>t(i,[...s,"children",a]))};p(n)&&Array.isArray(n.root)&&n.root.forEach((r,s)=>t(r,["root",s]))};var zt=n=>n.replace(/\/\*[\s\S]*?\*\//g," ").replace(/(^|[^:\\])\/\/.*$/gm,"$1"),qt=/\bexport\s+(default\s+)?(?:abstract\s+)?class\s+([A-Za-z_$][\w$]*)?/g,Xt=/\bexport\s+(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*=\s*class\b/g,Jt=/\bexport\s+(type\s+)?\{([^}]*)\}/g,Zt=/\bexport\s+\*\s+(?:as\s+[A-Za-z_$][\w$]*\s+)?from\b/,Qt=n=>{let e=zt(n),t=[];for(let r of e.matchAll(qt))t.push(r[1]?"default":r[2]??"default");for(let r of e.matchAll(Xt))t.push(r[1]);for(let r of e.matchAll(Jt))if(!r[1])for(let s of r[2].split(",")){let o=s.trim();if(!o||o.startsWith("type "))continue;let i=/\bas\s+([A-Za-z_$][\w$]*)$/.exec(o);t.push(i?i[1]:o.split(/\s+/)[0])}return{names:t,hasStar:Zt.test(e)}},st=n=>{let e=new Map,t=[],r=[];for(let s of n.files){if(!(s.endsWith(".ts")||s.endsWith(".js"))||s.endsWith(".d.ts"))continue;let o;try{o=n.readText(s)}catch{continue}if(!we(s,o))continue;t.push(s);let{names:i,hasStar:a}=Qt(o);a&&r.push(s);for(let c of i){let l=`${_e}${c}`;e.has(l)||e.set(l,s)}}return{ids:e,entries:t,opaqueEntries:r}};var ae="(user: component properties not checked)",ot=[".pix3scene",".ts",".js",".json",".pix3anim",".atlas",".yaml",".yml",".html",".css"],er=["sprites/","audio/"],tr=(n,e)=>{let t=n.files.filter(s=>ot.some(o=>s.endsWith(o))).map(s=>{try{return{file:s,text:n.readText(s)}}catch{return{file:s,text:""}}}),r=[];for(let s of n.files){if(!er.some(c=>s.startsWith(c)))continue;let o=s.slice(s.lastIndexOf("/")+1);if(o.startsWith(".")||ot.some(c=>s.endsWith(c))||e.has(s))continue;let i=s.slice(0,s.lastIndexOf("/")+1);t.some(({file:c,text:l})=>c!==s&&(l.includes(s)||c.startsWith(i)&&c.slice(i.length).indexOf("/")===-1&&l.includes(o)))||r.push(m({code:"W_UNUSED_ASSET",file:s,message:`${s} is not referenced by any scene, script or resource file.`,fix:"use it, or delete it to keep exports small"}))}return r},F=async n=>{let e=new k(n.projectRoot),t=new ue,r=console.log;console.log=()=>{};try{Re(t)}finally{console.log=r}let s={project:e,registry:t,userScripts:st(e),prefabs:new L(e)},o=n.files===void 0,i=[...n.files??e.scenes()].sort(),a=[],c=[],l=new Map,d=new Map;for(let g of i){if(!e.has(g)){a.push(m({code:"E_SHAPE",file:g,message:`${g} does not exist.`}));continue}let E=e.readText(g);d.set(g,E),c.push({file:g,sha256:je(e,g)});let x=ze(s,g,E);l.set(g,x),a.push(...x.diagnostics)}if(o){let g=new Set;for(let E of l.values())for(let x of E.references)g.add(x);a.push(...tr(e,g))}let u=[...l.values()].some(g=>g.usesUserComponents),f=[],h;if(n.hydrate===!1)h={state:"disabled"},u&&f.push(`Level 2 disabled (--no-hydrate) ${ae}.`);else{let g=i.filter(P=>{let R=l.get(P);return R?.parsed&&!R.diagnostics.some(dt=>dt.severity==="error")}),E=await rt(e,s.userScripts.entries,t,n.scriptImports??tt,n.esbuildSpecifier),x=!0,I=!1;E.status==="unavailable"?(x=!1,I=!0,a.push(m({code:"W_HYDRATE_SKIPPED",file:"scripts",message:`Level 2 skipped for scenes with user: components: ${E.reason} ${ae}.`,fix:"npm install esbuild next to @pix3/cli, or run with --no-hydrate"}))):E.status==="failed"&&(x=!1,I=!0,a.push(...E.diagnostics));let le=new Set;for(let P of e.scenes())for(let R of s.prefabs.instanceTargets(P))le.add(R);let{targetPlatform:lt}=Le(e),N=0,O=0;for(let P of g){let R=l.get(P);if(R?.parsed){if(I&&R.usesUserComponents){O+=1;continue}a.push(...await nt({env:s,registry:t,file:P,text:d.get(P)??"",parsed:R.parsed,targetPlatform:lt,isPrefab:le.has(P),checkUserConfig:x})),N+=1}}h={state:"ran",filesHydrated:N,filesSkipped:i.length-N,...O>0?{filesSkippedForScripts:O}:{}},i.length-N>0&&f.push(`Level 2 did not hydrate ${i.length-N} of ${i.length} file(s): it runs only on files with no level-1 errors${I?" and, while scripts cannot be loaded, without user: components":""}.`),E.status==="failed"&&O>0&&f.push(`SKIPPED: ${O} scene(s) with user: components were not checked at level 2 because the project scripts do not compile (E_SCRIPT_COMPILE above) \u2014 E_UNKNOWN_CONFIG_KEY, E_PROPERTY_TYPE and every other user: component check are missing from this report, not passed. Fix the compile error and run again.`),!x&&u&&f.push(`Project scripts could not be loaded ${ae}.`)}return a.sort(Ne),{projectRoot:e.root,files:c,diagnostics:a,errorCount:a.filter(g=>g.severity==="error").length,warningCount:a.filter(g=>g.severity==="warning").length,level2:h,notes:f}};var it=`Usage: pix3 validate [paths\u2026] [--json] [--no-hydrate] [--project <dir>]
|
|
2
|
+
import{$c as le,Mb as ie,Nb as ae,Pb as De,Qb as Ie,Vc as Me,Wc as Ue,Xc as Fe,Xf as Ge,Zc as j,_c as ce,_f as Be,a as J,ad as de,b as Te,cd as pe,dd as fe,ed as ue,id as We,kd as Ke,md as Ve,od as Ye,pa as je,pd as X,rd as He,sa as Ce,sd as he,td as me,zf as ze}from"./chunk-VIHIF4G2.js";import{d as q,pd as Le}from"./chunk-6ACT6FNV.js";import{existsSync as fn,statSync as un}from"node:fs";import{resolve as Ae}from"node:path";var Ze=q(J(),1);import{dirname as ar,join as Nn,resolve as An}from"node:path";import{createHash as nr}from"node:crypto";import{chmodSync as Sn,existsSync as sr,mkdirSync as wn,readFileSync as bn,renameSync as vn,statSync as Pn,writeFileSync as kn}from"node:fs";import{dirname as qe,join as or,resolve as ir}from"node:path";var Je=r=>nr("sha256").update(r).digest("hex");var Xe=(r,e)=>{for(let t=ir(r);;t=qe(t)){let n=or(t,e);if(sr(n))return n;if(qe(t)===t)return null}};var w="pix3project.yaml";var Qe=r=>{let e=Xe(r,w);return e===null?null:ar(e)};var et=["coin","jump","hit","explosion","powerup","click"],U={wave:"square",freq:440,freqEnd:440,slide:0,arpAt:0,arpMul:1,vibratoDepth:0,vibratoHz:0,duty:.5,attack:.002,decay:.05,sustainLevel:.6,sustain:.05,release:.1,lowpassHz:0,lowpassQ:.707,volume:.8},jn={coin:{...U,freq:988,freqEnd:988,arpAt:.07,arpMul:4/3,duty:.5,decay:.02,sustainLevel:.7,sustain:.1,release:.18,volume:.7},jump:{...U,freq:260,freqEnd:720,slide:.16,duty:.35,decay:.04,sustainLevel:.6,sustain:.06,release:.1,lowpassHz:5e3,volume:.7},hit:{...U,wave:"noise",freq:1400,freqEnd:180,slide:.12,attack:.001,decay:.05,sustainLevel:.3,sustain:.02,release:.08,lowpassHz:3200,lowpassQ:1.2,volume:.85},explosion:{...U,wave:"noise",freq:520,freqEnd:40,slide:.7,attack:.002,decay:.12,sustainLevel:.55,sustain:.15,release:.5,lowpassHz:1400,lowpassQ:1,volume:.9},powerup:{...U,wave:"saw",freq:330,freqEnd:1320,slide:.38,vibratoDepth:.04,vibratoHz:14,attack:.005,decay:.05,sustainLevel:.7,sustain:.25,release:.15,lowpassHz:6e3,volume:.65},click:{...U,wave:"square",freq:1800,freqEnd:1200,slide:.02,duty:.25,attack:5e-4,decay:.012,sustainLevel:0,sustain:0,release:.012,lowpassHz:8e3,volume:.6}};import{readFileSync as hr}from"node:fs";import{join as mr}from"node:path";import{existsSync as cr,mkdtempSync as In,readFileSync as lr,rmSync as Mn}from"node:fs";import{dirname as ge,join as dr}from"node:path";import{fileURLToPath as pr}from"node:url";var fr="@pix3/cli",ur=r=>{try{return JSON.parse(lr(r,"utf8")).name===fr}catch{return!1}},Z,tt=()=>{if(Z!==void 0)return Z;let r=ge(pr(import.meta.url)),e=r;for(;;){let t=dr(e,"package.json");if(cr(t)&&ur(t))return Z=e;let n=ge(e);if(n===e)break;e=n}return Z=ge(r)};var gr=()=>{try{let r=hr(mr(tt(),"package.json"),"utf8"),e=JSON.parse(r);return typeof e.version=="string"?e.version:"0.0.0"}catch{return"0.0.0"}},rt=gr();var Bn=`pix3 ${rt}
|
|
5
3
|
|
|
6
|
-
|
|
4
|
+
Usage:
|
|
5
|
+
pix3 new List the starters (2d, 3d)
|
|
6
|
+
pix3 new <2d|3d> [dir] Create an empty project (dir defaults to pix3-<2d|3d>)
|
|
7
|
+
[--name <project name>]
|
|
8
|
+
pix3 editor [--project <dir>] Find (or start, detached) the project's Vite dev server
|
|
9
|
+
[--stop] [--chrome-only] from .pix3/dev.json and open the editor in Chrome, its
|
|
10
|
+
[--no-chrome] [--port <n>] CDP behind the token proxy ws://127.0.0.1:9333/pix3 (next
|
|
11
|
+
[--cdp-port <n>] free port when taken); --stop-chrome ends that Chrome.
|
|
12
|
+
[--headless] [--stop-chrome] Over SSH: prints what to run on your machine instead;
|
|
13
|
+
[--url <editor url>] there, --url opens a forwarded remote editor (no project)
|
|
14
|
+
[--ssh <host>] and prints the line that copies the token to <host>
|
|
15
|
+
pix3 agent-setup [claude|codex] Write the project's MCP config for chrome-devtools-mcp
|
|
16
|
+
[--repair] [--cdp-port <n>] (.mcp.json, .codex/config.toml, with the proxy token);
|
|
17
|
+
[--remote] --repair fixes an entry that drifted (version, port,
|
|
18
|
+
[--project <dir>] token, the P1 --browserUrl launch); --remote: the agent
|
|
19
|
+
is on an SSH host, Chrome on your machine (forwarded)
|
|
20
|
+
pix3 validate [paths\u2026] [--json] Strict scene check: schema, references, guards, then
|
|
21
|
+
[--no-hydrate] each scene loaded as the game loads it (exit 1 on errors)
|
|
22
|
+
pix3 check [--json] validate + TypeScript check of the scripts + version
|
|
23
|
+
[--no-hydrate] [--offline] check (exit 1 on errors)
|
|
24
|
+
[--project <dir>]
|
|
25
|
+
pix3 smoke [scene] [--json] Run the game headless in Node for N frames (no browser):
|
|
26
|
+
[--changed | --all] script throws with frame + stack, console errors, missing
|
|
27
|
+
[--frames N] [--timeout S] res:// (exit 1 on errors, 2 when it cannot run). No scene =
|
|
28
|
+
[--project <dir>] the scenes git changes reach, else every top-level scene
|
|
29
|
+
pix3 tree [scene] [--json] One line per node (type#id, pos, size, layout, components,
|
|
30
|
+
[--depth N] [--types A,B] prefab instances); no scene = project overview
|
|
31
|
+
[--props] [--project <dir>]
|
|
32
|
+
pix3 kit [--update] Install (or update) the agent kit: AGENTS.md, CLAUDE.md,
|
|
33
|
+
[--migrate] .claude/skills/pix3-*, script types; --migrate: a 1.x kit
|
|
34
|
+
[--project <dir>] \u2192 2.x (drops the pix3 mcp server, retired files, pix3Hybrid)
|
|
35
|
+
pix3 character-compile <spec> A 2D character from frame PNGs: <slug>.pix3anim with
|
|
36
|
+
[--dry-run] [--force] <variant>.<state> clips + a prefab with
|
|
37
|
+
[--json] [--project <dir>] core:CharacterVisual2D (refuses to overwrite)
|
|
38
|
+
pix3 sfx <preset|"text"> Synthesize a sound effect offline into a WAV (coin, jump,
|
|
39
|
+
[--out <file.wav>] hit, explosion, powerup, click; or "big explosion")
|
|
40
|
+
[--seed <n>] [--json]
|
|
41
|
+
pix3 gap "<what was missing>" Record a gap Pix3 had (you worked around it) in
|
|
42
|
+
[--kind capability|tool|node|doc|other] .pix3/gaps.jsonl;
|
|
43
|
+
[--detail <text>] [--context <text>] --list prints them
|
|
44
|
+
[--agent <name>] [--list] [--json] [--project <dir>]
|
|
45
|
+
pix3 help <command> That command's own usage (also pix3 <command> --help)
|
|
46
|
+
pix3 --version
|
|
47
|
+
`,Q={new:`Usage: pix3 new [2d|3d] [dir] [--name <project name>]
|
|
48
|
+
|
|
49
|
+
Without a starter: list them. With one: create an empty Vite + @pix3/vite-plugin project in dir
|
|
50
|
+
(default pix3-<2d|3d>) with the agent kit (AGENTS.md, CLAUDE.md, .claude/skills/pix3-*, script
|
|
51
|
+
types). The same as npm create pix3.
|
|
52
|
+
|
|
53
|
+
--name name the project name (default: from the folder)
|
|
54
|
+
`,editor:`Usage: pix3 editor [--project <dir>] [--stop] [--stop-chrome] [--chrome-only] [--no-chrome]
|
|
55
|
+
[--headless] [--port <n>] [--cdp-port <n>] [--url <editor url>] [--ssh <host>]
|
|
56
|
+
[--remote-port <n>]
|
|
57
|
+
|
|
58
|
+
Find the project's Vite dev server (.pix3/dev.json), or start it detached, and open the editor
|
|
59
|
+
(/__pix3/) in a Chrome whose DevTools are reachable only through the token proxy
|
|
60
|
+
ws://127.0.0.1:9333/pix3 (the next free port up to 9339 when that one is taken). Idempotent.
|
|
61
|
+
Over SSH it prints what to run on your machine instead of starting Chrome.
|
|
62
|
+
|
|
63
|
+
--project dir project folder (default: nearest folder with pix3project.yaml)
|
|
64
|
+
--stop stop the dev server this command started
|
|
65
|
+
--stop-chrome close the Chrome (and its proxy) pix3 editor started
|
|
66
|
+
--no-chrome only the dev server
|
|
67
|
+
--chrome-only only Chrome, for a dev server that already runs
|
|
68
|
+
--headless headless Chrome (a plain tab)
|
|
69
|
+
--port n the dev server's port (Vite --port --strictPort)
|
|
70
|
+
--cdp-port n the proxy's preferred port (default 9333); over SSH: the one RemoteForward
|
|
71
|
+
port of this host (stored; never a scan)
|
|
72
|
+
--url url on your machine: open a forwarded remote editor (no project here)
|
|
73
|
+
--ssh host with --url: the host the printed token-copy line names
|
|
74
|
+
--remote-port n with --url: the remote port of your RemoteForward line (default 9333) \u2014 the
|
|
75
|
+
only one the proxy proves its token for
|
|
76
|
+
`,"agent-setup":`Usage: pix3 agent-setup [claude|codex] [--repair] [--remote] [--cdp-port <n>] [--project <dir>]
|
|
77
|
+
|
|
78
|
+
Write the project's MCP config for chrome-devtools-mcp \u2014 .mcp.json (Claude Code) and/or
|
|
79
|
+
.codex/config.toml (Codex), both by default \u2014 pointing at the token proxy through the 0600 file
|
|
80
|
+
~/.pix3/cdp-mcp.json (no token in the project or on a command line).
|
|
81
|
+
|
|
82
|
+
--repair rewrite an entry that drifted (version, port, token, an older launch)
|
|
83
|
+
--remote the agent runs on an SSH host, Chrome on your machine (through the forward)
|
|
84
|
+
--cdp-port n the proxy port (default: the one pix3 editor recorded, else 9333); with
|
|
85
|
+
--remote the RemoteForward port (stored; default the one pix3 editor stored)
|
|
86
|
+
--project dir project folder (default: nearest folder with pix3project.yaml)
|
|
87
|
+
`,validate:`Usage: pix3 validate [paths\u2026] [--json] [--no-hydrate] [--project <dir>]
|
|
88
|
+
|
|
89
|
+
paths .pix3scene / .prefab files or folders (default: every scene and .prefab)
|
|
7
90
|
--json machine-readable report (diagnostics + sha256 of each validated file)
|
|
8
|
-
--no-hydrate
|
|
91
|
+
--no-hydrate do not load the scenes as the game does: no project code runs (user: component
|
|
92
|
+
properties not checked)
|
|
9
93
|
--project dir project root (default: nearest folder with pix3project.yaml)
|
|
10
|
-
`,
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
94
|
+
`,check:`Usage: pix3 check [--json] [--no-hydrate] [--offline] [--no-sync] [--project <dir>]
|
|
95
|
+
|
|
96
|
+
Everything \`pix3 validate\` checks, plus a TypeScript type-check of the project's scripts
|
|
97
|
+
(tsc --noEmit) and a version check.
|
|
98
|
+
|
|
99
|
+
--json machine-readable report (diagnostics, sha256 of every checked file, typecheck,
|
|
100
|
+
kit)
|
|
101
|
+
--no-hydrate do not load the scenes as the game does (user: component properties not checked)
|
|
102
|
+
--offline never install TypeScript (fails with the command to run instead)
|
|
103
|
+
--no-sync do not ask a running Pix3 editor (.pix3/dev.json) to flush its unsaved scenes first
|
|
104
|
+
--project dir project root (default: nearest folder with pix3project.yaml)
|
|
105
|
+
`,smoke:`Usage: pix3 smoke [scene] [--changed | --all] [--frames N] [--timeout S] [--json] [--no-sync] [--project <dir>]
|
|
106
|
+
|
|
107
|
+
Run the game headless in Node \u2014 no browser, no editor: the project's scripts compiled, the scene
|
|
108
|
+
loaded by the real loader, N frames of 1/60 s stepped by the real SceneRunner. Reports every
|
|
109
|
+
script throw (onAttach/onStart/onUpdate, with script name, frame and stack), console.error/warn,
|
|
110
|
+
unhandled rejections, missing res:// files and per-frame step time. Nothing is rendered; audio,
|
|
111
|
+
input and network are inert.
|
|
112
|
+
|
|
113
|
+
scene .pix3scene to run (res://, project-relative or a path) \u2014 the surest way to test
|
|
114
|
+
the game: pix3 smoke scenes/main.pix3scene. With no scene, several run one after
|
|
115
|
+
another, a line each: in a git repo with uncommitted changes, the top-level scenes
|
|
116
|
+
those changes reach (the scene, a prefab/overlay it instances, a user: script it
|
|
117
|
+
attaches); otherwise \u2014 or when a changed scene/script reaches none \u2014 every
|
|
118
|
+
top-level scene (not a prefab or a .prefab, not scenes/ui), scenes/main.pix3scene
|
|
119
|
+
first.
|
|
120
|
+
--changed only the scenes changed files reach (needs git; exit 2 when nothing changed)
|
|
121
|
+
--all every top-level scene, whatever git says
|
|
122
|
+
--frames N frames to step (default 120 = 2 s of game time)
|
|
123
|
+
--timeout S wall-clock limit in seconds (default 20) \u2192 exit 2, E_SMOKE_TIMEOUT
|
|
124
|
+
--json machine-readable report
|
|
125
|
+
--no-sync do not ask a running Pix3 editor (.pix3/dev.json) to flush its unsaved scenes first
|
|
126
|
+
--project dir project folder (default: nearest folder with pix3project.yaml)
|
|
127
|
+
|
|
128
|
+
Exit: 0 = no errors, 1 = errors, 2 = could not run (no scene, bundle failure, unsupported, timeout,
|
|
129
|
+
or E_RUNTIME_VERSION: the project installs another @pix3/runtime than this CLI runs).
|
|
130
|
+
`,tree:`Usage: pix3 tree [scene] [--depth N] [--types A,B] [--props] [--json] [--project <dir>]
|
|
131
|
+
|
|
132
|
+
One line per node \u2014 type#id "name", position, size, anchor layout, components, prefab
|
|
133
|
+
instances (\u21B3 instance res://\u2026 (N overrides, M properties)) \u2014 indented by depth. Read this instead of the
|
|
134
|
+
whole .pix3scene when you need to find your way around a scene. A node anchored by margins
|
|
135
|
+
(layout: left/right/top/bottom) shows them \u2014 layout=left/top(left=40,top=30) \u2014 and the pos and
|
|
136
|
+
size they give at the design size (the parent's size, viewportBaseSize for a root; ? where the
|
|
137
|
+
file does not say enough), not the 0 its file holds on that axis.
|
|
138
|
+
|
|
139
|
+
scene .pix3scene (res://, project-relative or a path). Without one: every scene and
|
|
140
|
+
prefab in the project with node counts, node types and components.
|
|
141
|
+
--depth N stop N levels below the roots (0 = roots only); cut subtrees show "\u2026 +K below"
|
|
142
|
+
--types A,B only nodes of these types (or carrying these components; \`instance\` = prefab
|
|
143
|
+
instances), with their ancestors as "\xB7" context lines
|
|
144
|
+
--props also print each node's properties that differ from the type's defaults
|
|
145
|
+
--json the same as nested JSON
|
|
146
|
+
--project dir project folder (default: nearest folder with pix3project.yaml, else cwd)
|
|
147
|
+
`,kit:`Usage: pix3 kit [--update] [--migrate] [--project <dir>]
|
|
148
|
+
|
|
149
|
+
Install the agent kit into a project: AGENTS.md, CLAUDE.md, .claude/skills/pix3-*, the script
|
|
150
|
+
types (.pix3/types). An existing file is left alone unless --update; a file you edited is never
|
|
151
|
+
overwritten.
|
|
152
|
+
|
|
153
|
+
--update replace kit files that are unedited and out of date
|
|
154
|
+
--migrate a 1.x kit \u2192 2.x: drop the pix3 mcp server from .mcp.json, the retired files and
|
|
155
|
+
metadata.pix3Hybrid, then --update (edited files kept and reported)
|
|
156
|
+
--project dir project folder (default: nearest folder with pix3project.yaml)
|
|
157
|
+
`,"character-compile":`Usage: pix3 character-compile <spec.yaml|spec.json> [--project <dir>] [--dry-run] [--force] [--json]
|
|
158
|
+
|
|
159
|
+
Build a 2D character from frame PNGs: <sprites>/<slug>/<slug>.pix3anim (clips named
|
|
160
|
+
<variant>.<state>), the frames copied beside it as <variant>_<state>_<nnnn>.png, and the prefab
|
|
161
|
+
<prefabs>/<Name>.pix3scene \u2014 an AnimatedSprite2D root with core:CharacterVisual2D. Game code
|
|
162
|
+
then says character.playState('attack', { restart: true }) / character.setVariant('bow').
|
|
163
|
+
|
|
164
|
+
The spec (paths relative to the spec file):
|
|
165
|
+
name: Goblin # display name and prefab file name
|
|
166
|
+
slug: goblin # sprite folder (default: from name)
|
|
167
|
+
anchor: { x: 0.5, y: 0.9 } # where the node position lands, y from the top (the feet)
|
|
168
|
+
defaultVariant: sword # the pair the prefab starts on (default: the first clip's)
|
|
169
|
+
defaultState: idle
|
|
170
|
+
spriteDirectory: sprites # default sprites
|
|
171
|
+
prefabDirectory: scenes/prefabs # default scenes/prefabs
|
|
172
|
+
clips:
|
|
173
|
+
- { variant: sword, state: idle, fps: 10, frames: [art/sword/idle_1.png, art/sword/idle_2.png] }
|
|
174
|
+
- { variant: sword, state: attack, sequence: art/sword/attack } # art/sword/attack_<n>.png
|
|
175
|
+
- { state: die, sequence: art/die, loop: false } # no variant: clip "die"
|
|
176
|
+
|
|
177
|
+
Defaults are proposals and are printed as warnings: fps 12; loop false for attack, die, death,
|
|
178
|
+
hit and hurt, true otherwise. Frames must be PNG.
|
|
179
|
+
|
|
180
|
+
--project dir project root (default: nearest folder with pix3project.yaml)
|
|
181
|
+
--dry-run print what would be written, write nothing
|
|
182
|
+
--force replace files that exist with other bytes (default: refuse, write nothing)
|
|
183
|
+
--json machine-readable report
|
|
184
|
+
|
|
185
|
+
Exit: 0 = written (or nothing to do), 1 = refused (bad spec, missing frame, existing files),
|
|
186
|
+
2 = usage or no project.
|
|
187
|
+
`,sfx:`Usage: pix3 sfx <preset|"description"> [--out <file.wav>] [--seed <n>] [--json]
|
|
188
|
+
|
|
189
|
+
Synthesizes a short sound effect offline into a 44.1 kHz 16-bit mono WAV.
|
|
190
|
+
presets ${et.join(", ")}
|
|
191
|
+
description words that name a preset ("coin pickup", "big explosion"), plus modifiers:
|
|
192
|
+
high/low, short/long, soft
|
|
193
|
+
--out output file (default: audio/<preset>.wav in the project root)
|
|
194
|
+
--seed a variation of the preset (same seed, same sound); default: the preset as tuned
|
|
195
|
+
--json print { path, res, durationMs, preset, seed, modifiers, bytes, peak, params }
|
|
196
|
+
Reference it from a scene or script as res://<path inside the project>.
|
|
197
|
+
`,gap:`Usage: pix3 gap "<what was missing>" [--kind capability|tool|node|doc|other] [--detail <text>]
|
|
198
|
+
[--context <text>] [--agent <name>] [--json] [--project <dir>]
|
|
199
|
+
pix3 gap --list [--json] [--project <dir>]
|
|
200
|
+
|
|
201
|
+
Record, in .pix3/gaps.jsonl, something Pix3 lacked that you worked around \u2014 not your own
|
|
202
|
+
mistakes or the game's features. One line per record.
|
|
203
|
+
|
|
204
|
+
--kind k capability, tool, node, doc or other (default other)
|
|
205
|
+
--detail text what you did instead
|
|
206
|
+
--context text the file or task it happened in
|
|
207
|
+
--agent name who records it (default: from the environment \u2014 claude-code, codex)
|
|
208
|
+
--list print the recorded gaps
|
|
209
|
+
--json machine-readable output
|
|
210
|
+
--project dir project folder (default: nearest folder with pix3project.yaml)
|
|
211
|
+
`};var yr={E_YAML:{severity:"error",level:1,summary:"the file is not valid YAML"},E_SHAPE:{severity:"error",level:1,summary:"document structure is wrong (root, node, component or override shape)"},E_UNKNOWN_NODE_TYPE:{severity:"error",level:1,summary:"unknown `type:`"},W_TYPE_CASE:{severity:"warning",level:1,summary:"`type:` loads, but is not the canonical spelling"},E_UNKNOWN_PROPERTY:{severity:"error",level:1,summary:"a key under `properties:` the loader never reads for this type"},W_WRITE_ONLY_PROPERTY:{severity:"warning",level:1,summary:"the editor saves this property but the loader does not read it back"},W_LEGACY_KEY:{severity:"warning",level:1,summary:"a read-compat spelling; the editor rewrites it on save"},E_PROPERTY_TYPE:{severity:"error",level:1,summary:"value has the wrong type"},E_PROPERTY_RANGE:{severity:"error",level:1,summary:"value rejected by the property's validation rule (e.g. fov > 0)"},W_PROPERTY_RANGE:{severity:"warning",level:1,summary:"number outside the inspector's min..max (a UI range, not a load error)"},E_PROPERTY_ENUM:{severity:"error",level:1,summary:"value is not one of the options"},E_UNKNOWN_COMPONENT:{severity:"error",level:1,summary:"unknown component type"},E_UNKNOWN_CONFIG_KEY:{severity:"error",level:1,summary:"component config key the component does not declare (`user:` ones at level 2)"},E_USER_SCRIPT_NOT_FOUND:{severity:"error",level:1,summary:"no script under scripts/ exports a Script class with this name"},E_MISSING_RESOURCE:{severity:"error",level:1,summary:"referenced file does not exist"},E_MISSING_PREFAB:{severity:"error",level:1,summary:"`instance:` target does not exist"},E_PREFAB_CYCLE:{severity:"error",level:1,summary:"prefab instances form a cycle"},E_PREFAB_ROOT:{severity:"error",level:1,summary:"an instanced prefab must have exactly one root node"},E_UNKNOWN_OVERRIDE_TARGET:{severity:"error",level:1,summary:"`overrides.byLocalId` names a node the prefab does not have"},E_DUPLICATE_ID:{severity:"error",level:1,summary:"two nodes share an id"},E_EMOJI_AS_ART:{severity:"error",level:1,summary:"text that is nothing but emoji \u2014 a picture standing in for a sprite"},E_SVG_INVALID:{severity:"error",level:1,summary:"a referenced .svg has no <svg> root or no SVG xmlns \u2014 the browser will not decode it"},E_SVG_NO_SIZE:{severity:"error",level:1,summary:"a referenced .svg has no usable width/height (and no viewBox to derive one)"},W_SVG_VIEWBOX_ONLY:{severity:"warning",level:1,summary:"a referenced .svg has a viewBox but no width/height \u2014 renders at 300x150"},W_SVG_EXTERNAL_REF:{severity:"warning",level:1,summary:"a referenced .svg points outside itself (href, url(), @import) \u2014 never loads as an image"},E_ANIM_JSON:{severity:"error",level:1,summary:"a .pix3anim is not a JSON object the loader can read (or its clips are not a list)"},E_MISSING_FRAME:{severity:"error",level:1,summary:"an image a .pix3anim names (a frame texturePath, the spritesheet) does not exist; its .svg frames get the E_SVG_* checks too"},E_LOCALE_MISSING:{severity:"error",level:1,summary:"the default or fallback locale has no locales/<id>.json \u2014 labelKeys show the key"},W_LOCALE_MISSING:{severity:"warning",level:1,summary:"another declared locale has no locales/<id>.json \u2014 its texts fall back"},E_LOCALE_JSON:{severity:"error",level:1,summary:"the default or fallback locale table is not a JSON object with strings/sprites maps \u2014 it loads empty"},W_LOCALE_JSON:{severity:"warning",level:1,summary:"another locale table is not a JSON object with strings/sprites maps \u2014 it loads empty"},E_LOCALE_VALUE:{severity:"error",level:1,summary:"a non-string value in the default or fallback locale table (the runtime drops it)"},W_LOCALE_VALUE:{severity:"warning",level:1,summary:"a non-string value in another locale table (the runtime drops it; the text falls back)"},E_LOCALE_KEY:{severity:"error",level:1,summary:"a labelKey the default (and fallback) locale has no text for \u2014 the key shows on screen"},E_LOCALE_SCRIPT_KEY:{severity:"error",level:1,summary:"a literal key a script passes to tr()/setTextKey()/trPlural() that the default (and fallback) locale has no text for \u2014 the key shows on screen"},E_LOCALE_SPRITE_KEY:{severity:"error",level:1,summary:"a Sprite2D textureKey no default (or fallback) sprites entry has, on a sprite with no texture of its own \u2014 it draws nothing"},W_LOCALE_SPRITE_KEY:{severity:"warning",level:1,summary:"a textureKey / stateTextureKeys / trSprite() key no default (or fallback) sprites entry has \u2014 the node's own texture shows in every locale, trSprite gives null"},E_MISSING_LOCALE_SPRITE:{severity:"error",level:1,summary:"an image a locale table's sprites names does not exist (its .svg ones get the E_SVG_* checks too)"},E_AUTOLOAD:{severity:"error",level:1,summary:"a pix3project.yaml autoload that never runs: no singleton/scriptPath, a repeated singleton, or a script that is missing, outside scripts/, or exports no Script class named like the file"},W_MANIFEST_EXPORT:{severity:"warning",level:1,summary:"pix3project.yaml has the 1.x export: block (prune / include / exclude globs), which no 2.x build reads"},W_LEGACY_VERSION:{severity:"warning",level:1,summary:"scene `version:` is missing or not the current format version"},W_UNUSED_ASSET:{severity:"warning",level:1,summary:"a file under sprites/ or audio/ nothing references (whole-project runs only)"},E_LOAD:{severity:"error",level:2,summary:"the loader rejected the scene or warned"},E_PENDING_COMPONENT:{severity:"error",level:2,summary:"a component stayed unregistered after the scripts compiled"},E_SCRIPT_COMPILE:{severity:"error",level:2,summary:"the project scripts do not compile"},W_SCRIPT_IMPORT:{severity:"warning",level:2,summary:"compiled scripts threw on import in Node; user: properties not checked"},W_HYDRATE_SKIPPED:{severity:"warning",level:2,summary:"scenes not loaded as the game loads them"},W_RENDERABILITY:{severity:"warning",level:2,summary:"the scene may not show anything (W_RENDERABILITY_<ISSUE>)"}},nt=()=>`${Q.validate}
|
|
19
212
|
Codes:
|
|
20
|
-
${Object.entries(
|
|
213
|
+
${Object.entries(yr).map(([r,e])=>` ${r.padEnd(26)} L${e.level} ${e.summary}`).join(`
|
|
21
214
|
`)}
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
`),
|
|
215
|
+
`,xr=r=>r.startsWith("W_")?"warning":"error",g=r=>{let e={severity:xr(r.code),code:r.code,file:r.file,message:r.message};return r.nodeId!==void 0&&(e.nodeId=r.nodeId),r.path!==void 0&&(e.path=r.path),r.line!==void 0&&(e.line=r.line),r.fix!==void 0&&(e.fix=r.fix),e},st=(r,e)=>r.file.localeCompare(e.file)||(r.line??Number.MAX_SAFE_INTEGER)-(e.line??Number.MAX_SAFE_INTEGER)||(r.path??"").localeCompare(e.path??"")||r.code.localeCompare(e.code),ot=r=>{let e=`${r.file}${r.line!==void 0?`:${r.line}`:""}`,t=r.nodeId!==void 0?` [${r.nodeId}]`:"",n=`${e} ${r.severity} ${r.code}${t} ${r.message}
|
|
216
|
+
`;return r.path&&(n+=` at ${r.path}
|
|
217
|
+
`),r.fix&&(n+=` fix: ${r.fix}
|
|
218
|
+
`),n},it=r=>r.state==="ran"?`${r.filesHydrated} scene file(s) loaded as the game loads them${r.filesSkippedForScripts?`, ${r.filesSkippedForScripts} NOT loaded (scripts do not compile \u2014 user: components unchecked)`:""}`:r.state==="disabled"?"scenes not loaded (--no-hydrate)":`scenes not loaded: ${r.reason}`;var pt=q(J(),1);import{readdirSync as Er,readFileSync as ct}from"node:fs";import{isAbsolute as Sr,join as ye,relative as lt,resolve as at,sep as dt}from"node:path";var $=q(J(),1),P=r=>{let e="";for(let t of r)typeof t=="number"?e+=`[${t}]`:e+=e.length===0?t:`.${t}`;return e},R=r=>{let e=new $.LineCounter,t=(0,$.parseDocument)(r,{lineCounter:e,prettyErrors:!1,uniqueKeys:!0});if(t.errors.length>0)return{errors:t.errors.map(s=>({message:s.message.split(`
|
|
219
|
+
`)[0]??s.message,line:s.linePos?.[0]?.line}))};let n=s=>s===void 0?void 0:e.linePos(s).line;return{data:t.toJS({maxAliasCount:1e3}),lineOf(s){let o=t.contents,i=n(o?.range?.[0]);for(let a of s)if((0,$.isMap)(o)){let c=o.items.find(l=>(0,$.isScalar)(l.key)?l.key.value===a:l.key===a);if(!c)return i;i=n((0,$.isScalar)(c.key)?c.key.range?.[0]:void 0)??i,o=c.value}else if((0,$.isSeq)(o)&&typeof a=="number"){let c=o.items[a];if(!c)return i;i=n(c.range?.[0])??i,o=c}else return i;return i}}},p=r=>typeof r=="object"&&r!==null&&!Array.isArray(r);var wr=new Set(["node_modules",".yalc",".git",".pix3","dist",".vite",".cache"]),br=[".pix3scene",".prefab"],xe=r=>{let e=r.toLowerCase();return br.some(t=>e.endsWith(t))},F=class{root;files;fileSet;textCache=new Map;constructor(e){this.root=at(e),this.files=ft(this.root,this.root).sort(),this.fileSet=new Set(this.files)}has(e){return this.fileSet.has(e)}absolute(e){return ye(this.root,e)}relativeOf(e){let t=lt(this.root,at(e));return t.startsWith("..")||Sr(t)?null:t.split(dt).join("/")}readText(e){let t=this.textCache.get(e);if(t!==void 0)return t;let n=ct(this.absolute(e),"utf8");return this.textCache.set(e,n),n}scenes(){return this.files.filter(xe)}},ft=(r,e)=>{let t=[],n;try{n=Er(e,{withFileTypes:!0})}catch{return t}for(let s of n)if(s.isDirectory()){if(wr.has(s.name))continue;t.push(...ft(r,ye(e,s.name)))}else s.isFile()&&t.push(lt(r,ye(e,s.name)).split(dt).join("/"));return t},O=r=>{let e=r.trim();if(e.length===0)return null;let t=/^([a-z][a-z0-9+.-]*):/i.exec(e)?.[1]?.toLowerCase();if(t&&t!=="res")return null;let s=(t==="res"?e.replace(/^res:\/\//i,""):e).replace(/[?#].*$/,""),o=[];for(let i of s.replace(/\\/g,"/").split("/"))if(!(i===""||i===".")){if(i===".."){if(o.length===0)return null;o.pop();continue}o.push(vr(i))}return o.length===0?null:o.join("/")},vr=r=>{try{return decodeURIComponent(r)}catch{return r}},ut=(r,e)=>Je(ct(r.absolute(e))),ht=r=>{if(!r.has(w))return{};try{let e=(0,pt.parse)(r.readText(w));return p(e)?typeof e.targetPlatform=="string"?{targetPlatform:e.targetPlatform}:{}:{}}catch{return{}}};var Pr=(r,e)=>{if(r===e)return 0;if(r.length===0)return e.length;if(e.length===0)return r.length;let t=Array.from({length:e.length+1},(n,s)=>s);for(let n=1;n<=r.length;n++){let s=[n];for(let o=1;o<=e.length;o++){let i=t[o-1]+(r[n-1]===e[o-1]?0:1);s[o]=Math.min(s[o-1]+1,t[o]+1,i)}t=s}return t[e.length]},C=(r,e)=>{let t=r.toLowerCase(),n,s=Number.POSITIVE_INFINITY;for(let i of e){if(i===r)continue;if(i.toLowerCase()===t)return i;let a=Pr(t,i.toLowerCase());a<s&&(s=a,n=i)}let o=r.length>=10?3:2;return s<=o?n:void 0};var mt="http://www.w3.org/2000/svg",kr=/^\s*(\d+(?:\.\d+)?|\.\d+)(?:e[+-]?\d+)?\s*(px|pt|pc|mm|cm|in|q|em|ex)?\s*$/i,_r=r=>{let e=r.replace(/^\uFEFF/,"");for(;;)if(e=e.replace(/^\s+/,""),e.startsWith("<?")){let n=e.indexOf("?>");if(n<0)return null;e=e.slice(n+2)}else if(e.startsWith("<!--")){let n=e.indexOf("-->");if(n<0)return null;e=e.slice(n+3)}else if(/^<!DOCTYPE/i.test(e)){let n=/^<!DOCTYPE[^>[]*(\[[\s\S]*?\])?[^>]*>/i.exec(e);if(!n)return null;e=e.slice(n[0].length)}else break;let t=/^<((?:[A-Za-z_][\w.-]*:)?svg)(?=[\s/>])[^>]*>/.exec(e);return t?t[0]:null},ee=(r,e)=>{let n=new RegExp(`\\s${e.replace(":","\\:")}\\s*=\\s*(?:"([^"]*)"|'([^']*)')`,"i").exec(r);return n?n[1]??n[2]:void 0},gt=r=>{if(r===void 0)return!1;let e=kr.exec(r);return e!==null&&Number(e[1])>0},$r=r=>{if(r===void 0)return!1;let e=r.trim().split(/[\s,]+/).map(Number);return e.length===4&&e.every(Number.isFinite)&&e[2]>0&&e[3]>0},Rr=r=>{let e=[],t=n=>{let s=n.trim();return s!==""&&!s.startsWith("#")&&!/^data:/i.test(s)};for(let n of r.matchAll(/\s(?:xlink:)?href\s*=\s*(?:"([^"]*)"|'([^']*)')/gi)){let s=n[1]??n[2]??"";t(s)&&e.push(`href="${s.trim()}"`)}for(let n of r.matchAll(/url\(\s*(['"]?)([^)'"]*)\1\s*\)/gi))t(n[2])&&e.push(`url(${n[2].trim()})`);for(let n of r.matchAll(/@import\s+([^;]+);?/gi))e.push(`@import ${n[1].trim()}`);return[...new Set(e)]},yt='start from <svg xmlns="http://www.w3.org/2000/svg" width="W" height="H" viewBox="0 0 W H">',Or=r=>{let e=_r(r);if(!e)return[{code:"E_SVG_INVALID",message:"the file does not start with an <svg> root element, so no browser decodes it.",fix:yt}];let t=[],n=/^<([A-Za-z_][\w.-]*):svg/.exec(e)?.[1],s=ee(e,n?`xmlns:${n}`:"xmlns");s!==mt&&t.push({code:"E_SVG_INVALID",message:`the <svg> root has ${s===void 0?"no xmlns":`xmlns="${s}"`}; as an image (which every texture load is) the browser refuses to decode it.`,fix:`add xmlns="${mt}" to the <svg> tag`});let o=gt(ee(e,"width")),i=gt(ee(e,"height")),a=$r(ee(e,"viewBox"));!o&&!i?t.push(a?{code:"W_SVG_VIEWBOX_ONLY",message:"the <svg> root has a viewBox but no width/height (or only % / style sizes): browsers give it no natural size \u2014 Chrome rasterises it into a 300x150 box with the art letterboxed inside, so the sprite has the wrong size and aspect.",fix:"add width/height in px to the <svg> tag (the viewBox size, or the size the node draws it at)"}:{code:"E_SVG_NO_SIZE",message:"the <svg> root has no width/height and no viewBox: it rasterises into a default 300x150 box and the art is cropped to it.",fix:`add width/height in px and a matching viewBox \u2014 ${yt}`}):o!==i&&!a&&t.push({code:"E_SVG_NO_SIZE",message:`the <svg> root has only ${o?"width":"height"} and no viewBox: the missing dimension defaults to ${o?"150":"300"}px.`,fix:`add ${o?"height":"width"} in px (and a viewBox) to the <svg> tag`});let c=Rr(r);return c.length>0&&t.push({code:"W_SVG_EXTERNAL_REF",message:`references outside the file (${c.slice(0,3).join(", ")}${c.length>3?", \u2026":""}) never load when an SVG is drawn as an image \u2014 not in the editor, not in play mode, not in the export.`,fix:"inline the shapes or embed the image as a data: URI; use generic font families (sans-serif), no webfonts"}),t},xt=new WeakMap,Y=(r,e)=>{let t=xt.get(r);t||(t=new Map,xt.set(r,t));let n=t.get(e);if(!n){let s;try{s=r.readText(e)}catch{s=""}n=Or(s),t.set(e,n)}return n},H=r=>/\.svg$/i.test(r);var v=r=>r===null?"null":Array.isArray(r)?`an array of ${r.length}`:typeof r=="string"?`the string ${JSON.stringify(r)}`:typeof r=="object"?"a mapping":`${typeof r} ${String(r)}`,Se=r=>typeof r=="number"&&Number.isFinite(r),Nr=/^(#([0-9a-f]{3}|[0-9a-f]{4}|[0-9a-f]{6}|[0-9a-f]{8})|(rgb|rgba|hsl|hsla)\([^)]*\)|[a-z]+)$/i,Ar=r=>{let e=r.ui?.options;return Array.isArray(e)?e:p(e)?Object.values(e):[]},Ee=(r,e)=>{if(Array.isArray(r)){if(r.length<e.length)return`expected ${e.length} numbers [${e.join(", ")}], got ${v(r)}`;let t=r.slice(0,e.length).findIndex(n=>!Se(n));return t===-1?null:`component ${t} is ${v(r[t])}, not a number`}if(p(r)){let t=e.find(n=>n in r&&!Se(r[n]));return t?`${t} is ${v(r[t])}, not a number`:e.some(n=>n in r)?null:`expected {${e.join(", ")}}`}return`expected [${e.join(", ")}], got ${v(r)}`},k=(r,e)=>({code:"E_PROPERTY_TYPE",message:r,fix:e}),Et=r=>r.ui?.editor?.endsWith("-resource")===!0||r.ui?.resourceType!==void 0,we=(r,e,t=r.name)=>{switch(r.type){case"number":{if(!Se(e)){let i=typeof e=="string"&&e.trim()!==""&&Number.isFinite(Number(e));return k(`${t} must be a number, got ${v(e)}.`,i?`write ${t}: ${Number(e)} (no quotes)`:void 0)}let n=r.validation?.validate(e);if(n===!1||typeof n=="string")return{code:"E_PROPERTY_RANGE",message:`${t} is ${e}: ${typeof n=="string"?n:"rejected by the property's validation"}.`};let s=r.ui?.min,o=r.ui?.max;return s!==void 0&&e<s||o!==void 0&&e>o?{code:"W_PROPERTY_RANGE",message:`${t} is ${e}, outside the inspector's ${s??"-\u221E"}..${o??"\u221E"} range; it loads as written, but the inspector cannot produce it.`}:null}case"boolean":return typeof e=="boolean"?null:k(`${t} must be true or false, got ${v(e)}.`,e==="true"||e==="false"?`write ${t}: ${e} (no quotes)`:void 0);case"string":return e===null||typeof e=="string"?null:k(`${t} must be a string, got ${v(e)}.`,typeof e=="number"||typeof e=="boolean"?`quote it: ${t}: "${String(e)}"`:void 0);case"color":return e===null?null:typeof e!="string"?k(`${t} must be a colour string like "#ff8800", got ${v(e)}.`,typeof e=="number"?`write ${t}: "#${e.toString(16).padStart(6,"0")}"`:void 0):Nr.test(e.trim())?null:k(`${t} is not a colour: ${JSON.stringify(e)}.`,'use "#rrggbb"');case"enum":case"select":{let n=Ar(r);if(n.length===0||n.includes(e))return null;let s=typeof e=="string"?n.find(o=>typeof o=="string"&&o.toLowerCase()===e.toLowerCase()):void 0;return{code:"E_PROPERTY_ENUM",message:`${t} is ${v(e)}; allowed: ${n.map(o=>JSON.stringify(o)).join(", ")}.`,fix:s!==void 0?`write ${t}: ${String(s)}`:void 0}}case"vector2":{let n=Ee(e,["x","y"]);return n?k(`${t}: ${n}.`,`write ${t}: [x, y]`):null}case"vector3":case"euler":{let n=Ee(e,["x","y","z"]);return n?k(`${t}: ${n}.`,`write ${t}: [x, y, z]`):null}case"vector4":{let n=Ee(e,["x","y","z","w"]);return n?k(`${t}: ${n}.`,`write ${t}: [x, y, z, w]`):null}case"node":return e===null||typeof e=="string"?null:k(`${t} must be a node id (string), got ${v(e)}.`);case"object":return Et(r)?be("texture",e,t):null;default:return null}},be=(r,e,t,n)=>{switch(r){case"texture":return e===null||typeof e=="string"||p(e)&&typeof e.url=="string"?null:k(`${t} must be a texture: { type: texture, url: res://\u2026 } or a path string, got ${v(e)}.`);case"resource-path":return e===null||typeof e=="string"?null:k(`${t} must be a path string (res://\u2026), got ${v(e)}.`);case"array":return Array.isArray(e)?null:k(`${t} must be a list, got ${v(e)}.`);case"record":return p(e)?null:k(`${t} must be a mapping, got ${v(e)}.`);case"enum":return n===void 0||n.includes(e)?null:{code:"E_PROPERTY_ENUM",message:`${t} is ${v(e)}; allowed: ${n.join(", ")}.`};default:return we({name:t,type:r,getValue:()=>{},setValue:()=>{}},e,t)}},St=Et;var Tr=Be,wt=new Map,ve=r=>{let e=wt.get(r.type);if(e)return e;let t=Tr[r.classExport],n=typeof t?.getPropertySchema=="function"?t.getPropertySchema().properties:[];return wt.set(r.type,n),n},te=class{constructor(e){this.project=e}project;cache=new Map;get(e){let t=this.cache.get(e);if(t)return t;let n;if(!this.project.has(e))n={ok:!1,reason:"missing"};else{let s=R(this.project.readText(e));if("errors"in s||!p(s.data))n={ok:!1,reason:"unparsable"};else{let o=s.data.root;n={ok:!0,roots:Array.isArray(o)?o:[]}}}return this.cache.set(e,n),n}instanceTargets(e){let t=this.get(e);if(!t.ok)return[];let n=[],s=o=>{if(p(o)){if(typeof o.instance=="string"){let i=O(o.instance);i&&n.push(i)}Array.isArray(o.children)&&o.children.forEach(s)}};return t.roots.forEach(s),n}findCycle(e,t){if(t.includes(e))return[...t.slice(t.indexOf(e)),e];let n=[...t,e];for(let s of this.instanceTargets(e)){let o=this.findCycle(s,n);if(o)return o}return null}effectiveDefinition(e,t=new Set){if(typeof e.instance!="string")return e;let n=O(e.instance);if(!n||t.has(n))return null;let s=this.get(n);return!s.ok||s.roots.length!==1||!p(s.roots[0])?null:this.effectiveDefinition(s.roots[0],new Set([...t,n]))}localIdOf(e){let t=this.effectiveDefinition(e);return t&&typeof t.id=="string"?t.id:null}},bt=/^res:\/\//i,vt=r=>typeof r=="string"?r.trim()!=="":p(r)&&typeof r.url=="string"&&r.url.trim()!=="",Pe=r=>vt(r.texture??r.texturePath),re=class{constructor(e,t,n){this.env=e;this.file=t;this.parsed=n}env;file;parsed;diagnostics=[];references=new Set;labelKeys=[];spriteKeys=[];usesUserComponents=!1;svgChecked=new Set;seenIds=new Map;report(e){let{at:t,...n}=e;this.diagnostics.push(g({...n,file:this.file,path:t?P(t):n.path,line:t?this.parsed.lineOf(t):void 0}))}noteLabelKey(e,t,n){typeof e!="string"||e.trim()===""||this.labelKeys.push({file:this.file,key:e,nodeId:t,path:P(n),line:this.parsed.lineOf(n)})}noteSpriteKey(e,t,n,s,o){typeof e!="string"||e.trim()===""||this.spriteKeys.push({file:this.file,key:e,property:t,hasOwnTexture:n,nodeId:s,path:P(o),line:this.parsed.lineOf(o)})}noteSpriteKeys(e,t,n,s,o,i){if(e==="Sprite2D"&&t==="textureKey")this.noteSpriteKey(n,t,s(),o,i);else if(e==="Button2D"&&t==="stateTextureKeys"&&p(n))for(let[a,c]of Object.entries(n))this.noteSpriteKey(c,`${t}.${a}`,!0,o,[...i,a]);else e==="Button2D"&&/^texture(?:Normal|Hover|Pressed|Disabled)Key$/.test(t)&&this.noteSpriteKey(n,t,!0,o,i)}run(){let e=this.parsed.data;if(!p(e)){this.report({code:"E_SHAPE",message:`A scene must be a YAML mapping with a root: list, got ${e===null?"an empty document":typeof e}.`,fix:"start the file with `version: 1.0.0` and `root:`",at:[]});return}for(let t of Object.keys(e))ce.includes(t)||this.report({code:"E_SHAPE",message:`"${t}" is not a scene document key (allowed: ${ce.join(", ")}); the loader ignores it.`,fix:t==="nodes"||t==="children"?"rename it to root:":void 0,at:[t]});if(e.version===void 0?this.report({code:"W_LEGACY_VERSION",message:`The scene has no version:; the current format is ${j}.`,fix:`add version: ${j}`,at:[]}):String(e.version)!==j&&this.report({code:"W_LEGACY_VERSION",message:`Scene format version ${String(e.version)} is not the current ${j}; the loader has no migrations and reads it as ${j}.`,fix:`set version: ${j}`,at:["version"]}),e.metadata!==void 0&&e.metadata!==null&&!p(e.metadata)&&this.report({code:"E_SHAPE",message:"metadata: must be a mapping.",at:["metadata"]}),e.root===void 0||e.root===null){this.report({code:"E_SHAPE",message:"The scene has no root: list \u2014 it would load as an empty scene.",fix:"add root: with at least one node",at:[]});return}if(!Array.isArray(e.root)){this.report({code:"E_SHAPE",message:`root: must be a list of nodes, got ${p(e.root)?"a mapping":typeof e.root}.`,fix:"write each root node as a list item: root:\\n - id: \u2026",at:["root"]});return}e.root.forEach((t,n)=>this.checkNode(t,["root",n]))}checkNode(e,t){if(!p(e)){this.report({code:"E_SHAPE",message:`A node must be a mapping with at least id: and type:, got ${e===null?"null":typeof e}.`,at:t});return}let n=typeof e.id=="string"&&e.id.trim().length>0?e.id:void 0;if(n===void 0)this.report({code:"E_SHAPE",message:e.id===void 0?"The node has no id: \u2014 the loader accepts it with an undefined id, and a second one collides.":`id: must be a non-empty string, got ${JSON.stringify(e.id)}.`,fix:"give every node a unique string id:",at:e.id===void 0?t:[...t,"id"]});else{let a=this.seenIds.get(n);a!==void 0?this.report({code:"E_DUPLICATE_ID",nodeId:n,message:`Duplicate node id "${n}" (first used at ${a}); the loader rejects the scene.`,fix:"rename one of them",at:[...t,"id"]}):this.seenIds.set(n,P(t))}let s=n;for(let a of Object.keys(e))le.includes(a)||this.report({code:"E_SHAPE",nodeId:s,message:`"${a}" is not a node key (allowed: ${le.join(", ")}); the loader ignores it.`,fix:"if it is a property, move it under properties:",at:[...t,a]});for(let a of["name","type"])e[a]!==void 0&&typeof e[a]!="string"&&this.report({code:"E_SHAPE",nodeId:s,message:`${a}: must be a string.`,at:[...t,a]});e.groups!==void 0&&!(Array.isArray(e.groups)&&e.groups.every(a=>typeof a=="string"))&&this.report({code:"E_SHAPE",nodeId:s,message:"groups: must be a list of strings.",at:[...t,"groups"]}),e.metadata!==void 0&&e.metadata!==null&&!p(e.metadata)&&this.report({code:"E_SHAPE",nodeId:s,message:"metadata: must be a mapping.",at:[...t,"metadata"]});let o=e.properties;o!=null&&!p(o)&&this.report({code:"E_SHAPE",nodeId:s,message:`properties: must be a mapping, got ${Array.isArray(o)?"a list":typeof o}.`,at:[...t,"properties"]});let i=p(o)?o:{};if(e.instance!==void 0){this.checkInstanceNode(e,s,i,t);return}this.checkNodeType(e,s,i,t),this.checkComponents(e.components,s,t),e.children!==void 0&&e.children!==null&&(Array.isArray(e.children)?e.children.forEach((a,c)=>this.checkNode(a,[...t,"children",c])):this.report({code:"E_SHAPE",nodeId:s,message:`children: must be a list of nodes, got ${p(e.children)?"a mapping":typeof e.children}.`,at:[...t,"children"]}))}checkNodeType(e,t,n,s){let o=typeof e.type=="string"?e.type:void 0,i=o===void 0?"Node3D":ae(o);if(i==="Layout2D"){this.report({code:"E_UNKNOWN_NODE_TYPE",nodeId:t,message:"Layout2D is no longer supported; the loader rejects the scene.",fix:"use a Group2D (or Node2D) with layout: anchors",at:[...s,"type"]});return}if(i===null){let c=o?De(o):void 0;this.report({code:"E_UNKNOWN_NODE_TYPE",nodeId:t,message:Ie(String(e.name??t??"?"),String(o)),fix:c?`type: ${c}`:void 0,at:[...s,"type"]});return}if(o!==void 0&&o!==i){let c=ie(o)===ie(i);this.report({code:"W_TYPE_CASE",nodeId:t,message:c?`type: ${o} loads as ${i}, but only because the loader forgives case and separators; the editor rewrites it on save.`:`type: ${o} is a read-compat alias of ${i}; the editor rewrites it on save.`,fix:`type: ${i}`,at:[...s,"type"]})}let a=pe(i);a&&this.checkNodeProperties(a,n,t,[...s,"properties"])}checkNodeProperties(e,t,n,s){let o=ve(e);for(let[i,a]of Object.entries(t)){let c=[...s,i];i==="labelKey"&&this.noteLabelKey(a,n,c),this.noteSpriteKeys(e.type,i,a,()=>Pe(t),n,c),e.type==="AnimatedSprite3D"&&i==="frames"&&Array.isArray(a)&&this.checkFrameList(a,n,c);let l=fe(e,o,i);switch(l.kind){case"schema":this.checkSchemaValue(l.property,a,n,c,i);break;case"write-only":this.report({code:"W_WRITE_ONLY_PROPERTY",nodeId:n,message:`${e.type}.${i} is saved by the editor but not read back by the loader, so it has no effect after a reload.`,at:c});break;case"extra":this.checkRule(e,l.rule,a,n,c,i);break;case"relocated":this.report({code:"E_UNKNOWN_PROPERTY",nodeId:n,message:`${e.type} does not read "${i}" here \u2014 on disk it lives at ${l.diskPath}. As written it loads, is saved back, and does nothing.`,fix:`move it to properties.${l.diskPath}`,at:c});break;case"not-stored":this.report({code:"E_UNKNOWN_PROPERTY",nodeId:n,message:`"${i}" is not stored under properties: \u2014 ${l.reason}.`,fix:i==="id"||i==="name"||i==="type"||i==="groups"?`move it up to the node entry (${i}:)`:"remove it",at:c});break;case"unknown":{let d=[...o.filter(f=>fe(e,o,f.name).kind==="schema").map(f=>f.name),...Object.keys(e.extras)],u=C(i,d);this.report({code:"E_UNKNOWN_PROPERTY",nodeId:n,message:`${e.type} has no property "${i}"; the loader keeps it, saves it back, and nothing reads it.`,fix:u?`did you mean ${u}?`:void 0,at:c});break}}this.checkStrings(a,c,n)}}checkRule(e,t,n,s,o,i){t.preferred&&this.report({code:"W_LEGACY_KEY",nodeId:s,message:`${i} is a read-compat spelling; the editor saves it as ${t.preferred}.`,fix:`rename to ${t.preferred}`,at:o});let a=ve(e),c=t.schemaName?a.find(l=>l.name===t.schemaName):void 0;if(c)this.checkSchemaValue(c,n,s,o,i);else if(t.kind){let l=be(t.kind,n,i,t.options);l&&this.report({...l,nodeId:s,at:o}),!l&&(t.kind==="texture"||t.kind==="resource-path")&&this.checkResourceValue(n,s,o)}if(t.nested&&p(n))for(let[l,d]of Object.entries(n)){let u=t.nested[l],f=[...o,l];if(!u){let h=C(l,Object.keys(t.nested));this.report({code:"E_UNKNOWN_PROPERTY",nodeId:s,message:`${i}.${l} is not read by the loader (known: ${Object.keys(t.nested).join(", ")}).`,fix:h?`did you mean ${i}.${h}?`:void 0,at:f});continue}this.checkRule(e,u,d,s,f,`${i}.${l}`)}}checkSchemaValue(e,t,n,s,o){let i=we(e,t,o);if(i){this.report({...i,nodeId:n,at:s});return}St(e)&&this.checkResourceValue(t,n,s)}checkResourceValue(e,t,n){let s=typeof e=="string"?e:p(e)&&typeof e.url=="string"?e.url:null;!s||bt.test(s)||s.trim()===""||this.checkReference(s,t,p(e)?[...n,"url"]:n)}checkReference(e,t,n){let s=O(e);if(!s)return;if(this.references.add(s),this.env.project.has(s)){H(s)&&this.checkSvg(s,e,t,n);return}let o=this.env.project.files.find(i=>i.toLowerCase()===s.toLowerCase());this.report({code:"E_MISSING_RESOURCE",nodeId:t,message:`${e} does not exist in the project.`,fix:o?`the file is res://${o} (case differs)`:void 0,at:n})}checkFrameList(e,t,n){e.forEach((s,o)=>{let i=[...n,o];if(vt(s)){this.checkResourceValue(s,t,i);return}this.report({code:"E_PROPERTY_TYPE",nodeId:t,message:`frames[${o}] is ${s===null?"null":typeof s=="string"?"an empty string":JSON.stringify(s)}, not a texture: the sprite shows nothing on that frame.`,fix:"a res:// image path (or { url: res://\u2026 })",at:i})})}checkSvg(e,t,n,s){if(!this.svgChecked.has(e)){this.svgChecked.add(e);for(let o of Y(this.env.project,e))this.report({code:o.code,nodeId:n,message:`${t}: ${o.message}`,fix:o.fix,at:s})}}checkStrings(e,t,n){if(typeof e=="string"){bt.test(e)&&this.checkReference(e,n,t);let s=[...t].reverse().find(i=>typeof i=="string"),o=s?We(s,e):null;o&&this.report({code:"E_EMOJI_AS_ART",nodeId:n,message:o.message,fix:o.fix,at:t});return}if(Array.isArray(e))e.forEach((s,o)=>this.checkStrings(s,[...t,o],n));else if(p(e))for(let[s,o]of Object.entries(e))this.checkStrings(o,[...t,s],n)}checkComponents(e,t,n){if(e==null)return;let s=[...n,"components"];if(!Array.isArray(e)){this.report({code:"E_SHAPE",nodeId:t,message:`components: must be a list, got ${p(e)?"a mapping":typeof e}.`,fix:"write each component as a list item: components:\\n - type: core:Rotate",at:s});return}e.forEach((o,i)=>this.checkComponent(o,t,[...s,i]))}checkComponent(e,t,n){if(!p(e)){this.report({code:"E_SHAPE",nodeId:t,message:"A component must be a mapping with type:.",at:n});return}for(let a of Object.keys(e))de.includes(a)||this.report({code:"E_SHAPE",nodeId:t,message:`"${a}" is not a component key (allowed: ${de.join(", ")}).`,fix:"component settings go under config:",at:[...n,a]});let s=e.type;if(typeof s!="string"||s.trim()===""){this.report({code:"E_SHAPE",nodeId:t,message:"The component has no type: \u2014 the loader parks it as an unregistered component.",fix:"add type: core:<Name> or user:<ExportedClass>",at:n});return}e.enabled!==void 0&&typeof e.enabled!="boolean"&&this.report({code:"E_SHAPE",nodeId:t,message:"enabled: must be true or false.",at:[...n,"enabled"]}),e.id!==void 0&&typeof e.id!="string"&&this.report({code:"E_SHAPE",nodeId:t,message:"A component id: must be a string.",at:[...n,"id"]});let o=e.config;o!=null&&!p(o)&&this.report({code:"E_SHAPE",nodeId:t,message:"config: must be a mapping.",at:[...n,"config"]});let i=p(o)?o:{};if(this.checkStrings(i,[...n,"config"],t),s.startsWith("core:")){if(!this.env.registry.getComponentType(s)){let l=C(s,this.env.registry.getAllComponentTypes().map(d=>d.id));this.report({code:"E_UNKNOWN_COMPONENT",nodeId:t,message:`${s} is not a built-in component; the loader parks it and it never runs.`,fix:l?`type: ${l}`:void 0,at:[...n,"type"]});return}let c=this.env.registry.getComponentPropertySchema(s)?.properties??[];this.checkConfig(s,c,i,t,[...n,"config"]);return}if(s.startsWith("user:")){if(this.usesUserComponents=!0,this.env.userScripts.ids.has(s)||this.env.userScripts.opaqueEntries.length>0)return;let a=C(s,this.env.userScripts.ids.keys()),c=s.slice(5);this.report({code:"E_USER_SCRIPT_NOT_FOUND",nodeId:t,message:`No script under scripts/ (or src/scripts/) exports a Script class named ${c}; the component would stay unregistered. The id is the EXPORT name of a Script class in a .ts file (not .js) under those folders.`,fix:a?`type: ${a}`:`create scripts/${c}.ts with export class ${c} extends Script`,at:[...n,"type"]});return}this.report({code:"E_UNKNOWN_COMPONENT",nodeId:t,message:`Component type "${s}" has no namespace: built-ins are core:<Name>, project scripts user:<ExportedClass>.`,fix:this.env.registry.getComponentType(`core:${s}`)?`type: core:${s}`:`type: user:${s}`,at:[...n,"type"]})}checkConfig(e,t,n,s,o){for(let[i,a]of Object.entries(n)){let c=t.find(l=>l.name===i);if(!c){let l=C(i,t.map(d=>d.name));this.report({code:"E_UNKNOWN_CONFIG_KEY",nodeId:s,message:`${e} has no setting "${i}" (declared: ${t.map(d=>d.name).join(", ")||"none"}); it is kept and ignored.`,fix:l?`did you mean ${l}?`:void 0,at:[...o,i]});continue}this.checkSchemaValue(c,a,s,[...o,i],i)}}checkInstanceNode(e,t,n,s){for(let u of["children","components"])e[u]!==void 0&&this.report({code:"E_SHAPE",nodeId:t,message:`${u}: on an instance node is ignored \u2014 the node's content comes from the prefab.`,fix:"move them into the prefab, or wrap the instance in a parent node",at:[...s,u]});if(typeof e.instance!="string"||e.instance.trim()===""){this.report({code:"E_SHAPE",nodeId:t,message:"instance: must be a res:// path to a .pix3scene prefab.",at:[...s,"instance"]});return}let o=O(e.instance),i=[...s,"instance"];if(!o||!this.env.project.has(o)){this.report({code:"E_MISSING_PREFAB",nodeId:t,message:`Prefab ${e.instance} does not exist; the loader fails the whole scene.`,at:i});return}this.references.add(o);let a=this.env.prefabs.findCycle(o,[this.file]);if(a){this.report({code:"E_PREFAB_CYCLE",nodeId:t,message:`Prefab instances form a cycle: ${a.join(" -> ")}.`,at:i});return}let c=this.env.prefabs.get(o);if(!c.ok)return;if(c.roots.length!==1){this.report({code:"E_PREFAB_ROOT",nodeId:t,message:`Prefab ${e.instance} has ${c.roots.length} root nodes; an instanced prefab must have exactly one.`,at:i});return}let l=p(c.roots[0])?c.roots[0]:null,d=l?this.env.prefabs.effectiveDefinition(l):null;d&&this.checkInstanceProperties(d,n,t,[...s,"properties"]),this.checkOverrides(e.overrides,l,t,[...s,"overrides"])}checkInstanceProperties(e,t,n,s){let o=typeof e.type=="string"?ae(e.type):"Node3D",i=o?pe(o):null;if(!i)return;let a=ve(i);for(let[c,l]of Object.entries(t)){let d=[...s,c];if(this.checkStrings(l,d,n),c==="labelKey"&&this.noteLabelKey(l,n,d),this.noteSpriteKeys(i.type,c,l,()=>Pe(t)||p(e.properties)&&Pe(e.properties),n,d),c==="transform"){if(!p(l)){this.report({code:"E_PROPERTY_TYPE",nodeId:n,message:"transform must be a mapping.",at:d});continue}for(let[f,h]of Object.entries(l)){let x=ue[f],b=x?a.find(E=>E.name===x):void 0;if(!b){this.report({code:"E_UNKNOWN_PROPERTY",nodeId:n,message:`transform.${f} is not applied to an instance (known: ${Object.keys(ue).join(", ")}).`,at:[...d,f]});continue}this.checkSchemaValue(b,h,n,[...d,f],`transform.${f}`)}continue}let u=a.find(f=>f.name===c);if(!u){let f=C(c,a.map(h=>h.name));this.report({code:"E_UNKNOWN_PROPERTY",nodeId:n,message:`The prefab's ${i.type} has no property "${c}"; on an instance (and in overrides) properties are the node's schema names.`,fix:f?`did you mean ${f}?`:void 0,at:d});continue}this.checkSchemaValue(u,l,n,d,c)}}checkOverrides(e,t,n,s){if(e==null)return;if(!p(e)||e.byLocalId!==void 0&&!p(e.byLocalId)){this.report({code:"E_SHAPE",nodeId:n,message:"overrides: must be a mapping of the form { byLocalId: { <node path>: { properties: \u2026 } } }.",at:s});return}let o=p(e.byLocalId)?e.byLocalId:{};for(let[i,a]of Object.entries(o)){let c=[...s,"byLocalId",i];if(!p(a)||a.properties!==void 0&&!p(a.properties)){this.report({code:"E_SHAPE",nodeId:n,message:"An override entry must be { properties: { \u2026 } }.",at:c});continue}if(!t)continue;let l=this.resolveOverrideTarget(t,i);if(l!==void 0){if(l===null){this.report({code:"E_UNKNOWN_OVERRIDE_TARGET",nodeId:n,message:`The prefab has no node at "${i}"; the loader warns and drops this override.`,fix:"override keys are node ids from the prefab root, joined with / for nested nodes",at:c});continue}this.checkInstanceProperties(l,p(a.properties)?a.properties:{},n,[...c,"properties"])}}}resolveOverrideTarget(e,t){let n=this.env.prefabs,s=n.localIdOf(e),o=n.effectiveDefinition(e);if(!s||!o)return;let i=t.replace(/\\/g,"/").replace(/^\/+/,"").trim(),a=[i.split("/")];if(i===s)return o;i.startsWith(`${s}/`)&&a.push(i.slice(s.length+1).split("/"));for(let c of a){let l=o;for(let d of c){let u=Array.isArray(l?.children)?l.children:[],f=null;for(let h of u){if(!p(h))continue;let x=n.localIdOf(h);if(x===null&&typeof h.instance=="string")return;if(x===d){f=n.effectiveDefinition(h);break}}if(l=f,!l)break}if(l)return l}return null}},Pt=(r,e,t)=>{let n=R(t);if("errors"in n)return{diagnostics:n.errors.map(o=>g({code:"E_YAML",file:e,line:o.line,message:`YAML does not parse: ${o.message}`})),references:new Set,parsed:null,labelKeys:[],spriteKeys:[],usesUserComponents:!1};let s=new re(r,e,n);return s.run(),{diagnostics:s.diagnostics,references:s.references,parsed:n,labelKeys:s.labelKeys,spriteKeys:s.spriteKeys,usesUserComponents:s.usesUserComponents}},kt=(r,e,t,n,s,o,i,a)=>{let c=new re(r,e,t);return c.checkConfig(n,s,o,i,a),c.diagnostics};import{join as Jr}from"node:path";var _t=()=>{let r={addColorStop:()=>{}};return new Proxy({},{get(e,t){return t==="measureText"?n=>({width:n.length*10,actualBoundingBoxAscent:10,actualBoundingBoxDescent:3,actualBoundingBoxLeft:0,actualBoundingBoxRight:n.length*10}):t==="getImageData"?()=>({data:new Uint8ClampedArray(4),width:1,height:1}):typeof t=="string"&&t.startsWith("create")?()=>r:()=>{}},set:()=>!0})},Lr=()=>({style:{},src:"",crossOrigin:null,complete:!1,width:0,height:0,addEventListener:()=>{},removeEventListener:()=>{}}),$t=()=>({createElement(r){if(r!=="canvas")throw new Error(`canvas-only document shim: createElement('${r}') is not provided`);return{width:1,height:1,style:{},getContext:e=>e==="2d"?_t():null,addEventListener:()=>{},removeEventListener:()=>{}}},createElementNS(r,e){if(e!=="img")throw new Error(`canvas-only document shim: createElementNS('${e}') is not provided`);return Lr()}}),ke=()=>typeof Reflect.get(globalThis,"document")<"u"?()=>{}:(Reflect.set(globalThis,"document",$t()),()=>{Reflect.deleteProperty(globalThis,"document")});import{closeSync as Cr,existsSync as Ot,openSync as Dr,readFileSync as Nt,readSync as Ir,statSync as Mr}from"node:fs";import{isAbsolute as Ur,join as Fr,relative as Wr,resolve as At}from"node:path";function Rt(r){if(r.length<10)return null;let e=new DataView(r.buffer,r.byteOffset,r.byteLength),t=(n,s)=>String.fromCharCode(...r.subarray(n,n+s));if(r.length>=24&&e.getUint32(0)===2303741511&&t(12,4)==="IHDR")return A(e.getUint32(16),e.getUint32(20));if(t(0,4)==="GIF8")return A(e.getUint16(6,!0),e.getUint16(8,!0));if(r[0]===255&&r[1]===216){let n=2;for(;n+9<r.length;){if(r[n]!==255)return null;let s=r[n+1];if(s===255){n+=1;continue}if(s===216||s===1||s>=208&&s<=215){n+=2;continue}let o=e.getUint16(n+2);if(s>=192&&s<=207&&![196,200,204].includes(s))return A(e.getUint16(n+7),e.getUint16(n+5));n+=2+o}return null}if(r.length>=30&&t(0,4)==="RIFF"&&t(8,4)==="WEBP"){let n=t(12,4);if(n==="VP8 ")return A(e.getUint16(26,!0)&16383,e.getUint16(28,!0)&16383);if(n==="VP8L"){let s=e.getUint32(21,!0);return A((s&16383)+1,(s>>>14&16383)+1)}if(n==="VP8X"){let s=o=>r[o]|r[o+1]<<8|r[o+2]<<16;return A(s(24)+1,s(27)+1)}return null}return jr(r)}function A(r,e){return r>0&&e>0?{width:r,height:e}:null}function jr(r){let e=new TextDecoder("utf-8").decode(r.subarray(0,4096)),t=/<svg\b[^>]*>/i.exec(e)?.[0];if(!t)return null;let n=l=>new RegExp(`\\s${l}\\s*=\\s*["']([^"']*)["']`,"i").exec(t)?.[1]??null,s=l=>{let d=l?/^\s*([0-9]*\.?[0-9]+)\s*(px)?\s*$/i.exec(l):null;return d?Number(d[1]):null},o=s(n("width")),i=s(n("height")),a=n("viewBox")?.trim().split(/[\s,]+/).map(Number),c=a&&a.length===4&&a.every(Number.isFinite)&&a[2]>0&&a[3]>0?a[2]/a[3]:null;return o!==null&&i!==null?A(o,i):o!==null?A(o,c?o/c:150):i!==null?A(c?i*c:300,i):A(300,150)}var z=class extends Te{projectDir;transformText;constructor(e,t={}){super("/"),this.projectDir=At(e),this.transformText=t.transformText}pathOf(e){let t=this.normalize(e).replace(/^\/+/,"");return this.containedPath(t)}isProjectResource(e){let t=this.getScheme(e);return t===""||t==="res"}exists(e){let t=this.pathOf(e);if(!t||!Ot(t))return!1;try{return Mr(t).isFile()}catch{return!1}}async fetchText(e){let t=this.requirePath(e),n=Nt(t,"utf8");return this.transformText?this.transformText(n,t):n}async fetchBlob(e){return new Blob([new Uint8Array(Nt(this.requirePath(e)))])}containedPath(e){let t=decodeURIComponent(e.replace(/^\/+/,"")),n=At(Fr(this.projectDir,t)),s=Wr(this.projectDir,n);return s.startsWith("..")||Ur(s)?null:n}requirePath(e){let t=`res://${e.replace(/^\/+/,"")}`,n=this.containedPath(e);if(!n)throw new T(t,"resolves outside the project folder");if(!Ot(n))throw new T(t);return n}},T=class extends Error{resource;constructor(e,t="file not found"){super(`Missing resource ${e} (${t})`),this.name="MissingResourceError",this.resource=e}},D=class extends Error{resource;constructor(e){super(`Resource ${e} exists but is not decoded outside the browser`),this.name="ResourceNotDecodedError",this.resource=e}},G=class extends je{missingSet=new Set;disk;constructor(e){super(e),this.disk=e}get missing(){return[...this.missingSet]}async loadTexture(e,t){this.require(e);let n=new Le,s=this.disk.isProjectResource(e)?this.disk.pathOf(e):null,o=s?Kr(s):null;return o&&(n.image={width:o.width,height:o.height}),n}async loadAsset(e){throw this.require(e),new D(e)}async loadSpineAsset(e){throw this.require(e.skeletonPath),this.require(e.atlasPath),e.texturePath&&this.require(e.texturePath),new D(e.skeletonPath)}require(e){if(this.disk.isProjectResource(e)&&!this.disk.exists(e))throw this.missingSet.add(e),new T(e)}};function Kr(r){let e=null;try{e=Dr(r,"r");let t=new Uint8Array(64*1024),n=Ir(e,t,0,t.length,0);return Rt(t.subarray(0,n))}catch{return null}finally{e!==null&&Cr(e)}}var Vr=[/^Lit is in dev mode/],Yr=r=>r instanceof Error?r.message:typeof r=="string"?r:String(r);async function _e(r){let e=[],t={warn:console.warn,debug:console.debug,info:console.info,log:console.log};console.warn=(...n)=>{let s=n.find(i=>i instanceof Error);if(s instanceof D)return;let o=n.map(Yr).join(" ");Vr.some(i=>i.test(o))||e.push({message:o,error:s})},console.debug=()=>{},console.info=()=>{},console.log=()=>{};try{return{result:await r(),warnings:e}}finally{console.warn=t.warn,console.debug=t.debug,console.info=t.info,console.log=t.log}}import{mkdtempSync as Hr,rmSync as zr,writeFileSync as Gr}from"node:fs";import{tmpdir as Br}from"node:os";import{join as $e}from"node:path";import{pathToFileURL as qr}from"node:url";var Tt=async r=>{try{return await import(r)}catch{return null}},Re="pix3-stub",Lt=(r,e,t)=>({stdin:{contents:e.map((n,s)=>`export * as __pix3_entry_${s} from ${JSON.stringify(`./${n}`)};`).join(`
|
|
220
|
+
`),resolveDir:r.root,sourcefile:t,loader:"ts"},bundle:!0,write:!1,format:"esm",target:"es2022",logLevel:"silent",loader:{".glsl":"text",".vert":"text",".frag":"text",".css":"empty",".wasm":"binary"},define:{"import.meta.env.BASE_URL":JSON.stringify("/"),"import.meta.env.DEV":"true","import.meta.env.PROD":"false","import.meta.env.MODE":JSON.stringify("development")}}),jt=(r,e,t,n)=>({name:r,setup(s){s.onResolve({filter:/^@pix3\/runtime$/},()=>({path:e.runtime,external:!0})),s.onResolve({filter:/^three$/},()=>({path:e.three,external:!0})),s.onResolve({filter:/\?(raw|url)$/},o=>{let[i,a]=o.path.split("?");return{path:$e(o.resolveDir,i),namespace:a==="raw"?"pix3-raw":"pix3-url"}}),s.onResolve({filter:/^[^./]/},o=>o.kind==="entry-point"?void 0:n(o,s)),s.onLoad({filter:/.*/,namespace:Re},()=>({contents:"module.exports = {};",loader:"js"})),s.onLoad({filter:/.*/,namespace:"pix3-raw"},async o=>{let{readFile:i}=await import("node:fs/promises");return{contents:`export default ${JSON.stringify(await i(o.path,"utf8"))};`,loader:"js"}}),s.onLoad({filter:/.*/,namespace:"pix3-url"},o=>({contents:`export default ${JSON.stringify(`res://${t.relativeOf(o.path)??o.path}`)};`,loader:"js"}))}}),Ct=r=>{let e=r.errors??[];return e.length>0?e:[{text:String(r),location:null}]},Dt=async(r,e,t,n)=>{let s=Hr($e(Br(),n)),o=$e(s,"scripts.mjs");try{Gr(o,r);let i=await import(qr(o).href),a=[];return e.forEach((c,l)=>{let d=i[`__pix3_entry_${l}`];p(d)&&a.push(...He(t,d,c))}),a}finally{zr(s,{recursive:!0,force:!0})}};var It={runtime:"@pix3/runtime",three:"three"},Mt=async(r,e,t,n,s="esbuild")=>{if(e.length===0)return{status:"none"};let o=await Tt(s);if(!o)return{status:"unavailable",reason:"esbuild is not installed (it is an optional dependency of @pix3/cli), so project scripts cannot be compiled"};let i;try{i=(await o.build({...Lt(r,e,"pix3-validate-scripts.ts"),absWorkingDir:r.root,platform:"neutral",plugins:[jt("pix3-validate-host-imports",n,r,c=>({path:c.path,namespace:Re}))]})).outputFiles?.[0]?.text??""}catch(a){return{status:"failed",diagnostics:Ct(a).map(l=>g({code:"E_SCRIPT_COMPILE",file:l.location?.file?r.relativeOf(Jr(r.root,l.location.file))??l.location.file:"scripts",line:l.location?.line,message:`Project scripts do not compile: ${l.text}. The editor registers none of them, so every user: component stays unregistered.`}))}}try{return{status:"loaded",registered:await Dt(i,e,t,"pix3-validate-")}}catch(a){return{status:"failed",diagnostics:[g({code:"W_SCRIPT_IMPORT",file:"scripts",message:`The compiled project scripts threw when imported in Node (${a instanceof Error?a.message:String(a)}) \u2014 probably a browser global used at module top level. user: component properties not checked.`})]}}},Xr=/Component type "(.*?)" is not registered yet — kept as pending on node "(.*?)"/,Zr=/Override target "(.*?)" not found/,Ut=async r=>{let{env:e,registry:t,file:n}=r,s=[],o=new z(e.project.root),i=new Ue(new G(o),t,o),a=ke();try{let{result:c,warnings:l}=await _e(()=>i.parseScene(r.text,{filePath:`res://${n}`}));for(let d of l){let u=Xr.exec(d.message);u?s.push(g({code:"E_PENDING_COMPONENT",file:n,nodeId:u[2],message:`Component ${u[1]} on node "${u[2]}" is still unregistered after compiling the project scripts; it never runs.`})):d.error instanceof T?s.push(g({code:"E_MISSING_RESOURCE",file:n,message:`${d.error.resource} does not exist (the loader warned: ${d.message.split(":")[0]}).`})):Zr.test(d.message)?s.push(g({code:"E_UNKNOWN_OVERRIDE_TARGET",file:n,message:`${d.message.replace(/^\[SceneLoader\]\s*/,"")} The override is dropped.`})):s.push(g({code:"E_LOAD",file:n,message:`The loader warned: ${d.message.replace(/^\[SceneLoader\]\s*/,"")}`}))}if(r.checkUserConfig&&Qr(r.parsed.data,(d,u,f,h)=>{if(!d.startsWith("user:"))return;let x=t.getComponentPropertySchema(d)?.properties;x&&s.push(...kt(e,n,r.parsed,d,x,u,f,h))}),!r.isPrefab)for(let d of Fe(c.rootNodes,{targetPlatform:r.targetPlatform}))d.code!=="inert-nodes"&&s.push(g({code:`W_RENDERABILITY_${d.code.toUpperCase().replace(/-/g,"_")}`,file:n,nodeId:d.nodeIds[0],message:d.message}))}catch(c){c instanceof Me?s.push(g({code:"E_LOAD",file:n,message:`The loader rejects the scene: ${c.message}${c.details.length>0?` (${c.details.join("; ")})`:""}`})):c instanceof T?s.push(g({code:"E_MISSING_RESOURCE",file:n,message:`${c.resource} does not exist; the loader fails the whole scene.`})):s.push(g({code:"E_LOAD",file:n,message:`The loader threw ${c instanceof Error?`${c.name}: ${c.message}`:String(c)}.`}))}finally{a()}return s},Qr=(r,e)=>{let t=(n,s)=>{if(!p(n))return;let o=typeof n.id=="string"?n.id:void 0;Array.isArray(n.components)&&n.components.forEach((i,a)=>{p(i)&&typeof i.type=="string"&&e(i.type,p(i.config)?i.config:{},o,[...s,"components",a,"config"])}),Array.isArray(n.children)&&n.children.forEach((i,a)=>t(i,[...s,"children",a]))};p(r)&&Array.isArray(r.root)&&r.root.forEach((n,s)=>t(n,["root",s]))};var Ft=q(J(),1);var Oe=".pix3anim",Wt=r=>{let e=R(r);return"errors"in e?()=>{}:t=>e.lineOf(t)},Kt=(r,e)=>{let t=[],n;try{n=r.readText(e)}catch{return t}let s;try{s=JSON.parse(n)}catch(d){return t.push(g({code:"E_ANIM_JSON",file:e,message:`${e} is not valid JSON (${d instanceof Error?d.message:String(d)}): the AnimatedSprite2D that plays it draws nothing.`,fix:"a .pix3anim is JSON \u2014 see pix3anim.md in the pix3-scene-format skill"})),t}let o=Wt(n),i=(d,u,f,h)=>{t.push(g({code:d,file:e,path:P(u),line:o(u),message:f,fix:h}))};if(!p(s))return i("E_ANIM_JSON",[],`${e} must be a JSON object ({ "version", "texturePath", "clips" }), got ${Array.isArray(s)?"a list":s===null?"null":typeof s}.`),t;s.clips!==void 0&&!Array.isArray(s.clips)&&i("E_ANIM_JSON",["clips"],'"clips" must be a list of clips: the loader finds none.');let a=[];typeof s.texturePath=="string"&&s.texturePath.trim()&&a.push({value:s.texturePath,at:["texturePath"],what:"the spritesheet"}),(Array.isArray(s.clips)?s.clips:[]).forEach((d,u)=>{if(!p(d)||!Array.isArray(d.frames))return;let f=typeof d.name=="string"&&d.name?d.name:`#${u+1}`;d.frames.forEach((h,x)=>{!p(h)||typeof h.texturePath!="string"||!h.texturePath.trim()||a.push({value:h.texturePath,at:["clips",u,"frames",x,"texturePath"],what:`clip "${f}" frame ${x+1}`})})});let l=new Set;for(let{value:d,at:u,what:f}of a){let h=O(d);if(h){if(!r.has(h)){let x=r.files.find(b=>b.toLowerCase()===h.toLowerCase());i("E_MISSING_FRAME",u,`${f}: ${d} does not exist in the project \u2014 the sprite draws nothing on that frame.`,x?`the file is res://${x} (case differs)`:void 0);continue}if(!(!H(h)||l.has(h))){l.add(h);for(let x of Y(r,h))i(x.code,u,`${f}: ${d}: ${x.message}`,x.fix)}}}return t},Vt=r=>{let e;if(r.has(w))try{let s=(0,Ft.parse)(r.readText(w));e=p(s)?s.localization:void 0}catch{e=void 0}if(p(e)){let s=Array.isArray(e.locales)?[...new Set(e.locales.filter(i=>typeof i=="string"&&i!==""))]:[],o=typeof e.defaultLocale=="string"&&e.defaultLocale?e.defaultLocale:s[0]??"";if(o){let i=typeof e.fallbackLocale=="string"&&e.fallbackLocale?e.fallbackLocale:o;return{defaultLocale:o,fallbackLocale:i,locales:s.includes(o)?s:[o,...s],declared:!0}}}let t=r.files.map(s=>/^locales\/([^/]+)\.json$/.exec(s)?.[1]??null).filter(s=>s!==null).sort();if(t.length===0)return null;let n=t.includes("en")?"en":t[0];return{defaultLocale:n,fallbackLocale:n,locales:t,declared:!1}},W=r=>`locales/${r}.json`,en=(r,e)=>r.defaultLocale===e?["localization","defaultLocale"]:r.fallbackLocale===e?["localization","fallbackLocale"]:["localization","locales",r.locales.indexOf(e)],Yt=(r,e)=>{let t=[],n=new Map,s=[...new Set([e.defaultLocale,e.fallbackLocale,...e.locales])];for(let o of s){let i=o===e.defaultLocale||o===e.fallbackLocale,a=o===e.defaultLocale?"the default locale":i?"the fallback locale":"a declared locale",c=i?"every labelKey shows the key itself":`its texts fall back to ${e.fallbackLocale}`,l=W(o),d=E=>`${i?"E":"W"}_${E}`;if(!r.has(l)){let E,m=en(e,o);if(e.declared){let y=R(r.readText(w));E="errors"in y?void 0:y.lineOf(m)}t.push(g({code:d("LOCALE_MISSING"),file:e.declared?w:l,...e.declared?{path:P(m),line:E}:{},message:`${a} "${o}" has no table ${l}: the game loads it empty \u2014 ${c}.`,fix:`write ${l} (copy the default locale's table), or drop "${o}" from localization:`}));continue}let u=r.readText(l),f;try{f=JSON.parse(u)}catch(E){t.push(g({code:d("LOCALE_JSON"),file:l,message:`${l} is not valid JSON (${E instanceof Error?E.message:String(E)}): ${a} "${o}" loads empty \u2014 ${c}.`,fix:'{ "$meta": { "locale": "\u2026" }, "strings": { "key": "text" }, "sprites": {} }'}));continue}let h=Wt(u);if(!p(f)){t.push(g({code:d("LOCALE_JSON"),file:l,message:`${l} must be a JSON object with "strings" (and "sprites"): ${a} "${o}" loads empty \u2014 ${c}.`}));continue}let x={},b={};for(let E of["strings","sprites"]){let m=f[E];if(m!==void 0){if(!p(m)){t.push(g({code:d("LOCALE_JSON"),file:l,path:E,line:h([E]),message:`"${E}" of ${l} must be an object of key \u2192 ${E==="strings"?"text":"res:// image"}; the runtime reads none of it \u2014 ${c}.`}));continue}for(let[y,S]of Object.entries(m)){if(typeof S=="string"){E==="strings"?x[y]=S:(b[y]=S,t.push(...tn(r,l,y,S,h)));continue}t.push(g({code:d("LOCALE_VALUE"),file:l,path:P([E,y]),line:h([E,y]),message:`${E}."${y}" in ${l} is ${Array.isArray(S)?"a list":S===null?"null":typeof S=="object"?"an object":`a ${typeof S}`}, not a string: the runtime drops it${i?" and the key shows as itself":`, the text falls back to ${e.fallbackLocale}`}.`,fix:p(S)?`keys are flat: "${y}.${Object.keys(S)[0]??"name"}": "\u2026"`:`write it as a string: "${y}": "${String(S)}"`}))}}}n.set(o,{strings:x,sprites:b})}return{diagnostics:t,tables:n}},tn=(r,e,t,n,s)=>{let o=O(n);if(!o)return[];let i=["sprites",t],a={file:e,path:P(i),line:s(i)};if(!r.has(o)){let c=r.files.find(l=>l.toLowerCase()===o.toLowerCase());return[g({code:"E_MISSING_LOCALE_SPRITE",...a,message:`sprites."${t}" in ${e} is ${n}, which does not exist in the project: a node keyed to "${t}" draws nothing in this locale.`,fix:c?`the file is res://${c} (case differs)`:void 0})]}return H(o)?Y(r,o).map(c=>g({code:c.code,...a,message:`sprites."${t}": ${n}: ${c.message}`,fix:c.fix})):[]},ne=(r,e,t)=>{let n=e.get(r.defaultLocale)?.[t];if(!n)return null;let s=e.get(r.fallbackLocale)?.[t];return o=>!!(n[o]||s?.[o])},B=r=>`${W(r.defaultLocale)}${r.fallbackLocale!==r.defaultLocale?` nor in ${W(r.fallbackLocale)}`:""}`,Ht=(r,e,t)=>{let n=ne(r,e,"strings");if(!n)return[];let s=W(r.defaultLocale);return t.filter(o=>!n(o.key)).map(o=>g({code:"E_LOCALE_KEY",file:o.file,nodeId:o.nodeId,path:o.path,line:o.line,message:`labelKey "${o.key}" has no text in ${B(r)}: the node shows "${o.key}" on screen.`,fix:`add "${o.key}": "<text>" to "strings" of ${s} (and the other locales)`}))},zt=(r,e,t)=>{let n=ne(r,e,"sprites");if(!n)return[];let s=W(r.defaultLocale);return t.filter(o=>!n(o.key)).map(o=>g({code:o.hasOwnTexture?"W_LOCALE_SPRITE_KEY":"E_LOCALE_SPRITE_KEY",file:o.file,nodeId:o.nodeId,path:o.path,line:o.line,message:`${o.property} "${o.key}" has no image in "sprites" of ${B(r)}: ${o.hasOwnTexture?"the node shows its own texture in every locale":"the node has no texture of its own and draws nothing"}.`,fix:`add "${o.key}": "res://sprites/\u2026" to "sprites" of ${s} (and the other locales)`}))},rn=/\.(?:[cm]?[jt]s|[jt]sx)$/,nn=r=>rn.test(r)&&!Ke.test(r),Gt=(r,e,t)=>{let n=ne(e,t,"strings"),s=ne(e,t,"sprites");if(!n||!s)return[];let o=W(e.defaultLocale),i=[];for(let a of r.files){if(!nn(a))continue;let c;try{c=r.readText(a)}catch{continue}for(let{fn:l,key:d,line:u}of ze(c)){let f=`${l}('${d}')`;if(l==="trSprite"){if(s(d))continue;i.push(g({code:"W_LOCALE_SPRITE_KEY",file:a,line:u,message:`${f}: "${d}" has no image in "sprites" of ${B(e)}, so the script gets null.`,fix:`add "${d}": "res://sprites/\u2026" to "sprites" of ${o} (and the other locales)`}));continue}if(l==="trPlural"&&n(`${d}.other`)||n(d))continue;let h=l==="trPlural"?`${d}.other`:d;i.push(g({code:"E_LOCALE_SCRIPT_KEY",file:a,line:u,message:l==="trPlural"?`${f}: neither "${d}.other" nor "${d}" has text in ${B(e)}: a count with no "${d}.<one|few|many>" of its own shows "${d}" on screen.`:`${f}: "${d}" has no text in ${B(e)}: the game shows "${d}" on screen.`,fix:`add "${h}": "<text>" to "strings" of ${o} (and the other locales)`}))}}return i};var sn=r=>r.replace(/\/\*[\s\S]*?\*\//g," ").replace(/(^|[^:\\])\/\/.*$/gm,"$1"),on=/\bexport\s+(default\s+)?(?:abstract\s+)?class\s+([A-Za-z_$][\w$]*)?/g,an=/\bexport\s+(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*=\s*class\b/g,cn=/\bexport\s+(type\s+)?\{([^}]*)\}/g,ln=/\bexport\s+\*\s+(?:as\s+[A-Za-z_$][\w$]*\s+)?from\b/,se=r=>{let e=sn(r),t=[];for(let n of e.matchAll(on))t.push(n[1]?"default":n[2]??"default");for(let n of e.matchAll(an))t.push(n[1]);for(let n of e.matchAll(cn))if(!n[1])for(let s of n[2].split(",")){let o=s.trim();if(!o||o.startsWith("type "))continue;let i=/\bas\s+([A-Za-z_$][\w$]*)$/.exec(o);t.push(i?i[1]:o.split(/\s+/)[0])}return{names:t,hasStar:ln.test(e)}};var Bt=r=>{let e=new Map,t=[],n=[];for(let s of r.files){if(!X(s))continue;let o;try{o=r.readText(s)}catch{continue}t.push(s);let{names:i,hasStar:a}=se(o);a&&n.push(s);for(let c of i){let l=`${Ve}${c}`;e.has(l)||e.set(l,s)}}return{ids:e,entries:t,opaqueEntries:n}};var qt=`- singleton: GameState
|
|
221
|
+
scriptPath: scripts/GameState.ts`,Jt=r=>{if(!r.has(w))return[];let e=R(r.readText(w));if("errors"in e||!p(e.data))return[];let t=e.data.autoloads;if(t==null)return[];let n=[],s=(i,a,c)=>{n.push(g({code:"E_AUTOLOAD",file:w,path:P(i),line:e.lineOf(i),message:a,...c?{fix:c}:{}}))};if(!Array.isArray(t))return s(["autoloads"],"autoloads: must be a list; the game reads none.",`autoloads:
|
|
222
|
+
${qt}`),n;let o=new Set;return t.forEach((i,a)=>{let c=["autoloads",a],[l]=me([i]);if(!l){s(c,"an autoload needs a singleton name and a scriptPath; this one is skipped.",qt);return}if(o.has(l.singleton)){s([...c,"singleton"],`singleton "${l.singleton}" is declared twice; the game keeps the first.`,"give each autoload its own singleton name");return}if(o.add(l.singleton),!l.enabled)return;let d=O(l.scriptPath)??l.scriptPath,u=he(d),f=u.slice(5),h=[...c,"scriptPath"];if(!Ye(d)){s(h,`${d} is not under scripts/ or src/scripts/: project scripts outside them are never registered, so "${l.singleton}" never runs.`,`move it to scripts/${f}.ts`);return}if(!r.has(d)){s(h,`${d} does not exist: autoload "${l.singleton}" never runs.`,`create ${d} with export class ${f} extends Script`);return}if(!X(d)){s(h,`${d} is not a script entry (project scripts are .ts files under scripts/ or src/scripts/, not .js, specs, tests or declarations): autoload "${l.singleton}" never runs.`,`move the class to scripts/${f}.ts`);return}let x=r.readText(d),{names:b,hasStar:E}=se(x);!E&&!b.includes(f)&&s(h,`${d} does not export ${f}: an autoload's class is the one named like its file (${u}), so "${l.singleton}" never runs.`,`export class ${f} extends Script (or point scriptPath at the file named like the class)`)}),n},Xt=(r,e)=>{if(!r.has(w))return[];let t=R(r.readText(w));if("errors"in t||!p(t.data)||!Array.isArray(t.data.autoloads))return[];let n=[],s=new Set;return t.data.autoloads.forEach((o,i)=>{let[a]=me([o]);if(!a||s.has(a.singleton)||(s.add(a.singleton),!a.enabled))return;let c=O(a.scriptPath)??a.scriptPath;if(!r.has(c))return;let l=he(c);if(e.getComponentType(l))return;let d=["autoloads",i,"scriptPath"];n.push(g({code:"E_AUTOLOAD",file:w,path:P(d),line:t.lineOf(d),message:`${c} compiles, but registers no Script class ${l.slice(5)} (${l}): autoload "${a.singleton}" never runs.`,fix:`export class ${l.slice(5)} extends Script`}))}),n};var Zt=r=>{if(!r.has(w))return[];let e=R(r.readText(w));return"errors"in e||!p(e.data)||!("export"in e.data)?[]:[g({code:"W_MANIFEST_EXPORT",file:w,path:"export",line:e.lineOf(["export"]),message:"export: is the asset selection of the 1.x build; the 2.x build ignores it (every scene, prefab and whatever the project references by res:// ships \u2014 see dist/<name>.report.json).",fix:"Delete the export: block. Build options (format, entry scene, compress, strip) are pix3({ \u2026 }) in vite.config.ts."})]};var Ne="(user: component properties not checked)",Qt=[".pix3scene",".prefab",".ts",".js",".json",".pix3anim",".atlas",".yaml",".yml",".html",".css"],dn=["sprites/","audio/"],pn=(r,e)=>{let t=r.files.filter(s=>Qt.some(o=>s.endsWith(o))).map(s=>{try{return{file:s,text:r.readText(s)}}catch{return{file:s,text:""}}}),n=[];for(let s of r.files){if(!dn.some(c=>s.startsWith(c)))continue;let o=s.slice(s.lastIndexOf("/")+1);if(o.startsWith(".")||Qt.some(c=>s.endsWith(c))||e.has(s))continue;let i=s.slice(0,s.lastIndexOf("/")+1);t.some(({file:c,text:l})=>c!==s&&(l.includes(s)||c.startsWith(i)&&c.slice(i.length).indexOf("/")===-1&&l.includes(o)))||n.push(g({code:"W_UNUSED_ASSET",file:s,message:`${s} is not referenced by any scene, script or resource file.`,fix:"use it, or delete it to keep exports small"}))}return n},oe=async r=>{let e=new F(r.projectRoot),t=new Ce,n=console.log;console.log=()=>{};try{Ge(t)}finally{console.log=n}let s={project:e,registry:t,userScripts:Bt(e),prefabs:new te(e)},o=r.files===void 0,i=[...r.files??e.scenes()].sort(),a=[],c=[],l=new Map,d=new Map;for(let m of i){if(!e.has(m)){a.push(g({code:"E_SHAPE",file:m,message:`${m} does not exist.`}));continue}let y=e.readText(m);d.set(m,y),c.push({file:m,sha256:ut(e,m)});let S=Pt(s,m,y);l.set(m,S),a.push(...S.diagnostics)}if(o){let m=new Set;for(let y of l.values())for(let S of y.references)m.add(S);a.push(...pn(e,m))}let u=new Set(o?e.files.filter(m=>m.endsWith(Oe)):[]);for(let m of l.values())for(let y of m.references)y.endsWith(Oe)&&e.has(y)&&u.add(y);for(let m of[...u].sort())a.push(...Kt(e,m));let f=Vt(e);if(f){let m=Yt(e,f);o&&(a.push(...m.diagnostics),a.push(...Gt(e,f,m.tables)));let y=[...l.values()],S=y.flatMap(M=>M.labelKeys);a.push(...Ht(f,m.tables,S));let I=y.flatMap(M=>M.spriteKeys);a.push(...zt(f,m.tables,I))}let h=Jt(e);a.push(...h),a.push(...Zt(e));let x=[...l.values()].some(m=>m.usesUserComponents),b=[],E;if(r.hydrate===!1)E={state:"disabled"},x&&b.push(`Scenes not loaded as the game loads them (--no-hydrate) ${Ne}.`);else{let m=i.filter(_=>{let N=l.get(_);return N?.parsed&&!N.diagnostics.some(L=>L.severity==="error")}),y=await Mt(e,s.userScripts.entries,t,r.scriptImports??It,r.esbuildSpecifier),S=!0,I=!1;if(y.status==="unavailable"?(S=!1,I=!0,a.push(g({code:"W_HYDRATE_SKIPPED",file:"scripts",message:`Scenes with user: components not loaded as the game loads them: ${y.reason} ${Ne}.`,fix:"npm install esbuild next to @pix3/cli, or run with --no-hydrate"}))):y.status==="failed"&&(S=!1,I=!0,a.push(...y.diagnostics)),y.status==="loaded"){let _=L=>(L??"").split(".")[0],N=new Set(h.map(L=>_(L.path)));a.push(...Xt(e,t).filter(L=>!N.has(_(L.path))))}let M=new Set;for(let _ of e.scenes())for(let N of s.prefabs.instanceTargets(_))M.add(N);let{targetPlatform:rr}=ht(e),K=0,V=0;for(let _ of m){let N=l.get(_);if(N?.parsed){if(I&&N.usesUserComponents){V+=1;continue}a.push(...await Ut({env:s,registry:t,file:_,text:d.get(_)??"",parsed:N.parsed,targetPlatform:rr,isPrefab:M.has(_)||_.toLowerCase().endsWith(".prefab"),checkUserConfig:S})),K+=1}}E={state:"ran",filesHydrated:K,filesSkipped:i.length-K,...V>0?{filesSkippedForScripts:V}:{}},i.length-K>0&&b.push(`${i.length-K} of ${i.length} scene file(s) were not loaded as the game loads them: that runs only on files with no structural errors${I?" and, while scripts cannot be loaded, without user: components":""}.`),y.status==="failed"&&V>0&&b.push(`SKIPPED: ${V} scene(s) with user: components were not loaded as the game loads them because the project scripts do not compile (E_SCRIPT_COMPILE above) \u2014 E_UNKNOWN_CONFIG_KEY, E_PROPERTY_TYPE and every other user: component check are missing from this report, not passed. Fix the compile error and run again.`),!S&&x&&b.push(`Project scripts could not be loaded ${Ne}.`)}return a.sort(st),{projectRoot:e.root,files:c,diagnostics:a,errorCount:a.filter(m=>m.severity==="error").length,warningCount:a.filter(m=>m.severity==="warning").length,level2:E,notes:b}};var hn=r=>{let e=[],t=!1,n=!0,s,o=!1;for(let i=0;i<r.length;i++){let a=r[i];if(a==="--json")t=!0;else if(a==="--no-hydrate")n=!1;else if(a==="--help"||a==="-h")o=!0;else if(a==="--project"){if(s=r[++i],!s||s.startsWith("--"))return{error:"--project needs a folder"}}else if(a.startsWith("--project="))s=a.slice(10);else{if(a.startsWith("-"))return{error:`unknown option ${a}`};e.push(a)}}return{paths:e,json:t,hydrate:n,project:s,help:o}},mn=(r,e,t)=>{let n=new Set;for(let s of t){let o=Ae(e,s),i=r.relativeOf(o);if(i===null)return{error:`${s} is outside the project ${r.root}`};if(fn(o)&&un(o).isDirectory()){let a=i===""?"":`${i}/`;for(let c of r.scenes())c.startsWith(a)&&n.add(c)}else if(xe(i))n.add(i);else return{error:`${s} is not a .pix3scene or .prefab file`}}return{files:[...n]}},gn=r=>{let e="";for(let n of r.diagnostics)e+=ot(n);for(let n of r.notes)e+=`note: ${n}
|
|
223
|
+
`;let t=it(r.level2);return e+=`${r.files.length} file(s) checked, ${r.errorCount} error(s), ${r.warningCount} warning(s); ${t}.
|
|
224
|
+
`,e},yn=r=>`${JSON.stringify({ok:r.errorCount===0,projectRoot:r.projectRoot,errorCount:r.errorCount,warningCount:r.warningCount,level2:r.level2,files:r.files,diagnostics:r.diagnostics,notes:r.notes},null,2)}
|
|
225
|
+
`,er=async(r,e)=>{let t=hn(r);if("error"in t)return e.stderr(`pix3 validate: ${t.error}
|
|
226
|
+
|
|
227
|
+
${Q.validate}`),2;if(t.help)return e.stdout(nt()),0;let n=t.project?Ae(e.cwd,t.project):t.paths.length>0?Ae(e.cwd,t.paths[0]):e.cwd,s=t.project?n:Qe(n);if(!s)return e.stderr(`pix3 validate: no pix3project.yaml in ${n} or any parent folder. Run it inside a Pix3 project, or pass --project <dir>.
|
|
228
|
+
`),2;let o=new F(s),i=t.paths.length>0?mn(o,e.cwd,t.paths):null;if(i&&"error"in i)return e.stderr(`pix3 validate: ${i.error}
|
|
229
|
+
`),2;let a=await oe({projectRoot:s,files:i?.files,hydrate:t.hydrate,scriptImports:e.scriptImports,esbuildSpecifier:e.esbuildSpecifier});return e.stdout(t.json?yn(a):gn(a)),a.errorCount>0?1:0};var tr=()=>({runtime:new URL("./runtime.js",import.meta.url).href,three:new URL("./three.js",import.meta.url).href}),Do=(r,e)=>er(r,{...e,scriptImports:tr()}),Io=r=>oe({...r,scriptImports:tr()});export{Do as runBundledValidate,Io as validateBundledProject};
|