Pracino Mobile Investigation

Map Marker Overlap
Code Audit

Updated: July 15, 2026· Version 2

A math-first review of every place the current map marker pipeline can still produce visual or tap-target overlap after clustering, fan expansion, declutter, and camera transitions, now checked against the dense Candidates-map repro from Ostrava and extended with a bounded resolution policy.

01Executive summary

The layout is deterministic and its current tests are green, but several collision classes sit outside what those tests assert.

9
Findings
7
High-confidence
26/26
Layout tests
0
Code changes
Top finding

Adjacent fan columns can overlap by roughly half a salary pill while remaining separate Supercluster outputs.

Tap behavior

Several overlaps also share native Marker hit regions, so the visible top marker is not necessarily the marker that receives the tap.

Excluded regressions

The iOS top-pill sag and white-ring fill bleed are solved and are not counted below.

Recommended response

Use a Candidates-scoped macro-obstacle pass with a fixed-quadrant 46.4 dp envelope. It stays inside the current 49.4 dp transition budget without restoring Dock-style re-rolls.

02Where the guarantees stop

The pipeline prevents ordinary pills from colliding with known obstacles, but does not establish a global non-overlap invariant.

flowchart LR
A[Map points] --> B[Supercluster radius]
B --> C[Clusters and standalone pins]
C --> D[Fan selection and marker budget]
D --> E[Obstacle rectangles]
E --> F[Greedy pill declutter]
F --> G[Rendered markers]
E -. no obstacle-vs-obstacle pass .-> H[Fan / fan and Cluster / Circle overlap]
F -. unconditional fallback .-> I[Mini overlap]
D -. transient form swap .-> J[Even-fan collapse overlap]
G -. rendered size differs .-> K[Font scale and hit-area overlap]

The critical seam is obstacleRects: fan legs and Cluster pills claim space before ordinary salary pills, but those obstacles are never checked against one another. Circle Pins and selected pins are also immovable, so they join the same unresolved collision class.

03Ranked findings

The first four reproduce through the real Supercluster-to-layout path; later findings cover rendering contracts and boundary behavior.

RankFindingTriggerConfidenceCovered by specs
1Adjacent fans overlapTwo coincident groups just outside cluster radiusHigh
2Mini and selected pin overlap a fanOdd fan plus boxed neighbouring salary pinHigh
3Even-fan collapse puts Cluster over a pillZoom-out threshold with occupied center gapHigh
4Rounded radius is below immovable footprint pairsHalf-zoom boundaryHigh
5Rendered footprints exceed layout estimatesFont scale 1.3 and wrapper paddingMedium-high
6Short fan viewports self-overlapLandscape or split-screen heightHigh
7First-leaf fan anchor erodes separationNear-epsilon leaves toward a neighbourMedium-high
8Live zoom and grid crossings escape the modelMid-gesture or budget-saturated crossingMedium
9Coordinate-only updates are discardedSame IDs and visual state, changed coordinatesHigh behavior / lower frequency

041. Adjacent fans overlap

Separate Supercluster outputs can still be much closer than the width of their rendered pills.

Concrete arrangement. At longitudeDelta = 0.02 on a 390 dp-wide viewport, place two exact-coordinate triples on the same latitude with centers 0.0026 degrees apart. Supercluster zoom 14 keeps them as separate clusters about 50.7 dp apart. Both become three-leg fans with center salary pills. A nine-character salary has a 101 dp estimated footprint, producing about 50.3 dp of overlap.

Math mechanism. Supercluster radius is a center-distance merge threshold, not a rectangle-clearance guarantee. Fans are added as obstacles, but no obstacle-vs-obstacle pass exists. Even fans can overlap row-to-row when their columns align.

Tap impact. The two center legs occupy the same hit region. Both are default-anchor Markers, so winner selection depends on render/platform ordering.

Evidence. map-marker-layout.ts:174-191, 401-431, 502-531; map-view.tsx:524-562; map-bounds.ts:3-8.

