v0.5.10 — recalibrate distance curves so range can't move the threat tier

The scores were already absolute, but the tier is max(score) over everything
*reported* and the range slider decides what is reported — so the setting
still leaked into the alarm. Measured against a real 195-node cache: standing
still and dragging the slider from 300 m to 500 m flipped the app from GREEN
to YELLOW with nothing physical changing.

The fix is calibration, not plumbing. Every curve now crosses below the YELLOW
line at roughly the distance the thing stops being able to act on you. A Flock
camera reads plates at ~30-50 m, so it is RED on top of it, ORANGE at 100 m,
and GREEN by 500 m — still drawn on the map, just no longer an alarm. Waze
keeps a wider band deliberately: a police car covers 700 m in under a minute,
a bollarded camera never moves. Aircraft are unaffected, since that scanner
uses its own ranges rather than the slider.

The main-screen slider is relabelled "show within" to say what it actually is
— a view control, not a sensitivity control.

Verified on device: sweeping 200 m -> 4900 m leaves the tier at GREEN while
the reported count goes 0 -> 133, and standing 29 m from a real camera reads
89 RED, then 50 at 252 m and 41 at 373 m. Scores match the falloff formula
exactly.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RsfSpnwuxCkcXKkD9XH8SR
This commit is contained in:
KaraZajac
2026-09-16 23:47:20 -04:00
co-authored by Claude Opus 5
parent 32e2ef2606
commit 19622b87e1
6 changed files with 90 additions and 45 deletions
+28 -16
View File
@@ -80,17 +80,27 @@ freshness window, because police move and a surveyed camera does not.
| Distance | DeFlock ALPR | Waze police (fresh) |
|---|---|---|
| 0 m | 92 RED | 80 ORANGE |
| 50 m | 85 RED | 75 ORANGE |
| 100 m | 77 ORANGE | 70 ORANGE |
| 200 m | 60 YELLOW | 62 YELLOW |
| 600 m | 45 YELLOW | 49 YELLOW |
| 1200 m | 35 GREEN | 41 YELLOW |
| 3000 m | 22 GREEN | 28 GREEN |
| 0 m | 95 RED | 80 ORANGE |
| 50 m | 85 RED | 78 ORANGE |
| 100 m | 72 ORANGE | 70 ORANGE |
| 200 m | 55 YELLOW | 62 YELLOW |
| 350 m | 42 YELLOW | 52 YELLOW |
| 500 m | 35 GREEN | 47 YELLOW |
| 1000 m | 26 GREEN | 34 GREEN |
| 3000 m | 18 GREEN | 23 GREEN |
A fixed ALPR peaks higher and decays faster than a crowd-sourced police pin:
its position is surveyed and exact, while a Waze report is a coarse pin on a
car that may be moving toward you.
Each curve crosses below the YELLOW line (40) at roughly the distance the thing
stops being able to act on you. A Flock camera reads plates at ~30–50 m, so it
is RED on top of it, ORANGE at 100 m, and GREEN by 500 m — still drawn on the
map, just not an alarm. Waze keeps a wider band on purpose: a police car covers
700 m in under a minute; a bollarded camera never moves.
**This calibration is what makes a single range slider safe.** The tier is
`max(score)` over everything reported, and the slider decides what gets
reported — so with looser curves, standing still and dragging it from 300 m to
500 m flipped the app GREEN → YELLOW with nothing physical changing. Now
sweeping 200 m → 4900 m leaves the tier untouched, while standing 29 m from a
camera still reads **89 RED**.
The user-facing circle uses the full 4-tier mapping. Cross-source corroboration
naturally pushes the global max upward (a BLE OUI hit *and* a DeFlock map
@@ -264,12 +274,13 @@ Tap the gear icon in the top-right.
- **Detection sources**: toggle BLE / WiFi / DeFlock / Waze / Aircraft / Commercial independently.
Changes take effect on the next Start. While scanning, a **Restart scan to
apply** button appears that does `stop()` + `start()` in one tap.
- **Detection radius** lives on the **main screen**, under the map — it's the
one setting you reach for while moving, so it isn't buried in Settings.
100 m – 5000 m, default 500 m, committing on release rather than per-pixel so
dragging it doesn't restart the location scanners on every frame. It sets what
gets reported and what the circle draws — *not* how alarming a hit is, which
is purely a function of real distance.
- **Range** (`show within`) lives on the **main screen**, under the map — it's
the one control you reach for while moving. 100 m – 5000 m, default 500 m,
committing on release rather than per-pixel so dragging it doesn't restart the
location scanners on every frame. It is a **view control, not a sensitivity
control**: it sets what the circle draws and what the drill-down lists, and
nothing else. Scores come from real distance alone, so widening it adds dots
and rows but cannot change the threat tier.
- **Waze police feed**: paste your own OpenWeb Ninja API key — see
[Waze setup](#waze-setup-bring-your-own-api-key). Stored encrypted on-device
(Android Keystore), never baked into the APK. Empty = Waze source off.
@@ -317,6 +328,7 @@ field-tested. Current release **v0.5.7**. Notable changes:
- v0.5.7 — Citizen source **removed entirely** (client, scanner, scoring, settings, map dots and the drill-down row). Its endpoint now returns HTTP 410 Gone, so there was nothing left to degrade gracefully into. OVERWATCH is now a five-source app: BLE, WiFi, DeFlock, Waze, Commercial.
- v0.5.8 — **AIRCRAFT source**: police / surveillance aircraft overhead via free community ADS-B feeds, matched against a bundled 1,971-entry registry of US law-enforcement airframes (regenerate with `scripts/gen-le-aircraft.py`), plus loiter/orbit detection so unlisted aircraft circling overhead still register. Overpass query widened to speed cameras and generic surveillance nodes, each scored on its own curve. DeFlock/Waze/aircraft all scored by continuous distance falloff. New [SOURCES.md](SOURCES.md) reference.
- v0.5.9 — Detection-radius slider moved onto the main screen (under the map, where you reach for it while moving) and a source-color legend added beneath the circle: ALPR red, speed camera amber, other cameras gray, Waze police blue, aircraft violet.
- v0.5.10 — Recalibrated every distance curve so the range slider can no longer move the threat tier: each crosses below YELLOW at roughly the distance the thing stops being able to act on you (ALPR is RED on top of it, GREEN by 500 m). The main-screen slider is relabelled `show within` to say what it is — a view control, not a sensitivity control.
## License
+23 -8
View File
@@ -347,16 +347,31 @@ decides what gets *reported*, never how alarming it is.
| Distance | ALPR | Speed camera | Generic camera | Waze police | Aircraft |
|---|---|---|---|---|---|
| 0 m | 92 | 75 | 55 | 80 | 88 |
| 50 m | 85 | 70 | 48 | — | — |
| 200 m | 60 | 52 | 38 | 62 | — |
| 600 m | 45 | 40 | 30 | 49 | — |
| 1000 m | — | — | — | — | 80 |
| 1500 m | 30 | 28 | 22 | — | — |
| 3000 m | 22 | 20 | 18 | 28 | 68 |
| 6000 m | — | — | — | — | 55 |
| 0 m | 95 | 78 | 55 | 80 | 88 |
| 50 m | 85 | 70 | 46 | 78 | — |
| 100 m | 72 | 63 | 42 | 70 | — |
| 200 m | 55 | 50 | 34 | 62 | — |
| 350 m | 42 | 38 | 28 | 52 | — |
| 500 m | 35 | 32 | 26 | 47 | — |
| 1000 m | 26 | 27 | 20 | 34 | 80 |
| 3000 m | 18 | 16 | 14 | 23 | 68 |
| 15000 m | — | — | — | — | 30 |
**Every curve crosses below 40 (YELLOW) at roughly the distance the thing stops
being able to act on you.** That is not cosmetic — it is what makes a single
range slider safe. The on-screen tier is `max(score)` over everything reported,
and the slider decides what is reported, so loose curves let the setting leak
into the alarm: measured against a real 195-node cache, standing still and
dragging from 300 m to 500 m flipped the app GREEN → YELLOW with nothing
physical changing. After recalibration, sweeping 200 m → 4900 m leaves the tier
untouched (reported items 0 → 133), while standing 29 m from a Flock camera
still reads 89 RED.
Aircraft are exempt from the range slider entirely — `AircraftScanner` uses its
own 15 km reporting range (8 km for unregistered contacts), because an aircraft
orbiting overhead is still watching you from a distance at which a camera
cannot see you at all.
A surveyed ALPR peaks highest and decays fastest — its position is exact and it
cannot see you from a kilometre away. A Waze report is a coarse pin on a car
that may be moving toward you. An aircraft decays slowest of all, because one
+2 -2
View File
@@ -14,8 +14,8 @@ android {
applicationId = "org.soulstone.overwatch"
minSdk = 26
targetSdk = 35
versionCode = 25
versionName = "0.5.9"
versionCode = 26
versionName = "0.5.10"
}
// Fixed debug keystore committed to the repo (a debug key is non-secret — its
@@ -284,11 +284,23 @@ object ConfidenceEngine {
* by [falloff].
*
* These are **absolute distances, deliberately not a fraction of the user's
* detection radius.** A camera 200 m away is exactly as close whether the
* radius slider reads 300 m or 5 km, so it has to score the same either
* way; keying off the radius would make the threat level move when the
* user touched a setting, which is the opposite of what the number means.
* The radius decides what gets *reported*, never how alarming it is.
* range setting.** A camera 200 m away is exactly as close whether the
* slider reads 300 m or 5 km, so it has to score the same either way.
*
* That alone was not enough, and the gap is worth recording. The on-screen
* tier is `max(score)` over everything *reported*, and the slider decides
* what gets reported — so with the original curves, standing still and
* dragging the slider from 300 m to 500 m flipped the app GREEN -> YELLOW
* with nothing physical changing (measured against a real 195-node cache).
* The scores were absolute but the tier still leaked the setting.
*
* The fix is calibration, not plumbing: each curve now crosses below the
* YELLOW line (40) at roughly the distance the thing stops being able to
* act on you. A Flock camera reads plates at ~30-50 m, so it is RED on top
* of it, ORANGE at 100 m, and GREEN by 500 m — visible on the map, not an
* alarm. Widening the view then adds dots and list rows but cannot change
* the tier. Waze keeps a wider alert band on purpose: a police car covers
* 700 m in under a minute, a bollarded camera never moves.
*
* A fixed ALPR's position is surveyed and exact, so it stays alarming
* closer in and decays slowly. The 50 m and 200 m values are carried over
@@ -296,7 +308,8 @@ object ConfidenceEngine {
* unchanged.
*/
private val DEFLOCK_FALLOFF = arrayOf(
0f to 92f, 50f to 85f, 200f to 60f, 600f to 45f, 1500f to 30f, 3000f to 22f
0f to 95f, 25f to 90f, 50f to 85f, 100f to 72f, 200f to 55f,
350f to 42f, 600f to 30f, 1500f to 22f, 3000f to 18f
)
/**
@@ -304,7 +317,8 @@ object ConfidenceEngine {
* you if you're speeding past it — relevant, not alarming.
*/
private val SPEED_CAMERA_FALLOFF = arrayOf(
0f to 75f, 50f to 70f, 200f to 52f, 600f to 40f, 1500f to 28f, 3000f to 20f
0f to 78f, 50f to 70f, 150f to 55f, 300f to 42f, 500f to 32f,
1500f to 22f, 3000f to 16f
)
/**
@@ -313,7 +327,7 @@ object ConfidenceEngine {
* distance and never drives the tier on its own.
*/
private val CAMERA_FALLOFF = arrayOf(
0f to 55f, 50f to 48f, 200f to 38f, 600f to 30f, 1500f to 22f, 3000f to 18f
0f to 55f, 50f to 46f, 150f to 38f, 300f to 30f, 800f to 22f, 3000f to 14f
)
/**
@@ -322,7 +336,8 @@ object ConfidenceEngine {
* the old flat baseline of 55.
*/
private val WAZE_FALLOFF = arrayOf(
0f to 80f, 100f to 70f, 300f to 55f, 800f to 45f, 2000f to 32f, 4000f to 25f
0f to 80f, 100f to 70f, 300f to 55f, 600f to 44f, 900f to 36f,
2000f to 26f, 4000f to 20f
)
/**
@@ -401,11 +401,14 @@ private fun ThreatMapCircle(
}
/**
* Detection radius, on the main screen because it is the one setting a user
* actually reaches for while moving — it decides both what counts as a
* detection and how much ground the circle shows. Commits on release rather
* than per-pixel so dragging it doesn't restart the location scanners on
* every frame.
* How far out to look — a view control, not a sensitivity control.
*
* It sets what the circle draws and what the drill-down lists, and nothing
* else: scores come from real distance alone (see ConfidenceEngine's falloff
* tables), so widening this adds dots and rows but cannot change the threat
* tier. It lives on the main screen because it is the one setting a user
* reaches for while actually moving. Commits on release rather than
* per-pixel, so dragging doesn't restart the location scanners every frame.
*/
@Composable
private fun RadiusSlider(
@@ -420,7 +423,7 @@ private fun RadiusSlider(
horizontalArrangement = Arrangement.SpaceBetween
) {
Text(
text = "detection radius",
text = "show within",
color = MaterialTheme.colorScheme.onSurfaceVariant,
fontSize = 11.sp,
fontFamily = FontFamily.Monospace
+4 -4
View File
@@ -53,7 +53,7 @@
<div class="container hero-inner">
<div class="hero-eyebrow">
<span class="dot dot-pulse"></span>
v0.5.9 · Android · passive
v0.5.10 · Android · passive
</div>
<h1 class="hero-title">
<span class="display-wordmark">OVERWATCH</span>
@@ -75,7 +75,7 @@
<div class="stat-chip"><span class="num" data-target="6">0</span><span>fused sources</span></div>
<div class="stat-chip stat-vfy"><span class="num" data-target="4">0</span><span>threat tiers</span></div>
<div class="stat-chip"><span class="num" data-target="0">0</span><span>packets sent</span></div>
<div class="stat-chip"><span class="num" data-target="24">0</span><span>releases</span></div>
<div class="stat-chip"><span class="num" data-target="25">0</span><span>releases</span></div>
</div>
<div class="cta-row">
@@ -432,7 +432,7 @@
<ul>
<li>Grab the latest debug-signed APK from
<a href="https://github.com/KaraZajac/OVERWATCH/releases/latest">Releases</a>
(currently <strong>v0.5.9</strong>).</li>
(currently <strong>v0.5.10</strong>).</li>
</ul>
</div>
<div class="tl-col tl-active">
@@ -501,7 +501,7 @@
transmit, probe, jam, or interfere with any device or network.
</p>
<p class="footer-meta">
v0.5.9 · <a href="https://github.com/KaraZajac/OVERWATCH">github.com/KaraZajac/OVERWATCH</a>
v0.5.10 · <a href="https://github.com/KaraZajac/OVERWATCH">github.com/KaraZajac/OVERWATCH</a>
</p>
<div class="onion-line">
<span class="onion-label">Also on Tor</span>