From 5830d5861ef6dcb9e680568c567760f9c8949a8d Mon Sep 17 00:00:00 2001 From: John Dvorak Date: Thu, 21 May 2026 17:10:38 -0700 Subject: [PATCH] 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 --- packages/imhotep-solver/src/predicates.ts | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/packages/imhotep-solver/src/predicates.ts b/packages/imhotep-solver/src/predicates.ts index 4723591..2920ece 100644 --- a/packages/imhotep-solver/src/predicates.ts +++ b/packages/imhotep-solver/src/predicates.ts @@ -80,7 +80,7 @@ export const BUILTIN_PREDICATES: PredicateDescriptor[] = [ { name: 'atMost', 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: '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'] }, // Spatial alias predicates { name: 'beside', arity: 2, domains: ['element', 'element'], requiredFacts: ['subject.primaryBox', 'reference.primaryBox'] }, @@ -735,7 +735,16 @@ export const inStackingContextPredicate: PredicateEvaluator = { return makePredicateResult('indeterminate'); } 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]); }, };