Spec gap. Current fan tests contain one fan only. Add a real Supercluster fixture with two odd fans just outside the radius and assert pairwise visual and hit rectangles.

Smallest fix seam. Resolve cluster/fan obstacles against one another before ordinary pin declutter. Do not treat a larger global radius as an isolated visual tweak; it changes density, fan onset, and marker churn.

052. Mini fallback and selection overlap

Fallback changes form, but it does not prove the fallback rectangle is safe.

Concrete arrangement. Use one exact-coordinate triple at longitude 0 and a standalone salary pin at longitude 0.0026 on a 390 x 844 viewport. The fan's center and outer legs block every full-pill nudge. The standalone pin collapses to a mini at its true anchor, but the 28 dp mini still intersects the 101 dp center fan pill by about 13.8 dp. Selecting it makes it immovable and full-size at the same coordinate, increasing overlap to about 50.3 dp.

Math mechanism. When every nudge fails, the mini rectangle is inserted unconditionally. Selected pills and Circle Pins are pre-claimed as immovable, but are not resolved against fan/Cluster obstacles.

Evidence. map-marker-layout.ts:227-281, 515-519; map-view.tsx:524-619.

Spec gap. The boxed-in test verifies only isMini; the selected test verifies priority. Neither asserts clearance. Add the integrated odd-fan-plus-neighbour case for mini and selected forms.

Smallest fix seam. Validate the mini rectangle before accepting fallback and define a deliberate last resort. Selection needs the same explicit obstacle policy; this does not imply restoring rejected Dock behavior.

063. Even-fan collapse overlaps

The transient badge and the obstacle model describe different shapes.

Concrete arrangement. A two-leg fan on a 390 x 844 viewport has 71.74 dp spacing, leaving an empty center gap. A salary pin just outside the Supercluster radius can sit at the same latitude in that gap. During zoom-out, collapseFans renders a 38 dp Cluster at the fan center but still declutters against the two invisible fan legs. At 50.7 dp horizontal separation, the Cluster and salary pill overlap by about 18.8 dp.

Math mechanism. Preserving fan obstacles during collapse is collision-equivalent for odd fans because they have a center leg. It is not equivalent for even fans.

Evidence. map-marker-layout.ts:351-363, 451-485, 502-531; map-view.tsx:186-228.

Spec gap. The existing two-leaf collapse test places its standalone pill far from the center and asserts identical pin layout. Add a just-outside-radius pill occupying the even-fan gap and assert against the shape that actually renders.

Smallest fix seam. Use the rendered badge footprint during collapse, then verify the resulting pin movement against the threshold-continuity gate.

074. Cluster radius boundary overlap

Integer zoom rounding reduces effective screen-space separation below several fixed marker pairs.

Concrete arrangement. At longitudeDelta = 360 / 2^14.5 on a 390 dp viewport, zoom rounds to 15 and the effective radius is about 32.3 dp. A non-coincident two-point Cluster and Circle Pin can remain separate at 32.64 dp center distance. Their 38 and 32 dp widths overlap by about 2.36 dp. Two minimum Clusters overlap more; larger count labels widen them further. Two Circle Pins narrowly avoid visual overlap, but their wrapper-sized hit areas can still overlap by roughly three dp.

Math mechanism. The documented square-root-of-two radius wobble makes the low-end radius smaller than Cluster/Cluster and Cluster/Circle half-width sums. Both elements are immovable in the current sweep.

Evidence. map-bounds.ts:3-8, 32-39; map-view.tsx:296-304; map-marker-layout.ts:174-243; map-markers.tsx:142-180, 419-424.

Spec gap. Pure layout fixtures bypass Supercluster. Add an integrated q = n + 0.5 case and distinguish visible rectangles from native hit rectangles.

Smallest fix seam. Resolve immovable pairs locally or explicitly choose tap priority; avoid treating CLUSTER_RADIUS as a one-number fix.

085. Rendered footprints exceed estimates

The pure geometry models nominal text, while the component supports larger rendered text and extra wrapper space.

