All findings · Lucene finding · LFV-1113 · spatial-strategies-inclusive-zero-bound-excludes-other-zero

BBoxStrategy and PointVectorStrategy miss a shape on a query edge at 0 when one zero is -0.0 and the other +0.0, so BBoxStrategy finds a point Intersecting a rectangle but not Within it

A point at longitude -0.0 (for example from WKT POINT(-0 5)) lies on the west edge of the rectangle from 0 to 10, yet BBoxStrategy IsWithin and PointVectorStrategy silently miss it while BBoxStrategy Intersects finds it. Every release that ships the strategies has it (PointVectorStrategy since 4.0.0, BBoxStrategy since 4.10.0); a zero bound covering both zeros fixes it.

Summary

Priority
Low, rank 424In every release that ships them, the experimental BBoxStrategy and PointVectorStrategy silently miss a shape that lies exactly on a query edge at 0 when the coordinate and the edge are zeros of opposite sign. It needs a -0.0 coordinate exactly on such an edge, a rare boundary value, so it is Low.
Impact
silent wrong results
Since
4.0.0 (as old as the strategies; current code from LUCENE-7094, 6.0.0)introduced 2012-02-20 · over 14 years
Affected
4.0.0 to 10.6.0 and main; not fixed upstream
Status
documented
Found by
semi-formal: a Z3 query for doubles that are equal but sort apart, then a caller grep and executed probes

AI-assisted page: this finding was first signalled by a semi-formal check (an exact check of the real jar, or a modelling hypothesis that a tool then witnessed) and then checked with AI tools; the checks it went through, and what each found, are under Prior reports, history and checks. Not yet triaged by a human. Snapshot of 2026-11-06.

The finding in 30 seconds

Both strategies turn the query rectangle into DoublePoint range queries. An inclusive bound is passed unchanged, and DoublePoint sorts -0.0 just below +0.0, so an edge at one zero leaves out a coordinate at the other zero, although spatial4j treats the two as the same coordinate.

Background: two zeros, two orders

  1. A Java double has two zeros, +0.0 and -0.0. The primitive comparison -0.0 == 0.0 is true, but Double.compare(-0.0, 0.0) is negative: in that order -0.0 comes first.
  2. spatial4j's relate of a rectangle and a point, which SpatialOperation.Intersects and IsWithin evaluate through for this pair, compares coordinates with primitive operators, so a point at x = -0.0 lies on a rectangle edge at x = 0.0. (spatial4j's equals uses Double.compare; see "Not part of this finding" below.)
  3. BBoxStrategy and PointVectorStrategy (module lucene/spatial-extras) index coordinates as DoublePoint fields and answer queries with DoublePoint.newRangeQuery, whose ranges are inclusive and "consistent with Double#compareTo".
Diagram of the bug: a point stored at longitude -0.0 lies on the west edge x = 0.0 of a query rectangle; spatial4j treats -0.0 and +0.0 as equal, but the DoublePoint range [0.0, 10] used by BBoxStrategy IsWithin and PointVectorStrategy sorts -0.0 just below +0.0 and excludes it, while BBoxStrategy Intersects, built from exclusive bounds, still finds it; widening the bound to [-0.0, 10] fixes it.
The point at -0.0 sits on the rectangle's edge at 0.0 for spatial4j, but the inclusive DoublePoint range starts at +0.0 and leaves it out; BBoxStrategy's exclusive Intersects bounds do not. Select the still to open it at full size. Generated with AI, checked against the code.

The finding, step by step

  1. Index one point at longitude -0.0, latitude 5 with BBoxStrategy and PointVectorStrategy, for example parsed from WKT POINT(-0 5). Java has two zeros, and -0.0 == 0.0 is true.
  2. Query the rectangle minX = 0, maxX = 10, minY = 0, maxY = 10. The point is on its west edge, and SpatialOperation.Intersects and IsWithin both evaluate to true. You expect 1 hit from each of the four queries.
  3. You get [1, 0, 0, 0]: only BBoxStrategy Intersects finds the point, with no error. Stored at +0.0, the same point is found by all four.
  4. Why: both strategies pass the edge 0.0 unchanged as an inclusive DoublePoint range bound, and DoublePoint sorts like Double.compareTo, where -0.0 comes just before +0.0, so -0.0 falls outside [0.0, 10]. BBoxStrategy Intersects uses only exclusive bounds, moved with Math.nextUp/nextDown, which treat both zeros alike.
  5. The same happens on the equator, in non-geo contexts, the other way round (+0.0 stored, -0.0 edge), for BBoxStrategy Contains and for PointVectorStrategy circles. Hits are only lost, never added.

Who is affected

Applications that use BBoxStrategy (IsWithin, Contains) or PointVectorStrategy (rectangle or circle queries), both @lucene.experimental, with default config. It needs a shape exactly on a query edge at x = 0 or y = 0 (prime meridian, equator, tile boundary) whose coordinate is -0.0 from caller data, such as WKT POINT(-0 5). Missed hits only; rare.

Mechanism, terms and numbers847 words

BBoxStrategy and PointVectorStrategy (module lucene/spatial-extras, both @lucene.experimental) answer a spatial query with DoublePoint range queries on the stored coordinates. An inclusive bound taken from the query rectangle is passed to DoublePoint.newRangeQuery unchanged, and DoublePoint ranges follow Double#compareTo, where -0.0 sorts just below +0.0. spatial4j, whose relate decides SpatialOperation.evaluate for a point and a rectangle, compares coordinates there as primitives, where -0.0 == 0.0. So a shape exactly on a query edge at x = 0 or y = 0 whose coordinate is the other zero silently drops out: a point stored at (-0.0, 5) against Rect(0.0, 10, 0, 10) gives hits [BBox Intersects 1, BBox IsWithin 0, PointVector Intersects 0, PointVector IsWithin 0] instead of [1, 1, 1, 1]. BBoxStrategy's Intersects uses only exclusive bounds, moved with Math.nextUp/Math.nextDown, which give the same double for both zeros, so it still finds the point: BBoxStrategy contradicts itself. The same miss affects BBoxStrategy Contains, PointVectorStrategy circle queries, the Y axis, non-geo contexts and the reverse direction. Only missed hits, never extra ones. Every release that ships the strategies has it (PointVectorStrategy since 4.0.0, BBoxStrategy since 4.10.0) through 10.6.0 and main. Widening a zero bound to [-0.0, +0.0] before the range query is built fixes it.

