@holoscript/core 8.0.13 → 8.0.14
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/README.md +14 -0
- package/bin/holo-decision.mjs +133 -0
- package/dist/{GLTFPipeline-L76YTXKA.js → GLTFPipeline-6LS3UDYX.js} +6 -6
- package/dist/{GLTFPipeline-ZWJN7GI6.cjs → GLTFPipeline-VTI6PRRQ.cjs} +10 -10
- package/dist/{HoloCompositionParser-P4Y3CXKQ.js → HoloCompositionParser-GILABNN3.js} +4 -4
- package/dist/{HoloCompositionParser-6GW2BZID.cjs → HoloCompositionParser-ZVRYVEA5.cjs} +11 -11
- package/dist/{HoloScriptPlusParser-RFNHRMPE.cjs → HoloScriptPlusParser-WTPRPRPR.cjs} +7 -7
- package/dist/{HoloScriptPlusParser-HQV6IJAH.js → HoloScriptPlusParser-ZP53ECSL.js} +4 -4
- package/dist/{SceneIRCompiler-KWUFHZ4O.cjs → SceneIRCompiler-7KQ5NKAU.cjs} +11 -11
- package/dist/{SceneIRCompiler-63KA3M2F.js → SceneIRCompiler-VX75RB6H.js} +6 -6
- package/dist/{chunk-EM2OGPYE.js → chunk-2GKF7JQ6.js} +3 -3
- package/dist/{chunk-HVX7QQYK.js → chunk-3KHRZTNW.js} +3 -3
- package/dist/{chunk-UTUD6GJI.js → chunk-42Z3SVHQ.js} +3 -3
- package/dist/{chunk-A4OOLBZD.js → chunk-47XZ62KM.js} +5 -5
- package/dist/{chunk-GGR7QUKK.cjs → chunk-4CRMVEON.cjs} +37 -37
- package/dist/{chunk-QBY7LRN2.js → chunk-4NEPHK2K.js} +3 -3
- package/dist/{chunk-65S62L4D.cjs → chunk-5B2AJKA5.cjs} +20 -20
- package/dist/{chunk-PJFTTSKE.js → chunk-5T6BWIB7.js} +4 -4
- package/dist/{chunk-KFVNCW4Q.js → chunk-62OYZT6Q.js} +3 -3
- package/dist/{chunk-Y57AVOM2.cjs → chunk-645EWDZW.cjs} +4 -4
- package/dist/{chunk-BP4KTMLY.js → chunk-6FCNMTL5.js} +4 -4
- package/dist/{chunk-77QI552M.js → chunk-73EDVY5D.js} +3 -3
- package/dist/{chunk-7IDJDMPU.cjs → chunk-7FYSSI3C.cjs} +27 -2
- package/dist/{chunk-WNYXT4TE.cjs → chunk-7ITJP33Q.cjs} +3 -3
- package/dist/chunk-7ZKQ32XK.js +1571 -0
- package/dist/{chunk-P7JEKJTZ.cjs → chunk-B6JHTTMR.cjs} +25 -14
- package/dist/{chunk-FG6PYGIZ.js → chunk-BLCWQ225.js} +4 -4
- package/dist/{chunk-FRSKLX6I.js → chunk-C73GVAYC.js} +10 -7
- package/dist/{chunk-PXVAPGTR.cjs → chunk-CESMPUEZ.cjs} +3774 -1138
- package/dist/{chunk-SQXAZHDG.js → chunk-CHIZSMWD.js} +3 -3
- package/dist/{chunk-G35CAQ7I.cjs → chunk-D44CEFY4.cjs} +5 -5
- package/dist/{chunk-TC7JEO6R.cjs → chunk-ERYKKPHW.cjs} +711 -52
- package/dist/{chunk-DJ5BNRIS.js → chunk-EYHPTJ3F.js} +3675 -1046
- package/dist/{chunk-J3UHV5T4.cjs → chunk-FGXJPJ3W.cjs} +4 -4
- package/dist/{chunk-IJU6AS67.cjs → chunk-FHR2AVMA.cjs} +23 -23
- package/dist/{chunk-LMB555QB.cjs → chunk-FOM36K5I.cjs} +18 -18
- package/dist/{chunk-ZRPALBQB.cjs → chunk-GGH3ZYDB.cjs} +5 -5
- package/dist/{chunk-2BYAUHBO.js → chunk-GI5YYZH6.js} +3 -3
- package/dist/{chunk-VTEXJOSD.cjs → chunk-GYJLP4BO.cjs} +30 -30
- package/dist/{chunk-OC6GV5QH.cjs → chunk-HCONR4J6.cjs} +38 -38
- package/dist/{chunk-B3CWTCK2.js → chunk-IA7FCSRY.js} +27 -2
- package/dist/{chunk-5FYMCVBY.js → chunk-IXFNA5TT.js} +4 -4
- package/dist/{chunk-G6KACNMO.js → chunk-JCVSCKUH.js} +4 -4
- package/dist/{chunk-FHVPN7BN.js → chunk-JQCXLKAU.js} +3 -3
- package/dist/{chunk-F3YT6GUF.cjs → chunk-KLM6QD6D.cjs} +14 -14
- package/dist/{chunk-UGQ3QNV6.js → chunk-KOGPVAP6.js} +24 -8
- package/dist/{chunk-CKV5NYAE.js → chunk-KYJD2UMV.js} +4 -4
- package/dist/{chunk-LB3ZZMKT.cjs → chunk-L4DBUX3N.cjs} +10 -10
- package/dist/{chunk-RXQNRZ5F.js → chunk-LGRKBTPM.js} +3 -3
- package/dist/{chunk-PDXX5UI4.cjs → chunk-LQNL3IYV.cjs} +38 -38
- package/dist/{chunk-WWNCM473.js → chunk-M7XR3F53.js} +4 -4
- package/dist/{chunk-IFW5IW4L.cjs → chunk-ML4V5PVU.cjs} +670 -627
- package/dist/{chunk-Q4K2GEMW.cjs → chunk-N7IYRAFG.cjs} +13 -10
- package/dist/chunk-N7MIIDLI.cjs +1605 -0
- package/dist/{chunk-G6EKZHXY.js → chunk-NWKARJM2.js} +4 -4
- package/dist/{chunk-VVAAZITA.cjs → chunk-OOP4S3GU.cjs} +5 -5
- package/dist/{chunk-U3D4SYQ5.js → chunk-ORBVCOTC.js} +3 -3
- package/dist/{chunk-RRQIAJLQ.cjs → chunk-OSKGCXAQ.cjs} +5 -5
- package/dist/{chunk-QEDMOGF3.cjs → chunk-P65OIMJT.cjs} +7 -7
- package/dist/{chunk-QM7FJB5W.js → chunk-PSWCJQS4.js} +4 -4
- package/dist/{chunk-RZ2EPXO3.cjs → chunk-PZ46WRHX.cjs} +8 -8
- package/dist/{chunk-AATYYLYK.js → chunk-Q4J2K7FI.js} +16 -5
- package/dist/{chunk-ONPVM4IV.js → chunk-QLPRI3QW.js} +698 -45
- package/dist/{chunk-WTH23ISI.js → chunk-R5VKLUMR.js} +4 -4
- package/dist/{chunk-NQN5LFN4.cjs → chunk-REN3LHO6.cjs} +19 -19
- package/dist/{chunk-JEJ7TT54.js → chunk-RVN7OPCP.js} +4 -4
- package/dist/{chunk-ZK7KX5CD.cjs → chunk-SE2Y5BSL.cjs} +6 -6
- package/dist/{chunk-2X3IPCBK.cjs → chunk-SKVBUGMC.cjs} +7 -7
- package/dist/{chunk-GZPUA2TL.cjs → chunk-SNMQUWIO.cjs} +10 -10
- package/dist/{chunk-OIBRWLPY.cjs → chunk-TKDDIE3T.cjs} +54 -3
- package/dist/{chunk-SQAAWO4V.cjs → chunk-TMNAKNV2.cjs} +5 -5
- package/dist/{chunk-5LVFTGKH.js → chunk-TZQKJOPC.js} +3 -3
- package/dist/{chunk-TSTFYGGX.js → chunk-UKEK2NZJ.js} +3 -3
- package/dist/{chunk-3IUHTGDD.cjs → chunk-VA7THINF.cjs} +38 -38
- package/dist/{chunk-Z557DVYN.js → chunk-VMCR5QVI.js} +4 -4
- package/dist/{chunk-IS7NTKY5.cjs → chunk-VTFLKSJL.cjs} +14 -14
- package/dist/{chunk-RVNXBF2M.js → chunk-VXHB6ZHY.js} +4 -4
- package/dist/{chunk-S4O7CFHQ.cjs → chunk-VZI7S7P6.cjs} +7 -7
- package/dist/{chunk-BQJ3YBF3.js → chunk-W4AJMMQR.js} +667 -628
- package/dist/{chunk-2KKOYZYI.cjs → chunk-XZC75AJ4.cjs} +5 -5
- package/dist/{chunk-FWCHPCD4.js → chunk-Y2OCKXVD.js} +4 -4
- package/dist/{chunk-XF63CZ7E.cjs → chunk-YBM34FUJ.cjs} +6 -6
- package/dist/{chunk-DUG3OQAS.js → chunk-YDSNDCV3.js} +3 -3
- package/dist/{chunk-566ZB4LZ.cjs → chunk-YM7UUYR5.cjs} +5 -5
- package/dist/{chunk-BCP4E7MD.cjs → chunk-YMI3ED3B.cjs} +26 -10
- package/dist/{chunk-CP7V3ATL.js → chunk-Z7NUL73U.js} +51 -4
- package/dist/cli/holoscript-runner.cjs +10 -10
- package/dist/cli/holoscript-runner.js +3 -3
- package/dist/compiler/agent-inference.cjs +8 -8
- package/dist/compiler/agent-inference.js +4 -4
- package/dist/compiler/android-xr.cjs +7 -7
- package/dist/compiler/android-xr.js +5 -5
- package/dist/compiler/android.cjs +6 -6
- package/dist/compiler/android.js +4 -4
- package/dist/compiler/context.cjs +6 -6
- package/dist/compiler/context.js +3 -3
- package/dist/compiler/daimon-seed.cjs +9 -9
- package/dist/compiler/daimon-seed.js +4 -4
- package/dist/compiler/domain-block-utils.cjs +165 -165
- package/dist/compiler/domain-block-utils.js +4 -4
- package/dist/compiler/dtdl.cjs +7 -7
- package/dist/compiler/dtdl.js +4 -4
- package/dist/compiler/gltf-pipeline.cjs +8 -8
- package/dist/compiler/gltf-pipeline.js +1 -1
- package/dist/compiler/godot.cjs +6 -6
- package/dist/compiler/godot.js +5 -5
- package/dist/compiler/incremental.cjs +8 -8
- package/dist/compiler/incremental.js +3 -3
- package/dist/compiler/index.cjs +871 -207
- package/dist/compiler/index.d.ts +21 -1
- package/dist/compiler/index.js +696 -60
- package/dist/compiler/ios.cjs +6 -6
- package/dist/compiler/ios.js +4 -4
- package/dist/compiler/llm-provider-capabilities.cjs +6 -6
- package/dist/compiler/llm-provider-capabilities.js +3 -3
- package/dist/compiler/openxr.cjs +6 -6
- package/dist/compiler/openxr.js +5 -5
- package/dist/compiler/sdf.cjs +7 -7
- package/dist/compiler/sdf.js +5 -5
- package/dist/compiler/state.cjs +6 -6
- package/dist/compiler/state.js +4 -4
- package/dist/compiler/trait-composition.cjs +7 -7
- package/dist/compiler/trait-composition.js +3 -3
- package/dist/compiler/unity.cjs +6 -6
- package/dist/compiler/unity.js +5 -5
- package/dist/compiler/unreal.cjs +7 -7
- package/dist/compiler/unreal.js +5 -5
- package/dist/compiler/urdf.cjs +14 -14
- package/dist/compiler/urdf.js +5 -5
- package/dist/compiler/usd-physics.cjs +7 -7
- package/dist/compiler/usd-physics.js +4 -4
- package/dist/compiler/visionos.cjs +6 -6
- package/dist/compiler/visionos.js +5 -5
- package/dist/compiler/vrchat.cjs +7 -7
- package/dist/compiler/vrchat.js +5 -5
- package/dist/compiler/wasm.cjs +10 -10
- package/dist/compiler/wasm.js +5 -5
- package/dist/compiler/webgpu.cjs +6 -6
- package/dist/compiler/webgpu.js +5 -5
- package/dist/entries/scripting.cjs +5 -5
- package/dist/entries/scripting.js +1 -1
- package/dist/evolution/index.cjs +17 -17
- package/dist/evolution/index.js +5 -5
- package/dist/index.cjs +915 -704
- package/dist/index.d.ts +70 -5
- package/dist/index.js +191 -108
- package/dist/parser.cjs +18 -18
- package/dist/parser.js +4 -4
- package/dist/reconstruction/index.cjs +127 -38
- package/dist/reconstruction/index.d.ts +159 -0
- package/dist/reconstruction/index.js +11 -2
- package/dist/runtime.d.ts +1 -0
- package/dist/traitDocs/traitDocs.cjs +8 -8
- package/dist/traitDocs/traitDocs.js +2 -2
- package/dist/traits/index.cjs +566 -471
- package/dist/traits/index.js +93 -11
- package/package.json +20 -16
- package/dist/chunk-DVHZD5HV.cjs +0 -639
- package/dist/chunk-G72754QB.js +0 -625
|
@@ -0,0 +1,1571 @@
|
|
|
1
|
+
import { createHoloMapRuntime, HOLOMAP_DEFAULTS } from './chunk-47XZ62KM.js';
|
|
2
|
+
import { parseHolo } from './chunk-JQCXLKAU.js';
|
|
3
|
+
import { createHash } from 'crypto';
|
|
4
|
+
|
|
5
|
+
// src/reconstruction/FusedAttentionKernel.ts
|
|
6
|
+
var FUSED_ATTENTION_WGSL = `
|
|
7
|
+
struct Params {
|
|
8
|
+
qRows: u32,
|
|
9
|
+
kRows: u32,
|
|
10
|
+
dModel: u32,
|
|
11
|
+
vCols: u32,
|
|
12
|
+
scale: f32,
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
@group(0) @binding(0) var<storage, read> q: array<f32>;
|
|
16
|
+
@group(0) @binding(1) var<storage, read> k: array<f32>;
|
|
17
|
+
@group(0) @binding(2) var<storage, read> v: array<f32>;
|
|
18
|
+
@group(0) @binding(3) var<storage, read_write> out: array<f32>;
|
|
19
|
+
@group(0) @binding(4) var<uniform> params: Params;
|
|
20
|
+
|
|
21
|
+
fn qIndex(row: u32, col: u32) -> u32 {
|
|
22
|
+
return row * params.dModel + col;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
fn kIndex(row: u32, col: u32) -> u32 {
|
|
26
|
+
return row * params.dModel + col;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
fn vIndex(row: u32, col: u32) -> u32 {
|
|
30
|
+
return row * params.vCols + col;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
fn oIndex(row: u32, col: u32) -> u32 {
|
|
34
|
+
return row * params.vCols + col;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
@compute @workgroup_size(1, 1, 1)
|
|
38
|
+
fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
|
|
39
|
+
let qRow = gid.x;
|
|
40
|
+
if (qRow >= params.qRows) {
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
var maxScore = -1e30;
|
|
45
|
+
for (var kRow: u32 = 0u; kRow < params.kRows; kRow = kRow + 1u) {
|
|
46
|
+
var dot = 0.0;
|
|
47
|
+
for (var d: u32 = 0u; d < params.dModel; d = d + 1u) {
|
|
48
|
+
dot = dot + q[qIndex(qRow, d)] * k[kIndex(kRow, d)];
|
|
49
|
+
}
|
|
50
|
+
let score = dot * params.scale;
|
|
51
|
+
if (score > maxScore) {
|
|
52
|
+
maxScore = score;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
var denom = 0.0;
|
|
57
|
+
for (var kRow: u32 = 0u; kRow < params.kRows; kRow = kRow + 1u) {
|
|
58
|
+
var dot = 0.0;
|
|
59
|
+
for (var d: u32 = 0u; d < params.dModel; d = d + 1u) {
|
|
60
|
+
dot = dot + q[qIndex(qRow, d)] * k[kIndex(kRow, d)];
|
|
61
|
+
}
|
|
62
|
+
denom = denom + exp(dot * params.scale - maxScore);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
for (var c: u32 = 0u; c < params.vCols; c = c + 1u) {
|
|
66
|
+
var acc = 0.0;
|
|
67
|
+
for (var kRow: u32 = 0u; kRow < params.kRows; kRow = kRow + 1u) {
|
|
68
|
+
var dot = 0.0;
|
|
69
|
+
for (var d: u32 = 0u; d < params.dModel; d = d + 1u) {
|
|
70
|
+
dot = dot + q[qIndex(qRow, d)] * k[kIndex(kRow, d)];
|
|
71
|
+
}
|
|
72
|
+
let w = exp(dot * params.scale - maxScore) / max(denom, 1e-9);
|
|
73
|
+
acc = acc + w * v[vIndex(kRow, c)];
|
|
74
|
+
}
|
|
75
|
+
out[oIndex(qRow, c)] = acc;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
`;
|
|
79
|
+
function cpuFusedAttention(input) {
|
|
80
|
+
const { q, k, v, qRows, kRows, dModel, vCols } = input;
|
|
81
|
+
const out = new Float32Array(qRows * vCols);
|
|
82
|
+
const scale = 1 / Math.sqrt(Math.max(dModel, 1));
|
|
83
|
+
for (let qr = 0; qr < qRows; qr += 1) {
|
|
84
|
+
let maxScore = -Number.MAX_VALUE;
|
|
85
|
+
const scores = new Float32Array(kRows);
|
|
86
|
+
for (let kr = 0; kr < kRows; kr += 1) {
|
|
87
|
+
let dot = 0;
|
|
88
|
+
for (let d = 0; d < dModel; d += 1) {
|
|
89
|
+
dot += q[qr * dModel + d] * k[kr * dModel + d];
|
|
90
|
+
}
|
|
91
|
+
const s = dot * scale;
|
|
92
|
+
scores[kr] = s;
|
|
93
|
+
if (s > maxScore) maxScore = s;
|
|
94
|
+
}
|
|
95
|
+
let denom = 0;
|
|
96
|
+
for (let kr = 0; kr < kRows; kr += 1) {
|
|
97
|
+
const ex = Math.exp(scores[kr] - maxScore);
|
|
98
|
+
scores[kr] = ex;
|
|
99
|
+
denom += ex;
|
|
100
|
+
}
|
|
101
|
+
for (let c = 0; c < vCols; c += 1) {
|
|
102
|
+
let acc = 0;
|
|
103
|
+
for (let kr = 0; kr < kRows; kr += 1) {
|
|
104
|
+
const w = scores[kr] / Math.max(denom, 1e-9);
|
|
105
|
+
acc += w * v[kr * vCols + c];
|
|
106
|
+
}
|
|
107
|
+
out[qr * vCols + c] = acc;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
return out;
|
|
111
|
+
}
|
|
112
|
+
async function webGpuFusedAttention(input) {
|
|
113
|
+
const nav = globalThis.navigator;
|
|
114
|
+
const gpu = nav.gpu;
|
|
115
|
+
if (!gpu) {
|
|
116
|
+
throw new Error("WebGPU not available");
|
|
117
|
+
}
|
|
118
|
+
const adapter = await gpu.requestAdapter();
|
|
119
|
+
if (!adapter) {
|
|
120
|
+
throw new Error("No WebGPU adapter");
|
|
121
|
+
}
|
|
122
|
+
const device = await adapter.requestDevice();
|
|
123
|
+
const { q, k, v, qRows, kRows, dModel, vCols } = input;
|
|
124
|
+
const qBytes = q.byteLength;
|
|
125
|
+
const kBytes = k.byteLength;
|
|
126
|
+
const vBytes = v.byteLength;
|
|
127
|
+
const outElems = qRows * vCols;
|
|
128
|
+
const outBytes = outElems * 4;
|
|
129
|
+
const qBuf = device.createBuffer({
|
|
130
|
+
size: Math.max(4, qBytes),
|
|
131
|
+
usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST
|
|
132
|
+
});
|
|
133
|
+
const kBuf = device.createBuffer({
|
|
134
|
+
size: Math.max(4, kBytes),
|
|
135
|
+
usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST
|
|
136
|
+
});
|
|
137
|
+
const vBuf = device.createBuffer({
|
|
138
|
+
size: Math.max(4, vBytes),
|
|
139
|
+
usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST
|
|
140
|
+
});
|
|
141
|
+
const outBuf = device.createBuffer({
|
|
142
|
+
size: Math.max(4, outBytes),
|
|
143
|
+
usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST
|
|
144
|
+
});
|
|
145
|
+
const uniBuf = device.createBuffer({
|
|
146
|
+
size: 256,
|
|
147
|
+
usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST
|
|
148
|
+
});
|
|
149
|
+
const uniformData = new ArrayBuffer(256);
|
|
150
|
+
const u32 = new Uint32Array(uniformData, 0, 4);
|
|
151
|
+
const f32 = new Float32Array(uniformData, 16, 1);
|
|
152
|
+
u32[0] = qRows;
|
|
153
|
+
u32[1] = kRows;
|
|
154
|
+
u32[2] = dModel;
|
|
155
|
+
u32[3] = vCols;
|
|
156
|
+
f32[0] = 1 / Math.sqrt(Math.max(dModel, 1));
|
|
157
|
+
device.queue.writeBuffer(qBuf, 0, q.buffer, q.byteOffset, q.byteLength);
|
|
158
|
+
device.queue.writeBuffer(kBuf, 0, k.buffer, k.byteOffset, k.byteLength);
|
|
159
|
+
device.queue.writeBuffer(vBuf, 0, v.buffer, v.byteOffset, v.byteLength);
|
|
160
|
+
device.queue.writeBuffer(uniBuf, 0, uniformData);
|
|
161
|
+
const module = device.createShaderModule({ code: FUSED_ATTENTION_WGSL });
|
|
162
|
+
const bindLayout = device.createBindGroupLayout({
|
|
163
|
+
entries: [
|
|
164
|
+
{
|
|
165
|
+
binding: 0,
|
|
166
|
+
visibility: GPUShaderStage.COMPUTE,
|
|
167
|
+
buffer: { type: "read-only-storage" }
|
|
168
|
+
},
|
|
169
|
+
{
|
|
170
|
+
binding: 1,
|
|
171
|
+
visibility: GPUShaderStage.COMPUTE,
|
|
172
|
+
buffer: { type: "read-only-storage" }
|
|
173
|
+
},
|
|
174
|
+
{
|
|
175
|
+
binding: 2,
|
|
176
|
+
visibility: GPUShaderStage.COMPUTE,
|
|
177
|
+
buffer: { type: "read-only-storage" }
|
|
178
|
+
},
|
|
179
|
+
{ binding: 3, visibility: GPUShaderStage.COMPUTE, buffer: { type: "storage" } },
|
|
180
|
+
{ binding: 4, visibility: GPUShaderStage.COMPUTE, buffer: { type: "uniform" } }
|
|
181
|
+
]
|
|
182
|
+
});
|
|
183
|
+
const pipelineLayout = device.createPipelineLayout({ bindGroupLayouts: [bindLayout] });
|
|
184
|
+
const pipeline = await device.createComputePipelineAsync({
|
|
185
|
+
layout: pipelineLayout,
|
|
186
|
+
compute: { module, entryPoint: "main" }
|
|
187
|
+
});
|
|
188
|
+
const bindGroup = device.createBindGroup({
|
|
189
|
+
layout: bindLayout,
|
|
190
|
+
entries: [
|
|
191
|
+
{ binding: 0, resource: { buffer: qBuf } },
|
|
192
|
+
{ binding: 1, resource: { buffer: kBuf } },
|
|
193
|
+
{ binding: 2, resource: { buffer: vBuf } },
|
|
194
|
+
{ binding: 3, resource: { buffer: outBuf } },
|
|
195
|
+
{ binding: 4, resource: { buffer: uniBuf } }
|
|
196
|
+
]
|
|
197
|
+
});
|
|
198
|
+
const encoder = device.createCommandEncoder();
|
|
199
|
+
const pass = encoder.beginComputePass();
|
|
200
|
+
pass.setPipeline(pipeline);
|
|
201
|
+
pass.setBindGroup(0, bindGroup);
|
|
202
|
+
pass.dispatchWorkgroups(qRows, 1, 1);
|
|
203
|
+
pass.end();
|
|
204
|
+
const readBuf = device.createBuffer({
|
|
205
|
+
size: Math.max(4, outBytes),
|
|
206
|
+
usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ
|
|
207
|
+
});
|
|
208
|
+
encoder.copyBufferToBuffer(outBuf, 0, readBuf, 0, outBytes);
|
|
209
|
+
device.queue.submit([encoder.finish()]);
|
|
210
|
+
await readBuf.mapAsync(GPUMapMode.READ);
|
|
211
|
+
const mapped = new Float32Array(readBuf.getMappedRange());
|
|
212
|
+
const copy = new Float32Array(outElems);
|
|
213
|
+
copy.set(mapped);
|
|
214
|
+
readBuf.unmap();
|
|
215
|
+
qBuf.destroy();
|
|
216
|
+
kBuf.destroy();
|
|
217
|
+
vBuf.destroy();
|
|
218
|
+
outBuf.destroy();
|
|
219
|
+
uniBuf.destroy();
|
|
220
|
+
readBuf.destroy();
|
|
221
|
+
return copy;
|
|
222
|
+
}
|
|
223
|
+
var CpuFusedAttentionBackend = class {
|
|
224
|
+
constructor() {
|
|
225
|
+
this.name = "cpu";
|
|
226
|
+
}
|
|
227
|
+
async compute(input) {
|
|
228
|
+
return cpuFusedAttention(input);
|
|
229
|
+
}
|
|
230
|
+
};
|
|
231
|
+
var WebGpuFusedAttentionBackend = class {
|
|
232
|
+
constructor() {
|
|
233
|
+
this.name = "webgpu";
|
|
234
|
+
}
|
|
235
|
+
async compute(input) {
|
|
236
|
+
try {
|
|
237
|
+
return await webGpuFusedAttention(input);
|
|
238
|
+
} catch {
|
|
239
|
+
return cpuFusedAttention(input);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
};
|
|
243
|
+
async function createFusedAttentionBackend() {
|
|
244
|
+
if (typeof navigator !== "undefined" && "gpu" in navigator) {
|
|
245
|
+
return new WebGpuFusedAttentionBackend();
|
|
246
|
+
}
|
|
247
|
+
return new CpuFusedAttentionBackend();
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// src/reconstruction/TrajectoryMemory.ts
|
|
251
|
+
var TRAJECTORY_DEFAULTS = {
|
|
252
|
+
maxKeyframes: 512,
|
|
253
|
+
keyframeStrideMeters: 0.25,
|
|
254
|
+
loopClosureThreshold: 0.92
|
|
255
|
+
};
|
|
256
|
+
function createTrajectoryMemory(_config) {
|
|
257
|
+
throw new Error(
|
|
258
|
+
"TrajectoryMemory is scaffolded in Sprint 1; implementation lands in Sprint 2. See reconstruction/RFC-HoloMap.md \xA7Trajectory"
|
|
259
|
+
);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// src/reconstruction/AnchorContext.ts
|
|
263
|
+
var ANCHOR_DEFAULTS = {
|
|
264
|
+
warmupFrames: 30,
|
|
265
|
+
minConfidence: 0.75,
|
|
266
|
+
maxDriftBeforeReanchor: 2
|
|
267
|
+
};
|
|
268
|
+
function createAnchorContext(_config) {
|
|
269
|
+
throw new Error(
|
|
270
|
+
"AnchorContext is scaffolded in Sprint 1; implementation lands in Sprint 2. See reconstruction/RFC-HoloMap.md \xA7Anchor"
|
|
271
|
+
);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// src/reconstruction/mobileSensorBundle.ts
|
|
275
|
+
var HOLOMAP_MOBILE_SENSOR_BUNDLE_VERSION = "holomap-mobile-sensor-bundle/v1";
|
|
276
|
+
var ARCORE_DEPTH_RECOMMENDED_RANGE_MILLIMETERS = {
|
|
277
|
+
near: 500,
|
|
278
|
+
far: 5e3
|
|
279
|
+
};
|
|
280
|
+
function finiteNumber(value) {
|
|
281
|
+
return Number.isFinite(value);
|
|
282
|
+
}
|
|
283
|
+
function finiteArray(values, expectedLength) {
|
|
284
|
+
if (values.length !== expectedLength) return false;
|
|
285
|
+
for (let i = 0; i < values.length; i += 1) {
|
|
286
|
+
if (!finiteNumber(Number(values[i]))) return false;
|
|
287
|
+
}
|
|
288
|
+
return true;
|
|
289
|
+
}
|
|
290
|
+
function clampUnit(value) {
|
|
291
|
+
if (!Number.isFinite(value)) return 0;
|
|
292
|
+
return Math.max(0, Math.min(1, value));
|
|
293
|
+
}
|
|
294
|
+
function requirePositiveInteger(value, name) {
|
|
295
|
+
if (!Number.isInteger(value) || value <= 0) {
|
|
296
|
+
throw new Error(`${name} must be a positive integer`);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
function normalizeArCoreDepthRange(range) {
|
|
300
|
+
const near = range?.near ?? ARCORE_DEPTH_RECOMMENDED_RANGE_MILLIMETERS.near;
|
|
301
|
+
const far = range?.far ?? ARCORE_DEPTH_RECOMMENDED_RANGE_MILLIMETERS.far;
|
|
302
|
+
if (!finiteNumber(near) || near <= 0)
|
|
303
|
+
throw new Error("arCoreDepthRange.near must be positive finite");
|
|
304
|
+
if (!finiteNumber(far) || far <= near)
|
|
305
|
+
throw new Error("arCoreDepthRange.far must be finite and greater than near");
|
|
306
|
+
return { near, far };
|
|
307
|
+
}
|
|
308
|
+
function requirePlane(width, height, values, name, maxValue) {
|
|
309
|
+
requirePositiveInteger(width, `${name}.width`);
|
|
310
|
+
requirePositiveInteger(height, `${name}.height`);
|
|
311
|
+
const expected = width * height;
|
|
312
|
+
if (values.length !== expected) {
|
|
313
|
+
throw new Error(`${name}.values length must be ${expected}`);
|
|
314
|
+
}
|
|
315
|
+
for (let i = 0; i < values.length; i += 1) {
|
|
316
|
+
const value = Number(values[i]);
|
|
317
|
+
if (!Number.isFinite(value) || value < 0 || value > maxValue) {
|
|
318
|
+
throw new Error(`${name}.values[${i}] must be a finite value from 0 to ${maxValue}`);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
function nearestPlaneIndex(x, y, srcWidth, srcHeight, dstWidth, dstHeight) {
|
|
323
|
+
const sx = Math.min(srcWidth - 1, Math.max(0, Math.floor((x + 0.5) * srcWidth / dstWidth)));
|
|
324
|
+
const sy = Math.min(srcHeight - 1, Math.max(0, Math.floor((y + 0.5) * srcHeight / dstHeight)));
|
|
325
|
+
return sy * srcWidth + sx;
|
|
326
|
+
}
|
|
327
|
+
function arCoreDepthMillimetersToUnit(depthMillimeters, range) {
|
|
328
|
+
if (!Number.isFinite(depthMillimeters) || depthMillimeters <= 0) return 1;
|
|
329
|
+
return clampUnit((depthMillimeters - range.near) / (range.far - range.near));
|
|
330
|
+
}
|
|
331
|
+
function arCoreDepthCellIsTrusted(depthMillimeters, range) {
|
|
332
|
+
return Number.isFinite(depthMillimeters) && depthMillimeters > 0 && depthMillimeters <= range.far;
|
|
333
|
+
}
|
|
334
|
+
function toUint8Array(values, name) {
|
|
335
|
+
const out = new Uint8Array(values.length);
|
|
336
|
+
for (let i = 0; i < values.length; i += 1) {
|
|
337
|
+
const value = Number(values[i]);
|
|
338
|
+
if (!Number.isFinite(value) || value < 0 || value > 255) {
|
|
339
|
+
throw new Error(`${name}[${i}] must be a finite byte`);
|
|
340
|
+
}
|
|
341
|
+
out[i] = Math.round(value);
|
|
342
|
+
}
|
|
343
|
+
return out;
|
|
344
|
+
}
|
|
345
|
+
function toFloat32UnitArray(values, name) {
|
|
346
|
+
const out = new Float32Array(values.length);
|
|
347
|
+
for (let i = 0; i < values.length; i += 1) {
|
|
348
|
+
const value = Number(values[i]);
|
|
349
|
+
if (!Number.isFinite(value)) throw new Error(`${name}[${i}] must be finite`);
|
|
350
|
+
out[i] = clampUnit(value);
|
|
351
|
+
}
|
|
352
|
+
return out;
|
|
353
|
+
}
|
|
354
|
+
function confidenceToUnitArray(plane) {
|
|
355
|
+
const out = new Float32Array(plane.values.length);
|
|
356
|
+
for (let i = 0; i < plane.values.length; i += 1) {
|
|
357
|
+
const value = Number(plane.values[i]);
|
|
358
|
+
if (!Number.isFinite(value)) throw new Error(`sceneDepthConfidence[${i}] must be finite`);
|
|
359
|
+
if (plane.encoding === "unit") out[i] = clampUnit(value);
|
|
360
|
+
else if (plane.encoding === "arkit-0-2") out[i] = clampUnit(value / 2);
|
|
361
|
+
else out[i] = clampUnit(value / 255);
|
|
362
|
+
}
|
|
363
|
+
return out;
|
|
364
|
+
}
|
|
365
|
+
function arCoreDepthFrameToMobileSensorFrame(frame, depthRangeMillimeters) {
|
|
366
|
+
requirePositiveInteger(frame.width, "arCoreFrame.width");
|
|
367
|
+
requirePositiveInteger(frame.height, "arCoreFrame.height");
|
|
368
|
+
if (frame.stride !== 3 && frame.stride !== 4) {
|
|
369
|
+
throw new Error("arCoreFrame.stride must be 3 or 4");
|
|
370
|
+
}
|
|
371
|
+
if (!finiteNumber(frame.timestampMs)) throw new Error("arCoreFrame.timestampMs must be finite");
|
|
372
|
+
if (!Number.isInteger(frame.index) || frame.index < 0) {
|
|
373
|
+
throw new Error("arCoreFrame.index must be a non-negative integer");
|
|
374
|
+
}
|
|
375
|
+
if (!finiteArray(frame.rgb, frame.width * frame.height * frame.stride)) {
|
|
376
|
+
throw new Error("arCoreFrame.rgb length/content invalid");
|
|
377
|
+
}
|
|
378
|
+
const depth = frame.depthImage16Bits;
|
|
379
|
+
const range = normalizeArCoreDepthRange(depthRangeMillimeters);
|
|
380
|
+
requirePlane(depth.width, depth.height, depth.millimeters, "arCoreFrame.depthImage16Bits", 65535);
|
|
381
|
+
if (frame.rawDepthConfidenceImage) {
|
|
382
|
+
const confidence = frame.rawDepthConfidenceImage;
|
|
383
|
+
requirePlane(
|
|
384
|
+
confidence.width,
|
|
385
|
+
confidence.height,
|
|
386
|
+
confidence.values,
|
|
387
|
+
"arCoreFrame.rawDepthConfidenceImage",
|
|
388
|
+
255
|
|
389
|
+
);
|
|
390
|
+
if (confidence.width !== depth.width || confidence.height !== depth.height) {
|
|
391
|
+
throw new Error("arCoreFrame.rawDepthConfidenceImage dimensions must match depthImage16Bits");
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
const pixelCount = frame.width * frame.height;
|
|
395
|
+
const normalizedDepth = new Float32Array(pixelCount);
|
|
396
|
+
const metricDepthMeters = new Float32Array(pixelCount);
|
|
397
|
+
const normalizedConfidence = new Float32Array(pixelCount);
|
|
398
|
+
for (let y = 0; y < frame.height; y += 1) {
|
|
399
|
+
for (let x = 0; x < frame.width; x += 1) {
|
|
400
|
+
const dstIndex = y * frame.width + x;
|
|
401
|
+
const srcIndex = nearestPlaneIndex(
|
|
402
|
+
x,
|
|
403
|
+
y,
|
|
404
|
+
depth.width,
|
|
405
|
+
depth.height,
|
|
406
|
+
frame.width,
|
|
407
|
+
frame.height
|
|
408
|
+
);
|
|
409
|
+
const depthMillimeters = Number(depth.millimeters[srcIndex]);
|
|
410
|
+
normalizedDepth[dstIndex] = arCoreDepthMillimetersToUnit(depthMillimeters, range);
|
|
411
|
+
metricDepthMeters[dstIndex] = arCoreDepthCellIsTrusted(depthMillimeters, range) ? depthMillimeters / 1e3 : 0;
|
|
412
|
+
const sensorConfidence = frame.rawDepthConfidenceImage ? clampUnit(Number(frame.rawDepthConfidenceImage.values[srcIndex]) / 255) : 1;
|
|
413
|
+
normalizedConfidence[dstIndex] = arCoreDepthCellIsTrusted(depthMillimeters, range) ? sensorConfidence : 0;
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
return {
|
|
417
|
+
index: frame.index,
|
|
418
|
+
timestampMs: frame.timestampMs,
|
|
419
|
+
width: frame.width,
|
|
420
|
+
height: frame.height,
|
|
421
|
+
stride: frame.stride,
|
|
422
|
+
rgb: frame.rgb,
|
|
423
|
+
sceneDepth: {
|
|
424
|
+
width: frame.width,
|
|
425
|
+
height: frame.height,
|
|
426
|
+
values: normalizedDepth
|
|
427
|
+
},
|
|
428
|
+
sceneDepthMeters: {
|
|
429
|
+
width: frame.width,
|
|
430
|
+
height: frame.height,
|
|
431
|
+
values: metricDepthMeters
|
|
432
|
+
},
|
|
433
|
+
sceneDepthConfidence: {
|
|
434
|
+
width: frame.width,
|
|
435
|
+
height: frame.height,
|
|
436
|
+
encoding: "unit",
|
|
437
|
+
values: normalizedConfidence
|
|
438
|
+
},
|
|
439
|
+
cameraTransformColumnMajor4x4: frame.cameraTransformColumnMajor4x4,
|
|
440
|
+
devicePose: frame.devicePose
|
|
441
|
+
};
|
|
442
|
+
}
|
|
443
|
+
function createArCoreDepthMobileSensorBundle(input) {
|
|
444
|
+
if (!input.bundleId) throw new Error("arCoreBundle.bundleId missing");
|
|
445
|
+
if (!Array.isArray(input.frames) || input.frames.length === 0) {
|
|
446
|
+
throw new Error("arCoreBundle.frames missing");
|
|
447
|
+
}
|
|
448
|
+
return {
|
|
449
|
+
schemaVersion: HOLOMAP_MOBILE_SENSOR_BUNDLE_VERSION,
|
|
450
|
+
bundleId: input.bundleId,
|
|
451
|
+
capture: {
|
|
452
|
+
platform: "android-arcore-depth",
|
|
453
|
+
deviceModel: input.deviceModel,
|
|
454
|
+
coordinateSystem: "arcore-right-handed-y-up",
|
|
455
|
+
intrinsics: input.intrinsics
|
|
456
|
+
},
|
|
457
|
+
frames: input.frames.map(
|
|
458
|
+
(frame) => arCoreDepthFrameToMobileSensorFrame(frame, input.depthRangeMillimeters)
|
|
459
|
+
)
|
|
460
|
+
};
|
|
461
|
+
}
|
|
462
|
+
function normalizeQuaternion(q) {
|
|
463
|
+
const len = Math.hypot(q[0], q[1], q[2], q[3]);
|
|
464
|
+
if (len <= 1e-12) return [0, 0, 0, 1];
|
|
465
|
+
return [q[0] / len, q[1] / len, q[2] / len, q[3] / len];
|
|
466
|
+
}
|
|
467
|
+
function cameraPoseFromColumnMajorTransform(transform, confidence = 1) {
|
|
468
|
+
if (transform.length !== 16 || !finiteArray(transform, 16)) {
|
|
469
|
+
throw new Error("cameraTransformColumnMajor4x4 must contain 16 finite numbers");
|
|
470
|
+
}
|
|
471
|
+
const m00 = Number(transform[0]);
|
|
472
|
+
const m01 = Number(transform[4]);
|
|
473
|
+
const m02 = Number(transform[8]);
|
|
474
|
+
const m10 = Number(transform[1]);
|
|
475
|
+
const m11 = Number(transform[5]);
|
|
476
|
+
const m12 = Number(transform[9]);
|
|
477
|
+
const m20 = Number(transform[2]);
|
|
478
|
+
const m21 = Number(transform[6]);
|
|
479
|
+
const m22 = Number(transform[10]);
|
|
480
|
+
const trace = m00 + m11 + m22;
|
|
481
|
+
let rotation;
|
|
482
|
+
if (trace > 0) {
|
|
483
|
+
const s = Math.sqrt(trace + 1) * 2;
|
|
484
|
+
rotation = [(m21 - m12) / s, (m02 - m20) / s, (m10 - m01) / s, 0.25 * s];
|
|
485
|
+
} else if (m00 > m11 && m00 > m22) {
|
|
486
|
+
const s = Math.sqrt(1 + m00 - m11 - m22) * 2;
|
|
487
|
+
rotation = [0.25 * s, (m01 + m10) / s, (m02 + m20) / s, (m21 - m12) / s];
|
|
488
|
+
} else if (m11 > m22) {
|
|
489
|
+
const s = Math.sqrt(1 + m11 - m00 - m22) * 2;
|
|
490
|
+
rotation = [(m01 + m10) / s, 0.25 * s, (m12 + m21) / s, (m02 - m20) / s];
|
|
491
|
+
} else {
|
|
492
|
+
const s = Math.sqrt(1 + m22 - m00 - m11) * 2;
|
|
493
|
+
rotation = [(m02 + m20) / s, (m12 + m21) / s, 0.25 * s, (m10 - m01) / s];
|
|
494
|
+
}
|
|
495
|
+
return {
|
|
496
|
+
position: [Number(transform[12]), Number(transform[13]), Number(transform[14])],
|
|
497
|
+
rotation: normalizeQuaternion(rotation),
|
|
498
|
+
confidence: clampUnit(confidence)
|
|
499
|
+
};
|
|
500
|
+
}
|
|
501
|
+
function validateMobileSensorBundle(bundle) {
|
|
502
|
+
const errors = [];
|
|
503
|
+
if (bundle.schemaVersion !== HOLOMAP_MOBILE_SENSOR_BUNDLE_VERSION)
|
|
504
|
+
errors.push("schemaVersion mismatch");
|
|
505
|
+
if (!bundle.bundleId) errors.push("bundleId missing");
|
|
506
|
+
if (!bundle.capture?.platform) errors.push("capture.platform missing");
|
|
507
|
+
const intrinsics = bundle.capture?.intrinsics;
|
|
508
|
+
if (!intrinsics) {
|
|
509
|
+
errors.push("capture.intrinsics missing");
|
|
510
|
+
} else {
|
|
511
|
+
for (const key of ["width", "height", "fx", "fy", "cx", "cy"]) {
|
|
512
|
+
if (!finiteNumber(intrinsics[key]) || intrinsics[key] <= 0) {
|
|
513
|
+
errors.push(`capture.intrinsics.${key} must be positive finite`);
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
if (!intrinsics.source) errors.push("capture.intrinsics.source missing");
|
|
517
|
+
}
|
|
518
|
+
if (!Array.isArray(bundle.frames) || bundle.frames.length === 0) errors.push("frames missing");
|
|
519
|
+
for (const frame of bundle.frames ?? []) {
|
|
520
|
+
const prefix = `frame[${frame?.index ?? "?"}]`;
|
|
521
|
+
if (!Number.isInteger(frame.index) || frame.index < 0)
|
|
522
|
+
errors.push(`${prefix}.index must be a non-negative integer`);
|
|
523
|
+
if (!finiteNumber(frame.timestampMs)) errors.push(`${prefix}.timestampMs must be finite`);
|
|
524
|
+
if (!Number.isInteger(frame.width) || frame.width <= 0)
|
|
525
|
+
errors.push(`${prefix}.width must be positive integer`);
|
|
526
|
+
if (!Number.isInteger(frame.height) || frame.height <= 0)
|
|
527
|
+
errors.push(`${prefix}.height must be positive integer`);
|
|
528
|
+
if (frame.stride !== 3 && frame.stride !== 4) errors.push(`${prefix}.stride must be 3 or 4`);
|
|
529
|
+
const pixelCount = frame.width * frame.height;
|
|
530
|
+
if (!finiteArray(frame.rgb, pixelCount * frame.stride))
|
|
531
|
+
errors.push(`${prefix}.rgb length/content invalid`);
|
|
532
|
+
if (frame.sceneDepth) {
|
|
533
|
+
if (frame.sceneDepth.width !== frame.width || frame.sceneDepth.height !== frame.height) {
|
|
534
|
+
errors.push(`${prefix}.sceneDepth dimensions must match frame`);
|
|
535
|
+
}
|
|
536
|
+
if (!finiteArray(frame.sceneDepth.values, pixelCount))
|
|
537
|
+
errors.push(`${prefix}.sceneDepth values invalid`);
|
|
538
|
+
}
|
|
539
|
+
if (frame.sceneDepthMeters) {
|
|
540
|
+
if (frame.sceneDepthMeters.width !== frame.width || frame.sceneDepthMeters.height !== frame.height) {
|
|
541
|
+
errors.push(`${prefix}.sceneDepthMeters dimensions must match frame`);
|
|
542
|
+
}
|
|
543
|
+
if (!finiteArray(frame.sceneDepthMeters.values, pixelCount))
|
|
544
|
+
errors.push(`${prefix}.sceneDepthMeters values invalid`);
|
|
545
|
+
}
|
|
546
|
+
if (frame.sceneDepthConfidence) {
|
|
547
|
+
if (frame.sceneDepthConfidence.width !== frame.width || frame.sceneDepthConfidence.height !== frame.height) {
|
|
548
|
+
errors.push(`${prefix}.sceneDepthConfidence dimensions must match frame`);
|
|
549
|
+
}
|
|
550
|
+
if (!finiteArray(frame.sceneDepthConfidence.values, pixelCount)) {
|
|
551
|
+
errors.push(`${prefix}.sceneDepthConfidence values invalid`);
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
if (frame.cameraTransformColumnMajor4x4 && !finiteArray(frame.cameraTransformColumnMajor4x4, 16)) {
|
|
555
|
+
errors.push(`${prefix}.cameraTransformColumnMajor4x4 invalid`);
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
return errors;
|
|
559
|
+
}
|
|
560
|
+
function mobileSensorBundleToFrames(bundle) {
|
|
561
|
+
const errors = validateMobileSensorBundle(bundle);
|
|
562
|
+
if (errors.length > 0)
|
|
563
|
+
throw new Error(`Invalid HoloMap mobile sensor bundle: ${errors.join("; ")}`);
|
|
564
|
+
return bundle.frames.map((frame) => ({
|
|
565
|
+
index: frame.index,
|
|
566
|
+
timestampMs: frame.timestampMs,
|
|
567
|
+
width: frame.width,
|
|
568
|
+
height: frame.height,
|
|
569
|
+
stride: frame.stride,
|
|
570
|
+
rgb: toUint8Array(frame.rgb, `frame[${frame.index}].rgb`),
|
|
571
|
+
depth: frame.sceneDepth ? toFloat32UnitArray(frame.sceneDepth.values, `frame[${frame.index}].sceneDepth`) : void 0,
|
|
572
|
+
depthMeters: frame.sceneDepthMeters ? new Float32Array(Array.from(frame.sceneDepthMeters.values, Number)) : void 0,
|
|
573
|
+
depthConfidence: frame.sceneDepthConfidence ? confidenceToUnitArray(frame.sceneDepthConfidence) : void 0,
|
|
574
|
+
cameraIntrinsics: {
|
|
575
|
+
width: bundle.capture.intrinsics.width,
|
|
576
|
+
height: bundle.capture.intrinsics.height,
|
|
577
|
+
fx: bundle.capture.intrinsics.fx,
|
|
578
|
+
fy: bundle.capture.intrinsics.fy,
|
|
579
|
+
cx: bundle.capture.intrinsics.cx,
|
|
580
|
+
cy: bundle.capture.intrinsics.cy
|
|
581
|
+
},
|
|
582
|
+
devicePose: frame.cameraTransformColumnMajor4x4 ? cameraPoseFromColumnMajorTransform(
|
|
583
|
+
frame.cameraTransformColumnMajor4x4,
|
|
584
|
+
frame.devicePose?.confidence ?? 1
|
|
585
|
+
) : frame.devicePose
|
|
586
|
+
}));
|
|
587
|
+
}
|
|
588
|
+
async function replayMobileSensorBundle(bundle, config = {}) {
|
|
589
|
+
const frames = mobileSensorBundleToFrames(bundle);
|
|
590
|
+
const runtime = createHoloMapRuntime();
|
|
591
|
+
await runtime.init({
|
|
592
|
+
...HOLOMAP_DEFAULTS,
|
|
593
|
+
seed: 0,
|
|
594
|
+
modelHash: bundle.bundleId,
|
|
595
|
+
videoHash: bundle.bundleId,
|
|
596
|
+
targetFPS: 1e5,
|
|
597
|
+
...config
|
|
598
|
+
});
|
|
599
|
+
const steps = [];
|
|
600
|
+
try {
|
|
601
|
+
for (const frame of frames) {
|
|
602
|
+
const step = await runtime.step(frame);
|
|
603
|
+
if (step) steps.push(step);
|
|
604
|
+
}
|
|
605
|
+
const manifest = await runtime.finalize();
|
|
606
|
+
const meshAnchorCount = bundle.frames.reduce(
|
|
607
|
+
(sum, frame) => sum + (frame.meshAnchors?.length ?? 0),
|
|
608
|
+
0
|
|
609
|
+
);
|
|
610
|
+
return {
|
|
611
|
+
source: {
|
|
612
|
+
bundleId: bundle.bundleId,
|
|
613
|
+
platform: bundle.capture.platform,
|
|
614
|
+
frameCount: bundle.frames.length,
|
|
615
|
+
meshAnchorCount
|
|
616
|
+
},
|
|
617
|
+
steps,
|
|
618
|
+
manifest
|
|
619
|
+
};
|
|
620
|
+
} finally {
|
|
621
|
+
await runtime.dispose();
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
var PERCEIVER_CONSENSUS_VERSION = "perceiver-consensus-v3";
|
|
625
|
+
var POSITION_EPSILON = 1e-6;
|
|
626
|
+
var PERCEIVER_FACT_CLASSES = [
|
|
627
|
+
"source-name",
|
|
628
|
+
"agent-entities",
|
|
629
|
+
"affordance-count",
|
|
630
|
+
"affordance-names",
|
|
631
|
+
"physical-entities",
|
|
632
|
+
"geometry",
|
|
633
|
+
"position"
|
|
634
|
+
];
|
|
635
|
+
function canonicalPhysicalId(name) {
|
|
636
|
+
let folded = name.replace(/[^a-zA-Z0-9_]/g, "_").toLowerCase();
|
|
637
|
+
if (folded.startsWith("_")) folded = "a" + folded;
|
|
638
|
+
return folded;
|
|
639
|
+
}
|
|
640
|
+
function sha256Hex(s) {
|
|
641
|
+
return createHash("sha256").update(s).digest("hex");
|
|
642
|
+
}
|
|
643
|
+
function positionsEqual(a, b) {
|
|
644
|
+
return a.length === b.length && a.every((v, i) => Math.abs(v - b[i]) <= POSITION_EPSILON);
|
|
645
|
+
}
|
|
646
|
+
function derivePerceiverConsensus(derivations) {
|
|
647
|
+
if (derivations.length < 2) {
|
|
648
|
+
throw new Error(
|
|
649
|
+
"PerceiverConsensusReceipt: consensus requires >= 2 independent perceivers \u2014 a single-perceiver receipt is circular"
|
|
650
|
+
);
|
|
651
|
+
}
|
|
652
|
+
const ids = derivations.map((d) => d.perceiver);
|
|
653
|
+
if (new Set(ids).size !== ids.length) {
|
|
654
|
+
throw new Error(
|
|
655
|
+
`PerceiverConsensusReceipt: duplicate perceiver ids ${JSON.stringify(ids)} \u2014 self-agreement is not consensus`
|
|
656
|
+
);
|
|
657
|
+
}
|
|
658
|
+
const disagreements = [];
|
|
659
|
+
let comparedFacts = 0;
|
|
660
|
+
const expressers = (cls) => derivations.filter((d) => d.expresses.includes(cls));
|
|
661
|
+
const named = expressers("source-name").filter((d) => d.sourceName != null);
|
|
662
|
+
let sourceName = named[0]?.sourceName ?? null;
|
|
663
|
+
if (named.length >= 2) {
|
|
664
|
+
comparedFacts++;
|
|
665
|
+
const names = new Set(named.map((d) => d.sourceName));
|
|
666
|
+
if (names.size > 1) {
|
|
667
|
+
sourceName = null;
|
|
668
|
+
disagreements.push({
|
|
669
|
+
fact: "sourceName",
|
|
670
|
+
claims: Object.fromEntries(named.map((d) => [d.perceiver, d.sourceName])),
|
|
671
|
+
detail: `perceivers claim different source compositions: ${[...names].join(" vs ")}`
|
|
672
|
+
});
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
const agentVoters = expressers("agent-entities");
|
|
676
|
+
if (agentVoters.length >= 2) {
|
|
677
|
+
const byPerceiver = new Map(
|
|
678
|
+
agentVoters.map((d) => [d.perceiver, new Map(d.entities.map((e) => [e.id, e]))])
|
|
679
|
+
);
|
|
680
|
+
const idUnion = [...new Set(agentVoters.flatMap((d) => d.entities.map((e) => e.id)))].sort();
|
|
681
|
+
const sharedIds = [];
|
|
682
|
+
for (const id of idUnion) {
|
|
683
|
+
comparedFacts++;
|
|
684
|
+
if (agentVoters.every((d) => byPerceiver.get(d.perceiver).has(id))) {
|
|
685
|
+
sharedIds.push(id);
|
|
686
|
+
} else {
|
|
687
|
+
const dissent = agentVoters.filter((d) => !byPerceiver.get(d.perceiver).has(id));
|
|
688
|
+
disagreements.push({
|
|
689
|
+
fact: `entity:${id}`,
|
|
690
|
+
claims: Object.fromEntries(
|
|
691
|
+
agentVoters.map((d) => [
|
|
692
|
+
d.perceiver,
|
|
693
|
+
byPerceiver.get(d.perceiver).has(id) ? "present" : "absent"
|
|
694
|
+
])
|
|
695
|
+
),
|
|
696
|
+
detail: `agent entity "${id}" is invisible to ${dissent.map((d) => d.perceiver).join(", ")} \u2014 the surface is lying to someone`
|
|
697
|
+
});
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
for (const id of sharedIds) {
|
|
701
|
+
const countVoters = agentVoters.filter((d) => d.expresses.includes("affordance-count"));
|
|
702
|
+
if (countVoters.length >= 2) {
|
|
703
|
+
comparedFacts++;
|
|
704
|
+
const perEntity = countVoters.map((d) => ({
|
|
705
|
+
perceiver: d.perceiver,
|
|
706
|
+
entity: byPerceiver.get(d.perceiver).get(id)
|
|
707
|
+
}));
|
|
708
|
+
const counts = new Set(perEntity.map((p) => p.entity.offerCount));
|
|
709
|
+
if (counts.size > 1) {
|
|
710
|
+
disagreements.push({
|
|
711
|
+
fact: `entity:${id}:offerCount`,
|
|
712
|
+
claims: Object.fromEntries(perEntity.map((p) => [p.perceiver, p.entity.offerCount])),
|
|
713
|
+
detail: `affordance count on "${id}" diverges: ${perEntity.map((p) => `${p.perceiver} derives ${p.entity.offerCount}`).join("; ")}`
|
|
714
|
+
});
|
|
715
|
+
continue;
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
const nameVoters = agentVoters.filter(
|
|
719
|
+
(d) => d.expresses.includes("affordance-names") && Array.isArray(byPerceiver.get(d.perceiver).get(id).offers)
|
|
720
|
+
);
|
|
721
|
+
if (nameVoters.length >= 2) {
|
|
722
|
+
comparedFacts++;
|
|
723
|
+
const actionSets = nameVoters.map(
|
|
724
|
+
(d) => [...new Set(byPerceiver.get(d.perceiver).get(id).offers.map((o) => o.action))].sort().join(",")
|
|
725
|
+
);
|
|
726
|
+
if (new Set(actionSets).size > 1) {
|
|
727
|
+
disagreements.push({
|
|
728
|
+
fact: `entity:${id}:offerActions`,
|
|
729
|
+
claims: Object.fromEntries(nameVoters.map((d, i) => [d.perceiver, actionSets[i]])),
|
|
730
|
+
detail: `named affordances on "${id}" diverge: ${nameVoters.map((d, i) => `${d.perceiver} derives [${actionSets[i]}]`).join("; ")}`
|
|
731
|
+
});
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
const physVoters = expressers("physical-entities");
|
|
737
|
+
if (physVoters.length >= 2) {
|
|
738
|
+
const byPerceiver = new Map(
|
|
739
|
+
physVoters.map((d) => [
|
|
740
|
+
d.perceiver,
|
|
741
|
+
new Map((d.physicalEntities ?? []).map((e) => [e.id, e]))
|
|
742
|
+
])
|
|
743
|
+
);
|
|
744
|
+
const idUnion = [
|
|
745
|
+
...new Set(physVoters.flatMap((d) => (d.physicalEntities ?? []).map((e) => e.id)))
|
|
746
|
+
].sort();
|
|
747
|
+
const sharedIds = [];
|
|
748
|
+
for (const id of idUnion) {
|
|
749
|
+
comparedFacts++;
|
|
750
|
+
if (physVoters.every((d) => byPerceiver.get(d.perceiver).has(id))) {
|
|
751
|
+
sharedIds.push(id);
|
|
752
|
+
} else {
|
|
753
|
+
const dissent = physVoters.filter((d) => !byPerceiver.get(d.perceiver).has(id));
|
|
754
|
+
disagreements.push({
|
|
755
|
+
fact: `physical:${id}`,
|
|
756
|
+
claims: Object.fromEntries(
|
|
757
|
+
physVoters.map((d) => [
|
|
758
|
+
d.perceiver,
|
|
759
|
+
byPerceiver.get(d.perceiver).has(id) ? "present" : "absent"
|
|
760
|
+
])
|
|
761
|
+
),
|
|
762
|
+
detail: `physical entity "${id}" is invisible to ${dissent.map((d) => d.perceiver).join(", ")} \u2014 the surface is lying to someone`
|
|
763
|
+
});
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
for (const id of sharedIds) {
|
|
767
|
+
const geomVoters = physVoters.filter(
|
|
768
|
+
(d) => d.expresses.includes("geometry") && typeof byPerceiver.get(d.perceiver).get(id).geometry === "string"
|
|
769
|
+
);
|
|
770
|
+
if (geomVoters.length >= 2) {
|
|
771
|
+
comparedFacts++;
|
|
772
|
+
const geoms = geomVoters.map((d) => byPerceiver.get(d.perceiver).get(id).geometry);
|
|
773
|
+
if (new Set(geoms).size > 1) {
|
|
774
|
+
disagreements.push({
|
|
775
|
+
fact: `physical:${id}:geometry`,
|
|
776
|
+
claims: Object.fromEntries(geomVoters.map((d, i) => [d.perceiver, geoms[i]])),
|
|
777
|
+
detail: `geometry of "${id}" diverges: ${geomVoters.map((d, i) => `${d.perceiver} derives ${geoms[i]}`).join("; ")}`
|
|
778
|
+
});
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
const posVoters = physVoters.filter(
|
|
782
|
+
(d) => d.expresses.includes("position") && Array.isArray(byPerceiver.get(d.perceiver).get(id).position)
|
|
783
|
+
);
|
|
784
|
+
if (posVoters.length >= 2) {
|
|
785
|
+
comparedFacts++;
|
|
786
|
+
const positions = posVoters.map((d) => byPerceiver.get(d.perceiver).get(id).position);
|
|
787
|
+
const agrees = positions.every((p) => positionsEqual(p, positions[0]));
|
|
788
|
+
if (!agrees) {
|
|
789
|
+
disagreements.push({
|
|
790
|
+
fact: `physical:${id}:position`,
|
|
791
|
+
claims: Object.fromEntries(
|
|
792
|
+
posVoters.map((d, i) => [d.perceiver, JSON.stringify(positions[i])])
|
|
793
|
+
),
|
|
794
|
+
detail: `position of "${id}" diverges beyond \u03B5=${POSITION_EPSILON}: ${posVoters.map((d, i) => `${d.perceiver} derives ${JSON.stringify(positions[i])}`).join("; ")}`
|
|
795
|
+
});
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
for (const src of expressers("affordance-names")) {
|
|
801
|
+
for (const entity of src.entities) {
|
|
802
|
+
for (const offer of entity.offers ?? []) {
|
|
803
|
+
if (!offer.target) continue;
|
|
804
|
+
const actorKey = canonicalPhysicalId(entity.id);
|
|
805
|
+
const targetKey = canonicalPhysicalId(offer.target);
|
|
806
|
+
for (const p of expressers("physical-entities")) {
|
|
807
|
+
const map = new Map((p.physicalEntities ?? []).map((e) => [e.id, e]));
|
|
808
|
+
const actor = map.get(actorKey);
|
|
809
|
+
const target = map.get(targetKey);
|
|
810
|
+
const fact = `grounding:${entity.id}:${offer.action}`;
|
|
811
|
+
const claim = `${offer.action}@${offer.target}`;
|
|
812
|
+
if (!actor || !target) {
|
|
813
|
+
comparedFacts++;
|
|
814
|
+
disagreements.push({
|
|
815
|
+
fact,
|
|
816
|
+
claims: {
|
|
817
|
+
[src.perceiver]: claim,
|
|
818
|
+
[p.perceiver]: !actor ? `no physical actor "${actorKey}"` : `no physical target "${targetKey}"`
|
|
819
|
+
},
|
|
820
|
+
detail: `${src.perceiver} offers "${offer.action}" on "${offer.target}" but ${p.perceiver} derives no physical ${!actor ? "actor" : "target"} \u2014 the affordance is ungrounded`
|
|
821
|
+
});
|
|
822
|
+
continue;
|
|
823
|
+
}
|
|
824
|
+
const center = actor.reachCenter ?? actor.position;
|
|
825
|
+
const radius = actor.reachRadius ?? (actor.mobility !== "actuated" && actor.extent != null ? actor.extent : void 0);
|
|
826
|
+
if (!center || radius == null || !target.position || target.extent == null) {
|
|
827
|
+
continue;
|
|
828
|
+
}
|
|
829
|
+
comparedFacts++;
|
|
830
|
+
const dist = Math.hypot(
|
|
831
|
+
...center.map((v, i) => v - target.position[i])
|
|
832
|
+
);
|
|
833
|
+
const reach = radius + target.extent + POSITION_EPSILON;
|
|
834
|
+
if (dist > reach) {
|
|
835
|
+
disagreements.push({
|
|
836
|
+
fact,
|
|
837
|
+
claims: {
|
|
838
|
+
[src.perceiver]: claim,
|
|
839
|
+
[p.perceiver]: `unreachable (dist ${dist.toFixed(3)} > reach ${reach.toFixed(3)})`
|
|
840
|
+
},
|
|
841
|
+
detail: `${src.perceiver} offers "${offer.action}" on "${offer.target}" but ${p.perceiver} derives no reachable affordance (distance ${dist.toFixed(3)} exceeds the ${actor.mobility ?? "fixed"}-mobility outer reach envelope ${reach.toFixed(3)}) \u2014 the surface is lying to someone`
|
|
842
|
+
});
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
}
|
|
848
|
+
const verdict = disagreements.length > 0 ? "FALSIFIED" : "CONSENSUS";
|
|
849
|
+
const perceivers = derivations.map((d) => ({
|
|
850
|
+
perceiver: d.perceiver,
|
|
851
|
+
artifactHash: d.artifactHash,
|
|
852
|
+
expresses: [...d.expresses].sort(),
|
|
853
|
+
entityCount: d.entities.length,
|
|
854
|
+
physicalEntityCount: d.physicalEntities?.length ?? 0,
|
|
855
|
+
// DERIVED coverage (whu2): the complement of declared voting rights,
|
|
856
|
+
// unioned with the extractor's free-text gap notes — a hardcoded list
|
|
857
|
+
// can go stale the day an artifact learns a fact class; the complement
|
|
858
|
+
// cannot.
|
|
859
|
+
coverageGaps: [
|
|
860
|
+
.../* @__PURE__ */ new Set([
|
|
861
|
+
...PERCEIVER_FACT_CLASSES.filter((c) => !d.expresses.includes(c)),
|
|
862
|
+
...d.coverageGaps
|
|
863
|
+
])
|
|
864
|
+
].sort()
|
|
865
|
+
})).sort((a, b) => a.perceiver.localeCompare(b.perceiver));
|
|
866
|
+
const receiptHash = sha256Hex(
|
|
867
|
+
[
|
|
868
|
+
PERCEIVER_CONSENSUS_VERSION,
|
|
869
|
+
verdict,
|
|
870
|
+
sourceName ?? "",
|
|
871
|
+
...perceivers.map(
|
|
872
|
+
(p) => `${p.perceiver}:${p.artifactHash}:${p.entityCount}:${p.physicalEntityCount}`
|
|
873
|
+
),
|
|
874
|
+
...disagreements.map((d) => `${d.fact}=${JSON.stringify(Object.entries(d.claims).sort())}`).sort()
|
|
875
|
+
].join("|")
|
|
876
|
+
);
|
|
877
|
+
return {
|
|
878
|
+
version: PERCEIVER_CONSENSUS_VERSION,
|
|
879
|
+
verdict,
|
|
880
|
+
sourceName,
|
|
881
|
+
perceivers,
|
|
882
|
+
comparedFacts,
|
|
883
|
+
disagreements,
|
|
884
|
+
receiptHash
|
|
885
|
+
};
|
|
886
|
+
}
|
|
887
|
+
var WEBGPU_PERCEIVER = "webgpu";
|
|
888
|
+
function deriveWebGPUPerception(artifact) {
|
|
889
|
+
if (typeof artifact !== "string" || !artifact.includes("holoGraphObjects")) {
|
|
890
|
+
throw new Error(
|
|
891
|
+
"deriveWebGPUPerception: input is not a WebGPU compile artifact (missing holoGraphObjects scene registry)"
|
|
892
|
+
);
|
|
893
|
+
}
|
|
894
|
+
const sourceName = /^\/\/ Source: composition "([^"]+)"/m.exec(artifact)?.[1] ?? null;
|
|
895
|
+
const manifestRaw = /const holoSceneManifest = (\{[\s\S]*?\});/.exec(artifact)?.[1];
|
|
896
|
+
if (manifestRaw) {
|
|
897
|
+
let manifest;
|
|
898
|
+
try {
|
|
899
|
+
manifest = JSON.parse(manifestRaw);
|
|
900
|
+
} catch (e) {
|
|
901
|
+
throw new Error(
|
|
902
|
+
`deriveWebGPUPerception: holoSceneManifest is not valid JSON \u2014 refusing a malformed manifest (${e.message})`
|
|
903
|
+
);
|
|
904
|
+
}
|
|
905
|
+
const objects = manifest.objects;
|
|
906
|
+
if (!Array.isArray(objects)) {
|
|
907
|
+
throw new Error("deriveWebGPUPerception: holoSceneManifest has no objects[] \u2014 malformed manifest");
|
|
908
|
+
}
|
|
909
|
+
const entities2 = [];
|
|
910
|
+
const physicalEntities2 = [];
|
|
911
|
+
for (const o of objects) {
|
|
912
|
+
const id = typeof o.id === "string" ? o.id : "";
|
|
913
|
+
const traits = Array.isArray(o.traits) ? o.traits : [];
|
|
914
|
+
if (!id) continue;
|
|
915
|
+
const affordances = Array.isArray(o.affordances) ? o.affordances.filter((a) => typeof a === "string") : [];
|
|
916
|
+
const position = Array.isArray(o.position) && o.position.every((n) => Number.isFinite(n)) ? o.position : void 0;
|
|
917
|
+
const geometry = typeof o.geometry === "string" ? o.geometry : void 0;
|
|
918
|
+
if (traits.includes("agent")) {
|
|
919
|
+
const entity = {
|
|
920
|
+
id,
|
|
921
|
+
kind: "agent",
|
|
922
|
+
offerCount: affordances.length,
|
|
923
|
+
offers: affordances.map((action) => ({ action }))
|
|
924
|
+
};
|
|
925
|
+
if (geometry) entity.geometry = geometry;
|
|
926
|
+
if (position) entity.position = position;
|
|
927
|
+
entities2.push(entity);
|
|
928
|
+
}
|
|
929
|
+
if (o.visible === true) {
|
|
930
|
+
const physical = { id: canonicalPhysicalId(id), label: id };
|
|
931
|
+
if (geometry) physical.geometry = geometry;
|
|
932
|
+
if (position) physical.position = position;
|
|
933
|
+
if (physicalEntities2.some((e) => e.id === physical.id)) {
|
|
934
|
+
throw new Error(
|
|
935
|
+
`deriveWebGPUPerception: two scene objects fold to canonical id "${physical.id}" \u2014 refusing an ambiguous world`
|
|
936
|
+
);
|
|
937
|
+
}
|
|
938
|
+
physicalEntities2.push(physical);
|
|
939
|
+
}
|
|
940
|
+
}
|
|
941
|
+
return {
|
|
942
|
+
perceiver: WEBGPU_PERCEIVER,
|
|
943
|
+
artifactHash: createHash("sha256").update(artifact).digest("hex"),
|
|
944
|
+
expresses: [
|
|
945
|
+
"source-name",
|
|
946
|
+
"agent-entities",
|
|
947
|
+
"affordance-count",
|
|
948
|
+
"affordance-names",
|
|
949
|
+
// the manifest names actions — full parity (rycr)
|
|
950
|
+
"physical-entities",
|
|
951
|
+
"geometry",
|
|
952
|
+
"position"
|
|
953
|
+
],
|
|
954
|
+
sourceName,
|
|
955
|
+
entities: entities2,
|
|
956
|
+
physicalEntities: physicalEntities2,
|
|
957
|
+
coverageGaps: []
|
|
958
|
+
};
|
|
959
|
+
}
|
|
960
|
+
const entities = [];
|
|
961
|
+
const physicalEntities = [];
|
|
962
|
+
const pushes = artifact.match(/holoGraphObjects\.push\(\{[\s\S]*?\}\);/g) ?? [];
|
|
963
|
+
for (const push of pushes) {
|
|
964
|
+
const id = /\bid: "([^"]+)"/.exec(push)?.[1];
|
|
965
|
+
const traitsRaw = /\btraits: (\[[^\]]*\])/.exec(push)?.[1];
|
|
966
|
+
if (!id || !traitsRaw) continue;
|
|
967
|
+
let traits;
|
|
968
|
+
try {
|
|
969
|
+
traits = JSON.parse(traitsRaw);
|
|
970
|
+
} catch {
|
|
971
|
+
continue;
|
|
972
|
+
}
|
|
973
|
+
if (!Array.isArray(traits)) continue;
|
|
974
|
+
const geometry = /\bgeometry: "([^"]+)"/.exec(push)?.[1];
|
|
975
|
+
const positionRaw = /\bposition: (\[[^\]]*\])/.exec(push)?.[1];
|
|
976
|
+
let position;
|
|
977
|
+
if (positionRaw) {
|
|
978
|
+
try {
|
|
979
|
+
const parsed = JSON.parse(positionRaw);
|
|
980
|
+
if (Array.isArray(parsed) && parsed.every((n) => Number.isFinite(n))) position = parsed;
|
|
981
|
+
} catch {
|
|
982
|
+
}
|
|
983
|
+
}
|
|
984
|
+
if (traits.includes("agent")) {
|
|
985
|
+
const entity = {
|
|
986
|
+
id,
|
|
987
|
+
kind: "agent",
|
|
988
|
+
offerCount: traits.filter((t) => t === "tool").length
|
|
989
|
+
};
|
|
990
|
+
if (geometry) entity.geometry = geometry;
|
|
991
|
+
if (position) entity.position = position;
|
|
992
|
+
entities.push(entity);
|
|
993
|
+
}
|
|
994
|
+
if (/\bvisible: true\b/.test(push)) {
|
|
995
|
+
const physical = { id: canonicalPhysicalId(id), label: id };
|
|
996
|
+
if (geometry) physical.geometry = geometry;
|
|
997
|
+
if (position) physical.position = position;
|
|
998
|
+
if (physicalEntities.some((e) => e.id === physical.id)) {
|
|
999
|
+
throw new Error(
|
|
1000
|
+
`deriveWebGPUPerception: two scene objects fold to canonical id "${physical.id}" \u2014 refusing an ambiguous world`
|
|
1001
|
+
);
|
|
1002
|
+
}
|
|
1003
|
+
physicalEntities.push(physical);
|
|
1004
|
+
}
|
|
1005
|
+
}
|
|
1006
|
+
return {
|
|
1007
|
+
perceiver: WEBGPU_PERCEIVER,
|
|
1008
|
+
artifactHash: createHash("sha256").update(artifact).digest("hex"),
|
|
1009
|
+
expresses: [
|
|
1010
|
+
"source-name",
|
|
1011
|
+
"agent-entities",
|
|
1012
|
+
"affordance-count",
|
|
1013
|
+
"physical-entities",
|
|
1014
|
+
"geometry",
|
|
1015
|
+
"position"
|
|
1016
|
+
],
|
|
1017
|
+
sourceName,
|
|
1018
|
+
entities,
|
|
1019
|
+
physicalEntities,
|
|
1020
|
+
coverageGaps: ["affordance-action-names"]
|
|
1021
|
+
};
|
|
1022
|
+
}
|
|
1023
|
+
var AGENT_INFERENCE_PERCEIVER = "agent-inference";
|
|
1024
|
+
function deriveAgentInferencePerception(files) {
|
|
1025
|
+
if (files == null || typeof files !== "object" || typeof files["config.json"] !== "string") {
|
|
1026
|
+
throw new Error(
|
|
1027
|
+
"deriveAgentInferencePerception: input is not an agent-inference artifact map (missing config.json)"
|
|
1028
|
+
);
|
|
1029
|
+
}
|
|
1030
|
+
let config;
|
|
1031
|
+
try {
|
|
1032
|
+
config = JSON.parse(files["config.json"]);
|
|
1033
|
+
} catch (e) {
|
|
1034
|
+
throw new Error(
|
|
1035
|
+
`deriveAgentInferencePerception: config.json is not valid JSON \u2014 refusing to derive from a malformed artifact (${e.message})`
|
|
1036
|
+
);
|
|
1037
|
+
}
|
|
1038
|
+
const agents = config.agents;
|
|
1039
|
+
if (!Array.isArray(agents)) {
|
|
1040
|
+
throw new Error(
|
|
1041
|
+
"deriveAgentInferencePerception: config.json has no agents[] \u2014 malformed artifact"
|
|
1042
|
+
);
|
|
1043
|
+
}
|
|
1044
|
+
const banner = files["agent.ts"] ?? files["agent.py"] ?? "";
|
|
1045
|
+
const structuredSource = config.source;
|
|
1046
|
+
const sourceName = typeof structuredSource === "string" && structuredSource.length > 0 ? structuredSource : /^\s*\*\s*(.+?) — Agent Inference Script$/m.exec(banner)?.[1] ?? null;
|
|
1047
|
+
const entities = agents.map((a) => {
|
|
1048
|
+
const agent = a;
|
|
1049
|
+
if (typeof agent.name !== "string" || agent.name.length === 0) {
|
|
1050
|
+
throw new Error(
|
|
1051
|
+
"deriveAgentInferencePerception: agent without a name in config.json \u2014 malformed artifact"
|
|
1052
|
+
);
|
|
1053
|
+
}
|
|
1054
|
+
const details = Array.isArray(agent.tool_details) ? agent.tool_details.filter(
|
|
1055
|
+
(t) => typeof t?.name === "string"
|
|
1056
|
+
) : null;
|
|
1057
|
+
const offers = details ? details.map((t) => ({
|
|
1058
|
+
action: t.name,
|
|
1059
|
+
...typeof t.target === "string" && t.target.length > 0 ? { target: t.target } : {}
|
|
1060
|
+
})) : (Array.isArray(agent.tools) ? agent.tools.filter((t) => typeof t === "string") : []).map(
|
|
1061
|
+
(action) => ({ action })
|
|
1062
|
+
);
|
|
1063
|
+
return {
|
|
1064
|
+
id: agent.name,
|
|
1065
|
+
kind: "agent",
|
|
1066
|
+
offerCount: offers.length,
|
|
1067
|
+
offers
|
|
1068
|
+
};
|
|
1069
|
+
});
|
|
1070
|
+
const hash = createHash("sha256");
|
|
1071
|
+
for (const name of Object.keys(files).sort()) {
|
|
1072
|
+
hash.update(name).update("\0").update(files[name]).update("\0");
|
|
1073
|
+
}
|
|
1074
|
+
return {
|
|
1075
|
+
perceiver: AGENT_INFERENCE_PERCEIVER,
|
|
1076
|
+
artifactHash: hash.digest("hex"),
|
|
1077
|
+
expresses: ["source-name", "agent-entities", "affordance-count", "affordance-names"],
|
|
1078
|
+
sourceName,
|
|
1079
|
+
entities,
|
|
1080
|
+
coverageGaps: ["spatial-position", "geometry", "non-agent-entities"]
|
|
1081
|
+
};
|
|
1082
|
+
}
|
|
1083
|
+
var URDF_PERCEIVER = "urdf";
|
|
1084
|
+
function deriveUrdfPerception(artifact) {
|
|
1085
|
+
if (typeof artifact !== "string" || !/<robot[\s>]/.test(artifact)) {
|
|
1086
|
+
throw new Error(
|
|
1087
|
+
"deriveUrdfPerception: input is not a URDF robot description (missing <robot> element)"
|
|
1088
|
+
);
|
|
1089
|
+
}
|
|
1090
|
+
const sourceName = /<!-- Source: composition "([^"]+)" -->/.exec(artifact)?.[1] ?? null;
|
|
1091
|
+
const jointByChild = /* @__PURE__ */ new Map();
|
|
1092
|
+
for (const joint of artifact.match(/<joint\s+[^>]*>[\s\S]*?<\/joint>/g) ?? []) {
|
|
1093
|
+
const type = /<joint\s+[^>]*type="([^"]+)"/.exec(joint)?.[1];
|
|
1094
|
+
const child = /<child\s+link="([^"]+)"/.exec(joint)?.[1];
|
|
1095
|
+
if (!type || !child) continue;
|
|
1096
|
+
const parsed = { type };
|
|
1097
|
+
parsed.parent = /<parent\s+link="([^"]+)"/.exec(joint)?.[1];
|
|
1098
|
+
const xyz = /<origin\s+xyz="([^"]+)"/.exec(joint)?.[1];
|
|
1099
|
+
if (xyz) {
|
|
1100
|
+
const parts = xyz.trim().split(/\s+/).map(Number);
|
|
1101
|
+
if (parts.length === 3 && parts.every(Number.isFinite)) parsed.origin = parts;
|
|
1102
|
+
}
|
|
1103
|
+
const rpy = /<origin\s+[^>]*rpy="([^"]+)"/.exec(joint)?.[1];
|
|
1104
|
+
if (rpy) {
|
|
1105
|
+
const parts = rpy.trim().split(/\s+/).map(Number);
|
|
1106
|
+
parsed.rotated = parts.some((v) => Number.isFinite(v) && Math.abs(v) > 1e-9);
|
|
1107
|
+
}
|
|
1108
|
+
const lower = /<limit\s+[^>]*lower="([\d.eE+-]+)"/.exec(joint)?.[1];
|
|
1109
|
+
const upper = /<limit\s+[^>]*upper="([\d.eE+-]+)"/.exec(joint)?.[1];
|
|
1110
|
+
if (lower != null && Number.isFinite(Number(lower))) parsed.lower = Number(lower);
|
|
1111
|
+
if (upper != null && Number.isFinite(Number(upper))) parsed.upper = Number(upper);
|
|
1112
|
+
jointByChild.set(child, parsed);
|
|
1113
|
+
}
|
|
1114
|
+
function worldChainOrigin(link) {
|
|
1115
|
+
const sum = [0, 0, 0];
|
|
1116
|
+
let cur = link;
|
|
1117
|
+
for (let hops = 0; cur && hops < 64; hops++) {
|
|
1118
|
+
const j = jointByChild.get(cur);
|
|
1119
|
+
if (!j) return sum;
|
|
1120
|
+
if (j.rotated) return null;
|
|
1121
|
+
if (j.origin) {
|
|
1122
|
+
sum[0] += j.origin[0];
|
|
1123
|
+
sum[1] += j.origin[1];
|
|
1124
|
+
sum[2] += j.origin[2];
|
|
1125
|
+
}
|
|
1126
|
+
cur = j.parent;
|
|
1127
|
+
}
|
|
1128
|
+
return cur ? null : sum;
|
|
1129
|
+
}
|
|
1130
|
+
const physicalEntities = [];
|
|
1131
|
+
const links = artifact.match(/<link\s+name="[^"]+">[\s\S]*?<\/link>/g) ?? [];
|
|
1132
|
+
for (const link of links) {
|
|
1133
|
+
const label = /<link\s+name="([^"]+)">/.exec(link)?.[1];
|
|
1134
|
+
if (!label) continue;
|
|
1135
|
+
const visual = /<visual>([\s\S]*?)<\/visual>/.exec(link)?.[1];
|
|
1136
|
+
if (!visual) continue;
|
|
1137
|
+
const entity = { id: canonicalPhysicalId(label), label };
|
|
1138
|
+
const geom = /<geometry>\s*<(\w+)[\s/>]/.exec(visual)?.[1];
|
|
1139
|
+
if (geom) entity.geometry = geom;
|
|
1140
|
+
const sphereR = /<sphere\s+radius="([\d.eE+-]+)"/.exec(visual)?.[1];
|
|
1141
|
+
const boxSize = /<box\s+size="([^"]+)"/.exec(visual)?.[1];
|
|
1142
|
+
const cyl = /<cylinder\s+radius="([\d.eE+-]+)"\s+length="([\d.eE+-]+)"/.exec(visual);
|
|
1143
|
+
if (sphereR != null && Number.isFinite(Number(sphereR))) {
|
|
1144
|
+
entity.extent = Number(sphereR);
|
|
1145
|
+
} else if (boxSize) {
|
|
1146
|
+
const dims = boxSize.trim().split(/\s+/).map(Number);
|
|
1147
|
+
if (dims.length === 3 && dims.every(Number.isFinite)) {
|
|
1148
|
+
entity.extent = Math.hypot(...dims) / 2;
|
|
1149
|
+
}
|
|
1150
|
+
} else if (cyl && Number.isFinite(Number(cyl[1])) && Number.isFinite(Number(cyl[2]))) {
|
|
1151
|
+
entity.extent = Math.hypot(Number(cyl[1]), Number(cyl[2]) / 2);
|
|
1152
|
+
}
|
|
1153
|
+
const world = worldChainOrigin(label);
|
|
1154
|
+
if (world) entity.position = world;
|
|
1155
|
+
const joint = jointByChild.get(label);
|
|
1156
|
+
const jointType = joint?.type ?? "fixed";
|
|
1157
|
+
entity.mobility = jointType === "fixed" ? "fixed" : "actuated";
|
|
1158
|
+
if (world && entity.extent != null) {
|
|
1159
|
+
entity.reachCenter = world;
|
|
1160
|
+
if (jointType === "fixed") {
|
|
1161
|
+
entity.reachRadius = entity.extent;
|
|
1162
|
+
} else if (jointType === "revolute" || jointType === "continuous") {
|
|
1163
|
+
entity.reachRadius = entity.extent;
|
|
1164
|
+
} else if (jointType === "prismatic" && joint?.lower != null && joint?.upper != null) {
|
|
1165
|
+
entity.reachRadius = entity.extent + Math.max(Math.abs(joint.lower), Math.abs(joint.upper));
|
|
1166
|
+
}
|
|
1167
|
+
}
|
|
1168
|
+
if (physicalEntities.some((e) => e.id === entity.id)) {
|
|
1169
|
+
throw new Error(
|
|
1170
|
+
`deriveUrdfPerception: two links fold to canonical id "${entity.id}" \u2014 refusing an ambiguous world`
|
|
1171
|
+
);
|
|
1172
|
+
}
|
|
1173
|
+
physicalEntities.push(entity);
|
|
1174
|
+
}
|
|
1175
|
+
return {
|
|
1176
|
+
perceiver: URDF_PERCEIVER,
|
|
1177
|
+
artifactHash: createHash("sha256").update(artifact).digest("hex"),
|
|
1178
|
+
expresses: ["source-name", "physical-entities", "geometry", "position"],
|
|
1179
|
+
sourceName,
|
|
1180
|
+
entities: [],
|
|
1181
|
+
// URDF has no agent vocabulary — abstains from agent facts
|
|
1182
|
+
physicalEntities,
|
|
1183
|
+
coverageGaps: ["agent-entities", "affordance-count", "affordance-action-names"]
|
|
1184
|
+
};
|
|
1185
|
+
}
|
|
1186
|
+
|
|
1187
|
+
// src/reconstruction/enforceVerifiedViewReceipts.ts
|
|
1188
|
+
var BINDING_TRAITS = ["bind", "chart", "sparkline", "model", "each"];
|
|
1189
|
+
function isDegenerateParse(comp) {
|
|
1190
|
+
const noRealName = !comp.name || comp.name === "implicit";
|
|
1191
|
+
const noObjects = (comp.objects?.length ?? 0) === 0;
|
|
1192
|
+
const noState = (comp.state?.properties?.length ?? 0) === 0;
|
|
1193
|
+
return noRealName && noObjects && noState;
|
|
1194
|
+
}
|
|
1195
|
+
function derivedProjectionNode(config) {
|
|
1196
|
+
const state = config?.state;
|
|
1197
|
+
if (typeof state !== "string" || !state) return null;
|
|
1198
|
+
const path = config?.path;
|
|
1199
|
+
return typeof path === "string" && path ? `${state}.${path}` : state;
|
|
1200
|
+
}
|
|
1201
|
+
function firstBindingTrait(traits) {
|
|
1202
|
+
return traits.find((t) => typeof t.name === "string" && BINDING_TRAITS.includes(t.name));
|
|
1203
|
+
}
|
|
1204
|
+
function collectTargets(objects, out, flags) {
|
|
1205
|
+
for (const o of objects ?? []) {
|
|
1206
|
+
const traits = o.traits ?? [];
|
|
1207
|
+
const bindTrait = firstBindingTrait(traits);
|
|
1208
|
+
if (bindTrait) {
|
|
1209
|
+
const node = derivedProjectionNode(bindTrait.config);
|
|
1210
|
+
if (node) {
|
|
1211
|
+
flags.anyBinding = true;
|
|
1212
|
+
const hasProjects = traits.some((t) => t.name === "projects");
|
|
1213
|
+
if (!hasProjects && typeof o.name === "string" && o.name) {
|
|
1214
|
+
out.push({ object: o.name, node });
|
|
1215
|
+
}
|
|
1216
|
+
}
|
|
1217
|
+
}
|
|
1218
|
+
collectTargets(o.children, out, flags);
|
|
1219
|
+
}
|
|
1220
|
+
}
|
|
1221
|
+
function escapeRegExp(s) {
|
|
1222
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1223
|
+
}
|
|
1224
|
+
function matchBrace(source, bodyStart) {
|
|
1225
|
+
let depth = 1;
|
|
1226
|
+
let inString = false;
|
|
1227
|
+
for (let i = bodyStart; i < source.length; i++) {
|
|
1228
|
+
const ch = source[i];
|
|
1229
|
+
if (inString) {
|
|
1230
|
+
if (ch === "\\") {
|
|
1231
|
+
i++;
|
|
1232
|
+
} else if (ch === '"') {
|
|
1233
|
+
inString = false;
|
|
1234
|
+
}
|
|
1235
|
+
continue;
|
|
1236
|
+
}
|
|
1237
|
+
if (ch === '"') {
|
|
1238
|
+
inString = true;
|
|
1239
|
+
} else if (ch === "{") {
|
|
1240
|
+
depth++;
|
|
1241
|
+
} else if (ch === "}") {
|
|
1242
|
+
depth--;
|
|
1243
|
+
if (depth === 0) return i;
|
|
1244
|
+
}
|
|
1245
|
+
}
|
|
1246
|
+
return -1;
|
|
1247
|
+
}
|
|
1248
|
+
function injectProjectsIntoObject(source, objectName, node) {
|
|
1249
|
+
const re = new RegExp(`object\\s+"${escapeRegExp(objectName)}"\\s*\\{`, "g");
|
|
1250
|
+
let m;
|
|
1251
|
+
while ((m = re.exec(source)) !== null) {
|
|
1252
|
+
const bodyStart = m.index + m[0].length;
|
|
1253
|
+
const close = matchBrace(source, bodyStart);
|
|
1254
|
+
if (close < 0) return source;
|
|
1255
|
+
const body = source.slice(bodyStart, close);
|
|
1256
|
+
if (body.includes("@projects")) {
|
|
1257
|
+
re.lastIndex = close;
|
|
1258
|
+
continue;
|
|
1259
|
+
}
|
|
1260
|
+
const lineStart = source.lastIndexOf("\n", close - 1) + 1;
|
|
1261
|
+
const braceIndent = source.slice(lineStart, close).match(/^[ \t]*/)?.[0] ?? "";
|
|
1262
|
+
const innerIndent = braceIndent + " ";
|
|
1263
|
+
const injection = `${innerIndent}@projects { node: "${node}" }
|
|
1264
|
+
`;
|
|
1265
|
+
return source.slice(0, lineStart) + injection + source.slice(lineStart);
|
|
1266
|
+
}
|
|
1267
|
+
return source;
|
|
1268
|
+
}
|
|
1269
|
+
function injectVerifiedView(source) {
|
|
1270
|
+
const m = /composition(?:\s+"[^"]*")?\s*\{/.exec(source);
|
|
1271
|
+
if (!m) return source;
|
|
1272
|
+
const insertAt = m.index + m[0].length;
|
|
1273
|
+
return `${source.slice(0, insertAt)}
|
|
1274
|
+
@verified_view${source.slice(insertAt)}`;
|
|
1275
|
+
}
|
|
1276
|
+
function enforceVerifiedViewReceipts(source) {
|
|
1277
|
+
let parsed;
|
|
1278
|
+
try {
|
|
1279
|
+
parsed = parseHolo(source);
|
|
1280
|
+
} catch {
|
|
1281
|
+
return source;
|
|
1282
|
+
}
|
|
1283
|
+
if (!parsed || !parsed.success || !parsed.ast) return source;
|
|
1284
|
+
const comp = parsed.ast;
|
|
1285
|
+
const targets = [];
|
|
1286
|
+
const flags = { anyBinding: false };
|
|
1287
|
+
collectTargets(comp.objects, targets, flags);
|
|
1288
|
+
const hasVerifiedView = (comp.traits ?? []).some((t) => t?.name === "verified_view");
|
|
1289
|
+
const needVerifiedView = flags.anyBinding && !hasVerifiedView;
|
|
1290
|
+
if (targets.length === 0 && !needVerifiedView) return source;
|
|
1291
|
+
let out = source;
|
|
1292
|
+
for (const t of targets) out = injectProjectsIntoObject(out, t.object, t.node);
|
|
1293
|
+
if (needVerifiedView) out = injectVerifiedView(out);
|
|
1294
|
+
return out;
|
|
1295
|
+
}
|
|
1296
|
+
var UNPARSEABLE = {
|
|
1297
|
+
parsed: false,
|
|
1298
|
+
hasBindings: false,
|
|
1299
|
+
verifiedViewOn: false,
|
|
1300
|
+
complete: false,
|
|
1301
|
+
violations: []
|
|
1302
|
+
};
|
|
1303
|
+
function diagnoseVerifiedView(source) {
|
|
1304
|
+
let parsed;
|
|
1305
|
+
try {
|
|
1306
|
+
parsed = parseHolo(source);
|
|
1307
|
+
} catch {
|
|
1308
|
+
return UNPARSEABLE;
|
|
1309
|
+
}
|
|
1310
|
+
if (!parsed || !parsed.success || !parsed.ast) return UNPARSEABLE;
|
|
1311
|
+
const comp = parsed.ast;
|
|
1312
|
+
if (isDegenerateParse(comp)) return UNPARSEABLE;
|
|
1313
|
+
const roots = new Set((comp.state?.properties ?? []).map((p) => p.key));
|
|
1314
|
+
const scanRoots = (objs) => {
|
|
1315
|
+
for (const o of objs ?? []) {
|
|
1316
|
+
for (const t of o.traits ?? []) {
|
|
1317
|
+
if (t.name === "fetch") {
|
|
1318
|
+
const into = t.config?.into;
|
|
1319
|
+
roots.add(typeof into === "string" && into ? into : "data");
|
|
1320
|
+
}
|
|
1321
|
+
if (t.name === "each") {
|
|
1322
|
+
const as = t.config?.as;
|
|
1323
|
+
roots.add(typeof as === "string" && as ? as : "item");
|
|
1324
|
+
}
|
|
1325
|
+
if (t.name === "computed") {
|
|
1326
|
+
const nm = t.config?.name;
|
|
1327
|
+
if (typeof nm === "string" && nm) roots.add(nm);
|
|
1328
|
+
}
|
|
1329
|
+
if (t.name === "hook") {
|
|
1330
|
+
const returns = t.config?.returns;
|
|
1331
|
+
if (typeof returns === "string") {
|
|
1332
|
+
for (const r of returns.split(",").map((s) => s.trim())) {
|
|
1333
|
+
if (r) roots.add(r);
|
|
1334
|
+
}
|
|
1335
|
+
}
|
|
1336
|
+
}
|
|
1337
|
+
}
|
|
1338
|
+
scanRoots(o.children);
|
|
1339
|
+
}
|
|
1340
|
+
};
|
|
1341
|
+
scanRoots(comp.objects);
|
|
1342
|
+
const verifiedViewOn = (comp.traits ?? []).some((t) => t?.name === "verified_view");
|
|
1343
|
+
const violations = [];
|
|
1344
|
+
let hasBindings = false;
|
|
1345
|
+
const walk = (objs) => {
|
|
1346
|
+
for (const o of objs ?? []) {
|
|
1347
|
+
const traits = o.traits ?? [];
|
|
1348
|
+
const el = typeof o.name === "string" && o.name ? o.name : "element";
|
|
1349
|
+
const bindTrait = firstBindingTrait(traits);
|
|
1350
|
+
const node = bindTrait ? derivedProjectionNode(bindTrait.config) : null;
|
|
1351
|
+
const projectsTrait = traits.find((t) => t.name === "projects");
|
|
1352
|
+
const claimedRaw = projectsTrait?.config?.node;
|
|
1353
|
+
const claimed = typeof claimedRaw === "string" ? claimedRaw : null;
|
|
1354
|
+
if (node) {
|
|
1355
|
+
hasBindings = true;
|
|
1356
|
+
if (!projectsTrait) {
|
|
1357
|
+
violations.push({
|
|
1358
|
+
element: el,
|
|
1359
|
+
node,
|
|
1360
|
+
reason: "missing-projects",
|
|
1361
|
+
detail: `renders "${node}" but declares no @projects receipt`
|
|
1362
|
+
});
|
|
1363
|
+
} else if (claimed !== node) {
|
|
1364
|
+
violations.push({
|
|
1365
|
+
element: el,
|
|
1366
|
+
node: claimed,
|
|
1367
|
+
reason: "mismatched-node",
|
|
1368
|
+
detail: `claims to project "${claimed ?? ""}" but is bound to "${node}"`
|
|
1369
|
+
});
|
|
1370
|
+
} else if (!roots.has(node.split(".")[0])) {
|
|
1371
|
+
violations.push({
|
|
1372
|
+
element: el,
|
|
1373
|
+
node,
|
|
1374
|
+
reason: "hallucinated-root",
|
|
1375
|
+
detail: `projects "${node}" but no state node "${node.split(".")[0]}" exists`
|
|
1376
|
+
});
|
|
1377
|
+
}
|
|
1378
|
+
} else if (projectsTrait) {
|
|
1379
|
+
violations.push({
|
|
1380
|
+
element: el,
|
|
1381
|
+
node: claimed,
|
|
1382
|
+
reason: "projects-without-binding",
|
|
1383
|
+
detail: `claims to project "${claimed ?? ""}" but has no data binding`
|
|
1384
|
+
});
|
|
1385
|
+
}
|
|
1386
|
+
walk(o.children);
|
|
1387
|
+
}
|
|
1388
|
+
};
|
|
1389
|
+
walk(comp.objects);
|
|
1390
|
+
if (hasBindings && !verifiedViewOn) {
|
|
1391
|
+
violations.push({
|
|
1392
|
+
element: "(composition)",
|
|
1393
|
+
node: null,
|
|
1394
|
+
reason: "no-verified-view",
|
|
1395
|
+
detail: "surface binds data but does not declare composition-level @verified_view"
|
|
1396
|
+
});
|
|
1397
|
+
}
|
|
1398
|
+
return { parsed: true, hasBindings, verifiedViewOn, complete: violations.length === 0, violations };
|
|
1399
|
+
}
|
|
1400
|
+
function isProvenanceComplete(source) {
|
|
1401
|
+
return diagnoseVerifiedView(source).complete;
|
|
1402
|
+
}
|
|
1403
|
+
var SURFACE_TWIN_VERSION = "surface-twin-v1";
|
|
1404
|
+
function resolveAuthoritative(entityState, node) {
|
|
1405
|
+
if (entityState === void 0) return { found: false, value: null };
|
|
1406
|
+
if (entityState === null || typeof entityState !== "object") {
|
|
1407
|
+
return { found: true, value: entityState };
|
|
1408
|
+
}
|
|
1409
|
+
const dot = node.indexOf(".");
|
|
1410
|
+
const field = dot >= 0 ? node.slice(dot + 1) : node;
|
|
1411
|
+
let cur = entityState;
|
|
1412
|
+
for (const seg of field.split(".")) {
|
|
1413
|
+
if (cur !== null && typeof cur === "object" && seg in cur) {
|
|
1414
|
+
cur = cur[seg];
|
|
1415
|
+
} else {
|
|
1416
|
+
return { found: false, value: null };
|
|
1417
|
+
}
|
|
1418
|
+
}
|
|
1419
|
+
return { found: true, value: cur };
|
|
1420
|
+
}
|
|
1421
|
+
function displayEquals(a, b) {
|
|
1422
|
+
return String(a) === String(b);
|
|
1423
|
+
}
|
|
1424
|
+
function applyProjectionTransform(raw, transform) {
|
|
1425
|
+
const base = raw ?? 0;
|
|
1426
|
+
const value = transform.precision !== void 0 ? Number(base).toFixed(transform.precision) : String(base);
|
|
1427
|
+
return `${transform.prefix ?? ""}${value}${transform.suffix ?? ""}`;
|
|
1428
|
+
}
|
|
1429
|
+
function checkSurfaceTwinCorrespondence(input) {
|
|
1430
|
+
const { contract, displayedValues, authoritativeState } = input;
|
|
1431
|
+
const unavailable = new Set(input.unavailableEntities ?? []);
|
|
1432
|
+
const divergences = [];
|
|
1433
|
+
const abstentions = [];
|
|
1434
|
+
let checked = 0;
|
|
1435
|
+
for (const p of contract.projections ?? []) {
|
|
1436
|
+
if (!p.entity) {
|
|
1437
|
+
abstentions.push({ node: p.node, reason: "no-entity-binding" });
|
|
1438
|
+
continue;
|
|
1439
|
+
}
|
|
1440
|
+
const transform = p.transform;
|
|
1441
|
+
if (!p.identity && !transform) {
|
|
1442
|
+
abstentions.push({ node: p.node, entity: p.entity, reason: "non-identity-transform" });
|
|
1443
|
+
continue;
|
|
1444
|
+
}
|
|
1445
|
+
if (unavailable.has(p.entity)) {
|
|
1446
|
+
abstentions.push({ node: p.node, entity: p.entity, reason: "authority-unavailable" });
|
|
1447
|
+
continue;
|
|
1448
|
+
}
|
|
1449
|
+
if (!(p.node in displayedValues)) {
|
|
1450
|
+
abstentions.push({ node: p.node, entity: p.entity, reason: "display-missing" });
|
|
1451
|
+
continue;
|
|
1452
|
+
}
|
|
1453
|
+
const auth = resolveAuthoritative(authoritativeState[p.entity], p.node);
|
|
1454
|
+
if (!auth.found) {
|
|
1455
|
+
abstentions.push({ node: p.node, entity: p.entity, reason: "authority-missing" });
|
|
1456
|
+
continue;
|
|
1457
|
+
}
|
|
1458
|
+
checked++;
|
|
1459
|
+
const displayed = displayedValues[p.node];
|
|
1460
|
+
const expected = transform ? applyProjectionTransform(auth.value, transform) : auth.value;
|
|
1461
|
+
if (!displayEquals(displayed, expected)) {
|
|
1462
|
+
divergences.push({
|
|
1463
|
+
node: p.node,
|
|
1464
|
+
entity: p.entity,
|
|
1465
|
+
displayed,
|
|
1466
|
+
authoritative: auth.value,
|
|
1467
|
+
...transform ? { expected } : {},
|
|
1468
|
+
detail: transform ? `surface shows ${JSON.stringify(displayed)} but twin "${p.entity}" holds ${JSON.stringify(auth.value)} which formats to ${JSON.stringify(expected)}` : `surface shows ${JSON.stringify(displayed)} but twin "${p.entity}" holds ${JSON.stringify(auth.value)}`
|
|
1469
|
+
});
|
|
1470
|
+
}
|
|
1471
|
+
}
|
|
1472
|
+
const verdict = divergences.length > 0 ? "FALSIFIED" : "CONSENSUS";
|
|
1473
|
+
const canonical = JSON.stringify({
|
|
1474
|
+
version: SURFACE_TWIN_VERSION,
|
|
1475
|
+
verdict,
|
|
1476
|
+
checked,
|
|
1477
|
+
divergences: [...divergences].sort((a, b) => a.node.localeCompare(b.node)),
|
|
1478
|
+
abstentions: [...abstentions].sort(
|
|
1479
|
+
(a, b) => (a.node + a.reason).localeCompare(b.node + b.reason)
|
|
1480
|
+
)
|
|
1481
|
+
});
|
|
1482
|
+
const receiptHash = createHash("sha256").update(canonical).digest("hex");
|
|
1483
|
+
return { version: SURFACE_TWIN_VERSION, verdict, checked, divergences, abstentions, receiptHash };
|
|
1484
|
+
}
|
|
1485
|
+
async function verifySurfaceTwinLive(input) {
|
|
1486
|
+
const { contract, displayedValues, fetchAuthoritativeState } = input;
|
|
1487
|
+
const entities = [
|
|
1488
|
+
...new Set(
|
|
1489
|
+
(contract.projections ?? []).map((p) => p.entity).filter((e) => typeof e === "string" && e.length > 0)
|
|
1490
|
+
)
|
|
1491
|
+
];
|
|
1492
|
+
const authoritativeState = {};
|
|
1493
|
+
const unavailableEntities = [];
|
|
1494
|
+
const results = await Promise.allSettled(entities.map((e) => fetchAuthoritativeState(e)));
|
|
1495
|
+
results.forEach((r, i) => {
|
|
1496
|
+
const entity = entities[i];
|
|
1497
|
+
if (r.status === "fulfilled" && r.value !== null && r.value !== void 0) {
|
|
1498
|
+
authoritativeState[entity] = r.value;
|
|
1499
|
+
} else {
|
|
1500
|
+
unavailableEntities.push(entity);
|
|
1501
|
+
}
|
|
1502
|
+
});
|
|
1503
|
+
return checkSurfaceTwinCorrespondence({
|
|
1504
|
+
contract,
|
|
1505
|
+
displayedValues,
|
|
1506
|
+
authoritativeState,
|
|
1507
|
+
unavailableEntities
|
|
1508
|
+
});
|
|
1509
|
+
}
|
|
1510
|
+
|
|
1511
|
+
// src/reconstruction/extractDisplayedProjections.ts
|
|
1512
|
+
var HTML_ENTITIES = {
|
|
1513
|
+
"&": "&",
|
|
1514
|
+
"<": "<",
|
|
1515
|
+
">": ">",
|
|
1516
|
+
""": '"',
|
|
1517
|
+
"'": "'",
|
|
1518
|
+
"'": "'"
|
|
1519
|
+
};
|
|
1520
|
+
function decodeEntities(s) {
|
|
1521
|
+
return s.replace(/&(?:amp|lt|gt|quot|#39|#x27);/g, (m) => HTML_ENTITIES[m] ?? m);
|
|
1522
|
+
}
|
|
1523
|
+
function textContent(inner) {
|
|
1524
|
+
return decodeEntities(inner.replace(/<[^>]*>/g, "")).replace(/\s+/g, " ").trim();
|
|
1525
|
+
}
|
|
1526
|
+
function extractDisplayedProjections(html) {
|
|
1527
|
+
const out = {};
|
|
1528
|
+
const attrRe = /data-holo-projects="([^"]+)"/g;
|
|
1529
|
+
let m;
|
|
1530
|
+
while ((m = attrRe.exec(html)) !== null) {
|
|
1531
|
+
const node = m[1];
|
|
1532
|
+
if (node in out) continue;
|
|
1533
|
+
const lt = html.lastIndexOf("<", m.index);
|
|
1534
|
+
if (lt < 0) continue;
|
|
1535
|
+
const tagMatch = /^<([a-zA-Z][a-zA-Z0-9-]*)/.exec(html.slice(lt, m.index + 1));
|
|
1536
|
+
if (!tagMatch) continue;
|
|
1537
|
+
const tag = tagMatch[1];
|
|
1538
|
+
const openEnd = html.indexOf(">", m.index);
|
|
1539
|
+
if (openEnd < 0) continue;
|
|
1540
|
+
if (html[openEnd - 1] === "/") {
|
|
1541
|
+
out[node] = "";
|
|
1542
|
+
continue;
|
|
1543
|
+
}
|
|
1544
|
+
const contentStart = openEnd + 1;
|
|
1545
|
+
const openRe = new RegExp(`<${tag}(?=[\\s/>])`, "g");
|
|
1546
|
+
const close = `</${tag}>`;
|
|
1547
|
+
let depth = 1;
|
|
1548
|
+
let i = contentStart;
|
|
1549
|
+
while (i < html.length && depth > 0) {
|
|
1550
|
+
const nextClose = html.indexOf(close, i);
|
|
1551
|
+
if (nextClose < 0) break;
|
|
1552
|
+
openRe.lastIndex = i;
|
|
1553
|
+
const openM = openRe.exec(html);
|
|
1554
|
+
if (openM && openM.index < nextClose) {
|
|
1555
|
+
depth++;
|
|
1556
|
+
i = openM.index + tag.length + 1;
|
|
1557
|
+
} else {
|
|
1558
|
+
depth--;
|
|
1559
|
+
if (depth === 0) {
|
|
1560
|
+
out[node] = textContent(html.slice(contentStart, nextClose));
|
|
1561
|
+
}
|
|
1562
|
+
i = nextClose + close.length;
|
|
1563
|
+
}
|
|
1564
|
+
}
|
|
1565
|
+
}
|
|
1566
|
+
return out;
|
|
1567
|
+
}
|
|
1568
|
+
|
|
1569
|
+
export { AGENT_INFERENCE_PERCEIVER, ANCHOR_DEFAULTS, ARCORE_DEPTH_RECOMMENDED_RANGE_MILLIMETERS, HOLOMAP_MOBILE_SENSOR_BUNDLE_VERSION, PERCEIVER_CONSENSUS_VERSION, PERCEIVER_FACT_CLASSES, POSITION_EPSILON, SURFACE_TWIN_VERSION, TRAJECTORY_DEFAULTS, URDF_PERCEIVER, WEBGPU_PERCEIVER, applyProjectionTransform, arCoreDepthFrameToMobileSensorFrame, cameraPoseFromColumnMajorTransform, canonicalPhysicalId, checkSurfaceTwinCorrespondence, createAnchorContext, createArCoreDepthMobileSensorBundle, createFusedAttentionBackend, createTrajectoryMemory, deriveAgentInferencePerception, derivePerceiverConsensus, deriveUrdfPerception, deriveWebGPUPerception, derivedProjectionNode, diagnoseVerifiedView, enforceVerifiedViewReceipts, extractDisplayedProjections, isProvenanceComplete, mobileSensorBundleToFrames, replayMobileSensorBundle, validateMobileSensorBundle, verifySurfaceTwinLive };
|
|
1570
|
+
//# sourceMappingURL=chunk-7ZKQ32XK.js.map
|
|
1571
|
+
//# sourceMappingURL=chunk-7ZKQ32XK.js.map
|