Concrete arrangement. Place salary pills at the first separation the estimated rectangles accept, then use the supported 1.3 font scale. Layout assumes 32 dp height and eight dp per character. Rendering adds four dp of top/right wrapper space and allows 30 percent text growth. Bundled-font measurement indicates 58 000 Kc grows from about 67 px at 13 px to 86 px at 16.9 px; the resulting outer marker is roughly 117 dp rather than the 101 dp estimate.

Math mechanism. The three-dp collision gap cannot absorb the difference between nominal layout constants and maximum supported rendering. Cluster count text has the same scale mismatch.

Evidence. map-marker-layout.ts:42-63, 174-191; map-markers.tsx:142-180, 333-355, 419-448; shared text.tsx:7-16; theme index.ts:8-11.

Confidence note. The contract mismatch is definite; the exact 117 dp figure comes from the bundled font rather than a React Native onLayout capture.

Smallest fix/spec. Measure marker components at font scale 1.3 and feed conservative maximum dimensions into pure layout. Disabling map-label scaling is smaller code but is an accessibility decision, not a neutral geometry fix.

096. Short viewports self-overlap

Fan spacing scales with viewport height but has no minimum tied to the rendered leg.

Concrete arrangement. At 390 dp landscape height, fan step is 33.15 dp. Base 32 dp pills have only 1.15 dp clearance; an availability dot extends four dp upward and overlaps the pill above by about 2.85 dp. At maximum font scale, the pills themselves can exceed the step. In a five-leg fan, an inner same-side Marker container reaches into the outer pill and its less-negative z-index can win the overlapping tap region. Below 376.5 dp height, the above-leg spine calculation receives a negative height.

Math mechanism. Spacing is fixed at 8.5 percent of viewport height, while spine and collision assumptions use exactly 32 dp. Same-side Marker containers intentionally extend from their pill back to the shared anchor.

Evidence. map-marker-layout.ts:17-24, 120-134; map-markers.tsx:29-40, 184-233, 313-332.

Spec gap. Both suites use a 1000 dp viewport. Add 360/390 dp cases with four/five legs, availability dots, max-scale height, and explicit tap priority.

Smallest fix seam. Clamp step to maximum rendered leg height plus clearance, or reshape/reduce fans on short viewports.

107. First-leaf fan anchoring

The fan can move away from the aggregate coordinate on which Supercluster separation was based.

Concrete arrangement. Put two cluster centers just outside the radius, then order their first leaves toward one another within the allowed coincidence boxes. The coincidence predicate accepts independent spans just below 0.0005 degrees and anchors every leg at leaves[0]. At delta 0.02 on a 390 dp viewport, each fan can shift about 9.75 dp horizontally; two fans can close by almost 19.5 dp before pill widths are considered.

Math mechanism. Supercluster clearance applies to aggregate/projected feature geometry. Fan obstacles are later relocated to an arbitrary leaf, making overlap dependent on leaf order.

Evidence. map-marker-layout.ts:120-135, 286-296, 413-431, 508-519.

Spec gap. Current fan fixtures put the first leaf at the cluster coordinate. Add a leaf-order permutation test near epsilon with a neighbouring output feature.

Smallest fix seam. Anchor to stable cluster geometry or a deterministic centroid, then characterize the visual/tap transition before changing existing behavior.

118. Transition boundaries are not collision-tested

Settled-frame continuity is covered; interpolated camera frames and actual grid crossings are not.

Concrete arrangement. During zoom-out, map-coordinate distances contract continuously while fixed marker footprints are not re-decluttered until settle. At a high-density grid crossing near the 120-marker budget, query population and center-distance fan priority can change, swapping one coincident Cluster from badge to fan and exposing the earlier collision classes abruptly.

Math mechanism. Declutter is settle-scoped. Grid snapping deliberately freezes layout content until the query key changes; the boundary can then change feature set, fan selection, and budget discontinuously. Pure same-zoom pan within one query is translation-invariant and did not reveal a separate collision mechanism.