The mechanism (main e1faeba08bde, lucene/spatial-extras/.../spatial/)

  1. The contract. SpatialStrategy.makeQuery builds "a Query based principally on SpatialOperation and Shape" (SpatialStrategy.java:116). SpatialOperation's predicates are explained by "the source implementation of evaluate", and "Lucene-spatial makes no distinction of shape boundaries" (query/SpatialOperation.java:30-35). Intersects is relate(...).intersects() (:89-95); IsWithin is "CoveredBy ... (boundary-neutral)", relate == WITHIN || equals (:124-136).
  2. BBoxStrategy, inclusive bounds. makeWithin (bbox/BBoxStrategy.java:523) states "docMinX >= queryExtent.getMinX() ... docMaxX <= queryExtent.getMaxX()" (:526-527) and builds inclusive ranges (:531-532, :546-547); makeContains (:283) is the mirror (:286-287, :291-292, :304-305).
  3. BBoxStrategy, the range helper makeNumericRangeQuery (:651-672): a null bound becomes ±infinity (:654-660); an exclusive lower bound becomes Math.nextUp(min) (:663), an exclusive upper bound Math.nextDown(max) (:667), then DoublePoint.newRangeQuery(fieldname, min, max) (:670). An inclusive bound is passed unchanged. Fix site. Every caller passes its null side as exclusive, so that side ends at -/+Double.MAX_VALUE.
  4. BBoxStrategy Intersects is "has an envelope AND NOT disjoint" (makeIntersects, :473, :491-494), and makeDisjoint (:366) uses only exclusive bounds, e.g. docMaxX < query minX (:393). For the edge 0.0 that range is [-Double.MAX_VALUE, -4.9E-324] (printed bbox__maxX:[-1.7976931348623157E308 TO -4.9E-324]), which holds neither zero, so the point is not disjoint and is found. Math.nextUp/nextDown return the same double for -0.0 and +0.0.
  5. PointVectorStrategy. makeQuery (vector/PointVectorStrategy.java:188-205) answers both Intersects and IsWithin of a rectangle with makeWithin(bbox) (:194), and a circle with makeWithin(circle.getBoundingBox()) plus a distance filter (:196-198). makeWithin (:209-222) adds MUST ranges on x and y via rangeQuery (:228-238), which passes the bounds unchanged to DoublePoint.newRangeQuery (:238). Fix site.
  6. The order. DoublePoint.newRangeQuery: "Ranges are inclusive ... Range comparisons are consistent with Double#compareTo(Double)" (lucene/core/.../document/DoublePoint.java:210-213). So [0.0, 10] excludes a stored -0.0, and [-10, -0.0] excludes a stored +0.0.

Worked example (the repro)

Point (-0.0, 5), query Rect(minX=0.0, maxX=10, minY=0, maxY=10):

  • BBox IsWithin needs docMinX in [0.0, Double.MAX_VALUE] (printed bbox__minX:[0.0 TO 1.7976931348623157E308]): -0.0 sorts below 0.0, so 0 hits.
  • PointVector, both operations, needs x in [0.0, 10.0]: 0 hits.
  • BBox Intersects needs the point to be outside every disjoint range, e.g. docMaxX in [-Double.MAX_VALUE, -4.9E-324] or docMinX in [10.000000000000002, Double.MAX_VALUE]: -0.0 is in none of them, so 1 hit.

More executed views

  • Same point, other zero (control, default IndexWriterConfig, main and 10.6.0): makePoint(0.0, 5) gives [1, 1, 1, 1]; makePoint(-0.0, 5) and WKT POINT(-0 5) give [1, 0, 0, 0].
  • Parsed query strings: a point at longitude -0.0, latitude 51.5 is missed by IsWithin(ENVELOPE(0, 180, 90, -90)) (the eastern hemisphere) in both strategies and by Intersects(ENVELOPE(0, 20, 60, 0)) in PointVectorStrategy; RecursivePrefixTreeStrategy finds it. The western neighbour cell still finds the point, so it drops out on one side only.
  • Circles: Circle(Pt(5, 0), 5 deg) has a spatial4j-computed bounding box with west edge +0.0; PointVectorStrategy misses a point at (-0.0, 0) that lies on the circle.
  • Bounded-exhaustive sweep (exhaustive over a small domain: coordinates {-10, -0.0, +0.0, 10}, 121 query rectangles, 137 shapes, 5 operations, 4 strategies, geo and non-geo; plus 7 selected circles; 439,082 checks): 6,120 zero-sign defects, all missed matches (per context BBox IsWithin 1,184, BBox Contains 1,313, PointVector Intersects 276 and IsWithin 276, circles 5-6 per operation); 0 for BBox Intersects / IsDisjointTo / IsEqualTo, RecursivePrefixTreeStrategy and SerializedDVStrategy; 0 with the fix.

