fix: resolve inStackingContext arity/semantics mismatch

- Change BUILTIN_PREDICATES[15] arity from 1 to 2 to match binary usage
  in the FOL compiler and topology engine
- Update evaluator to handle both unary (does element have stacking context)
  and binary (do both elements share the same stacking context) cases
- Binary comparison: both elements must have a valid stacking context
  AND their stackingContextOf values must match
- Aligns predicate semantics with the legacy engine in solver/topology.ts
  and the topology query layer's inStackingContext(graph, subj, ref?) API
This commit is contained in:
John Dvorak
2026-05-21 17:10:38 -07:00
parent 19559b658b
commit 5830d5861e
+11 -2
View File
@@ -80,7 +80,7 @@ export const BUILTIN_PREDICATES: PredicateDescriptor[] = [
{ name: 'atMost', arity: 1, domains: ['element'], requiredFacts: ['subject.primaryBox'] }, { name: 'atMost', arity: 1, domains: ['element'], requiredFacts: ['subject.primaryBox'] },
{ name: 'between', arity: 1, domains: ['element'], requiredFacts: ['subject.primaryBox'] }, { name: 'between', arity: 1, domains: ['element'], requiredFacts: ['subject.primaryBox'] },
{ name: 'clippedBy', arity: 2, domains: ['element', 'element'], requiredFacts: ['subject.clipChain', 'reference.clipChain'] }, { name: 'clippedBy', arity: 2, domains: ['element', 'element'], requiredFacts: ['subject.clipChain', 'reference.clipChain'] },
{ name: 'inStackingContext', arity: 1, domains: ['element'], requiredFacts: ['topology.stackingContextOf'] }, { name: 'inStackingContext', arity: 2, domains: ['element', 'element'], requiredFacts: ['topology.stackingContextOf'] },
{ name: 'separatedFrom', arity: 2, domains: ['element', 'element'], requiredFacts: ['subject.primaryBox', 'reference.primaryBox'] }, { name: 'separatedFrom', arity: 2, domains: ['element', 'element'], requiredFacts: ['subject.primaryBox', 'reference.primaryBox'] },
// Spatial alias predicates // Spatial alias predicates
{ name: 'beside', arity: 2, domains: ['element', 'element'], requiredFacts: ['subject.primaryBox', 'reference.primaryBox'] }, { name: 'beside', arity: 2, domains: ['element', 'element'], requiredFacts: ['subject.primaryBox', 'reference.primaryBox'] },
@@ -735,7 +735,16 @@ export const inStackingContextPredicate: PredicateEvaluator = {
return makePredicateResult('indeterminate'); return makePredicateResult('indeterminate');
} }
const sc = world.topology.stackingContextOf[subjectId - 1] ?? 0; const sc = world.topology.stackingContextOf[subjectId - 1] ?? 0;
const pass = sc > 0; const subjectHasSC = sc > 0;
if (tuple.length >= 2 && tuple[1] !== undefined && tuple[1] !== 0) {
const referenceId = tuple[1];
const refSC = world.topology.stackingContextOf[referenceId - 1] ?? 0;
const pass = subjectHasSC && refSC > 0 && sc === refSC;
return makePredicateResult(pass ? 'true' : 'false', { stackingContext: sc, referenceStackingContext: refSC }, [subjectId, referenceId]);
}
const pass = subjectHasSC;
return makePredicateResult(pass ? 'true' : 'false', { stackingContext: sc }, [subjectId]); return makePredicateResult(pass ? 'true' : 'false', { stackingContext: sc }, [subjectId]);
}, },
}; };