Files
ARCADIA-Studio/harness/experiments/behavior-layout-policy-20260912/experiment.mjs
T

88 lines
5.0 KiB
JavaScript

import assert from 'node:assert/strict';
import { writeFile } from 'node:fs/promises';
import { performance } from 'node:perf_hooks';
// Isolated policy comparison; these illustrative metrics are not Studio rules.
const separation = 10;
function absolute(rows) {
let end = -separation;
return rows.map(r => { const y = Math.max(r.y, end + separation); end=y+r.height; return {...r,y}; });
}
function gaps(rows) {
let end=0;
return rows.map((r,i) => { assert(r.gap >= (i?separation:0)); const y=end+r.gap; end=y+r.height; return {...r,y}; });
}
function encodeGaps(rows) {
return rows.map((r,i)=>({...r,gap:r.y-(i?rows[i-1].y+rows[i-1].height:0)}));
}
const positions = rows=>rows.map(r=>r.y);
function moveAbsolute(rows,index,requested) {
const current=absolute(rows);
const floor=index?current[index-1].y+current[index-1].height+separation:0;
const candidate=rows.map((r,i)=>({...r,y:i===index?Math.max(requested,floor):r.y}));
return {candidate,preview:absolute(candidate)};
}
function changeCount(before,after,key) {return before.reduce((n,r,i)=>n+(r[key]!==after[i][key]),0);}
const initial=[{id:'A',y:100,height:20},{id:'B',y:200,height:20}];
const initialG=encodeGaps(initial);
const cases=[];
function named(name,a,g,expectedA,expectedG) {
assert.deepEqual(positions(a),expectedA,name+' absolute');
assert.deepEqual(positions(g),expectedG,name+' gaps');
cases.push({name,absolute:positions(a),gapsWithoutInverse:positions(g)});
}
named('move A to50',absolute([{...initial[0],y:50},initial[1]]),gaps([{...initialG[0],gap:50},initialG[1]]),[50,200],[50,150]);
named('move A to250',absolute([{...initial[0],y:250},initial[1]]),gaps([{...initialG[0],gap:250},initialG[1]]),[250,280],[250,350]);
named('resize A to80',absolute([{...initial[0],height:80},initial[1]]),gaps([{...initialG[0],height:80},initialG[1]]),[100,200],[100,260]);
named('resize A to120',absolute([{...initial[0],height:120},initial[1]]),gaps([{...initialG[0],height:120},initialG[1]]),[100,230],[100,300]);
named('source reorder B A',absolute([initial[1],initial[0]]),gaps([initialG[1],initialG[0]]),[200,230],[80,200]);
named('delete A',absolute([initial[1]]),gaps([initialG[1]]),[200],[80]);
assert.deepEqual(positions(moveAbsolute(initial,1,110).preview),[100,130]);
let feasible=0,moves=0,absoluteChanges=0,gapChanges=0;
const started=performance.now();
for(const ha of [10,20,30])for(const hb of [10,20,30])for(const hc of [10,20,30])
for(let a=0;a<=40;a+=10)for(let b=a+ha+10;b<=90;b+=10)for(let c=b+hb+10;c<=140;c+=10) {
const rows=[{id:'A',height:ha,y:a},{id:'B',height:hb,y:b},{id:'C',height:hc,y:c}];
assert.deepEqual(positions(absolute(rows)),[a,b,c]);
assert.deepEqual(positions(gaps(encodeGaps(rows))),[a,b,c]);
feasible++;
for(let i=0;i<3;i++)for(const delta of [-50,-20,0,10,40]) {
const requested=rows[i].y+delta, {candidate,preview}=moveAbsolute(rows,i,requested);
const independent=[];
for(let j=0;j<3;j++) {
const floor=j?independent[j-1]+rows[j-1].height+10:0;
independent.push(Math.max(j===i?requested:rows[j].y,floor));
}
assert.deepEqual(positions(preview),independent);
assert.deepEqual(positions(absolute(preview)),independent);
assert.deepEqual(positions(absolute(JSON.parse(JSON.stringify(candidate)))),independent);
const encoded=encodeGaps(preview);
assert.deepEqual(positions(gaps(encoded)),independent);
assert.deepEqual(candidate.map(r=>r.id),['A','B','C']);
for(let j=0;j<i;j++) assert.deepEqual(candidate[j],rows[j]);
absoluteChanges+=changeCount(rows,candidate,'y');
gapChanges+=changeCount(encodeGaps(rows),encoded,'gap');
moves++;
}
}
// Independent event/frame attachment probes: no actual Scene/activation engine.
const selfCall=absolute([{id:'call',y:250,height:24},{id:'reply',y:320,height:0}]);
assert.equal(selfCall[0].y+24,274);
assert.equal(selfCall[1].y,320);
const children=[{y:160,height:20},{y:260,height:20}];
const requiredFrameBottom=Math.max(...children.map(r=>r.y+r.height))+20;
assert.equal(requiredFrameBottom,300);
assert.equal(Math.max(300,requiredFrameBottom),300);
assert.equal(Math.max(220,requiredFrameBottom),300);
assert.deepEqual(children,[{y:160,height:20},{y:260,height:20}]);
assert.equal(220-90,130); // delayed header top from moved create receive and header height
const result={
scope:'Illustrative linear policy and anchor experiment; no production parser, REST, Scene, GUI or full fragment/lifecycle acceptance.',
separation, cases, feasibleLayouts:feasible, moveCases:moves,
changedAuthoredCoordinateRecords:{absolute:absoluteChanges,gapsWithInverse:gapChanges},
elapsedMs:performance.now()-started,
assertions:'passed',
interpretation:'Both policies express all enumerated feasible layouts. Absolute coordinates preserve unaffected positions with fewer authored coordinate changes under these fixtures. Choose no production schema from this alone; full scope/anchor/inverse and source lifecycle validation remain required.'
};
await writeFile(new URL('./results.json',import.meta.url),JSON.stringify(result,null,2)+'\n',{flag:'wx'});
export default result;