v1.1.0: pooled runtime, 959 tests, production hardening (0 squash)
This commit is contained in:
@@ -0,0 +1,161 @@
|
||||
/**
|
||||
* Alignment evaluators:
|
||||
* alignedWith, centeredWithin
|
||||
*
|
||||
* Alignment is evaluated against border-box geometry in the shared frame.
|
||||
*/
|
||||
|
||||
import {
|
||||
type GeometryWorld,
|
||||
type ClauseDescriptor,
|
||||
type ClauseResult,
|
||||
} from './registry.js';
|
||||
|
||||
function findBoxIndex(world: GeometryWorld, subjectId: number): number {
|
||||
const { boxes } = world;
|
||||
for (let i = 0; i < boxes.boxId.length; i++) {
|
||||
if (boxes.subjectId[i] === subjectId) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
function getBorderRect(world: GeometryWorld, subjectId: number) {
|
||||
const idx = findBoxIndex(world, subjectId);
|
||||
if (idx === -1) return null;
|
||||
const b = world.boxes;
|
||||
return {
|
||||
left: b.borderLeft[idx],
|
||||
top: b.borderTop[idx],
|
||||
right: b.borderRight[idx],
|
||||
bottom: b.borderBottom[idx],
|
||||
};
|
||||
}
|
||||
|
||||
function result(
|
||||
clauseId: string,
|
||||
status: 'pass' | 'fail' | 'error',
|
||||
truth: 'determinate' | 'indeterminate',
|
||||
metrics?: Record<string, number>,
|
||||
witness?: Record<string, number | undefined>,
|
||||
): ClauseResult {
|
||||
return {
|
||||
clauseId,
|
||||
status,
|
||||
truth,
|
||||
metrics,
|
||||
witness: witness
|
||||
? {
|
||||
subjectId: witness.subjectId,
|
||||
referenceId: witness.referenceId,
|
||||
frameId: witness.frameId,
|
||||
}
|
||||
: undefined,
|
||||
};
|
||||
}
|
||||
|
||||
export function evaluateAlignedWith(
|
||||
world: GeometryWorld,
|
||||
clause: ClauseDescriptor,
|
||||
): ClauseResult {
|
||||
const { subjectRef, referenceRef, clauseId, options } = clause;
|
||||
if (subjectRef === undefined || referenceRef === undefined) {
|
||||
return result(clauseId, 'error', 'indeterminate', undefined, {
|
||||
subjectRef,
|
||||
referenceRef,
|
||||
});
|
||||
}
|
||||
|
||||
const sRect = getBorderRect(world, subjectRef);
|
||||
const rRect = getBorderRect(world, referenceRef);
|
||||
if (!sRect || !rRect) {
|
||||
return result(clauseId, 'error', 'indeterminate', undefined, {
|
||||
subjectRef,
|
||||
referenceRef,
|
||||
});
|
||||
}
|
||||
|
||||
const axis = (options?.axis as string) ?? 'centerY';
|
||||
const tolerance = (options?.tolerance as number) ?? 0;
|
||||
|
||||
let delta = 0;
|
||||
switch (axis) {
|
||||
case 'left':
|
||||
delta = Math.abs(sRect.left - rRect.left);
|
||||
break;
|
||||
case 'right':
|
||||
delta = Math.abs(sRect.right - rRect.right);
|
||||
break;
|
||||
case 'top':
|
||||
delta = Math.abs(sRect.top - rRect.top);
|
||||
break;
|
||||
case 'bottom':
|
||||
delta = Math.abs(sRect.bottom - rRect.bottom);
|
||||
break;
|
||||
case 'centerX':
|
||||
delta = Math.abs(
|
||||
(sRect.left + sRect.right) / 2 - (rRect.left + rRect.right) / 2,
|
||||
);
|
||||
break;
|
||||
case 'centerY':
|
||||
delta = Math.abs(
|
||||
(sRect.top + sRect.bottom) / 2 - (rRect.top + rRect.bottom) / 2,
|
||||
);
|
||||
break;
|
||||
default:
|
||||
delta = Math.abs(
|
||||
(sRect.top + sRect.bottom) / 2 - (rRect.top + rRect.bottom) / 2,
|
||||
);
|
||||
}
|
||||
|
||||
const pass = delta <= tolerance;
|
||||
return result(
|
||||
clauseId,
|
||||
pass ? 'pass' : 'fail',
|
||||
'determinate',
|
||||
{ delta, tolerance, axis: axis === 'centerY' ? 0 : axis === 'centerX' ? 1 : 2 },
|
||||
{ subjectId: subjectRef, referenceId: referenceRef },
|
||||
);
|
||||
}
|
||||
|
||||
export function evaluateCenteredWithin(
|
||||
world: GeometryWorld,
|
||||
clause: ClauseDescriptor,
|
||||
): ClauseResult {
|
||||
const { subjectRef, referenceRef, clauseId, options } = clause;
|
||||
if (subjectRef === undefined || referenceRef === undefined) {
|
||||
return result(clauseId, 'error', 'indeterminate', undefined, {
|
||||
subjectRef,
|
||||
referenceRef,
|
||||
});
|
||||
}
|
||||
|
||||
const sRect = getBorderRect(world, subjectRef);
|
||||
const rRect = getBorderRect(world, referenceRef);
|
||||
if (!sRect || !rRect) {
|
||||
return result(clauseId, 'error', 'indeterminate', undefined, {
|
||||
subjectRef,
|
||||
referenceRef,
|
||||
});
|
||||
}
|
||||
|
||||
const tolerance = (options?.tolerance as number) ?? 0;
|
||||
|
||||
const centerXSubject = (sRect.left + sRect.right) / 2;
|
||||
const centerYSubject = (sRect.top + sRect.bottom) / 2;
|
||||
const centerXRef = (rRect.left + rRect.right) / 2;
|
||||
const centerYRef = (rRect.top + rRect.bottom) / 2;
|
||||
|
||||
const deltaX = Math.abs(centerXSubject - centerXRef);
|
||||
const deltaY = Math.abs(centerYSubject - centerYRef);
|
||||
|
||||
const pass = deltaX <= tolerance && deltaY <= tolerance;
|
||||
return result(
|
||||
clauseId,
|
||||
pass ? 'pass' : 'fail',
|
||||
'determinate',
|
||||
{ deltaX, deltaY, tolerance },
|
||||
{ subjectId: subjectRef, referenceId: referenceRef },
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user