@rig-c/review 0.7.1 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +21 -4
- package/docs/DESIGN.md +2 -1
- package/docs/REVIEW_PACKAGE.md +16 -6
- package/package.json +1 -1
- package/src/applied-context.ts +35 -7
- package/src/cli.ts +1 -1
- package/src/cloth-recipe.ts +199 -0
- package/src/environment-check.ts +149 -0
- package/src/environment-schedule.ts +183 -0
- package/src/feedback-export.ts +8 -1
- package/src/front.ts +4 -1
- package/src/handoff.ts +20 -13
- package/src/open.ts +1 -1
- package/src/readings-run.ts +2 -1
- package/src/readings.ts +12 -6
- package/src/rebuild.ts +185 -32
- package/src/retune-check.ts +100 -0
- package/src/server.ts +11 -1
- package/src/session.ts +3 -0
- package/web/cloth-panel.ts +168 -0
- package/web/environment-world.ts +42 -0
- package/web/index.html +1 -1
- package/web/main.ts +112 -9
- package/web/readings-panel.ts +1 -1
- package/web/rebuild-client.ts +13 -8
- package/web/rig-overlays.ts +2 -1
- package/web/styles.css +2 -0
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Setup · Cloth (#111), page side: one form per cloth tag the installed reader accepts, holding the recipe's fields as
|
|
3
|
+
* the producer config states them, and a link preview on the stage — each chain the tag's parts are meshed on, drawn
|
|
4
|
+
* from its config `points` and `tip` in rig px of the setup pose (the crop → world mapping the cuts, chest and bones
|
|
5
|
+
* views use). Every change is a config patch op at `/cloth/<tag>/…` (`src/cloth-recipe.ts`); Apply sends them as one
|
|
6
|
+
* `rig` patch, which the server runs as `rig-parts propose --cloth … --write-config` and then builds the written config
|
|
7
|
+
* (`build` when the config declares limbs, else `rig --config`); the scratch build reloads in place under the stage's
|
|
8
|
+
* Environment. After an Apply the preview adds the chains of the config the producer wrote. The producer's refusals
|
|
9
|
+
* (`CONFIG_CLOTH_*`, `PROPOSE_CLOTH_DRAFTED`, `PROPOSE_CLOTH_PART`, `PROPOSE_WRITE_CONFIG_*`) come back verbatim.
|
|
10
|
+
* Hand-editing the drafted constraints is the physics block's, after the draft. The draft keeps the pending edits
|
|
11
|
+
* (`#cloth-pending`).
|
|
12
|
+
*/
|
|
13
|
+
import {ClothFieldError,fieldText,recipeFromFields,recipeOps,RECIPE_FIELDS,type ClothTagView,type RecipeField} from '../src/cloth-recipe.ts';
|
|
14
|
+
import type {PatchOp} from '../src/config-patch.ts';
|
|
15
|
+
import type {CropWorldMapping} from '../src/chest-regions.ts';
|
|
16
|
+
import type {OverlayPoint,OverlayProject,OverlayRegion} from './rig-overlays.ts';
|
|
17
|
+
import type {ClothStatusView,PatchJobView,RebuildRefusal} from './rebuild-client.ts';
|
|
18
|
+
|
|
19
|
+
export interface ClothPanelDeps {
|
|
20
|
+
project:()=>OverlayProject|null;mapping:()=>CropWorldMapping;inspector:()=>string|undefined;
|
|
21
|
+
pause:()=>void;changed:()=>void;saved:()=>void;
|
|
22
|
+
apply:(ops:PatchOp[])=>Promise<PatchJobView|RebuildRefusal|null>;
|
|
23
|
+
reset:()=>Promise<void>;
|
|
24
|
+
token:(name:string)=>string;
|
|
25
|
+
}
|
|
26
|
+
export interface ClothPanel {
|
|
27
|
+
load(status:ClothStatusView|null,reason:string|null):void;
|
|
28
|
+
tag():string;select(tag:string):void;
|
|
29
|
+
values(tag?:string):Record<string,string>;set(key:string,text:string,tag?:string):void;declare(on:boolean,tag?:string):void;
|
|
30
|
+
ops():PatchOp[];
|
|
31
|
+
active():boolean;setActive(on:boolean):void;
|
|
32
|
+
overlayRegions():OverlayRegion[];
|
|
33
|
+
links():{tag:string;inspected:ClothTagView|null;drafted:ClothTagView|null};
|
|
34
|
+
apply():Promise<PatchJobView|RebuildRefusal|null>;
|
|
35
|
+
shown(job:PatchJobView|RebuildRefusal|null):void;
|
|
36
|
+
status(text:string,error:boolean):void;busy(value:boolean):void;
|
|
37
|
+
restore():void;clear():void;sync():void;dispose():void;
|
|
38
|
+
}
|
|
39
|
+
const node=<T extends HTMLElement>(id:string)=>{const found=document.getElementById(id);if(!found)throw new Error(`Missing UI element: ${id}`);return found as T;};
|
|
40
|
+
const make=<K extends keyof HTMLElementTagNameMap>(tag:K,props:Record<string,unknown>={},...children:(Node|string)[]):HTMLElementTagNameMap[K]=>{const item=document.createElement(tag);Object.assign(item,props);item.append(...children);return item;};
|
|
41
|
+
const isObject=(v:unknown):v is Record<string,unknown>=>!!v&&typeof v==='object'&&!Array.isArray(v);
|
|
42
|
+
|
|
43
|
+
export function bindClothPanel(deps:ClothPanelDeps):ClothPanel {
|
|
44
|
+
const state=node('cloth-panel-state'),toggle=node<HTMLButtonElement>('cloth-links'),picker=node<HTMLSelectElement>('cloth-tag'),declared=node<HTMLInputElement>('cloth-declared'),fields=node('cloth-fields'),pending=node('cloth-pending-list');
|
|
45
|
+
const applyButton=node<HTMLButtonElement>('cloth-apply'),resetButton=node<HTMLButtonElement>('cloth-reset'),statusOut=node('cloth-status'),readout=node('cloth-readout'),draft=node<HTMLTextAreaElement>('cloth-pending');
|
|
46
|
+
let status:ClothStatusView|null=null,reason:string|null=null,selected='',active=false,busy=false;
|
|
47
|
+
/** Per touched tag: every form field's text, and whether the tag's recipe is declared. */
|
|
48
|
+
let edits=new Map<string,{declared:boolean;values:Record<string,string>}>();
|
|
49
|
+
let drafted:ClothTagView[]=[];
|
|
50
|
+
|
|
51
|
+
const panelStatus=(text:string,error:boolean)=>{statusOut.textContent=text;statusOut.title=text;statusOut.classList.toggle('error',error);};
|
|
52
|
+
const recipe=(tag:string)=>isObject(status?.recipes)?status!.recipes![tag]:undefined;
|
|
53
|
+
const initial=(tag:string)=>({declared:recipe(tag)!==undefined,values:Object.fromEntries(RECIPE_FIELDS.map(f=>[f.key,fieldText(recipe(tag),f)]))});
|
|
54
|
+
const current=(tag:string)=>edits.get(tag)??initial(tag);
|
|
55
|
+
const touched=(tag:string)=>{const e=edits.get(tag),i=initial(tag);return !!e&&(e.declared!==i.declared||RECIPE_FIELDS.some(f=>(e.values[f.key]??'')!==(i.values[f.key]??'')));};
|
|
56
|
+
const save=()=>{draft.value=JSON.stringify({cloth:Object.fromEntries([...edits].filter(([tag])=>touched(tag)))});draft.dispatchEvent(new Event('input',{bubbles:true}));deps.saved();};
|
|
57
|
+
/** Every pending op, tag by tag in the reader's order; a field that does not read is named and stops the list. */
|
|
58
|
+
function allOps():PatchOp[] {
|
|
59
|
+
const config={cloth:status?.recipes??undefined},out:PatchOp[]=[];
|
|
60
|
+
for(const tag of status?.targets??[]){if(!touched(tag))continue;const e=current(tag);out.push(...recipeOps(config,tag,e.declared?recipeFromFields(e.values):null));}
|
|
61
|
+
return out;
|
|
62
|
+
}
|
|
63
|
+
const change=()=>{save();render();deps.changed();};
|
|
64
|
+
|
|
65
|
+
function renderFields(){
|
|
66
|
+
const tag=selected,e=current(tag),pendingKeys=new Set(edits.has(tag)?RECIPE_FIELDS.filter(f=>(e.values[f.key]??'')!==(initial(tag).values[f.key]??'')).map(f=>f.key):[]);
|
|
67
|
+
declared.checked=e.declared;declared.disabled=busy||!tag;
|
|
68
|
+
const input=(f:RecipeField)=>{
|
|
69
|
+
const value=e.values[f.key]??'';let control:HTMLInputElement|HTMLSelectElement;
|
|
70
|
+
if(f.kind==='bool'||f.kind==='shape'){const options=f.kind==='bool'?['','true','false']:['','linear','smooth'];control=make('select',{},...options.map(o=>make('option',{value:o,textContent:o||'(absent)'})));control.value=value;}
|
|
71
|
+
else control=make('input',{type:'text',value,autocomplete:'off',inputMode:f.kind==='names'?'text':'decimal',placeholder:f.kind==='pair'?'root, tip':f.kind==='list'||f.kind==='names'?'a, b, …':'(absent)'});
|
|
72
|
+
control.disabled=busy||!e.declared;control.dataset.clothField=f.key;control.setAttribute('aria-label',`${tag} ${f.label}`);
|
|
73
|
+
control.onchange=()=>{panel.set(f.key,control.value);};
|
|
74
|
+
return make('label',{className:pendingKeys.has(f.key)?'cloth-field pending':'cloth-field'},make('span',{textContent:f.label+(f.required?' *':'')}),control,make('code',{textContent:`/cloth/${tag}/${f.path.join('/')}`}));
|
|
75
|
+
};
|
|
76
|
+
fields.replaceChildren(...(tag?RECIPE_FIELDS.map(input):[make('p',{className:'hint',textContent:'No tag to edit.'})]));
|
|
77
|
+
}
|
|
78
|
+
function renderPending(){
|
|
79
|
+
let ops:PatchOp[]=[],error:string|null=null;
|
|
80
|
+
try{ops=allOps();}catch(err){if(err instanceof ClothFieldError)error=err.message;else throw err;}
|
|
81
|
+
const items=ops.map(o=>make('li',{},make('code',{textContent:`${o.op} ${o.path}`}),o.op==='remove'?'':` → ${JSON.stringify(o.value)}`));
|
|
82
|
+
const revert=[...edits.keys()].filter(touched).map(tag=>{const b=make('button',{type:'button',textContent:`Revert ${tag}`,disabled:busy});b.onclick=()=>{edits.delete(tag);change();};return make('li',{},b);});
|
|
83
|
+
pending.replaceChildren(...(error?[make('li',{className:'error',textContent:error})]:[]),...(items.length?items:[make('li',{className:'hint',textContent:'No pending ops.'})]),...revert);
|
|
84
|
+
applyButton.disabled=busy||!items.length||!!error||!!reason||!!status?.reason;resetButton.disabled=busy;
|
|
85
|
+
toggle.disabled=!status;toggle.setAttribute('aria-pressed',String(active));
|
|
86
|
+
}
|
|
87
|
+
function renderPicker(){
|
|
88
|
+
const tags=status?.targets??[];
|
|
89
|
+
picker.replaceChildren(...tags.map(t=>make('option',{value:t,textContent:`${t}${recipe(t)===undefined?' (no recipe)':''}${touched(t)?' · pending':''}`})));
|
|
90
|
+
if(!tags.includes(selected))selected=tags[0]??'';picker.value=selected;picker.disabled=busy||!tags.length;
|
|
91
|
+
}
|
|
92
|
+
function renderState(){
|
|
93
|
+
if(!status){state.textContent=reason??'CLOTH_UNAVAILABLE: no producer handoff';state.classList.add('error');return;}
|
|
94
|
+
const view=status.tags.find(t=>t.tag===selected),mapping=deps.mapping();
|
|
95
|
+
state.classList.toggle('error',!!status.reason||!mapping.available);
|
|
96
|
+
state.textContent=`${status.targets.length} tag(s) the installed reader accepts${status.targetsRead?'':' (its list was not readable: the tags the config declares)'} · ${selected||'—'}: ${view?`${view.parts.length} part(s) (${view.parts.join(', ')||'none'}), ${view.chains.length} chain(s) in the config`:'—'} · built by ${status.route==='build'?'rig-parts build (the config declares limbs)':'rig --config'}${status.keypoints?` · keypoints ${status.keypoints.path}`:''}${status.reason?` · Apply: ${status.reason}`:''}${mapping.available?'':` · ${mapping.reason}: no links are drawn`}`;
|
|
97
|
+
}
|
|
98
|
+
function render(){renderPicker();renderFields();renderPending();renderState();}
|
|
99
|
+
|
|
100
|
+
const setActive=(on:boolean)=>{active=on&&!!status;if(active)deps.pause();render();deps.changed();};
|
|
101
|
+
toggle.onclick=()=>setActive(!active);
|
|
102
|
+
picker.onchange=()=>{selected=picker.value;render();deps.changed();};
|
|
103
|
+
declared.onchange=()=>panel.declare(declared.checked);
|
|
104
|
+
applyButton.onclick=()=>{void panel.apply();};
|
|
105
|
+
resetButton.onclick=()=>{if(!busy)void deps.reset();};
|
|
106
|
+
const observer=new MutationObserver(()=>panel.sync());
|
|
107
|
+
for(const selector of document.querySelectorAll('[data-inspector]'))observer.observe(selector,{attributes:true,attributeFilter:['aria-pressed','aria-selected']});
|
|
108
|
+
const colors=()=>({stroke:deps.token('--rv-mesh-region'),fill:'transparent',handle:deps.token('--rv-mesh-handle'),ink:deps.token('--rv-bone-ink'),labelBg:deps.token('--rv-bone-label-bg'),pending:deps.token('--rv-bone-pending')});
|
|
109
|
+
const world=(p:[number,number])=>{const m=deps.mapping();return m.available?m.toWorld(p[0],p[1]):null;};
|
|
110
|
+
const restore=()=>{
|
|
111
|
+
let doc:unknown=null;try{doc=JSON.parse(draft.value||'null');}catch{doc=null;}
|
|
112
|
+
edits=new Map();
|
|
113
|
+
const kept=isObject(doc)&&isObject(doc.cloth)?doc.cloth:{};
|
|
114
|
+
for(const [tag,v] of Object.entries(kept))if((status?.targets??[]).includes(tag)&&isObject(v)&&typeof v.declared==='boolean'&&isObject(v.values))edits.set(tag,{declared:v.declared,values:Object.fromEntries(RECIPE_FIELDS.map(f=>[f.key,typeof (v.values as Record<string,unknown>)[f.key]==='string'?(v.values as Record<string,string>)[f.key]!:'']))});
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
const panel:ClothPanel={
|
|
118
|
+
load(next,why){status=next;reason=next?null:why;drafted=[];if(!next)active=false;restore();render();deps.changed();},
|
|
119
|
+
tag:()=>selected,
|
|
120
|
+
select(tag){selected=tag;render();deps.changed();},
|
|
121
|
+
values:(tag=selected)=>({...current(tag).values}),
|
|
122
|
+
set(key,text,tag=selected){const f=RECIPE_FIELDS.find(x=>x.key===key);if(!f)throw new Error(`CLOTH_FIELD_UNKNOWN: ${key}`);const e=current(tag);edits.set(tag,{declared:e.declared,values:{...e.values,[key]:text}});change();},
|
|
123
|
+
declare(on,tag=selected){const e=current(tag);edits.set(tag,{declared:on,values:{...e.values}});change();},
|
|
124
|
+
ops:allOps,
|
|
125
|
+
active:()=>active,setActive,
|
|
126
|
+
links(){return {tag:selected,inspected:status?.tags.find(t=>t.tag===selected)??null,drafted:drafted.find(t=>t.tag===selected)??null};},
|
|
127
|
+
overlayRegions(){
|
|
128
|
+
if(!active||!status)return [];const c=colors(),out:OverlayRegion[]=[];
|
|
129
|
+
// One label per set (the first chain carries it); every link joint is a handle square.
|
|
130
|
+
const draw=(view:ClothTagView|undefined,stroke:string,label:string)=>{let first=true;for(const chain of view?.chains??[]){const w=chain.points.map(world);if(w.some(p=>!p))continue;out.push({name:first?label:'',points:w as OverlayPoint[],stroke,fill:c.fill,handles:true,open:true,active:null,colors:{handle:stroke,ink:c.ink,labelBg:c.labelBg}});first=false;}};
|
|
131
|
+
const inspected=status.tags.find(t=>t.tag===selected),written=drafted.find(t=>t.tag===selected);
|
|
132
|
+
// The written chains are drawn over the config's; when they are the same links, one label says so.
|
|
133
|
+
const same=!!written&&JSON.stringify(written.chains)===JSON.stringify(inspected?.chains??[]);
|
|
134
|
+
draw(inspected,c.stroke,`${selected} · ${inspected?.chains.length??0} chain(s) · config${same?' = written':''}`);
|
|
135
|
+
if(!same)draw(written,c.pending,`${selected} · ${written?.chains.length??0} chain(s) · written`);
|
|
136
|
+
return out;
|
|
137
|
+
},
|
|
138
|
+
async apply(){
|
|
139
|
+
let ops:PatchOp[];try{ops=allOps();}catch(err){if(err instanceof ClothFieldError){panelStatus(err.message,true);return null;}throw err;}
|
|
140
|
+
if(!ops.length||busy){panelStatus('CLOTH_NOTHING_PENDING: no pending ops',true);return null;}
|
|
141
|
+
const result=await deps.apply(ops);panel.shown(result);return result;
|
|
142
|
+
},
|
|
143
|
+
shown(job){
|
|
144
|
+
if(!job){readout.replaceChildren();drafted=[];deps.changed();return;}
|
|
145
|
+
const lines:HTMLElement[]=[],line=(label:string,text:string,error=false)=>lines.push(make('div',{className:'physics-readout-line'},make('span',{className:'eyebrow',textContent:label}),make('code',{textContent:text,title:text,className:error?'error':''})));
|
|
146
|
+
if(!('id' in job)){line('REFUSED',job.error,true);readout.replaceChildren(...lines);return;}
|
|
147
|
+
const c=job.cloth;
|
|
148
|
+
for(const r of c?.refusals??[])line('REFUSED',r,true);
|
|
149
|
+
if(c?.line)line('WROTE',c.line);
|
|
150
|
+
if(c?.written){line('WRITTEN DIFF',`${c.written.count} path(s) against cloth.json${c.written.sections.length?` · ${c.written.sections.join(', ')}`:''}${c.written.outside.length?` · outside the drafted sections: ${c.written.outside.slice(0,8).join(', ')}`:' · every one under the drafted sections'}`,c.written.outside.length>0);for(const p of c.written.paths.slice(0,12))line('PATH',p);}
|
|
151
|
+
if(c)line('ROUTE',c.route==='build'?'rig-parts build --config config.json (the config declares limbs)':'rig-parts rig --config config.json');
|
|
152
|
+
for(const r of job.assembled?.refusals??[])line('REFUSED',r,true);
|
|
153
|
+
for(const r of job.assembled?.checkFails??[])line('CHECK FAIL',r,true);
|
|
154
|
+
if(job.status!=='built'&&job.producerLine&&!(c?.refusals??[]).length)line('REFUSED',job.producerLine,true);
|
|
155
|
+
if(job.status==='built'&&job.check?.line)line('CHECK',job.check.line,job.check.pass!==true);
|
|
156
|
+
drafted=job.status==='built'&&c?c.links:[];
|
|
157
|
+
readout.replaceChildren(...lines);deps.changed();
|
|
158
|
+
},
|
|
159
|
+
status:panelStatus,
|
|
160
|
+
busy(value){busy=value;render();},
|
|
161
|
+
restore(){restore();render();deps.changed();},
|
|
162
|
+
clear(){edits=new Map();change();},
|
|
163
|
+
sync(){renderState();if(active&&deps.inspector()!=='rig')setActive(false);},
|
|
164
|
+
dispose(){observer.disconnect();},
|
|
165
|
+
};
|
|
166
|
+
render();
|
|
167
|
+
return panel;
|
|
168
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Environment block's "Use for readings" (issue #112), the DOM-free half: the controls as the exported
|
|
3
|
+
* `rigc-environment/1` schedule for a clip, its bytes and their hash, the feedback context that carries them, and the
|
|
4
|
+
* "Check under this world" rows (each readout bar under the default world beside the same bar under this one).
|
|
5
|
+
*/
|
|
6
|
+
import { scheduleSummary, scheduleText, sha256Hex, SCHEDULE_CONTEXT_SCHEMA, windSchedule, ScheduleError, type EnvironmentSchedule, type ScheduleContext, type WindScheduleExport } from '../src/environment-schedule.ts';
|
|
7
|
+
import type { WindSnapshot } from '../src/environment.ts';
|
|
8
|
+
import type { CheckSummary } from '../src/applied-context.ts';
|
|
9
|
+
import { READOUT_BARS } from '../src/physics-tuning.ts';
|
|
10
|
+
|
|
11
|
+
export const ENVIRONMENT_NOT_APPLIED = 'ENVIRONMENT_NOT_APPLIED: the player stands at the runtime start values (wind [1, 0], gravity [0, 1]), which check and render read without --environment; move a control first';
|
|
12
|
+
export interface WorldExport { export: WindScheduleExport; schedule: EnvironmentSchedule; text: string; sha256: string; summary: string; animation: string }
|
|
13
|
+
/** The controls as a schedule over `animation` (`duration` s), or why there is none. */
|
|
14
|
+
export function worldExport(snapshot: WindSnapshot, gravity: number, applied: boolean, animation: string, duration: number): WorldExport | { error: string } {
|
|
15
|
+
if (!applied) return { error: ENVIRONMENT_NOT_APPLIED };
|
|
16
|
+
try {
|
|
17
|
+
const e = windSchedule(snapshot, gravity, duration), text = scheduleText(e.schedule), sha256 = sha256Hex(text);
|
|
18
|
+
return { export: e, schedule: e.schedule, text, sha256, summary: scheduleSummary(e, sha256), animation };
|
|
19
|
+
} catch (error) { if (error instanceof ScheduleError) return { error: error.message }; throw error; }
|
|
20
|
+
}
|
|
21
|
+
export const scheduleContext = (w: WorldExport, used: boolean): ScheduleContext => ({ schema: SCHEDULE_CONTEXT_SCHEMA, sha256: w.sha256, size: new TextEncoder().encode(w.text).length, text: w.text, usedForReadings: used });
|
|
22
|
+
|
|
23
|
+
/** `POST /api/environment-check`'s job as the page reads it. */
|
|
24
|
+
export interface EnvironmentCheckView {
|
|
25
|
+
id: string; status: 'running' | 'checked' | 'refused' | 'failed'; build: 'inspected' | 'scratch'; buildId: string | null;
|
|
26
|
+
schedule: { sha256: string; size: number; steps: number }; producer: { package: string; version: string };
|
|
27
|
+
exitCode: number | null; wallMs: number | null; check: CheckSummary | null;
|
|
28
|
+
environment: { path: string; sha256: string; steps: number } | null; environmentLine: string | null;
|
|
29
|
+
bars: { name: string; line: string }[]; error: string | null; lines: string[];
|
|
30
|
+
}
|
|
31
|
+
export interface EnvironmentRow { kind: 'label' | 'bar' | 'error'; text: string }
|
|
32
|
+
/** The panel's lines: which schedule check read, then per bar the default world's line and this world's, or the refusal verbatim. */
|
|
33
|
+
export function environmentCheckRows(job: EnvironmentCheckView, defaultLine: (name: string) => string, defaultLabel: string): EnvironmentRow[] {
|
|
34
|
+
if (job.status !== 'checked') return [{ kind: 'error', text: job.error ?? `ENVIRONMENT_CHECK_FAILED: ${job.status}` }];
|
|
35
|
+
const rows: EnvironmentRow[] = [{ kind: 'label', text: `${job.build} build · ${job.producer.package} ${job.producer.version} check --environment · ${job.environmentLine ?? `check.json environment sha256 ${job.environment?.sha256 ?? '?'}`}${job.wallMs !== null ? ` · ${(job.wallMs / 1000).toFixed(1)} s` : ''}` }];
|
|
36
|
+
for (const name of READOUT_BARS) {
|
|
37
|
+
rows.push({ kind: 'label', text: `${defaultLabel}: ${defaultLine(name)}` });
|
|
38
|
+
rows.push({ kind: 'bar', text: `this world: ${job.bars.find(b => b.name === name)?.line ?? `${name}: not in check summary`}` });
|
|
39
|
+
}
|
|
40
|
+
if (job.check?.line) rows.push({ kind: 'label', text: `this world · ${job.check.line}` });
|
|
41
|
+
return rows;
|
|
42
|
+
}
|
package/web/index.html
CHANGED
|
@@ -6,6 +6,6 @@
|
|
|
6
6
|
<header class="topbar"><div class="brand"><span class="brand-mark">r</span>rig review</div><div><strong id="package-title">Review package</strong><span id="revision" class="subtle"></span></div><div class="mode-switch" role="group" aria-label="Mode"><button type="button" data-mode="rig" data-inspector="rig" aria-controls="inspector-rig" aria-pressed="true">Setup</button><button type="button" data-mode="playback" data-inspector="playback" aria-controls="inspector-playback" aria-pressed="false">Animate</button></div><div class="top-actions"><span id="dirty" class="subtle">Loading…</span><button id="write-review" type="button" class="primary" aria-haspopup="dialog" aria-controls="review-dialog">Write review</button></div></header>
|
|
7
7
|
<div class="layout">
|
|
8
8
|
<aside class="left-panel"><div class="left-header parts-header"><h2>PARTS</h2><input id="part-search" data-part-search type="search" autocomplete="off" aria-label="Find slot" placeholder="Find slot"></div><details id="package-context" class="package-context"><summary><span class="eyebrow">CHECKPOINTS</span><span id="checkpoint-title">Select a checkpoint</span></summary><nav id="checkpoints" aria-label="Checkpoints"></nav><section><label for="reference-select" class="label">Reference</label><select id="reference-select"></select><div id="reference-info" class="hint"></div><div id="person-control" hidden><label for="person-select" class="label space-top">Person in input pose</label><select id="person-select"></select></div></section></details><aside id="parts-explorer" class="parts-explorer" aria-label="Parts explorer"><button type="button" data-part-whole aria-pressed="true">Whole rig</button><label class="part-solo"><input id="part-solo" data-part-solo type="checkbox" disabled>Solo selected slot</label><p data-part-status class="hint" role="status">Loading actual slots…</p><ul data-part-list aria-label="Actual rig slots"></ul><p class="hint">Select in context or enable Solo. Thumbnails are each slot's declared page region; a type icon stands in where the model declares none. Atlas pages are not part thumbnails.</p></aside></aside>
|
|
9
|
-
<main><div class="viewbar"><fieldset id="layer-controls" class="layer-toggles"><legend class="sr-only">Rig layers</legend><label class="layer-toggle" title="No rig supplied"><input aria-keyshortcuts="I" id="layer-images" type="checkbox" checked disabled><span>Images</span><kbd aria-hidden="true">I</kbd></label><label class="layer-toggle" title="No rig supplied"><input aria-keyshortcuts="M" id="layer-mesh" type="checkbox" disabled><span>Mesh edges</span><kbd aria-hidden="true">M</kbd></label><label class="layer-toggle" title="No rig supplied"><input aria-keyshortcuts="B" id="layer-bones" type="checkbox" disabled><span>Bone points</span><kbd aria-hidden="true">B</kbd></label></fieldset><div class="view-tools"><button id="chest-edit" type="button" class="view-tool" aria-pressed="false" aria-controls="chest-editor-bar" title="Place the two chest regions on the rig: drag a centre, the core edge or the band" hidden>Chest regions</button><button id="mesh-edit" type="button" class="view-tool" aria-pressed="false" aria-controls="mesh-editor-bar" title="Edit the selected slot's actual mesh as a typed delta: preview only, the producer retriangulates" hidden>Edit mesh</button></div><div class="zoom-controls"><label class="camera-sync"><input id="camera-sync" type="checkbox" checked>Sync cameras</label><button id="zoom-out" aria-label="Zoom out">−</button><output id="zoom-value">100%</output><button id="zoom-in" aria-label="Zoom in">+</button><button id="fit">Fit</button></div><details id="view-menu" class="view-menu"><summary aria-label="View layout">View: <span id="view-menu-label">Side by side</span></summary><div class="view-menu-list" role="group" aria-label="View"><button type="button" data-view="compare" aria-pressed="true">Side by side</button><button type="button" data-view="reference" aria-pressed="false">Reference</button><button type="button" data-view="rig" aria-pressed="false">Rig</button></div></details></div><div id="chest-editor-bar" class="chest-editor-bar" role="region" aria-label="Chest region editor" hidden><div class="bust-group" role="group" aria-label="Chest editor mode"><input id="bust-mode" type="hidden" value="circles"><button id="bust-mode-circles" type="button" data-bust-mode="circles" aria-pressed="true" title="Two circles, each centre, core and band set by hand (#99)">Circles</button><button id="bust-mode-landmarks" type="button" data-bust-mode="landmarks" aria-pressed="false" title="Three clicks (right apex, left apex, cleavage); the producer derives both circles (contour.bust)">Landmarks</button></div><div class="bust-group" data-chest-mode="circles"><span class="bust-tag bust-tag-r">● R</span><label>X<input id="bust-r-x" type="number" step="1" inputmode="decimal" aria-label="R region centre x"></label><label>Y<input id="bust-r-y" type="number" step="1" inputmode="decimal" aria-label="R region centre y"></label><label>R<input id="bust-r-r" type="number" min="6" max="120" step="1" inputmode="decimal" aria-label="R region core radius"></label><label>Band<input id="bust-r-band" type="number" min="4" max="120" step="1" inputmode="decimal" aria-label="R region band"></label></div><div class="bust-group" data-chest-mode="circles"><span class="bust-tag bust-tag-l">● L</span><label>X<input id="bust-l-x" type="number" step="1" inputmode="decimal" aria-label="L region centre x"></label><label>Y<input id="bust-l-y" type="number" step="1" inputmode="decimal" aria-label="L region centre y"></label><label>R<input id="bust-l-r" type="number" min="6" max="120" step="1" inputmode="decimal" aria-label="L region core radius"></label><label>Band<input id="bust-l-band" type="number" min="4" max="120" step="1" inputmode="decimal" aria-label="L region band"></label></div><div id="bust-landmarks" class="bust-group" data-chest-mode="landmarks" role="group" aria-label="Chest landmarks (rig px)" hidden><button id="lm-pick-apex_r" type="button" data-landmark="apex_r" aria-pressed="false" title="Click the part to place the apex r; drag its marker to move it">Apex R</button><label>X<input id="lm-apex_r-x" type="number" step="1" inputmode="decimal" aria-label="Apex R x (rig px)"></label><label>Y<input id="lm-apex_r-y" type="number" step="1" inputmode="decimal" aria-label="Apex R y (rig px)"></label><button id="lm-pick-apex_l" type="button" data-landmark="apex_l" aria-pressed="false" title="Click the part to place the apex l; drag its marker to move it">Apex L</button><label>X<input id="lm-apex_l-x" type="number" step="1" inputmode="decimal" aria-label="Apex L x (rig px)"></label><label>Y<input id="lm-apex_l-y" type="number" step="1" inputmode="decimal" aria-label="Apex L y (rig px)"></label><button id="lm-pick-cleavage" type="button" data-landmark="cleavage" aria-pressed="false" title="Click the part to place the cleavage; drag its marker to move it">Cleavage</button><label>X<input id="lm-cleavage-x" type="number" step="1" inputmode="decimal" aria-label="Cleavage x (rig px)"></label><label>Y<input id="lm-cleavage-y" type="number" step="1" inputmode="decimal" aria-label="Cleavage y (rig px)"></label></div><div id="bust-landmark-params" class="bust-group" data-chest-mode="landmarks" role="group" aria-label="Landmark constants" hidden><label title="Core ratio r / (r + band); the producer's default is 0.22">α<input id="lm-alpha" type="range" min="0.05" max="0.95" step="0.01" value="0.22" aria-label="Alpha, core ratio"><output id="lm-alpha-value" for="lm-alpha">0.22</output></label><label title="Cleavage clearance in rig px; the producer's default is 3.8">Clearance<input id="lm-clearance" type="range" min="0" max="40" step="0.1" value="3.8" aria-label="Clearance, rig px"><output id="lm-clearance-value" for="lm-clearance">3.8</output></label><label title="Control bone for bust_r; empty is the producer's default boob_r">Bone R<input id="lm-bone_r" type="text" list="lm-bones" placeholder="boob_r" spellcheck="false" autocomplete="off"></label><label title="Control bone for bust_l; empty is the producer's default boob_l">Bone L<input id="lm-bone_l" type="text" list="lm-bones" placeholder="boob_l" spellcheck="false" autocomplete="off"></label><datalist id="lm-bones"></datalist></div><span id="lm-derived" class="hint" data-chest-mode="landmarks" aria-live="polite" hidden></span><div class="bust-group" data-chest-mode="circles"><button id="bust-mirror" type="button" title="Copy R's y, core radius and band to L">Mirror R → L</button><button id="bust-download" type="button" title="Download chest-regions.json and its binding; evidence, not an approval">Download JSON</button></div><div class="bust-group"><button id="bust-apply" type="button" title="Run the installed producer (rig-parts remesh) on these regions or landmarks into a scratch build and load it here; the package is unchanged">Apply · Rebuild</button><button id="bust-reset" type="button" title="Return the stage to the inspected build" disabled>Reset</button></div><span id="handoff-line" class="hint handoff-line" aria-live="polite" title="Producer handoff: the config, parts and inspected rig an Apply rebuilds from"></span><span id="chest-editor-status" class="hint" aria-live="polite"></span><pre id="chest-editor-log" class="rebuild-log" aria-label="Producer output" hidden></pre><textarea id="rebuild-applied" class="sr-only" hidden aria-hidden="true" tabindex="-1"></textarea></div><div id="mesh-editor-bar" class="chest-editor-bar mesh-editor-bar" role="region" aria-label="Mesh edit" hidden><div class="bust-group" role="group" aria-label="Mesh edit sub-mode"><button id="mesh-mode-modify" type="button" data-mesh-mode="modify" aria-pressed="false" aria-keyshortcuts="V" title="Modify · key V">Modify<kbd aria-hidden="true">V</kbd></button><button id="mesh-mode-create" type="button" data-mesh-mode="create" aria-pressed="false" aria-keyshortcuts="C" title="Create · key C">Create<kbd aria-hidden="true">C</kbd></button><button id="mesh-mode-delete" type="button" data-mesh-mode="delete" aria-pressed="false" aria-keyshortcuts="X" title="Delete · key X">Delete<kbd aria-hidden="true">X</kbd></button><button id="mesh-mode-edges" type="button" data-mesh-mode="edges" aria-pressed="false" aria-keyshortcuts="E" title="Edges · key E">Edges<kbd aria-hidden="true">E</kbd></button></div><div class="bust-group"><button id="mesh-undo" type="button" aria-keyshortcuts="Control+Z Meta+Z" title="Undo the last action · Cmd/Ctrl-Z" disabled>Undo</button><button id="mesh-redo" type="button" aria-keyshortcuts="Control+Shift+Z Meta+Shift+Z" title="Redo · Shift-Cmd/Ctrl-Z" disabled>Redo</button><button id="mesh-reset" type="button" title="Drop the whole delta for this attachment (asks first)" disabled>Reset</button></div><div class="bust-group"><button id="mesh-download" type="button" title="Download mesh-edit.json and its binding; evidence, not an approval">Download JSON</button><button id="mesh-apply" type="button" title="Send the delta to the producer (rig-parts remesh --delta) and load the scratch revision in place; nothing is written into the package">Apply · Rebuild</button></div><textarea id="mesh-edit-draft" class="sr-only" hidden aria-hidden="true" tabindex="-1"></textarea><span id="mesh-editor-status" class="hint" aria-live="polite"></span></div><div id="stage-layout" class="stages"><section id="reference-pane" class="stage-pane" aria-label="Reference"><span id="reference-label" class="pane-label">Reference</span><div id="reference-viewport" class="viewport"><div id="reference-content" class="reference-content"></div><div id="reference-empty" class="empty">No reference supplied.</div></div></section><section id="rig-pane" class="stage-pane" aria-label="Rig"><span class="pane-label">Generated rig · read only</span><div id="rig-viewport" class="viewport"><div id="rig-root"></div><canvas id="rig-overlay" class="rig-overlay" aria-label="Actual rig mesh and bone overlay"></canvas><div id="idle-handles" class="idle-handles" hidden aria-label="Chain amplitude handles"></div><div id="rig-fields-stage" class="rig-fields-stage" hidden aria-label="Rig field handles"></div><div id="rig-empty" class="empty">No compiled model supplied.</div></div></section></div><div class="stage-footer"><span id="coordinates">Drag to pan · scroll to zoom · cameras synchronized</span><span>Source files remain unchanged.</span></div><section class="inspector" aria-label="Inspection controls"><div class="inspector-heading sticky-header"><strong>INSPECTOR</strong><span class="hint">Local view controls · source files stay unchanged</span></div><div class="inspector-body"><section id="inspector-rig" aria-label="Setup" data-inspector-panel="rig"><nav class="block-row" aria-label="Setup blocks"><button type="button" data-block-target="selected-part">Part</button><button type="button" data-block-target="modeling-tools">Modeling</button><button type="button" data-block-target="chest-regions">Chest</button><button type="button" data-block-target="mesh-panel">Mesh</button><button type="button" data-block-target="weights">Weights</button><button type="button" data-block-target="bones-panel">Bones</button><button type="button" data-block-target="rig-fields">Fields</button><button type="button" data-block-target="cuts-panel">Cuts</button></nav><section id="selected-part" aria-label="Selected part" aria-live="polite"></section><section id="modeling-tools"><div class="eyebrow">MODELING REVIEW</div><button data-proposal="mesh" class="tool">Mesh density <small>Add feedback</small></button><button data-proposal="contour" class="tool">Contour curvature <small>Add feedback</small></button><button data-proposal="chest" class="tool">Chest position <small>Add feedback</small></button><label for="part-set" class="label space-top">Part presets</label><select id="part-set" disabled><option>No sets supplied</option></select><p class="hint">Preview only · does not create motion keys.</p></section><section id="chest-regions" class="chest-regions" aria-labelledby="chest-regions-heading"><div class="eyebrow" id="chest-regions-heading">CHEST REGIONS · DETAIL</div><p class="hint">Crop pixels, x screen-right and y down (<code>family-chest-regions/1</code>). Place the circles with <strong>Chest regions</strong> in the view bar; this block holds names, bones, semi-axes, clip and the contour. Equal semi-axes export as a circle, unequal ones (or a clip) as the generator's 32-sample polygon. Exporting writes nothing into the package and is not an approval.</p><p id="chest-mapping" class="hint"></p><div class="chest-grid"><label>Crop width<input id="chest-crop-w" type="number" step="any" inputmode="decimal"></label><label>Crop height<input id="chest-crop-h" type="number" step="any" inputmode="decimal"></label></div><fieldset class="chest-row"><legend>Region 1</legend><label>Name<input id="chest-0-name" type="text" autocomplete="off" spellcheck="false"></label><label>Bone<select id="chest-0-bone"><option value="">Select bone</option></select></label><label>Centre x<input id="chest-0-cx" type="number" step="any" inputmode="decimal"></label><label>Centre y<input id="chest-0-cy" type="number" step="any" inputmode="decimal"></label><label>Semi-axis x<input id="chest-0-ax" type="number" step="any" inputmode="decimal"></label><label>Semi-axis y<input id="chest-0-ay" type="number" step="any" inputmode="decimal"></label><label>Spacing<input id="chest-0-spacing" type="number" step="any" inputmode="decimal"></label><label>Band<input id="chest-0-band" type="number" step="any" inputmode="decimal"></label><label>Medial clip x<input id="chest-0-clip" type="number" step="any" inputmode="decimal" placeholder="none"></label></fieldset><fieldset class="chest-row"><legend>Region 2</legend><label>Name<input id="chest-1-name" type="text" autocomplete="off" spellcheck="false"></label><label>Bone<select id="chest-1-bone"><option value="">Select bone</option></select></label><label>Centre x<input id="chest-1-cx" type="number" step="any" inputmode="decimal"></label><label>Centre y<input id="chest-1-cy" type="number" step="any" inputmode="decimal"></label><label>Semi-axis x<input id="chest-1-ax" type="number" step="any" inputmode="decimal"></label><label>Semi-axis y<input id="chest-1-ay" type="number" step="any" inputmode="decimal"></label><label>Spacing<input id="chest-1-spacing" type="number" step="any" inputmode="decimal"></label><label>Band<input id="chest-1-band" type="number" step="any" inputmode="decimal"></label><label>Medial clip x<input id="chest-1-clip" type="number" step="any" inputmode="decimal" placeholder="none"></label></fieldset><fieldset class="chest-row"><legend>Contour</legend><label>Tolerance<input id="chest-tolerance" type="number" step="any" inputmode="decimal"></label><label>Margin<input id="chest-margin" type="number" step="any" inputmode="decimal"></label><label>Spacing<input id="chest-spacing" type="number" step="any" inputmode="decimal"></label><label>Max edge<input id="chest-max-edge" type="number" step="any" inputmode="decimal"></label><label>Stray<input id="chest-stray" type="number" step="any" inputmode="decimal"></label><label class="chest-check"><input id="chest-bridges" type="checkbox">Bridges</label></fieldset><details class="chest-more"><summary>Generator and mesh fields</summary><label for="chest-generator" class="label space-top">Public generator</label><input id="chest-generator" type="text" autocomplete="off" spellcheck="false" placeholder="Generator that will read this file"><label for="chest-r" class="label space-top">Mesh r (optional)</label><input id="chest-r" type="number" step="any" inputmode="decimal"><label for="chest-segments" class="label space-top">Mesh segments JSON (optional)</label><textarea id="chest-segments" rows="3" spellcheck="false"></textarea><label for="chest-source-rule" class="label space-top">Source rule (optional)</label><input id="chest-source-rule" type="text" autocomplete="off"></details><p id="chest-status" class="hint" aria-live="polite"></p><button id="chest-export" type="button" class="outline block">Download chest-regions.json</button></section><section id="mesh-panel" class="physics-panel mesh-panel" aria-labelledby="mesh-panel-heading"><div class="row"><span id="mesh-panel-heading" class="eyebrow">MESH · APPLY REMESHES</span></div><p id="mesh-panel-state" class="hint">Select a mesh part in the parts list.</p><p class="hint">Edits the selected part's <code>meshes.<part></code> entry from the handoff config (the grid and contour modes). Apply sends the whole pending entry to the installed producer's <code>remesh --mesh</code> (one patch op at <code>/meshes/<part></code>), compiles and checks the revision, and reloads it with the mesh edges on; Reset returns to the inspected build and the config's entry. Slider ranges are review's; the number field takes any value, and the producer judges it.</p><div id="mesh-params" class="physics-controls mesh-params"></div><div class="row mesh-tools"><span class="eyebrow">REGIONS</span><button id="mesh-add-circle" type="button" disabled>Add circle</button><button id="mesh-add-polygon" type="button" aria-pressed="false" disabled>Draw polygon</button><button id="mesh-regions-edit" type="button" aria-pressed="false" disabled>Drag on stage</button></div><div id="mesh-regions" class="mesh-regions"></div><div class="row mesh-tools"><span class="eyebrow">SEGMENTS</span><select id="mesh-segment-bone" aria-label="Bone to add as a segment" disabled></select><button id="mesh-segment-add" type="button" disabled>Add</button></div><ol id="mesh-segments" class="physics-pending-list mesh-segments"></ol><div class="row"><span class="eyebrow">PENDING ENTRY · DIFF AGAINST THE CONFIG</span></div><ol id="mesh-diff" class="physics-pending-list"></ol><ul id="mesh-precheck" class="mesh-precheck" aria-label="Review pre-check in the config loader's names"></ul><div class="row"><button id="mesh-panel-apply" type="button" class="primary" disabled>Apply</button><button id="mesh-panel-reset" type="button" disabled>Reset</button><button id="mesh-folds" type="button" title="rigc render --folds of the drawn animation on the inspected build and on this revision (#108 readings)">Folds reading</button></div><output id="mesh-status" class="environment-readout"></output><div id="mesh-counts" class="physics-readout" aria-label="Vertex and triangle counts before and after"></div><div id="mesh-readout" class="physics-readout" aria-label="TEXTURE_STRETCH before and after Apply"></div><textarea id="mesh-pending" class="sr-only" hidden aria-hidden="true" tabindex="-1"></textarea></section><section id="weights" class="physics-panel weights-block" aria-labelledby="weights-heading"><div class="row"><span id="weights-heading" class="eyebrow">WEIGHTS · SETUP POSE · WATCH ONLY</span></div><p class="hint">The selected slot's mesh at the setup pose, each vertex coloured by the weight the compiled model document stores for the picked bone (0 → 1), or by its dominant bone under <strong>Max influence</strong>. An unweighted mesh is its slot's bone at 1.0. Hover a vertex for its stored values. Nothing here edits, applies or records a weight.</p><div class="row weights-controls"><button id="weights-show" type="button" aria-pressed="false">Show weights</button><label>Colour by<select id="weights-mode"><option value="bone">Bone weight</option><option value="max">Max influence</option></select></label><label>Bone<select id="weights-bone" disabled></select></label></div><p id="weights-state" class="hint" aria-live="polite"></p><div id="weights-legend" class="weights-legend" aria-label="Weights legend"></div><output id="weights-hover" class="environment-readout"></output></section><section id="bones-panel" class="physics-panel bones-panel" aria-labelledby="bones-panel-heading"><div class="row"><span id="bones-panel-heading" class="eyebrow">BONES · CONFIG ANCHORS · APPLY REBUILDS</span></div><p id="bones-panel-state" class="hint">Reading the producer handoff…</p><p class="hint">The producer config's bones at their <code>at</code>, chain <code>points</code> and <code>tip</code> (rig px, the setup pose; the stage may be posed by an animation). Drag a handle on the stage or edit the numbers: each move is a config patch op at <code>/bones/<i>/at</code>, <code>/bones/<i>/points/<k></code> or <code>/bones/<i>/tip</code> naming its bone, moved in whole rig px. <strong>Check</strong> runs <code>propose --from-config</code> on a scratch copy of the patched config (LINT and coverage lines; LINT blocks nothing) and <code>compare</code> against the handoff config. <strong>Apply</strong> rebuilds the whole rig (<code>rig --config</code>, then <code>check</code>) and needs the reproduced baseline; Reset returns to the inspected build and clears the pending ops. Adding, removing or re-parenting bones is out of scope.</p><div class="row bones-controls"><button id="bones-edit" type="button" aria-pressed="false" disabled>Edit bones on stage</button><label for="bones-select">Bone</label><select id="bones-select" disabled></select></div><div id="bones-anchor-editor" class="bones-anchor-editor"></div><div class="row"><span class="eyebrow">PENDING OPS</span></div><ol id="bones-pending-list" class="physics-pending-list"></ol><div class="row"><button id="bones-check" type="button" disabled>Check</button><button id="bones-apply" type="button" class="primary" disabled>Apply</button><button id="bones-reset-build" type="button" disabled>Reset</button></div><output id="bones-status" class="environment-readout"></output><div id="bones-compare" class="bones-lines" aria-label="compare: origin and tip distance per moved bone"></div><div id="bones-lint" class="bones-lines" aria-label="propose --from-config: LINT and coverage lines"></div><textarea id="bones-pending" class="sr-only" hidden aria-hidden="true" tabindex="-1"></textarea></section><section id="rig-fields" class="physics-panel rig-fields" aria-labelledby="rig-fields-heading"><div class="row"><span id="rig-fields-heading" class="eyebrow">RIG FIELDS · CONFIG · APPLY REBUILDS</span><span id="rig-fields-dial-label" class="idle-preview-label" hidden>preview — Apply decides</span></div><p id="rig-fields-state" class="hint">Reading the producer handoff…</p><p class="hint">Small producer config fields, each a config patch op: the bone a rigid part rides (<code>/regions/<part></code>), a lash's blink still row (<code>/motion/blink/still/<part></code>, dragged on the stage), a stop's inward / outward bounds (<code>/stops/<i></code>, arcs dragged at the pivot), a bone's inherit mode (<code>/bones/<i>/inherit</code>; a chain's <code>link_inherit</code>) and <code>turn</code>'s pivot and per-part depth (<code>/turn/pivot</code>, <code>/turn/depths/<part></code>). <strong>Apply</strong> rebuilds the whole rig (<code>rig --config</code>, then <code>check</code>) and needs the reproduced baseline; the producer's refusal is shown as printed. The dial only previews the drawn build's <code>turn</code> bone and is never recorded. Reset returns to the inspected build and clears the pending ops.</p><div id="rig-fields-region" class="rig-fields-group"></div><div id="rig-fields-still" class="rig-fields-group"></div><div id="rig-fields-stops" class="rig-fields-group"></div><div id="rig-fields-inherit" class="rig-fields-group"></div><div id="rig-fields-turn" class="rig-fields-group"></div><div class="row"><span class="eyebrow">PENDING OPS</span></div><ol id="rig-fields-pending-list" class="physics-pending-list"></ol><div class="row"><button id="rig-fields-apply" type="button" class="primary" disabled>Apply</button><button id="rig-fields-reset" type="button" disabled>Reset</button><button id="rig-fields-reading" type="button" disabled>Angle range</button></div><output id="rig-fields-status" class="environment-readout"></output><div id="rig-fields-readout" class="physics-readout" aria-label="BLINK_NO_HOLE and FEET_STAY_LEVEL before and after"></div><textarea id="rig-fields-pending" class="sr-only" hidden aria-hidden="true" tabindex="-1"></textarea></section><section id="cuts-panel" class="physics-panel bones-panel cuts-panel" aria-labelledby="cuts-panel-heading"><div class="row"><span id="cuts-panel-heading" class="eyebrow">CUTS · PATCHES · LIMBS · APPLY RUNS RIG-PARTS BUILD</span></div><p id="cuts-panel-state" class="hint">Reading the producer handoff…</p><p class="hint">The producer config's <code>assemble.cuts</code> polygons, <code>assemble.patches</code> boxes and each limb joint with its <code>range</code> arc, on the stage in rig px of the setup pose. Drag a vertex, a box corner or a joint, or edit the numbers: each change is a config patch op (<code>/assemble/cuts/<i>/polygon/<k></code>, <code>/assemble/patches/<i>/box</code>, <code>/limbs/<limb>/segments/<k>/range</code>; a joint is its bone's origin, <code>/bones/…</code>). A new cut or patch adds its entry and a <code>regions</code> bone. <strong>Apply</strong> runs <code>rig-parts build</code> (assemble → rig → check) from the declared painting and See-through runs into scratch and needs the reproduced baseline; the producer's refusals (<code>ASSEMBLE_CUT_*</code>, <code>ASSEMBLE_UNDER_*</code>, <code>CONFIG_LIMB_*</code>, <code>CONFIG_PART_UNIQUE</code>) are shown as printed, and the recomposite error map, the <code>seam pair</code> lines and the build's wall clock come back with the build. Review paints no <code>under</code> or <code>cap</code> image. Reset returns to the inspected build and clears the pending ops.</p><div class="row bones-controls"><button id="cuts-edit" type="button" aria-pressed="false" disabled>Edit cuts on stage</button><label for="cuts-select">Entry</label><select id="cuts-select" disabled></select></div><div id="cuts-editor" class="bones-anchor-editor"></div><div id="cuts-add" class="row cuts-add"><span class="eyebrow">ADD</span><select data-cuts-add="from" aria-label="Plan part the new cut takes pixels from"></select><input data-cuts-add="name" type="text" autocomplete="off" placeholder="new part" aria-label="New part name"><input data-cuts-add="bone" type="text" list="lm-bones" autocomplete="off" placeholder="bone" aria-label="Bone the new part's region rides"><select data-cuts-add="draw" aria-label="Where the new part is drawn"></select><input data-cuts-add="overlap" type="number" min="0" step="1" value="0" inputmode="numeric" aria-label="Cut overlap band in rig px"><select data-cuts-add="alpha" aria-label="New patch alpha"><option value="silhouette">silhouette</option><option value="box">box</option></select><button type="button" data-cuts-add="place-cut">Place cut</button><button type="button" data-cuts-add="place-patch">Place patch</button><button type="button" data-cuts-add="finish">Finish</button><button type="button" data-cuts-add="cancel">Cancel</button></div><div class="row"><span class="eyebrow">PENDING OPS</span></div><ol id="cuts-pending-list" class="physics-pending-list"></ol><div class="row"><button id="cuts-apply" type="button" class="primary" disabled>Apply</button><button id="cuts-reset" type="button" disabled>Reset</button></div><output id="cuts-status" class="environment-readout"></output><div id="cuts-readout" class="physics-readout" aria-label="The build's refusals, cut lines, recomposite, seam pairs and wall clock"></div><textarea id="cuts-pending" class="sr-only" hidden aria-hidden="true" tabindex="-1"></textarea></section></section><section id="inspector-playback" aria-label="Animate" data-inspector-panel="playback" hidden><nav class="block-row" aria-label="Animate blocks"><button type="button" data-block-target="transport" data-block-includes="frame-controls motion-tools angle-panel">Transport</button><button type="button" data-block-target="sound-panel">Sound</button><button type="button" data-block-target="environment">Environment</button><button type="button" data-block-target="physics-panel">Physics</button><button type="button" data-block-target="idle-tracks">Idle tracks</button><button type="button" data-block-target="readings">Readings</button><button type="button" data-block-target="requirements">Requirements</button><button type="button" data-block-target="retarget">Retarget</button><button type="button" data-block-target="report-panel">Report</button></nav><section id="transport" class="transport"><button id="play" aria-label="Play" disabled>▶</button><label for="animation-select" class="sr-only">Animation</label><select id="animation-select" disabled><option>No animations supplied</option></select><input id="scrub" type="range" min="0" max="1" step="0.001" value="0" aria-label="Playback time"><output id="time-label">0.0000 / 0.0000 s</output><output id="frame-label">rate unknown</output><label class="idle-repeat"><input id="idle-repeat" type="checkbox" checked>Repeat idle</label><div id="reading-ticks" class="reading-ticks" hidden aria-label="Reading ticks"></div><div id="idle-blink-strip" class="idle-blink-strip" hidden><button id="idle-blink-marker" type="button" class="idle-blink-marker" aria-label="Blink time">◆</button></div></section><section id="frame-controls" class="frame-controls" hidden aria-label="Frame navigation using the explicit clock"><label for="frame-number">Frame</label><input id="frame-number" type="number" min="0" step="1" inputmode="numeric" aria-label="Frame number to inspect"><button id="frame-jump" type="button">Go</button><input id="frame-scrub" type="range" min="0" max="1" step="1" value="0" aria-label="Scrub by frame"><span id="frame-clock-label" class="hint"></span><p id="frame-outside" class="hint" hidden>The current time is outside the explicit frame range. Enter a frame number to go to its exact time.</p></section><section id="motion-tools"><div class="eyebrow">MOTION · EDITS BUILD THROUGH THE PRODUCER</div><button data-proposal="pose" class="tool">Inspect pose <small>Add feedback</small></button><button data-proposal="timing" class="tool">Inspect interval <small>Add feedback</small></button><button data-proposal="audio" class="tool">Audio links <small>Add feedback</small></button><button data-proposal="angle" class="tool">Angle inspection <small id="axis-state">No inspection axes supplied</small></button></section><section id="angle-panel" class="angle-panel" hidden><label for="axis-select">Rig inspection axis</label><select id="axis-select"></select><label id="axis-label" for="axis-value"></label><input id="axis-value" type="range" disabled><output id="axis-value-label"></output><p class="hint">Maps one inspection axis to its animation time. This does not rotate the camera.</p></section><section id="sound-panel" class="sound-panel"><div class="row"><span class="eyebrow">SOUND CUES</span><label><input id="sync-audio" type="checkbox">Play cue audio in sync</label></div><div id="cues"></div><div class="row"><label for="audio-volume">Audio volume</label><input id="audio-volume" type="range" min="0" max="1" step="0.05" value="0.8"><output id="audio-volume-label">80%</output></div><div id="event-log" class="hint" aria-live="polite">Runtime events: none</div></section><section id="environment" class="environment-panel" aria-labelledby="environment-heading"><div class="row"><span id="environment-heading" class="eyebrow">ENVIRONMENT · INSPECTION ONLY</span></div><p class="hint">Wind, gravity and impulses go to the player while you inspect. Nothing is written to the model or motion; feedback records the state as context.</p><input id="environment-applied" type="checkbox" hidden aria-hidden="true" tabindex="-1"><div class="row"><label for="wind-base">Wind</label><input id="wind-base" type="range" min="-100" max="100" step="1" value="0" disabled><output id="wind-base-label">0</output></div><div class="row"><label><input id="wind-breeze" type="checkbox" disabled>Breeze</label><button id="wind-gust" type="button" disabled>Gust</button><label><input id="wind-repeat" type="checkbox" disabled>Repeat</label></div><div class="row"><label for="wind-gust-peak">Gust peak</label><input id="wind-gust-peak" type="number" min="0" max="200" step="1" value="48" inputmode="decimal" disabled><label for="wind-gust-duration">Duration (s)</label><input id="wind-gust-duration" type="number" min="0.1" max="20" step="0.1" value="2.4" inputmode="decimal" disabled></div><output id="wind-readout" class="environment-readout">No compiled model drawn · environment unavailable</output><div class="row"><label for="gravity">Gravity</label><input id="gravity" type="range" min="0" max="4" step="0.05" value="1" disabled><output id="gravity-label">1.00</output></div><div class="row"><button id="jolt" type="button" disabled>Jolt</button><label title="Dragging the rig pane moves the rig's physics instead of the camera. Hold Space to pan."><input id="drag-trail" type="checkbox" disabled>Drag to trail</label><button id="physics-reset" type="button" disabled>Reset physics</button></div><p class="hint">Drag to trail: dragging the rig pane tells the physics the rig moved; the camera stays. Hold Space to pan. Reset returns the runtime start values and re-poses from the current time, clearing the physics history.</p></section><section id="physics-panel" class="physics-panel" aria-labelledby="physics-panel-heading"><div class="row"><span id="physics-panel-heading" class="eyebrow">PHYSICS TUNING · APPLY REBUILDS</span></div><p id="physics-panel-state" class="hint">No compiled model drawn.</p><p class="hint">Each edit is a config patch op at <code>/constraints/<i>/<field></code> naming its constraint. Apply runs the installed producer (<code>rig --config</code>, then <code>check</code>) into a scratch build and reloads it under the same wind, gust and jolt state; Reset returns to the inspected build and clears the pending ops. Live preview before Apply waits on play#147.</p><div class="row physics-chain-row"><label for="physics-chain">Chain</label><select id="physics-chain" disabled></select><button id="physics-chain-pick" type="button" aria-pressed="false" disabled>Pick on stage</button></div><div class="row physics-ramp-row"><label for="physics-ramp-field">Ramp</label><select id="physics-ramp-field" disabled></select><label for="physics-ramp-root">root</label><input id="physics-ramp-root" type="number" step="any" inputmode="decimal" disabled><label for="physics-ramp-tip">tip</label><input id="physics-ramp-tip" type="number" step="any" inputmode="decimal" disabled><button id="physics-ramp" type="button" disabled>Ramp</button></div><div class="physics-work"><div id="physics-rows" class="physics-table-wrap" role="region" aria-label="Physics constraints the config declares" tabindex="0"></div><div id="physics-row-editor" class="physics-row-editor"></div></div><div id="physics-locked" class="physics-table-wrap physics-locked" role="region" aria-label="Constraints that are not editable" tabindex="0" hidden></div><div class="row"><span class="eyebrow">PENDING OPS</span></div><ol id="physics-pending-list" class="physics-pending-list"></ol><div class="row"><button id="physics-apply" type="button" class="primary" disabled>Apply</button><button id="physics-reset-build" type="button" disabled>Reset</button></div><output id="physics-apply-status" class="environment-readout"></output><div id="physics-readout" class="physics-readout" aria-label="Check readout before and after Apply"></div><textarea id="physics-pending" class="sr-only" hidden aria-hidden="true" tabindex="-1"></textarea></section><section id="idle-tracks" class="physics-panel idle-panel" aria-labelledby="idle-tracks-heading"><div class="row"><span id="idle-tracks-heading" class="eyebrow">IDLE TRACKS · APPLY REBUILDS</span><span id="idle-preview-label" class="idle-preview-label" aria-live="polite" hidden>preview — Apply decides</span></div><p id="idle-tracks-state" class="hint">No compiled model drawn.</p><p class="hint">Each edit is a config patch op at <code>/motion/tracks/<i>/…</code> or <code>/motion/blink/…</code>. Period is offered only among the divisors of <code>motion.duration</code> (the producer refuses any other, <code>CONFIG_PERIOD_DIVIDES_DURATION</code>). While edits are pending the preview drives the edited bones through the player's <code>adjust</code> with the producer's sine, <code>v(t) = base + amp·sin(2π(t/period − phase))</code>, on the bone the idle keys (<code><bone>_ctl</code> under ctl keys); it is never recorded or submitted. Apply runs the installed producer (<code>rig --config</code>, then <code>check</code>) into a scratch build and replaces the preview with the built idle; Reset returns to the inspected build and clears the pending ops.</p><p id="idle-keys-line" class="hint"></p><div class="physics-work"><div id="idle-track-list" class="physics-table-wrap" role="region" aria-label="Idle tracks the config states" tabindex="0"></div><div id="idle-track-editor" class="physics-row-editor"></div></div><datalist id="idle-bones"></datalist><div class="row idle-add-row"><span class="eyebrow">ADD</span><input data-idle-add-bone type="text" list="idle-bones" autocomplete="off" placeholder="bone" aria-label="Bone for a new track" disabled><select data-idle-add-prop aria-label="Property for a new track" disabled></select><button data-idle-add-single type="button" disabled>Add track</button><select data-idle-add-chain aria-label="Chain for a new chain track" disabled></select><button data-idle-add-chain-button type="button" disabled>Add chain track</button></div><div id="idle-blink" class="idle-blink"></div><div class="row"><label class="idle-preview-toggle"><input data-idle-preview type="checkbox" checked>Preview edits (not recorded)</label></div><div id="idle-precheck" class="idle-precheck" aria-label="Review pre-check"></div><div class="row"><span class="eyebrow">PENDING OPS</span></div><ol id="idle-pending-list" class="physics-pending-list"></ol><div class="row"><button id="idle-apply" type="button" class="primary" disabled>Apply</button><button id="idle-reset" type="button" disabled>Reset</button></div><output id="idle-apply-status" class="environment-readout"></output><div id="idle-readout" class="physics-readout" aria-label="Check readout before and after Apply"></div><textarea id="idle-pending" class="sr-only" hidden aria-hidden="true" tabindex="-1"></textarea></section><section id="readings" class="readings-panel" aria-labelledby="readings-heading"><div class="row"><span id="readings-heading" class="eyebrow">READINGS · RIGC RENDER</span></div><p class="hint">Each reading is one <code>rigc render</code> of the drawn animation, on the inspected build and on the latest scratch build. Its frames become ticks under the timeline (<span class="tick-swatch inspected" aria-hidden="true"></span> inspected, <span class="tick-swatch scratch" aria-hidden="true"></span> scratch; click one to seek) and each pair gets its worst-frame line. A reading, not a gate: an unknown slot or bone is refused with rigc's own line.</p><datalist id="readings-slots"></datalist><datalist id="readings-bones"></datalist><div class="row"><label><input id="readings-folds" type="checkbox">Folds</label><label><input id="readings-overlaps" type="checkbox">Overlaps</label></div><div class="row readings-pair"><label><input id="readings-pass-on" type="checkbox">Pass-through</label><input id="readings-pass-cloth" type="text" list="readings-slots" autocomplete="off" placeholder="cloth slot" aria-label="Pass-through cloth slot"><span aria-hidden="true">:</span><input id="readings-pass-leg" type="text" list="readings-slots" autocomplete="off" placeholder="leg slot" aria-label="Pass-through leg slot"></div><div class="row readings-pair"><label><input id="readings-seam-on" type="checkbox">Seam</label><input id="readings-seam-a" type="text" list="readings-slots" autocomplete="off" placeholder="slot" aria-label="Seam first slot"><span aria-hidden="true">:</span><input id="readings-seam-b" type="text" list="readings-slots" autocomplete="off" placeholder="slot" aria-label="Seam second slot"></div><div class="row readings-pair"><label><input id="readings-angle-on" type="checkbox">Angle range</label><input id="readings-angle-bone" type="text" list="readings-bones" autocomplete="off" placeholder="bone" aria-label="Angle range bone"><span aria-hidden="true">vs</span><input id="readings-angle-relative" type="text" list="readings-bones" autocomplete="off" placeholder="relative bone" aria-label="Angle range relative-to bone"></div><div class="row"><button id="readings-run" type="button" disabled>Run readings</button></div><p id="readings-status" class="hint" aria-live="polite">No compiled model drawn.</p><ul id="readings-worst" class="readings-worst" aria-label="Worst frame per reading"></ul></section><section id="requirements" class="readings-panel requirements-panel" aria-labelledby="requirements-heading"><div class="row"><span id="requirements-heading" class="eyebrow">REQUIREMENTS · RIG-PARTS CHECK</span></div><p class="hint">Declare what the scene asks of the motion: a slot (picked on the stage), its mesh or a bone, an animation and a kind, with your own bar. Check writes a <code>spine-parts-requirements/1</code> file into scratch and runs the installed producer's <code>rig-parts check --requirements</code> on the inspected rig or the latest scratch build; each requirement gets one line with its worst frame (click to seek). A refusal is the producer's, by name. Waivers (<code>may_fold</code>), scene targets and the part exception lists are not written here. Submitted feedback carries the file by hash and content.</p><div id="requirements-body"></div></section><section id="retarget" class="readings-panel requirements-panel retarget-panel" aria-labelledby="retarget-heading"><div class="row"><span id="retarget-heading" class="eyebrow">RETARGET · RIG-POSE</span></div><p class="hint">Put a pose sequence the package declares onto this rig: edit the <code>rig-pose-map/1</code> (per chain, the bone that carries each segment), set the clean and retarget flags, and Apply. The installed <code>@rig-c/pose</code> runs <code>rig-pose clean</code> and <code>rig-pose retarget</code> onto the handoff rig, and its <code>rigc</code> compiles the result, all into scratch; the new animation plays here beside the reference (pick the sequence under Reference). Review writes no keys and never writes the package's <code>motion.json</code>. Refusals (<code>POSE_NOT_CLEANED</code>, <code>POSE_BEND_ONE_WAY</code>, <code>RAISED_HEAD</code>, <code>MOTION_NAME_FREE</code>, <code>MAP_*</code>) are the producer's, verbatim. FEET, STANCE, FLIP, SHORT, END-ON, LEG-SCALE and REACH lines seek to their frame.</p><div id="retarget-body"></div></section><section id="report-panel" class="report-panel" aria-labelledby="report-heading"><div class="report-header"><h2 id="report-heading">Report events</h2><span id="report-summary" class="subtle"></span><a id="report-source-link" target="_blank" rel="noopener" hidden>Source report</a></div><p id="report-unavailable" class="hint" hidden>No rig-pose-report/1 events supplied.</p><div id="report-browser"><div class="report-controls"><label for="report-category" class="sr-only">Report event category</label><select id="report-category"><option value="all">All events</option></select><button id="report-previous" type="button" aria-label="Previous event page">Previous</button><output id="report-page"></output><button id="report-next" type="button" aria-label="Next event page">Next</button></div><div id="report-empty-categories" class="report-empty-categories"></div><p id="report-missing" class="hint" hidden></p><ol id="report-events" class="report-event-list" aria-label="Read-only report events"></ol><section id="report-selection" class="report-selection" hidden aria-labelledby="report-selection-heading"><h3 id="report-selection-heading"></h3><p id="report-timing" class="hint"></p><div class="report-controls"><label id="report-time-label" for="report-time" hidden>Select explicit time</label><select id="report-time" hidden></select><button id="report-seek" type="button" disabled>Go to explicit time</button></div><div id="report-target-control" hidden><label for="report-target" class="label space-top">Select a target named in the report</label><select id="report-target"></select></div><p id="report-prefill-note" class="hint"></p><p id="report-provenance" class="hint"></p><details><summary>Source event details</summary><pre id="report-details"></pre></details></section></div></section></section></div></section></main></div><footer class="status-bar"><span id="message" class="status-message" role="status" aria-live="polite">Loading local review package…</span><div class="status-readouts" aria-label="Status"><span id="player-state" class="subtle">No rig supplied</span><span id="layout-state" class="subtle" role="status" aria-label="Effective layout">Loading inputs…</span><span id="renderer-state" class="subtle" aria-label="Last draw backend" title="DOM images for rigid parts; WebGL meshes when available, Canvas2D fallback.">Not rendered</span><span id="layer-state" class="hint">No rig supplied</span><span id="idle-state" class="hint" role="status">Idle preview unavailable</span><span id="control-state" class="hint" role="status"></span><div class="draft-notice"><span id="draft-state" role="status" aria-live="polite"></span><div id="draft-recovery"></div></div></div><div class="status-source"><details class="view-region"><summary>View region</summary><div class="view-region-body"><span class="hint">Numeric source region · opening this link restores a paused inspection</span><pre id="view-coordinates" class="numeric-view" aria-label="Current numeric inspection region"></pre><p class="hint">Sync cameras links fit-relative zoom and viewport-relative pan across visible panes. Turn it off to inspect each pane independently. Toolbar zoom and Fit use the last active pane. Hold Space while dragging a video to pan without taking over its native controls.</p></div></details><span id="source-hash" class="subtle"></span></div></footer><dialog id="review-dialog" class="review-dialog" aria-labelledby="review-dialog-title"><div class="review-dialog-header"><h2 id="review-dialog-title">Write review</h2><span class="hint">Drafts save locally as you type · nothing is submitted until you press Submit</span><button id="review-dialog-close" type="button">Close</button></div><section id="inspector-feedback" class="review-dialog-body" aria-label="Feedback"><div class="review-summary"><div class="eyebrow">REVIEW</div><label for="review-status" class="label">Review status</label><select id="review-status"><option value="pending">Pending</option><option value="approved">Approved</option><option value="changes-requested">Changes requested</option></select><section class="provenance" aria-label="Reviewed source"><div class="eyebrow">REVIEWED SOURCE</div><div class="provenance-row"><span>Package revision</span><strong id="provenance-version">—</strong></div><div class="provenance-row"><span>rig SHA-256</span><code id="provenance-rig">—</code></div><div class="provenance-row"><span>motion SHA-256</span><code id="provenance-motion">—</code></div><details><summary>Full provenance</summary><pre id="provenance-full"></pre></details><div class="source-files"><div class="eyebrow">SOURCE FILES · READ ONLY</div><div id="source-files"></div></div><p class="hint">Submits server-verified file hashes and tool versions. Your selected review status is preserved.</p></section><div class="divider"></div></div><div id="checkpoint-detail"><div class="eyebrow">SELECTED CHECKPOINT</div><h1 id="detail-title">Checkpoint</h1><p id="detail-note" class="hint"></p><fieldset class="outcome"><legend>Checkpoint outcome</legend><label><input type="radio" name="outcome" value="pending" checked>Pending</label><label><input type="radio" name="outcome" value="pass">Pass</label><label><input type="radio" name="outcome" value="fail">Needs changes</label></fieldset><label for="checkpoint-note" class="label space-top">Review notes</label><textarea id="checkpoint-note" rows="5" placeholder="Describe the part, conditions and what looks wrong."></textarea></div><div class="divider"></div><section id="proposal-panel"><div class="eyebrow">STRUCTURED FEEDBACK</div><div class="label">Frame range · inclusive</div><div class="frame-range"><label>Start<input id="feedback-start" type="number" min="0" step="1" inputmode="numeric" placeholder="e.g. 18"></label><label>End<input id="feedback-end" type="number" min="0" step="1" inputmode="numeric" placeholder="e.g. 32"></label></div><p id="feedback-range-hint" class="hint">Checking frame bounds…</p><label for="feedback-target-kind" class="label space-top">Target type</label><select id="feedback-target-kind"><option value="">Select target</option><option value="rig">Entire rig</option><option value="bone">Bone</option><option value="slot">Slot</option></select><label for="feedback-target-name" class="label space-top">Target name</label><input id="feedback-target-name" type="text" list="feedback-target-names" autocomplete="off" placeholder="Actual bone or slot name"><datalist id="feedback-target-names"></datalist><p id="feedback-target-hint" class="hint"></p><label for="feedback-category" class="label space-top">Category</label><select id="feedback-category"><option value="">Select category</option><option value="defect">Defect</option><option value="quality">Quality</option></select><label for="feedback-summary" class="label space-top">One-line summary</label><input id="feedback-summary" type="text" maxlength="1000" placeholder="e.g. The left wrist moves outside the sleeve"><button id="add-proposal" class="outline block">Add feedback</button><p id="proposal-boundary" class="hint">Records the current checkpoint, time, reference, camera and inspection axis. Motion is read only: inspect, mark ranges and export feedback. No keys or source files are edited.</p><p id="feedback-warnings" class="hint" role="status" hidden></p><p id="feedback-applied-check" class="hint error" role="alert" hidden></p><div id="proposal-list"></div></section><details id="strip-panel" class="report-panel"><summary>Export frame strip</summary><p class="hint">Uses the feedback form start and end frames, inclusive. Inspection time and source files remain unchanged.</p><label for="strip-scope" class="label">Export range</label><select id="strip-scope"><option value="interval">Entered start–end frames</option><option value="feedback">All feedback ranges</option></select><label for="strip-reference" class="label space-top">Reference video</label><select id="strip-reference"><option value="">Select reference video</option></select><label for="strip-format" class="label space-top">Format</label><select id="strip-format"><option value="png">PNG strip</option><option value="json">JSON frames and provenance</option></select><p class="hint">PNG requires frameTimes in the selected report. Without it, JSON preserves requested frames with time: null. Batch export requires every feedback item to match the selected animation. Unsubmitted feedback is included; exporting does not submit the review.</p><button id="strip-export" type="button" disabled>Download strip</button><p id="strip-status" class="hint" role="status" aria-live="polite">Loading package…</p></details></section><div class="review-dialog-actions"><span id="review-message" class="review-message" role="status" aria-live="polite"></span><span id="review-draft-state" class="review-draft-state" role="status" aria-live="polite"></span><button id="save" class="primary" disabled>Submit review</button></div></dialog></div>
|
|
9
|
+
<main><div class="viewbar"><fieldset id="layer-controls" class="layer-toggles"><legend class="sr-only">Rig layers</legend><label class="layer-toggle" title="No rig supplied"><input aria-keyshortcuts="I" id="layer-images" type="checkbox" checked disabled><span>Images</span><kbd aria-hidden="true">I</kbd></label><label class="layer-toggle" title="No rig supplied"><input aria-keyshortcuts="M" id="layer-mesh" type="checkbox" disabled><span>Mesh edges</span><kbd aria-hidden="true">M</kbd></label><label class="layer-toggle" title="No rig supplied"><input aria-keyshortcuts="B" id="layer-bones" type="checkbox" disabled><span>Bone points</span><kbd aria-hidden="true">B</kbd></label></fieldset><div class="view-tools"><button id="chest-edit" type="button" class="view-tool" aria-pressed="false" aria-controls="chest-editor-bar" title="Place the two chest regions on the rig: drag a centre, the core edge or the band" hidden>Chest regions</button><button id="mesh-edit" type="button" class="view-tool" aria-pressed="false" aria-controls="mesh-editor-bar" title="Edit the selected slot's actual mesh as a typed delta: preview only, the producer retriangulates" hidden>Edit mesh</button></div><div class="zoom-controls"><label class="camera-sync"><input id="camera-sync" type="checkbox" checked>Sync cameras</label><button id="zoom-out" aria-label="Zoom out">−</button><output id="zoom-value">100%</output><button id="zoom-in" aria-label="Zoom in">+</button><button id="fit">Fit</button></div><details id="view-menu" class="view-menu"><summary aria-label="View layout">View: <span id="view-menu-label">Side by side</span></summary><div class="view-menu-list" role="group" aria-label="View"><button type="button" data-view="compare" aria-pressed="true">Side by side</button><button type="button" data-view="reference" aria-pressed="false">Reference</button><button type="button" data-view="rig" aria-pressed="false">Rig</button></div></details></div><div id="chest-editor-bar" class="chest-editor-bar" role="region" aria-label="Chest region editor" hidden><div class="bust-group" role="group" aria-label="Chest editor mode"><input id="bust-mode" type="hidden" value="circles"><button id="bust-mode-circles" type="button" data-bust-mode="circles" aria-pressed="true" title="Two circles, each centre, core and band set by hand (#99)">Circles</button><button id="bust-mode-landmarks" type="button" data-bust-mode="landmarks" aria-pressed="false" title="Three clicks (right apex, left apex, cleavage); the producer derives both circles (contour.bust)">Landmarks</button></div><div class="bust-group" data-chest-mode="circles"><span class="bust-tag bust-tag-r">● R</span><label>X<input id="bust-r-x" type="number" step="1" inputmode="decimal" aria-label="R region centre x"></label><label>Y<input id="bust-r-y" type="number" step="1" inputmode="decimal" aria-label="R region centre y"></label><label>R<input id="bust-r-r" type="number" min="6" max="120" step="1" inputmode="decimal" aria-label="R region core radius"></label><label>Band<input id="bust-r-band" type="number" min="4" max="120" step="1" inputmode="decimal" aria-label="R region band"></label></div><div class="bust-group" data-chest-mode="circles"><span class="bust-tag bust-tag-l">● L</span><label>X<input id="bust-l-x" type="number" step="1" inputmode="decimal" aria-label="L region centre x"></label><label>Y<input id="bust-l-y" type="number" step="1" inputmode="decimal" aria-label="L region centre y"></label><label>R<input id="bust-l-r" type="number" min="6" max="120" step="1" inputmode="decimal" aria-label="L region core radius"></label><label>Band<input id="bust-l-band" type="number" min="4" max="120" step="1" inputmode="decimal" aria-label="L region band"></label></div><div id="bust-landmarks" class="bust-group" data-chest-mode="landmarks" role="group" aria-label="Chest landmarks (rig px)" hidden><button id="lm-pick-apex_r" type="button" data-landmark="apex_r" aria-pressed="false" title="Click the part to place the apex r; drag its marker to move it">Apex R</button><label>X<input id="lm-apex_r-x" type="number" step="1" inputmode="decimal" aria-label="Apex R x (rig px)"></label><label>Y<input id="lm-apex_r-y" type="number" step="1" inputmode="decimal" aria-label="Apex R y (rig px)"></label><button id="lm-pick-apex_l" type="button" data-landmark="apex_l" aria-pressed="false" title="Click the part to place the apex l; drag its marker to move it">Apex L</button><label>X<input id="lm-apex_l-x" type="number" step="1" inputmode="decimal" aria-label="Apex L x (rig px)"></label><label>Y<input id="lm-apex_l-y" type="number" step="1" inputmode="decimal" aria-label="Apex L y (rig px)"></label><button id="lm-pick-cleavage" type="button" data-landmark="cleavage" aria-pressed="false" title="Click the part to place the cleavage; drag its marker to move it">Cleavage</button><label>X<input id="lm-cleavage-x" type="number" step="1" inputmode="decimal" aria-label="Cleavage x (rig px)"></label><label>Y<input id="lm-cleavage-y" type="number" step="1" inputmode="decimal" aria-label="Cleavage y (rig px)"></label></div><div id="bust-landmark-params" class="bust-group" data-chest-mode="landmarks" role="group" aria-label="Landmark constants" hidden><label title="Core ratio r / (r + band); the producer's default is 0.22">α<input id="lm-alpha" type="range" min="0.05" max="0.95" step="0.01" value="0.22" aria-label="Alpha, core ratio"><output id="lm-alpha-value" for="lm-alpha">0.22</output></label><label title="Cleavage clearance in rig px; the producer's default is 3.8">Clearance<input id="lm-clearance" type="range" min="0" max="40" step="0.1" value="3.8" aria-label="Clearance, rig px"><output id="lm-clearance-value" for="lm-clearance">3.8</output></label><label title="Control bone for bust_r; empty is the producer's default boob_r">Bone R<input id="lm-bone_r" type="text" list="lm-bones" placeholder="boob_r" spellcheck="false" autocomplete="off"></label><label title="Control bone for bust_l; empty is the producer's default boob_l">Bone L<input id="lm-bone_l" type="text" list="lm-bones" placeholder="boob_l" spellcheck="false" autocomplete="off"></label><datalist id="lm-bones"></datalist></div><span id="lm-derived" class="hint" data-chest-mode="landmarks" aria-live="polite" hidden></span><div class="bust-group" data-chest-mode="circles"><button id="bust-mirror" type="button" title="Copy R's y, core radius and band to L">Mirror R → L</button><button id="bust-download" type="button" title="Download chest-regions.json and its binding; evidence, not an approval">Download JSON</button></div><div class="bust-group"><button id="bust-apply" type="button" title="Run the installed producer (rig-parts remesh) on these regions or landmarks into a scratch build and load it here; the package is unchanged">Apply · Rebuild</button><button id="bust-reset" type="button" title="Return the stage to the inspected build" disabled>Reset</button></div><span id="handoff-line" class="hint handoff-line" aria-live="polite" title="Producer handoff: the config, parts and inspected rig an Apply rebuilds from"></span><span id="chest-editor-status" class="hint" aria-live="polite"></span><pre id="chest-editor-log" class="rebuild-log" aria-label="Producer output" hidden></pre><textarea id="rebuild-applied" class="sr-only" hidden aria-hidden="true" tabindex="-1"></textarea></div><div id="mesh-editor-bar" class="chest-editor-bar mesh-editor-bar" role="region" aria-label="Mesh edit" hidden><div class="bust-group" role="group" aria-label="Mesh edit sub-mode"><button id="mesh-mode-modify" type="button" data-mesh-mode="modify" aria-pressed="false" aria-keyshortcuts="V" title="Modify · key V">Modify<kbd aria-hidden="true">V</kbd></button><button id="mesh-mode-create" type="button" data-mesh-mode="create" aria-pressed="false" aria-keyshortcuts="C" title="Create · key C">Create<kbd aria-hidden="true">C</kbd></button><button id="mesh-mode-delete" type="button" data-mesh-mode="delete" aria-pressed="false" aria-keyshortcuts="X" title="Delete · key X">Delete<kbd aria-hidden="true">X</kbd></button><button id="mesh-mode-edges" type="button" data-mesh-mode="edges" aria-pressed="false" aria-keyshortcuts="E" title="Edges · key E">Edges<kbd aria-hidden="true">E</kbd></button></div><div class="bust-group"><button id="mesh-undo" type="button" aria-keyshortcuts="Control+Z Meta+Z" title="Undo the last action · Cmd/Ctrl-Z" disabled>Undo</button><button id="mesh-redo" type="button" aria-keyshortcuts="Control+Shift+Z Meta+Shift+Z" title="Redo · Shift-Cmd/Ctrl-Z" disabled>Redo</button><button id="mesh-reset" type="button" title="Drop the whole delta for this attachment (asks first)" disabled>Reset</button></div><div class="bust-group"><button id="mesh-download" type="button" title="Download mesh-edit.json and its binding; evidence, not an approval">Download JSON</button><button id="mesh-apply" type="button" title="Send the delta to the producer (rig-parts remesh --delta) and load the scratch revision in place; nothing is written into the package">Apply · Rebuild</button></div><textarea id="mesh-edit-draft" class="sr-only" hidden aria-hidden="true" tabindex="-1"></textarea><span id="mesh-editor-status" class="hint" aria-live="polite"></span></div><div id="stage-layout" class="stages"><section id="reference-pane" class="stage-pane" aria-label="Reference"><span id="reference-label" class="pane-label">Reference</span><div id="reference-viewport" class="viewport"><div id="reference-content" class="reference-content"></div><div id="reference-empty" class="empty">No reference supplied.</div></div></section><section id="rig-pane" class="stage-pane" aria-label="Rig"><span class="pane-label">Generated rig · read only</span><div id="rig-viewport" class="viewport"><div id="rig-root"></div><canvas id="rig-overlay" class="rig-overlay" aria-label="Actual rig mesh and bone overlay"></canvas><div id="idle-handles" class="idle-handles" hidden aria-label="Chain amplitude handles"></div><div id="rig-fields-stage" class="rig-fields-stage" hidden aria-label="Rig field handles"></div><div id="rig-empty" class="empty">No compiled model supplied.</div></div></section></div><div class="stage-footer"><span id="coordinates">Drag to pan · scroll to zoom · cameras synchronized</span><span>Source files remain unchanged.</span></div><section class="inspector" aria-label="Inspection controls"><div class="inspector-heading sticky-header"><strong>INSPECTOR</strong><span class="hint">Local view controls · source files stay unchanged</span></div><div class="inspector-body"><section id="inspector-rig" aria-label="Setup" data-inspector-panel="rig"><nav class="block-row" aria-label="Setup blocks"><button type="button" data-block-target="selected-part">Part</button><button type="button" data-block-target="modeling-tools">Modeling</button><button type="button" data-block-target="chest-regions">Chest</button><button type="button" data-block-target="mesh-panel">Mesh</button><button type="button" data-block-target="weights">Weights</button><button type="button" data-block-target="bones-panel">Bones</button><button type="button" data-block-target="rig-fields">Fields</button><button type="button" data-block-target="cuts-panel">Cuts</button><button type="button" data-block-target="cloth-panel">Cloth</button></nav><section id="selected-part" aria-label="Selected part" aria-live="polite"></section><section id="modeling-tools"><div class="eyebrow">MODELING REVIEW</div><button data-proposal="mesh" class="tool">Mesh density <small>Add feedback</small></button><button data-proposal="contour" class="tool">Contour curvature <small>Add feedback</small></button><button data-proposal="chest" class="tool">Chest position <small>Add feedback</small></button><label for="part-set" class="label space-top">Part presets</label><select id="part-set" disabled><option>No sets supplied</option></select><p class="hint">Preview only · does not create motion keys.</p></section><section id="chest-regions" class="chest-regions" aria-labelledby="chest-regions-heading"><div class="eyebrow" id="chest-regions-heading">CHEST REGIONS · DETAIL</div><p class="hint">Crop pixels, x screen-right and y down (<code>family-chest-regions/1</code>). Place the circles with <strong>Chest regions</strong> in the view bar; this block holds names, bones, semi-axes, clip and the contour. Equal semi-axes export as a circle, unequal ones (or a clip) as the generator's 32-sample polygon. Exporting writes nothing into the package and is not an approval.</p><p id="chest-mapping" class="hint"></p><div class="chest-grid"><label>Crop width<input id="chest-crop-w" type="number" step="any" inputmode="decimal"></label><label>Crop height<input id="chest-crop-h" type="number" step="any" inputmode="decimal"></label></div><fieldset class="chest-row"><legend>Region 1</legend><label>Name<input id="chest-0-name" type="text" autocomplete="off" spellcheck="false"></label><label>Bone<select id="chest-0-bone"><option value="">Select bone</option></select></label><label>Centre x<input id="chest-0-cx" type="number" step="any" inputmode="decimal"></label><label>Centre y<input id="chest-0-cy" type="number" step="any" inputmode="decimal"></label><label>Semi-axis x<input id="chest-0-ax" type="number" step="any" inputmode="decimal"></label><label>Semi-axis y<input id="chest-0-ay" type="number" step="any" inputmode="decimal"></label><label>Spacing<input id="chest-0-spacing" type="number" step="any" inputmode="decimal"></label><label>Band<input id="chest-0-band" type="number" step="any" inputmode="decimal"></label><label>Medial clip x<input id="chest-0-clip" type="number" step="any" inputmode="decimal" placeholder="none"></label></fieldset><fieldset class="chest-row"><legend>Region 2</legend><label>Name<input id="chest-1-name" type="text" autocomplete="off" spellcheck="false"></label><label>Bone<select id="chest-1-bone"><option value="">Select bone</option></select></label><label>Centre x<input id="chest-1-cx" type="number" step="any" inputmode="decimal"></label><label>Centre y<input id="chest-1-cy" type="number" step="any" inputmode="decimal"></label><label>Semi-axis x<input id="chest-1-ax" type="number" step="any" inputmode="decimal"></label><label>Semi-axis y<input id="chest-1-ay" type="number" step="any" inputmode="decimal"></label><label>Spacing<input id="chest-1-spacing" type="number" step="any" inputmode="decimal"></label><label>Band<input id="chest-1-band" type="number" step="any" inputmode="decimal"></label><label>Medial clip x<input id="chest-1-clip" type="number" step="any" inputmode="decimal" placeholder="none"></label></fieldset><fieldset class="chest-row"><legend>Contour</legend><label>Tolerance<input id="chest-tolerance" type="number" step="any" inputmode="decimal"></label><label>Margin<input id="chest-margin" type="number" step="any" inputmode="decimal"></label><label>Spacing<input id="chest-spacing" type="number" step="any" inputmode="decimal"></label><label>Max edge<input id="chest-max-edge" type="number" step="any" inputmode="decimal"></label><label>Stray<input id="chest-stray" type="number" step="any" inputmode="decimal"></label><label class="chest-check"><input id="chest-bridges" type="checkbox">Bridges</label></fieldset><details class="chest-more"><summary>Generator and mesh fields</summary><label for="chest-generator" class="label space-top">Public generator</label><input id="chest-generator" type="text" autocomplete="off" spellcheck="false" placeholder="Generator that will read this file"><label for="chest-r" class="label space-top">Mesh r (optional)</label><input id="chest-r" type="number" step="any" inputmode="decimal"><label for="chest-segments" class="label space-top">Mesh segments JSON (optional)</label><textarea id="chest-segments" rows="3" spellcheck="false"></textarea><label for="chest-source-rule" class="label space-top">Source rule (optional)</label><input id="chest-source-rule" type="text" autocomplete="off"></details><p id="chest-status" class="hint" aria-live="polite"></p><button id="chest-export" type="button" class="outline block">Download chest-regions.json</button></section><section id="mesh-panel" class="physics-panel mesh-panel" aria-labelledby="mesh-panel-heading"><div class="row"><span id="mesh-panel-heading" class="eyebrow">MESH · APPLY REMESHES</span></div><p id="mesh-panel-state" class="hint">Select a mesh part in the parts list.</p><p class="hint">Edits the selected part's <code>meshes.<part></code> entry from the handoff config (the grid and contour modes). Apply sends the whole pending entry to the installed producer's <code>remesh --mesh</code> (one patch op at <code>/meshes/<part></code>), compiles and checks the revision, and reloads it with the mesh edges on; Reset returns to the inspected build and the config's entry. Slider ranges are review's; the number field takes any value, and the producer judges it.</p><div id="mesh-params" class="physics-controls mesh-params"></div><div class="row mesh-tools"><span class="eyebrow">REGIONS</span><button id="mesh-add-circle" type="button" disabled>Add circle</button><button id="mesh-add-polygon" type="button" aria-pressed="false" disabled>Draw polygon</button><button id="mesh-regions-edit" type="button" aria-pressed="false" disabled>Drag on stage</button></div><div id="mesh-regions" class="mesh-regions"></div><div class="row mesh-tools"><span class="eyebrow">SEGMENTS</span><select id="mesh-segment-bone" aria-label="Bone to add as a segment" disabled></select><button id="mesh-segment-add" type="button" disabled>Add</button></div><ol id="mesh-segments" class="physics-pending-list mesh-segments"></ol><div class="row"><span class="eyebrow">PENDING ENTRY · DIFF AGAINST THE CONFIG</span></div><ol id="mesh-diff" class="physics-pending-list"></ol><ul id="mesh-precheck" class="mesh-precheck" aria-label="Review pre-check in the config loader's names"></ul><div class="row"><button id="mesh-panel-apply" type="button" class="primary" disabled>Apply</button><button id="mesh-panel-reset" type="button" disabled>Reset</button><button id="mesh-folds" type="button" title="rigc render --folds of the drawn animation on the inspected build and on this revision (#108 readings)">Folds reading</button></div><output id="mesh-status" class="environment-readout"></output><div id="mesh-counts" class="physics-readout" aria-label="Vertex and triangle counts before and after"></div><div id="mesh-readout" class="physics-readout" aria-label="TEXTURE_STRETCH before and after Apply"></div><textarea id="mesh-pending" class="sr-only" hidden aria-hidden="true" tabindex="-1"></textarea></section><section id="weights" class="physics-panel weights-block" aria-labelledby="weights-heading"><div class="row"><span id="weights-heading" class="eyebrow">WEIGHTS · SETUP POSE · WATCH ONLY</span></div><p class="hint">The selected slot's mesh at the setup pose, each vertex coloured by the weight the compiled model document stores for the picked bone (0 → 1), or by its dominant bone under <strong>Max influence</strong>. An unweighted mesh is its slot's bone at 1.0. Hover a vertex for its stored values. Nothing here edits, applies or records a weight.</p><div class="row weights-controls"><button id="weights-show" type="button" aria-pressed="false">Show weights</button><label>Colour by<select id="weights-mode"><option value="bone">Bone weight</option><option value="max">Max influence</option></select></label><label>Bone<select id="weights-bone" disabled></select></label></div><p id="weights-state" class="hint" aria-live="polite"></p><div id="weights-legend" class="weights-legend" aria-label="Weights legend"></div><output id="weights-hover" class="environment-readout"></output></section><section id="bones-panel" class="physics-panel bones-panel" aria-labelledby="bones-panel-heading"><div class="row"><span id="bones-panel-heading" class="eyebrow">BONES · CONFIG ANCHORS · APPLY REBUILDS</span></div><p id="bones-panel-state" class="hint">Reading the producer handoff…</p><p class="hint">The producer config's bones at their <code>at</code>, chain <code>points</code> and <code>tip</code> (rig px, the setup pose; the stage may be posed by an animation). Drag a handle on the stage or edit the numbers: each move is a config patch op at <code>/bones/<i>/at</code>, <code>/bones/<i>/points/<k></code> or <code>/bones/<i>/tip</code> naming its bone, moved in whole rig px. <strong>Check</strong> runs <code>propose --from-config</code> on a scratch copy of the patched config (LINT and coverage lines; LINT blocks nothing) and <code>compare</code> against the handoff config. <strong>Apply</strong> rebuilds the whole rig (<code>rig --config</code>, then <code>check</code>) and needs the reproduced baseline; Reset returns to the inspected build and clears the pending ops. Adding, removing or re-parenting bones is out of scope.</p><div class="row bones-controls"><button id="bones-edit" type="button" aria-pressed="false" disabled>Edit bones on stage</button><label for="bones-select">Bone</label><select id="bones-select" disabled></select></div><div id="bones-anchor-editor" class="bones-anchor-editor"></div><div class="row"><span class="eyebrow">PENDING OPS</span></div><ol id="bones-pending-list" class="physics-pending-list"></ol><div class="row"><button id="bones-check" type="button" disabled>Check</button><button id="bones-apply" type="button" class="primary" disabled>Apply</button><button id="bones-reset-build" type="button" disabled>Reset</button></div><output id="bones-status" class="environment-readout"></output><div id="bones-compare" class="bones-lines" aria-label="compare: origin and tip distance per moved bone"></div><div id="bones-lint" class="bones-lines" aria-label="propose --from-config: LINT and coverage lines"></div><textarea id="bones-pending" class="sr-only" hidden aria-hidden="true" tabindex="-1"></textarea></section><section id="rig-fields" class="physics-panel rig-fields" aria-labelledby="rig-fields-heading"><div class="row"><span id="rig-fields-heading" class="eyebrow">RIG FIELDS · CONFIG · APPLY REBUILDS</span><span id="rig-fields-dial-label" class="idle-preview-label" hidden>preview — Apply decides</span></div><p id="rig-fields-state" class="hint">Reading the producer handoff…</p><p class="hint">Small producer config fields, each a config patch op: the bone a rigid part rides (<code>/regions/<part></code>), a lash's blink still row (<code>/motion/blink/still/<part></code>, dragged on the stage), a stop's inward / outward bounds (<code>/stops/<i></code>, arcs dragged at the pivot), a bone's inherit mode (<code>/bones/<i>/inherit</code>; a chain's <code>link_inherit</code>) and <code>turn</code>'s pivot and per-part depth (<code>/turn/pivot</code>, <code>/turn/depths/<part></code>). <strong>Apply</strong> rebuilds the whole rig (<code>rig --config</code>, then <code>check</code>) and needs the reproduced baseline; the producer's refusal is shown as printed. The dial only previews the drawn build's <code>turn</code> bone and is never recorded. Reset returns to the inspected build and clears the pending ops.</p><div id="rig-fields-region" class="rig-fields-group"></div><div id="rig-fields-still" class="rig-fields-group"></div><div id="rig-fields-stops" class="rig-fields-group"></div><div id="rig-fields-inherit" class="rig-fields-group"></div><div id="rig-fields-turn" class="rig-fields-group"></div><div class="row"><span class="eyebrow">PENDING OPS</span></div><ol id="rig-fields-pending-list" class="physics-pending-list"></ol><div class="row"><button id="rig-fields-apply" type="button" class="primary" disabled>Apply</button><button id="rig-fields-reset" type="button" disabled>Reset</button><button id="rig-fields-reading" type="button" disabled>Angle range</button></div><output id="rig-fields-status" class="environment-readout"></output><div id="rig-fields-readout" class="physics-readout" aria-label="BLINK_NO_HOLE and FEET_STAY_LEVEL before and after"></div><textarea id="rig-fields-pending" class="sr-only" hidden aria-hidden="true" tabindex="-1"></textarea></section><section id="cuts-panel" class="physics-panel bones-panel cuts-panel" aria-labelledby="cuts-panel-heading"><div class="row"><span id="cuts-panel-heading" class="eyebrow">CUTS · PATCHES · LIMBS · APPLY RUNS RIG-PARTS BUILD</span></div><p id="cuts-panel-state" class="hint">Reading the producer handoff…</p><p class="hint">The producer config's <code>assemble.cuts</code> polygons, <code>assemble.patches</code> boxes and each limb joint with its <code>range</code> arc, on the stage in rig px of the setup pose. Drag a vertex, a box corner or a joint, or edit the numbers: each change is a config patch op (<code>/assemble/cuts/<i>/polygon/<k></code>, <code>/assemble/patches/<i>/box</code>, <code>/limbs/<limb>/segments/<k>/range</code>; a joint is its bone's origin, <code>/bones/…</code>). A new cut or patch adds its entry and a <code>regions</code> bone. <strong>Apply</strong> runs <code>rig-parts build</code> (assemble → rig → check) from the declared painting and See-through runs into scratch and needs the reproduced baseline; the producer's refusals (<code>ASSEMBLE_CUT_*</code>, <code>ASSEMBLE_UNDER_*</code>, <code>CONFIG_LIMB_*</code>, <code>CONFIG_PART_UNIQUE</code>) are shown as printed, and the recomposite error map, the <code>seam pair</code> lines and the build's wall clock come back with the build. Review paints no <code>under</code> or <code>cap</code> image. Reset returns to the inspected build and clears the pending ops.</p><div class="row bones-controls"><button id="cuts-edit" type="button" aria-pressed="false" disabled>Edit cuts on stage</button><label for="cuts-select">Entry</label><select id="cuts-select" disabled></select></div><div id="cuts-editor" class="bones-anchor-editor"></div><div id="cuts-add" class="row cuts-add"><span class="eyebrow">ADD</span><select data-cuts-add="from" aria-label="Plan part the new cut takes pixels from"></select><input data-cuts-add="name" type="text" autocomplete="off" placeholder="new part" aria-label="New part name"><input data-cuts-add="bone" type="text" list="lm-bones" autocomplete="off" placeholder="bone" aria-label="Bone the new part's region rides"><select data-cuts-add="draw" aria-label="Where the new part is drawn"></select><input data-cuts-add="overlap" type="number" min="0" step="1" value="0" inputmode="numeric" aria-label="Cut overlap band in rig px"><select data-cuts-add="alpha" aria-label="New patch alpha"><option value="silhouette">silhouette</option><option value="box">box</option></select><button type="button" data-cuts-add="place-cut">Place cut</button><button type="button" data-cuts-add="place-patch">Place patch</button><button type="button" data-cuts-add="finish">Finish</button><button type="button" data-cuts-add="cancel">Cancel</button></div><div class="row"><span class="eyebrow">PENDING OPS</span></div><ol id="cuts-pending-list" class="physics-pending-list"></ol><div class="row"><button id="cuts-apply" type="button" class="primary" disabled>Apply</button><button id="cuts-reset" type="button" disabled>Reset</button></div><output id="cuts-status" class="environment-readout"></output><div id="cuts-readout" class="physics-readout" aria-label="The build's refusals, cut lines, recomposite, seam pairs and wall clock"></div><textarea id="cuts-pending" class="sr-only" hidden aria-hidden="true" tabindex="-1"></textarea></section><section id="cloth-panel" class="physics-panel bones-panel cloth-panel" aria-labelledby="cloth-panel-heading"><div class="row"><span id="cloth-panel-heading" class="eyebrow">CLOTH · RECIPE PER TAG · APPLY RUNS RIG-PARTS PROPOSE --CLOTH</span></div><p id="cloth-panel-state" class="hint">Reading the producer handoff…</p><p class="hint">The producer config's <code>cloth</code> block: one recipe per tag the installed reader accepts, with its fields as written. Each change is a config patch op at <code>/cloth/<tag>/…</code>. <strong>Show links</strong> draws the chains the tag's parts are meshed on, from the config's <code>points</code> and <code>tip</code> in rig px of the setup pose, and after an Apply the chains of the config the producer wrote. <strong>Apply</strong> runs <code>rig-parts propose --cloth --write-config</code> on the declared parts, painting and keypoints into scratch, then builds the written config (<code>rig-parts build</code> when the config declares <code>limbs</code>, else <code>rig --config</code>) and reloads it under the Environment's wind and gravity; the producer's refusals (<code>CONFIG_CLOTH_*</code>, <code>PROPOSE_CLOTH_DRAFTED</code>, <code>PROPOSE_CLOTH_PART</code>, <code>PROPOSE_WRITE_CONFIG_*</code>) are shown as printed. Review edits the recipe, never the drafted constraints. Reset returns to the inspected build and clears the pending ops.</p><div class="row bones-controls"><button id="cloth-links" type="button" aria-pressed="false" disabled>Show links on stage</button><label for="cloth-tag">Tag</label><select id="cloth-tag" disabled></select><label class="cloth-declared"><input id="cloth-declared" type="checkbox" disabled> recipe declared</label></div><div id="cloth-fields" class="cloth-fields"></div><div class="row"><span class="eyebrow">PENDING OPS</span></div><ol id="cloth-pending-list" class="physics-pending-list"></ol><div class="row"><button id="cloth-apply" type="button" class="primary" disabled>Apply</button><button id="cloth-reset" type="button" disabled>Reset</button></div><output id="cloth-status" class="environment-readout"></output><div id="cloth-readout" class="physics-readout" aria-label="Propose's refusals, the written config's diff, the route and check"></div><textarea id="cloth-pending" class="sr-only" hidden aria-hidden="true" tabindex="-1"></textarea></section></section><section id="inspector-playback" aria-label="Animate" data-inspector-panel="playback" hidden><nav class="block-row" aria-label="Animate blocks"><button type="button" data-block-target="transport" data-block-includes="frame-controls motion-tools angle-panel">Transport</button><button type="button" data-block-target="sound-panel">Sound</button><button type="button" data-block-target="environment">Environment</button><button type="button" data-block-target="physics-panel">Physics</button><button type="button" data-block-target="idle-tracks">Idle tracks</button><button type="button" data-block-target="readings">Readings</button><button type="button" data-block-target="requirements">Requirements</button><button type="button" data-block-target="retarget">Retarget</button><button type="button" data-block-target="report-panel">Report</button></nav><section id="transport" class="transport"><button id="play" aria-label="Play" disabled>▶</button><label for="animation-select" class="sr-only">Animation</label><select id="animation-select" disabled><option>No animations supplied</option></select><input id="scrub" type="range" min="0" max="1" step="0.001" value="0" aria-label="Playback time"><output id="time-label">0.0000 / 0.0000 s</output><output id="frame-label">rate unknown</output><label class="idle-repeat"><input id="idle-repeat" type="checkbox" checked>Repeat idle</label><div id="reading-ticks" class="reading-ticks" hidden aria-label="Reading ticks"></div><div id="idle-blink-strip" class="idle-blink-strip" hidden><button id="idle-blink-marker" type="button" class="idle-blink-marker" aria-label="Blink time">◆</button></div></section><section id="frame-controls" class="frame-controls" hidden aria-label="Frame navigation using the explicit clock"><label for="frame-number">Frame</label><input id="frame-number" type="number" min="0" step="1" inputmode="numeric" aria-label="Frame number to inspect"><button id="frame-jump" type="button">Go</button><input id="frame-scrub" type="range" min="0" max="1" step="1" value="0" aria-label="Scrub by frame"><span id="frame-clock-label" class="hint"></span><p id="frame-outside" class="hint" hidden>The current time is outside the explicit frame range. Enter a frame number to go to its exact time.</p></section><section id="motion-tools"><div class="eyebrow">MOTION · EDITS BUILD THROUGH THE PRODUCER</div><button data-proposal="pose" class="tool">Inspect pose <small>Add feedback</small></button><button data-proposal="timing" class="tool">Inspect interval <small>Add feedback</small></button><button data-proposal="audio" class="tool">Audio links <small>Add feedback</small></button><button data-proposal="angle" class="tool">Angle inspection <small id="axis-state">No inspection axes supplied</small></button></section><section id="angle-panel" class="angle-panel" hidden><label for="axis-select">Rig inspection axis</label><select id="axis-select"></select><label id="axis-label" for="axis-value"></label><input id="axis-value" type="range" disabled><output id="axis-value-label"></output><p class="hint">Maps one inspection axis to its animation time. This does not rotate the camera.</p></section><section id="sound-panel" class="sound-panel"><div class="row"><span class="eyebrow">SOUND CUES</span><label><input id="sync-audio" type="checkbox">Play cue audio in sync</label></div><div id="cues"></div><div class="row"><label for="audio-volume">Audio volume</label><input id="audio-volume" type="range" min="0" max="1" step="0.05" value="0.8"><output id="audio-volume-label">80%</output></div><div id="event-log" class="hint" aria-live="polite">Runtime events: none</div></section><section id="environment" class="environment-panel" aria-labelledby="environment-heading"><div class="row"><span id="environment-heading" class="eyebrow">ENVIRONMENT · INSPECTION ONLY</span></div><p class="hint">Wind, gravity and impulses go to the player while you inspect. Nothing is written to the model or motion; feedback records the state as context.</p><input id="environment-applied" type="checkbox" hidden aria-hidden="true" tabindex="-1"><div class="row"><label for="wind-base">Wind</label><input id="wind-base" type="range" min="-100" max="100" step="1" value="0" disabled><output id="wind-base-label">0</output></div><div class="row"><label><input id="wind-breeze" type="checkbox" disabled>Breeze</label><button id="wind-gust" type="button" disabled>Gust</button><label><input id="wind-repeat" type="checkbox" disabled>Repeat</label></div><div class="row"><label for="wind-gust-peak">Gust peak</label><input id="wind-gust-peak" type="number" min="0" max="200" step="1" value="48" inputmode="decimal" disabled><label for="wind-gust-duration">Duration (s)</label><input id="wind-gust-duration" type="number" min="0.1" max="20" step="0.1" value="2.4" inputmode="decimal" disabled></div><output id="wind-readout" class="environment-readout">No compiled model drawn · environment unavailable</output><div class="row"><label for="gravity">Gravity</label><input id="gravity" type="range" min="0" max="4" step="0.05" value="1" disabled><output id="gravity-label">1.00</output></div><div class="row"><button id="jolt" type="button" disabled>Jolt</button><label title="Dragging the rig pane moves the rig's physics instead of the camera. Hold Space to pan."><input id="drag-trail" type="checkbox" disabled>Drag to trail</label><button id="physics-reset" type="button" disabled>Reset physics</button></div><p class="hint">Drag to trail: dragging the rig pane tells the physics the rig moved; the camera stays. Hold Space to pan. Reset returns the runtime start values and re-poses from the current time, clearing the physics history.</p><div class="row"><label title="Readings and Apply's check read this wind and gravity as a rigc-environment/1 schedule; the wind clock restarts with the clip so you watch the same world"><input id="environment-use" type="checkbox" disabled>Use for readings</label><button id="environment-export" type="button" disabled>Export schedule</button><button id="environment-check" type="button" disabled>Check under this world</button></div><output id="environment-schedule" class="environment-readout">No schedule · the controls are not applied (the player stands at the runtime start values)</output><div id="environment-check-lines" class="environment-check-lines" aria-live="polite"></div><p class="hint">The schedule is the controls as a <code>rigc-environment/1</code> file from the clip's first frame: one step when the wind is steady, else one step per frame at 12 fps (between frames rigc holds the frame's value). A one-off Gust is not in it; Repeat puts gusts in it. Check under this world runs <code>rig-parts check --environment</code> on the build on the stage; the default-world line is that build's own check.</p></section><section id="physics-panel" class="physics-panel" aria-labelledby="physics-panel-heading"><div class="row"><span id="physics-panel-heading" class="eyebrow">PHYSICS TUNING · APPLY REBUILDS</span></div><p id="physics-panel-state" class="hint">No compiled model drawn.</p><p class="hint">Each edit is a config patch op at <code>/constraints/<i>/<field></code> naming its constraint. Apply runs the installed producer (<code>rig --config</code>, then <code>check</code>) into a scratch build and reloads it under the same wind, gust and jolt state; Reset returns to the inspected build and clears the pending ops. Live preview before Apply waits on play#147.</p><div class="row physics-chain-row"><label for="physics-chain">Chain</label><select id="physics-chain" disabled></select><button id="physics-chain-pick" type="button" aria-pressed="false" disabled>Pick on stage</button></div><div class="row physics-ramp-row"><label for="physics-ramp-field">Ramp</label><select id="physics-ramp-field" disabled></select><label for="physics-ramp-root">root</label><input id="physics-ramp-root" type="number" step="any" inputmode="decimal" disabled><label for="physics-ramp-tip">tip</label><input id="physics-ramp-tip" type="number" step="any" inputmode="decimal" disabled><button id="physics-ramp" type="button" disabled>Ramp</button></div><div class="physics-work"><div id="physics-rows" class="physics-table-wrap" role="region" aria-label="Physics constraints the config declares" tabindex="0"></div><div id="physics-row-editor" class="physics-row-editor"></div></div><div id="physics-locked" class="physics-table-wrap physics-locked" role="region" aria-label="Constraints that are not editable" tabindex="0" hidden></div><div class="row"><span class="eyebrow">PENDING OPS</span></div><ol id="physics-pending-list" class="physics-pending-list"></ol><div class="row"><button id="physics-apply" type="button" class="primary" disabled>Apply</button><button id="physics-reset-build" type="button" disabled>Reset</button></div><output id="physics-apply-status" class="environment-readout"></output><div id="physics-readout" class="physics-readout" aria-label="Check readout before and after Apply"></div><textarea id="physics-pending" class="sr-only" hidden aria-hidden="true" tabindex="-1"></textarea></section><section id="idle-tracks" class="physics-panel idle-panel" aria-labelledby="idle-tracks-heading"><div class="row"><span id="idle-tracks-heading" class="eyebrow">IDLE TRACKS · APPLY REBUILDS</span><span id="idle-preview-label" class="idle-preview-label" aria-live="polite" hidden>preview — Apply decides</span></div><p id="idle-tracks-state" class="hint">No compiled model drawn.</p><p class="hint">Each edit is a config patch op at <code>/motion/tracks/<i>/…</code> or <code>/motion/blink/…</code>. Period is offered only among the divisors of <code>motion.duration</code> (the producer refuses any other, <code>CONFIG_PERIOD_DIVIDES_DURATION</code>). While edits are pending the preview drives the edited bones through the player's <code>adjust</code> with the producer's sine, <code>v(t) = base + amp·sin(2π(t/period − phase))</code>, on the bone the idle keys (<code><bone>_ctl</code> under ctl keys); it is never recorded or submitted. Apply runs the installed producer (<code>rig --config</code>, then <code>check</code>) into a scratch build and replaces the preview with the built idle; Reset returns to the inspected build and clears the pending ops.</p><p id="idle-keys-line" class="hint"></p><div class="physics-work"><div id="idle-track-list" class="physics-table-wrap" role="region" aria-label="Idle tracks the config states" tabindex="0"></div><div id="idle-track-editor" class="physics-row-editor"></div></div><datalist id="idle-bones"></datalist><div class="row idle-add-row"><span class="eyebrow">ADD</span><input data-idle-add-bone type="text" list="idle-bones" autocomplete="off" placeholder="bone" aria-label="Bone for a new track" disabled><select data-idle-add-prop aria-label="Property for a new track" disabled></select><button data-idle-add-single type="button" disabled>Add track</button><select data-idle-add-chain aria-label="Chain for a new chain track" disabled></select><button data-idle-add-chain-button type="button" disabled>Add chain track</button></div><div id="idle-blink" class="idle-blink"></div><div class="row"><label class="idle-preview-toggle"><input data-idle-preview type="checkbox" checked>Preview edits (not recorded)</label></div><div id="idle-precheck" class="idle-precheck" aria-label="Review pre-check"></div><div class="row"><span class="eyebrow">PENDING OPS</span></div><ol id="idle-pending-list" class="physics-pending-list"></ol><div class="row"><button id="idle-apply" type="button" class="primary" disabled>Apply</button><button id="idle-reset" type="button" disabled>Reset</button></div><output id="idle-apply-status" class="environment-readout"></output><div id="idle-readout" class="physics-readout" aria-label="Check readout before and after Apply"></div><textarea id="idle-pending" class="sr-only" hidden aria-hidden="true" tabindex="-1"></textarea></section><section id="readings" class="readings-panel" aria-labelledby="readings-heading"><div class="row"><span id="readings-heading" class="eyebrow">READINGS · RIGC RENDER</span></div><p class="hint">Each reading is one <code>rigc render</code> of the drawn animation, on the inspected build and on the latest scratch build. Its frames become ticks under the timeline (<span class="tick-swatch inspected" aria-hidden="true"></span> inspected, <span class="tick-swatch scratch" aria-hidden="true"></span> scratch; click one to seek) and each pair gets its worst-frame line. A reading, not a gate: an unknown slot or bone is refused with rigc's own line.</p><datalist id="readings-slots"></datalist><datalist id="readings-bones"></datalist><div class="row"><label><input id="readings-folds" type="checkbox">Folds</label><label><input id="readings-overlaps" type="checkbox">Overlaps</label></div><div class="row readings-pair"><label><input id="readings-pass-on" type="checkbox">Pass-through</label><input id="readings-pass-cloth" type="text" list="readings-slots" autocomplete="off" placeholder="cloth slot" aria-label="Pass-through cloth slot"><span aria-hidden="true">:</span><input id="readings-pass-leg" type="text" list="readings-slots" autocomplete="off" placeholder="leg slot" aria-label="Pass-through leg slot"></div><div class="row readings-pair"><label><input id="readings-seam-on" type="checkbox">Seam</label><input id="readings-seam-a" type="text" list="readings-slots" autocomplete="off" placeholder="slot" aria-label="Seam first slot"><span aria-hidden="true">:</span><input id="readings-seam-b" type="text" list="readings-slots" autocomplete="off" placeholder="slot" aria-label="Seam second slot"></div><div class="row readings-pair"><label><input id="readings-angle-on" type="checkbox">Angle range</label><input id="readings-angle-bone" type="text" list="readings-bones" autocomplete="off" placeholder="bone" aria-label="Angle range bone"><span aria-hidden="true">vs</span><input id="readings-angle-relative" type="text" list="readings-bones" autocomplete="off" placeholder="relative bone" aria-label="Angle range relative-to bone"></div><div class="row"><button id="readings-run" type="button" disabled>Run readings</button></div><p id="readings-status" class="hint" aria-live="polite">No compiled model drawn.</p><ul id="readings-worst" class="readings-worst" aria-label="Worst frame per reading"></ul></section><section id="requirements" class="readings-panel requirements-panel" aria-labelledby="requirements-heading"><div class="row"><span id="requirements-heading" class="eyebrow">REQUIREMENTS · RIG-PARTS CHECK</span></div><p class="hint">Declare what the scene asks of the motion: a slot (picked on the stage), its mesh or a bone, an animation and a kind, with your own bar. Check writes a <code>spine-parts-requirements/1</code> file into scratch and runs the installed producer's <code>rig-parts check --requirements</code> on the inspected rig or the latest scratch build; each requirement gets one line with its worst frame (click to seek). A refusal is the producer's, by name. Waivers (<code>may_fold</code>), scene targets and the part exception lists are not written here. Submitted feedback carries the file by hash and content.</p><div id="requirements-body"></div></section><section id="retarget" class="readings-panel requirements-panel retarget-panel" aria-labelledby="retarget-heading"><div class="row"><span id="retarget-heading" class="eyebrow">RETARGET · RIG-POSE</span></div><p class="hint">Put a pose sequence the package declares onto this rig: edit the <code>rig-pose-map/1</code> (per chain, the bone that carries each segment), set the clean and retarget flags, and Apply. The installed <code>@rig-c/pose</code> runs <code>rig-pose clean</code> and <code>rig-pose retarget</code> onto the handoff rig, and its <code>rigc</code> compiles the result, all into scratch; the new animation plays here beside the reference (pick the sequence under Reference). Review writes no keys and never writes the package's <code>motion.json</code>. Refusals (<code>POSE_NOT_CLEANED</code>, <code>POSE_BEND_ONE_WAY</code>, <code>RAISED_HEAD</code>, <code>MOTION_NAME_FREE</code>, <code>MAP_*</code>) are the producer's, verbatim. FEET, STANCE, FLIP, SHORT, END-ON, LEG-SCALE and REACH lines seek to their frame.</p><div id="retarget-body"></div></section><section id="report-panel" class="report-panel" aria-labelledby="report-heading"><div class="report-header"><h2 id="report-heading">Report events</h2><span id="report-summary" class="subtle"></span><a id="report-source-link" target="_blank" rel="noopener" hidden>Source report</a></div><p id="report-unavailable" class="hint" hidden>No rig-pose-report/1 events supplied.</p><div id="report-browser"><div class="report-controls"><label for="report-category" class="sr-only">Report event category</label><select id="report-category"><option value="all">All events</option></select><button id="report-previous" type="button" aria-label="Previous event page">Previous</button><output id="report-page"></output><button id="report-next" type="button" aria-label="Next event page">Next</button></div><div id="report-empty-categories" class="report-empty-categories"></div><p id="report-missing" class="hint" hidden></p><ol id="report-events" class="report-event-list" aria-label="Read-only report events"></ol><section id="report-selection" class="report-selection" hidden aria-labelledby="report-selection-heading"><h3 id="report-selection-heading"></h3><p id="report-timing" class="hint"></p><div class="report-controls"><label id="report-time-label" for="report-time" hidden>Select explicit time</label><select id="report-time" hidden></select><button id="report-seek" type="button" disabled>Go to explicit time</button></div><div id="report-target-control" hidden><label for="report-target" class="label space-top">Select a target named in the report</label><select id="report-target"></select></div><p id="report-prefill-note" class="hint"></p><p id="report-provenance" class="hint"></p><details><summary>Source event details</summary><pre id="report-details"></pre></details></section></div></section></section></div></section></main></div><footer class="status-bar"><span id="message" class="status-message" role="status" aria-live="polite">Loading local review package…</span><div class="status-readouts" aria-label="Status"><span id="player-state" class="subtle">No rig supplied</span><span id="layout-state" class="subtle" role="status" aria-label="Effective layout">Loading inputs…</span><span id="renderer-state" class="subtle" aria-label="Last draw backend" title="DOM images for rigid parts; WebGL meshes when available, Canvas2D fallback.">Not rendered</span><span id="layer-state" class="hint">No rig supplied</span><span id="idle-state" class="hint" role="status">Idle preview unavailable</span><span id="control-state" class="hint" role="status"></span><div class="draft-notice"><span id="draft-state" role="status" aria-live="polite"></span><div id="draft-recovery"></div></div></div><div class="status-source"><details class="view-region"><summary>View region</summary><div class="view-region-body"><span class="hint">Numeric source region · opening this link restores a paused inspection</span><pre id="view-coordinates" class="numeric-view" aria-label="Current numeric inspection region"></pre><p class="hint">Sync cameras links fit-relative zoom and viewport-relative pan across visible panes. Turn it off to inspect each pane independently. Toolbar zoom and Fit use the last active pane. Hold Space while dragging a video to pan without taking over its native controls.</p></div></details><span id="source-hash" class="subtle"></span></div></footer><dialog id="review-dialog" class="review-dialog" aria-labelledby="review-dialog-title"><div class="review-dialog-header"><h2 id="review-dialog-title">Write review</h2><span class="hint">Drafts save locally as you type · nothing is submitted until you press Submit</span><button id="review-dialog-close" type="button">Close</button></div><section id="inspector-feedback" class="review-dialog-body" aria-label="Feedback"><div class="review-summary"><div class="eyebrow">REVIEW</div><label for="review-status" class="label">Review status</label><select id="review-status"><option value="pending">Pending</option><option value="approved">Approved</option><option value="changes-requested">Changes requested</option></select><section class="provenance" aria-label="Reviewed source"><div class="eyebrow">REVIEWED SOURCE</div><div class="provenance-row"><span>Package revision</span><strong id="provenance-version">—</strong></div><div class="provenance-row"><span>rig SHA-256</span><code id="provenance-rig">—</code></div><div class="provenance-row"><span>motion SHA-256</span><code id="provenance-motion">—</code></div><details><summary>Full provenance</summary><pre id="provenance-full"></pre></details><div class="source-files"><div class="eyebrow">SOURCE FILES · READ ONLY</div><div id="source-files"></div></div><p class="hint">Submits server-verified file hashes and tool versions. Your selected review status is preserved.</p></section><div class="divider"></div></div><div id="checkpoint-detail"><div class="eyebrow">SELECTED CHECKPOINT</div><h1 id="detail-title">Checkpoint</h1><p id="detail-note" class="hint"></p><fieldset class="outcome"><legend>Checkpoint outcome</legend><label><input type="radio" name="outcome" value="pending" checked>Pending</label><label><input type="radio" name="outcome" value="pass">Pass</label><label><input type="radio" name="outcome" value="fail">Needs changes</label></fieldset><label for="checkpoint-note" class="label space-top">Review notes</label><textarea id="checkpoint-note" rows="5" placeholder="Describe the part, conditions and what looks wrong."></textarea></div><div class="divider"></div><section id="proposal-panel"><div class="eyebrow">STRUCTURED FEEDBACK</div><div class="label">Frame range · inclusive</div><div class="frame-range"><label>Start<input id="feedback-start" type="number" min="0" step="1" inputmode="numeric" placeholder="e.g. 18"></label><label>End<input id="feedback-end" type="number" min="0" step="1" inputmode="numeric" placeholder="e.g. 32"></label></div><p id="feedback-range-hint" class="hint">Checking frame bounds…</p><label for="feedback-target-kind" class="label space-top">Target type</label><select id="feedback-target-kind"><option value="">Select target</option><option value="rig">Entire rig</option><option value="bone">Bone</option><option value="slot">Slot</option></select><label for="feedback-target-name" class="label space-top">Target name</label><input id="feedback-target-name" type="text" list="feedback-target-names" autocomplete="off" placeholder="Actual bone or slot name"><datalist id="feedback-target-names"></datalist><p id="feedback-target-hint" class="hint"></p><label for="feedback-category" class="label space-top">Category</label><select id="feedback-category"><option value="">Select category</option><option value="defect">Defect</option><option value="quality">Quality</option></select><label for="feedback-summary" class="label space-top">One-line summary</label><input id="feedback-summary" type="text" maxlength="1000" placeholder="e.g. The left wrist moves outside the sleeve"><button id="add-proposal" class="outline block">Add feedback</button><p id="proposal-boundary" class="hint">Records the current checkpoint, time, reference, camera and inspection axis. Motion is read only: inspect, mark ranges and export feedback. No keys or source files are edited.</p><p id="feedback-warnings" class="hint" role="status" hidden></p><p id="feedback-applied-check" class="hint error" role="alert" hidden></p><div id="proposal-list"></div></section><details id="strip-panel" class="report-panel"><summary>Export frame strip</summary><p class="hint">Uses the feedback form start and end frames, inclusive. Inspection time and source files remain unchanged.</p><label for="strip-scope" class="label">Export range</label><select id="strip-scope"><option value="interval">Entered start–end frames</option><option value="feedback">All feedback ranges</option></select><label for="strip-reference" class="label space-top">Reference video</label><select id="strip-reference"><option value="">Select reference video</option></select><label for="strip-format" class="label space-top">Format</label><select id="strip-format"><option value="png">PNG strip</option><option value="json">JSON frames and provenance</option></select><p class="hint">PNG requires frameTimes in the selected report. Without it, JSON preserves requested frames with time: null. Batch export requires every feedback item to match the selected animation. Unsubmitted feedback is included; exporting does not submit the review.</p><button id="strip-export" type="button" disabled>Download strip</button><p id="strip-status" class="hint" role="status" aria-live="polite">Loading package…</p></details></section><div class="review-dialog-actions"><span id="review-message" class="review-message" role="status" aria-live="polite"></span><span id="review-draft-state" class="review-draft-state" role="status" aria-live="polite"></span><button id="save" class="primary" disabled>Submit review</button></div></dialog></div>
|
|
10
10
|
<script type="module" src="main.js"></script>
|
|
11
11
|
</body></html>
|