Not part of this finding: IsEqualTo (an exact match, which agrees with spatial4j's own Double.compare-based equals); IsWithin of two rectangles identical up to a zero's sign (spatial4j's equals again); BBoxStrategy Contains / IsEqualTo extra hits for non-rectangles, which occur without any zero because other shapes are indexed by their bounding box (BBoxStrategy.java:54-55).

Exposure and likelihood394 words
  • Who is hit. Applications that index points or rectangles with BBoxStrategy and query with IsWithin or Contains, or with PointVectorStrategy and query a rectangle (Intersects or IsWithin) or a circle. Both strategies are @lucene.experimental (BBoxStrategy.java:71, PointVectorStrategy.java:80); otherwise default codec, IndexWriterConfig and searcher, any index size, geo and non-geo contexts.
  • When. A shape exactly on a query edge at x = 0 or y = 0 (the prime meridian, the equator, a hemisphere or tile boundary, or a 0 bound in a non-geo context), with -0.0 on one side and +0.0 on the other. The query edge is usually an ordinary +0.0; -0.0 comes from caller data, for example WKT or text such as -0 or -0.0, a negated 0.0 (a 0-degree "W" longitude stored as -lon), or Math.ceil of a small negative number. The WKT reader keeps the sign, in POINT(-0 5) and in ENVELOPE(-10, -0, 10, 0). In the hunt's producer search none of 4,114 spatial4j circle bounding boxes had a -0.0 edge, but spatial4j's pointOnBearing returned x = -0.0 in 88 of 82,280 calls, all from 8 start points on longitude 180 (2 of them the poles), e.g. from (180, 10) with bearing 0 and distance 90: another way -0.0 can reach caller data.
  • Size of the error. Only missed matches, never extra ones: a raw inclusive bound at one zero only leaves out a coordinate at the other zero, and every inclusive range sits in MUST or SHOULD clauses (BBoxStrategy's only MUST_NOT clauses, :390, :398, :494, hold term queries on ±180 and the exclusive-only disjoint query; PointVectorStrategy has none). In the sweep all 6,120 zero-sign defects were misses. The shape drops out on one side of the edge only; the neighbouring cell still finds it.
  • Silent. No exception and no assertion, with assertions on and off.
  • Other strategies are fine. In the point-on-edge cases tested, RecursivePrefixTreeStrategy and SerializedDVStrategy in the same module return the hit, and so do the core geo queries: the XYShape and LatLonShape box queries and the XYPointField polygon query (48 checks, 0 mismatches), and LatLonPoint.newBoxQuery, LatLonPoint.newPolygonQuery and XYPointField.newBoxQuery (32 checks, 0 mismatches, run by this doc's fact check).
  • Likelihood: rare. It needs an exact -0.0 on exactly a zero edge, in two experimental strategies; the searches recorded on 2026-11-02 found no report of it since 4.0.0 (2012).

Minimal repro

One JUnit test for lucene/spatial-extras, public API only: index one point at (-0.0, 5) with both strategies, check that IsWithin.evaluate is true for the rectangle 0..10 by 0..10, then count the four queries. Deterministic, single thread; fails with assertions on and off.

TestNegativeZeroOnZeroEdge.java (lucene/spatial-extras, public API)64 lines
/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.lucene.spatial;

import java.util.Arrays;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
import org.apache.lucene.index.DirectoryReader;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.spatial.bbox.BBoxStrategy;
import org.apache.lucene.spatial.query.SpatialArgs;
import org.apache.lucene.spatial.query.SpatialOperation;
import org.apache.lucene.spatial.vector.PointVectorStrategy;
import org.apache.lucene.store.Directory;
import org.apache.lucene.tests.util.LuceneTestCase;
import org.locationtech.spatial4j.context.SpatialContext;
import org.locationtech.spatial4j.shape.Point;
import org.locationtech.spatial4j.shape.Rectangle;

public class TestNegativeZeroOnZeroEdge extends LuceneTestCase {

  public void testPointAtNegativeZeroOnZeroEdge() throws Exception {
    SpatialContext ctx = SpatialContext.GEO;
    Point point = ctx.makePoint(-0.0, 5);
    Rectangle rect = ctx.makeRectangle(0.0, 10, 0, 10);
    assertTrue(SpatialOperation.IsWithin.evaluate(point, rect));
    SpatialStrategy bbox = BBoxStrategy.newInstance(ctx, "bbox");
    SpatialStrategy pv = PointVectorStrategy.newInstance(ctx, "pv");
    try (Directory dir = newDirectory()) {
      try (IndexWriter w = new IndexWriter(dir, newIndexWriterConfig())) {
        Document doc = new Document();
        for (Field f : bbox.createIndexableFields(point)) doc.add(f);
        for (Field f : pv.createIndexableFields(point)) doc.add(f);
        w.addDocument(doc);
      }
      try (DirectoryReader r = DirectoryReader.open(dir)) {
        IndexSearcher s = new IndexSearcher(r);
        int[] hits = {
          s.count(bbox.makeQuery(new SpatialArgs(SpatialOperation.Intersects, rect))),
          s.count(bbox.makeQuery(new SpatialArgs(SpatialOperation.IsWithin, rect))),
          s.count(pv.makeQuery(new SpatialArgs(SpatialOperation.Intersects, rect))),
          s.count(pv.makeQuery(new SpatialArgs(SpatialOperation.IsWithin, rect)))
        };
        // hits of BBox Intersects, BBox IsWithin, PointVector Intersects, PointVector IsWithin
        assertEquals("[1, 1, 1, 1]", Arrays.toString(hits));
      }
    }
  }
}
Command
cp $REPRO/TestNegativeZeroOnZeroEdge.java $LUCENE/lucene/spatial-extras/src/test/org/apache/lucene/spatial/
cd $LUCENE && ./gradlew :lucene:spatial-extras:test --tests TestNegativeZeroOnZeroEdge -Ptests.seed=5EED1113 -Ptests.asserts=true
# also with -Ptests.asserts=false; 5 of 5 random-seed iterations (-Ptests.iters=5) failed the same way
Expected
[1, 1, 1, 1]: the point is on the rectangle's west edge, so all four queries find it.
Observed
[1, 0, 0, 0], no error: expected:<[1, [1, 1, 1]]> but was:<[1, [0, 0, 0]]>, with assertions on and off.

All files of this repro: repro/LFV-1113/

More files, environment, full output236 words

The test is the hunt's minimal repro with the license header added and its one comment reworded so that it passes :lucene:spatial-extras:check -x test (google-java-format); the code is otherwise identical. It uses newIndexWriterConfig(), the usual randomized test config; a plain IndexWriterConfig fails the same way (the independent reproducer's test and this doc's control). Executed: seed 5EED1113 (codec Asserting(Lucene104)), 5 random-seed iterations and the plain config; not every codec was run, and no codec dependence is expected because the order comes from DoublePoint's sortable encoding of the double (DoublePoint.java:176).

Executed: gradle on main e1faeba08bde with assertions on and off (seed 5EED1113) and 5 iterations with a random master seed: fails every time; with the fix it passes. The hunt's runs fail the same way.

Use the same test as the regression test.

Environment

lucenemain e1faeba08bde (12.0.0-SNAPSHOT)
jdkOpenJDK 25.0.3 (build 25.0.3+9-LTS)
osLinux, aarch64 (only this platform was run)
Command (environment)
cp $REPRO/TestNegativeZeroOnZeroEdge.java $LUCENE/lucene/spatial-extras/src/test/org/apache/lucene/spatial/
cd $LUCENE && ./gradlew :lucene:spatial-extras:test --tests TestNegativeZeroOnZeroEdge -Ptests.seed=5EED1113 -Ptests.asserts=true
# also with -Ptests.asserts=false; 5 of 5 random-seed iterations (-Ptests.iters=5) failed the same way

Observed output

TestNegativeZeroOnZeroEdge > testPointAtNegativeZeroOnZeroEdge FAILED
    org.junit.ComparisonFailure: expected:<[1, [1, 1, 1]]> but was:<[1, [0, 0, 0]]>
        at __randomizedtesting.SeedInfo.seed([5EED1113:6E82DED7DFEFBC3D]:0)
        at app/junit@4.13.2/org.junit.Assert.assertEquals(Assert.java:117)
        at app/junit@4.13.2/org.junit.Assert.assertEquals(Assert.java:146)
        at app//org.apache.lucene.spatial.TestNegativeZeroOnZeroEdge.testPointAtNegativeZeroOnZeroEdge(TestNegativeZeroOnZeroEdge.java:60)
:lucene:spatial-extras:test (FAILURE): 1 test, 1  failure

# JUnit's diff brackets: expected "[1, 1, 1, 1]", actual "[1, 0, 0, 0]"
# with the fix: :lucene:spatial-extras:test (SUCCESS): 1 test

Why it looks unintended

  • For a point, Intersects and IsWithin a rectangle are the same question, yet BBoxStrategy answers 1 and 0 for the same document.
  • The strategies promise SpatialOperation predicates, defined by evaluate(), which is true here and is the oracle of the module's own tests.
  • RecursivePrefixTreeStrategy, SerializedDVStrategy and the core geo point and shape queries all find such a point.
The full argument214 words
  • Contract. BBoxStrategy "supports all SpatialOperations" (BBoxStrategy.java:47), qualified at :57 as "Supports most SpatialOperations but not Overlaps", and its makeQuery maps Intersects, IsWithin and Contains to their own queries (:265-271); PointVectorStrategy: "Intersects and IsWithin is supported" (PointVectorStrategy.java:63-64). The query is built "principally on SpatialOperation" (SpatialStrategy.java:116), whose predicates are defined by evaluate() and are boundary-neutral (SpatialOperation.java:30-35, :124). BBoxStrategy's own comments state the Within and Contains conditions as plain comparisons, docMinX >= queryExtent.getMinX() (:526-527, :286-287), which hold for -0.0 against 0.0. Nothing says -0.0 is a different position from 0.0.
  • Tests. StrategyTestCase.testOperation asserts operation.evaluate(indexedShape, queryShape) == match before it searches (StrategyTestCase.java:233), and RandomSpatialOpStrategyTestCase uses operation.evaluate as truth (prefix/RandomSpatialOpStrategyTestCase.java:103). TestBBoxStrategy.testAlongDatelineOppositeSign and testWithinBugDatelineEdge require IsWithin to match when one edge is written as +180 and as -180 (bbox/TestBBoxStrategy.java:154, :126): the same kind of equivalence. No test in the module uses -0.0.
  • History. The strategies built inclusive ranges from raw bounds from the start (2012). LUCENE-7094 (#8150, 2016-03-30) moved them to DoublePoint ranges and to Math.nextUp/nextDown for exclusive bounds, which created the Intersects-vs-Within split; its discussion never mentions zeros. LUCENE-7594 (#8645, 2016-12-21), nine months later, set the Double.compare guidance for core point ranges and never mentions spatial-extras.
The devil's-advocate check195 words

The strongest "intended" case (built by the intended-behaviour verifier; an intended-lens cross-model review, OpenAI GPT-5.6, rated it moderate): DoublePoint documents Double#compareTo order on purpose, and LUCENE-7594's reporter wrote that Float/Double points "are supposed to be consistent with Double/Float.compare, so +0 is supposed to compare greater than -0"; both strategies say they are built on DoublePoint ranges; spatial4j's RectangleImpl.equals is sign-sensitive too; the strategies are experimental, and callers could normalize -0.0.

Why it fails: DoublePoint order is the encoding the strategies use, not the contract they promise. Users call SpatialOperation, whose predicates are evaluate(), and the module's tests use evaluate as truth; RPT, SerializedDV and the core geo queries find a point on the zero edge. The sign-sensitive equals is why IsEqualTo is not part of this finding; a point against a rectangle goes through relate, which compares primitives. "Experimental" allows API changes, not missed hits, and nothing documents a normalization rule while the WKT reader keeps -0.0. Decisive: BBoxStrategy answers Intersects and IsWithin of a point differently for the same document. Switching its exclusive bounds to DoublePoint.nextUp would remove the contradiction only by making Intersects miss too. Verdict: not intended, a defect.

Suggested fix

Recommended Widen a zero bound to [-0.0, +0.0] (fix A)

Make a zero range bound cover both zeros, right before DoublePoint.newRangeQuery in BBoxStrategy.makeNumericRangeQuery and PointVectorStrategy.rangeQuery: if (min == 0) min = -0d; if (max == 0) max = 0d;. The repro then passes, and so do the whole lucene/spatial-extras test suite (118 tests) and :lucene:spatial-extras:check -x test.

      // spatial4j compares coordinates as primitives (-0.0 == 0.0) but DoublePoint orders -0.0
      // below 0.0, so a zero bound must cover both zeros
      if (min == 0) {
        min = -0d;
      }
      if (max == 0) {
        max = 0d;
      }

      return DoublePoint.newRangeQuery(fieldname, min, max); // fieldName in PointVectorStrategy
All options and trade-offs4 options · 648 words

One fix was built and validated in a scratch worktree of e1faeba08bde; the alternatives are listed with their trade-offs.

Widen a zero bound to [-0.0, +0.0] (fix A) (validated by the checks) (recommended)

The same lines in both methods, just before the return DoublePoint.newRangeQuery(...). min and max are Double, so min == 0 unboxes and is true for both zeros; a lower bound of either zero becomes -0.0 and an upper bound +0.0, so a zero edge includes both. Exclusive bounds were already moved off zero by nextUp/nextDown, so they are unaffected. The exact-match path used by IsEqualTo and the dateline clauses is left alone.

      // spatial4j compares coordinates as primitives (-0.0 == 0.0) but DoublePoint orders -0.0
      // below 0.0, so a zero bound must cover both zeros
      if (min == 0) {
        min = -0d;
      }
      if (max == 0) {
        max = 0d;
      }

      return DoublePoint.newRangeQuery(fieldname, min, max); // fieldName in PointVectorStrategy

Trade-offs

Results change only on a zero edge: on the inclusive range paths the patch changes, a -0.0 input now gets the answer of its +0.0 twin (the exact-term path used by IsEqualTo is unchanged and still tells the zeros apart, as spatial4j's equals does), so BBoxStrategy's documented bounding-box extra hits for non-rectangles under Contains also appear for those twins (no new kind of mismatch). No index-format change; documents already stored with -0.0 are found without reindexing. One comparison per bound (two per range query) at query-build time.

What the checks validated

  • Gradle, JDK 25, scratch worktree of e1faeba08bde (removed afterwards): the test fails on stock with assertions on and off (seed 5EED1113) and in 5 of 5 random-seed iterations, and passes with the fix, assertions on and off.
  • With the fix: the test plus TestBBoxStrategy and TestPointVectorStrategy, 17 tests, 0 failures, 1 skipped; the whole :lucene:spatial-extras:test, 118 tests including the new one, 0 failures, 5 skipped (on stock: the same 118 tests with exactly 1 failure, the new test); :lucene:spatial-extras:check -x test passes (google-java-format, ecj lint and forbidden APIs ran; eclint was not configured and Error Prone was skipped).
  • Which copy of the test: the 5 iterations, the 17-test and 118-test runs and the fixed run with assertions off used an earlier copy whose only difference was one over-long comment line; the check, the fixed run with assertions on and the stock runs on and off used the final copy. The fact check re-ran the final copy through gradle: stock fails on and off, fixed passes on and off, and the whole fixed module passes (118 tests, 5 skipped).
  • The hunt: the minimal repro and the mirror-direction test pass with the fix shadowed, the cross-model reviewers' own tests pass with it, and the bounded-exhaustive sweep has 0 zero-sign defects.

Normalize -0.0 to +0.0 at index and query time

Add 0.0 to every indexed coordinate in createIndexableFields and to every query bound (x + 0.0 turns -0.0 into +0.0).

Trade-offs

Fixes new documents only: those already indexed with -0.0 stay invisible on a 0 edge until reindexed, and it touches more code paths (stored values, doc values, distance sources). Not validated.

Make the exclusive bounds follow Double.compare

Use DoublePoint.nextUp/nextDown instead of Math.nextUp/nextDown in BBoxStrategy.makeNumericRangeQuery, as LUCENE-7594 recommends for core ranges.

Trade-offs

Not recommended: it removes the contradiction only by making Intersects miss the point too, so both strategies would still disagree with SpatialOperation.evaluate, RPT and SerializedDV. Not validated. A side note: DoublePoint's 1-D newRangeQuery javadoc names DoublePoint.nextUp/nextDown for exclusive ranges (DoublePoint.java:210-211), while the n-dimensional overload's javadoc still says Math#nextUp / Math.nextDown (:232-233).

Document that coordinates must not be -0.0

State the restriction in the class javadocs.

Trade-offs

Not recommended: it legitimises silent missed hits that a small change removes, and the WKT reader produces -0.0 from ordinary input. Not validated.

How it was found

A Z3 query asked which doubles are equal under == but sort apart under Double.compare; the only answers are the two zeros. A grep for code that hands spatial4j bounds to DoublePoint ranges found these two strategies, and executed probes on main and 10.6.0 showed the missed hits. The hunt unit that judged it then widened and confirmed it.

The method in full170 words

Method: semi-formal (a Z3 witness for a generic fact about doubles, which became a Lucene lead through a caller grep and an executed probe).

  • Round 22 of the hunt ran a numeric-casts lens with Z3. One of its queries asked for non-NaN doubles x <= q under primitive comparison (the semantics spatial4j's relate uses) whose DoublePoint order is x > q; the only witnesses are x = +0.0, q = -0.0. A grep for callers that pass spatial4j bounds straight to DoublePoint ranges found only BBoxStrategy and PointVectorStrategy outside the lucene/sandbox module and the query parsers. Public-API probes on main and 10.6.0 (2026-11-02) showed the missed hits, and a core-geo control showed none.
  • The round's skeptic re-ran it. The hunt unit that judged it wrote a public-API JUnit, a bounded-exhaustive differential sweep with a sign-erasure oracle (439,082 checks), a producer search for -0.0 and a history port. An independent reproducer, cross-model reviews (OpenAI GPT-5.6, executed), a devil's-advocate verifier, an adjudication, an audit and a last minimal-repro check then re-ran it.

Prior reports, history and checks

Prior reports

No public report found in the searches recorded on 2026-11-02: GitHub issues and PRs incl. the migrated JIRA archive, JIRA (LUCENE, SOLR), the Lucene and Solr mailing lists, Solr, Elasticsearch, OpenSearch, lucenenet, spatial4j, the web and the open PRs. Nearest: #8645 (DoublePoint follows Double.compare), #8150 (the 2016 points cutover), #6833 (boundary-neutral predicates).

Searches4 searches

None of the hits is the same bug. Other nearest items: #8240 (LUCENE-7185, open: a comment suggests generating -0.0 in core random geo tests) and elastic/elasticsearch#35707 (2018: a downstream geo layer normalizes coordinates so they never return -0.0 before building Lucene geometry). Not fixed upstream: the newest commits on both strategy files, bc7deedc092 (#15499) and dfd536186fe (#15889), are cosmetic and in e1faeba08bde; none of the 16 open apache/lucene PRs updated since 2026-10-31, checked file by file on 2026-11-02, touches them or DoublePoint.java (a snapshot). Search limits: keyword search only; GitHub search returns at most 100 results per query; private reports are not visible.

What was searched, when, and what turned up
WhereWhenHowFound
GitHub issues, PRs and commits (apache/lucene incl. the migrated JIRA archive; apache/solr, lucenenet, Elasticsearch, OpenSearch, locationtech/spatial4j)2026-11-02a scripted prior-report search (71 queries: edge, boundary, Contains, circle, prime meridian, equator, -0.0, signed and negative zero, method and class names) on top of the finder's 51 queries; every apache/lucene item naming BBoxStrategy (20) or PointVectorStrategy (9) read or grepped; 4 more GitHub search queries in a same-day re-checkno report
Open PRs and upstream commits2026-11-02the changed files of the 16 open apache/lucene PRs updated since 2026-10-31 and all PRs created since; git log on both strategy filesonly #16847 touches spatial-extras, in other files; newest commits on the strategy files are cosmetic
JIRA, mailing lists and the web2026-11-02the LUCENE entries naming BBoxStrategy (21) or PointVectorStrategy (10) and the SOLR entries naming BBoxField (19); dev@, java-user@, issues@, commits@ and the Solr lists; web queriesno report
Nearest items (read)2026-11-02#8645 (LUCENE-7594), #8150 (LUCENE-7094), #6833 (LUCENE-5771), #8240 (LUCENE-7185), elastic/elasticsearch#35707the DoublePoint ordering decision, the points cutover, boundary-neutral semantics, a test-generation suggestion and a downstream normalization; none reports the miss

History

As old as the strategies: PointVectorStrategy since 4.0.0 (2012), BBoxStrategy since 4.10.0. Every executed release fails: 4.x and 5.x miss all four queries (legacy trie order); LUCENE-7094 (#8150, 6.0.0) moved to DoublePoint and made BBox Intersects find the point, creating the split. Not a regression; not fixed upstream.

Versions tested7 rows

Origin: PointVectorStrategy (then TwoDoublesStrategy) built an inclusive range from the raw rectangle bounds from its first commit, 66a852234d4 (2012-02-20, LUCENE-3795 / #4868); BBoxStrategy came with 26d3f1f813e (LUCENE-4175 / #5247, 2012-06-28) and reached a release through the branch_4x port c608265684c (2014-07-09). Today's lines: e1b45568b41 on master and f9da8164483 on branch_6_0 (LUCENE-7094 / #8150, 2016-03-30), which moved both strategies to DoublePoint.newRangeQuery with raw inclusive bounds and Math.nextUp/nextDown for exclusive ones; git blame on main attributes the range lines to it, later commits only reformat. Releases between the executed ones were not run individually; the range code is unchanged between them apart from formatting.

Versions and branches executed, with results
Version or commitVerdictResult
main e1faeba08bde affectedthis doc's test fails through gradle with assertions on and off; passes with the fix
10.6.0 affected[1, 0, 0, 0] (the reproducer's JUnit and this doc's control; +0.0 gives [1, 1, 1, 1])
10.5.2, 9.12.3 affected[1, 0, 0, 0] (the reproducer's JUnit, release jars)
6.0.0, 6.6.6, 7.7.3, 8.11.4, 9.0.0, 10.0.0 affected[1, 0, 0, 0] (main() port)
6.0.0, legacy trie encoding control[0, 0, 0, 0]: same spatial4j jar, so the 6.0 change in Intersects comes from the switch to DoublePoint
4.10.0, 4.10.4, 5.0.0, 5.5.5 affected[0, 0, 0, 0] (legacy trie order splits the zeros on every path)
4.0.0, 4.9.1 first releasePointVectorStrategy [0, 0]; BBoxStrategy not shipped yet

Independent checks

An independent reproducer, two executed cross-model reviews (both OpenAI GPT-5.6, with a refute and an intended lens), a devil's-advocate verifier, an adjudication, an audit and a last minimal-repro check reproduced it with their own tests and agree on the root cause. This doc's gradle runs reproduced it on main and validated the fix.

Checks and verdicts414 words

Every executed check saw the miss come from the inclusive DoublePoint bounds, and every one that ran the fix saw it pass. The cross-model reviews ran on an earlier main checkout, ea57360912ed, whose involved source files are identical to e1faeba08bde's.

Each independent check and its verdict
CheckVerdictWhat it found
first signal (round 22) missed hitsZ3 witness (only the two zeros), a caller grep, and public-API probes on main and 10.6.0 with misses in both strategies; a core-geo control (XYShape and LatLonShape box queries, XYPointField polygon query) had 0 mismatches.
finder reproducedMirror-direction JUnit (point at +0.0, edge at -0.0) fails on main (on, off), 10.6.0 and 9.12.3, passes with the fix; sweep 6,120 missed matches, 0 with the fix; history port back to 4.0.0.
cross-model review, refute lens real (high)OpenAI GPT-5.6, executed: its own equator test (latitude +0.0, query maximum -0.0) gives BBox Intersects 1, BBox IsWithin 0, PointVector IsWithin 0; the same-sign control passes; the fix passes it.
independent reproducer realOwn test with a plain IndexWriterConfig fails [1, 0, 0, 0] on main (on, off), 10.6.0, 10.5.2 and 9.12.3, passes with its own fix; probes for the equator, non-geo, a circle and Contains, each with a same-sign control.
cross-model review, intended lens real (high)OpenAI GPT-5.6, executed: its own test misses in BBox IsWithin and both PointVector queries while SerializedDV finds the point; the same-sign control finds it everywhere; the intended argument is moderate and outweighed.
devil's advocate realTested the DoublePoint order and LUCENE-7594, spatial4j's equals and the experimental status; none survives. Parsed-query probe: IsWithin(ENVELOPE(0, 180, 90, -90)) misses a point at longitude -0.0 in both strategies; RPT finds it.
adjudication confirmed (high), LowRe-ran the independent reproduction's test on main (on, off) and 10.6.0: fails; with the fix: passes; the finder's test fails too. Silent wrong results, security none.
audit and last minimal-repro check confirmedThe audit re-ran it on main (off) and confirmed it; the last check reduced the repro to one test that fails on main with assertions on and off and passes with the fix.
this doc reproduced; fix validatedGradle on main e1faeba08bde: the test fails with assertions on and off and in 5 random iterations; with the fix it passes, the whole lucene/spatial-extras suite passes (118 tests) and :lucene:spatial-extras:check -x test passes. A control on main and 10.6.0 finds the point at +0.0 and misses it at -0.0.

Diagram file

The diagram as a still image, generated with AI and checked against the code. It is under 10 MB, the limit for a GitHub attachment.

Sources and provenance

Finding record, Lucene and upstream sources, repro files and diagram notes1,536 words

Finding record

LFV-1113, snapshot of 2026-11-06

Registry title
BBoxStrategy and PointVectorStrategy miss a shape on a query edge at 0 when the shape's coordinate and the edge are zeros of opposite sign (-0.0 vs +0.0), so BBoxStrategy finds a point Intersecting a rectangle but not Within it
Area
spatial-extras / BBoxStrategy and PointVectorStrategy query construction (inclusive DoublePoint range bounds taken unchanged from spatial4j rectangles: BBoxStrategy.makeNumericRangeQuery, PointVectorStrategy.rangeQuery)
Priority
Low, rank 424: In every release that ships them, the experimental BBoxStrategy and PointVectorStrategy silently miss a shape that lies exactly on a query edge at 0 when the coordinate and the edge are zeros of opposite sign. It needs a -0.0 coordinate exactly on such an edge, a rare boundary value, so it is Low.
Impact class
silent wrong results
Impact note
Applications that index points or rectangles with the @lucene.experimental BBoxStrategy (IsWithin, Contains) or PointVectorStrategy (rectangle Intersects and IsWithin, and circle queries through their bounding box) silently lose a document that lies exactly on a query edge at x = 0 or y = 0 (the prime meridian, the equator, or a 0 bound in a non-geo context) when the document's coordinate and the edge are zeros of opposite sign, one -0.0 (from caller data such as WKT 'POINT(-0 5)' or a negated 0.0) and the other +0.0, at any index size with the default IndexWriterConfig, codec and searcher, in geo and non-geo contexts, in every release that ships them (PointVectorStrategy since 4.0.0, BBoxStrategy since 4.10.0); only missed matches, never extra ones.
Summary
You index a point stored at longitude -0.0 (for example parsed from 'POINT(-0 5)') with BBoxStrategy and PointVectorStrategy and query the rectangle Rect(minX=0.0, maxX=10, minY=0, maxY=10); you expect the point to match Intersects and IsWithin in both strategies, because SpatialOperation.evaluate says it lies on the rectangle's west edge (spatial4j compares coordinates as primitives, so -0.0 == 0.0). Instead BBoxStrategy finds it for Intersects but not for IsWithin, and PointVectorStrategy finds it for neither, with no error. Both strategies pass the rectangle's 0.0 edge unchanged as an inclusive DoublePoint range bound, and DoublePoint orders -0.0 below +0.0; BBoxStrategy's Intersects uses only exclusive bounds, which it moves with Math.nextUp/Math.nextDown, and those give the same result for both zeros.
Introduced
As old as the strategies, not a regression. PointVectorStrategy: 66a852234d4 (2012-02-20, David Wayne Smiley; initial copy of the spatial-lucene module, LUCENE-3795 / #4868; the class was then TwoDoublesStrategy, renamed by e9cffebf96f, LUCENE-4374), whose makeWithin built an inclusive NumericRangeQuery.newDoubleRange from the raw rectangle bounds. BBoxStrategy: 26d3f1f813e (LUCENE-4175 / #5247, 2012-06-28, Ryan McKinley), inclusive raw bounds in makeWithin / makeContains, released through the branch_4x port c608265684c (LUCENE-5714 / #6776 and LUCENE-5779, 2014-07-09). Today's lines and BBoxStrategy's Intersects-vs-IsWithin contradiction: e1b45568b41 on master and f9da8164483 on the 6.0 branch (LUCENE-7094 / #8150, 2016-03-30, nknize), which moved both strategies to DoublePoint.newRangeQuery with raw inclusive bounds and Math.nextUp/Math.nextDown for exclusive ones. [read: git blame on main attributes the range lines to e1b45568b41, later commits only reformat; git log -S 'Math.nextUp' and 'DoublePoint.newRangeQuery' on both files find only e1b45568b41; exec: every release that ships the strategies fails, and a 6.0.0 legacy-trie control (same spatial4j jar) misses on Intersects too, so the Intersects change at 6.0.0 comes from the switch to DoublePoint] (2012-02-20 (66a852234d4, PointVectorStrategy); BBoxStrategy 2012-06-28 on trunk (26d3f1f813e) and 2014-07-09 on branch_4x (c608265684c); current form 2016-03-30 (e1b45568b41); author dates equal commit dates for all cited commits)
First affected version
4.0.0 [exec: the release-portable port misses with PointVectorStrategy on 4.0.0, -da; BBoxStrategy first shipped in 4.10.0, which misses too]; not a regression: no release ever matched these shapes (3.6.2 ships only the old contrib spatial code, with no SpatialStrategy API)
Affected versions
Every release that ships the strategies: PointVectorStrategy 4.0.0 through 10.6.0 (released 2026-10-29, 4ae946e5a9b), BBoxStrategy 4.10.0 through 10.6.0, and main e1faeba08bde (12.0.0-SNAPSHOT). Executed with a point stored at x = -0.0 against Rect(0.0, 10, 0, 10), hits of [BBox Intersects, BBox IsWithin, PointVector Intersects, PointVector IsWithin], expected [1, 1, 1, 1], -1 = strategy not in the release (main() ports, -da): 4.0.0 and 4.9.1 [-1, -1, 0, 0]; 4.10.0, 4.10.4, 5.0.0 and 5.5.5 [0, 0, 0, 0] (the legacy trie encoding orders -0.0 below +0.0 on the exclusive path too, so BBox Intersects misses as well); 6.0.0, 6.6.6, 7.7.3, 8.11.4, 9.0.0, 9.12.3, 10.0.0, 10.6.0 and main [1, 0, 0, 0] [exec]. The independent reproducer's JUnit fails [1, 0, 0, 0] on main -ea and -da, and on 10.6.0, 10.5.2 and 9.12.3 with -da [exec]; the finder's mirror-direction JUnit fails on main -ea and -da, main ea57360912ed, 10.6.0 and 9.12.3 [exec]. Releases between the executed ones were not run individually; the range code is unchanged between them apart from formatting [read]. Upstream: the newest commits on both files (bc7deedc092 #15499, author 2025-12-11; dfd536186fe #15889, author 2026-04-01) are in e1faeba08bde and cosmetic; no open PR touches either file (2026-11-02) [read]. Not new in 10.6.
Status
documented (brief, checks and a per-finding doc exist)
Verification
Confirmed by AI-run checks: an independent repro, a cross-model review that agreed, an intended-behaviour check, a prior-report search and a skeptical audit; not yet triaged by a person.
Found by
semi-formal; also reached by Z3
Upstream and prior reports
none found (2026-11-02: 125 queries, 71 in a scripted prior-report search and 54 manual, on top of the finder's 51-query search and the independent reproduction's, the intended-behaviour check's and the adjudication's GitHub searches; rechecked the same day): apache/lucene issues and PRs (all 20 items naming BBoxStrategy and all 9 naming PointVectorStrategy read or grepped), commits, the LUCENE and SOLR JIRA archive (21 LUCENE entries naming BBoxStrategy, 10 naming PointVectorStrategy and 19 SOLR entries naming BBoxField scanned), downstream repositories (apache/solr, Elasticsearch, OpenSearch, lucenenet, locationtech/spatial4j), the dev@, java-user@, issues@, commits@ and solr-user@ lists, the web, the git history, and the changed-file lists of the 16 open apache/lucene PRs updated since 2026-10-31 (only #16847 touches spatial-extras, in other files): no report of the missed zero-edge matches or of BBoxStrategy's Intersects-vs-IsWithin contradiction. A same-day recheck found no apache/lucene issue or PR updated since 2026-10-31 for BBoxStrategy, PointVectorStrategy, "negative zero spatial" or "signed zero"; none of the 16 open PRs updated since 2026-10-31 touches BBoxStrategy.java, PointVectorStrategy.java or DoublePoint.java; the newest upstream commits on the two strategy files are still bc7deedc092 (#15499) and dfd536186fe (#15889), both in e1faeba08bde. Related, not reports: #8645 (LUCENE-7594, 2016: core Float/DoublePoint follow Double.compare, +0 above -0, and the Math.nextUp guidance was changed; spatial-extras is never mentioned), #8150 (LUCENE-7094, the 2016 points cutover that created today's inclusive/exclusive split; no discussion of zeros), #6833 (LUCENE-5771, boundary-neutral predicate semantics), #8240 (LUCENE-7185, open: a comment suggests generating -0.0 in core random geo tests) and elastic/elasticsearch#35707 (2018: a downstream geo layer normalizes coordinates so they never return -0.0 before building Lucene geometry).
Lucene commit tested
main e1faeba08bde (2026-11-02) through gradle in a scratch worktree, with assertions on and off and 5 random-seed iterations, without and with the fix (the repro, TestBBoxStrategy, TestPointVectorStrategy, the whole lucene/spatial-extras test suite and :lucene:spatial-extras:check -x test); a plain-IndexWriterConfig control on main and the 10.6.0 release jars. The hunt ran the repro or a main() port on 4.0.0, 4.9.1, 4.10.0, 4.10.4, 5.0.0, 5.5.5, 6.0.0 (also with the legacy trie encoding), 6.6.6, 7.7.3, 8.11.4, 9.0.0, 9.12.3, 10.0.0, 10.5.2 and 10.6.0, and a bounded-exhaustive sweep of 439,082 checks on main. Only Linux aarch64 with OpenJDK 25.0.3 was run.

Lucene and upstream sources

Finding record
  • lucene/spatial-extras/src/java/org/apache/lucene/spatial/bbox/BBoxStrategy.java:47 (at e1faeba08bde) ('It supports all SpatialOperations'), :54-55 (other shapes are indexed by their bounding box), :71 (@lucene.experimental), :283-305 (makeContains: comment 'docMinX <= queryExtent.getMinX() ... docMaxX >= queryExtent.getMaxX()', inclusive bounds at :291-292 and :304-305), :366-450 and :473 (makeDisjoint and makeIntersects: exclusive bounds only, e.g. :374-375, :386, :393), :523-547 (makeWithin: comment 'docMinX >= queryExtent.getMinX() ... docMaxX <= queryExtent.getMaxX()', inclusive bounds at :531-532 and :546-547), :651-672 (makeNumericRangeQuery: exclusive bounds through Math.nextUp/Math.nextDown, inclusive bounds passed unchanged to DoublePoint.newRangeQuery; fix site)
  • lucene/spatial-extras/src/java/org/apache/lucene/spatial/vector/PointVectorStrategy.java:63-64 (at e1faeba08bde) ('Intersects and IsWithin is supported'), :73-74 (a circle query runs the same bbox query plus a distance filter), :80 (@lucene.experimental), :188-205 (makeQuery: rectangle -> makeWithin; circle -> makeWithin(circle.getBoundingBox()) plus DistanceRangeQuery), :209-222 (makeWithin: MUST ranges on x and y), :228-238 (rangeQuery: bounds passed unchanged to DoublePoint.newRangeQuery; fix site)
  • lucene/spatial-extras/src/java/org/apache/lucene/spatial/query/SpatialOperation.java:30-35 (at e1faeba08bde) (predicates explained by the source of evaluate(); 'Lucene-spatial makes no distinction of shape boundaries'), :89-95 (Intersects: indexedShape.relate(queryShape).intersects()), :124-136 (IsWithin, 'Meets the "CoveredBy" OGC definition (boundary-neutral)': relate == WITHIN || equals)
  • lucene/spatial-extras/src/java/org/apache/lucene/spatial/SpatialStrategy.java:116 (at e1faeba08bde) ('Make a Query based principally on SpatialOperation and Shape')
  • lucene/core/src/java/org/apache/lucene/document/DoublePoint.java:213 (at e1faeba08bde) and :235 ('Range comparisons are consistent with Double#compareTo(Double)'), :232-233 ('Ranges are inclusive')
The finding in 30 seconds
  • lucene/spatial-extras/src/java/org/apache/lucene/spatial/SpatialStrategy.java:116; (at e1faeba08bde) query/SpatialOperation.java:30-35, :89-95, :124-136
  • lucene/spatial-extras/src/java/org/apache/lucene/spatial/bbox/BBoxStrategy.java:283-305, (at e1faeba08bde) :366-393, :473-500, :523-547, :651-672 (fix site), :54-55
  • lucene/spatial-extras/src/java/org/apache/lucene/spatial/vector/PointVectorStrategy.java:188-205, (at e1faeba08bde) :209-222, :228-238 (fix site)
  • lucene/core/src/java/org/apache/lucene/document/DoublePoint.java:210-213, (at e1faeba08bde) :232-235
Who is affected
  • lucene/spatial-extras/src/java/org/apache/lucene/spatial/bbox/BBoxStrategy.java:47, (at e1faeba08bde) :71; vector/PointVectorStrategy.java:63-64, :80
Why it looks unintended
  • lucene/spatial-extras/src/java/org/apache/lucene/spatial/bbox/BBoxStrategy.java:47, (at e1faeba08bde) :57, :265-271, :286-287, :526-527; vector/PointVectorStrategy.java:63-64; SpatialStrategy.java:116; query/SpatialOperation.java:30-35, :124
  • lucene/spatial-extras/src/test/org/apache/lucene/spatial/StrategyTestCase.java:233; (at e1faeba08bde) prefix/RandomSpatialOpStrategyTestCase.java:103; bbox/TestBBoxStrategy.java:126, :154

Other statements trace to the hunt's working files (models, logs, reviews and briefs), which are not published.

Repro files

About the diagrams

A summary still (PNG).

Built 2026-11-06 from the hunt's record of this finding and its doc file. Lucene main under test in this hunt: ea57360912ed. Every listed finding was re-run on Lucene main e1faeba08bde on 2026-11-02.