Evidence. map-marker-layout.ts:33-40, 406-431; map-view.tsx:178-228, 322-342, 498-513; map-bounds.ts:41-71.

Spec gap. Transition specs compare settled regions with fixed features. Add interpolated rectangle checks and just-before/after real Supercluster queries across one bbox grid line under a saturated fan budget.

Smallest fix seam. Pin the boundary behavior first. Live per-frame React declutter conflicts with the marker-churn constraints and should not be the first implementation response.

129. Coordinate-only updates stay stale

The hook can prevent corrected coordinates from ever reaching Supercluster.

Concrete arrangement. If an unchanged ordered point set receives only latitude/longitude updates, useMapBrowsing reuses the previous array. A formerly stacked set can remain fanned or overlapping at stale coordinates; a new collision can also remain invisible until another visual field, ID, order, or count changes.

Math mechanism. Structural equality checks ID, salary, availability, and unlock state, but omits both coordinates. The Supercluster index therefore does not rebuild.

Evidence. use-map-browsing.ts:226-254, 481-500; map-view.tsx:296-321.

Spec gap. The scoped suites test the pure layout only. Add a hook rerender test for coordinate-only updates.

Smallest fix. Include latitude and longitude in the structural equality check.

13Ostrava repro attribution

The supplied Candidates-map scene is a high-density normal case, and all three visible classes fall out of the current obstacle math without a platform-specific explanation.

281
Source points
11
Clusters
16 / 47 legs
Fans
16
Standalone pins

The scene renders 74 native markers, leaving 46 slots below the 120-marker cap. Fan-budget saturation and pin truncation are therefore not causing the overlap. With hidden candidates scattered by about 500 m server-side, many outputs land far enough apart to avoid clustering but still close enough for their fixed-size screen rectangles to intersect.

At z14 fan mode on a 390 dp-wide viewport, the rounded Supercluster radius supplies roughly 50.2–64.6 dp of screen separation. A typical 101 dp salary pill spans roughly 290–370 m at the same zoom, while the cluster radius is only about 185 m near Ostrava. That mismatch makes near-coincident scatter an expected collision source rather than a boundary fixture.

Visible classAttributionRequired separationAllowed overlapConfidence
2-count Cluster over salary pillFinding 1, with Finding 4 supplying the radius premise69.5 dp4.9–19.3 dp; probe: 18.8 dpHigh
Circle Pin over salary pillFinding 2, with Finding 4 supplying the radius premise66.5 dp1.9–16.3 dp; probe: 15.8 dpHigh
Salary pill behind salary pillFinding 1, amplified by Finding 7101 dp36.4–50.8 dp; probe: 50.3 dpHigh

The direct probes use the real Supercluster-to-layout seam. No new Android/iOS rendering defect is needed: the matching result on both platforms is what the pure layout model predicts.

Frequency correction

Findings 1, 2, and 4 should be treated as routine Candidates-map behavior under the real privacy scatter, not as rare boundary conditions.

14Minimal obstacle policy

Resolve rendered macro obstacles first, preserve the user's active spatial references, then reuse the current salary-pill sweep.

Element classMovement policyWhy
Selected standalone markerHard anchor; claims firstIt is the user's active spatial reference
Fan containing the selected pointHard-anchor the entire fanLegs and spine must remain one column
Circle PinsStay true-to-anchorPreserves the existing exact anchored-circle transition contract
Unselected fanMove only as one atomic columnSolves fan/fan and Circle/fan collisions without distorting the fan
Cluster badgeMay move inside the bounded envelopeIt is an aggregate control rather than a precise person coordinate
Ordinary salary pillKeep the current nudge/collapse passIt already behaves once macro obstacles claim truthful rectangles
Mini fallbackAccept only after collision testingThe current unconditional anchor can recreate Finding 2

The claim order is:

  1. Selected standalone marker, or the whole fan containing the selected point.
  2. Circle Pins at their true anchors.
  3. Unselected fan columns in stable fan-key order.
  4. Cluster badges, higher point_count first and then stable ID.
  5. Ordinary salary pills in their existing availability priority.
  6. Collision-tested mini fallback.

