@zixcel/dom-renderer 0.0.0-stage → 0.10.1

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.
Files changed (42) hide show
  1. package/LICENSE +202 -0
  2. package/LICENSE-PREVIOUS +21 -0
  3. package/NOTICE +5 -0
  4. package/README.md +36 -2
  5. package/package.json +38 -4
  6. package/src/book-reader.d.mts +1 -0
  7. package/src/book-reader.mjs +36 -0
  8. package/src/frame-layout.d.mts +13 -0
  9. package/src/frame-layout.mjs +25 -0
  10. package/src/frame.d.mts +13 -0
  11. package/src/frame.mjs +47 -0
  12. package/src/index.d.mts +57 -0
  13. package/src/index.mjs +205 -0
  14. package/src/inspection-overview.d.mts +1 -0
  15. package/src/inspection-overview.mjs +35 -0
  16. package/src/npc-motion.d.mts +5 -0
  17. package/src/npc-motion.mjs +7 -0
  18. package/src/object-presentation.d.mts +55 -0
  19. package/src/object-presentation.mjs +24 -0
  20. package/src/open-world-projection.d.mts +29 -0
  21. package/src/open-world-projection.mjs +26 -0
  22. package/src/reconcile.d.mts +1 -0
  23. package/src/reconcile.mjs +21 -0
  24. package/src/spatial-patterns.d.mts +29 -0
  25. package/src/spatial-patterns.mjs +43 -0
  26. package/src/spatial.d.mts +8 -0
  27. package/src/spatial.mjs +27 -0
  28. package/src/structure-graph.d.mts +16 -0
  29. package/src/structure-graph.mjs +97 -0
  30. package/src/style.css +128 -0
  31. package/src/symbol.d.mts +2 -0
  32. package/src/symbol.mjs +19 -0
  33. package/src/world-gpu.d.mts +15 -0
  34. package/src/world-gpu.mjs +79 -0
  35. package/src/world-input.d.mts +3 -0
  36. package/src/world-input.mjs +21 -0
  37. package/src/world-model.d.mts +49 -0
  38. package/src/world-model.mjs +83 -0
  39. package/src/world-runtime.d.mts +36 -0
  40. package/src/world-runtime.mjs +312 -0
  41. package/src/world-surface.d.mts +7 -0
  42. package/src/world-surface.mjs +14 -0
