From 2bdda12030210efc6015852e8d8049279c94d75a Mon Sep 17 00:00:00 2001 From: John Dvorak Date: Fri, 22 May 2026 16:40:01 -0700 Subject: [PATCH] fix: getTopologyValue off-by-one bug in clause-based topology evaluators getTopologyValue used subject ID as direct array index (arr[subjectId] ?? 0), but solver subject IDs are 1-indexed while topology arrays are 0-indexed parallel arrays. This produced wrong results for any subjectId != position+1. Fixed by using world.subjects.ids.indexOf(subjectId) to map subject ID to array position, matching the predicate evaluator's getTopologyValueBySubject pattern. Affected callers: evaluateAttachedToScrollContainer, evaluateInStackingContext. The predicate-based evaluators in predicates.ts were already correct. --- packages/imhotep-solver/src/topology.ts | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/packages/imhotep-solver/src/topology.ts b/packages/imhotep-solver/src/topology.ts index 507f062..fd58627 100644 --- a/packages/imhotep-solver/src/topology.ts +++ b/packages/imhotep-solver/src/topology.ts @@ -33,11 +33,16 @@ function result( } /** - * Safely read a topology value indexed by subject id. - * Returns 0 when the subject is out of bounds (treated as "no relation"). + * Safely read a topology value for a given solver subject ID. + * + * Solver subject IDs are 1-indexed; topology arrays are 0-indexed parallel + * to world.subjects.ids. Returns 0 when the subject is not found (treated as + * "no relation"). */ -function getTopologyValue(arr: number[], subjectId: number): number { - return arr[subjectId] ?? 0; +function getTopologyValue(world: GeometryWorld, arr: number[], subjectId: number): number { + const idx = world.subjects.ids.indexOf(subjectId); + if (idx < 0 || idx >= arr.length) return 0; + return arr[idx]; } /** @@ -109,6 +114,7 @@ export function evaluateAttachedToScrollContainer( } const scrollContainerId = getTopologyValue( + world, world.topology.scrollContainerOf, subjectRef, ); @@ -139,7 +145,7 @@ export function evaluateInStackingContext( }); } - const sCtx = getTopologyValue(world.topology.stackingContextOf, subjectRef); + const sCtx = getTopologyValue(world, world.topology.stackingContextOf, subjectRef); if (referenceRef === undefined) { const pass = sCtx !== 0; @@ -152,7 +158,7 @@ export function evaluateInStackingContext( ); } - const rCtx = getTopologyValue(world.topology.stackingContextOf, referenceRef); + const rCtx = getTopologyValue(world, world.topology.stackingContextOf, referenceRef); const pass = sCtx !== 0 && sCtx === rCtx; return result(