Each fan placement is transactional: shift every leg rectangle by the same candidate offset, accept only if all legs clear the already claimed space, then claim the entire fan. During collapseFans, resolve the compact Cluster rectangle that actually renders rather than invisible fan-leg rectangles.

Circle rule

Fans move around Circle Pins; Circle Pins do not move. The current zero-displacement circle assertion can stay green.

Density limit

If a connected component cannot fit inside the bounded envelope, return a residual-collision diagnostic. Do not silently add farther slots.

15Transition budget without Dock

A one-quadrant candidate set bounds every re-placement, so the resolver cannot jump from one side of an anchor to the other.

Use one fixed quadrant of the existing nudge envelope for each movable macro class:

The quadrant can be class-fixed in a pure implementation—for example, fans positive x/y and Clusters negative x/y—or persisted immutably per marker key. It must not choose a sign from the currently nearest obstacle, because a new neighbour could reverse the sign on the next settle.

Budget proof

The candidate-set diameter is hypot(28, 37) = 46.4 dp, below the current 46.4 + 3 = 49.4 dp transition allowance. No offset grows with fan length, and no marker can re-roll across the anchor as Dock did.

The existing bidirectional pill ladder is not safe for these macro classes: it contains both y signs, so a legal placement can flip 74 dp from -37 to +37 even though each position is individually close to the anchor.

16Implementation choices

The first option matches the geometry to the Candidates map's approximate-location semantics without changing the Offers map.

RankOptionRiskEffortCoverage
1Candidates-scoped atomic macro resolverMediumMediumAll three reported classes; preserves Circle anchors and budget
2Global atomic macro resolverMedium-highMedium-lowSame geometry, but moves Offers-map aggregates too
3Candidates-specific upstream aggregationHighLow-mediumReduces density but gives no rectangle-level guarantee

1. Candidates-scoped atomic macro resolver — recommended

2. Global atomic macro resolver

Apply the same resolver directly to both map roles at map-marker-layout.ts:194-283, 451-555. This removes prop plumbing but changes truthful offer-marker behavior without evidence that the Offers map needs the same tradeoff.

3. Candidates-specific upstream aggregation

Adjust the role-specific Supercluster policy at map-view.tsx:296-321, with shared constants at map-bounds.ts:3-12. This is an honest fallback when the bounded resolver reports infeasible components, but it changes counts, fan timing, tap-to-zoom behavior, and churn while still not proving rectangle clearance.

First red specs

Promote the direct 18.8 dp Cluster/pill, 15.8 dp Circle/pill, and 50.3 dp pill/pill probes into pairwise visual and hit-rectangle assertions before implementing the resolver.

17Geometry to preserve

Several odd-looking pieces are compensating for known native-map behavior and should not be simplified casually.

Keep solved fixes

Preserve collapsable={false} on fan columns and the white ring wrapper; they prevent the known iOS sag and fill bleed.

Keep screen-space fans

Pixel offsets and asymmetric Marker anchors keep fan spacing constant through camera motion.

Keep split spines

Per-leg spine segments avoid cross-marker z-order dependence on Android.

Treat z-index as load-bearing

Negative distance z-index compensates for overlapping transparent leg containers, even though it is insufficient at short heights.

Protect churn controls

Grid-snapped padded bounds, anchor freezing, cluster radius, and the marker budget are performance and continuity controls—not standalone visual constants.

Do not restore Dock by default

Circle Pin and selected-pin immovability create collision blind spots, but moving them without transition evidence repeats the rejected Dock failure mode.

Keeping fan obstacles during collapseFans is also load-bearing for crossing continuity, despite causing the even-fan defect. Any correction must measure both clearance and displacement across the threshold.

18Verification and limits

What was checked, and what this code-only audit deliberately did not claim.

This was intentionally read-only. No marker code, tests, commits, device sessions, or issue-tracker state changed.