@@ -0,0 +1,97 @@
1
+ // Hatter 2026: bounded document-containment graph. No meaning inference or writes.
2
+ import {element} from './index.mjs'
3
+ import {inspectionOverview} from './inspection-overview.mjs'
4
+ export function graphDocuments(documents,{component='',query='',depth=3,maximum=256}={}){
5
+ if(!Array.isArray(documents)||documents.length>32||!Number.isInteger(depth)||depth<1||depth>6)throw Error('GraphInputInvalid')
6
+ const nodes=[],edges=[];let truncated=false
7
+ const add=(value,label,path,level,parent,document)=>{
8
+ if(nodes.length>=maximum){truncated=true;return}
9
+ const id=document.id+':'+path,node={id,label,path,level,value,document}
10
+ nodes.push(node);if(parent)edges.push({from:parent,to:id,label})
11
+ if(value&&typeof value==='object'){
12
+ const entries=Object.entries(value)
13
+ if(level>=depth){if(entries.length)truncated=true;return}
14
+ for(const [key,child]of entries){if(nodes.length>=maximum){truncated=true;break}add(child,key,path+'/'+key.replaceAll('~','~0').replaceAll('/','~1'),level+1,id,document)}
15
+ }
16
+ }
17
+ for(const doc of documents)if(!component||doc.id===component)add(doc.value,doc.label,'',0,null,doc)
18
+ if(query){const matches=new Set(nodes.filter(n=>(n.label+' '+n.path+' '+(n.value===null||typeof n.value!=='object'?String(n.value):'')).toLowerCase().includes(query.toLowerCase())).map(n=>n.id));return {nodes:nodes.filter(n=>matches.has(n.id)),edges:edges.filter(e=>matches.has(e.from)&&matches.has(e.to)),truncated}}
19
+ return {nodes,edges,truncated}
20
+ }
21
+ export function structureGraph(document,host,view,onRole,onRefresh,{embedded=false}={}){
22
+ const controller=new AbortController(),signal=controller.signal
23
+ let drawing=new AbortController()
24
+ let component='',query='',depth=1,zoom=1,pan={x:0,y:0},drag=null,selected=null,mode='overview'
25
+ const wrap=element(document,'section',null,{'aria-label':'Semantic structure graph'})
26
+ Object.assign(wrap.style,{position:'absolute',inset:'110px 12px 12px 110px',display:'grid',gridTemplateRows:'auto minmax(0,1fr) auto',background:'Canvas',border:'1px solid GrayText',borderRadius:'12px',overflow:'hidden'})
27
+ const media=document.defaultView.matchMedia('(max-width:600px)'),resize=()=>{wrap.style.inset=embedded?'auto':media.matches?'105px 8px 84px':'110px 12px 12px 110px'};resize();media.addEventListener('change',resize,{signal})
28
+ if(embedded)Object.assign(wrap.style,{position:'relative',height:'65dvh',minHeight:'300px',width:'100%'})
29
+ const toolbar=element(document,'div'),footer=element(document,'div'),detail=element(document,'details'),summary=element(document,'summary','Select a node for exact source details'),body=element(document,'dl')
30
+ Object.assign(toolbar.style,{display:'flex',gap:'6px',padding:'8px',flexWrap:'wrap'});Object.assign(footer.style,{padding:'8px',maxHeight:'35vh',overflow:'auto'})
31
+ const control=(label,values,current,change)=>{const select=element(document,'select',null,{'aria-label':label});for(const [value,text]of values)select.append(element(document,'option',text,{value}));select.value=current;select.addEventListener('change',()=>change(select.value),{signal});toolbar.append(select);return select}
32
+ const roleSelect=control('Role',view.roles.map(r=>[r.id,r.id]),view.selected??'',onRole)
33
+ const tabs=element(document,'div',null,{role:'tablist','aria-label':'Inspection view'}),tabButtons=[]
34
+ toolbar.append(tabs)
35
+ const graphControls=element(document,'div',null,{class:'inspection-graph-controls'})
36
+ Object.assign(graphControls.style,{display:'flex',gap:'6px',flexWrap:'wrap'})
37
+ Object.assign(tabs.style,{display:'flex',gap:'6px'})
38
+ const componentSelect=control('Component',[['','All components'],...view.documents.map(d=>[d.id,d.label])],'',value=>{component=value;selected=null;draw()})
39
+ control('Structure depth',[1,2,3,4,5,6].map(n=>[String(n),'Depth '+n]),'1',value=>{depth=Number(value);draw()})
40
+ const search=element(document,'input',null,{type:'search','aria-label':'Filter graph',placeholder:'Filter nodes'});search.style.width='140px';search.addEventListener('input',()=>{query=search.value;draw()},{signal});toolbar.append(search)
41
+ const svg=document.createElementNS('http://www.w3.org/2000/svg','svg'),group=document.createElementNS(svg.namespaceURI,'g')
42
+ svg.setAttribute('aria-label','Document containment graph');svg.setAttribute('role','group');Object.assign(svg.style,{width:'100%',height:'100%',minHeight:'0',touchAction:'none',background:'color-mix(in srgb,Canvas 96%,#5368df)'})
43
+ svg.append(group);const status=element(document,'p',null,{role:'status'});status.style.margin='0'
44
+ const button=(label,action)=>{const b=element(document,'button',label,{type:'button'});b.addEventListener('click',action,{signal});toolbar.append(b);return b}
45
+ const apply=()=>group.setAttribute('transform',`translate(${pan.x} ${pan.y}) scale(${zoom})`)
46
+ button('−',()=>{zoom=Math.max(.5,zoom/1.2);apply()});button('+',()=>{zoom=Math.min(8,zoom*1.2);apply()});button('Fit',()=>{zoom=1;pan={x:0,y:0};apply()})
47
+ const refresh=button('Refresh',()=>void onRefresh())
48
+ svg.addEventListener('wheel',e=>{if(e.ctrlKey||e.metaKey)return;e.preventDefault();zoom=Math.max(.5,Math.min(8,zoom*(e.deltaY<0?1.1:1/1.1)));apply()},{signal,passive:false})
49
+ svg.addEventListener('pointerdown',e=>{if(e.button||e.target.closest('[data-graph-node]'))return;drag={id:e.pointerId,x:e.clientX,y:e.clientY,pan:{...pan}};svg.setPointerCapture(e.pointerId)},{signal})
50
+ svg.addEventListener('pointermove',e=>{if(drag?.id!==e.pointerId)return;const matrix=svg.getScreenCTM();if(matrix){pan={x:Math.max(-5000,Math.min(5000,drag.pan.x+(e.clientX-drag.x)/matrix.a)),y:Math.max(-5000,Math.min(5000,drag.pan.y+(e.clientY-drag.y)/matrix.d))};apply()}},{signal})
51
+ for(const type of ['pointerup','pointercancel','lostpointercapture'])svg.addEventListener(type,()=>drag=null,{signal})
52
+ function inspect(node){selected=node.id;summary.textContent=node.document.label+' · '+(node.path||'/');detail.open=true;body.replaceChildren()
53
+ const source=node.document.source??{},fields=[['path',node.path||'/'],['source',source.method??node.document.id],['semantic revision',source.semanticRevision??''],['role',source.roleRef?.id??''],['role revision',source.roleRef?.revision??''],['role digest',source.roleRef?.digest_sha256??''],['memory revision',source.memoryRevision??''],...Object.entries((node.value&&typeof node.value==='object')?node.value:{value:node.value}).slice(0,64).map(([k,v])=>['field: '+k,v])]
54
+ for(const [key,value]of fields)body.append(element(document,'dt',key),element(document,'dd',value!==null&&typeof value==='object'?(Array.isArray(value)?'Array':'Object')+' ('+Object.keys(value).length+')':String(value)))
55
+ highlight()
56
+ }
57
+ function highlight(){for(const n of group.querySelectorAll('[data-graph-node]'))n.querySelector('rect').setAttribute('stroke',n.dataset.graphNode===selected?'#d97706':'#5368df');for(const line of group.querySelectorAll('line'))line.setAttribute('stroke',line.dataset.from===selected||line.dataset.to===selected?'#d97706':'#a5adc6')}
58
+ function draw(){
59
+ drawing.abort();drawing=new AbortController()
60
+ selected=null;detail.open=false;body.replaceChildren();summary.textContent='Select a node for exact source details'
61
+ const graph=graphDocuments(view.documents,{component,query,depth}),levels=new Map(),positions=new Map()
62
+ for(const node of graph.nodes){const list=levels.get(node.level)??[];list.push(node);levels.set(node.level,list)}
63
+ const height=Math.max(240,...[...levels.values()].map(ns=>ns.length*64+30)),width=(Math.max(0,...levels.keys())+1)*220+20
64
+ svg.setAttribute('viewBox',`0 0 ${width} ${height}`);group.replaceChildren()
65
+ const make=(tag,attrs)=>{const n=document.createElementNS(svg.namespaceURI,tag);for(const [k,v]of Object.entries(attrs))n.setAttribute(k,String(v));return n}
66
+ for(const [level,nodes]of levels)nodes.forEach((n,i)=>positions.set(n.id,{x:level*220+10,y:(i+.5)*height/nodes.length}))
67
+ for(const edge of graph.edges){const a=positions.get(edge.from),b=positions.get(edge.to);group.append(make('line',{x1:a.x+170,y1:a.y,x2:b.x,y2:b.y,'data-from':edge.from,'data-to':edge.to,stroke:'#a5adc6','stroke-width':2}))}
68
+ for(const node of graph.nodes){const p=positions.get(node.id),g=make('g',{transform:`translate(${p.x} ${p.y-20})`,'data-graph-node':node.id,role:'button',tabindex:0,'aria-label':node.document.label+' '+(node.path||'/')})
69
+ g.append(make('rect',{width:170,height:40,rx:8,fill:'Canvas',stroke:'#5368df','stroke-width':2}));const title=make('title',{});title.textContent=node.document.label+' '+(node.path||'/');const text=make('text',{x:8,y:25,fill:'CanvasText','font-size':13});text.textContent=node.label.length>21?node.label.slice(0,20)+'…':node.label;g.append(title,text);g.addEventListener('click',()=>inspect(node),{signal:drawing.signal});g.addEventListener('keydown',e=>{if(e.key==='Enter'||e.key===' '){e.preventDefault();inspect(node)}},{signal:drawing.signal});group.append(g)
70
+ }
71
+ status.textContent=graph.nodes.length+' nodes · '+graph.edges.length+' containment links'+(graph.truncated?' · Depth or node limit: increase depth or select a component.':'')+(view.documents.some(d=>d.source?.truncated)?' · Source incomplete.':'')
72
+ if(!view.documents.length)status.textContent='No adopted semantic structure available. Inspect owner readiness in Notifications.'
73
+ apply();highlight()
74
+ }
75
+ // One inspector, two views. Switching changes presentation only, not reads,
76
+ // selected role, semantic state or the world/camera behind the inspector.
77
+ for(const child of [...toolbar.children])if(child!==roleSelect&&child!==tabs&&child!==refresh)graphControls.append(child)
78
+ toolbar.append(graphControls)
79
+ const stage=element(document,'div',null,{class:'inspection-stage'}),overview=element(document,'div',null,{class:'inspection-overview',role:'tabpanel','aria-label':'Responsibility overview'}),graphPanel=element(document,'div',null,{class:'inspection-graph-panel',role:'tabpanel','aria-label':'Structure view'})
80
+ Object.assign(stage.style,{minHeight:'0',overflow:'hidden'});Object.assign(overview.style,{height:'100%',overflow:'auto',padding:'1rem',overflowWrap:'anywhere'});graphPanel.style.height='100%'
81
+ graphPanel.append(svg);stage.append(overview,graphPanel)
82
+ function display(){
83
+ overview.hidden=mode!=='overview';graphPanel.hidden=mode!=='structure';graphControls.hidden=mode!=='structure';footer.hidden=mode!=='structure'
84
+ for(const b of tabButtons){const active=b.dataset.view===mode;b.setAttribute('aria-selected',String(active));b.tabIndex=active?0:-1;b.style.borderColor=active?'Highlight':'GrayText';b.style.fontWeight=active?'bold':'normal'}
85
+ }
86
+ for(const [value,label]of [['overview','Overview'],['structure','Structure']]){
87
+ const tab=element(document,'button',label,{type:'button',role:'tab','data-view':value})
88
+ tab.style.minHeight='44px'
89
+ tab.addEventListener('click',()=>{mode=value;display()},{signal})
90
+ tab.addEventListener('keydown',event=>{if(['ArrowLeft','ArrowRight','Home','End'].includes(event.key)){event.preventDefault();mode=event.key==='Home'?'overview':event.key==='End'?'structure':mode==='overview'?'structure':'overview';display();tabButtons.find(b=>b.dataset.view===mode).focus()}},{signal})
91
+ tabs.append(tab);tabButtons.push(tab)
92
+ }
93
+ let overviewEvents=new AbortController()
94
+ function summaryView(){overviewEvents.abort();overviewEvents=new AbortController();inspectionOverview(document,overview,view.overview,id=>{component=id;componentSelect.value=id;mode='structure';draw();display()},overviewEvents.signal)}
95
+ detail.append(summary,body);footer.append(status,detail);wrap.append(toolbar,stage,footer);host.replaceChildren(wrap);draw();summaryView();display()
96
+ return {update(next){view=next;roleSelect.replaceChildren(...view.roles.map(r=>element(document,'option',r.id,{value:r.id})));roleSelect.value=view.selected??'';componentSelect.replaceChildren(element(document,'option','All components',{value:''}),...view.documents.map(d=>element(document,'option',d.label,{value:d.id})));if(!view.documents.some(d=>d.id===component))component='';componentSelect.value=component;draw();summaryView();display()},close(){overviewEvents.abort();drawing.abort();controller.abort();wrap.remove()}}
97
+ }
package/src/style.css ADDED
@@ -0,0 +1,128 @@
1
+ :root{font-family:system-ui,sans-serif;color-scheme:light dark;--space:clamp(.75rem,2vw,1.5rem)}*{box-sizing:border-box}body{margin:0}main{min-height:100dvh;padding:var(--space);max-width:100%;overflow-wrap:anywhere}header,nav,.item-tools{display:flex;align-items:center;gap:.65rem;flex-wrap:wrap}header{justify-content:space-between}h1{font-size:1.4rem}h2{font-size:1.1rem}button,input,select,textarea{font:inherit;max-width:100%}button{padding:.5rem .8rem;cursor:pointer;border:1px solid GrayText;border-radius:.4rem;background:Canvas;color:CanvasText}button:focus-visible,a:focus-visible,input:focus-visible,select:focus-visible,textarea:focus-visible{outline:3px solid Highlight;outline-offset:3px}button:disabled{opacity:.6;cursor:default}button[aria-pressed=true]{border-color:Highlight;background:Highlight;color:HighlightText}ul.scene-items{list-style:none;padding:0;display:grid;gap:var(--space)}.scene-items>li{padding:var(--space);border:1px solid GrayText;border-radius:.5rem;min-width:0}.item-tools{margin-bottom:.5rem}.item-tools button:first-child{max-width:100%;overflow-wrap:anywhere;text-align:start}dl{display:grid;grid-template-columns:minmax(5rem,1fr) minmax(0,3fr);gap:.3rem .7rem}dt{font-weight:600}dd{margin:0;min-width:0}dd dl{display:block}dd dd{padding-inline-start:.6rem}[role=alert]{border-inline-start:4px solid #c17a00;padding:.75rem;background:color-mix(in srgb,Canvas 93%,#c17a00)}form{display:grid;gap:1rem;max-width:40rem}label{display:grid;gap:.3rem}input,select{padding:.6rem;border:1px solid GrayText;border-radius:.35rem}table{border-collapse:collapse;max-width:100%}td,th{text-align:start;padding:.5rem;border-bottom:1px solid GrayText}@media(max-width:480px){dl{grid-template-columns:minmax(0,1fr)}dd{padding-inline-start:.6rem}header{align-items:flex-start}}
2
+ /* Hatter downstream 2026: CSS depth is presentation, never canonical position/time.
3
+ One DOM tree; responsive flow, no animation loop or offscreen renderer cache. */
4
+ .semantic-scene{isolation:isolate;min-width:0;container-type:inline-size}
5
+ .scene-view-controls{display:flex;gap:.5rem;flex-wrap:wrap;margin-block:.75rem}
6
+ /* Spatial mode is owned by Three.js; the hidden source board has no CSS camera. */
7
+ .scene-items>li[data-related=true]{border-color:Highlight}
8
+ .scene-items>li[data-selected=true]{outline:2px solid Highlight;outline-offset:2px}
9
+ .scene-items>li[data-focused=true]{border-inline-start:4px solid Highlight}
10
+ .scene-relations{list-style:none;padding:0;display:grid;gap:.5rem}
11
+ .scene-relations>li{display:flex;gap:.5rem;flex-wrap:wrap;align-items:center;padding:.5rem;border-inline-start:3px solid GrayText;border-radius:.4rem}
12
+ .scene-relations>li[data-active=true]{border-color:Highlight;background:color-mix(in srgb,Highlight 9%,Canvas)}
13
+ .entity-inspector{padding:var(--space)}
14
+ .scene-view-controls button,.scene-relations button,.item-tools button{min-height:44px;min-width:44px}
15
+ @media(prefers-reduced-motion:reduce){.semantic-scene *{scroll-behavior:auto}}
16
+ /* Hatter: XYZ is presentation only. Event-driven CSS/DOM, no render loop. */
17
+ .scene-camera{display:flex;gap:.4rem;flex-wrap:wrap;margin-block:.5rem}
18
+ .scene-camera button{min-height:44px;min-width:44px}
19
+ [data-view-mode=spatial] .scene-viewport{position:relative;height:clamp(22rem,64dvh,50rem);overflow:hidden}
20
+ .spatial-board{position:relative;min-width:0}
21
+ .scene-time{display:flex;gap:.5rem;align-items:center;overflow:auto;border-block:1px solid color-mix(in srgb,CanvasText 25%,Canvas);padding-block:.6rem;max-width:100%;scrollbar-gutter:stable}
22
+ .scene-time h2{flex:none;margin:0}.scene-time button{flex:none;min-height:44px;max-width:20rem;white-space:normal}
23
+ .semantic-scene>details{margin-block:.75rem}
24
+ [data-view-mode=structured] .scene-viewport{height:auto;overflow:visible}
25
+ [data-view-mode=structured] .scene-items>li{max-height:none}
26
+ @media(max-width:480px){.scene-camera button{padding:.4rem .5rem}.scene-viewport{width:100%}.scene-time{align-items:stretch}.scene-time h2{align-self:center}}
27
+ /* Hatter: full-window stage with edge tools and one locally scrollable panel. */
28
+ :root{--accent:#5368df;--line:color-mix(in srgb,CanvasText 16%,Canvas);--surface:color-mix(in srgb,Canvas 96%,var(--accent))}
29
+ html,body{height:100%;overflow:hidden}
30
+ body{background:var(--surface);color:CanvasText}
31
+ [hidden]{display:none!important}
32
+ .delivery-shell{position:relative;width:100%;height:100dvh;min-height:0;max-width:none;margin:0;padding:0;overflow:hidden;isolation:isolate}
33
+ .window-stage,.space-overview,.scene-surface{position:absolute;inset:0;overflow:hidden}
34
+ .window-stage{background:radial-gradient(ellipse at 50% 35%,color-mix(in srgb,var(--accent) 16%,Canvas),var(--surface) 75%)}
35
+ .space-overview:empty,.scene-surface:empty{display:none}
36
+ .ui-symbol{display:inline-block;width:1.4rem;height:1.4rem;flex:none}
37
+ .frame-bar{position:absolute;z-index:9;top:max(.75rem,env(safe-area-inset-top));left:1rem;right:1rem;display:flex;justify-content:space-between;align-items:center;pointer-events:none}
38
+ .frame-bar h1{font-size:1.05rem;letter-spacing:-.02em;margin:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
39
+ .delivery-state{display:flex;align-items:center;gap:.45rem;padding:.5rem .7rem;border:1px solid var(--line);border-radius:2rem;background:Canvas;font-size:.8rem;margin:0}
40
+ .delivery-state:before{content:'';width:.5rem;height:.5rem;border-radius:50%;background:#b17b24}.delivery-state[data-product-readiness=Ready]:before{background:#228a77}
41
+ .frame-dock{position:absolute;z-index:6;left:max(.5rem,env(safe-area-inset-left));top:50%;transform:translateY(-50%);display:flex;flex-direction:column;flex-wrap:nowrap;width:max-content;gap:.1rem;padding:.2rem;border:1px solid var(--line);border-radius:.8rem;background:Canvas;box-shadow:0 8px 25px #25396a15}
42
+ .frame-dock>button{display:grid;place-items:center;flex:none;border:0;border-radius:.75rem;position:relative}
43
+ .frame-dock>button[aria-expanded=true]{background:color-mix(in srgb,var(--accent) 15%,Canvas);color:var(--accent)}
44
+ .frame-dock>button[data-attention=true]:after{content:'';position:absolute;right:.3rem;top:.3rem;width:.45rem;height:.45rem;border-radius:50%;background:#c17a00}
45
+ .frame-panel{position:absolute;z-index:5;right:1rem;top:4.5rem;bottom:1rem;width:min(27rem,calc(100% - 6.5rem));display:flex;flex-direction:column;background:Canvas;border:1px solid var(--line);border-radius:1.2rem;box-shadow:0 15px 55px #25396a25;overflow:hidden}
46
+ .frame-panel>header{padding:.75rem 1rem;border-bottom:1px solid var(--line);flex:none;display:flex;justify-content:space-between}
47
+ .frame-panel>header h2{margin:0;font-size:1.05rem}.frame-panel>header button{width:44px;height:44px;font-size:1.5rem;border:0}
48
+ .frame-panel-body{padding:1rem;overflow:auto;overscroll-behavior:contain;min-height:0;flex:1;scrollbar-gutter:stable}
49
+ .frame-panel-body>section:empty{display:none}
50
+ .delivery-errors:empty,.delivery-index:empty,.empty-explanation:empty{display:none}
51
+ .delivery-index>button,.delivery-shell[data-panel=settings] .frame-panel-body>section>button{display:block;margin:.5rem 0;min-height:44px;width:100%;text-align:start}
52
+ .empty-explanation{color:GrayText;line-height:1.7}
53
+ .delivery-shell .semantic-scene{height:100%;width:100%;padding:0;border-radius:0;background:transparent}
54
+ .delivery-shell .semantic-scene>.scene-viewport{position:absolute;inset:0;height:100%;width:100%;border:0;border-radius:0;overflow:hidden;scrollbar-gutter:auto;background:transparent;touch-action:none}
55
+ .frame-panel .scene-viewport{height:auto;overflow:visible;touch-action:auto}
56
+ .frame-panel .scene-items{grid-template-columns:1fr}.frame-panel .scene-items>li{max-height:none}
57
+ .frame-panel .scene-time{flex-wrap:wrap;overflow:visible}.frame-panel .scene-time button{flex:1 1 auto}
58
+ .frame-panel .entity-inspector{margin:0;padding:0;border:0}
59
+ @media(max-width:600px){.frame-dock{top:auto;bottom:max(.5rem,env(safe-area-inset-bottom));left:50%;transform:translateX(-50%);flex-direction:row;gap:.2rem;padding:.35rem}.frame-panel{left:.7rem;right:.7rem;top:4rem;bottom:4.75rem;width:auto}.frame-bar{left:.7rem;right:.7rem}.frame-panel-body{padding:.8rem}}
60
+ @media(max-height:450px) and (min-width:601px){.frame-dock{flex-direction:row;top:auto;bottom:.5rem;left:50%;transform:translateX(-50%)}.frame-panel{bottom:4.5rem}}
61
+
62
+ /* Hatter: bounded object space. Coordinates are presentation, never semantic relationships. */
63
+ .world-object>span{font-size:.8rem;color:GrayText}
64
+ .world-empty-guide{position:absolute;text-align:center}.world-empty-guide button{margin:.5rem}
65
+ .object-fields{grid-template-columns:1fr;margin:0}.object-fields dd{font-size:1.05rem;overflow-wrap:anywhere}.object-fields dt{font-size:.8rem;color:GrayText}
66
+ .frame-dock button:after{pointer-events:none}
67
+
68
+ .frame-bar .delivery-state{pointer-events:auto;cursor:pointer}.frame-bar .delivery-state[data-attention=true]{border-color:#b45309;background:#fffbeb;color:#92400e}
69
+ .world-observation{position:absolute;z-index:4;top:4rem;left:1rem;max-width:60%;padding:.4rem .6rem;border-radius:.5rem;background:#fffbeb;color:#92400e;font-size:.8rem;pointer-events:none}
70
+
71
+ /* The world stays stable; only the requested overlay enters. */
72
+ .frame-panel:not([hidden]){animation:panel-enter .18s ease-out}
73
+ @keyframes panel-enter{from{opacity:0}to{opacity:1}}
74
+ @media(prefers-reduced-motion:reduce){.frame-panel:not([hidden]){animation:none}}
75
+ .frame-dock>button{width:44px;height:44px;padding:.4rem}
76
+ .frame-dock>button>span{display:none}.frame-dock .ui-symbol{width:18px;height:18px}
77
+ .scene-breadcrumbs{min-width:0;max-width:65%;gap:.5rem;flex-wrap:nowrap;background:Canvas;padding:.35rem .7rem;border:1px solid var(--line);border-radius:.8rem}
78
+ .scene-breadcrumbs>button{pointer-events:auto;min-width:44px;min-height:44px;border:0}
79
+ .world-picker{position:relative;min-width:0;pointer-events:auto}.world-picker>summary{display:flex;align-items:center;gap:.5rem;min-height:32px;list-style:none;cursor:pointer}.world-picker>summary::-webkit-details-marker{display:none}.world-picker>summary:after{content:'⌄';font-size:1rem;color:GrayText}.world-picker>summary:focus-visible{outline:3px solid Highlight;outline-offset:3px}.world-picker-options{position:absolute;z-index:8;top:calc(100% + .7rem);left:-.7rem;display:grid;gap:.3rem;width:min(19rem,calc(100vw - 2rem));padding:.65rem;background:Canvas;border:1px solid var(--line);border-radius:.8rem;box-shadow:0 12px 35px #25396a30}.world-picker-options small{margin:.4rem .35rem 0;color:GrayText}.world-picker-options button{min-height:44px;text-align:start;border:0}
80
+ .garden-focus{font-size:.8rem;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;border-inline-start:1px solid var(--line);padding-inline-start:.5rem;min-width:0}
81
+ .stage-tools{position:absolute;z-index:4;bottom:1rem;right:1rem;display:flex;align-items:center;gap:.25rem;background:Canvas;border:1px solid var(--line);padding:.35rem;border-radius:1rem}
82
+ .stage-tools button{min-width:40px;min-height:44px;border:0;padding:.35rem .5rem;font-size:.85rem}.stage-tools output{min-width:3rem;text-align:center;font-size:.8rem}
83
+ .world-objects{touch-action:none}
84
+ /* Bounded Three.js presentation. Native DOM conversations; no image assets. */
85
+ .world-conversation,.frame-panel[data-conversation=true]{position:absolute;inset:auto;z-index:7;width:min(320px,calc(100% - 16px));max-height:min(420px,calc(100dvh - 210px));overflow:auto;padding:1rem;border:1px solid #b6c9bf;border-radius:18px 18px 18px 4px;background:#f9faf2;color:#203d3c;box-shadow:0 6px 24px #203a3d30;animation:none}
86
+ .world-conversation header{position:sticky;top:-1rem;background:#f9faf2;padding:.35rem 0;gap:.5rem}.world-conversation button{min-height:44px;background:#fff;color:#203d3c;border-color:#b6c9bf}.world-conversation section{display:grid;gap:.5rem}.world-conversation p{margin:.25rem 0;line-height:1.45;font-size:.9rem}
87
+ .frame-panel[data-conversation=true]>header{position:relative;top:auto;background:transparent;padding:0 .5rem;min-height:44px;gap:.5rem;align-items:center}.frame-panel[data-conversation=true]>header h2{font-size:.9rem;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
88
+ .frame-panel[data-conversation=true]{transform:none}
89
+ .frame-panel[data-conversation=true] .frame-panel-body{padding:.65rem .85rem 1rem}.conversation-status{font-size:.8rem;font-weight:600}
90
+ .mind-world{position:absolute;inset:0;overflow:hidden;background:#b8d4de;color:#253c3d;isolation:isolate}
91
+ .world-canvas{display:block;width:100%;height:100%;touch-action:none;outline-offset:-4px!important}
92
+ .world-labels{position:absolute;inset:0;pointer-events:none}
93
+ .world-updates{position:absolute;left:108px;right:175px;bottom:1rem;max-height:35dvh;overflow:auto;background:#f9faf0f2;color:#203d3c;border:1px solid #9cbbaf;border-radius:10px;pointer-events:auto}.world-updates summary{padding:.7rem;cursor:pointer;min-height:44px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.world-updates p{font-size:.75rem;padding:0 .7rem}.world-updates button{display:block;width:100%;min-height:44px;overflow-wrap:anywhere;text-align:start}.world-name[data-updated=true]{border:2px solid #2783cc}.world-canvas{touch-action:none}
94
+ .world-name{position:absolute;transform:translate(-50%,-100%);display:grid;gap:.25rem;pointer-events:auto;width:max-content;max-width:174px;min-height:44px;border:1px solid #ffffffa0;border-radius:10px;background:#f9faf0ee;color:#203d3c;padding:.5rem .75rem;box-shadow:0 4px 0 #293d4320;text-align:center}
95
+ .world-name strong{font-size:.86rem;line-height:1.25}.world-name span{font-size:.67rem;color:#4e6966}.world-name{max-height:78px;overflow:hidden;overflow-wrap:anywhere}.world-name[data-aimed=true]{outline:2px solid #608b76;outline-offset:3px}.world-name:disabled{background:#e2e2de}
96
+ [data-live-state]:not([data-live-state=Live]) .world-engine:after{content:'Last observation · live updates unavailable';display:block;padding:.5rem}[data-live-state]:not([data-live-state=Live]) .world-engine *,[data-live-state]:not([data-live-state=Live]) .world-name:before{animation-play-state:paused!important}@media(prefers-reduced-motion:reduce){.world-engine summary:before,.world-name:before{animation:none!important}}
97
+ .world-engine{position:absolute;left:12px;top:115px;z-index:3;max-width:min(290px,calc(100% - 24px));max-height:45dvh;overflow:auto;background:#f9faf0f2;color:#203d3c;border-radius:12px;font-size:.8rem}.world-engine summary{padding:.75rem;min-height:44px;cursor:pointer}.world-engine>div{padding:0 .75rem .75rem}.world-engine dl{display:grid;grid-template-columns:1fr auto;gap:.35rem}.world-engine dd{margin:0}.world-engine[data-active=true] summary:before,.world-name[data-activity=AwaitingResult]:before{content:'◈';display:inline-block;color:#1678ba}.world-name[data-activity=Waiting],.world-name[data-activity=InputNeeded]{border-color:#be8b22}.world-name[data-activity=Failed],.world-name[data-activity=Uncertain],.world-name[data-activity=Paused]{border-color:#b53c47}.world-name[data-updated=true]{animation:record-arrival .7s ease-out 1}@keyframes record-arrival{from{outline:8px solid #2783cc80}to{outline:0 solid transparent}}.mind-world[data-motion-paused=true] *{animation-play-state:paused!important}.delivery-shell[data-panel] .world-engine{visibility:hidden}@media(prefers-reduced-motion:reduce){.world-engine *,.world-name{animation:none!important}}@media(max-width:600px){.world-engine{top:auto;bottom:13rem;max-width:210px}}@media(max-height:450px){.world-engine{top:4.5rem;bottom:auto;max-height:45dvh}}
98
+ .world-aim{position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);font:22px monospace;color:#fff;text-shadow:0 1px 3px #263e43;pointer-events:none}
99
+ .world-hud{position:absolute;inset:0;pointer-events:none;font-size:.8rem}.world-hud button,.world-hud details{pointer-events:auto}
100
+ .world-hint{position:absolute;bottom:158px;left:50%;transform:translateX(-50%);max-width:70%;min-height:44px;padding:.45rem .8rem;border:1px solid #ffffff9c;border-radius:30px;background:#203a3dd9;color:#fff;text-align:center;white-space:normal;cursor:pointer}
101
+ .world-controls{position:absolute;bottom:74px;left:50%;transform:translateX(-50%);display:flex;gap:.5rem}.world-controls button{min-height:44px;background:#f9faf2;color:#284a46;border:1px solid #b6c9bf}.world-controls button:first-child{min-width:130px}
102
+ .world-walk{position:absolute;bottom:85px;left:112px;display:grid;grid-template-columns:repeat(3,44px);gap:4px}.world-walk button{width:44px;height:44px;padding:0;background:#f9faf0dc;color:#284a46;touch-action:none;border-color:#b6c9bf}.world-walk button:first-child{grid-column:2}.world-walk button:nth-child(2){grid-column:1}
103
+ .world-nearby{position:absolute;top:115px;right:18px;max-width:min(310px,calc(100% - 40px));max-height:calc(100dvh - 240px);overflow:auto;border:1px solid #ffffffb0;border-radius:12px;background:#f9faf0f2;color:#284a46;box-shadow:0 4px 0 #293d4310}.world-nearby summary{padding:.65rem 1rem;cursor:pointer;min-height:44px}.world-directory{padding:.5rem;max-height:40dvh;overflow:auto}.world-directory>div{display:flex;gap:.3rem;margin:.2rem}.world-directory button{min-height:44px;background:white;color:#284a46;border-color:#b6c9bf}.world-directory button:first-child{flex:1;text-align:start;overflow-wrap:anywhere}
104
+ .world-layout{display:grid;gap:.4rem;padding:.5rem}.world-layout label{display:grid;gap:.2rem;font-size:.8rem}.world-layout select{width:100%;min-width:0;min-height:44px;background:white;color:#284a46}.world-breadcrumb{position:absolute;top:66px;left:12px;right:12px;display:flex;gap:.5rem;align-items:center;pointer-events:auto;color:white;text-shadow:0 1px 3px #000}.world-breadcrumb output{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.world-breadcrumb button{min-height:44px;flex-shrink:0}.world-pagination{display:flex;align-items:center;justify-content:space-between;padding:.5rem;gap:.25rem}.world-pagination button{min-height:44px}
105
+ .world-gpu-error{position:absolute;top:25%;left:15%;right:15%;padding:1rem;background:Canvas;color:CanvasText;border:2px solid #bd8b33}
106
+ .world-empty-guide{z-index:1;background:#f9faf0ed;border:1px solid #fff;border-radius:1rem;padding:1rem;inset:30% 20% auto;color:#284a46}
107
+ .frame-panel{z-index:8}.frame-panel .object-fields{gap:.75rem}
108
+ @media(max-width:600px){.world-name{max-width:130px;padding:.4rem .5rem}.world-name strong{font-size:.75rem}.world-name span{font-size:.63rem}.world-nearby{top:104px;right:10px;max-width:240px}.world-hint{bottom:265px;width:max-content;max-width:90%;font-size:.75rem}.world-walk{left:12px;bottom:138px}.world-controls{bottom:148px;left:auto;right:12px;transform:none;flex-direction:column;gap:5px}.world-controls button:first-child{min-width:110px}.world-empty-guide{inset:25% 1rem auto}.world-gpu-error{left:1rem;right:1rem}.stage-tools{bottom:5.2rem}}
109
+ @media(max-height:500px) and (min-width:601px){.world-hint{bottom:130px}.world-nearby{top:10px;right:140px}.world-walk{bottom:80px}.world-controls{bottom:78px}}
110
+ .object-solid{display:inline-grid;place-items:center;width:40px;height:40px;border:1px solid var(--line);border-radius:6px}.object-solid>.ui-symbol{width:24px;height:24px}
111
+ .frame-panel[data-book=true]{background:#15242be8;color:#f6f1e5;border-color:#c9bb96}.frame-panel[data-book=true]>header{display:none}.frame-panel[data-book=true] .frame-panel-body{padding:0}.game-book{height:100%;display:flex;flex-direction:column;padding:.6rem;gap:.5rem}.game-book>header{border-bottom:1px solid #fff5;padding-bottom:.4rem}.game-book h2{font-size:1rem;margin:.2rem 0}.game-book-heading p{font-size:.8rem;margin:.2rem 0}.game-book>header button{min-height:44px}.game-book-body{flex:1;min-height:0;display:grid;grid-template-columns:90px minmax(0,1fr);gap:.6rem;align-items:center}.game-book-object{width:68px;height:92px;display:grid;place-items:center;color:#e9cf90;background:#29434a;border:2px solid #c2a36a;box-shadow:6px 6px #eee5d2,10px 11px 16px #0008;transform:perspective(350px) rotateY(-15deg)}.game-book-object .ui-symbol{width:36px;height:36px}.game-book-page{background:#f5f0e4;color:#243a3d;padding:.8rem;max-height:100%;overflow:auto;box-shadow:inset 4px 0 #d4c6a6;overflow-wrap:anywhere}.game-book-page>small{font-size:.7rem;color:#596f6b}.game-book-page .object-fields{display:block}.game-book-page dd{margin:.2rem 0 .8rem;white-space:pre-wrap}.game-book>nav{border-top:1px solid #fff5;padding-top:.4rem}.game-book>nav button{min-height:44px;background:#23434b;color:#fff}@media(max-width:600px){.game-book-body{grid-template-columns:55px minmax(0,1fr)}.game-book-object{width:45px;height:64px}.game-book-page{padding:.5rem}}
112
+ /* Product chrome has one camera toolbar, one contextual overlay and four destinations. */
113
+ .delivery-shell[data-camera-toolbar] .world-controls [data-overview-control],.delivery-shell[data-camera-toolbar] .scene-camera{display:none}
114
+ .delivery-shell[data-panel] .world-nearby,.delivery-shell[data-panel] .world-controls,.delivery-shell[data-panel] .world-updates{visibility:hidden}
115
+ .delivery-shell[data-panel] .world-hint{visibility:hidden}
116
+ .frame-panel[data-conversation=true]{background:rgba(247,250,242,.84);box-shadow:0 8px 32px #102e3029;border:1px solid #ffffffa8;padding:0}
117
+ .frame-panel[data-conversation=true]>.frame-panel-body{background:transparent}
118
+ .frame-panel[data-conversation=true] button{background:#ffffff9c;color:#203d3c;border-color:#9eb7ac}.frame-panel[data-conversation=true] button:hover{background:#ffffffe0}.frame-panel[data-conversation=true] button:active{background:#fff}.frame-panel[data-conversation=true] button:disabled{opacity:.6}
119
+ .frame-panel[data-conversation=true] form{gap:.65rem}.frame-panel[data-conversation=true] textarea{width:100%;min-height:5rem;resize:vertical;padding:.6rem;border:1px solid #9eb7ac;border-radius:.4rem;background:#ffffffde;color:#203d3c}.operation-guidance,.operation-example{line-height:1.5;margin:.35rem 0 .7rem}.operation-example{font-size:.8rem;color:#355852}
120
+ .conversation-choices{display:grid;gap:.65rem}.conversation-choices>section{display:grid;gap:.65rem}
121
+ .conversation-lead{padding:.75rem;border:1px solid #88aa9d;border-radius:12px;background:#ffffffb9}.conversation-lead h3{margin:.15rem 0;font-size:1rem}.conversation-lead p{margin:.3rem 0;max-height:8rem;overflow:auto;overflow-wrap:anywhere}.conversation-lead small{color:#4c6663;overflow-wrap:anywhere}.conversation-lead nav{display:flex;align-items:center;justify-content:space-between;gap:.4rem}.conversation-lead nav button{min-height:40px}.conversation-lead nav span{white-space:nowrap}.conversation-primary{width:100%;margin-top:.45rem;min-height:44px}
122
+ .conversation-back{display:grid;place-items:center;width:44px;height:44px;padding:0;margin-bottom:.6rem;font-size:1.15rem}.conversation-prompts{padding:.65rem;border:1px solid #b98730;border-radius:12px;background:#fff8e4d9}.conversation-prompts h3{margin:0 0 .4rem;font-size:.9rem}.conversation-prompts>div{display:grid;gap:.35rem}.conversation-prompts button{min-height:44px;text-align:start}.conversation-deck [hidden]{display:none!important}
123
+ .conversation-deck [data-person-shelf]{padding:.5rem;border:1px solid #88aa9d;border-radius:12px}.conversation-deck [data-person-shelf] button{display:block;width:100%;min-height:44px;text-align:start}
124
+ .world-nearby>input{display:block;width:calc(100% - 1rem);margin:.5rem;min-height:44px}.world-nearby>p{padding:.5rem}
125
+ .frame-panel-body details>button{display:block;width:100%;margin:.5rem 0;text-align:start;min-height:44px}
126
+ .world-map{position:absolute;right:1rem;top:10.5rem;width:min(240px,55vw);padding:.4rem;border:1px solid #9cbbaf;border-radius:12px;background:#f9faf0f2;color:#203d3c}.world-map summary{min-height:44px;padding:.5rem;cursor:pointer}.world-map small{display:block}.world-map-list{position:relative;height:180px;margin:.5rem 0;background:#d9e8df;border-radius:8px}.world-map-point{position:absolute;transform:translate(-50%,-50%);width:30px;height:30px;padding:0;border-radius:50%;background:#fff}.world-map-point[data-selected=true],.world-name[data-selected=true]{outline:3px solid #ec9d32}.world-map-point[data-marked=true],.world-name[data-marked=true]{border-color:#ad4c8d}.world-map>button{width:100%;min-height:44px}.delivery-shell[data-panel] .world-map{visibility:hidden}@media(max-width:600px){.world-map{left:.5rem;right:auto;top:10rem;width:min(180px,45vw)}.world-map-list{height:145px}}
127
+ @media(max-width:600px){.stage-tools{top:4.4rem;bottom:auto;right:.7rem;left:auto;transform:none}.world-nearby{top:8.1rem;right:.7rem;max-height:calc(100dvh - 260px)}.world-breadcrumb{top:4.5rem;right:165px;font-size:.8rem}.world-updates{left:.7rem;right:.7rem;bottom:4.2rem}.frame-panel{bottom:4.2rem}.world-hint{bottom:10.5rem}.world-controls{bottom:12rem}.world-walk{bottom:12rem}}
128
+ @media(max-height:450px) and (min-width:601px){.world-updates{bottom:6rem;left:1rem;right:1rem}.world-nearby{top:4.5rem;right:1rem}.world-hint{display:none}.world-breadcrumb{right:340px}}
@@ -0,0 +1,2 @@
1
+ /** @param {Document} document @param {string} [name] */
2
+ export function symbol(document: Document, name?: string): SVGSVGElement;
package/src/symbol.mjs ADDED
@@ -0,0 +1,19 @@
1
+ // Installed vector primitives; labels/routes come from the site, never SVG/HTML input.
2
+ /** @param {Document} document @param {string} [name] */
3
+ export function symbol(document,name='layers'){
4
+ const paths={home:['m3 11 9-8 9 8','M5 9v12h14V9','M9 21v-8h6v8'],plus:['M12 4v16M4 12h16'],settings:['M12 8a4 4 0 1 0 0 8 4 4 0 0 0 0-8Z','M12 2v3m0 14v3M2 12h3m14 0h3M5 5l2 2m10 10 2 2M5 19l2-2M17 7l2-2'],clock:['M22 12a10 10 0 1 1-20 0 10 10 0 0 1 20 0Z','M12 6v6l4 2'],layers:['M12 3 2 8l10 5 10-5-10-5Z','m2 12 10 5 10-5','m2 17 10 5 10-5'],
5
+ menu:['M4 6h16M4 12h16M4 18h16'],
6
+ person:['M16 7a4 4 0 1 1-8 0 4 4 0 0 1 8 0Z','M4 22v-3a8 8 0 0 1 16 0v3'],
7
+ nodes:['M9 5H5v4h4V5Z','M19 15h-4v4h4v-4Z','M9 15H5v4h4v-4Z','M7 9v6m2-8h8v8M9 17h6'],
8
+ package:['m3 7 9-5 9 5v10l-9 5-9-5V7Z','m3 7 9 5 9-5M12 12v10M7 4l10 6'],
9
+ book:['M4 3h14a2 2 0 0 1 2 2v15H6a2 2 0 0 1-2-2V3Z','M4 18a2 2 0 0 1 2-2h14','M8 7h8m-8 4h6'],
10
+ chip:['M6 6h12v12H6V6Z','M9 9h6v6H9V9Z','M9 2v4m6-4v4M9 18v4m6-4v4M2 9h4m-4 6h4m12-6h4m-4 6h4'],
11
+ info:['M22 12a10 10 0 1 1-20 0 10 10 0 0 1 20 0Z','M12 11v6m0-10v1'],
12
+ search:['M17 10a7 7 0 1 1-14 0 7 7 0 0 1 14 0Z','m15 15 6 6'],
13
+ play:['m8 4 12 8-12 8V4Z'],
14
+ activity:['M2 12h4l3-8 6 16 3-8h4'],check:['m5 12 4 4L19 6'],warning:['m12 3 10 18H2L12 3Z','M12 9v5m0 3v1']}
15
+ const svg=document.createElementNS('http://www.w3.org/2000/svg','svg')
16
+ for(const [key,value]of Object.entries({viewBox:'0 0 24 24',fill:'none',stroke:'currentColor','stroke-width':'1.6','stroke-linecap':'round','stroke-linejoin':'round','aria-hidden':'true',focusable:'false',class:'ui-symbol'}))svg.setAttribute(key,value)
17
+ for(const d of paths[name]??paths.layers){const p=document.createElementNS(svg.namespaceURI,'path');p.setAttribute('d',d);svg.append(p)}
18
+ return svg
19
+ }
@@ -0,0 +1,15 @@
1
+ export function createGpu(canvas: any): {
2
+ pattern(value: any): void;
3
+ upload(data: any, ranges?: any[]): void;
4
+ motionStates(states: any): void;
5
+ draw(pose: any, aspect: any, zoom: any, size: any, milliseconds?: number, visibleActors?: Set<any>): void;
6
+ readonly count: any;
7
+ readonly stats: {
8
+ calls: any;
9
+ triangles: any;
10
+ geometries: any;
11
+ textures: any;
12
+ };
13
+ close: () => void;
14
+ };
15
+ export function worldGeometry(model: any, actors?: any[]): Float32Array<ArrayBuffer>;
@@ -0,0 +1,79 @@
1
+ // One instanced cube mesh, one program, no textures, physics, asset fetches or semantic state.
2
+ import {WebGLRenderer,Scene,PerspectiveCamera,BoxGeometry,MeshLambertMaterial,InstancedMesh,Matrix4,Color,DirectionalLight,AmbientLight,Fog} from 'three'
3
+ import {worldLimits,viewCamera} from './world-model.mjs'
4
+ import {objectTemplates} from './object-presentation.mjs'
5
+ import {npcMotion} from './npc-motion.mjs'
6
+ export function createGpu(canvas){
7
+ let renderer=null,geometry=null,material=null,mesh=null,closed=false,bufferWidth=0,bufferHeight=0,previous=null,pendingStart=Infinity,pendingEnd=0,baseCount=0,actors=[],actorStates=new Map(),lastVisible=new Set()
8
+ const scene=new Scene(),camera=new PerspectiveCamera(70,1,.08,120),matrix=new Matrix4(),color=new Color()
9
+ function close(){if(closed)return;closed=true;previous=null;actors=[];actorStates.clear();lastVisible.clear();mesh?.dispose();geometry?.dispose();material?.dispose();scene.clear();renderer?.dispose()}
10
+ try{
11
+ renderer=new WebGLRenderer({canvas,alpha:false,antialias:false,stencil:false,preserveDrawingBuffer:false,powerPreference:'low-power'})
12
+ scene.background=new Color('#b8d4de');scene.fog=new Fog('#b8d4de',16,64)
13
+ scene.add(new AmbientLight(0xffffff,1.7));const light=new DirectionalLight(0xffffff,2);light.position.set(-4,10,6);scene.add(light)
14
+ geometry=new BoxGeometry(1,1,1);material=new MeshLambertMaterial()
15
+ mesh=new InstancedMesh(geometry,material,worldLimits.instances);mesh.count=0;mesh.frustumCulled=false;scene.add(mesh)
16
+ return {
17
+ pattern(value){const space=value==='constellation',background=space?'#101b35':'#b8d4de';scene.background.set(background);scene.fog=new Fog(background,space?40:16,space?100:64)},
18
+ upload(data,ranges=[]){
19
+ if(closed)throw Error('WorldDisposed')
20
+ if(data.length%9||data.length/9>=worldLimits.instances)throw Error('WorldGeometryLimitExceeded')
21
+ baseCount=data.length/9;mesh.count=baseCount
22
+ for(let i=0;i<baseCount;i++){
23
+ const j=i*9;if(previous&&data.subarray(j,j+9).every((value,index)=>value===previous[j+index]))continue
24
+ matrix.makeScale(data[j+3],data[j+4],data[j+5]);matrix.setPosition(data[j],data[j+1],data[j+2]);mesh.setMatrixAt(i,matrix);mesh.setColorAt(i,color.setRGB(data[j+6],data[j+7],data[j+8]))
25
+ pendingStart=Math.min(pendingStart,i);pendingEnd=Math.max(pendingEnd,i+1)
26
+ }
27
+ previous=data;actors=ranges
28
+ },
29
+ motionStates(states){actorStates=states},
30
+ draw(pose,aspect,zoom,size,milliseconds=0,visibleActors=new Set()){if(closed)return;if(size.width!==bufferWidth||size.height!==bufferHeight){renderer.setSize(size.width,size.height,false);bufferWidth=size.width;bufferHeight=size.height}const distance=pose.focus?Math.hypot(pose.x-pose.focus.x,pose.y-pose.focus.y,pose.z-pose.focus.z):0;scene.fog.near=Math.max(16,distance*.75);scene.fog.far=Math.max(64,distance*2+20)
31
+ mesh.count=baseCount+(pose.cameraMode==='third-person'?1:0)
32
+ let matrixStart=pendingStart,matrixEnd=pendingEnd,colorStart=pendingStart,colorEnd=pendingEnd
33
+ for(const actor of actors){if(!visibleActors.has(actor.id)&&!lastVisible.has(actor.id))continue;const motion=visibleActors.has(actor.id)?npcMotion(actor.id,milliseconds,actorStates.get(actor.id)):{x:0,y:0,swing:0}
34
+ for(let i=actor.start;i<actor.end;i++){const j=i*9,part=i-actor.start,limb=part===1||part===4?1:part===2||part===5?-1:0;matrix.makeScale(previous[j+3],previous[j+4],previous[j+5]);matrix.setPosition(previous[j]+motion.x,previous[j+1]+motion.y+limb*motion.swing,previous[j+2]);mesh.setMatrixAt(i,matrix)}
35
+ matrixStart=Math.min(matrixStart,actor.start);matrixEnd=Math.max(matrixEnd,actor.end)
36
+ }
37
+ lastVisible=new Set(visibleActors)
38
+ if(pose.cameraMode==='third-person'){
39
+ matrix.makeScale(.48,.9,.42);matrix.setPosition(pose.x,pose.y-1.4,pose.z);mesh.setMatrixAt(baseCount,matrix);mesh.setColorAt(baseCount,color.setRGB(.96,.76,.28))
40
+ matrixStart=Math.min(matrixStart,baseCount);matrixEnd=Math.max(matrixEnd,baseCount+1);colorStart=Math.min(colorStart,baseCount);colorEnd=Math.max(colorEnd,baseCount+1)
41
+ }
42
+ for(const [attribute,start,end,size]of [[mesh.instanceMatrix,matrixStart,matrixEnd,16],[mesh.instanceColor,colorStart,colorEnd,3]])if(attribute){attribute.clearUpdateRanges();if(start!==Infinity){attribute.addUpdateRange(start*size,(end-start)*size);attribute.needsUpdate=true}}
43
+ viewCamera(pose,aspect,zoom,camera);renderer.render(scene,camera);pendingStart=Infinity;pendingEnd=0},
44
+ get count(){return mesh.count},get stats(){return {calls:renderer.info.render.calls,triangles:renderer.info.render.triangles,geometries:renderer.info.memory.geometries,textures:renderer.info.memory.textures}},close
45
+ }
46
+ }catch(error){close();throw error}
47
+ }
48
+ const palette=[[.39,.68,.64],[.62,.49,.79],[.89,.61,.38],[.37,.59,.79],[.76,.43,.53]]
49
+ export function worldGeometry(model,actors=[]){
50
+ let offsetY=0
51
+ const values=[],cube=(x,y,z,sx,sy,sz,color)=>values.push(x,y+offsetY,z,sx,sy,sz,...color)
52
+ // A bounded, decorative thinking garden. Terrain does not assert knowledge or relationships.
53
+ if(model.pattern==='constellation'){
54
+ // Fixed procedural background, no texture loading or permanent animation loop.
55
+ for(let i=0;i<96;i++)cube(Math.sin(i*2.399)*35,2+(i%13)*2.5,-15-(i%17)*3,.07,.07,.07,[.64,.74,.92])
56
+ }else{
57
+ cube(0,-.65,-150,60,1.2,370,[.55,.65,.55])
58
+ cube(0,-.015,-150,2.3,.06,368,[.83,.82,.68])
59
+ }
60
+ const byId=new Map(model.items.map(item=>[item.id,item]))
61
+ for(const edge of model.relations){
62
+ const from=byId.get(edge.from),to=byId.get(edge.to),color=edge.active?[.92,.61,.21]:[.24,.55,.62],fy=(from.y??0)+.2,ty=(to.y??0)+.2
63
+ // Orthogonal connectors use the same instanced mesh; only declared edges.
64
+ cube((from.x+to.x)/2,fy,from.z,Math.abs(to.x-from.x)+.06,.06,.06,color)
65
+ cube(to.x,(fy+ty)/2,from.z,.06,Math.abs(ty-fy)+.06,.06,color)
66
+ cube(to.x,ty,(from.z+to.z)/2,.06,.06,Math.abs(to.z-from.z)+.06,color)
67
+ }
68
+ for(const item of model.items){
69
+ const start=values.length/9
70
+ const hash=[...item.id].reduce((v,c)=>(Math.imul(v,31)+c.charCodeAt(0))|0,0),color=item.disabled?[.52,.54,.56]:palette[(hash>>>0)%palette.length],{x,z}=item
71
+ offsetY=item.y??0
72
+ if(item.form!=='book')cube(x,.07,z,2,.18,1.8,item.selected?[.98,.77,.34]:[.75,.80,.70])
73
+ const tints={accent:item.needsReview?[.9,.61,.16]:color,dark:[.25,.31,.4],face:[.89,.77,.63],light:[.94,.96,.96],warning:[.9,.61,.16]}
74
+ for(const [dx,y,dz,sx,sy,sz,tint]of objectTemplates[item.form])cube(x+dx,y,z+dz,sx,sy,sz,tints[tint])
75
+ if(item.kind==='character')actors.push({id:item.id,start,end:values.length/9})
76
+ }
77
+ if(values.length/9>=worldLimits.instances)throw Error('WorldGeometryLimitExceeded')
78
+ return new Float32Array(values)
79
+ }
@@ -0,0 +1,3 @@
1
+ /** @param {Document} document */
2
+ export function worldControlGuide(document: Document): HTMLElement;
3
+ export function keyDirection(code: any): any;
@@ -0,0 +1,21 @@
1
+ // Presentation input only: shared movement bindings and on-demand control guide.
2
+ const movement=[
3
+ ['forward','W / ↑',['KeyW','ArrowUp']],['back','S / ↓',['KeyS','ArrowDown']],
4
+ ['left','A / ←',['KeyA','ArrowLeft']],['right','D / →',['KeyD','ArrowRight']],
5
+ ['rise','R / Page Up',['KeyR','PageUp']],['descend','F / Page Down',['KeyF','PageDown']]
6
+ ]
7
+ const directions=new Map(movement.flatMap(([action,,codes])=>codes.map(code=>[code,action])))
8
+ export const keyDirection=code=>directions.get(code)
9
+ /** @param {Document} document */
10
+ export function worldControlGuide(document){
11
+ const guide=document.createElement('section');guide.setAttribute('aria-label','World controls')
12
+ const section=(title,rows)=>{const heading=document.createElement('h3'),list=document.createElement('dl');heading.textContent=title
13
+ for(const [input,action]of rows){const term=document.createElement('dt'),description=document.createElement('dd');term.textContent=input;description.textContent=action;list.append(term,description)}
14
+ guide.append(heading,list)
15
+ }
16
+ section('Overview', [['Drag','Pan across the world'],['Wheel / pinch','Zoom'],['Click / tap an object','Select a target and open its context'],['Map / M','Find a projected target by the same reference'],['View all','Fit the whole space']])
17
+ const note=document.createElement('p');note.textContent='Choose Walk through world under In this space → Navigation, then close the panel and select the world to use movement keys.';guide.append(note)
18
+ section('Walk through world',movement.map(([action,label])=>[label,action[0].toUpperCase()+action.slice(1)]).concat([['Drag','Look around'],['Wheel','Move forward / back'],['E','Inspect the aimed object'],['M','Open or close Map'],['Q','Mark the aimed or selected target'],['Escape','Pause movement or close the open panel']]))
19
+ section('Keyboard navigation',[['Tab / Shift + Tab','Move between controls'],['Enter / Space','Activate the focused button']])
20
+ return guide
21
+ }
@@ -0,0 +1,49 @@
1
+ export function layoutWorld(objects: any, relations?: any[], pattern?: string, spacing?: string): {
2
+ pattern?: string;
3
+ items: any[];
4
+ relations: any[];
5
+ };
6
+ export function initialPose(): {
7
+ x: number;
8
+ y: number;
9
+ z: number;
10
+ yaw: number;
11
+ pitch: number;
12
+ };
13
+ export function fitPose(items: any, aspect?: number): {
14
+ x: any;
15
+ y: any;
16
+ z: any;
17
+ yaw: number;
18
+ pitch: number;
19
+ focus: {
20
+ x: any;
21
+ y: any;
22
+ z: any;
23
+ };
24
+ };
25
+ export function movePose(pose: any, forward: any, side: any, seconds: any, vertical?: number): any;
26
+ export function lookPose(pose: any, dx: any, dy: any): any;
27
+ export function viewCamera(pose: any, aspect: any, zoom?: number, camera?: any): any;
28
+ export function cameraMatrix(pose: any, aspect: any, zoom?: number): any;
29
+ export function screenPoint(matrix: any, x: any, y: any, z: any, width: any, height: any): {
30
+ x: number;
31
+ y: number;
32
+ depth: number;
33
+ visible: boolean;
34
+ };
35
+ export function renderSize(width: any, height: any, dpr: any): {
36
+ width: number;
37
+ height: number;
38
+ };
39
+ export function pickWorld(items: any, pose: any, x: any, y: any, width: any, height: any, zoom?: number): any;
40
+ export const worldLimits: Readonly<{
41
+ objects: 512;
42
+ relations: 512;
43
+ pixels: 1440000;
44
+ dpr: 1.5;
45
+ fps: 30;
46
+ instances: 8192;
47
+ radius: 28;
48
+ }>;
49
+ export function clamp(value: any, min: any, max: any): number;
@@ -0,0 +1,83 @@
1
+ import {PerspectiveCamera,Vector3,Raycaster,Vector2,Box3} from 'three'
2
+ import {objectPresentation,spacingPresets,libraryLayout,objectBounds} from './object-presentation.mjs'
3
+ // Presentation geometry only. IDs and declared relations are never inferred from position.
4
+ export const worldLimits=Object.freeze({objects:512,relations:512,pixels:1440000,dpr:1.5,fps:30,instances:8192,radius:28})
5
+ export const clamp=(value,min,max)=>Math.max(min,Math.min(max,value))
6
+ export function layoutWorld(objects,relations=[],pattern='ground',spacing='balanced'){
7
+ const distances=spacingPresets[spacing];if(!distances)throw Error('SpatialSpacingInvalid')
8
+ if(!['ground','constellation','garden'].includes(pattern))throw Error('SpatialPatternInvalid')
9
+ if(!Array.isArray(objects)||objects.length>worldLimits.objects||!Array.isArray(relations)||relations.length>worldLimits.relations)throw Error('WorldCapacityExceeded')
10
+ const ids=new Set()
11
+ const items=objects.map((object,index)=>{
12
+ object={...object,...objectPresentation(object)}
13
+ if(typeof object.id!=='string'||!object.id||ids.has(object.id))throw Error('WorldIdentityInvalid')
14
+ ids.add(object.id)
15
+ if(pattern==='garden'){
16
+ const actors=objects.filter(o=>o.kind==='character'),actor=object.kind==='character'?actors.findIndex(o=>o.id===object.id):actors.findIndex(o=>o.id===object.anchor)
17
+ const base={x:actor<0?0:(actor%7-3)*distances.person,z:actor<0?-8:-Math.floor(actor/7)*distances.person*2},plot=object.plot??0
18
+ if(object.kind==='character')return {...object,...base,y:0}
19
+ const x=base.x+distances.group+(plot%4)*distances.group,z=base.z-distances.group-Math.floor(plot/4)*distances.group
20
+ if(object.kind==='group')return {...object,x,y:0,z}
21
+ const slot=object.member??0,l=libraryLayout
22
+ return {...object,x:x+(slot%l.columns-(l.columns-1)/2)*l.slotWidth,y:l.baseY+Math.floor(slot/l.columns)*l.rowHeight,z:z+l.frontZ}
23
+ }
24
+ if(pattern==='constellation'){
25
+ const actors=objects.filter(i=>i.kind==='character'),isActor=object.kind==='character',order=isActor?actors.findIndex(o=>o.id===object.id):objects.slice(0,index).filter(i=>i.kind!=='character').length
26
+ const count=objects.length-actors.length,ring=Math.floor(order/8),angle=(order%8)/Math.min(8,count-ring*8)*Math.PI*2
27
+ return {...object,x:isActor?(order-(actors.length-1)/2)*3.4:Math.sin(angle)*(5+ring*4),y:isActor?0:2+ring*3+(order%3)*1.2,z:isActor?-1:-8+Math.cos(angle)*(3+ring*2)}
28
+ }
29
+ const row=Math.floor(index/7),count=Math.min(7,objects.length-row*7),column=index%7
30
+ return {...object,x:(column-(count-1)/2)*3.4,z:-row*4.5-1-Math.cos(column)*.4,y:0}
31
+ })
32
+ return {items,relations:relations.filter(edge=>ids.has(edge.from)&&ids.has(edge.to)),...(pattern!=='ground'?{pattern}:{})}
33
+ }
34
+ export function initialPose(){return {x:0,y:1.85,z:9,yaw:0,pitch:-.035}}
35
+ export function fitPose(items,aspect=1){
36
+ if(!items.length)return {...initialPose(),y:8,z:14,pitch:-.6,focus:{x:0,y:0,z:0}}
37
+ const box=new Box3()
38
+ for(const item of items){box.expandByPoint(new Vector3(item.x-1,item.y??0,item.z-1));box.expandByPoint(new Vector3(item.x+1,(item.y??0)+3,item.z+1))}
39
+ const center=box.getCenter(new Vector3()),radius=box.getSize(new Vector3()).length()/2
40
+ const angle=Math.min(70*Math.PI/360,Math.atan(Math.tan(70*Math.PI/360)*Math.max(.1,aspect)))
41
+ const distance=Math.max(8,radius/Math.sin(angle)*1.45)
42
+ return {x:center.x,y:center.y+distance*.6,z:center.z+distance*.8,yaw:0,pitch:-Math.asin(.6),focus:{x:center.x,y:center.y,z:center.z}}
43
+ }
44
+ export function movePose(pose,forward,side,seconds,vertical=0){
45
+ const length=Math.max(1,Math.hypot(forward,side,vertical)),speed=4.5*Math.min(.05,Math.max(0,seconds))/length
46
+ return {...pose,x:clamp(pose.x+(Math.cos(pose.yaw)*side+Math.sin(pose.yaw)*forward)*speed,-worldLimits.radius,worldLimits.radius),y:clamp(pose.y+vertical*speed,.4,18),z:clamp(pose.z+(Math.sin(pose.yaw)*side-Math.cos(pose.yaw)*forward)*speed,-350,worldLimits.radius)}
47
+ }
48
+ export function lookPose(pose,dx,dy){return {...pose,yaw:(pose.yaw+clamp(dx,-150,150)*.004)%(Math.PI*2),pitch:clamp(pose.pitch-clamp(dy,-150,150)*.004,-.9,.9)}}
49
+ export function viewCamera(pose,aspect,zoom=1,camera=new PerspectiveCamera(70,aspect,.08,120)){
50
+ camera.aspect=aspect;camera.far=2000;camera.zoom=clamp(zoom,.65,1.5)
51
+ if(pose.cameraMode==='third-person')camera.position.set(pose.x-Math.sin(pose.yaw)*3,pose.y+1.2,pose.z+Math.cos(pose.yaw)*3)
52
+ else camera.position.set(pose.x,pose.y,pose.z)
53
+ camera.rotation.set(pose.pitch-(pose.cameraMode==='third-person'?.17:0),-pose.yaw,0,'YXZ');camera.updateProjectionMatrix();camera.updateMatrixWorld()
54
+ return camera
55
+ }
56
+ export function cameraMatrix(pose,aspect,zoom=1){
57
+ const camera=viewCamera(pose,aspect,zoom)
58
+ return camera.projectionMatrix.clone().multiply(camera.matrixWorldInverse).elements
59
+ }
60
+ export function screenPoint(matrix,x,y,z,width,height){
61
+ const v=[x,y,z,1],q=[0,0,0,0]
62
+ for(let r=0;r<4;r++)for(let c=0;c<4;c++)q[r]+=matrix[c*4+r]*v[c]
63
+ if(q[3]<=.08)return null
64
+ const nx=q[0]/q[3],ny=q[1]/q[3]
65
+ return {x:(nx*.5+.5)*width,y:(.5-ny*.5)*height,depth:q[3],visible:Math.abs(nx)<.95&&Math.abs(ny)<.88}
66
+ }
67
+ export function renderSize(width,height,dpr){
68
+ const scale=Math.min(worldLimits.dpr,Math.max(1,dpr),Math.sqrt(worldLimits.pixels/Math.max(1,width*height)))
69
+ return {width:Math.max(1,Math.floor(width*scale)),height:Math.max(1,Math.floor(height*scale))}
70
+ }
71
+ export function pickWorld(items,pose,x,y,width,height,zoom=1){
72
+ const ray=new Raycaster(),camera=viewCamera(pose,width/height,zoom),point=new Vector3(),box=new Box3()
73
+ ray.setFromCamera(new Vector2(2*x/width-1,1-2*y/height),camera)
74
+ let best=null,distance=2000
75
+ for(const item of items){
76
+ if(item.disabled)continue
77
+ const y=item.y??0
78
+ const [width,height,depth]=objectBounds[item.form??objectPresentation(item).form]
79
+ box.min.set(item.x-width/2,y,item.z-depth/2);box.max.set(item.x+width/2,y+height,item.z+depth/2)
80
+ if(ray.ray.intersectBox(box,point)){const near=point.distanceTo(camera.position);if(near<distance){best=item.id;distance=near}}
81
+ }
82
+ return best
83
+ }
@@ -0,0 +1,36 @@
1
+ export function createWorld(host: any, { objects, relations, pose: restored, onActivate, conversation, activity, target, pattern, context, activityPresentation }?: {
2
+ objects?: any[];
3
+ relations?: any[];
4
+ onActivate?: () => void;
5
+ conversation?: any;
6
+ activity?: any;
7
+ target?: any;
8
+ pattern?: string;
9
+ context?: any;
10
+ activityPresentation?: {};
11
+ }): {
12
+ update: (objects: any, relations?: any[]) => void;
13
+ close: () => void;
14
+ mount: (nextHost: any, options: any) => void;
15
+ park: () => void;
16
+ snapshot: () => {
17
+ target: any;
18
+ presentation: any;
19
+ navigation: string;
20
+ populated: boolean;
21
+ x: number;
22
+ y: number;
23
+ z: number;
24
+ yaw: number;
25
+ pitch: number;
26
+ } | {
27
+ target: any;
28
+ presentation: any;
29
+ navigation: string;
30
+ populated: boolean;
31
+ };
32
+ activity(value: any): void;
33
+ apply(value: any): void;
34
+ change(action: any): void;
35
+ pause(value: any): void;
36
+ };