motion / src /movement-network.js
harshthedev's picture
SuperHQ Motion experiment: locomotion and action checkpoints, runtime, clips, evaluation
6adbbb1 verified
Raw History Blame Contribute Delete
3.38 kB
import { DenseNetwork } from './network.js';
/** Gated action residuals. Ordinary movement uses the frozen base. */
export class MovementNetwork {
constructor(manifest, base, adapter, jumpAdapter=null) {
if (!['superhq-motion-gated-v1','superhq-motion-branched-v1'].includes(manifest.format) || manifest.input_width !== 48 ||
manifest.output_width !== 143 || manifest.gate_indices?.join(',') !== '34,35,36,37' ||
base.layers[0].inputs !== 34 || adapter.layers[0].inputs !== 48 ||
base.layers.at(-1).outputs !== 143 || adapter.layers.at(-1).outputs !== 143) {
throw new Error('Invalid movement action network contract.');
}
this.base = base;
this.adapter = adapter;
this.branched=manifest.format==='superhq-motion-branched-v1';this.jumpAdapter=jumpAdapter;
if(this.branched&&(!jumpAdapter||jumpAdapter.layers[0].inputs!==48||jumpAdapter.layers.at(-1).outputs!==143))throw Error('Missing jump residual.');
this.output = new Float32Array(143);
}
forward(input) {
if (!(input instanceof Float32Array) || input.length !== 48 || !input.every(Number.isFinite)) {
throw new Error('Expected 48 finite action features.');
}
const gate = Math.max(0, Math.min(1, input[34] + input[35] + input[36] + input[37]));
if(this.branched&&(Array.from(input.subarray(34,38)).some(v=>v<0||v>1)||(input[34]>0&&input.subarray(35,38).some(v=>v>0))))throw Error('Stance and jump gates must be exclusive.');
this.output.set(this.base.forward(input.subarray(0, 34)));
if (gate > 0) {
const delta = (this.branched&&input[34]===0?this.jumpAdapter:this.adapter).forward(input);
for (let i = 0; i < this.output.length; i++) this.output[i] += gate * delta[i];
}
if (!this.output.every(Number.isFinite)) throw new Error('Nonfinite movement prediction.');
return this.output;
}
static async load(baseUrl,{fetch:fetcher=globalThis.fetch}={}) {
const root = new URL(baseUrl, globalThis.location?.href);
const request = async url => {
const response = await fetcher(url);
if (!response.ok) throw new Error(`Movement asset failed (${response.status}).`);
return response;
};
const manifest = await (await request(new URL('movement.json', root))).json();
if (manifest.base !== 'base/' || manifest.adapter !== 'adapter/') throw new Error('Invalid action asset paths.');
if(manifest.format==='superhq-motion-branched-v1'&&manifest.jump_adapter!=='jump/')throw Error('Invalid jump asset path.');
const load = async name => {
const folder = new URL(name, root);
const data = await (await request(new URL('network.json', folder))).json();
if (data.weights !== 'weights.bin') throw new Error('Invalid weight path.');
const buffer = await (await request(new URL(data.weights, folder))).arrayBuffer();
const digest = await crypto.subtle.digest('SHA-256', buffer);
const hash = Array.from(new Uint8Array(digest), b => b.toString(16).padStart(2, '0')).join('');
if (hash !== data.sha256) throw new Error('Movement weight checksum differs.');
return new DenseNetwork(data, buffer);
};
const [base, adapter,jump] = await Promise.all([load(manifest.base), load(manifest.adapter),manifest.jump_adapter?load(manifest.jump_adapter):null]);
return new MovementNetwork(manifest, base, adapter,jump);
}
}