Every experiment the autonomous loop has run, across all 5 evaluation eras — kept and discarded. Each row was pre-registered before training (hypothesis, method, expected outcome, architecture figure), then trained on a single GPU and scored. Every score on this page is measured by one frozen ruler: the worst mission score on held-out viewpoints — (1 − usable-fix rate) + false-fix rate, the product requirement rather than an error statistic (currently 3,007 KB, hard limit 4 MiB). The earlier eras were judged by different rulers at the time, and where those two disagree the row says so. One agent designs, one implements; failures stay on the record. New here? Start with the overview.
Goal reached: mission score 0.040 — very nearly every frame yields a usable fix.
5 experiments in this era, 4 kept — and 76 before it, across 4 earlier evaluation eras. The chart below shows all 81. Cost of the whole search: $364.64 — $342.51 of agent tokens (equivalent API cost; the work ran on a Max subscription) plus $22.13 of rented GPU, recorded for 61 of 81 experiments.
| # | Experiment | Category | Mission score | Time | Cost | Status | |||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| ▸ | 5.5 | Decode-consistent training targets: bilinear tent cell labels + residual offset against the model's own pooled centroid
7a1314f9
new best: 0.090 → 0.040 |
loss | 0.040 | 22 m 58 s | $3.90 | kept | ||||||||||||||||||||
|
The design under test — technical diagram — gray = frozen contract · ink = the current design · red = this experiment's change In plain words The model learns Berlin as a grid of tiles, like a giant bingo card. Until now, a photo taken straddling two tiles was taught 'you are in tile A, full stop' -- even though a nearly identical photo one step left would be taught 'tile B, full stop'. That is confusing teaching, and the model wasted effort memorizing an invisible boundary instead of the city. Now we teach it honestly: 'you are 60% in A, 40% in B', matching where the camera really stands. And the little aim-correction step at the end used to re-add information the averaging already provided, overshooting the target; now it is taught to fix only whatever small error actually remains. What it cost
by model — claude-fable-5 $2.81 · claude-opus-5 $1.09 · claude-haiku-4-5-20251001 $0.00 The agent lines are the equivalent API cost of the tokens each agent used, as recorded by the harness when it ran. The research ran on a claude.ai Max subscription, so that is what it would have cost billed per token — not money that left the account. The GPU line is real spend. Why it's the new best The best design so far. It improved the mission score from 0.090 to 0.040 — 0.050 better, beating the previous champion — so its code was committed as the new best, and later experiments branch from here. Hypothesis Experiment 4 proved that border-split belief is knowledge, not confusion, by pooling the 3x3 neighborhood at DECODE time (abstain 0.1475 -> 0.05, kept at 0.09018). But the TRAINING signal still teaches the pre-exp-4 objective: every view gets a hard one-cell label, even though the 24 m lattice places roughly 1 in 3 training views within ~12 m of a 128 m cell border, where nearly identical frames receive contradictory hard labels. That label noise is visible in exp 4's curves: train top1 capped at 0.83 and mean max softmax at 0.57 with loss still falling at epoch 8 -- the model spends capacity and epochs fighting an arbitrary tile boundary instead of memorizing place appearance. Worse, there is a latent train/decode inconsistency the soft decode exposed: the offset head is trained to point from the CONTAINING cell's centre to the true spot, but at decode it is added to the pooled CENTROID -- so whenever mass legitimately splits across a border (which tent-shaped belief makes systematic), the sub-cell position is double-counted, pushing the fix past the truth by up to the offset magnitude. The fix for both is the same principle: make the training targets the thing the decode actually reads. (1) Replace the hard cell label with a bilinear 'tent' target that splits probability mass over the <=4 cell centres nearest the true continuous position -- a target whose 3x3-pooled mass at truth is ~1.0 everywhere and whose centroid IS the true position, so gradients from nearly identical border views agree instead of conflicting. (2) Retrain the offset head as a RESIDUAL corrector: its target becomes the (detached) gap between the model's own pooled-centroid decode and the true position, clamped to (-0.5, 0.5) cell units -- it now fixes whatever error the centroid leaves rather than re-adding position the centroid already encodes. This is one mechanism -- train/decode consistency -- expressed in the two targets, with the deployed graph untouched. Method Training-signal change only; the exported inference graph does not change. In model/train.py: extract_chunk additionally returns the continuous pixel position (cx, cy) per view; a new helper builds the soft target distribution by bilinear tent weighting over the <=4 nearest cell centres (edge-clamped via scatter_add), mixed with the existing 0.05 uniform label smoothing; the offset target is computed per batch as the clamped residual between the true normalized position and the model's own detached 3x3 pooled centroid (recomputed in train.py from softmax(logits) using the model's cell_u/cell_v buffers, mirroring forward()'s mask logic). In model/model.py: loss_fn's signature changes to accept a dense soft target ([B, n_cells]) and the residual offset target, computing soft cross-entropy -(target * log_softmax).sum(1).mean() + 1.0 * smooth_l1 on the offset. CellLocNet, forward(), export_onnx, CELL_PX, epochs, LR schedule, and the full-lattice coverage loop all stay exactly as in exp 4. Expected outcome mission_score drops from 0.09018 to <= 0.07, target ~0.05. Mechanically, per the standing metric check the gain must appear in usable_fix_rate itself: usable 0.93 -> >= 0.95, abstain 0.05 -> <= 0.03 (removing contradictory-label gradients lets the same 8 epochs converge further, sharpening pooled belief above the 0.3 threshold on views that currently scatter), false_fix_rate stays <= 0.02-0.03 (eliminating the offset double-count near borders should reduce systematic overshoot, offsetting any risk from newly-confident frames; a perfectly-learned interior view still reaches pooled mass ~0.95 despite smoothing, far above threshold). Median error should fall from 36.4 m toward ~25-30 m via clean centroid interpolation plus a true residual corrector (logged only). Training-log expectations: per-cell top1 and max-softmax will read LOWER by design on border views (mass is supposed to split) -- judge convergence by loss and by eval, not by top1 falling short of exp 4's 0.83. Informative null: if training converges cleanly but eval abstain stays ~0.05, the remaining abstentions are far-field aliasing, not border ambiguity -- redirecting the next experiment at the feature extractor, the one inference stage never yet changed. Gates unaffected: parameter count is byte-identical (3.08 MB < 4 MiB) and the exported graph is unchanged (1.6 ms << 250 ms). Result mission score = 0.040131 (previous best 0.09018) — (1 - usable_fix_rate) + false_fix_rate, worst bucket; full breakdown in metrics.json Conclusion KEPT — mission score improved (0.09018 -> 0.040131); change committed as 7a1314f Where the model was tested — and how far off it was Each map is one full test area. Every dot is one held-out viewpoint: the ground under it was mapped during training, but this exact framing — 11–17 m off the nearest training vantage, at its own rotation — is one the model has never seen. It was shown a 128 m crop centered there and asked for its position. Dot color = the distance between its answer and the truth — green ≤ 100 m (at goal), amber ≤ 250 m, red beyond. A working model turns these maps green; spatial clusters of red reveal which parts of an area confuse it. One real test, end to end
→
24.1 m
miss, this crop 0.71
self-reported confidence Scoreboard — mission score per area × lighting
mission score per cell — (1 − usable-fix rate) + false-fix
rate; the worst cell (underlined) is the experiment's score. red = failed cell,
ink = better than abstaining on everything
Fits the aircraft? all deployment gates passed
largest per-area model 3,007 KB (limit 4,096 KB) · single-frame inference 1.6 ms on one CPU thread (proxy limit 250 ms)
crops are sampled fresh each run from the frozen train split (~45,000 distinct positions per area, times random rotation), never from eval blocks; how many to use is the experiment's own choice
Where this experiment's time went: design (Haiku) 5 m 22 s · implement (Opus) 2 m 12 s · train 1 area 15 m 07 s · score 5 s · samples 1 s · publish 3 s · whole experiment 23 m 03 s
ts 2026-07-31T11:43:46 · commit 7a1314f9a465 ·
parent 4efadfe45bb1 · artifacts runs/20260731_112048_experiment5 ·
agent model — · took 22 m 58 s
The code for this experiment — the exact code that trained itThe exact bytes that trained this run, snapshotted before the keep/revert decision could touch them. · on GitHub ↗ model/model.py148 lines model/train.py212 lines The exact prompt given to the headless agent# Autoresearch experiment — the design stage
You are one experiment of an autonomous research loop for UAV geolocalization.
Read `CLAUDE.md`'s "BRANCH OVERRIDE" section first, then §3, §4 and §6.
The harness (loop.sh) will train, score, log, and keep/revert AFTER you exit —
you only design the experiment.
## THE GOAL: MEMORIZE ONE BOUNDING BOX. OVERFITTING IS THE FEATURE.
This branch builds a **visual memory of Berlin**, not a model that reasons
about aerial imagery in general. One model, one bbox, deployed only over that
bbox. It is *supposed* to know this specific city by heart. You are not trying
to generalize to other places, other lighting, or unmapped ground — none of
those are measured, and designing for them costs capacity you need for
memorizing.
**How the split works, because it determines what a good design looks like:**
* **Training covers ALL of Berlin** — every lattice position, whole raster.
The model is shown the entire area it must memorize.
* **Eval holds out VIEWPOINTS, not REGIONS.** Every eval frame stands on
ground that WAS in training, but is framed 11–17 m off the nearest training
vantage and at its own rotation. Mapped ground, novel view — exactly what an
aircraft over a mapped city faces.
* So the task is: **recall a memorized place from a viewpoint you have not
seen.** It is NOT: infer the location of ground you were never shown.
* A small 1-in-32-block region IS genuinely untrained, scored separately and
logged only. It can never affect keep/revert. Ignore it when designing;
it exists to reveal whether the model has spatial structure at all.
This matters because the split used to hold out regions, which left 28% of
Berlin untrained and put 100% of eval questions on never-seen ground — an
unanswerable task for a memorization model, and the likely reason ~60 earlier
experiments plateaued. Do not design as if that were still true.
**Scope:** ONE locale (Berlin), ONE lighting condition (raw daytime imagery,
no synthetic relighting — the relighting machinery is disabled on this branch).
There is no cross-lighting robustness to reason about and no other area's
texture to generalize to.
## THE METRIC IS THE PRODUCT REQUIREMENT, NOT AN ERROR STATISTIC
The aircraft takes a vision fix every 5–10 s and it is its ONLY drift
correction. Per frame exactly three things can happen:
| outcome | meaning |
|---|---|
| confident **and** within 100 m | **USABLE FIX** — this is the product |
| not confident | abstains — safe, it waits for the next frame |
| confident **but** outside 100 m | **FALSE FIX** — dangerous, it injects a wrong position into navigation |
mission_score = (1 - usable_fix_rate) + false_fix_rate [MINIMIZED]
* **0.0** = every frame a usable fix. **1.0** = abstains everywhere.
**2.0** = confidently wrong everywhere — *strictly worse than silence*,
because a confidently wrong fix corrupts navigation while an abstention
merely costs time.
* Therefore: **making the model honest is as valuable as making it
accurate.** Converting a confident-but-wrong frame into an abstention
improves the score. Converting an abstention into a correct confident fix
improves it twice as much. Both are real progress; design for either.
* A cell whose coverage falls below 0.2 scores FAIL — you cannot pass by
abstaining on everything.
**Do not optimize an error statistic.** Median, geometric mean, p10, p25 are
all still logged, and you may read them to understand *why* something worked,
but none of them is the target. Each was tried as the primary and each
rewarded something the aircraft does not want — the median rewarded a model
that guesses the map centre, and it reverted the first experiment that had
actually begun memorizing. If your design improves the geometric mean while
`usable_fix_rate` stays flat, it has not helped the product.
## Your job, in order
0. **Skim the library (optional input).** `autoresearch/library.md` holds
the human researcher's inspiration notes. They do not fix your answer
and you are free to ignore them — pick an entry up only when it
genuinely fits your read of the history, and if you build on one, say
so in your hypothesis.
1. **Review the research history.** Query the lineage DB:
`sqlite3 experiments.sqlite "SELECT id, title, category, hypothesis, expected_outcome, result, conclusion, primary_metric, kept FROM experiments ORDER BY id DESC LIMIT 15;"`
Note which hypotheses were supported/refuted. Do not repeat a refuted
experiment without a materially new angle.
**Plateau rule (advisory only on this branch):** the harness's automatic
pivot enforcement (mandatory-pivot preamble, backbone-carry rejection) is
disabled on berlin-slim — see CLAUDE.md "BRANCH OVERRIDE". Nothing will
force or reject a pivot below. That said, the underlying discipline still
applies by judgment: if three or more consecutive experiments were
reverted, don't attempt another variation of the last refuted mechanism.
Either pick a design family absent from the history (pretrained init,
training-scale/coverage, a different coordinate parameterization, capacity
allocation, decode resolution, …) or attack the bottleneck the refuted hypotheses
jointly point at — checking, via `arch_json`
(`SELECT arch_json FROM experiments WHERE kind='development' ORDER BY id
DESC LIMIT 10;`), which stage names never carry `"changed": true`, since a
losing streak is usually one unquestioned stage (trunk / descriptor /
decode) with everything else churning around it, not a missing family.
2. **Design ONE focused experiment** — proper experiment design, pre-registered
before you touch code. Write it to `runs/pending_experiment.json`:
```json
{
"title": "one-line name",
"category": "architecture|loss|augmentation|relighting|training|quantization|other",
"hypothesis": "what you believe is limiting the metric and why this change addresses it",
"method": "the ONE focused change, concretely (files, mechanism)",
"expected_outcome": "predicted effect on mission_score, and on usable_fix_rate / false_fix_rate specifically, quantified if possible",
"init_strategy": "from-scratch | pretrained:<name>",
"eli5": "2-4 sentences for a smart non-ML reader: what you changed and why it might help, in everyday language — analogies welcome, zero jargon",
"architecture": {"stages": [
{"name": "Camera frame", "detail": "128×128 px daytime crop, Berlin only, no synthetic lighting variants", "changed": false},
{"name": "Feature extractor", "detail": "plain-language description", "changed": false},
{"name": "…", "detail": "…", "changed": true}
]}
}
```
Do NOT draw the architecture figure here — that is a separate agent's
job, run later, only for experiments that actually reach training (see
`autoresearch/prompt_figure.md`). Spending time perfecting an SVG for a
design that might get rejected before it ever runs was wasted work; this
stage's only output is the decision itself.
`eli5` and `architecture` feed the human-facing gallery. `architecture.stages`
is the model's inference path left-to-right, camera frame → (lat, lon,
confidence) output — one box per stage, plain-language `detail`,
`"changed": true` ONLY on the stages this experiment touches. Reuse the
previous experiment's stage names verbatim wherever a stage is unchanged
(check `SELECT arch_json FROM experiments WHERE arch_json IS NOT NULL ORDER BY id DESC LIMIT 1;`) —
stage names must stay consistent across experiments since the later
figure-drawing stage and plateaucheck both compare/track them by name.
A change that only affects training (loss, augmentation, schedule) keeps
the inference stages unchanged and adds one final stage with
`"train_only": true` describing the training signal.
3. **Write the implementation brief.** You do NOT edit `model/` yourself —
a separate implementation agent applies your design, seeing only the
current code plus what you pre-registered. Add one more field to
`runs/pending_experiment.json`:
```
"implementation_brief": "exact file-level instructions: which functions/
blocks in model/model.py and model/train.py change and how, what stays
untouched, and every contract to preserve"
```
Be precise enough that a competent engineer with no other context
implements it in one pass. Always restate the fixed contracts:
`train.py`'s CLI and the ONNX export contract in `model/model.py`'s
docstring — the frozen scorer depends on them.
## Hard rules
- Edit ONLY `runs/pending_experiment.json`. You never edit `model/` (the
implementation stage does) and files listed in `/FROZEN` are off-limits;
the harness hard-reverts any change to them.
- ONE focused change per experiment — if you can't describe it in one sentence,
it's too big. Prefer architectural/procedural novelty over hyperparameter
nudges (§3): changing a learning rate is a weak experiment; changing the
coordinate parameterization, loss family, capacity allocation, decode
resolution, or model topology is a strong one.
- Do not run training yourself; the harness does that.
- Stay within the deployment gates: exported ONNX ≤ 4 MiB per area, host
latency proxy ≤ 250 ms (see pipeline/score.py).
- Keep one experiment tractable to train. This branch trains Berlin only, on
a single GPU; an inherently expensive per-sample mechanism (e.g. many-round
iterative solves over thousands of votes per crop) can still push a round
into hours. Budget the per-crop cost so a round finishes in a sensible
wall-time — an idea that can't be evaluated in a round can't be kept.What the training imagery looks like (example patches — illustration, not the training set)In the current configuration, training and eval use the raw daytime reference imagery as fetched — no synthetic relighting, no low-light sensor simulation (that machinery still exists in the frozen pipeline and is used by the full spec's 6-lighting-condition setup, just switched off here). Below, one example 256 m patch per area, as-is. This illustrates the dataset, not this experiment's performance — the actual training set is thousands of distinct crops (see “training data” above), and this rendering only changes if the source imagery is re-fetched. berlin | |||||||||||||||||||||||||||
| ▸ | 5.4 | Local belief pooling decode: confidence = softmax mass on the 3x3 cell neighborhood, fix = its mass-weighted centroid
7958b03d
new best: 0.183 → 0.090 |
architecture | 0.090 | 20 m 36 s | $2.75 | kept | ||||||||||||||||||||
|
The design under test — technical diagram — gray = frozen contract · ink = the current design · red = this experiment's change In plain words The model localizes by picking which 128-metre map tile it is over, like answering a multiple-choice question, and it only speaks up when one answer gets enough of its vote. But when the drone flies near the border between two tiles, the model honestly splits its vote between them — and then stays silent, even though both answers point at the same street corner. That silence was most of our remaining score. Now neighbouring tiles get to pool their votes: if the belief lands in one tight cluster on the map, we place the fix at the cluster's balance point and speak up; if it is scattered across the city, we stay silent exactly as before. What it cost
by model — claude-fable-5 $1.95 · claude-opus-5 $0.80 · claude-haiku-4-5-20251001 $0.00 The agent lines are the equivalent API cost of the tokens each agent used, as recorded by the harness when it ran. The research ran on a claude.ai Max subscription, so that is what it would have cost billed per token — not money that left the account. The GPU line is real spend. Why it's the new best The best design so far. It improved the mission score from 0.183 to 0.090 — 0.093 better, beating the previous champion — so its code was committed as the new best, and later experiments branch from here. Hypothesis Experiment 3's champion is accurate but needlessly silent: of the 0.1827 mission score, 0.1475 is abstention and only 0.0175 is false fixes, while its confident frames are 98% accurate (usable 0.835 vs false 0.0175). Eval views stand 11-17 m off-lattice, so a view near a cell border legitimately splits softmax mass between adjacent 128 m cells; with confidence defined as mass on the single argmax cell, a 2-way border split can and a 4-way corner split must fall below the scorer's 0.3 threshold even though every competing hypothesis names the same neighborhood. That is label ambiguity, not place ambiguity, and the current decode cannot tell them apart. Pooling belief over the 3x3 block centred on the argmax cell makes confidence measure 'does the model know WHERE it is' rather than 'does it know which arbitrary tile label applies', converting adjacent-split abstentions into fixes while leaving genuine far-field ambiguity (mass scattered across distant cells) below threshold. Placing the fix at the mass-weighted centroid of that local block should also cut error near borders, since the centroid interpolates between competing cell centres instead of snapping to one. Method Change ONLY the deployed decode in model/model.py's CellLocNet.forward(); training_outputs(), loss_fn, train.py, and the full-lattice training schedule stay byte-identical to the exp-3 champion. New forward(): p = softmax(logits); idx = argmax; build a [B, n_cells] mask selecting cells whose centre lies within 1.6 cell-pitches (Chebyshev, per axis) of the winning cell's centre — computed on the fly from the existing cell_u/cell_v buffers with Sub/Abs/Less/Mul ops so it exports cleanly to ONNX and stays edge-safe with no duplicate counting; q = p * mask; conf = sum(q); fix = sum(q/conf * cell centres) + the existing tanh offset head contribution, clamped to [0,1] as before. conf is a sub-sum of a softmax so it remains in [0,1] and is >= the old single-cell conf by construction: abstentions can only convert, never appear. Expected outcome mission_score drops from 0.1827 to <= 0.135, target ~0.10. Mechanically: abstain_rate 0.1475 -> <= 0.06 (adjacent splits rescued; genuinely ambiguous frames stay abstained), usable_fix_rate 0.835 -> >= 0.90, false_fix_rate 0.0175 -> <= 0.035 (some rescued frames will be wrong; the 98% accuracy of currently-confident frames bounds the risk). Standing metric check: the gain must appear in usable_fix_rate itself. Secondary: median error should fall from 48 m toward ~35 m via centroid interpolation (logged only). Informative null: if abstain_rate barely moves, the abstentions are far-field aliasing rather than adjacent splits, redirecting the next experiment at trunk/descriptor capacity. Gates unaffected: adds only [1, 2970]-sized elementwise ops (model 3.05 MB < 4 MiB, latency 1.6 ms << 250 ms). Result mission score = 0.09018 (previous best 0.182715) — (1 - usable_fix_rate) + false_fix_rate, worst bucket; full breakdown in metrics.json Conclusion KEPT — mission score improved (0.182715 -> 0.09018); change committed as 7958b03 Where the model was tested — and how far off it was Each map is one full test area. Every dot is one held-out viewpoint: the ground under it was mapped during training, but this exact framing — 11–17 m off the nearest training vantage, at its own rotation — is one the model has never seen. It was shown a 128 m crop centered there and asked for its position. Dot color = the distance between its answer and the truth — green ≤ 100 m (at goal), amber ≤ 250 m, red beyond. A working model turns these maps green; spatial clusters of red reveal which parts of an area confuse it. One real test, end to end
→
4.1 m
miss, this crop 0.56
self-reported confidence Scoreboard — mission score per area × lighting
mission score per cell — (1 − usable-fix rate) + false-fix
rate; the worst cell (underlined) is the experiment's score. red = failed cell,
ink = better than abstaining on everything
Fits the aircraft? all deployment gates passed
largest per-area model 3,007 KB (limit 4,096 KB) · single-frame inference 1.6 ms on one CPU thread (proxy limit 250 ms)
crops are sampled fresh each run from the frozen train split (~45,000 distinct positions per area, times random rotation), never from eval blocks; how many to use is the experiment's own choice
Where this experiment's time went: design (Haiku) 3 m 10 s · implement (Opus) 1 m 43 s · train 1 area 15 m 26 s · score 5 s · samples 1 s · publish 3 s · whole experiment 20 m 42 s
ts 2026-07-31T11:20:39 · commit 7958b03db79b ·
parent 235ba52f1ccf · artifacts runs/20260731_110003_experiment4 ·
agent model — · took 20 m 36 s
The code for this experiment — the exact code that trained itThe exact bytes that trained this run, snapshotted before the keep/revert decision could touch them. · on GitHub ↗ model/model.py145 lines model/train.py149 lines The exact prompt given to the headless agent# Autoresearch experiment — the design stage
You are one experiment of an autonomous research loop for UAV geolocalization.
Read `CLAUDE.md`'s "BRANCH OVERRIDE" section first, then §3, §4 and §6.
The harness (loop.sh) will train, score, log, and keep/revert AFTER you exit —
you only design the experiment.
## THE GOAL: MEMORIZE ONE BOUNDING BOX. OVERFITTING IS THE FEATURE.
This branch builds a **visual memory of Berlin**, not a model that reasons
about aerial imagery in general. One model, one bbox, deployed only over that
bbox. It is *supposed* to know this specific city by heart. You are not trying
to generalize to other places, other lighting, or unmapped ground — none of
those are measured, and designing for them costs capacity you need for
memorizing.
**How the split works, because it determines what a good design looks like:**
* **Training covers ALL of Berlin** — every lattice position, whole raster.
The model is shown the entire area it must memorize.
* **Eval holds out VIEWPOINTS, not REGIONS.** Every eval frame stands on
ground that WAS in training, but is framed 11–17 m off the nearest training
vantage and at its own rotation. Mapped ground, novel view — exactly what an
aircraft over a mapped city faces.
* So the task is: **recall a memorized place from a viewpoint you have not
seen.** It is NOT: infer the location of ground you were never shown.
* A small 1-in-32-block region IS genuinely untrained, scored separately and
logged only. It can never affect keep/revert. Ignore it when designing;
it exists to reveal whether the model has spatial structure at all.
This matters because the split used to hold out regions, which left 28% of
Berlin untrained and put 100% of eval questions on never-seen ground — an
unanswerable task for a memorization model, and the likely reason ~60 earlier
experiments plateaued. Do not design as if that were still true.
**Scope:** ONE locale (Berlin), ONE lighting condition (raw daytime imagery,
no synthetic relighting — the relighting machinery is disabled on this branch).
There is no cross-lighting robustness to reason about and no other area's
texture to generalize to.
## THE METRIC IS THE PRODUCT REQUIREMENT, NOT AN ERROR STATISTIC
The aircraft takes a vision fix every 5–10 s and it is its ONLY drift
correction. Per frame exactly three things can happen:
| outcome | meaning |
|---|---|
| confident **and** within 100 m | **USABLE FIX** — this is the product |
| not confident | abstains — safe, it waits for the next frame |
| confident **but** outside 100 m | **FALSE FIX** — dangerous, it injects a wrong position into navigation |
mission_score = (1 - usable_fix_rate) + false_fix_rate [MINIMIZED]
* **0.0** = every frame a usable fix. **1.0** = abstains everywhere.
**2.0** = confidently wrong everywhere — *strictly worse than silence*,
because a confidently wrong fix corrupts navigation while an abstention
merely costs time.
* Therefore: **making the model honest is as valuable as making it
accurate.** Converting a confident-but-wrong frame into an abstention
improves the score. Converting an abstention into a correct confident fix
improves it twice as much. Both are real progress; design for either.
* A cell whose coverage falls below 0.2 scores FAIL — you cannot pass by
abstaining on everything.
**Do not optimize an error statistic.** Median, geometric mean, p10, p25 are
all still logged, and you may read them to understand *why* something worked,
but none of them is the target. Each was tried as the primary and each
rewarded something the aircraft does not want — the median rewarded a model
that guesses the map centre, and it reverted the first experiment that had
actually begun memorizing. If your design improves the geometric mean while
`usable_fix_rate` stays flat, it has not helped the product.
## Your job, in order
0. **Skim the library (optional input).** `autoresearch/library.md` holds
the human researcher's inspiration notes. They do not fix your answer
and you are free to ignore them — pick an entry up only when it
genuinely fits your read of the history, and if you build on one, say
so in your hypothesis.
1. **Review the research history.** Query the lineage DB:
`sqlite3 experiments.sqlite "SELECT id, title, category, hypothesis, expected_outcome, result, conclusion, primary_metric, kept FROM experiments ORDER BY id DESC LIMIT 15;"`
Note which hypotheses were supported/refuted. Do not repeat a refuted
experiment without a materially new angle.
**Plateau rule (advisory only on this branch):** the harness's automatic
pivot enforcement (mandatory-pivot preamble, backbone-carry rejection) is
disabled on berlin-slim — see CLAUDE.md "BRANCH OVERRIDE". Nothing will
force or reject a pivot below. That said, the underlying discipline still
applies by judgment: if three or more consecutive experiments were
reverted, don't attempt another variation of the last refuted mechanism.
Either pick a design family absent from the history (pretrained init,
training-scale/coverage, a different coordinate parameterization, capacity
allocation, decode resolution, …) or attack the bottleneck the refuted hypotheses
jointly point at — checking, via `arch_json`
(`SELECT arch_json FROM experiments WHERE kind='development' ORDER BY id
DESC LIMIT 10;`), which stage names never carry `"changed": true`, since a
losing streak is usually one unquestioned stage (trunk / descriptor /
decode) with everything else churning around it, not a missing family.
2. **Design ONE focused experiment** — proper experiment design, pre-registered
before you touch code. Write it to `runs/pending_experiment.json`:
```json
{
"title": "one-line name",
"category": "architecture|loss|augmentation|relighting|training|quantization|other",
"hypothesis": "what you believe is limiting the metric and why this change addresses it",
"method": "the ONE focused change, concretely (files, mechanism)",
"expected_outcome": "predicted effect on mission_score, and on usable_fix_rate / false_fix_rate specifically, quantified if possible",
"init_strategy": "from-scratch | pretrained:<name>",
"eli5": "2-4 sentences for a smart non-ML reader: what you changed and why it might help, in everyday language — analogies welcome, zero jargon",
"architecture": {"stages": [
{"name": "Camera frame", "detail": "128×128 px daytime crop, Berlin only, no synthetic lighting variants", "changed": false},
{"name": "Feature extractor", "detail": "plain-language description", "changed": false},
{"name": "…", "detail": "…", "changed": true}
]}
}
```
Do NOT draw the architecture figure here — that is a separate agent's
job, run later, only for experiments that actually reach training (see
`autoresearch/prompt_figure.md`). Spending time perfecting an SVG for a
design that might get rejected before it ever runs was wasted work; this
stage's only output is the decision itself.
`eli5` and `architecture` feed the human-facing gallery. `architecture.stages`
is the model's inference path left-to-right, camera frame → (lat, lon,
confidence) output — one box per stage, plain-language `detail`,
`"changed": true` ONLY on the stages this experiment touches. Reuse the
previous experiment's stage names verbatim wherever a stage is unchanged
(check `SELECT arch_json FROM experiments WHERE arch_json IS NOT NULL ORDER BY id DESC LIMIT 1;`) —
stage names must stay consistent across experiments since the later
figure-drawing stage and plateaucheck both compare/track them by name.
A change that only affects training (loss, augmentation, schedule) keeps
the inference stages unchanged and adds one final stage with
`"train_only": true` describing the training signal.
3. **Write the implementation brief.** You do NOT edit `model/` yourself —
a separate implementation agent applies your design, seeing only the
current code plus what you pre-registered. Add one more field to
`runs/pending_experiment.json`:
```
"implementation_brief": "exact file-level instructions: which functions/
blocks in model/model.py and model/train.py change and how, what stays
untouched, and every contract to preserve"
```
Be precise enough that a competent engineer with no other context
implements it in one pass. Always restate the fixed contracts:
`train.py`'s CLI and the ONNX export contract in `model/model.py`'s
docstring — the frozen scorer depends on them.
## Hard rules
- Edit ONLY `runs/pending_experiment.json`. You never edit `model/` (the
implementation stage does) and files listed in `/FROZEN` are off-limits;
the harness hard-reverts any change to them.
- ONE focused change per experiment — if you can't describe it in one sentence,
it's too big. Prefer architectural/procedural novelty over hyperparameter
nudges (§3): changing a learning rate is a weak experiment; changing the
coordinate parameterization, loss family, capacity allocation, decode
resolution, or model topology is a strong one.
- Do not run training yourself; the harness does that.
- Stay within the deployment gates: exported ONNX ≤ 4 MiB per area, host
latency proxy ≤ 250 ms (see pipeline/score.py).
- Keep one experiment tractable to train. This branch trains Berlin only, on
a single GPU; an inherently expensive per-sample mechanism (e.g. many-round
iterative solves over thousands of votes per crop) can still push a round
into hours. Budget the per-crop cost so a round finishes in a sensible
wall-time — an idea that can't be evaluated in a round can't be kept.What the training imagery looks like (example patches — illustration, not the training set)In the current configuration, training and eval use the raw daytime reference imagery as fetched — no synthetic relighting, no low-light sensor simulation (that machinery still exists in the frozen pipeline and is used by the full spec's 6-lighting-condition setup, just switched off here). Below, one example 256 m patch per area, as-is. This illustrates the dataset, not this experiment's performance — the actual training set is thousands of distinct crops (see “training data” above), and this rendering only changes if the source imagery is re-fetched. berlin | |||||||||||||||||||||||||||
| ▸ | 5.3 | Full-lattice coverage training for the map-cell classifier: every epoch is a complete pass over all 72,712 vantages
014a6196
new best: 2.001 → 0.183 |
training | 0.183 | 22 m 28 s | $3.61 | kept | ||||||||||||||||||||
|
The design under test — technical diagram — gray = frozen contract · ink = the current design · red = this experiment's change In plain words Last time we gave the model a multiple-choice map of Berlin — 3,000 tiles, point to the right one — but we only let it study for 84 seconds, and it never even saw most of the city. It answered every question with a shrug. This experiment changes nothing about the model itself; we just run the full study session: show it every spot in Berlin, eight times over, from different angles each time — about 20 minutes instead of 84 seconds. A student who has actually walked every street can start answering with confidence instead of shrugging. What it cost
by model — claude-fable-5 $2.61 · claude-opus-5 $0.99 · claude-haiku-4-5-20251001 $0.00 The agent lines are the equivalent API cost of the tokens each agent used, as recorded by the harness when it ran. The research ran on a claude.ai Max subscription, so that is what it would have cost billed per token — not money that left the account. The GPU line is real spend. Why it's the new best The best design so far. It improved the mission score from 2.001 to 0.183 — 1.818 better, beating the previous champion — so its code was committed as the new best, and later experiments branch from here. Hypothesis Experiment 2's map-cell classification decode was not refuted — it was starved. Its cross-entropy loss started at ln(2970)=8.0 (uniform over cells) and was still falling linearly (8.10 -> 6.22) when training stopped after 83.8 seconds: 8 epochs x 6,000 sampled crops = 48,000 total views for a 2,970-class memorization problem, i.e. ~16 views per cell across all headings, while most of the 72,712-position training lattice was never seen even once. Final mean true-cell probability was e^-6.2 = 0.2%, so max softmax never approached the scorer's 0.3 confidence threshold and the model abstained on every frame — the coverage<0.2 FAIL, not a verdict on the parameterization. The limiting factor is training coverage, not architecture: a model asked to memorize all of Berlin must actually be shown all of Berlin, repeatedly. Measured throughput (48k views in 84s incl. extraction, ~565 views/s on MPS) says a full-lattice pass costs ~2 minutes, so complete coverage was affordable all along — exp 2 used less than 1% of a sensible training budget. Method Restore experiment 2's CellLocNet (model.py verbatim from runs/20260731_102754_experiment2/model_src/model.py — conv trunk to 160 ch, 2,970-way cell softmax over 128 m cells, within-cell offset head, confidence = winning-cell softmax mass, decode inside the ONNX graph) and change ONLY the training procedure in model/train.py: each epoch is now one full pass over ALL 72,712 lattice positions with a fresh random rotation per position per epoch (streamed in shuffled chunks of 4,096 to bound memory), instead of a 6,000-crop random subsample. With the harness's --epochs 8 this gives 581,696 total views (~196 per cell, 12x exp 2) in a projected ~20 min wall. Cosine LR decay 1e-3 -> 1e-4 across the run (a constant LR is wrong for a 12x-longer schedule). Loss, label smoothing 0.05, batch size 64, architecture, and export all unchanged from exp 2. Expected outcome Primary prediction: mission_score drops from the 2.00075 baseline to <= 1.2, target < 1.0. Mechanically: train CE should now approach memorization (well under 2.0 by the last pass; per-pass top-1 train accuracy logged to verify), pushing max softmax above the 0.3 threshold on memorized views, so coverage rises from 0.0 to >= 0.4 and clears the MIN_COVERAGE=0.2 FAIL gate. Because a correctly classified 128 m cell centre is within 90.5 m by construction, correct confident cells convert directly into usable fixes: usable_fix_rate >= 0.35, false_fix_rate <= 0.35. Standing metric check: the improvement must appear in usable_fix_rate itself. Informative failure: if 196 views/cell still leaves coverage near 0 (another 1e9), the bottleneck is not scale but the 160-d GAP descriptor's capacity or rotation invariance — which would redirect the next experiment to capacity/decode, with the per-pass accuracy curve as evidence either way. Result mission score = 0.182715 (previous best 2.00075) — (1 - usable_fix_rate) + false_fix_rate, worst bucket; full breakdown in metrics.json Conclusion KEPT — mission score improved (2.00075 -> 0.182715); change committed as 014a619 Where the model was tested — and how far off it was Each map is one full test area. Every dot is one held-out viewpoint: the ground under it was mapped during training, but this exact framing — 11–17 m off the nearest training vantage, at its own rotation — is one the model has never seen. It was shown a 128 m crop centered there and asked for its position. Dot color = the distance between its answer and the truth — green ≤ 100 m (at goal), amber ≤ 250 m, red beyond. A working model turns these maps green; spatial clusters of red reveal which parts of an area confuse it. One real test, end to end
→
53.6 m
miss, this crop 0.32
self-reported confidence Scoreboard — mission score per area × lighting
mission score per cell — (1 − usable-fix rate) + false-fix
rate; the worst cell (underlined) is the experiment's score. red = failed cell,
ink = better than abstaining on everything
Fits the aircraft? all deployment gates passed
largest per-area model 2,982 KB (limit 4,096 KB) · single-frame inference 1.6 ms on one CPU thread (proxy limit 250 ms)
crops are sampled fresh each run from the frozen train split (~45,000 distinct positions per area, times random rotation), never from eval blocks; how many to use is the experiment's own choice
Where this experiment's time went: design (Haiku) 4 m 58 s · implement (Opus) 1 m 55 s · train 1 area 15 m 19 s · score 6 s · samples 1 s · publish 2 s · whole experiment 22 m 33 s
ts 2026-07-31T10:59:55 · commit 014a61968297 ·
parent 3f53bcf4f5be · artifacts runs/20260731_103727_experiment3 ·
agent model — · took 22 m 28 s
The code for this experiment — the exact code that trained itThe exact bytes that trained this run, snapshotted before the keep/revert decision could touch them. · on GitHub ↗ model/model.py122 lines model/train.py149 lines The exact prompt given to the headless agent# Autoresearch experiment — the design stage
You are one experiment of an autonomous research loop for UAV geolocalization.
Read `CLAUDE.md`'s "BRANCH OVERRIDE" section first, then §3, §4 and §6.
The harness (loop.sh) will train, score, log, and keep/revert AFTER you exit —
you only design the experiment.
## THE GOAL: MEMORIZE ONE BOUNDING BOX. OVERFITTING IS THE FEATURE.
This branch builds a **visual memory of Berlin**, not a model that reasons
about aerial imagery in general. One model, one bbox, deployed only over that
bbox. It is *supposed* to know this specific city by heart. You are not trying
to generalize to other places, other lighting, or unmapped ground — none of
those are measured, and designing for them costs capacity you need for
memorizing.
**How the split works, because it determines what a good design looks like:**
* **Training covers ALL of Berlin** — every lattice position, whole raster.
The model is shown the entire area it must memorize.
* **Eval holds out VIEWPOINTS, not REGIONS.** Every eval frame stands on
ground that WAS in training, but is framed 11–17 m off the nearest training
vantage and at its own rotation. Mapped ground, novel view — exactly what an
aircraft over a mapped city faces.
* So the task is: **recall a memorized place from a viewpoint you have not
seen.** It is NOT: infer the location of ground you were never shown.
* A small 1-in-32-block region IS genuinely untrained, scored separately and
logged only. It can never affect keep/revert. Ignore it when designing;
it exists to reveal whether the model has spatial structure at all.
This matters because the split used to hold out regions, which left 28% of
Berlin untrained and put 100% of eval questions on never-seen ground — an
unanswerable task for a memorization model, and the likely reason ~60 earlier
experiments plateaued. Do not design as if that were still true.
**Scope:** ONE locale (Berlin), ONE lighting condition (raw daytime imagery,
no synthetic relighting — the relighting machinery is disabled on this branch).
There is no cross-lighting robustness to reason about and no other area's
texture to generalize to.
## THE METRIC IS THE PRODUCT REQUIREMENT, NOT AN ERROR STATISTIC
The aircraft takes a vision fix every 5–10 s and it is its ONLY drift
correction. Per frame exactly three things can happen:
| outcome | meaning |
|---|---|
| confident **and** within 100 m | **USABLE FIX** — this is the product |
| not confident | abstains — safe, it waits for the next frame |
| confident **but** outside 100 m | **FALSE FIX** — dangerous, it injects a wrong position into navigation |
mission_score = (1 - usable_fix_rate) + false_fix_rate [MINIMIZED]
* **0.0** = every frame a usable fix. **1.0** = abstains everywhere.
**2.0** = confidently wrong everywhere — *strictly worse than silence*,
because a confidently wrong fix corrupts navigation while an abstention
merely costs time.
* Therefore: **making the model honest is as valuable as making it
accurate.** Converting a confident-but-wrong frame into an abstention
improves the score. Converting an abstention into a correct confident fix
improves it twice as much. Both are real progress; design for either.
* A cell whose coverage falls below 0.2 scores FAIL — you cannot pass by
abstaining on everything.
**Do not optimize an error statistic.** Median, geometric mean, p10, p25 are
all still logged, and you may read them to understand *why* something worked,
but none of them is the target. Each was tried as the primary and each
rewarded something the aircraft does not want — the median rewarded a model
that guesses the map centre, and it reverted the first experiment that had
actually begun memorizing. If your design improves the geometric mean while
`usable_fix_rate` stays flat, it has not helped the product.
## Your job, in order
0. **Skim the library (optional input).** `autoresearch/library.md` holds
the human researcher's inspiration notes. They do not fix your answer
and you are free to ignore them — pick an entry up only when it
genuinely fits your read of the history, and if you build on one, say
so in your hypothesis.
1. **Review the research history.** Query the lineage DB:
`sqlite3 experiments.sqlite "SELECT id, title, category, hypothesis, expected_outcome, result, conclusion, primary_metric, kept FROM experiments ORDER BY id DESC LIMIT 15;"`
Note which hypotheses were supported/refuted. Do not repeat a refuted
experiment without a materially new angle.
**Plateau rule (advisory only on this branch):** the harness's automatic
pivot enforcement (mandatory-pivot preamble, backbone-carry rejection) is
disabled on berlin-slim — see CLAUDE.md "BRANCH OVERRIDE". Nothing will
force or reject a pivot below. That said, the underlying discipline still
applies by judgment: if three or more consecutive experiments were
reverted, don't attempt another variation of the last refuted mechanism.
Either pick a design family absent from the history (pretrained init,
training-scale/coverage, a different coordinate parameterization, capacity
allocation, decode resolution, …) or attack the bottleneck the refuted hypotheses
jointly point at — checking, via `arch_json`
(`SELECT arch_json FROM experiments WHERE kind='development' ORDER BY id
DESC LIMIT 10;`), which stage names never carry `"changed": true`, since a
losing streak is usually one unquestioned stage (trunk / descriptor /
decode) with everything else churning around it, not a missing family.
2. **Design ONE focused experiment** — proper experiment design, pre-registered
before you touch code. Write it to `runs/pending_experiment.json`:
```json
{
"title": "one-line name",
"category": "architecture|loss|augmentation|relighting|training|quantization|other",
"hypothesis": "what you believe is limiting the metric and why this change addresses it",
"method": "the ONE focused change, concretely (files, mechanism)",
"expected_outcome": "predicted effect on mission_score, and on usable_fix_rate / false_fix_rate specifically, quantified if possible",
"init_strategy": "from-scratch | pretrained:<name>",
"eli5": "2-4 sentences for a smart non-ML reader: what you changed and why it might help, in everyday language — analogies welcome, zero jargon",
"architecture": {"stages": [
{"name": "Camera frame", "detail": "128×128 px daytime crop, Berlin only, no synthetic lighting variants", "changed": false},
{"name": "Feature extractor", "detail": "plain-language description", "changed": false},
{"name": "…", "detail": "…", "changed": true}
]}
}
```
Do NOT draw the architecture figure here — that is a separate agent's
job, run later, only for experiments that actually reach training (see
`autoresearch/prompt_figure.md`). Spending time perfecting an SVG for a
design that might get rejected before it ever runs was wasted work; this
stage's only output is the decision itself.
`eli5` and `architecture` feed the human-facing gallery. `architecture.stages`
is the model's inference path left-to-right, camera frame → (lat, lon,
confidence) output — one box per stage, plain-language `detail`,
`"changed": true` ONLY on the stages this experiment touches. Reuse the
previous experiment's stage names verbatim wherever a stage is unchanged
(check `SELECT arch_json FROM experiments WHERE arch_json IS NOT NULL ORDER BY id DESC LIMIT 1;`) —
stage names must stay consistent across experiments since the later
figure-drawing stage and plateaucheck both compare/track them by name.
A change that only affects training (loss, augmentation, schedule) keeps
the inference stages unchanged and adds one final stage with
`"train_only": true` describing the training signal.
3. **Write the implementation brief.** You do NOT edit `model/` yourself —
a separate implementation agent applies your design, seeing only the
current code plus what you pre-registered. Add one more field to
`runs/pending_experiment.json`:
```
"implementation_brief": "exact file-level instructions: which functions/
blocks in model/model.py and model/train.py change and how, what stays
untouched, and every contract to preserve"
```
Be precise enough that a competent engineer with no other context
implements it in one pass. Always restate the fixed contracts:
`train.py`'s CLI and the ONNX export contract in `model/model.py`'s
docstring — the frozen scorer depends on them.
## Hard rules
- Edit ONLY `runs/pending_experiment.json`. You never edit `model/` (the
implementation stage does) and files listed in `/FROZEN` are off-limits;
the harness hard-reverts any change to them.
- ONE focused change per experiment — if you can't describe it in one sentence,
it's too big. Prefer architectural/procedural novelty over hyperparameter
nudges (§3): changing a learning rate is a weak experiment; changing the
coordinate parameterization, loss family, capacity allocation, decode
resolution, or model topology is a strong one.
- Do not run training yourself; the harness does that.
- Stay within the deployment gates: exported ONNX ≤ 4 MiB per area, host
latency proxy ≤ 250 ms (see pipeline/score.py).
- Keep one experiment tractable to train. This branch trains Berlin only, on
a single GPU; an inherently expensive per-sample mechanism (e.g. many-round
iterative solves over thousands of votes per crop) can still push a round
into hours. Budget the per-crop cost so a round finishes in a sensible
wall-time — an idea that can't be evaluated in a round can't be kept.What the training imagery looks like (example patches — illustration, not the training set)In the current configuration, training and eval use the raw daytime reference imagery as fetched — no synthetic relighting, no low-light sensor simulation (that machinery still exists in the frozen pipeline and is used by the full spec's 6-lighting-condition setup, just switched off here). Below, one example 256 m patch per area, as-is. This illustrates the dataset, not this experiment's performance — the actual training set is thousands of distinct crops (see “training data” above), and this rendering only changes if the source imagery is re-fetched. berlin | |||||||||||||||||||||||||||
| ▸ | 5.2 | Map-cell classification decode: pick 1 of ~3,000 128 m tiles, confidence = softmax mass
9011afe6
abstained on too many frames in a cell |
architecture | gated fail | 9 m 24 s | $4.25 | gated fail | ||||||||||||||||||||
|
The design under test — technical diagram — gray = frozen contract · ink = the current design · red = this experiment's change In plain words The old model answered 'where is this photo?' by guessing two GPS numbers directly — and when unsure, that kind of guess drifts toward the middle of the map, which is confidently wrong almost everywhere. The new model works like a mail sorter instead: Berlin is cut into about 3,000 postcode-sized tiles, and the model picks which tile the photo belongs to, then fine-tunes the spot inside it. The tiles are sized so that naming the right tile already counts as a good fix. Best of all, a multiple-choice answer comes with built-in honesty: if the model's belief is smeared across many tiles, its top choice scores low and the drone simply says 'not sure' and waits for the next frame — which is far safer than pointing at the wrong street with full confidence. What it cost
by model — claude-fable-5 $3.13 · claude-opus-5 $1.11 · claude-haiku-4-5-20251001 $0.00 The agent lines are the equivalent API cost of the tokens each agent used, as recorded by the harness when it ran. The research ran on a claude.ai Max subscription, so that is what it would have cost billed per token — not money that left the account. The GPU line is real spend. Why it failed the gate The model trained, but in at least one area × lighting combination it refused to answer on more than the allowed share of frames — and a model isn't allowed to pass by abstaining its way out of the hard cases. That cell is scored as a failure, which fails the whole experiment, so it was rolled back. Hypothesis The baseline fails for two coupled reasons visible in its metrics (coverage 1.0, false_fix_rate 1.0, p10 531 m): (1) sigmoid+MSE direct regression of (u,v) through a global-average-pooled feature is a mean-seeking objective — under ambiguity it interpolates toward the map centre instead of committing to a place, which is structurally wrong for a memorization task; (2) its confidence head is trained on 'error < half the map extent', a target that is always satisfied, so it confidently emits every guess and the mission score sits at the confidently-wrong-everywhere ceiling of 2.0. Reparameterizing localization as classification over a grid of 128 m map cells attacks both at once: cross-entropy over cells is multimodal and memorization-friendly (each cell gets its own logit — no averaging between competing hypotheses), and the softmax distribution is an intrinsic honesty signal — when belief is spread across many tiles the max probability drops below the scorer's 0.3 threshold and the frame becomes a safe abstention instead of a false fix. The cell size is chosen so the parameterization alone suffices: a 128 m cell's half-diagonal is 90.5 m < the 100 m usable radius, so a correctly classified cell centre is already a usable fix, with a small offset head adding margin. Method One change: replace the regression head/decode in model/model.py with a map-cell classification head plus within-cell offset, decoded inside the exported ONNX graph (argmax over cell logits -> gather precomputed cell-centre table -> add offset), with conf = max softmax probability; model/train.py switches the loss to cross-entropy on the centre cell + smooth-L1 on the within-cell offset, and resamples a fresh 6,000-crop lattice subset with fresh rotation angles each epoch (a coverage precondition of the mechanism: 2,970 cells cannot be covered by one fixed 6,000-crop draw, but 8 fresh draws see ~48k distinct vantages, ~16 per cell). No other stage changes; harness CLI and its 6000/8 budget respected as-is. Expected outcome mission_score drops from 2.00075 to <= 1.2, target < 1.0. Mechanically: usable_fix_rate rises from 0.0 to >= 0.25 (every correctly-classified confident cell is within 90.5 m by construction), false_fix_rate falls from 1.0 to <= 0.6 because low-softmax frames become abstentions rather than false fixes. Even the pessimistic outcome (model abstains heavily but keeps coverage >= 0.2 with decent accuracy on its confident subset) lands near 1.0, still a large improvement over 2.0. Standing metric check: the improvement must show up in usable_fix_rate itself, not only in the score. Residual risks, in order: coverage collapse below 0.2 (softmax never exceeds 0.3 -> FAIL 1e9; mitigated by light label smoothing only, 0.05, so a memorized cell can still reach ~0.95) and adjacent-cell confusion at cell borders (a near-miss into the neighbouring cell is a false fix; the offset head plus label-free CE sharpness should keep this bounded). Result mission score = 1000000000.0 (previous best 2.00075) — (1 - usable_fix_rate) + false_fix_rate, worst bucket; full breakdown in metrics.json Conclusion REVERTED — mission score did not improve (1000000000.0 vs best 2.00075); change discarded Where the model was tested — and how far off it was Each map is one full test area. Every dot is one held-out viewpoint: the ground under it was mapped during training, but this exact framing — 11–17 m off the nearest training vantage, at its own rotation — is one the model has never seen. It was shown a 128 m crop centered there and asked for its position. Dot color = the distance between its answer and the truth — green ≤ 100 m (at goal), amber ≤ 250 m, red beyond. A working model turns these maps green; spatial clusters of red reveal which parts of an area confuse it. One real test, end to end
→
457.5 m
miss, this crop 0.02
self-reported confidence Scoreboard — mission score per area × lighting
mission score per cell — (1 − usable-fix rate) + false-fix
rate; the worst cell (underlined) is the experiment's score. red = failed cell,
ink = better than abstaining on everything
Fits the aircraft? all deployment gates passed
largest per-area model 2,982 KB (limit 4,096 KB) · single-frame inference 1.6 ms on one CPU thread (proxy limit 250 ms)
crops are sampled fresh each run from the frozen train split (~45,000 distinct positions per area, times random rotation), never from eval blocks; how many to use is the experiment's own choice
Where this experiment's time went: design (Haiku) 5 m 19 s · implement (Opus) 2 m 22 s · train 1 area 1 m 25 s · score 5 s · samples 2 s · publish 3 s · whole experiment 9 m 30 s
ts 2026-07-31T10:37:18 · commit 9011afe60dec ·
parent 499b7aea219a · artifacts runs/20260731_102754_experiment2 ·
agent model — · took 9 m 24 s
The code for this experiment — the exact code that trained itThe exact bytes that trained this run, snapshotted before the keep/revert decision could touch them. model/model.py122 lines model/train.py124 lines The exact prompt given to the headless agent# Autoresearch experiment — the design stage
You are one experiment of an autonomous research loop for UAV geolocalization.
Read `CLAUDE.md`'s "BRANCH OVERRIDE" section first, then §3, §4 and §6.
The harness (loop.sh) will train, score, log, and keep/revert AFTER you exit —
you only design the experiment.
## THE GOAL: MEMORIZE ONE BOUNDING BOX. OVERFITTING IS THE FEATURE.
This branch builds a **visual memory of Berlin**, not a model that reasons
about aerial imagery in general. One model, one bbox, deployed only over that
bbox. It is *supposed* to know this specific city by heart. You are not trying
to generalize to other places, other lighting, or unmapped ground — none of
those are measured, and designing for them costs capacity you need for
memorizing.
**How the split works, because it determines what a good design looks like:**
* **Training covers ALL of Berlin** — every lattice position, whole raster.
The model is shown the entire area it must memorize.
* **Eval holds out VIEWPOINTS, not REGIONS.** Every eval frame stands on
ground that WAS in training, but is framed 11–17 m off the nearest training
vantage and at its own rotation. Mapped ground, novel view — exactly what an
aircraft over a mapped city faces.
* So the task is: **recall a memorized place from a viewpoint you have not
seen.** It is NOT: infer the location of ground you were never shown.
* A small 1-in-32-block region IS genuinely untrained, scored separately and
logged only. It can never affect keep/revert. Ignore it when designing;
it exists to reveal whether the model has spatial structure at all.
This matters because the split used to hold out regions, which left 28% of
Berlin untrained and put 100% of eval questions on never-seen ground — an
unanswerable task for a memorization model, and the likely reason ~60 earlier
experiments plateaued. Do not design as if that were still true.
**Scope:** ONE locale (Berlin), ONE lighting condition (raw daytime imagery,
no synthetic relighting — the relighting machinery is disabled on this branch).
There is no cross-lighting robustness to reason about and no other area's
texture to generalize to.
## THE METRIC IS THE PRODUCT REQUIREMENT, NOT AN ERROR STATISTIC
The aircraft takes a vision fix every 5–10 s and it is its ONLY drift
correction. Per frame exactly three things can happen:
| outcome | meaning |
|---|---|
| confident **and** within 100 m | **USABLE FIX** — this is the product |
| not confident | abstains — safe, it waits for the next frame |
| confident **but** outside 100 m | **FALSE FIX** — dangerous, it injects a wrong position into navigation |
mission_score = (1 - usable_fix_rate) + false_fix_rate [MINIMIZED]
* **0.0** = every frame a usable fix. **1.0** = abstains everywhere.
**2.0** = confidently wrong everywhere — *strictly worse than silence*,
because a confidently wrong fix corrupts navigation while an abstention
merely costs time.
* Therefore: **making the model honest is as valuable as making it
accurate.** Converting a confident-but-wrong frame into an abstention
improves the score. Converting an abstention into a correct confident fix
improves it twice as much. Both are real progress; design for either.
* A cell whose coverage falls below 0.2 scores FAIL — you cannot pass by
abstaining on everything.
**Do not optimize an error statistic.** Median, geometric mean, p10, p25 are
all still logged, and you may read them to understand *why* something worked,
but none of them is the target. Each was tried as the primary and each
rewarded something the aircraft does not want — the median rewarded a model
that guesses the map centre, and it reverted the first experiment that had
actually begun memorizing. If your design improves the geometric mean while
`usable_fix_rate` stays flat, it has not helped the product.
## Your job, in order
0. **Skim the library (optional input).** `autoresearch/library.md` holds
the human researcher's inspiration notes. They do not fix your answer
and you are free to ignore them — pick an entry up only when it
genuinely fits your read of the history, and if you build on one, say
so in your hypothesis.
1. **Review the research history.** Query the lineage DB:
`sqlite3 experiments.sqlite "SELECT id, title, category, hypothesis, expected_outcome, result, conclusion, primary_metric, kept FROM experiments ORDER BY id DESC LIMIT 15;"`
Note which hypotheses were supported/refuted. Do not repeat a refuted
experiment without a materially new angle.
**Plateau rule (advisory only on this branch):** the harness's automatic
pivot enforcement (mandatory-pivot preamble, backbone-carry rejection) is
disabled on berlin-slim — see CLAUDE.md "BRANCH OVERRIDE". Nothing will
force or reject a pivot below. That said, the underlying discipline still
applies by judgment: if three or more consecutive experiments were
reverted, don't attempt another variation of the last refuted mechanism.
Either pick a design family absent from the history (pretrained init,
training-scale/coverage, a different coordinate parameterization, capacity
allocation, decode resolution, …) or attack the bottleneck the refuted hypotheses
jointly point at — checking, via `arch_json`
(`SELECT arch_json FROM experiments WHERE kind='development' ORDER BY id
DESC LIMIT 10;`), which stage names never carry `"changed": true`, since a
losing streak is usually one unquestioned stage (trunk / descriptor /
decode) with everything else churning around it, not a missing family.
2. **Design ONE focused experiment** — proper experiment design, pre-registered
before you touch code. Write it to `runs/pending_experiment.json`:
```json
{
"title": "one-line name",
"category": "architecture|loss|augmentation|relighting|training|quantization|other",
"hypothesis": "what you believe is limiting the metric and why this change addresses it",
"method": "the ONE focused change, concretely (files, mechanism)",
"expected_outcome": "predicted effect on mission_score, and on usable_fix_rate / false_fix_rate specifically, quantified if possible",
"init_strategy": "from-scratch | pretrained:<name>",
"eli5": "2-4 sentences for a smart non-ML reader: what you changed and why it might help, in everyday language — analogies welcome, zero jargon",
"architecture": {"stages": [
{"name": "Camera frame", "detail": "128×128 px daytime crop, Berlin only, no synthetic lighting variants", "changed": false},
{"name": "Feature extractor", "detail": "plain-language description", "changed": false},
{"name": "…", "detail": "…", "changed": true}
]}
}
```
Do NOT draw the architecture figure here — that is a separate agent's
job, run later, only for experiments that actually reach training (see
`autoresearch/prompt_figure.md`). Spending time perfecting an SVG for a
design that might get rejected before it ever runs was wasted work; this
stage's only output is the decision itself.
`eli5` and `architecture` feed the human-facing gallery. `architecture.stages`
is the model's inference path left-to-right, camera frame → (lat, lon,
confidence) output — one box per stage, plain-language `detail`,
`"changed": true` ONLY on the stages this experiment touches. Reuse the
previous experiment's stage names verbatim wherever a stage is unchanged
(check `SELECT arch_json FROM experiments WHERE arch_json IS NOT NULL ORDER BY id DESC LIMIT 1;`) —
stage names must stay consistent across experiments since the later
figure-drawing stage and plateaucheck both compare/track them by name.
A change that only affects training (loss, augmentation, schedule) keeps
the inference stages unchanged and adds one final stage with
`"train_only": true` describing the training signal.
3. **Write the implementation brief.** You do NOT edit `model/` yourself —
a separate implementation agent applies your design, seeing only the
current code plus what you pre-registered. Add one more field to
`runs/pending_experiment.json`:
```
"implementation_brief": "exact file-level instructions: which functions/
blocks in model/model.py and model/train.py change and how, what stays
untouched, and every contract to preserve"
```
Be precise enough that a competent engineer with no other context
implements it in one pass. Always restate the fixed contracts:
`train.py`'s CLI and the ONNX export contract in `model/model.py`'s
docstring — the frozen scorer depends on them.
## Hard rules
- Edit ONLY `runs/pending_experiment.json`. You never edit `model/` (the
implementation stage does) and files listed in `/FROZEN` are off-limits;
the harness hard-reverts any change to them.
- ONE focused change per experiment — if you can't describe it in one sentence,
it's too big. Prefer architectural/procedural novelty over hyperparameter
nudges (§3): changing a learning rate is a weak experiment; changing the
coordinate parameterization, loss family, capacity allocation, decode
resolution, or model topology is a strong one.
- Do not run training yourself; the harness does that.
- Stay within the deployment gates: exported ONNX ≤ 4 MiB per area, host
latency proxy ≤ 250 ms (see pipeline/score.py).
- Keep one experiment tractable to train. This branch trains Berlin only, on
a single GPU; an inherently expensive per-sample mechanism (e.g. many-round
iterative solves over thousands of votes per crop) can still push a round
into hours. Budget the per-crop cost so a round finishes in a sensible
wall-time — an idea that can't be evaluated in a round can't be kept.What the training imagery looks like (example patches — illustration, not the training set)In the current configuration, training and eval use the raw daytime reference imagery as fetched — no synthetic relighting, no low-light sensor simulation (that machinery still exists in the frozen pipeline and is used by the full spec's 6-lighting-condition setup, just switched off here). Below, one example 256 m patch per area, as-is. This illustrates the dataset, not this experiment's performance — the actual training set is thousands of distinct crops (see “training data” above), and this rendering only changes if the source imagery is re-fetched. berlin | |||||||||||||||||||||||||||
| ▸ | 5.1 | Baseline seed: current model/ code, no design agent
92d1f12e
starting line: mission score 2.001 |
other | 2.001 | 47 s | — | kept | ||||||||||||||||||||
|
The design under test — technical diagram — gray = frozen contract · ink = the current design · red = this experiment's change In plain words This row is the starting line, not an idea being tested. The naive starting model is trained and measured so there is a real number on the board. It turns out to be the worst possible result -- confidently wrong on every single frame -- which is exactly what the new score is designed to expose. What it cost No agent accounting survives for this experiment — its run directory was deleted. That is a missing receipt, not a zero. Why it's the new best The first scoreable run of this lineage, so there was nothing to beat — it sets the mission score (2.001) that every later experiment must improve on. Its code is committed as the starting point later experiments branch from. Hypothesis Not a hypothesis -- a starting line. Run with SKIP_AGENT=1 so no design or implementation agent is involved: whatever is in model/ is trained and scored as-is to establish the number later experiments must beat, and to prove the harness works end to end on the mission-score metric. Method Train the current model/ code (the original from-scratch TinyLocNet) unchanged and score it with the frozen scorer. No code change proposed or applied. Expected outcome An honest starting mission score, so 'kept' from here on means genuinely better. Result mission score = 2.00075 (previous best 1e18) — (1 - usable_fix_rate) + false_fix_rate, worst bucket; full breakdown in metrics.json Conclusion KEPT — mission score improved (1e18 -> 2.00075); change committed as 92d1f12 Where the model was tested — and how far off it was Each map is one full test area. Every dot is one held-out viewpoint: the ground under it was mapped during training, but this exact framing — 11–17 m off the nearest training vantage, at its own rotation — is one the model has never seen. It was shown a 128 m crop centered there and asked for its position. Dot color = the distance between its answer and the truth — green ≤ 100 m (at goal), amber ≤ 250 m, red beyond. A working model turns these maps green; spatial clusters of red reveal which parts of an area confuse it. One real test, end to end
→
2.01 km
miss, this crop 0.99
self-reported confidence Scoreboard — mission score per area × lighting
mission score per cell — (1 − usable-fix rate) + false-fix
rate; the worst cell (underlined) is the experiment's score. red = failed cell,
ink = better than abstaining on everything
Fits the aircraft? all deployment gates passed
largest per-area model 385 KB (limit 4,096 KB) · single-frame inference 0.6 ms on one CPU thread (proxy limit 250 ms)
crops are sampled fresh each run from the frozen train split (~45,000 distinct positions per area, times random rotation), never from eval blocks; how many to use is the experiment's own choice
Where this experiment's time went: train 1 area 33 s · score 4 s · samples 1 s · publish 3 s · whole experiment 52 s
ts 2026-07-31T10:25:28 · commit 92d1f12e10c6 ·
parent 92d1f12e10c6 · artifacts runs/20260731_102441_experiment1 ·
agent model — · took 47 s
The code for this experiment — the exact code that trained itThe exact bytes that trained this run, snapshotted before the keep/revert decision could touch them. · on GitHub ↗ model/model.py79 lines model/train.py101 lines The exact prompt given to the headless agent# Autoresearch experiment — the design stage
You are one experiment of an autonomous research loop for UAV geolocalization.
Read `CLAUDE.md`'s "BRANCH OVERRIDE" section first, then §3, §4 and §6.
The harness (loop.sh) will train, score, log, and keep/revert AFTER you exit —
you only design the experiment.
## THE GOAL: MEMORIZE ONE BOUNDING BOX. OVERFITTING IS THE FEATURE.
This branch builds a **visual memory of Berlin**, not a model that reasons
about aerial imagery in general. One model, one bbox, deployed only over that
bbox. It is *supposed* to know this specific city by heart. You are not trying
to generalize to other places, other lighting, or unmapped ground — none of
those are measured, and designing for them costs capacity you need for
memorizing.
**How the split works, because it determines what a good design looks like:**
* **Training covers ALL of Berlin** — every lattice position, whole raster.
The model is shown the entire area it must memorize.
* **Eval holds out VIEWPOINTS, not REGIONS.** Every eval frame stands on
ground that WAS in training, but is framed 11–17 m off the nearest training
vantage and at its own rotation. Mapped ground, novel view — exactly what an
aircraft over a mapped city faces.
* So the task is: **recall a memorized place from a viewpoint you have not
seen.** It is NOT: infer the location of ground you were never shown.
* A small 1-in-32-block region IS genuinely untrained, scored separately and
logged only. It can never affect keep/revert. Ignore it when designing;
it exists to reveal whether the model has spatial structure at all.
This matters because the split used to hold out regions, which left 28% of
Berlin untrained and put 100% of eval questions on never-seen ground — an
unanswerable task for a memorization model, and the likely reason ~60 earlier
experiments plateaued. Do not design as if that were still true.
**Scope:** ONE locale (Berlin), ONE lighting condition (raw daytime imagery,
no synthetic relighting — the relighting machinery is disabled on this branch).
There is no cross-lighting robustness to reason about and no other area's
texture to generalize to.
## THE METRIC IS THE PRODUCT REQUIREMENT, NOT AN ERROR STATISTIC
The aircraft takes a vision fix every 5–10 s and it is its ONLY drift
correction. Per frame exactly three things can happen:
| outcome | meaning |
|---|---|
| confident **and** within 100 m | **USABLE FIX** — this is the product |
| not confident | abstains — safe, it waits for the next frame |
| confident **but** outside 100 m | **FALSE FIX** — dangerous, it injects a wrong position into navigation |
mission_score = (1 - usable_fix_rate) + false_fix_rate [MINIMIZED]
* **0.0** = every frame a usable fix. **1.0** = abstains everywhere.
**2.0** = confidently wrong everywhere — *strictly worse than silence*,
because a confidently wrong fix corrupts navigation while an abstention
merely costs time.
* Therefore: **making the model honest is as valuable as making it
accurate.** Converting a confident-but-wrong frame into an abstention
improves the score. Converting an abstention into a correct confident fix
improves it twice as much. Both are real progress; design for either.
* A cell whose coverage falls below 0.2 scores FAIL — you cannot pass by
abstaining on everything.
**Do not optimize an error statistic.** Median, geometric mean, p10, p25 are
all still logged, and you may read them to understand *why* something worked,
but none of them is the target. Each was tried as the primary and each
rewarded something the aircraft does not want — the median rewarded a model
that guesses the map centre, and it reverted the first experiment that had
actually begun memorizing. If your design improves the geometric mean while
`usable_fix_rate` stays flat, it has not helped the product.
## Your job, in order
0. **Skim the library (optional input).** `autoresearch/library.md` holds
the human researcher's inspiration notes. They do not fix your answer
and you are free to ignore them — pick an entry up only when it
genuinely fits your read of the history, and if you build on one, say
so in your hypothesis.
1. **Review the research history.** Query the lineage DB:
`sqlite3 experiments.sqlite "SELECT id, title, category, hypothesis, expected_outcome, result, conclusion, primary_metric, kept FROM experiments ORDER BY id DESC LIMIT 15;"`
Note which hypotheses were supported/refuted. Do not repeat a refuted
experiment without a materially new angle.
**Plateau rule (advisory only on this branch):** the harness's automatic
pivot enforcement (mandatory-pivot preamble, backbone-carry rejection) is
disabled on berlin-slim — see CLAUDE.md "BRANCH OVERRIDE". Nothing will
force or reject a pivot below. That said, the underlying discipline still
applies by judgment: if three or more consecutive experiments were
reverted, don't attempt another variation of the last refuted mechanism.
Either pick a design family absent from the history (pretrained init,
training-scale/coverage, a different coordinate parameterization, capacity
allocation, decode resolution, …) or attack the bottleneck the refuted hypotheses
jointly point at — checking, via `arch_json`
(`SELECT arch_json FROM experiments WHERE kind='development' ORDER BY id
DESC LIMIT 10;`), which stage names never carry `"changed": true`, since a
losing streak is usually one unquestioned stage (trunk / descriptor /
decode) with everything else churning around it, not a missing family.
2. **Design ONE focused experiment** — proper experiment design, pre-registered
before you touch code. Write it to `runs/pending_experiment.json`:
```json
{
"title": "one-line name",
"category": "architecture|loss|augmentation|relighting|training|quantization|other",
"hypothesis": "what you believe is limiting the metric and why this change addresses it",
"method": "the ONE focused change, concretely (files, mechanism)",
"expected_outcome": "predicted effect on mission_score, and on usable_fix_rate / false_fix_rate specifically, quantified if possible",
"init_strategy": "from-scratch | pretrained:<name>",
"eli5": "2-4 sentences for a smart non-ML reader: what you changed and why it might help, in everyday language — analogies welcome, zero jargon",
"architecture": {"stages": [
{"name": "Camera frame", "detail": "128×128 px daytime crop, Berlin only, no synthetic lighting variants", "changed": false},
{"name": "Feature extractor", "detail": "plain-language description", "changed": false},
{"name": "…", "detail": "…", "changed": true}
]}
}
```
Do NOT draw the architecture figure here — that is a separate agent's
job, run later, only for experiments that actually reach training (see
`autoresearch/prompt_figure.md`). Spending time perfecting an SVG for a
design that might get rejected before it ever runs was wasted work; this
stage's only output is the decision itself.
`eli5` and `architecture` feed the human-facing gallery. `architecture.stages`
is the model's inference path left-to-right, camera frame → (lat, lon,
confidence) output — one box per stage, plain-language `detail`,
`"changed": true` ONLY on the stages this experiment touches. Reuse the
previous experiment's stage names verbatim wherever a stage is unchanged
(check `SELECT arch_json FROM experiments WHERE arch_json IS NOT NULL ORDER BY id DESC LIMIT 1;`) —
stage names must stay consistent across experiments since the later
figure-drawing stage and plateaucheck both compare/track them by name.
A change that only affects training (loss, augmentation, schedule) keeps
the inference stages unchanged and adds one final stage with
`"train_only": true` describing the training signal.
3. **Write the implementation brief.** You do NOT edit `model/` yourself —
a separate implementation agent applies your design, seeing only the
current code plus what you pre-registered. Add one more field to
`runs/pending_experiment.json`:
```
"implementation_brief": "exact file-level instructions: which functions/
blocks in model/model.py and model/train.py change and how, what stays
untouched, and every contract to preserve"
```
Be precise enough that a competent engineer with no other context
implements it in one pass. Always restate the fixed contracts:
`train.py`'s CLI and the ONNX export contract in `model/model.py`'s
docstring — the frozen scorer depends on them.
## Hard rules
- Edit ONLY `runs/pending_experiment.json`. You never edit `model/` (the
implementation stage does) and files listed in `/FROZEN` are off-limits;
the harness hard-reverts any change to them.
- ONE focused change per experiment — if you can't describe it in one sentence,
it's too big. Prefer architectural/procedural novelty over hyperparameter
nudges (§3): changing a learning rate is a weak experiment; changing the
coordinate parameterization, loss family, capacity allocation, decode
resolution, or model topology is a strong one.
- Do not run training yourself; the harness does that.
- Stay within the deployment gates: exported ONNX ≤ 4 MiB per area, host
latency proxy ≤ 250 ms (see pipeline/score.py).
- Keep one experiment tractable to train. This branch trains Berlin only, on
a single GPU; an inherently expensive per-sample mechanism (e.g. many-round
iterative solves over thousands of votes per crop) can still push a round
into hours. Budget the per-crop cost so a round finishes in a sensible
wall-time — an idea that can't be evaluated in a round can't be kept.What the training imagery looks like (example patches — illustration, not the training set)In the current configuration, training and eval use the raw daytime reference imagery as fetched — no synthetic relighting, no low-light sensor simulation (that machinery still exists in the frozen pipeline and is used by the full spec's 6-lighting-condition setup, just switched off here). Below, one example 256 m patch per area, as-is. This illustrates the dataset, not this experiment's performance — the actual training set is thousands of distinct crops (see “training data” above), and this rendering only changes if the source imagery is re-fetched. berlin | |||||||||||||||||||||||||||
| ▸ | 4.4 | Re-implementation of lost experiment 2 — classify-then-refine: 32x32 map-cell softmax + within-cell offset replaces direct coordinate regression
c0f3cd4e
The loop kept this — it beat the running best under the metric then in force. On today's ruler it scores 1.840 — worse than silence. |
architecture | 1.840 | 3 m 36 s | — | KEPT | ||||||||||||||||||||
|
The design under test — technical diagram — gray = frozen contract · ink = the current design · red = this experiment's change In plain words An earlier experiment taught the model to answer a multiple-choice question — which of 1024 map squares is this photo from? — before pointing at the exact spot inside the winning square. That version genuinely found 8% of places within 100 meters, but our old scorecard was broken: it graded models on a statistic that favored playing it safe near the map's center, so the multiple-choice model was wrongly judged worse and its code was deleted. The scorecard is fixed now, and this experiment simply rebuilds that deleted model from its saved blueprint, so the research can continue from the best design we actually found. Hypothesis This is a faithful re-implementation, not a new idea. Experiment 2 replaced MSE coordinate regression with PlaNet-style cell classification plus a within-cell offset, and under the corrected geometric-mean metric it is the best result so far (1323.6 m geomean, 8.0% of held-out viewpoints within 100 m vs 0.0% for the baseline). It was reverted at the time by the old, broken median metric — which rewarded centre-guessing — and the revert destroyed its source; only the design record survives (runs/20260731_094303_experiment2/experiment.json). The original hypothesis stands as validated by that run: a single sigmoid (u,v) head trained with MSE over a multimodal answer space regresses to the map centroid under ambiguity, and reparameterizing the output as discrete cell classification (cross-entropy over a 32x32 grid, ~217 m cells) plus a teacher-forced offset removes that averaging failure mode. Restoring it gives the loop a correct champion to build from. Method Identical to experiment 2's registered method. In model/model.py only the head and loss change: keep TinyLocNet's conv trunk (~100k params) verbatim; replace the single Linear(128,3) head with three heads over the same GAP feature — cell logits Linear(128,1024), per-cell offsets Linear(128,2048) tanh-bounded to half a cell, and the same loose-BCE confidence head as the baseline. Inference decodes argmax cell center + gathered offset inside the exported ONNX graph, so the frozen [[u,v,conf]] contract is untouched. Loss becomes cross-entropy on the true cell + MSE on the true cell's offset (teacher-forced) + the baseline's 0.1-weighted confidence BCE. model/train.py changes one line (call the new raw-output training path). Grid is defined in normalized [0,1] map coordinates, so nothing is Berlin-specific. Conclusion at the time KEPT — metric improved (1780.5 -> 1323.62 m); change committed as c0f3cd4 Re-measured on today's ruler Mission score 1.840 — 8.0% of held-out frames give a usable fix, 92.0% are confident and wrong, 0.0% abstain. Median miss 2.14 km. Its run artifacts were deleted, so the usable- and false-fix rates were recovered arithmetically from the coverage and hit-rate this era's scorer logged at the time. The eval set and target were already identical to today's, so this is a derivation rather than an estimate. What it cost No agent accounting survives for this experiment — its run directory was deleted. That is a missing receipt, not a zero. What it was measured against then Optimised: worst-case geometric-mean error (m), scoring 1.32 km. Asked over: viewpoint holdout, daytime only, Berlin only. The evaluation was fixed here: held-out viewpoints over mapped ground instead of held-out regions. The metric was still an error statistic. Run artifacts were not kept. era Viewpoint holdout · ts 2026-07-31T10:12:13 ·
commit c0f3cd4e602d · artifacts runs/20260731_100837_experiment4 ·
took 3 m 36 s
The code for this experiment — its code, from the commitRead back out of this experiment’s own commit. It was kept, so the loop committed model/ and then recorded that commit — the tree is this experiment’s code. · on GitHub ↗model/model.py132 lines model/train.py101 lines | |||||||||||||||||||||||||||
| ▸ | 4.3 | Coverage over repetition: spend the same 48k-step budget on ~48k fresh-rotated distinct vantages instead of 6000 static crops replayed 8x
f74a8666
The loop discarded this — no improvement under the metric then in force. |
training | 1.995 | 6 m 36 s | — | DISCARDED | ||||||||||||||||||||
|
The design under test — technical diagram — gray = frozen contract · ink = the current design · red = this experiment's change In plain words This model is supposed to memorize what every part of Berlin looks like from above, like a taxi driver memorizing a city. But it turns out we were only ever showing it 8% of the flashcards — the same 6,000 photos over and over, always held at the same angle — and then quizzing it on the whole city. We didn't give it more study time; we made every flashcard in the same study session a new place, held at a new angle. Same effort, spent on seeing the whole city once instead of a small corner eight times. Hypothesis The metric is limited by training coverage, not architecture. The baseline's 1993.71 m is ~29% of Berlin's map extent (regression-to-the-mean), and experiment 2's head reparameterization changed nothing (2136.91 m, reverted) — its own pre-registered refutation signature says the trunk's features can't separate locations. But train.py shows why: training samples 6000 of ~78,000 lattice positions (~8%) ONCE, freezes one rotation per crop, and replays those 6000 static images for 8 epochs (29 s total). The frozen v2 split guarantees every eval viewpoint is 11-17 m from a lattice framing, yet the 6000-crop subsample silently re-creates the v1 pathology inside the training procedure: the nearest actually-trained vantage averages ~44 m from an eval viewpoint (Poisson nearest-neighbor at 6000 points over 47 km^2), with a mismatched frozen heading, and 92% of vantages are never shown to a model whose entire premise is memorizing its box. A memorization model cannot memorize a map it was never shown. Streaming the full lattice with fresh per-visit rotations at the SAME step count isolates data coverage/freshness as the single variable: any gain is attributable to the data diet, not extra compute. Method Rewrite model/train.py's data procedure only: replace load-once-static-tensor training (load_training_tensors -> one fixed 6000-crop tensor reused every epoch) with epoch-wise streaming over a global shuffled permutation of the FULL train lattice (~78k positions from pipeline.dataset.list_crops), consuming max_crops_per_bucket x n_buckets positions per epoch (6000/epoch at harness defaults, reshuffling when the permutation is exhausted), extracting each 128x128 crop on the fly per batch with a freshly drawn uniform(0,360) rotation at every visit. Architecture (model/model.py), loss, optimizer, batch size, and total gradient-step count are all unchanged — with harness defaults (EPOCHS=8, TRAIN_CROPS=6000) the model sees 48,000 presentations either way; this experiment changes only WHICH images those presentations are: ~48k distinct vantages (~62% of the lattice, mean eval-to-nearest-trained-vantage distance ~16 m) instead of 6000 vantages repeated 8x (~44 m). Conclusion at the time REVERTED under the corrected metric. It was KEPT at the time because the old median metric rewarded it for being a marginally better centre-guesser, despite locating almost nothing (1 good hit in ~500 vs experiment 2's 40). Re-measured on today's ruler Mission score 1.995 — 0.2% of held-out frames give a usable fix, 99.8% are confident and wrong, 0.0% abstain. Median miss 1.87 km. Its run artifacts were deleted, so the usable- and false-fix rates were recovered arithmetically from the coverage and hit-rate this era's scorer logged at the time. The eval set and target were already identical to today's, so this is a derivation rather than an estimate. What it cost No agent accounting survives for this experiment — its run directory was deleted. That is a missing receipt, not a zero. What it was measured against then Optimised: worst-case geometric-mean error (m), scoring 1.67 km. Asked over: viewpoint holdout, daytime only, Berlin only. The evaluation was fixed here: held-out viewpoints over mapped ground instead of held-out regions. The metric was still an error statistic. Run artifacts were not kept. era Viewpoint holdout · ts 2026-07-31T09:56:08 ·
commit f74a866699a9 · artifacts runs/20260731_094932_experiment3 ·
took 6 m 36 s
The code for this experiment — not retainedSource not retained. Until 2026-07-31 the loop discarded a reverted experiment’s code outright, and this row predates that fix. Its design record, its verdict and its exported model survive; the code does not. It is left blank rather than filled with the parent’s code, which is what this row’s recorded commit actually contains. | |||||||||||||||||||||||||||
| ▸ | 4.2 | Classify-then-refine: 32x32 map-cell softmax + within-cell offset replaces direct coordinate regression
4affa746
The loop kept this — it beat the running best under the metric then in force. On today's ruler it scores 1.840 — worse than silence. |
architecture | 1.840 | 6 m 20 s | — | KEPT | ||||||||||||||||||||
|
The design under test — technical diagram — gray = frozen contract · ink = the current design · red = this experiment's change In plain words The old model had to blurt out exact GPS coordinates in one shot, like being asked to point at a city map blindfolded — and when unsure, the math of its training pushes it to hedge toward the middle of the map, which is precisely wrong everywhere. The new model answers a multiple-choice question first: the map is divided into a 32-by-32 checkerboard of roughly 200-meter squares, and it picks which square the photo is from. Then a second small step points to the exact spot inside that square. Multiple choice can't hedge — you can't answer 'somewhere between Tegel and Tempelhof' by picking a spot in the middle — so being unsure no longer drags every guess toward the map's center. Hypothesis The baseline's 1993.71 m worst-case median is ~29% of Berlin's 6.9 km map extent — the signature of MSE regression-to-the-mean, not of weak features. A single sigmoid (u,v) head trained with MSE over a large multimodal answer space is pulled toward the map centroid whenever the features are ambiguous, so even correctly recognized locations get dragged inward. Reparameterizing the output as discrete spatial cell classification (cross-entropy over a 32x32 grid, ~217 m cells) plus a within-cell offset regressor removes the averaging failure mode entirely: cross-entropy places probability mass on the right cell without interpolating between distant hypotheses (PlaNet-style geo-cell classification; CenterNet-style heatmap+offset decoding is the same idea for keypoints). The trunk, data, and training budget stay identical, isolating the parameterization as the variable. Method In model/model.py only the head and loss change: keep TinyLocNet's conv trunk (~100k params) verbatim; replace the single Linear(128,3) head with three heads over the same GAP feature — cell logits Linear(128,1024), per-cell offsets Linear(128,2048) tanh-bounded to half a cell, and the same loose-BCE confidence head as the baseline. Inference decodes argmax cell center + gathered offset inside the exported ONNX graph, so the frozen [[u,v,conf]] contract is untouched. Loss becomes cross-entropy on the true cell + MSE on the true cell's offset (teacher-forced) + the baseline's 0.1-weighted confidence BCE. model/train.py changes one line (call the new raw-output training path). Grid is defined in normalized [0,1] map coordinates, so nothing is Berlin-specific. Conclusion at the time KEPT under the corrected metric — but scored and REVERTED at the time by the old median metric, which destroyed its source. Only its design survives (see runs/20260731_094303_experiment2/experiment.json); a later experiment re-implements it. Re-measured on today's ruler Mission score 1.840 — 8.0% of held-out frames give a usable fix, 92.0% are confident and wrong, 0.0% abstain. Median miss 2.14 km. Its run artifacts were deleted, so the usable- and false-fix rates were recovered arithmetically from the coverage and hit-rate this era's scorer logged at the time. The eval set and target were already identical to today's, so this is a derivation rather than an estimate. What it cost No agent accounting survives for this experiment — its run directory was deleted. That is a missing receipt, not a zero. What it was measured against then Optimised: worst-case geometric-mean error (m), scoring 1.32 km. Asked over: viewpoint holdout, daytime only, Berlin only. The evaluation was fixed here: held-out viewpoints over mapped ground instead of held-out regions. The metric was still an error statistic. Run artifacts were not kept. era Viewpoint holdout · ts 2026-07-31T09:49:23 ·
commit 4affa746e608 · artifacts runs/20260731_094303_experiment2 ·
took 6 m 20 s
The code for this experiment — its code, from the commitRead back out of this experiment’s own commit. It was kept, so the loop committed model/ and then recorded that commit — the tree is this experiment’s code. · on GitHub ↗model/model.py79 lines model/train.py101 lines | |||||||||||||||||||||||||||
| ▸ | 4.1 | Baseline seed on the corrected viewpoint-holdout split
8bdcc185
The loop kept this — it beat the running best under the metric then in force. On today's ruler it scores 2.000 — worse than silence. |
other | 2.000 | 44 s | — | KEPT | ||||||||||||||||||||
|
The design under test — technical diagram — gray = frozen contract · ink = the current design · red = this experiment's change In plain words We found that the exam had been unfair in a way nobody noticed: the model was studying one set of streets and then being tested exclusively on different streets it had never been allowed to see. Now it studies the whole city and gets tested on the same city photographed from angles and positions it never studied from -- which is exactly what a drone flying overhead would actually do. This row is the naive starting model re-measured on that fair exam. Hypothesis Not a hypothesis -- a starting line. The previous split held out whole REGIONS of Berlin, so 100% of eval questions were centred on ground the model had never been shown and 65% of eval frames contained no familiar pixel at all. Every score before this one was measured against an unanswerable question and cannot be compared to anything measured now. Method Rebuild the split to hold out VIEWPOINTS instead of regions: training covers every lattice position of the whole city; eval frames sit 11-17 m off the nearest training vantage at their own rotation. Then re-run the original from-scratch TinyLocNet baseline with no design agent (SKIP_AGENT=1) to establish an honest number on the new ruler. Conclusion at the time KEPT — metric improved (1e18 -> 1993.71 m); change committed as 8bdcc18 Re-measured on today's ruler Mission score 2.000 — 0.0% of held-out frames give a usable fix, 100.0% are confident and wrong, 0.0% abstain. Median miss 1.99 km. Its run artifacts were deleted, so the usable- and false-fix rates were recovered arithmetically from the coverage and hit-rate this era's scorer logged at the time. The eval set and target were already identical to today's, so this is a derivation rather than an estimate. What it cost No agent accounting survives for this experiment — its run directory was deleted. That is a missing receipt, not a zero. What it was measured against then Optimised: worst-case geometric-mean error (m), scoring 1.78 km. Asked over: viewpoint holdout, daytime only, Berlin only. The evaluation was fixed here: held-out viewpoints over mapped ground instead of held-out regions. The metric was still an error statistic. Run artifacts were not kept. era Viewpoint holdout · ts 2026-07-31T09:42:22 ·
commit 8bdcc18531c8 · artifacts runs/20260731_094138_experiment1 ·
took 44 s
The code for this experiment — its code, from the commitRead back out of this experiment’s own commit. It was kept, so the loop committed model/ and then recorded that commit — the tree is this experiment’s code. · on GitHub ↗model/model.py79 lines model/train.py101 lines | |||||||||||||||||||||||||||
| ▸ | 3.5 | Per-epoch position resampling: expose all ~45k Berlin train vantages instead of a frozen 6k subset
0ede8d45
The loop kept this — it beat the running best under the metric then in force. It cannot be placed on today's ruler at all — see the row for why. |
training | — | 16 m 23 s | — | NO SCORE | ||||||||||||||||||||
|
The design under test — technical diagram — gray = frozen contract · ink = the current design · red = this experiment's change In plain words The model has been studying the same 6,000 flashcards over and over — 96 study sessions with the exact same deck — when the full deck of Berlin views actually has 45,000 cards. We now deal a fresh random hand of 6,000 cards before every study session. Same total study time, same brain, but by the end it has seen essentially every card in the deck a few times instead of 13% of them dozens of times. Seeing far more of Berlin should make it much better at telling apart neighborhoods that look confusingly similar. Hypothesis The champion's 810 m is limited by training-set exposure, not by its (validated) grid-classification architecture. train.py's load_training_plan picks 6,000 crop positions ONCE and reuses them for all 96 epochs — only the rotation is refreshed. The frozen split (pipeline/dataset.py) documents ~45k available train positions for a ~7 km area, so the model literally never sees ~87% of its own training territory: of the grid cells that contain at least one train position, a fixed 6k draw leaves hundreds with zero positives for the entire run, and the cells it does cover average only ~2 fixed vantage points memorized 96 times over. That is exactly the label starvation experiment 4 diagnosed — but exp 4 attacked it by reparameterizing the decode head (coarse cells + offsets), which was refuted at 920 m. This experiment attacks the same bottleneck directly in the training procedure, with the architecture untouched: redraw the 6,000 positions at random from the full ~45k train list before every epoch. At unchanged per-epoch cost (extraction time scales with count, not novelty; ~3.3 s/epoch measured), 96 epochs then cover essentially every trainable position (P(a position is never drawn) ≈ 0.867^96 ≈ 1e-6), turning the long budget into dataset-scale coverage instead of repetition — every populated cell gets positives, and lookalike cells get far more distinct negatives to discriminate against. (Note: this is the third attempt at iteration 5 — the same design was pre-registered twice before but never reached a score; both prior attempts died to harness interruptions, not to any result. It is re-registered here after independently re-verifying its claims against the current champion code.) Method model/train.py only: move training-plan sampling inside the epoch loop so each epoch draws a fresh random 6,000 positions (with fresh rotations, as now) from the full train-split crop list, instead of freezing one 6,000-position plan for the whole run. model/model.py untouched — identical architecture, loss, and ONNX export. Conclusion at the time KEPT — metric improved (810.02 -> 760.65 m); change committed as 0ede8d4 Not on today's ruler Its run directory was deleted and its logged record does not carry enough to reconstruct the rates. This one is simply lost. What it cost No agent accounting survives for this experiment — its run directory was deleted. That is a missing receipt, not a zero. What it was measured against then Optimised: worst-case median error (m), target 100 m, scoring 760.6 m. Asked over: region holdout, daytime only, Berlin only. Scope cut to one city and one lighting condition for iteration speed. Same broken region-holdout eval. era Berlin only · ts 2026-07-31T09:19:51 ·
commit 0ede8d452bd2 · artifacts runs/20260731_090328_experiment5 ·
took 16 m 23 s
The code for this experiment — its code, from the commitRead back out of this experiment’s own commit. It was kept, so the loop committed model/ and then recorded that commit — the tree is this experiment’s code. · on GitHub ↗model/model.py123 lines model/train.py175 lines | |||||||||||||||||||||||||||
| ▸ | 3.4 | Hierarchical decode: 16x16 coarse-cell classification + per-cell continuous offset regression from a layout-preserving descriptor
f625d8d7
The loop discarded this — no improvement under the metric then in force. |
architecture | 1.653 | 17 m 29 s | $3.59 | DISCARDED | ||||||||||||||||||||
|
The design under test — technical diagram — gray = frozen contract · ink = the current design · red = this experiment's change In plain words The old model had to answer 'which of 4096 tiny map squares is this photo from?' -- but the exam is deliberately held on patches of Berlin the model never studied, and with 4096 choices it had only ever seen one or two examples of each. Worse, squares inside the exam patches never appeared in study material at all, so the model literally learned to never answer them. The new model first picks one of 256 much bigger neighborhoods -- big enough that every one was at least partly studied -- and then points to a spot inside it, the way you'd say 'that's Kreuzberg, near the top-left corner of the park'. Pointing is a smooth skill that transfers into the unstudied patches, where memorizing tiny squares could not. Hypothesis Exp 3's remaining 810 m has two structural causes baked into the flat 64x64 (108 m/cell) classification head, both fixed by the same reparameterization. (1) Never-trained-cell suppression: the frozen split holds out 360 px (360 m) eval blocks that training windows may never touch, so every cell lying inside an eval block appears ONLY as a softmax negative across all 96 epochs -- the converged model systematically pushes probability mass AWAY from exactly the cells where every eval crop lives, forcing predictions onto the ring of trained cells around each block (a >=1-2 cell, 110-250 m structural floor) and, worse, letting mass scatter to lookalike trained cells elsewhere. (2) Label starvation: 6000 training crops over 4096 cells is ~1.8 positives per cell, far too sparse to learn a 4096-way discrimination. Reparameterizing to a 16x16 coarse grid (434 m cells) + continuous within-cell offset removes both at once: eval blocks are isolated on the split's diagonal lattice (no two adjacent), so every 434 m coarse cell is guaranteed to contain trained territory (no suppressed classes), each coarse class gets ~23 positives instead of ~1.8, and the offset regressor is a continuous function that can interpolate into the unseen eval-block interior of a cell it recognizes -- discrete classes cannot interpolate, regression can. The offset needs to know WHERE in the cell the crop sits, which a global-average-pooled texture bag cannot express, so the descriptor keeps a coarse 4x4 spatial layout instead of averaging it away; this descriptor change is load-bearing for the offset head, not a separate idea. Method In model/model.py only: replace the flat GRID=64 head with (a) a descriptor that preserves layout -- 1x1 conv 128->32 on the 8x8 feature map, 2x2 avg-pool to 4x4x32, flatten to 512 -- (b) a 256-way coarse-cell head (16x16 grid, ~434 m cells) and (c) a per-cell offset head (512->512, i.e. (du,dv) per cell, tanh-bounded to +/-0.75 cell). Output uv = posterior-weighted sum over cells of (cell center + offset); conf = max posterior mass in any 2x2 cell neighborhood. Loss = soft-label CE over the 16x16 grid (sigma 0.5 cells) + true-cell offset MSE + composed-uv MSE. model/train.py is untouched (forward_logits/loss_fn interface preserved as a tuple). Conclusion at the time REVERTED — metric did not improve (920.52 m vs best 810.02 m); change discarded Re-measured on today's ruler Mission score 1.653 — 6.5% of held-out frames give a usable fix, 71.8% are confident and wrong, 21.8% abstain. Median miss 600.8 m. Measured, not converted: its exported model was re-run through the current scorer for this page. What it cost
by model — claude-fable-5 $2.80 · claude-opus-5 $0.79 · claude-haiku-4-5-20251001 $0.00 The agent lines are the equivalent API cost of the tokens each agent used, as recorded by the harness when it ran. The research ran on a claude.ai Max subscription, so that is what it would have cost billed per token — not money that left the account. The GPU line is real spend. What it was measured against then Optimised: worst-case median error (m), target 100 m, scoring 920.5 m. Asked over: region holdout, daytime only, Berlin only. Scope cut to one city and one lighting condition for iteration speed. Same broken region-holdout eval. Scoreboard — re-scored today Produced by running this experiment's exported model through the current frozen scorer on the current eval set. It is not what this experiment was told at the time.
era Berlin only · ts 2026-07-31T08:56:20 ·
commit f625d8d7df16 · artifacts runs/20260731_083851_experiment4 ·
took 17 m 29 s
The code for this experiment — not retainedSource not retained. Until 2026-07-31 the loop discarded a reverted experiment’s code outright, and this row predates that fix. Its design record, its verdict and its exported model survive; the code does not. It is left blank rather than filled with the parent’s code, which is what this row’s recorded commit actually contains. | |||||||||||||||||||||||||||
| ▸ | 3.3 | Re-land the validated 64x64 grid-classification head with a convergence-scale training budget (x12 epochs, cosine LR, fresh per-epoch rotations)
bea8a408
The loop kept this — it beat the running best under the metric then in force. On today's ruler it scores 1.556 — worse than silence. |
training | 1.556 | 17 m 28 s | $3.70 | KEPT | ||||||||||||||||||||
|
The design under test — technical diagram — gray = frozen contract · ink = the current design · red = this experiment's change In plain words The last experiment changed the model's job from 'blurt out GPS numbers' to 'point at the right square on a 64x64 checkerboard of Berlin' - and it genuinely got 3.6x more accurate. But it was disqualified by a fairness rule: it was only sure enough to give an answer on 1 in 10 photos, and the rules demand an answer on at least 1 in 5. The catch: it had only been allowed to study for 33 seconds. This experiment brings the same model back and lets it actually study - about 100 passes through the material instead of 8, with every aerial photo shown at a fresh random rotation each pass so it learns places rather than memorizing exact pictures. A model that has properly studied is both more accurate and more confident, which should clear the 'too unsure to answer' disqualification. Hypothesis Experiment 2's grid-classification architecture was validated, not refuted: on the frames it answered, median error was 743 m vs the kept baseline's 2674.93 m (3.6x better), and it passed both deployment gates (2.5 MiB, 0.63 ms). It failed only the frozen coverage gate (10.75% < 20%). That coverage failure is a symptom of undertraining, not a broken confidence design: its confidence IS posterior mass in a 5x5-cell neighborhood, and after only 8 epochs (33 s wall) the train loss sat at 6.87 nats against a ~3.1-nat soft-label entropy floor - i.e. the posterior was ~3.8 nats (~45x in probability-mass terms) more diffuse than a converged one, so almost no eval frame could concentrate 0.3 mass anywhere. The 8-epoch budget was sized for the harness-proving baseline (loop.sh EPOCHS=8), and loop.sh's own comments document the accepted pattern of train.py applying an internal epoch multiplier. Additionally, the baseline trainer freezes ONE random rotation per crop for the whole run, which at 96 epochs would mean memorizing 6000 fixed images; eval crops carry random headings, so fresh per-epoch rotations are required for the longer budget to convert into generalization (eval blocks are never seen in training - the split is genuinely held-out territory). Prediction: a converged posterior is simultaneously sharper (higher coverage) and more accurate (lower median), fixing the gate failure and the metric with one cause. Method Two files, one focused change: restore experiment 2's model.py exactly as pre-registered there (64x64 grid logits head, Gaussian-soft-label cross-entropy + soft-argmax aux loss, in-graph soft-argmax coordinate readout, confidence = max 5x5-neighborhood posterior mass - all byte-equivalent to exp 2's brief, deliberately including the unchanged confidence definition as the experimental control), and scale training in train.py: internal EPOCH_MULT=12 (harness's --epochs 8 -> 96 effective epochs), CosineAnnealingLR from 1e-3 to 1e-5 over the full run, and re-extraction of all training crops with fresh random rotation angles at the start of every epoch instead of one frozen rotation at load time. Trunk, data selection, batch size, optimizer family, CLI, and ONNX contract untouched. Conclusion at the time KEPT — metric improved (2674.93 -> 810.02 m); change committed as bea8a40 Re-measured on today's ruler Mission score 1.556 — 10.8% of held-out frames give a usable fix, 66.2% are confident and wrong, 23.0% abstain. Median miss 417.5 m. Measured, not converted: its exported model was re-run through the current scorer for this page. What it cost
by model — claude-fable-5 $2.70 · claude-opus-5 $1.00 · claude-haiku-4-5-20251001 $0.00 The agent lines are the equivalent API cost of the tokens each agent used, as recorded by the harness when it ran. The research ran on a claude.ai Max subscription, so that is what it would have cost billed per token — not money that left the account. The GPU line is real spend. What it was measured against then Optimised: worst-case median error (m), target 100 m, scoring 810.0 m. Asked over: region holdout, daytime only, Berlin only. Scope cut to one city and one lighting condition for iteration speed. Same broken region-holdout eval. Scoreboard — re-scored today Produced by running this experiment's exported model through the current frozen scorer on the current eval set. It is not what this experiment was told at the time.
era Berlin only · ts 2026-07-31T08:38:41 ·
commit bea8a4083210 · artifacts runs/20260731_082113_experiment3 ·
took 17 m 28 s
The code for this experiment — its code, from the commitRead back out of this experiment’s own commit. It was kept, so the loop committed model/ and then recorded that commit — the tree is this experiment’s code. · on GitHub ↗model/model.py123 lines model/train.py149 lines | |||||||||||||||||||||||||||
| ▸ | 3.2 | Reparameterize localization as 64x64 map-grid classification with in-graph soft-argmax
d9805aa2
Failed a deployment gate: too large, too slow, or it abstained on too many frames to be scoreable. |
architecture | — | 6 m 37 s | $3.15 | GATED FAIL | ||||||||||||||||||||
|
The design under test — technical diagram — gray = frozen contract · ink = the current design · red = this experiment's change In plain words The old model had to blurt out map coordinates directly, like guessing GPS numbers. When a model like that is unsure, naming the middle of the map minimizes its average penalty - so it learned exactly that and nothing else (its error matches a dead-center guess almost to the meter). The new model instead treats Berlin as a 64x64 checkerboard and answers a multiple-choice question: 'which square am I looking over?' - then fine-tunes the answer by averaging the best-scoring neighboring squares. Multiple choice gives no credit for hedging toward the middle, so the model is forced to actually recognize places, and its confidence becomes honest: it is literally how much probability it puts on one small neighborhood of the map. Hypothesis The baseline's 2674.93 m is centroid collapse, not weak features: mean distance from the center of the ~6.9 km Berlin raster to a uniform point is ~0.38 x 6900 = ~2640 m, matching the score almost exactly. Direct (u,v) MSE regression makes 'predict the map center' the loss-minimizing answer for an uncertain model, and gradient descent finds that basin first and stays there. Reformulating the output as classification over spatial cells removes that basin entirely - the map center is just one class among 4096 with no special status - and turns the task into recognition ('which cell does this crop look like?'), which CNNs learn far more readily than metric regression. This is the classification-over-cells trick from geo-localization prior art (PlaNet et al.) applied at single-city scale. Method Replace TinyLocNet's 3-unit regression head with a linear head producing 64x64=4096 logits over a grid of map cells (~108 m/cell on the 6939x6828 px raster). Training: cross-entropy against Gaussian-smoothed soft cell labels (sigma = 1 cell) plus a small auxiliary L2 on the soft-argmax coordinate. Inference (all inside the ONNX graph): softmax posterior -> expected (u,v) via posterior-weighted average of cell centers (soft-argmax), confidence = maximum probability mass in any 5x5-cell neighborhood (avg_pool2d * 25, then spatial max) - i.e. P(true location within ~+/-270 m of the peak), which is semantically meaningful and comfortably clears the frozen scorer's CONF_THRESHOLD=0.3 when the posterior is peaked while staying low when it is diffuse. The separate BCE-trained confidence head is deleted; trunk, data loading, augmentation, optimizer, epochs, and CLI are untouched. Conclusion at the time REVERTED — metric did not improve (1000000000.0 m vs best 2674.93 m); change discarded Not on today's ruler It failed a deployment gate, so there was no working model to score then and none to re-score now. What it cost
by model — claude-fable-5 $2.17 · claude-opus-5 $0.98 · claude-haiku-4-5-20251001 $0.00 The agent lines are the equivalent API cost of the tokens each agent used, as recorded by the harness when it ran. The research ran on a claude.ai Max subscription, so that is what it would have cost billed per token — not money that left the account. The GPU line is real spend. What it was measured against then Optimised: worst-case median error (m), target 100 m, scoring —. Asked over: region holdout, daytime only, Berlin only. Scope cut to one city and one lighting condition for iteration speed. Same broken region-holdout eval. era Berlin only · ts 2026-07-31T08:21:04 ·
commit d9805aa27bfc · artifacts runs/20260731_081427_experiment2 ·
took 6 m 37 s
The code for this experiment — not retainedSource not retained. Until 2026-07-31 the loop discarded a reverted experiment’s code outright, and this row predates that fix. Its design record, its verdict and its exported model survive; the code does not. It is left blank rather than filled with the parent’s code, which is what this row’s recorded commit actually contains. | |||||||||||||||||||||||||||
| ▸ | 3.1 | Baseline seed: original TinyLocNet, fresh berlin-slim lineage
28f2ebe1
The loop kept this — it beat the running best under the metric then in force. On today's ruler it scores 1.996 — worse than silence. |
other | 1.996 | 16 s | — | KEPT | ||||||||||||||||||||
|
The design under test — technical diagram — gray = frozen contract · ink = the current design · red = this experiment's change In plain words This row isn't a new idea being tested -- it's the starting line. The original, deliberately naive tiny CNN from the project's very first bootstrap gets retrained from scratch on Berlin's daytime-only imagery, with no design agent involved, just to prove the pipeline still works end-to-end after today's changes and to get a real number on the board. Everything after this row is a genuine attempt to beat it. Hypothesis Not a research hypothesis -- this is a deliberate reset, not an experiment testing an idea. After pivoting to the berlin-slim branch (single locale, no synthetic relighting, 100 m milestone), the old champion architecture was tuned for a different problem (4 areas x 6 lighting buckets) and the old experiment lineage no longer meant anything under the new scoring, so both were reset by request. Method Restore the original from-scratch bootstrap baseline (TinyLocNet, model/model.py + model/train.py from commit c0a1bdf) as the starting point, wipe experiments.sqlite so this branch's lineage starts at experiment 1 (old history archived, not deleted), and run the harness with SKIP_AGENT=1 -- no design/implementation agent, train+score+log only -- to prove the fixed harness end-to-end and establish a real number for the next experiment to actually beat. Conclusion at the time KEPT — metric improved (1e18 -> 2674.93 m); change committed as 28f2ebe Re-measured on today's ruler Mission score 1.996 — 0.2% of held-out frames give a usable fix, 99.8% are confident and wrong, 0.0% abstain. Median miss 2.68 km. Measured, not converted: its exported model was re-run through the current scorer for this page. What it cost No agent accounting survives for this experiment — its run directory was deleted. That is a missing receipt, not a zero. What it was measured against then Optimised: worst-case median error (m), target 100 m, scoring 2.67 km. Asked over: region holdout, daytime only, Berlin only. Scope cut to one city and one lighting condition for iteration speed. Same broken region-holdout eval. Scoreboard — re-scored today Produced by running this experiment's exported model through the current frozen scorer on the current eval set. It is not what this experiment was told at the time.
era Berlin only · ts 2026-07-31T08:13:58 ·
commit 28f2ebe186f8 · artifacts runs/20260731_081342_experiment1 ·
took 16 s
The code for this experiment — its code, from the commitRead back out of this experiment’s own commit. It was kept, so the loop committed model/ and then recorded that commit — the tree is this experiment’s code. · on GitHub ↗model/model.py79 lines model/train.py101 lines | |||||||||||||||||||||||||||
| ▸ | 2.65 | Harness smoke test — SKIP_AGENT loop.sh iteration, 200 crops, 1 epoch
5e9419c3
The loop discarded this — no improvement under the metric then in force. |
other | 1.406 | 28 s | — | DISCARDED | ||||||||||||||||||||
|
Method Not a research experiment. Second SKIP_AGENT=1 harness run, three minutes after the first. No design or implementation agent ran (both 0 s); 200 crops, 1 epoch, ~12 s of training. Its score reflects the training budget, not a hypothesis — there was no hypothesis. Conclusion at the time REVERTED — metric did not improve (2706.81 m vs best 743.07 m); change discarded Re-measured on today's ruler Mission score 1.406 — 0.2% of held-out frames give a usable fix, 40.8% are confident and wrong, 59.0% abstain. Median miss 2.50 km. Measured, not converted: its exported model was re-run through the current scorer for this page. What it cost No agent accounting survives for this experiment — its run directory was deleted. That is a missing receipt, not a zero. What it was measured against then Optimised: worst-case median error (m), target 20 m, scoring 2.71 km. Asked over: region holdout, 6 lighting buckets, 4 areas. The long grind: 62 experiments over ten days against a general-purpose, four-area, six-lighting-bucket problem. Scored on a region holdout that asked a memorization model to locate ground it had never been shown. Scoreboard — re-scored today Produced by running this experiment's exported model through the current frozen scorer on the current eval set. It is not what this experiment was told at the time.
era Four areas, six lighting buckets · ts 2026-07-31T07:40:05 ·
commit 5e9419c3bd39 · artifacts runs/20260731_073937_experiment65 ·
took 28 s
The code for this experiment — not retainedSource not retained. Until 2026-07-31 the loop discarded a reverted experiment’s code outright, and this row predates that fix. Its design record, its verdict and its exported model survive; the code does not. It is left blank rather than filled with the parent’s code, which is what this row’s recorded commit actually contains. | |||||||||||||||||||||||||||
| ▸ | 2.64 | Harness smoke test — SKIP_AGENT loop.sh iteration, 200 crops, 1 epoch
13657130
Failed a deployment gate: too large, too slow, or it abstained on too many frames to be scoreable. |
other | — | 34 s | — | GATED FAIL | ||||||||||||||||||||
|
Method Not a research experiment. The loop was run with SKIP_AGENT=1 to exercise the harness end-to-end on the morning of 31 July, before the berlin-slim branch was cut. No design agent and no implementation agent ran (both recorded 0 s); training used 200 crops for 1 epoch, ~19 s, against a real experiment's 36,000 crops over 8 epochs. The model it produced failed a deployment gate, which is what a 19-second model does. Conclusion at the time REVERTED — metric did not improve (1000000000.0 m vs best 743.07 m); change discarded Not on today's ruler It failed a deployment gate, so there was no working model to score then and none to re-score now. What it cost No agent accounting survives for this experiment — its run directory was deleted. That is a missing receipt, not a zero. What it was measured against then Optimised: worst-case median error (m), target 20 m, scoring —. Asked over: region holdout, 6 lighting buckets, 4 areas. The long grind: 62 experiments over ten days against a general-purpose, four-area, six-lighting-bucket problem. Scored on a region holdout that asked a memorization model to locate ground it had never been shown. era Four areas, six lighting buckets · ts 2026-07-31T07:37:17 ·
commit 1365713063fa · artifacts runs/20260731_073643_experiment64 ·
took 34 s
The code for this experiment — not retainedSource not retained. Until 2026-07-31 the loop discarded a reverted experiment’s code outright, and this row predates that fix. Its design record, its verdict and its exported model survive; the code does not. It is left blank rather than filled with the parent’s code, which is what this row’s recorded commit actually contains. | |||||||||||||||||||||||||||
| ▸ | 2.63 | From-scratch trunk + domain-contrastive pretraining + fixed retinex channel + D4 symmetry + top-K-masked decode (forced pivot, mobilenet_v3_small banned)
c4616251
The loop discarded this — no improvement under the metric then in force. |
architecture | 1.563 | 64 m 52 s | $9.41 | DISCARDED | ||||||||||||||||||||
|
In plain words The last 27 attempts to beat the champion split into two piles: ones that changed HOW the model reads out its final answer from its internal map of guesses (all of them made things worse, sometimes much worse), and ones that changed WHAT the model's 'eyes' actually notice about the photo (the two best results in the whole project's history both came from this second pile). This round combines, for the first time, the two best 'better eyes' ideas that were only ever tried separately: first, the eyes get a two-part warm-up where they practice matching the SAME spot photographed under two different lighting conditions before they ever try to guess coordinates, so they learn to see past lighting changes on their own; second, every photo also gets a extra 'brightness contrast' sketch layered on top that stays roughly the same for a given spot regardless of what time of day it is, handing the eyes a shortcut to the same lesson. On top of that, since this round isn't allowed to reuse the champion's borrowed eyes at all, the two rival 'day expert / night expert' sub-brains are replaced with one brain that just gently retunes itself based on brightness, the model now also checks the mirror image of each rotated view (aerial photos really do look the same flipped), and instead of scanning its whole 1,024-cell guess-map when committing to an answer, it first throws out everything except its top 64 most-likely guesses so far-off lookalike spots can't quietly tug the final answer toward them. Hypothesis 27 rounds since champion (exp-35, 743.07 m) have tried two disjoint kinds of change: reparameterizing the DECISION mechanism (VQ codebook exp-51 2751.73m, continuous GMM exp-52 2140.32m, three dense-per-patch-consensus variants exp-47/48/49 up to 2655.65m, IRLS robust consensus exp-55 2360.39m, spatial MoE exp-50 1109.13m, retrieve-then-rerank exp-45 1213.14m, factorized row/col axes exp-46 1e9, coordinate-generated field exp-39 1103.28m, coarse-to-fine exp-40 1011.58m) and reparameterizing the TRUNK/descriptor while leaving the decision mechanism alone or nearly alone. Every single decision-mechanism alternative to plain grid-classification-field-plus-soft-argmax has landed 1.5-3.7x worse than champion -- eight-plus independent measurements is enough to treat that family as refuted, not a shape still worth reaching into. Trunk-side changes tell a different story: exp-42 (domain-native contrastive self-supervised pretraining of a from-scratch trunk, decode/gate/field left completely untouched) reached 846.42 m, the single closest result in the entire post-champion history -- and exp-60 (a fixed, non-learned illumination-invariant retinex input channel + from-scratch trunk + cross-lighting consistency loss, gate deleted, decode otherwise untouched) reached 963.42 m, the best result of any FULLY pivot-compliant design (every non-frozen stage genuinely changed) measured so far. These are the two best post-champion results in the whole history and they attack the same thing from two different, non-overlapping angles -- what the trunk represents -- rather than how the field commits to an answer. They have never been combined, and neither has ever been tested inside a design that also legitimately rebuilds gate/ensemble/decode/confidence, which this round's compliance rule requires. Two more recent attempts at trunk swaps (exp-61 int8-quantization-freed from-scratch trunk, exp-62 SqueezeNet1.1 Fire trunk + FiLM + D4 + adaptive-beta decode) never actually trained -- both crashed on the GPU across all four areas identically before a single epoch completed, an infrastructure failure, not a measurement -- so they neither support nor refute anything, and this design deliberately keeps memory/compute modest (narrow from-scratch trunk, D4 without a wide pretrained trunk stacked under it, no learned per-example adaptive-beta MLP) specifically to avoid repeating whatever tipped those two rounds over an edge. Dispatcher/specialist-split designs (exp-30, 46, 59) have also uniformly lost, one catastrophically (exp-59, 2134.12 m) -- that family is not revisited here. Method model/model.py: delete `from torchvision.models import mobilenet_v3_small`, `PRETRAINED_TRUNK_PATH`, `_build_pretrained_trunk`, `IMAGENET_MEAN`/`IMAGENET_STD` buffers, `dark_logits`, and `gate`. Replace the trunk with a small from-scratch conv stack (~150-200k params, stride 16, 48 output channels at 8x8 -- deliberately matched to champion's own 48ch/8x8 shape rather than widened, since exp-61's capacity analysis and the original pre-exp-11 from-scratch baseline (128ch, still lost to the pretrained trunk) both show raw width was never champion's advantage -- pretraining quality was) taking a 4-channel input: the raw 3-channel RGB crop plus one new fixed, non-learned illumination-invariant channel computed in forward() every pass (log-luminance minus its own large-radius fixed Gaussian blur, via a non-trainable depthwise Conv2d buffer -- no new trainable parameters, reused from CLAUDE.md SS4's ambient-vs-active-light framing and exp-60's validated approach). Extend `_c4_stack` to `_d4_stack`: the existing 4 rotations plus each one's horizontal mirror (`.flip(3)`), 8 exact-pixel views total, pure index ops, no interpolation -- keep the trunk narrow specifically so this 2x view-count increase over champion's C4 does not repeat exp-62's likely memory-driven crash (that round paired D4 with an 8x-wider 384-channel trunk; this round does not widen the trunk at all). Delete the two-expert gate/blend entirely; replace with ONE unified field head (GAP + 1x1 layout-squeeze, same shape as champion's layout-aware head) modulated by a single lightweight brightness-conditioned per-channel gain: `film_gain = nn.Linear(1, feat_ch)` (no hidden layer, ~50 params) applied as `fmap_mod = fmap * (1 + torch.tanh(film_gain(lum)).view(-1, feat_ch, 1, 1))` before the layout-squeeze and GAP -- a genuine but minimal-risk replacement for the deleted gate, not a full FiLM MLP. Change Decode: instead of sharpen-then-soft-argmax over all 1024 cells, mask to a fixed top-K=64 subset BEFORE sharpening (`topv, topi = torch.topk(logits, 64, dim=1)`; scatter into a full-size tensor filled with -inf; softmax(DECODE_BETA * masked_logits) so cells outside the top-64 get exactly zero probability) -- this directly targets the champion-failure-mode-probes diagnosis (true cell in the raw field's own top-20 of 1024 on 33-58% of crops, but top-1 only ~2%) by structurally excluding the long low-probability tail of distant lookalikes from ever contributing weighted mass to the soft-argmax centroid, rather than merely turning up the sharpening temperature on top of them. The unsharpened, UNMASKED full-field softmax is still used for the CE training loss (matching champion's exp-14 separation of 'field training signal' from 'commit decode') and for one confidence feature (top-64 raw mass fraction, a diagnostic of how concentrated the raw field already was before masking). Rebuild the confidence head over [averaged GAP descriptor, sharpened-masked peak mass, sharpened-masked entropy over 64 cells, gap between masked and unmasked decode, top-64 raw mass fraction] -- the old gate-derived inputs no longer exist. Add a self-supervised domain-contrastive pretraining routine (module-level function, called from train.py before fine-tuning): for a batch of sampled locations, load the SAME crop under two different randomly chosen lighting buckets (both already available via train.py's `_load_bucket_img` cache), run both through the trunk + GAP only (no field head), and apply a standard InfoNCE/SimCLR loss pulling the two lighting-views' GAP descriptors of the SAME location together and different locations' descriptors apart within the batch -- this is the exact recipe that produced exp-42's 846.42 m result, now applied to the new 4-channel (RGB+retinex) input. model/train.py: add a `pretrain_contrastive(model, area, meta, crops, renders_dir, data_dir, rng, device, steps=600)` function using the InfoNCE loss above, called once before the existing epoch loop in `train_area` (modest step count so four areas still finish in reasonable wall-time -- this phase only runs the trunk+GAP, not the full D4 ensemble or field head, so it is cheap per step). The existing fine-tuning loop (confusability-weighted sampler, cosine LR schedule, two-tier trunk/head LR split via `model.features.parameters()`, `calibrate_conf_shift`) is otherwise unchanged and must keep working against the new model unmodified -- only the `info['init']` string changes to describe the new strategy. `export_onnx` and `estimate_position`'s ONNX input/output contract (1x3x128x128 float32 in, [[u,v,conf]] out -- the retinex channel is computed INSIDE forward() from the 3-channel input, never added to the external contract) are unchanged. Conclusion at the time REVERTED — metric did not improve (1074.76 m vs best 743.07 m); change discarded Re-measured on today's ruler Mission score 1.563 — 4.0% of held-out frames give a usable fix, 60.2% are confident and wrong, 35.8% abstain. Median miss 551.2 m. Measured, not converted: its exported model was re-run through the current scorer for this page. What it cost
by model — claude-sonnet-5 $9.41 · claude-haiku-4-5-20251001 $0.01 The agent lines are the equivalent API cost of the tokens each agent used, as recorded by the harness when it ran. The research ran on a claude.ai Max subscription, so that is what it would have cost billed per token — not money that left the account. The GPU line is real spend. What it was measured against then Optimised: worst-case median error (m), target 20 m, scoring 1.07 km. Asked over: region holdout, 6 lighting buckets, 4 areas. The long grind: 62 experiments over ten days against a general-purpose, four-area, six-lighting-bucket problem. Scored on a region holdout that asked a memorization model to locate ground it had never been shown. Scoreboard — re-scored today Produced by running this experiment's exported model through the current frozen scorer on the current eval set. It is not what this experiment was told at the time.
era Four areas, six lighting buckets · ts 2026-07-30T20:58:48 ·
commit c46162512d6d · artifacts runs/20260730_195356_experiment63 ·
took 64 m 52 s
The code for this experiment — not retainedSource not retained. Until 2026-07-31 the loop discarded a reverted experiment’s code outright, and this row predates that fix. Its design record, its verdict and its exported model survive; the code does not. It is left blank rather than filled with the parent’s code, which is what this row’s recorded commit actually contains. | |||||||||||||||||||||||||||
| ▸ | 2.62 | Fire-module SqueezeNet1.1 trunk + FiLM-conditioned single field + D4 symmetry + adaptive-sharpening decode (forced pivot, mobilenet_v3_small banned)
0041192d
Failed a deployment gate: too large, too slow, or it abstained on too many frames to be scoreable. |
architecture | — | 26 m 11 s | $6.86 | GATED FAIL | ||||||||||||||||||||
|
The design under test — technical diagram — gray = frozen contract · ink = the current design · red = this experiment's change In plain words The model's 'eyes' have been the same borrowed eyes for 35 rounds, and the last 25 experiments all tried rearranging what happens AFTER the eyes without touching them -- none of it worked. This round we're not allowed to reuse those eyes at all, so we swap in a different pretrained set that sees the world through wider, richer 'glances' (8x more detail per glance) instead of trying to build new eyes from scratch, which has failed every single time it's been tried. Everything downstream is rebuilt to match: instead of two separate 'day expert' and 'night expert' sub-brains voting on the answer, there's now one brain that just retunes itself based on how bright the photo is; instead of checking the view from 4 rotated angles, we now also check its mirror image, since aerial photos really do look the same flipped left-right; and instead of always squinting at the guess-map by the same fixed amount before committing to an answer, the model now decides per-photo how hard to squint based on how confusing that particular map looks. Hypothesis 25 straight reverts since champion (exp-35, 743.07 m) have swept nearly every decode/output family (VQ codebook, GMM, dense per-patch consensus, spatial MoE, retrieve-rerank, factorized axes, coarse-to-fine, coordinate-generated field) and nearly every from-scratch-trunk-plus-objective family (contrastive, reconstruction/canonicalization, five from-scratch backbones, exp-61's 4x-wider from-scratch trunk which failed the deployment gate outright) while leaving champion's exact pretrained trunk (mobilenet_v3_small features[:9], 190k params, 48ch@8x8 GAP) and its two-expert gate untouched or barely touched. The single closest post-champion result in the whole history, exp-42 (846.42 m), is the ONE experiment that changed only the trunk's pretraining objective while leaving champion's full field/decode/gate/C4-ensemble shape completely intact -- jointly with exp-61's own capacity analysis, this points at trunk richness (channel width / feature vocabulary), not decode shape, as the binding constraint every decode-family pivot has failed to move. This round bans champion's exact backbone outright, so instead of falling back to a from-scratch trunk (a family already tried 10+ times and consistently starved at ~130k params, per exp-61's own count), it swaps in a genuinely different pretrained family: SqueezeNet1.1's squeeze-then-expand 'fire module' trunk (torchvision, BSD-3, ImageNet-pretrained), truncated to its first 11 layers -- structurally unlike MobileNetV3's depthwise-separable inverted-residual-plus-SE-plus-hardswish blocks, and critically ~8x wider per spatial position (384ch vs 48ch) at a comparable total trunk parameter count (~336k vs 190k), so the field head finally reads a richer descriptor instead of a wider from-scratch stack no prior experiment could make work. Around that new trunk, every downstream stage is rebuilt rather than carried over: the two-expert gate/blend (an unexamined mixture-of-experts mechanism kept unchanged since exp-12, itself ~570k params of duplicated capacity) is replaced by a single FiLM-conditioned head, so the freed duplication budget pays for the wider trunk's field head instead of a second expert; the C4 rotation ensemble becomes full D4 (rotations plus mirror reflections -- aerial views are genuinely left-right symmetric, unlike the handwriting-style tasks C4/D4 usually get used for, and reflection invariance has never been tried); and the fixed-temperature decode sharpening (DECODE_BETA=3.0 constant since exp-14) becomes a per-example learned function of the raw field's own entropy, so confusable (high-entropy) fields get sharpened harder before commit -- attacking the same lookalike-cell problem exp-35's training-side sampling improved, now also from the decode side. Method model/model.py: delete the mobilenet_v3_small import, PRETRAINED_TRUNK_PATH/mnv3s_features8.pt load, dark_logits/gate mixture, and fixed DECODE_BETA. Build the trunk from torchvision.models.squeezenet1_1's features[:11] (stem conv + 4 Fire blocks through the third maxpool, ~336k params, ImageNet-pretrained, BSD-3), loading a new checked-in weights slice model/pretrained/squeezenet1_1_features11.pt (same strict-load convention as the file it replaces). Replace the C4 rotation stack with a D4 stack (4 rotations x mirror flip = 8 exact pixel views). Replace the layout-squeeze+GAP+gated-dual-head with ONE FiLM-conditioned head: a tiny brightness-to-(scale,shift) MLP modulates the trunk's ~7x7x384 feature map per-channel before a slim layout squeeze (1 channel) + GAP(384) feed a single Linear to the 32x32 cell logits. Replace fixed DECODE_BETA with a tiny 2-input MLP (raw field entropy + raw field peak mass, both detached) producing a per-example adaptive beta that scales the logits before the softmax soft-argmax commit; the unsharpened field (beta-independent) is still used for CE training and for the confidence head's shape features, mirroring exp-14's separation of 'well-calibrated field' from 'sharpened commit'. Recompute the confidence head over the new descriptor plus the adaptive beta value itself. model/train.py: no structural changes needed -- keep the trunk submodule named self.features so the existing two-tier LR split (trunk 1e-4 / head 1e-3), the confusability-weighted sampler, and the conf_shift calibration all keep working unmodified; only update the one-line init string logged in train_area()'s info dict to name the new pretrained family. ONNX export / estimate_position contracts, GRID_K=32, TARGET_SIGMA_CELLS, and the overall loss_fn shape (Gaussian-CE + coord L2 + conf BCE) are unchanged. Conclusion at the time REVERTED — metric did not improve (1000000000.0 m vs best 743.07 m); change discarded Not on today's ruler It failed a deployment gate, so there was no working model to score then and none to re-score now. What it cost
by model — claude-sonnet-5 $6.85 · claude-haiku-4-5-20251001 $0.01 The agent lines are the equivalent API cost of the tokens each agent used, as recorded by the harness when it ran. The research ran on a claude.ai Max subscription, so that is what it would have cost billed per token — not money that left the account. The GPU line is real spend. What it was measured against then Optimised: worst-case median error (m), target 20 m, scoring —. Asked over: region holdout, 6 lighting buckets, 4 areas. The long grind: 62 experiments over ten days against a general-purpose, four-area, six-lighting-bucket problem. Scored on a region holdout that asked a memorization model to locate ground it had never been shown. era Four areas, six lighting buckets · ts 2026-07-30T19:53:50 ·
commit 0041192d9e77 · artifacts runs/20260730_192739_experiment62 ·
took 26 m 11 s
The code for this experiment — not retainedSource not retained. Until 2026-07-31 the loop discarded a reverted experiment’s code outright, and this row predates that fix. Its design record, its verdict and its exported model survive; the code does not. It is left blank rather than filled with the parent’s code, which is what this row’s recorded commit actually contains. | |||||||||||||||||||||||||||
| ▸ | 2.61 | Quantization-freed capacity: from-scratch depthwise-separable trunk + unified single-head field, weights trained for int8 export
9421cc39
Failed a deployment gate: too large, too slow, or it abstained on too many frames to be scoreable. |
architecture | — | 45 m 05 s | $7.88 | GATED FAIL | ||||||||||||||||||||
|
The design under test — technical diagram — gray = frozen contract · ink = the current design · red = this experiment's change In plain words Every recent attempt to swap out the one part of the model we're now required to remove made things worse, and the pattern points at a simple reason: the replacement parts were always smaller and cheaper than the piece they replaced, because the model has to fit in a fixed 4-megabyte file. This experiment tries a trick borrowed from how phones shrink apps: train the model's numbers to already sit on a coarse, round grid (like rounding every price to the nearest dollar instead of the nearest cent), so that when we later squeeze those numbers down to take a quarter of the storage space each, almost nothing is lost. That squeeze buys back enough room to build a genuinely bigger, single-focus model instead of the small or split-up replacements that kept losing. Hypothesis 20+ rounds since champion (exp-35, 743.07 m) have swept nearly every decode/output family (VQ codebook, GMM, dense per-patch consensus, spatial MoE, retrieve-rerank, factorized axes, coarse-to-fine, coordinate-generated field) and nearly every trunk-objective family (contrastive, reconstruction/canonicalization translators, ShuffleNetV2-pretrained, five from-scratch variants) on top of or in place of champion's field+soft-argmax decode, and all of them lost. The single closest post-champion result in the whole history is exp-42 (846.42 m, contrastive-pretrained from-scratch trunk) -- the ONE experiment that changed only the trunk's training objective while leaving the champion's full-capacity field/decode/C4-ensemble shape completely untouched. Every from-scratch trunk ever tried (exp 41-55) was a small plain conv stack (~130k params) next to champion's 190k-param pretrained trunk feeding a ~570k-param single field head; every experiment that also SPLIT capacity across two or more heads (the exp-12 gate kept in several designs, and explicitly the three independent dispatcher/specialist attempts exp-30/46/59) lost, exp-59 catastrophically (2134 m) despite a coherent gradient-interference hypothesis -- its own pre-registered risk note said capacity-per-regime could dominate over gradient interference, and the result supports exactly that: capacity, not decode shape or gradient crosstalk, is what this design has been starved of every time a from-scratch trunk or a split-capacity design has been tried. This round bans the champion's exact pretrained trunk outright, which finally forces the one lever no prior round has pulled: instead of accepting whatever capacity a from-scratch trunk happens to have at float32 byte cost, train every trunk/field/confidence weight tensor to tolerate int8 rounding (a fixed, non-learned round-to-grid transform with a straight-through gradient, applied every forward pass) and export the deployed artifact through real int8 weight quantization (onnxruntime dynamic quantization, not a training-time simulation left un-exported) -- turning the 4-byte-per-weight cost that has capped every from-scratch trunk's width so far into a 1-byte-per-weight cost, and spending the freed budget on both a wider trunk AND a single larger unified field (deleting the exp-12 gate/dark-expert split entirely, since the evidence above says specialization has been costing capacity, not buying it). Method In model/model.py: delete the mobilenet_v3_small import, _build_pretrained_trunk, PRETRAINED_TRUNK_PATH, and the ImageNet mean/std buffers entirely -- no torchvision import anywhere in the file. Replace with a from-scratch, never-pretrained depthwise-separable conv trunk (a stem 3->64 regular 3x3 conv stride 2, then three depthwise-separable stride-2 blocks 64->128->224->320, BatchNorm2d+ReLU6 after every conv, 128x128 input -> 8x8x320 output -- 4x wider than every from-scratch trunk tried in exps 41-55 because depthwise-separable convs buy width per byte far more cheaply than the plain conv stacks those experiments used). Delete self.dark_logits and self.gate entirely (the exp-12 two-expert blend) -- one unified loc_logits Linear head reads the new trunk's [320-d GAP + 8-channel/64-cell layout-squeeze code] descriptor, same layout-squeeze mechanism as today just re-widthed. Raise GRID_K from 32 to 40 (1024 -> 1600 cells, ~175 m cells instead of ~220 m) since the freed byte budget affords a finer field; DECODE_BETA, the Gaussian-smoothed-CE target, and the sharpened-soft-argmax decode formula are otherwise byte-identical, just operating over the new grid size. The C4 rotation-vote (4x forward pass, average field logits over the crop's 90-degree rotations) is kept -- it is a previously-validated, causally distinct mechanism (exp 16, +33%) this experiment is not testing -- but its code necessarily changes because there is no longer a gated blend to average before the vote: logits4 = self.loc_logits(...) directly, then the same reshape(4,n,-1).mean(dim=0). Confidence head is rebuilt with re-dimensioned Linear layers reading the new (320-d GAP + peak + entropy + sharp/unsharp gap) descriptor -- same three shape-derived features as today, same detached-gradient contract, just resized for the new GAP width. Add a QATConv2d/QATLinear module pair (thin nn.Conv2d/nn.Linear subclasses) that fake-quantize self.weight every forward call via a symmetric per-tensor 8-bit round-to-grid straight-through estimator (scale = weight.detach().abs().max()/127, quantized = round(weight/scale).clamp(-127,127)*scale, gradient passes straight through the round) before calling the normal functional conv/linear op -- used for every weighted layer in the trunk, layout_squeeze, loc_logits, and conf_head (NOT the tiny confidence-head bias-only ops or the fixed grid-center buffers). In model/train.py: replace the two-LR-group optimizer (trunk_params at 1e-4, head_params at 1e-3 -- a fine-tuning schedule that only makes sense for a pretrained trunk) with a single param group at 1e-3 for the whole from-scratch model, since there are no pretrained features to protect from early large gradients; update the `info["init"]` string to reflect from-scratch init with weight fake-quantization. After the existing epoch loop, calibration, and float32 export_onnx call (kept as an intermediate sanity artifact), add one final step: onnxruntime.quantization.quantize_dynamic(model_input=<float32 onnx path>, model_output=<same models/<area>.onnx path>, weight_type=QuantType.QInt8) with default (per-tensor, no reduce_range) settings, overwriting the scored artifact with the real int8-weight ONNX -- input/output tensor names, shapes, and the float32-in/float32-out contract are unchanged by dynamic quantization (only Conv/Gemm/MatMul weight tensors become int8; activations stay float32 end to end), so pipeline/score.py needs no changes. Confusability-weighted sampling (exp 35), TRAIN_REALIZATIONS, EPOCH_MULT, per-epoch resampling, and the calibration procedure are all left exactly as today -- this experiment's ONE hypothesis is capacity, not data strategy, and mixing in a sampler change would confound attribution. Conclusion at the time REVERTED — metric did not improve (1000000000.0 m vs best 743.07 m); change discarded Not on today's ruler It failed a deployment gate, so there was no working model to score then and none to re-score now. What it cost
by model — claude-sonnet-5 $7.87 · claude-haiku-4-5-20251001 $0.01 The agent lines are the equivalent API cost of the tokens each agent used, as recorded by the harness when it ran. The research ran on a claude.ai Max subscription, so that is what it would have cost billed per token — not money that left the account. The GPU line is real spend. What it was measured against then Optimised: worst-case median error (m), target 20 m, scoring —. Asked over: region holdout, 6 lighting buckets, 4 areas. The long grind: 62 experiments over ten days against a general-purpose, four-area, six-lighting-bucket problem. Scored on a region holdout that asked a memorization model to locate ground it had never been shown. era Four areas, six lighting buckets · ts 2026-07-30T19:27:34 ·
commit 9421cc39c20e · artifacts runs/20260730_184229_experiment61 ·
took 45 m 05 s
The code for this experiment — not retainedSource not retained. Until 2026-07-31 the loop discarded a reverted experiment’s code outright, and this row predates that fix. Its design record, its verdict and its exported model survive; the code does not. It is left blank rather than filled with the parent’s code, which is what this row’s recorded commit actually contains. | |||||||||||||||||||||||||||
| ▸ | 2.60 | Corrected exp-38 retry: fixed illumination-invariant channel + from-scratch trunk + cross-lighting consistency loss, gate deleted
4add0860
The loop discarded this — no improvement under the metric then in force. |
architecture | 1.681 | 86 m 54 s | $6.23 | DISCARDED | ||||||||||||||||||||
|
The design under test — technical diagram — gray = frozen contract · ink = the current design · red = this experiment's change In plain words Every recent experiment has tried a different way for the model to guess a location, or a different pretrained starting brain for it, and all of them lost to the current champion. This time we try something the project noticed early but never actually got to test properly: give the model a second, cleaned-up version of the photo, computed by simple non-learned math, that strips out roughly how bright the whole scene is and keeps mostly the sharp edges and shapes -- like handing it a rough charcoal sketch alongside the color photo. Buildings and roads look the same in that sketch whether it's noon or midnight; only the color photo changes drastically. We also add a training rule that nudges the model's internal summary of 'what this place looks like' to stay the same whether the photo was rendered as daytime or nighttime, since it's the same physical spot either way. Hypothesis 20 straight rounds since champion (exp-35, 743.07 m) have swept nearly every decode/aggregation family (VQ codebook, continuous GMM, dense per-patch consensus under 3 different aggregators, spatial MoE, retrieve-rerank, factorized axes, coarse-to-fine) and every plausible trunk-objective swap (contrastive, joint/sequential reconstruction, ShuffleNetV2-pretrained, from-scratch x5) on top of the champion's field+soft-argmax decode, and all of them lost -- several catastrophically. The single closest post-champion result in the entire history is exp-42 (846.42 m, +14%), which is the ONE experiment that changed only the trunk's pretraining objective (contrastive) while leaving the champion's field/decode/rotation-ensemble shape untouched; every experiment that also rewrote the decode landed at 1100-2655 m or worse. That pattern says the decode family is not the wall -- the trunk's descriptor content is. But no trunk-objective swap tried so far has attacked the specific, previously-diagnosed nuisance in that descriptor: iter-12 probes (project memory champion-failure-mode-probes; also cited by exp-33/38) found the champion's descriptor rides low-level resampling-blur statistics that carry zero location information (removing rotation-resample blur nearly doubles error, 983->1668 m on a matched crop), and CLAUDE.md SS4 itself names the fix at the data-generation level: separate reflected AMBIENT light (dims with time-of-day) from ACTIVE artificial lighting (streetlamps/windows, lighting-invariant). Exp 38 proposed giving the trunk a fixed, non-learned Retinex-style illumination-invariant channel (log-luminance minus its own large-radius Gaussian blur) precisely to hand the network that decomposition explicitly instead of hoping fine-tuning discovers it -- but it was REJECTED BEFORE IMPLEMENTATION, correctly, for leaving the lighting gate/field/confidence/training-signal stages self-reported unchanged under the pivot rule. The retinex idea itself was never trained or measured, unlike every learned-canonicalization alternative already tried and refuted (exp 43/44's reconstruction translators, exp 54's memory-safe corrected retry) -- this is a categorically different mechanism (a fixed geometric transform, not a learned translator with its own capacity/loss to fail), so it is not a repeat of those three refutations. This design is exp-38's corrected, fully-compliant re-attempt: every stage it touches actually changes in the code, and the fixed retinex channel is paired with a genuinely new training-signal addition (a cross-lighting descriptor-consistency loss) that gives the invariance hypothesis a second, independent lever rather than resting on the input channel alone. The champion's mobilenet_v3_small/mnv3s_features8.pt trunk is banned this round regardless, which forces a from-scratch trunk -- used here as the occasion to also delete the two-expert bright/dark gate (exp 12), since the retinex channel is meant to supply architecturally the invariance the gate was patching statistically. Method In model/model.py: (1) delete the mobilenet_v3_small import and _build_pretrained_trunk/PRETRAINED_TRUNK_PATH/IMAGENET_MEAN/IMAGENET_STD -- replace with a new from-scratch compact conv trunk (~4-5 conv blocks reaching an 8x8 spatial output, channel count sized to keep the exported ONNX comfortably under the 4 MiB gate) whose first conv accepts 4 input channels. (2) Add a fixed, non-learned Retinex-style preprocessing step computed inside forward() from the raw [0,1] RGB input: luminance -> log1p -> large-radius Gaussian blur (fixed depthwise-conv buffer, no gradient, sigma on the order of ~16 px on the 128x128 crop) -> retinex = log-luminance minus its own blur, clamped/rescaled to roughly [0,1] -- concatenated as a 4th channel before the first learned conv layer. This is computed from the existing ONNX input, so the (1x3x128x128 float32 [0,1]) export contract is unchanged. (3) Delete the dark_logits/gate branch entirely -- a single loc_logits head reads the unified layout+GAP descriptor (same field/decode math: Gaussian-smoothed-CE-trained 32x32 grid, DECODE_BETA-sharpened soft-argmax, unchanged) built from the new trunk's output. (4) Confidence head is rebuilt to read the new (non-gated) field's shape statistics (peak mass, entropy, sharpened-vs-unsharpened gap) -- same mechanism as champion, wired to the new descriptor since the old gate-derived inputs no longer exist. (5) Add a `return_descriptor: bool` option to TinyLocNet.forward so train.py can retrieve the pooled post-rotation-average GAP descriptor for the consistency loss (mirrors the existing return_logits pattern). In model/train.py: (6) sample_epoch additionally draws, for each per-bucket batch, one 'lighting partner' crop per location at the identical (cx, cy, heading) but from one other uniformly-random bucket (reusing the already-cached per-bucket renders/realizations -- no new rendering cost), returned alongside the primary batch. (7) train_area's step loop forwards both the primary and partner crop batches (return_descriptor=True) and adds a new CONSISTENCY_WEIGHT-scaled L2 term between their pooled descriptors to the existing loss_fn output, backpropagated jointly. (8) Keep EPOCH_MULT, TRAIN_REALIZATIONS, CONFUSABILITY_ALPHA, and the calibration procedure exactly as champion -- do not also scale up training length, since the partner-view forward pass already roughly doubles per-step compute and four areas must still finish in sensible wall time. Conclusion at the time REVERTED — metric did not improve (963.42 m vs best 743.07 m); change discarded Re-measured on today's ruler Mission score 1.681 — 3.8% of held-out frames give a usable fix, 71.8% are confident and wrong, 24.5% abstain. Median miss 927.4 m. Measured, not converted: its exported model was re-run through the current scorer for this page. What it cost
by model — claude-sonnet-5 $6.22 · claude-haiku-4-5-20251001 $0.01 The agent lines are the equivalent API cost of the tokens each agent used, as recorded by the harness when it ran. The research ran on a claude.ai Max subscription, so that is what it would have cost billed per token — not money that left the account. The GPU line is real spend. What it was measured against then Optimised: worst-case median error (m), target 20 m, scoring 963.4 m. Asked over: region holdout, 6 lighting buckets, 4 areas. The long grind: 62 experiments over ten days against a general-purpose, four-area, six-lighting-bucket problem. Scored on a region holdout that asked a memorization model to locate ground it had never been shown. Scoreboard — re-scored today Produced by running this experiment's exported model through the current frozen scorer on the current eval set. It is not what this experiment was told at the time.
era Four areas, six lighting buckets · ts 2026-07-30T18:42:21 ·
commit 4add08608d95 · artifacts runs/20260730_171527_experiment60 ·
took 86 m 54 s
The code for this experiment — not retainedSource not retained. Until 2026-07-31 the loop discarded a reverted experiment’s code outright, and this row predates that fix. Its design record, its verdict and its exported model survive; the code does not. It is left blank rather than filled with the parent’s code, which is what this row’s recorded commit actually contains. | |||||||||||||||||||||||||||
| ▸ | 2.59 | Independently-trained day/night specialist twins with a trunk-free brightness dispatcher
efa8f69b
The loop discarded this — no improvement under the metric then in force. |
architecture | 1.581 | 80 m 09 s | $6.15 | DISCARDED | ||||||||||||||||||||
|
The design under test — technical diagram — gray = frozen contract · ink = the current design · red = this experiment's change In plain words Instead of one brain trying to memorize the city in both daylight and pitch darkness at once, we build two smaller, separate brains -- one that only ever studies daytime photos, one that only ever studies night photos -- trained completely apart so neither one's lessons get diluted by the other's. A tiny doorman glances at how bright the incoming photo is and decides which brain should answer. We're testing whether the old one-brain-does-everything setup was quietly making both jobs a little worse than they could be on their own. Hypothesis 20 straight reverted rounds have exhaustively swept decode/output parameterizations (dense field, per-patch voting + attention pooling, per-patch voting + IRLS robust consensus, VQ codebook, continuous GMM, coarse-to-fine, retrieve-then-rerank, spatial MoE, canonicalization translators) on top of trunks that were EITHER the banned pretrained MobileNetV3-Small OR a from-scratch conv stack -- and every single one lost to champion (exp-35, 743.07m). The three prior 'dispatcher' rounds in this history (exp-30, exp-46, exp-50) all share one thing none of the decode sweeps questioned: the dispatcher blends two or more expert HEADS that read off a SINGLE SHARED trunk/backbone, trained jointly end to end every step -- so gradients from night crops still shape the features a day crop's expert reads, and vice versa. That is soft task-blending inside one optimization, not specialization. This round's forced pivot bans the champion's shared trunk outright, which finally makes the untested version of the dispatcher idea cheap to build correctly: TWO fully independent from-scratch trunks, each trained in its OWN optimization run on only its own half of the lighting buckets, so a night crop's gradient never touches a single day-specialist weight and vice versa -- true task decomposition, not joint multi-task blending. If the champion's ~743m worst-case has been capped by one shared representation being pulled in two directions (day's rich-texture regime vs. night's near-featureless regime), removing that shared-gradient interference by training two small purpose-built specialists in isolation, then only calibrating a tiny trunk-free dispatcher on top, should relax that cap -- at the cost of each specialist getting a smaller capacity budget than champion's single trunk, since both must fit the same 4 MiB envelope together. Method Replace the single shared-trunk model with two independent from-scratch specialist networks -- Day (morning/midday/afternoon) and Night (early_evening/evening/night) -- each its own small conv trunk + coarse classification field + sharpened soft-argmax decode + confidence head, no weights shared between them and no pretrained init anywhere (mobilenet_v3_small / mnv3s_features8.pt banned this round). A third, tiny, trunk-free dispatcher (3 raw-pixel brightness statistics -> 2-layer MLP -> sigmoid gate g) blends the two specialists' (u, v, conf) outputs for the final export. Training is staged, not joint: Phase 1 trains only the Day specialist on its 3 buckets; Phase 2 trains only the Night specialist on its 3 buckets, from a fresh optimizer with zero gradient crossover into Phase 1's weights; Phase 3 freezes both specialists completely and trains only the ~40-parameter gate across all 6 buckets on the frozen specialists' blended output. One ONNX graph is exported at the end (both specialists always run; the gate blend, not graph branching, does the routing), preserving the single-file single-forward-pass export contract. Conclusion at the time REVERTED — metric did not improve (2134.12 m vs best 743.07 m); change discarded Re-measured on today's ruler Mission score 1.581 — 0.8% of held-out frames give a usable fix, 58.8% are confident and wrong, 40.5% abstain. Median miss 959.9 m. Measured, not converted: its exported model was re-run through the current scorer for this page. What it cost
by model — claude-sonnet-5 $6.14 · claude-haiku-4-5-20251001 $0.01 The agent lines are the equivalent API cost of the tokens each agent used, as recorded by the harness when it ran. The research ran on a claude.ai Max subscription, so that is what it would have cost billed per token — not money that left the account. The GPU line is real spend. What it was measured against then Optimised: worst-case median error (m), target 20 m, scoring 2.13 km. Asked over: region holdout, 6 lighting buckets, 4 areas. The long grind: 62 experiments over ten days against a general-purpose, four-area, six-lighting-bucket problem. Scored on a region holdout that asked a memorization model to locate ground it had never been shown. Scoreboard — re-scored today Produced by running this experiment's exported model through the current frozen scorer on the current eval set. It is not what this experiment was told at the time.
era Four areas, six lighting buckets · ts 2026-07-23T21:50:49 ·
commit efa8f69bd1f7 · artifacts runs/20260723_203040_experiment56 ·
took 80 m 09 s
The code for this experiment — not retainedSource not retained. Until 2026-07-31 the loop discarded a reverted experiment’s code outright, and this row predates that fix. Its design record, its verdict and its exported model survive; the code does not. It is left blank rather than filled with the parent’s code, which is what this row’s recorded commit actually contains. | |||||||||||||||||||||||||||
| ▸ | 2.55 | Dense scene-coordinate voting with a differentiable IRLS robust consensus (from-scratch trunk)
c998b079
The loop discarded this — no improvement under the metric then in force. |
architecture | 1.761 | 21 m 51 s | $3.41 | DISCARDED | ||||||||||||||||||||
|
In plain words Instead of one all-knowing guess about where the photo was taken, we now let roughly a thousand tiny local patches of the same photo each make their own small, independent guess -- like asking a thousand people who each only glimpsed one corner of a map to point at where they think you are. Two earlier attempts at this already failed because they combined all those guesses in one single pass -- either just averaging them all equally, or letting one learned 'attention' pass decide the weights once and stick with it -- so a handful of confused, wrong guesses could still drag the final answer off course. This time the combining step runs several rounds: after each round it checks which guesses agree with the group and which ones look like outliers, and quietly turns down the volume on the outliers before averaging again -- the same trick a surveyor uses when throwing out one wildly-off measurement before re-averaging the rest. If a photo is genuinely confusing (say, featureless forest or open water), the votes never converge and the model honestly says 'not sure' instead of reporting a guess nobody actually agreed on. Hypothesis 20 straight rounds since champion exp-35 (743.07 m) have failed, and this round's forced direction pre-assigns the ACE/DSAC family: dense per-cell coordinate regression aggregated by a robust consensus, not a global classification field. This exact family (one global descriptor replaced by many local location votes, over the same single-crop-center supervision this project actually has) has been measured twice before, and both measurements point at the AGGREGATOR, not the per-cell voting idea, as the failure point: exp-4 (2563.37 m, reverted) used a plain unweighted mean over per-patch coordinate guesses, and exp-49 (2655.65 m, reverted, ~3.6x worse than champion) used a single learned softmax-attention-weighted mean. Both are one-shot, non-iterative pooling: a single set of weights (uniform, or one attention pass) is computed once and applied once, so an outlier vote that the attention head fails to suppress on its first and only pass drags the final answer proportionally to how much weight it received -- there is no mechanism to notice, after seeing the emerging consensus, that a given vote disagrees with it and correct course. This design tests the one aggregator in this family that has never been measured: iteratively reweighted least squares (IRLS) with a redescending kernel, which recomputes vote weights against the CURRENT consensus estimate over several rounds, so a vote that looked plausible in isolation but disagrees with where the other ~1,023 votes are converging gets progressively down-weighted rather than fixed at its first-pass score. This is mechanically closer to what ACE/DSAC actually do at inference (RANSAC re-scores hypotheses against an evolving consensus) than either prior attempt was, and it is a materially different bet from 'per-patch consensus is refuted' -- it is a bet that per-patch consensus was never fairly tested because both prior attempts used a pooling mechanism with no self-correction. Method Replace the champion's global-descriptor + 1024-way map-cell classification field entirely with: (1) a from-scratch, no-pretrained conv trunk producing a 16x16 spatial grid of local features (banned identifiers mnv3s_features8.pt / mobilenet_v3_small do not appear anywhere); (2) a per-cell head that independently regresses an absolute (u, v) coordinate guess plus a log-variance (confidence) from each cell's own local feature vector only -- no global pooling anywhere in the forward path; (3) the crop's four 90-degree C4 rotations each get their own independent trunk pass and their own 16x16 grid of votes, pooled together as 4x16x16 = 1,024 total independent votes (previously the four rotations' full field maps were averaged BEFORE any decision; now every rotation's every cell is one independent vote into the consensus stage); (4) a 3-round differentiable IRLS solve: start from a confidence-weighted mean of all 1,024 votes, then for each of 3 rounds recompute each vote's residual distance to the current estimate, apply a fixed-scale Geman-McClure redescending re-weighting (soft-inlier down-weighting of votes far from the emerging consensus), renormalize, and recompute the weighted mean -- fully differentiable, no argmax/hard threshold/data-dependent control flow anywhere, so it is both trainable end-to-end and ONNX-exportable as a fixed 3-iteration unroll; (5) overall confidence derived from the final iteration's effective weight concentration (inverse of the summed squared normalized weights, i.e. how few votes the answer actually rests on) and the final weighted residual spread, fed through a small calibrated head trained the same way as the champion's hit-predictor (BCE against a within-GOOD_ERR_UV label, detached inputs, post-hoc per-bucket conf_shift calibration). The confusability-weighted training sampler (exp-35) is rebuilt at the level of individual training locations (nearest-distant-location descriptor match, same exclusion radius idea) instead of via the now-deleted GRID_K classification grid, since there is no classifier grid left to bucket by. Loss = final-consensus-estimate L2 + 0.5x mean per-vote heteroscedastic NLL (trains each cell's own sigma to track its own actual error) + 0.3x confidence BCE, mirroring the champion's loss-term weighting shape but built for the new decode. Conclusion at the time REVERTED — metric did not improve (2360.39 m vs best 1176.5 m); change discarded Re-measured on today's ruler Mission score 1.761 — 0.2% of held-out frames give a usable fix, 76.2% are confident and wrong, 23.5% abstain. Median miss 2.44 km. Measured, not converted: its exported model was re-run through the current scorer for this page. What it cost
by model — claude-sonnet-5 $3.40 · claude-haiku-4-5-20251001 $0.00 The agent lines are the equivalent API cost of the tokens each agent used, as recorded by the harness when it ran. The research ran on a claude.ai Max subscription, so that is what it would have cost billed per token — not money that left the account. The GPU line is real spend. What it was measured against then Optimised: worst-case median error (m), target 20 m, scoring 2.36 km. Asked over: region holdout, 6 lighting buckets, 4 areas. The long grind: 62 experiments over ten days against a general-purpose, four-area, six-lighting-bucket problem. Scored on a region holdout that asked a memorization model to locate ground it had never been shown. Scoreboard — re-scored today Produced by running this experiment's exported model through the current frozen scorer on the current eval set. It is not what this experiment was told at the time.
era Four areas, six lighting buckets · ts 2026-07-23T17:03:59 ·
commit c998b0796247 · artifacts runs/20260723_164208_experiment55 ·
took 21 m 51 s
The code for this experiment — not retainedSource not retained. Until 2026-07-31 the loop discarded a reverted experiment’s code outright, and this row predates that fix. Its design record, its verdict and its exported model survive; the code does not. It is left blank rather than filled with the parent’s code, which is what this row’s recorded commit actually contains. | |||||||||||||||||||||||||||
| ▸ | 2.54 | Memory-safe flying delighting translator + from-scratch trunk: the never-fairly-tested canonicalization pivot, corrected
d06f7d8d
The loop discarded this — no improvement under the metric then in force. |
relighting | 1.496 | 19 m 42 s | $2.88 | DISCARDED | ||||||||||||||||||||
|
In plain words Two earlier attempts tried teaching the model to first 'clean up' a dark, noisy night photo into something that looks like a clear daytime picture before trying to figure out where it was taken -- like adjusting brightness and contrast before reading a blurry street sign. Both attempts crashed partway through training for a boring technical reason (the computer ran out of memory), so we never actually found out if the idea works. This round redesigns that cleanup step to be much smaller and lighter -- it works on a shrunk-down, thumbnail version of the picture instead of the full-size one -- so it can't run out of memory the same way, and we finally get to test the idea for real. Everything downstream is also rebuilt to match: a fresh, untrained map-reading network (since the old borrowed one is off-limits this round), with the old 'two separate experts, one for day, one for night' trick removed entirely, because the cleanup step should make that split unnecessary. Hypothesis 18 straight rounds (36-53) have failed to beat champion exp-35's 743.07 m. Splitting them by what they actually changed gives a clean signal: every one of the 11 rounds that swapped or reparameterized the DECISION mechanism while the descriptor-producing trunk was either untouched or forcibly swapped (36-40, 45, 46, 48-53 -- margin losses, coordinate-generated fields, coarse-to-fine, retrieve-rerank, factorized axes, dispatcher+MoE, per-patch consensus, residual VQ, Gaussian-mixture regression, a ShuffleNetV2 trunk paired with FiLM+top-K decode) landed clearly worse than champion, several catastrophically (1e9 x5, 1552-2752 m x6). The ONE round that instead kept the champion's exact field+soft-argmax decode shape and only changed what the trunk was trained to represent (exp 42, domain-native contrastive from-scratch pretraining) came closest of all 17 post-champion rounds at 846.42 m (+14%) -- the tightest gap in the entire losing streak, and the only result inside 1000 m. That single data point argues descriptor/appearance-canonicalization quality, not decision-mechanism shape, is the real remaining lever: champion's ImageNet-MobileNetV3 trunk (now banned) was never taught to discard lighting as a nuisance variable, and no downstream decode rewrite has ever fixed that by construction. CLAUDE.md SS4 explicitly names learned relighting as one of the areas most open to genuine improvement, and library L1 proposes exactly this ('canonicalize the night: denoise toward the clean render') -- but this exact family was tried twice (exp 43 joint reconstruction, exp 44 sequential flying translator) and BOTH were never actually measured: their train logs show a clean 0-byte crash on 2 of 4 areas each round (different area-pairs each time, ruling out an area-specific cause) diagnosed as an OOM/ENOSPC race from loop.sh's 4-way concurrent GPU launch colliding with a full-resolution (128x128) encoder-decoder's large intermediate activations. That is an infra artifact, not a refutation -- the canonicalization bet itself remains untested at the design level, exactly like exp 47/48's per-patch-consensus idea was before its own corrected re-attempt (exp 49) finally measured it (and refuted it, cleanly, this time). This design is that same kind of corrected re-attempt, not a repeat of 43 or 44: the translator is redesigned from scratch to make the OOM/ENOSPC failure structurally impossible (bottlenecked at 32x32 with narrow channels, one pass per crop instead of one pass per C4-rotation-copy) rather than hoping the same shape gets luckier concurrency timing, and the reconstruction loss is computed at a downsampled 32x32 target instead of full 128x128 pixels -- directly following exp 44's own pre-registered falsification fork (c), which predicted that a full-resolution L1 term risks oversmoothing genuine layout detail (building edges) while chasing pixel fidelity, and recommended exactly this fix ('weight the L1 term lower or restrict it to a coarser scale') for the next attempt. Method Insert a tiny from-scratch delighting translator ahead of the trunk in model/model.py: Conv2d(3,16,k=4,s=4) -> 32x32x16, two 3x3 GroupNorm+ReLU convs at 32x32x24, Conv2d(24,3,k=3) -> a 32x32x3 raw correction. The correction is bilinearly upsampled (parameter-free) to 128x128 and added residually to the input crop (clamped to [0,1]) to produce the 'cleaned' image the rest of the network sees; peak activation size never exceeds 32x32x24, and the crop is translated ONCE (not once per C4 rotation copy, unlike exp 44's ordering) -- both changes directly shrink the per-example memory footprint that the OOM/ENOSPC crashes were traced to. Replace the ImageNet-pretrained MobileNetV3-Small trunk (banned this round) with a from-scratch 4-stage plain conv stack (32/64/96/128 channels, stride 2 each, GroupNorm+ReLU), 128x128 -> 8x8x128, reading the CLEANED image's four C4 rotations. Delete the dual-expert lighting-gate blend (loc_logits/dark_logits/gate) entirely -- one unified field (GAP + 1x1-squeezed layout code -> GRID_K x GRID_K logits), matching the only structural choice (42/43/44) that removed the gate without itself causing a measured regression. Keep the existing sharpened-softmax soft-argmax decode exactly (DECODE_BETA mechanism unchanged in form; it is 'changed' only in that it now reads a single field instead of a gated blend, forced by the gate's removal). Confidence head gains one new detached input: the translator's own mean |correction| magnitude (how much repair the crop needed), alongside the existing peak/entropy/sharpened-vs-unsharpened-decode-gap features. Training: sample_epoch also extracts the pixel-aligned clean daytime reference crop (same cx/cy/angle, from reference.tif, already loaded for compute_cell_weights) for every drawn training location -- a free supervision pair per library L1. loss_fn gains a reconstruction term: L1 between (avg_pool2d(input,4) + raw_correction).clamp(0,1) and avg_pool2d(reference_crop,4), weight 0.3 (down from exp 43/44's implicit 1.0, per the falsification-fork-(c) fix). Training schedule: single LR group for all parameters (translator + trunk + head), since nothing is pretrained -- the old 10x trunk/head split existed only to protect transferred ImageNet features, which no longer exist. Conclusion at the time REVERTED — metric did not improve (1929.57 m vs best 1176.5 m); change discarded Re-measured on today's ruler Mission score 1.496 — 0.5% of held-out frames give a usable fix, 50.0% are confident and wrong, 49.5% abstain. Median miss 1.59 km. Measured, not converted: its exported model was re-run through the current scorer for this page. What it cost
by model — claude-sonnet-5 $2.88 · claude-haiku-4-5-20251001 $0.00 The agent lines are the equivalent API cost of the tokens each agent used, as recorded by the harness when it ran. The research ran on a claude.ai Max subscription, so that is what it would have cost billed per token — not money that left the account. The GPU line is real spend. What it was measured against then Optimised: worst-case median error (m), target 20 m, scoring 1.93 km. Asked over: region holdout, 6 lighting buckets, 4 areas. The long grind: 62 experiments over ten days against a general-purpose, four-area, six-lighting-bucket problem. Scored on a region holdout that asked a memorization model to locate ground it had never been shown. Scoreboard — re-scored today Produced by running this experiment's exported model through the current frozen scorer on the current eval set. It is not what this experiment was told at the time.
era Four areas, six lighting buckets · ts 2026-07-23T16:21:10 ·
commit d06f7d8de53f · artifacts runs/20260723_160127_experiment54 ·
took 19 m 42 s
The code for this experiment — not retainedSource not retained. Until 2026-07-31 the loop discarded a reverted experiment’s code outright, and this row predates that fix. Its design record, its verdict and its exported model survive; the code does not. It is left blank rather than filled with the parent’s code, which is what this row’s recorded commit actually contains. | |||||||||||||||||||||||||||
| ▸ | 2.53 | ShuffleNetV2 trunk with FiLM-conditioned single field and top-K decode replaces the two-expert MobileNet blend
01ab7892
The loop discarded this — no improvement under the metric then in force. |
architecture | 1.733 | 21 m 36 s | $2.96 | DISCARDED | ||||||||||||||||||||
|
In plain words Every one of the last 16 tries kept the same borrowed off-the-shelf 'photo recognition' brain and only changed how the model turns its guesses into a final answer -- and none of them won. The one time someone swapped the brain itself instead came closest to beating the champion. So this time we swap in a differently-built starter brain (ShuffleNet instead of MobileNet -- same idea of starting from a brain pretrained on ordinary photos, but organized in a structurally different way, like hiring someone with a different training background rather than re-coaching the same specialist). We also replace the old on/off 'is this a night photo?' light switch -- which forced two separate day-expert and night-expert opinions to be blended -- with a dial that continuously reads how dark the shot is. And when the model commits to an answer, it now only lets its 48 most-likely spots (out of 1024 possible) compete for the final vote, instead of letting one confident-looking but wrong far-away lookalike quietly drag the answer toward it. Hypothesis 16 straight reverted rounds (36-52) all churned the location-decision mechanism (softmax reparameterizations, attention consensus, spatial MoE, hard VQ coding, continuous Gaussian mixture) while leaving champion's ImageNet-MobileNetV3-Small trunk untouched, and every one of them landed well below champion (743.07 m) -- several catastrophically (1000000000.0 m x6, 2140-2751 m x3). The ONE round that instead changed the trunk and kept the field-decode family (exp 42, domain-native contrastive from-scratch trunk) came closest of all 17 post-champion rounds at 846.42 m (+14%), the tightest gap in the entire losing streak. That single data point, plus the uniform failure of every decode-family alternative, points at descriptor information content -- not the decision mechanism -- as the real wall: champion's two hard-gated experts (a layout-aware head and a GAP-only 'dark' head, blended by a sigmoid trained on raw brightness) are a coarse patch over a trunk whose SE-gated inverted-residual features were learned for natural photographs, not confusable low-resolution aerial texture, and whose lighting handling is a binary switch rather than a continuous read of illumination. With that backbone banned this round regardless, the experiment worth running is: does a structurally different pretrained feature family (grouped convolutions + channel shuffle, no depthwise-separable/SE blocks), combined with continuous rather than discrete lighting conditioning, close more of the gap than any of the 11 decode rewrites did -- while also fixing the one real mismatch the champion's own field never addressed: the frozen scorer's soft-argmax reads across all 1024 cells even though only a handful are ever genuinely competitive, letting a confident-looking distant lookalike quietly pull the answer toward it. Method Four coupled changes, one coherent design: (1) model/model.py's trunk is replaced with an ImageNet-pretrained ShuffleNetV2 x0.5 (torchvision, BSD-3) truncated to stem+stage2+stage3 (stride 16, ~96ch @ 8x8 for a 128x128 input) -- a structurally different backbone family from MobileNetV3, loaded from a new local weights asset model/pretrained/shufflenetv2_x0_5_stage3.pt. (2) The two hard-gated field experts (bright head + dark head + sigmoid blend) are deleted; one field FC reads a single descriptor built from GAP + the existing 1x1 spatial-layout squeeze, continuously modulated via FiLM (scale/shift from a tiny MLP over the crop's own mean luminance) instead of a binary day/night gate. (3) The four C4 rotation views are fused at the feature level (element-wise max over the 4 views' FiLM-modulated descriptors) before the single field FC runs once per crop, replacing the old late fusion of two already-blended heatmaps. (4) Decode is restricted to a top-48-of-1024 masked soft-argmax after sharpening (was: full 1024-way soft-argmax), and confidence is recomputed directly from that shortlist's normalized entropy through a 2-parameter calibrated score, replacing the old 4-feature learned confidence MLP. Training (model/train.py) gets three matching changes: confusability-weighted sampling (exp 35) is computed per (cell, lighting bucket) using each bucket's own rendered texture instead of one global reference-image weighting; loss_fn gains one new term, a margin penalty when the true location's cell is not already inside the model's own top-48 shortlist, so the training signal explicitly targets what the new decode reads; and the LR schedule adds a 500-step linear warmup before cosine decay, with the pretrained-trunk LR ratio dropped from 10x to 20x below the head LR. Conclusion at the time REVERTED — metric did not improve (1552.08 m vs best 1176.5 m); change discarded Re-measured on today's ruler Mission score 1.733 — 0.5% of held-out frames give a usable fix, 73.8% are confident and wrong, 25.8% abstain. Median miss 1.83 km. Measured, not converted: its exported model was re-run through the current scorer for this page. What it cost
by model — claude-sonnet-5 $2.95 · claude-haiku-4-5-20251001 $0.00 The agent lines are the equivalent API cost of the tokens each agent used, as recorded by the harness when it ran. The research ran on a claude.ai Max subscription, so that is what it would have cost billed per token — not money that left the account. The GPU line is real spend. What it was measured against then Optimised: worst-case median error (m), target 20 m, scoring 1.55 km. Asked over: region holdout, 6 lighting buckets, 4 areas. The long grind: 62 experiments over ten days against a general-purpose, four-area, six-lighting-bucket problem. Scored on a region holdout that asked a memorization model to locate ground it had never been shown. Scoreboard — re-scored today Produced by running this experiment's exported model through the current frozen scorer on the current eval set. It is not what this experiment was told at the time.
era Four areas, six lighting buckets · ts 2026-07-23T16:01:06 ·
commit 01ab78924253 · artifacts runs/20260723_153930_experiment53 ·
took 21 m 36 s
The code for this experiment — not retainedSource not retained. Until 2026-07-31 the loop discarded a reverted experiment’s code outright, and this row predates that fix. Its design record, its verdict and its exported model survive; the code does not. It is left blank rather than filled with the parent’s code, which is what this row’s recorded commit actually contains. | |||||||||||||||||||||||||||
| ▸ | 2.52 | Continuous Gaussian-mixture location regression replaces the shared map-cell classification field
5361f941
The loop discarded this — no improvement under the metric then in force. |
architecture | 1.476 | 21 m 32 s | $3.90 | DISCARDED | ||||||||||||||||||||
|
In plain words For 16 rounds straight, every fix has tried to make one master scoreboard -- ranking up to 1,024 possible neighborhoods against each other -- work a little better, whether that scoreboard was one big table, a filing-cabinet lookup, or a panel of votes. This round throws the scoreboard out completely. Instead, the model gets to say 'I think it's one of these 6 specific spots' (times 4, once per compass direction it double-checks itself from) and picks whichever single guess it's most sure about, the same way a person narrows down a place to a handful of candidates rather than ranking every neighborhood in the city against each other. Because the backbone network powering last round's champion is banned this round, we also had to retire the old sampling trick that leaned on a classroom-style grid of neighborhoods, and replace it with one that works location-by-location instead. Hypothesis 16 straight rounds since champion exp-35 (743.07 m) have failed, and every one of them left one thing structurally intact even when it looked abandoned: SOME shared, jointly-normalized comparison across many location candidates decides the answer -- a flat 1024-way softmax (36-40, 45, 46), a per-region softmax repeated 64 times (50), an attention-weighted mean over 256 votes (47-49), or a hard-argmin residual-VQ codebook lookup (51, which explicitly abandoned softmax for exclusive nearest-neighbor coding and still scored 2751.73 m, 3.7x worse than champion). champion-failure-mode-probes diagnosed the wall precisely: the true cell is the classifier's top-1 pick only ~2% of the time despite landing in its own top-20 (of 1024) on 33-58% of crops -- confusable lookalikes stay comparable under one shared scale no matter how that scale is reparameterized, factorized, shortlisted, or replaced with hard coding. This design tests the one parameterization family absent from all 51 rounds: a small, fully continuous, per-crop Gaussian mixture over (u,v), with each component's mean a FREE regression output (not a fixed grid center, not a codebook entry, not a spatially-partitioned expert), trained by exact mixture negative-log-likelihood (proper probabilistic multimodal regression, Bishop-style MDN) instead of cross-entropy-over-candidates, hard nearest-neighbor coding, or attention-averaged consensus. A K=6-component mixture per rotation view, pooled across the 4 C4 views into one 24-component mixture, gives the model just enough hypotheses to represent genuine ambiguity between a handful of lookalike districts -- without ever forcing 1024, 256, or 2304 candidates into one comparison at once, and without exp 37's winner-take-all-L2 failure mode (dead/uninitialized hypotheses starved of any gradient): every component gets a soft NLL-weighted gradient in proportion to its own likelihood under the true target, every step. The mnv3s_features8.pt / mobilenet_v3_small backbone is banned this round, which forces the trunk to become from-scratch anyway -- rather than treat that as a tax paid while leaving the decision mechanism alone (the exact failure the patience rule calls out), the forced trunk change is the occasion to also delete the GRID_K cell abstraction entirely: with no classification grid anywhere in the model, the champion's training-data mechanism (exp 35's confusability-weighted sampling, computed over 32x32 GRID_K classifier cells) has no cells left to attach to, so it is rebuilt at the level the new architecture actually operates on: individual training locations. This is a genuine whole-pipeline pivot (trunk, decode, loss, confidence, and the sampler all change together because the grid they shared is gone), not a decode swap bolted onto an unexamined trunk. Method Replace TinyLocNet's ImageNet-pretrained MobileNetV3-Small trunk + GAP/layout head + gated dark-expert + GRID_K x GRID_K classification field + beta-sharpened soft-argmax decode + field-shape confidence, in model/model.py, with: (1) a from-scratch conv trunk (~4 stride-2 blocks, 128x128 -> 8x8, ~300-400k params, no pretrained weights, no torchvision import); (2) a shared small head that reads {GAP descriptor, 1x1-squeezed spatial layout code} and outputs, per rotation view, K=6 mixture components (mean_u, mean_v via sigmoid to stay in [0,1], a scalar log_sigma, and a mixing logit); (3) the 4 C4 rotation views' components are pooled into one 24-component mixture (concatenate, renormalize mixing weights by softmax jointly over all 24 logits) instead of averaging field logits; (4) decode = the mean of the single highest-weight component (hard mode-commit, matching the project's established peak-commit lesson from exp 14 rather than a soft blended mean, which would reintroduce centroid shrinkage); (5) loss_fn becomes exact 2D-Gaussian mixture NLL over the pooled 24 components (isotropic covariance per component, log_sigma floored for numerical stability) plus the existing conf BCE term, recomputed on new features; (6) confidence is now {winning component's own mixing weight (post-hoc renormalized among the 24), winning component's own sigma, and the mixture weight distribution's entropy over all 24 components} in place of the deleted field's peak-mass/entropy/sharpened-vs-unsharpened-gap features -- same conf_head MLP shape and the same conf_shift calibration mechanism in train.py's calibrate_conf_shift (untouched procedure, only its downstream inputs change since there is no more field to read). In model/train.py: compute_cell_weights' GRID_K x GRID_K grid-cell confusability weighting is replaced by a per-crop-location confusability weight computed directly at the enumerable crop resolution (same cheap RGB+luminance-std descriptor from reference.tif, same nearest-DISTANT-match logic and neighbor exclusion radius, just indexed by individual crop position instead of by a now-nonexistent classification cell) -- crop_probs in train_area is built the same way structurally, just without the crop_cell/GRID_K indirection. The optimizer's param groups collapse from {trunk: 1e-4, head: 1e-3} to a single uniform LR group with cosine annealing (the 10x trunk/head LR split existed specifically to protect pretrained ImageNet features during fine-tuning; a from-scratch trunk has nothing to protect and should train at the same rate as the rest of the network). Epoch count, TRAIN_REALIZATIONS, batch size, and the C4 exact-rotation pixel-copy mechanism (_c4_stack) are unchanged. train.py's CLI (--area/--out-dir/--data-dir/--epochs/--max-crops-per-bucket/--seed) and model/model.py's ONNX export contract (1x3x128x128 float32 in [0,1] -> [[u, v, conf]]) are preserved exactly -- the frozen scorer depends on both. No reference to mnv3s_features8.pt, mobilenet_v3_small, or torchvision.models anywhere in model/ (backbonecheck.py scans the actual source, not self-reported intent). Conclusion at the time REVERTED — metric did not improve (2140.32 m vs best 1176.5 m); change discarded Re-measured on today's ruler Mission score 1.476 — 0.2% of held-out frames give a usable fix, 47.8% are confident and wrong, 52.0% abstain. Median miss 1.86 km. Measured, not converted: its exported model was re-run through the current scorer for this page. What it cost
by model — claude-sonnet-5 $3.89 · claude-haiku-4-5-20251001 $0.00 The agent lines are the equivalent API cost of the tokens each agent used, as recorded by the harness when it ran. The research ran on a claude.ai Max subscription, so that is what it would have cost billed per token — not money that left the account. The GPU line is real spend. What it was measured against then Optimised: worst-case median error (m), target 20 m, scoring 2.14 km. Asked over: region holdout, 6 lighting buckets, 4 areas. The long grind: 62 experiments over ten days against a general-purpose, four-area, six-lighting-bucket problem. Scored on a region holdout that asked a memorization model to locate ground it had never been shown. Scoreboard — re-scored today Produced by running this experiment's exported model through the current frozen scorer on the current eval set. It is not what this experiment was told at the time.
era Four areas, six lighting buckets · ts 2026-07-23T15:39:03 ·
commit 5361f94153a4 · artifacts runs/20260723_151731_experiment52 ·
took 21 m 32 s
The code for this experiment — not retainedSource not retained. Until 2026-07-31 the loop discarded a reverted experiment’s code outright, and this row predates that fix. Its design record, its verdict and its exported model survive; the code does not. It is left blank rather than filled with the parent’s code, which is what this row’s recorded commit actually contains. | |||||||||||||||||||||||||||
| ▸ | 2.51 | Residual vector-quantized location codebook: hard nearest-neighbor coding replaces the shared-softmax cell field
4752c6c2
The loop discarded this — no improvement under the metric then in force. |
quantization | 1.646 | 28 m 14 s | $3.73 | DISCARDED | ||||||||||||||||||||
|
In plain words Instead of asking the model to rank the crop's location against 1,024 possible spots on one shared scoreboard -- which is exactly why a lookalike neighborhood clear across town keeps beating the real one -- we give it two small 'which of these 48 does this look most like' filing steps instead, like sorting mail into a coarse bin first and then a fine sub-bin, each bin owning its own rough map position. Nothing ever has to out-rank all 1,024 rivals at once anymore, and every filing decision is graded directly by how close the final guessed spot lands to the truth, so mixing up two look-alike but far-apart places now directly costs the model accuracy instead of being a shrug-worthy near-miss. Hypothesis 15 straight rounds (36-50) have failed to beat champion exp-35's 743.07 m, and the project's own diagnostic probes (champion-failure-mode-probes memory) pin the wall precisely: the true cell is the field's top-1 pick only ~2% of the time despite landing in its own top-20 (of 1024) on 33-58% of crops in every bucket -- a lookalike cell on the far side of the city stays directly comparable, in raw logit magnitude, to the true cell under ONE shared softmax normalization. Every one of the 15 rounds since the champion attacked this from within that same family and lost: flat-table reparameterizations (39: coordinate-generated field, 1103.28 m; 40: coarse-to-fine field+offset, 1011.58 m; 45: retrieve-then-rerank shortlist, 1213.14 m; 46: factorized row/col axes, gated 1e9), objective swaps on the SAME field (34/36: hardest-impostor margin hinge, twice refuted), trunk-objective swaps feeding the SAME field (41-44: contrastive/reconstruction pretraining, 846.42 m best), and even the two designs that finally left classification-with-a-shared-softmax behind still lost badly: exp 49's dense per-patch consensus (no field, 256 votes attention-averaged into one blended answer) scored 2655.65 m -- worse than the champion by 3.6x, a real, measured refutation of naive soft-averaging as the fix; exp 50's spatial mixture-of-experts (disjoint per-neighborhood classifiers, still classification+softmax at both the coarse and fine stage, just partitioned) scored 1109.13 m. Both of the non-classification attempts still routed every decision through a SHARED, dense, differentiable mechanism -- one shared attention-weighted mean (49) or one shared teacher-forced geographic partition (50) -- so a lookalike's contribution to the final answer was diluted, not structurally excluded. Across all 50 rounds and 7 category values used (architecture/loss/augmentation/relighting/training/other), 'quantization' -- named explicitly in CLAUDE.md SS3 as an open axis and by this round's own patience directive as a legitimate untried pivot family -- has NEVER been the primary driver of a single experiment. This design tests it directly: replace the classifier (or classifier-like field/voting mechanism) entirely with a small, LEARNED, two-stage residual vector-quantization codebook. Location is encoded by hard nearest-neighbor lookup (argmin L2 distance) against a 48-entry coarse codebook, then the residual is looked up again against a second, independent 48-entry fine codebook -- never a shared softmax over more than 48 comparably-scaled candidates at once, and never a soft blend across all candidates the way exp 49's attention-weighted mean was. Because the codebook indices are also graded by the downstream coordinate-regression loss (via a straight-through gradient into the encoder), two geographically distant but visually similar locations that would collide onto the same code pair directly increase training loss for one of them -- concrete gradient pressure to keep them apart, which cross-entropy's 'good enough if the true class has decent relative probability' criterion never applied. Method In model/model.py: delete the ImageNet/MobileNetV3-Small trunk, the 1024-way loc_logits/dark_logits/gate field, GRID_K/_grid_centers/DECODE_BETA/TARGET_SIGMA_CELLS, and the soft-argmax decode entirely. Replace with: (1) a from-scratch 4-stage plain conv stack (32->64->96->128 ch, stride 2 each, GroupNorm+ReLU, 128x128->8x8x128, no pretrained weights anywhere); (2) a descriptor head (128-d GAP + a 1x1-conv layout squeeze of the 8x8 grid, concatenated, then linearly projected to a 64-d 'address' vector); (3) a two-stage residual VQ codebook -- stage-1 nn.Embedding(48,64), stage-2 nn.Embedding(48,64) applied to the stage-1 residual, both with argmin-L2 hard assignment + straight-through gradient + commitment/codebook losses; (4) a coordinate-lookup decode -- nn.Embedding(48,2) coarse coordinate per stage-1 code + nn.Embedding(48,2) tanh-bounded fine offset per stage-2 code, summed and clamped to [0,1]; (5) a confidence head reading the stage-1 and stage-2 quantization residual norms plus each stage's margin-to-runner-up-code, replacing the old field-shape confidence input. In model/train.py: delete compute_cell_weights/CONFUSABILITY_ALPHA/NEIGHBOR_EXCLUDE_R/crop_cell (GRID_K-based static pixel-appearance table) and replace with a dynamic per-training-location EMA of the model's own live squared coordinate error, recomputed into the sampling distribution at the start of every epoch, blended 0.5/0.5 against uniform (same blend spirit as the retired confusability sampler, now driven by the new architecture's own regression signal instead of a precomputed pixel-similarity table, since there is no cell grid left to compute pixel confusability over). Conclusion at the time REVERTED — metric did not improve (2751.73 m vs best 1176.5 m); change discarded Re-measured on today's ruler Mission score 1.646 — 0.0% of held-out frames give a usable fix, 64.5% are confident and wrong, 35.5% abstain. Median miss 2.66 km. Measured, not converted: its exported model was re-run through the current scorer for this page. What it cost
by model — claude-sonnet-5 $3.72 · claude-haiku-4-5-20251001 $0.00 The agent lines are the equivalent API cost of the tokens each agent used, as recorded by the harness when it ran. The research ran on a claude.ai Max subscription, so that is what it would have cost billed per token — not money that left the account. The GPU line is real spend. What it was measured against then Optimised: worst-case median error (m), target 20 m, scoring 2.75 km. Asked over: region holdout, 6 lighting buckets, 4 areas. The long grind: 62 experiments over ten days against a general-purpose, four-area, six-lighting-bucket problem. Scored on a region holdout that asked a memorization model to locate ground it had never been shown. Scoreboard — re-scored today Produced by running this experiment's exported model through the current frozen scorer on the current eval set. It is not what this experiment was told at the time.
era Four areas, six lighting buckets · ts 2026-07-23T15:17:23 ·
commit 4752c6c2d473 · artifacts runs/20260723_144909_experiment51 ·
took 28 m 14 s
The code for this experiment — not retainedSource not retained. Until 2026-07-31 the loop discarded a reverted experiment’s code outright, and this row predates that fix. Its design record, its verdict and its exported model survive; the code does not. It is left blank rather than filled with the parent’s code, which is what this row’s recorded commit actually contains. | |||||||||||||||||||||||||||
| ▸ | 2.50 | Spatial mixture-of-experts: a coarse macro-region classifier dispatches to disjoint local fine-cell experts
e4a5f33d
The loop discarded this — no improvement under the metric then in force. |
architecture | 1.408 | 64 m 50 s | $4.72 | DISCARDED | ||||||||||||||||||||
|
In plain words Imagine asking one person to instantly recognize the right one of 1024 nearly-identical street corners scattered across an entire city -- two corners on opposite sides of town can look confusingly alike, and that mix-up has sunk every design tried so far. Instead, we now ask an easy question first: 'which of the city's 64 neighborhoods is this?' (neighborhoods look quite different from each other, so this is a much easier call). Then we hand the photo to that neighborhood's own specialist, who only ever has to tell apart the 16 corners inside their own neighborhood -- corners that are actually near each other, not confusable look-alikes from across town. Each specialist only ever practices on their own neighborhood's photos, so they get good at exactly the local judgment call that matters, instead of one generalist trying to keep straight a whole city's worth of look-alikes at once. Hypothesis champion-failure-mode-probes shows the real wall is ranking, not decode: the true cell is the flat 1024-way field's top-1 pick only ~2% of eval crops despite landing in its own top-20 33-58% of the time -- the descriptor already narrows candidates down, but nothing has ever fixed the final ranking. Every one of the last 14 experiments (36-49), including the three straight dense-per-patch-consensus attempts (47-49, the last of which actually trained and scored 2655.65 m -- far worse than champion, so that specific mechanism is now empirically refuted, not just untested) and the five backbone swaps (41-46), left one thing structurally intact: ONE set of weights producing ONE softmax normalization that every cell (or every patch vote) is scored under. Cross-entropy under one shared softmax forces a lookalike cell on the far side of the city to stay directly comparable, in raw logit magnitude, to the true cell -- exactly the setting where near-identical texture between distant places creates a ranking tie the network can't break, no matter how the descriptor or the decode is reparameterized (flat field, coordinate-generated field, coarse-shared-head, factorized row/column axes, shortlist rerank, per-patch votes all tried, all failed). This design removes the shared-scale requirement by splitting the decision into two genuinely independent stages: a 64-way coarse macro-region classifier (large ~875 m regions, a much easier discrimination problem than 1024-way) picks a region, then ONE of 64 disjoint local 16-way classifiers -- each trained ONLY on the samples whose true location falls under its own macro cell (teacher-forced loss routing, never on the other 63 regions' data) -- ranks the ~220 m fine cells inside that region alone. A fine expert never has to out-score a distant lookalike, because that lookalike's cell lives entirely inside a different expert's disjoint 16-way softmax and is structurally invisible to it. This training-signal shape (per-region teacher-forced disjoint experts) has never been used in this project's history -- exp 30 and exp 46's 'dispatchers' routed on LIGHTING regime and blended full fields together, they did not train disjoint SPATIAL experts. Method In model/model.py: delete the ImageNet-pretrained MobileNetV3-Small trunk entirely (_build_pretrained_trunk, PRETRAINED_TRUNK_PATH, the mnv3s_features9.pt load, IMAGENET_MEAN/STD buffers, the torchvision import) and the C4 exact-90-degree inference-time rotation ensemble. Build a from-scratch 4-stage plain conv trunk (32->64->96->128 channels, stride 2 per stage, 128x128 -> 8x8x128, GroupNorm+ReLU, no pretrained weights, no torchvision backbone import anywhere). Replace the gated bright/dark two-expert field with: (1) a shared descriptor (128-d GAP concatenated with a 4-channel 1x1-conv layout squeeze flattened to 256-d, giving 384-d total); (2) a 64-way macro-region classifier, one Linear(384, 64); (3) a fine-cell expert bank, one Linear(384, 1024) reshaped to [N, 64, 16] -- mathematically 64 disjoint per-region 16-way heads sharing only the common input descriptor; independence comes from the LOSS (each 16-column slice is only ever teacher-forced against samples whose true location falls in that macro cell), not from separate modules. Decode (inference/export path, used for the shipped (u,v)): argmax the macro softmax, gather that macro's own 16-column slice from the fine bank, sharpen with the existing DECODE_BETA softmax, soft-argmax over that macro's 16 local cell-center offsets, add the argmax macro's own origin -- this IS the exported forward() path, no ground truth involved. Confidence: replace the old GAP+peak/entropy/gap hit-predictor with one reading [macro top-1 softmax prob, macro top1-top2 margin, selected-region's local peak mass, local entropy] (all detached) through a small Linear-ReLU-Linear-sigmoid, same conf_shift calibration buffer/mechanism as today (train.py's calibrate_conf_shift needs no changes -- it only touches model output[:,2] and model.conf_shift). Rotation robustness relies solely on train.py's existing continuous per-crop heading augmentation (sample_epoch's `rng.uniform(0,360)`, already there, untouched) now that the C4 ensemble is gone -- this frees the 4x inference compute the old ensemble spent, which comfortably covers the new dispatch machinery's cost (~2.4 MB / negligible extra FLOPs). Loss (model.py's loss_fn, called as today via `out, logits = model(x, return_logits=True); loss = loss_fn(out, logits, y)` -- `logits` becomes the tuple (macro_logits [N,64], fine_logits_all [N,64,16])): (a) macro CE against a Gaussian-smoothed target over the 8x8 macro grid (sigma ~0.8 macro-cell units, same math as today's TARGET_SIGMA_CELLS but on the coarse grid); (b) fine CE -- compute the TRUE macro index directly from target_uv (floor(u*32)//4, floor(v*32)//4), gather that macro's 16-column slice from fine_logits_all, and take CE against a Gaussian-smoothed target over the local 4x4 grid (sigma ~1.2 fine-cell units) built from the continuous target position expressed in local macro-relative fine-cell units -- gradient here only ever reaches the TRUE macro's 16 columns for that sample, never the other 63 regions'; (c) coord_loss -- for a clean, non-noisy training signal, decode a TEACHER-FORCED (u,v) from the same true-macro fine slice (sharpened softmax + local soft-argmax + true macro origin, computed inside loss_fn, not from `pred`) and L2 it against target_uv, so fine-head accuracy trains cleanly from step one instead of only through whichever macro argmax happens to pick early on; (d) conf_loss -- unchanged BCE shape, but the 'good' label is computed from the REAL deployed decode's error (`pred[:,:2]` vs target, i.e. the actual hard-argmax path), since confidence must calibrate against what will actually ship, not the oracle teacher-forced answer. Total loss = macro_ce + fine_ce + coord_loss + 0.3*conf_loss. Keep GRID_K = 32 exported from model.py (as MACRO_K=8 * FINE_K=4) so train.py's compute_cell_weights/crop_cell confusability-weighting code (which only ever uses the flat gy*32+gx index) needs no changes and remains valid -- it is not dead weight here, since the flat index maps 1:1 onto (macro, fine) via gy//4,gx//4 / gy%4,gx%4. In model/train.py: (i) collapse the optimizer from the pretrained-trunk 10x-lower-LR split (trunk_params/head_params groups) to a single Adam param group at 1e-3 for all of model.parameters(), since nothing is pretrained anymore; (ii) update the `init` string logged in train_area()'s info dict from the mobilenet_v3_small description to 'from-scratch 4-stage conv trunk + hierarchical 64-macro x 16-fine dispatch head'; (iii) ADD one new field to compute_cell_weights' output usage: also compute a macro-level confusability weight (same nearest-distant-lookalike-descriptor logic, but on the 8x8 macro-block-averaged reference.tif descriptor instead of the 32x32 cell descriptor) and blend it 0.5/0.5 with the existing per-cell weight before the CONFUSABILITY_ALPHA blend, so the sampler's oversampling signal reflects both granularities the new head actually dispatches on -- everything else in sample_epoch/prepare_realizations/calibrate_conf_shift/list_crops is untouched. Preserve exactly: train.py's CLI (`--area --out-dir --data-dir --epochs --max-crops-per-bucket --seed`), and model.py's ONNX export contract (1x3x128x128 float32 in [0,1] -> [[u, v, conf]] out, u/v normalized map coords, conf in [0,1]); export_onnx() itself needs no changes. Verify per-area ONNX stays well under the 4 MiB gate (estimated ~2.4 MB: ~186k trunk params + ~420k head/bank params, all fp32) and host latency proxy under 250 ms (single forward pass, no more 4x C4 stacking, so this pivot should sit comfortably under the OLD champion's already-passing latency, not closer to the gate). Conclusion at the time REVERTED — metric did not improve (1109.13 m vs best 743.07 m); change discarded Re-measured on today's ruler Mission score 1.408 — 5.8% of held-out frames give a usable fix, 46.5% are confident and wrong, 47.8% abstain. Median miss 588.8 m. Measured, not converted: its exported model was re-run through the current scorer for this page. What it cost
by model — claude-sonnet-5 $3.97 · claude-haiku-4-5-20251001 $0.00 The agent lines are the equivalent API cost of the tokens each agent used, as recorded by the harness when it ran. The research ran on a claude.ai Max subscription, so that is what it would have cost billed per token — not money that left the account. The GPU line is real spend. What it was measured against then Optimised: worst-case median error (m), target 20 m, scoring 1.11 km. Asked over: region holdout, 6 lighting buckets, 4 areas. The long grind: 62 experiments over ten days against a general-purpose, four-area, six-lighting-bucket problem. Scored on a region holdout that asked a memorization model to locate ground it had never been shown. Scoreboard — re-scored today Produced by running this experiment's exported model through the current frozen scorer on the current eval set. It is not what this experiment was told at the time.
era Four areas, six lighting buckets · ts 2026-07-23T05:57:51 ·
commit e4a5f33d0372 · artifacts runs/20260723_045301_iter10 ·
took 64 m 50 s
The code for this experiment — not retainedSource not retained. Until 2026-07-31 the loop discarded a reverted experiment’s code outright, and this row predates that fix. Its design record, its verdict and its exported model survive; the code does not. It is left blank rather than filled with the parent’s code, which is what this row’s recorded commit actually contains. | |||||||||||||||||||||||||||
| ▸ | 2.49 | Dense per-patch coordinate consensus (re-run after ENOSPC): from-scratch trunk, no map-cell field, error-driven sampler, disagreement-based confidence
7d7bea5d
The loop discarded this — no improvement under the metric then in force. |
architecture | 1.311 | 54 m 57 s | $3.52 | DISCARDED | ||||||||||||||||||||
|
In plain words Every past attempt asked one 'brain' to look at a whole photo and pick the single most likely spot on the map -- and it kept getting fooled by lookalike neighborhoods, even though it usually had the right answer somewhere in its shortlist. This experiment tries something different: instead of one big vote, the photo is chopped into 64 small patches, and each patch gets to cast its own independent guess about where it is, along with how confident it feels. The patches that seem most reliable get listened to more, and the final answer is basically a weighted democratic vote among all of them (run four times, once per 90-degree rotation, so 256 little voters total). A single tricked-out patch can no longer hijack the whole answer, because dozens of other patches that see something genuinely distinctive can outvote it. Confidence is also rebuilt from scratch: instead of training a separate mini-network to guess whether the answer is trustworthy, we just look directly at whether the 256 voters agree with each other. (This exact design was supposed to run last iteration but a disk-space bug crashed all four training jobs before they produced a single number -- we found and cleared the stale files causing it, so this iteration is the design's first real test.) Hypothesis Every one of rounds 36-46, including the 5 that swapped the backbone away from mnv3s_features8.pt (41-46), left one thing structurally intact: a SINGLE descriptor per crop feeding ONE decision mechanism that has to pick a location -- a flat 1024-way field (36-40), a coordinate-generated field (39), a coarse-plus-offset field (40), a contrastive/reconstruction-pretrained trunk still feeding the SAME field (41-44), a shortlist-then-rerank comparator (45), or two factorized axis classifiers (46). champion-failure-mode-probes memory shows exactly what that costs: the true cell is the champion field's top-1 pick only ~2% of the time despite landing in its own top-20 (of 1024) 33-58% of crops -- the descriptor already narrows the field down, but no single reparameterization of the final decision or of the descriptor feeding it has ever closed that gap, because all of them still route through one pooled global vector and one final decision. Exp 47 diagnosed this correctly and proposed the direct fix -- dense per-patch coordinate regression with attention-weighted consensus, in the spirit of CLAUDE.md SS3's named prior art (DSAC/ACE) -- but was rejected before implementation for leaving Output and the sampler untouched. Exp 48 was the corrected re-attempt: same core bet, with Output's confidence and the training sampler genuinely rebuilt around the new per-patch-vote representation instead of left as dead weight tied to a now-deleted classifier field. Exp 48 was implemented correctly (it passed pivot compliance) but every one of its four area-training jobs crashed with `OSError: [Errno 28] No space left on device` partway through -- it was NEVER actually trained or scored; the 1e9 recorded against it is an infrastructure artifact, not a measurement (same pattern as exp27-enospc-never-scored and exp33-oom-never-scored in memory). Root cause diagnosed this round: `/workspace` was at 100% (17M free of 50G), driven by `state/quarantine/` (20G of git-excluded PNG renders from already-scored, already-reverted experiments) plus stale `runs/*/train_*/renders/` from three old completed iterations (13G total) that were never cleaned up after those experiments were logged. Both are pure scratch with no scientific content -- already recorded in git/sqlite, or explicitly excluded from git as too large to keep. Cleared both this round (33G now free, confirmed via df), so this iteration is not a repeat of a refuted mechanism (the plateau rule's concern) but the FIRST actual test of a design that was correctly pre-registered, correctly approved, and never measured due to an unrelated disk-lifecycle bug. Method Identical mechanism to exp 48 (unchanged from its pre-registration, since the design itself was never invalidated): replace TinyLocNet's ImageNet-pretrained-MobileNetV3-Small trunk + gated 1024-way classification field + soft-argmax entirely with: (1) a from-scratch 4-stage plain conv trunk (32->64->96->128 channels, stride 2 each, 128x128 -> 8x8x128, no pretrained weights, no torchvision backbone import); (2) a single shared 1x1 conv 'voter head' that reads each of the 64 spatial positions independently and outputs that patch's own (u,v) guess (sigmoid-bounded to [0,1]) plus a raw reliability logit -- run over the crop's own C4 stack (4 exact 90-degree rotations) for 4x64=256 total independent votes; (3) a learned-temperature softmax over the 256 reliability logits produces attention weights, and the final (u,v) is their weighted mean -- this IS the decode, replacing soft-argmax over a field entirely; (4) confidence is a deterministic, non-learned function of the 256 votes' attention-weighted spread around their own mean plus the attention distribution's entropy, passed through a fixed linear-then-sigmoid transform with a single calibrated shift buffer (reusing train.py's existing calibrate_conf_shift mechanism unchanged) -- no conf_head module, no BCE loss term; (5) the training signal is a Huber loss on the pooled consensus decode, plus a per-vote regression term (each patch's own guess graded against the true location, weighted by its OWN current -- detached -- attention weight), plus a small entropy-floor term penalizing the attention distribution collapsing to a single dominant vote; (6) the training sampler drops the static confusability-weighted per-cell table entirely and replaces it with a dynamic per-location error EMA: every location carries a running estimate of the model's own current squared coordinate error there, recomputed after every minibatch, and each epoch's location draw is blended (alpha=0.7 error-weighted / 0.3 uniform) toward wherever the model is currently doing worst. Conclusion at the time REVERTED — metric did not improve (2655.65 m vs best 743.07 m); change discarded Re-measured on today's ruler Mission score 1.311 — 0.0% of held-out frames give a usable fix, 31.0% are confident and wrong, 69.0% abstain. Median miss 2.46 km. Measured, not converted: its exported model was re-run through the current scorer for this page. What it cost
by model — claude-sonnet-5 $2.88 · claude-haiku-4-5-20251001 $0.00 The agent lines are the equivalent API cost of the tokens each agent used, as recorded by the harness when it ran. The research ran on a claude.ai Max subscription, so that is what it would have cost billed per token — not money that left the account. The GPU line is real spend. What it was measured against then Optimised: worst-case median error (m), target 20 m, scoring 2.66 km. Asked over: region holdout, 6 lighting buckets, 4 areas. The long grind: 62 experiments over ten days against a general-purpose, four-area, six-lighting-bucket problem. Scored on a region holdout that asked a memorization model to locate ground it had never been shown. Scoreboard — re-scored today Produced by running this experiment's exported model through the current frozen scorer on the current eval set. It is not what this experiment was told at the time.
era Four areas, six lighting buckets · ts 2026-07-23T04:37:35 ·
commit 7d7bea5d9e1f · artifacts runs/20260723_034238_iter9 ·
took 54 m 57 s
The code for this experiment — not retainedSource not retained. Until 2026-07-31 the loop discarded a reverted experiment’s code outright, and this row predates that fix. Its design record, its verdict and its exported model survive; the code does not. It is left blank rather than filled with the parent’s code, which is what this row’s recorded commit actually contains. | |||||||||||||||||||||||||||
| ▸ | 2.48 | Dense per-patch coordinate consensus (corrected re-attempt): from-scratch trunk, no map-cell field, error-driven sampler, disagreement-based confidence
aedcce64
Failed a deployment gate: too large, too slow, or it abstained on too many frames to be scoreable. |
architecture | — | 14 m 08 s | $3.25 | GATED FAIL | ||||||||||||||||||||
|
In plain words Every past attempt asked one 'brain' to look at a whole photo and pick the single most likely spot on the map -- and it kept getting fooled by lookalike neighborhoods, even though it usually had the right answer somewhere in its shortlist. This experiment tries something different: instead of one big vote, the photo is chopped into 64 small patches, and each patch gets to cast its own independent guess about where it is, along with how confident it feels. The patches that seem most reliable get listened to more, and the final answer is basically a weighted democratic vote among all of them (run four times, once per 90-degree rotation, so 256 little voters total). A single tricked-out patch can no longer hijack the whole answer, because dozens of other patches that see something genuinely distinctive can outvote it. Confidence is also rebuilt from scratch: instead of training a separate mini-network to guess whether the answer is trustworthy, we just look directly at whether the 256 voters agree with each other -- lots of agreement means high confidence, a split vote means low confidence. Hypothesis Every one of the last 12 rounds (36-47), including the 5 that swapped the backbone away from mnv3s_features8.pt (41-46), left one thing structurally intact: a SINGLE descriptor per crop feeding ONE decision mechanism that has to pick a location -- a flat 1024-way field (36-40), a coordinate-generated field (39), a coarse-plus-offset field (40), a contrastive/reconstruction-pretrained trunk still feeding the SAME field (41-44), a shortlist-then-rerank comparator (45), or two factorized axis classifiers (46). champion-failure-mode-probes memory shows exactly what that costs: the true cell is the champion field's top-1 pick only ~2% of the time despite landing in its own top-20 (of 1024) 33-58% of crops -- the descriptor already narrows the field down, but no single reparameterization of the final decision (36-40, 45, 46) or of the descriptor feeding it (41-44) has ever closed that gap, because all of them still route through one pooled global vector and one final decision. Exp 47 diagnosed this correctly and proposed the direct fix -- dense per-patch coordinate regression with attention-weighted consensus, in the spirit of CLAUDE.md SS3's named prior art (DSAC/ACE): every one of 64 spatial locations on the trunk's own 8x8 feature grid (x4 for the C4 rotation copies = 256 total) independently proposes its own (u, v) guess plus a reliability weight from ONLY its own local receptive field, no global pooling, no map-cell dictionary at all -- but exp 47 was REJECTED BEFORE IMPLEMENTATION, correctly, because its own self-reported architecture left 'Output (lat, lon, confidence)' and the confusability-weighted sampler untouched, and the pivot rule requires at least one of those two to genuinely change alongside the representational core. That rejection was about compliance, not science -- the dense-consensus idea itself was never trained, never refuted. This is the corrected re-attempt (same pattern as exp 41 -> exp 42's rejected-then-corrected redo): same core scientific bet, but this time Output and the sampler are ACTUALLY rebuilt, not just relabeled, because both mechanisms are structurally tied to the very cell-classification framing this pivot deletes. The champion's confidence came from a small trained sub-network (conf_head MLP + BCE loss against a 'hit within GOOD_ERR_UV' label, calibrated by a shift buffer) reading hand-picked field-shape statistics (peak mass, entropy, sharpened-vs-unsharpened decode gap) that only exist because there IS a field to have a peak and an entropy over. With no field, there is no peak mass and no field entropy to compute -- confidence has to be rebuilt from what the new architecture actually produces: the 256 votes' own agreement. And the confusability-weighted sampler (exp 35, kept 743.07m) is a precomputed, static, per-GRID-CELL descriptor-similarity table -- it has no meaning once there is no grid of classifier rows to bias exposure toward. Both are replaced by mechanisms native to the new architecture rather than removed and left as dead weight: confidence becomes a deterministic (non-learned) statistic of the votes' weighted spread and attention entropy, calibrated by the same post-hoc shift technique as before; the sampler becomes a dynamic, per-individual-training-location EMA of the model's OWN live regression error, oversampling wherever the model is currently doing worst -- a training-DATA-strategy mechanism that only makes sense once locations are scored continuously by direct coordinate error instead of discretely by cell membership. Method Replace TinyLocNet's ImageNet-pretrained-MobileNetV3-Small-trunk + gated 1024-way classification field + soft-argmax entirely with: (1) a from-scratch 4-stage plain conv trunk (32->64->96->128 channels, stride 2 each, 128x128 -> 8x8x128, no pretrained weights, no torchvision backbone import); (2) a single shared 1x1 conv 'voter head' that reads each of the 64 spatial positions independently and outputs that patch's own (u,v) guess (sigmoid-bounded to [0,1]) plus a raw reliability logit -- run over the crop's own C4 stack (4 exact 90-degree rotations, kept from the champion) for 4x64=256 total independent votes; (3) a learned-temperature softmax over the 256 reliability logits produces attention weights, and the final (u,v) is their weighted mean -- this IS the decode, replacing soft-argmax over a field entirely; (4) confidence is now a deterministic, non-learned function of the 256 votes' attention-weighted spread around their own mean plus the attention distribution's entropy, passed through a fixed linear-then-sigmoid transform with a single calibrated shift buffer (reusing train.py's existing calibrate_conf_shift mechanism unchanged, since it only needs an opaque conf output to threshold against) -- no conf_head module, no BCE loss term, no field-shape statistics; (5) the training signal becomes a Huber loss on the pooled consensus decode, plus a per-vote regression term (each patch's own guess graded against the true location, weighted by its OWN current -- detached -- attention weight so the gradient shapes what patches propose without also being the sole driver of which patches get trusted), plus a small entropy-floor term that penalizes the attention distribution collapsing toward a single dominant vote; (6) the training sampler drops the static confusability-weighted per-cell table entirely and replaces it with a dynamic per-location error EMA: every location carries a running estimate of the model's own current squared coordinate error there, recomputed after every minibatch from that batch's actual predictions, and each epoch's location draw is blended (alpha=0.7 error-weighted / 0.3 uniform) toward wherever the model is currently doing worst -- oversampling hard locations directly by the metric that matters (coordinate error), not by a proxy (static color/texture similarity) that no longer has a corresponding classifier row to protect. Conclusion at the time REVERTED — metric did not improve (1000000000.0 m vs best 743.07 m); change discarded Not on today's ruler It failed a deployment gate, so there was no working model to score then and none to re-score now. What it cost
by model — claude-sonnet-5 $3.09 · claude-haiku-4-5-20251001 $0.00 The agent lines are the equivalent API cost of the tokens each agent used, as recorded by the harness when it ran. The research ran on a claude.ai Max subscription, so that is what it would have cost billed per token — not money that left the account. The GPU line is real spend. What it was measured against then Optimised: worst-case median error (m), target 20 m, scoring —. Asked over: region holdout, 6 lighting buckets, 4 areas. The long grind: 62 experiments over ten days against a general-purpose, four-area, six-lighting-bucket problem. Scored on a region holdout that asked a memorization model to locate ground it had never been shown. era Four areas, six lighting buckets · ts 2026-07-23T03:27:09 ·
commit aedcce64ff70 · artifacts runs/20260723_031301_iter8 ·
took 14 m 08 s
The code for this experiment — not retainedSource not retained. Until 2026-07-31 the loop discarded a reverted experiment’s code outright, and this row predates that fix. Its design record, its verdict and its exported model survive; the code does not. It is left blank rather than filled with the parent’s code, which is what this row’s recorded commit actually contains. | |||||||||||||||||||||||||||
| ▸ | 2.47 | Dense per-patch coordinate regression with learned-consensus pooling replaces the map-cell classification field
aedcce64
Failed a deployment gate: too large, too slow, or it abstained on too many frames to be scoreable. |
architecture | — | 7 m 27 s | $0.09 | GATED FAIL | ||||||||||||||||||||
|
In plain words Every design tried so far -- 46 of them -- works the same way underneath: look at the whole photo, boil it down to one summary, and make ONE guess about which of ~1,000 map squares it's from. The project's own diagnostics show that one-guess step is exactly where things go wrong: the right square is usually in the model's top 20 guesses, but almost never its actual first choice, because look-alike neighborhoods keep out-scoring the truth. This experiment tries something different: instead of one big guess, chop the photo into about 64 small patches, and let EACH patch make its OWN small guess about where the whole photo was taken, along with how much it trusts its own guess. Then we average all those guesses together, weighted by trust. A patch that's just plain asphalt might shrug and get ignored; a patch with a distinctive rooftop or intersection can outvote a handful of confused patches elsewhere. It's the difference between asking one expert for a single verdict versus polling a room full of people who each only saw a corner of the picture, and trusting the ones who sound sure of themselves. Hypothesis All 46 rounds of this project's history -- every kept champion since round 2, and all eleven straight reverts/rejections since (36-46) -- have shared one structural choice nobody has ever actually abandoned: a SINGLE descriptor (a GAP vector, a GAP+layout-squeeze concatenation, an axis-pooled row/column pair, or a coordinate-generated embedding) is produced per crop, then ONE decision head converts that single descriptor into a location, whether the head is a flat 1024-way classifier decoded by soft-argmax (2-38, 41-44), a coordinate-generated embedding table (39), a coarse-conv-field-plus-offset (40), a retrieve-then-rerank comparator (45), or two factorized 32-way axis classifiers (46). champion-failure-mode-probes memory is unambiguous about what that one-decision bottleneck costs: the true cell is the field's top-1 pick only ~2% of the time despite landing in its own top-20 (of 1024) 33-58% of the time in every lighting bucket -- the descriptor already narrows the answer down, but the SINGLE final decision cannot finish separating a lookalike from the truth, no matter how that decision is reparameterized (row/col split in 46), re-fed (retrieve-rerank in 45), or how the trunk feeding it is retrained (contrastive/reconstruction in 41-44). Two prior regression attempts (round 1's single-point regression, 3216.74 m, reverted for round 2's field; round 37's K=8 multi-hypothesis regression, 1100.35 m, reverted) both ALSO routed through one pooled global descriptor before producing their handful of hypotheses, so they inherited the same single-decision-point structure in miniature and both collapsed toward the map-centroid shrinkage failure mode named in the exp-14 docstring. CLAUDE.md SS3 names DSAC/ACE (Accelerated Coordinate Encoding) as the specific prior art this whole project's modeling family is modeled on, and no round in 46 has actually implemented what that family does: DENSE per-location coordinate regression, where every spatial patch of the input independently proposes a coordinate estimate from only its own local receptive field, and the many independent proposals are combined by a robust, learned-weight consensus (in ACE's case, RANSAC over per-pixel scene coordinates and reprojection-error weights; here, a differentiable attention-weighted mean over per-patch (u,v) proposals). This experiment is the first to try that: instead of one head making one decision from one descriptor, 4x64=256 independent per-patch voters (64 spatial locations on an 8x8 feature grid, times the 4 C4 rotation copies) each regress their own (u,v) guess plus a learned reliability weight from ONLY their own local feature vector, and a softmax-attention-weighted mean over all 256 votes is the final answer. A cell that only fools one or two lookalike-confusable patches gets outvoted by the dozens of other patches that see genuinely distinctive content elsewhere in the same crop -- the ranking wall is sidestepped by redundancy and consensus instead of being asked to make a sharper single 1-of-1024 (or 1-of-1024-factorized, or 1-of-8-shortlisted) decision, which is the one thing every single prior round, kept or reverted, has always ultimately reduced to. Method In model/model.py: delete the ImageNet-pretrained MobileNetV3-Small trunk entirely (import, PRETRAINED_TRUNK_PATH, IMAGENET_MEAN/STD buffers, and every prose reference to mnv3s_features8.pt / mobilenet_v3_small in the module docstring -- see implementation_brief's explicit checklist, learned from exp 41's harness-bug near-miss) and delete the entire classification field (loc_logits, dark_logits, gate, layout_squeeze, GRID_K, cell_u/cell_v, DECODE_BETA, the softmax-over-1024-cells decode). Replace the trunk with a from-scratch 4-stage plain conv stack (stride 2 each stage, channels 32->64->96->128, 128x128 input -> 8x8x128 output, no borrowed weights). Add a per-location voter head: a single shared 1x1 conv (Conv2d(128, 3, kernel_size=1)) applied to every one of the 8x8=64 spatial positions, producing (u_i, v_i, w_i) per position -- u_i, v_i pass through sigmoid (bounded to the [0,1] normalized map range, initialized near 0.5 via a zero-init final layer so an untrained model still defaults near the map center); w_i is a raw unbounded reliability logit. All 4 of the existing C4 pixel-rotation copies feed this same shared trunk+head, giving 4x64=256 total (u_i, v_i, w_i) proposals per crop -- pooling happens at the level of individual coordinate votes, not by averaging logits before one decode, which is itself a mechanism change from the exp-16 C4 scheme every prior round has kept unchanged. A learned scalar temperature (softplus-transformed, init ~3.0, mirroring the retired DECODE_BETA's role) scales w_i before an attention softmax over all 256 votes; the final (u,v) is the softmax-weighted mean of (u_i, v_i). Confidence head: replaces the retired field peak-mass/entropy signals with the weighted spread of the 256 votes around their own weighted mean (sqrt of the attention-weighted variance) plus the normalized entropy of the attention weights themselves, both detached, feeding the same small 2-layer MLP -> sigmoid -> conf_shift calibration mechanism as today (train.py's calibrate_conf_shift is untouched -- it only reads model output conf, agnostic to how conf was computed). loss_fn is rewritten: (1) aggregate_loss = Huber(pred[:,:2], target_uv, delta=0.05) on the final pooled decode -- this is what flies, so it is what is graded, preserving the project's decode-equals-what-the-loss-optimizes principle; (2) per_location_loss = mean over crops of sum_i(alpha_i.detach() * ||(u_i,v_i)-target||^2) -- pushes every vote toward the truth in proportion to its OWN current attention weight, so confidently-weighted patches are held accountable directly rather than only through the pooled gradient; (3) floor_loss = 0.05 * mean over ALL 256 votes unweighted of ||(u_i,v_i)-target||^2 -- a small always-on gradient to every location so none goes permanently dead (mirrors exp 37's epsilon=0.05 hypothesis-trickle term); (4) confidence BCE term, unchanged formula and 0.3 weight, target = aggregate coord error < GOOD_ERR_UV. Total: aggregate_loss + per_location_loss + floor_loss + 0.3*conf_loss. In model/train.py: rename the model(...,return_logits=True) call site to return_aux=True and update the one loss_fn call to match the new signature (pred, aux, target_uv) -- no other line in the epoch loop, sampler, or calibration path changes. Because nothing is pretrained anymore, collapse the optimizer's trunk/head 10x LR split into a single Adam param group at 1e-3 (mirroring exp 44-46's precedent for from-scratch trunks). Exp 35's confusability-weighted sampler must keep its own notion of a 32x32 map-cell grid for computing lookalike weights -- since model.py no longer defines any grid at all, add a standalone CONFUSABILITY_GRID_K = 32 constant directly in train.py (identical value, byte-identical compute_cell_weights/crop_cell math, same pattern exp 37/39/40 used when they removed model.py's grid) and drop the `GRID_K` import from model.py entirely. Update info['init'] to 'from-scratch' per SS9 logging. The ONNX export contract (1x3x128x128 in, [u,v,conf] out), export_onnx(), and estimate_position() are otherwise untouched. Conclusion at the time REJECTED — pivot directive was not honored (partial change, not a complete rethink); iteration discarded before implementation Not on today's ruler It failed a deployment gate, so there was no working model to score then and none to re-score now. What it cost
The agent lines are the equivalent API cost of the tokens each agent used, as recorded by the harness when it ran. The research ran on a claude.ai Max subscription, so that is what it would have cost billed per token — not money that left the account. The GPU line is real spend. What it was measured against then Optimised: worst-case median error (m), target 20 m, scoring —. Asked over: region holdout, 6 lighting buckets, 4 areas. The long grind: 62 experiments over ten days against a general-purpose, four-area, six-lighting-bucket problem. Scored on a region holdout that asked a memorization model to locate ground it had never been shown. era Four areas, six lighting buckets · ts 2026-07-23T03:13:01 ·
commit aedcce64ff70 · artifacts runs/20260723_030534_iter7 ·
took 7 m 27 s
The code for this experiment — not retainedSource not retained. Until 2026-07-31 the loop discarded a reverted experiment’s code outright, and this row predates that fix. Its design record, its verdict and its exported model survive; the code does not. It is left blank rather than filled with the parent’s code, which is what this row’s recorded commit actually contains. | |||||||||||||||||||||||||||
| ▸ | 2.46 | Two-expert lighting dispatcher with from-scratch trunks and factorized row/column coordinate fields
f42a8309
Failed a deployment gate: too large, too slow, or it abstained on too many frames to be scoreable. |
architecture | — | 40 m 55 s | $3.09 | GATED FAIL | ||||||||||||||||||||
|
In plain words Instead of one trunk trying to guess 1-of-1024 map squares in a single shot -- a task made brutally hard because dozens of squares just look alike -- we split the question in two much easier ones asked at the same time: 'which north-south band is this?' and 'which east-west band is this?' (32 choices each, instead of 1024 combined). We also build two completely separate small vision experts from scratch -- one tuned for bright scenes, one for dark ones -- with a cheap brightness-o-meter deciding how much to trust each, instead of one borrowed off-the-shelf vision network doing everything. And rather than showing the model four rotated copies of every photo at flight time (slow), we show it all four rotations during training instead, so it learns to handle any camera heading on its own without paying that cost when it's actually flying. Hypothesis Ten straight reverted rounds (36-45) all left the joint 32x32=1024-way single-softmax cell classifier untouched -- margin losses (34/36) and trunk-objective swaps (41-44) fought over the SAME one-shot 1024-way decision, coordinate reparameterizations (39/40) still asked one softmax to pick 1 of 1024 joint hypotheses, multi-hypothesis regression (37) and retrieve-then-rerank (45) both tried to patch the same decision from outside without changing its shape. Per champion-failure-mode-probes memory, that decision is the actual wall: the true cell is the field's top-1 pick only ~2% of the time even though it lands in the model's own top-20 (of 1024) 33-58% of the time -- the head is starved of clean per-class signal because 1024 mutually confusable classes each get only ~1/1024 of the gradient budget, and lookalike cells split that budget further. This round is also forced to abandon the mnv3s_features8.pt / mobilenet_v3_small trunk that every kept experiment since #11 (12-45) has carried unexamined, so the trunk swap is mandatory rather than optional -- rather than treat that as a tax to pay while leaving the field mechanism alone (which is exactly the failure pattern the patience rule is calling out), this design uses the forced trunk change as the occasion to factorize the coordinate decision itself: instead of one 1024-way joint classifier, use two INDEPENDENT 32-way classifiers, one over row-bands (v-axis) and one over column-bands (u-axis). A row-band and column-band decision each get ~32x the effective per-class training exposure a joint cell gets, and most confusable lookalike districts share one axis (same latitude band, different longitude, or vice versa) but rarely both -- so decomposing the decision directly attacks the top-1-vs-top-20 gap instead of reparameterizing around it again. Combined with this is a genuine (not merely nominal) attempt at the unified-vs-dispatcher axis CLAUDE.md SS3 flags as never resolved: two fully independent from-scratch trunks (bright-scene and low-light experts, no shared weights, no pretrained init) blended by a cheap raw-pixel-statistics dispatcher, rather than the single-shared-trunk + gated-head-blend compromise tried in exp 12/30 (both of which kept the SAME shared trunk and were never a real dispatcher test). Method model/model.py: delete the ImageNet MobileNetV3-Small trunk entirely (_build_pretrained_trunk, PRETRAINED_TRUNK_PATH, IMAGENET_MEAN/STD, the torchvision import) and the joint 1024-way loc_logits/dark_logits/layout_squeeze field. Build two independent from-scratch conv trunks (BrightTrunk, DarkTrunk -- same 4-layer depthwise-separable architecture, separately initialized, no weight sharing, no pretrained loading), each mapping 128x128x3 -> ~64x8x8. A lightweight dispatcher (2-layer MLP over 3 cheap raw-pixel statistics: mean luminance, luminance std, dark-pixel fraction -- no trunk dependency) outputs one sigmoid gate weight. Each trunk's 8x8 feature map is axis-pooled (mean over width -> row descriptor, mean over height -> column descriptor) into two small linear heads producing 32-way row-band and 32-way column-band logits; the two experts' row logits and column logits are each blended by the dispatcher gate. Row and column probabilities are independently 1-D soft-argmaxed into v and u (replacing the old 2-D soft-argmax over a joint field). The C4 exact-rotation inference-time ensemble (4x forward passes) is removed -- rotation robustness moves to training time instead (model/train.py: every epoch, each sampled location is rendered at all 4 exact 90-degree headings instead of one random continuous heading, using 1/4 as many distinct locations per epoch to hold the total crop/step budget fixed), which also frees the compute the two-trunk design spends (net: 2 trunks x 1 pass replaces 1 trunk x 4 passes -- roughly half the old inference compute). Confidence head reads row/col marginal peak mass + entropy plus a new signal only possible with genuinely independent experts: the distance between the bright-only decode and the dark-only decode (detached, high disagreement = low confidence). train.py's optimizer drops the pretrained-trunk 10x-lower-LR split (nothing is pretrained anymore -- all params train at one LR) and its init-strategy log string is updated to from-scratch per SS9. Conclusion at the time REVERTED — metric did not improve (1000000000.0 m vs best 743.07 m); change discarded Not on today's ruler It failed a deployment gate, so there was no working model to score then and none to re-score now. What it cost
by model — claude-sonnet-5 $2.61 · claude-haiku-4-5-20251001 $0.00 The agent lines are the equivalent API cost of the tokens each agent used, as recorded by the harness when it ran. The research ran on a claude.ai Max subscription, so that is what it would have cost billed per token — not money that left the account. The GPU line is real spend. What it was measured against then Optimised: worst-case median error (m), target 20 m, scoring —. Asked over: region holdout, 6 lighting buckets, 4 areas. The long grind: 62 experiments over ten days against a general-purpose, four-area, six-lighting-bucket problem. Scored on a region holdout that asked a memorization model to locate ground it had never been shown. era Four areas, six lighting buckets · ts 2026-07-23T02:49:40 ·
commit f42a830911ec · artifacts runs/20260723_020845_iter6 ·
took 40 m 55 s
The code for this experiment — not retainedSource not retained. Until 2026-07-31 the loop discarded a reverted experiment’s code outright, and this row predates that fix. Its design record, its verdict and its exported model survive; the code does not. It is left blank rather than filled with the parent’s code, which is what this row’s recorded commit actually contains. | |||||||||||||||||||||||||||
| ▸ | 2.45 | Retrieve-then-rerank: a coarse field proposes a shortlist, a pairwise comparator decides among it
0d258f6f
The loop discarded this — no improvement under the metric then in force. |
architecture | 1.591 | 60 m 53 s | $4.24 | DISCARDED | ||||||||||||||||||||
|
In plain words Picture the old system as one judge who has to instantly rank 1,024 possible neighborhoods for every photo, alone, in one shot -- it's good at narrowing things down to a handful of look-alikes but bad at picking the right one among them. This experiment splits that into two judges: a fast first judge still narrows the whole map down to its best 8 guesses (same job as before), but now a second, more careful judge is brought in ONLY to compare those 8 finalists side-by-side against the photo's fine details, the way you'd hold a few candidate street corners up next to a snapshot and look for what actually matches, rather than trying to rank all 1,024 at a glance. We also swap out the borrowed off-the-shelf vision backbone for a new one built from scratch, since the old one is off-limits this round. Hypothesis Two independent diagnostics from project memory (champion-failure-mode-probes) show the true cell is the champion's field top-1 pick only ~2% of the time despite landing in its own top-20 (of 1024) on 33-58% of crops in every lighting bucket: the descriptor already narrows the answer down, but a single 620-dim-input linear scorer (one fixed hyperplane per cell, trained without ever knowing which OTHER cells are in contention for a given crop) cannot finish separating a handful of genuine near-ties from the truth. Nine straight rounds (36-44) attacked this from every angle EXCEPT the one where a model gets a second, focused look at just its own top candidates: two margin-loss attempts (34, 36) pushed the SAME one-shot linear scores further apart without adding any comparison capacity; four architecture rewrites (39 coordinate-generated field, 40 coarse-to-fine field+offset, 31 neural-map correlation) reparameterized the SAME one-shot decision; four rounds (41/42/43/44) replaced the trunk's training objective (contrastive, reconstruction-joint, reconstruction-sequential) hoping a better descriptor would let the SAME one-shot linear head finish the job on its own. None of them gave the model machinery to explicitly ask 'given that it's probably cell A, B, or C, which one actually matches this crop's fine layout?' -- every design has forced one linear pass over 1024 independent hyperplanes to answer both 'roughly where' and 'precisely which of the confusable lookalikes' at once. This experiment splits that into two mechanically different stages: a coarse field (architecturally similar to the proven proposer, kept for cheap, well-calibrated shortlisting) picks the top-8 candidate cells, then a SEPARATE pairwise comparator -- a small MLP that explicitly takes the DIFFERENCE and PRODUCT between the crop's own fine local descriptor and each candidate cell's learned identity embedding -- scores only those 8, with capacity to spend specifically on telling near-ties apart rather than diluting a fixed linear budget across 1024 simultaneous binary decisions it never actually has to make at once. Before committing to this, I checked the last two rounds' raw train logs for a scientific-integrity issue: runs/20260722_214356_iter3/train_berlin.log and train_prignitz.log (exp 43) AND runs/20260722_231953_iter4/train_berlin.log and train_munich.log (exp 44) are all 0 bytes -- crashed before a single epoch printed -- while the other two areas in each run trained cleanly to a healthy converged loss every time. Two different area-subsets failed across the two runs (not the same pair), which rules out anything area-specific and points at loop.sh's four-way concurrent GPU launch (autoresearch/loop.sh:337-347) racing on CUDA context/memory init; the one thing exp 43 and exp 44 shared that earlier from-scratch rounds (41, 42) did not was a full encoder-DECODER shape reconstructing a 128x128x3 image (large intermediate activations at high spatial resolution), which is a plausible reason those two specific rounds tipped some subset of the 4 concurrent processes over a memory/timing edge that plain encoder-only designs (41, 42) never hit. Both were therefore NEVER actually refuted at the design level -- gated 1e9 by an infra race, not by their science -- and library L1 (both flavors: flying pre-model, training-only reconstruction auxiliary) is the common thread behind both crashes, so this design deliberately avoids any full-resolution pixel-reconstruction path and stays encoder-only (a small MLP over pooled descriptors, no decoder, no upsampling), both because I have no revised hypothesis that reconstruction itself was the problem and because I want this round's result to be readable as a real test of retrieve-then-rerank, not a third coin-flip against the same launch race. Method In model/model.py: delete the ImageNet-pretrained MobileNetV3-Small trunk entirely (no torchvision import, no checkpoint load) and replace it with a from-scratch 4-block conv stem (3->24->48->80->112 channels, stride 2 each block) that is architecturally distinct from the plain 4-stage stack reused identically across exps 41/42/43/44 -- it exposes TWO taps: the final 8x8x112 map (for the coarse stage, as before) and the intermediate 16x16x80 map (new, for the fine reranker). Coarse stage: GAP(8x8x112) + a 1x1-conv layout squeeze, into a Linear(620,1024) field head, trained with the SAME Gaussian-smoothed CE as the current champion -- this stage's only job is to propose a top-8 shortlist, not to be the final answer, so it is deliberately left close to the proven mechanism. NEW candidate re-rank stage: a 1x1 conv squeezes the 16x16x80 detail tap to 16 channels, GAP'd to a 16-d 'detail descriptor' per crop; a from-scratch nn.Embedding(1024, 16) gives every map cell a learned identity vector; for each of the coarse stage's top-8 candidate cells, a shared 2-layer MLP (64->32->1) scores [detail_desc, cell_embedding, |detail_desc-cell_embedding|, detail_desc*cell_embedding] -- a small pairwise comparator, evaluated 8 times per crop, that can spend nonlinear capacity specifically on the confusable-pair discrimination the diagnosed wall names, something a single 1024-way linear layer structurally cannot do. Decode and confidence move from the 1024-way field to this K=8 rerank output (sharpened soft-argmax over just the 8 candidate cell centers; confidence from the top1-vs-top2 margin of the K-way softmax). C4 rotation voting (kept, changed only in scope): all 4 rotations still feed the coarse stage exactly as today (geometric-mean logit average, unchanged mechanism) so shortlist quality keeps the proven heading-robustness; the fine reranker reads only the as-given (unrotated) crop's detail tap, both to bound compute/memory (see crash forensics above) and because its job -- comparing THIS crop's actual texture against a handful of candidate cells -- does not need the same rotation-marginalization the coarse proposer does. Lighting gate: removed (no day/night expert blend), consistent with every from-scratch round since 41 -- the two-stage split is the new mechanism for handling appearance ambiguity, not a lighting-conditional branch. Loss (loss_fn): coarse Gaussian-CE (unchanged formula) + a new rerank cross-entropy (target = the true cell's position within the K candidate indices, with the true cell force-included in the training-time candidate set by replacing the coarse stage's weakest of the top-8 whenever it isn't already present, so the reranker's gradient always reaches a real comparison against the truth) + the existing coordinate L2 (now against the K-way decode) + the existing confidence BCE, unchanged weights. model/train.py: remove the pretrained-vs-head 10x LR split (single Adam group at 1e-3, since nothing is pretrained anymore, matching exp 44's precedent) and add one line computing each batch's true cell index from yb to pass into forward() for the forced-inclusion rule; sample_epoch, compute_cell_weights (confusability sampler, exp 35, kept), prepare_realizations, calibrate_conf_shift, and the epoch/schedule constants are all untouched verbatim. Conclusion at the time REVERTED — metric did not improve (1213.14 m vs best 743.07 m); change discarded Re-measured on today's ruler Mission score 1.591 — 2.5% of held-out frames give a usable fix, 61.5% are confident and wrong, 36.0% abstain. Median miss 2.88 km. Measured, not converted: its exported model was re-run through the current scorer for this page. What it cost
by model — claude-sonnet-5 $3.54 · claude-haiku-4-5-20251001 $0.00 The agent lines are the equivalent API cost of the tokens each agent used, as recorded by the harness when it ran. The research ran on a claude.ai Max subscription, so that is what it would have cost billed per token — not money that left the account. The GPU line is real spend. What it was measured against then Optimised: worst-case median error (m), target 20 m, scoring 1.21 km. Asked over: region holdout, 6 lighting buckets, 4 areas. The long grind: 62 experiments over ten days against a general-purpose, four-area, six-lighting-bucket problem. Scored on a region holdout that asked a memorization model to locate ground it had never been shown. Scoreboard — re-scored today Produced by running this experiment's exported model through the current frozen scorer on the current eval set. It is not what this experiment was told at the time.
era Four areas, six lighting buckets · ts 2026-07-23T01:52:18 ·
commit 0d258f6ff469 · artifacts runs/20260723_005125_iter5 ·
took 60 m 53 s
The code for this experiment — not retainedSource not retained. Until 2026-07-31 the loop discarded a reverted experiment’s code outright, and this row predates that fix. Its design record, its verdict and its exported model survive; the code does not. It is left blank rather than filled with the parent’s code, which is what this row’s recorded commit actually contains. | |||||||||||||||||||||||||||
| ▸ | 2.44 | Sequential delighting translator + from-scratch trunk: canonicalize the pixels first, localize second
f61b14fa
Failed a deployment gate: too large, too slow, or it abstained on too many frames to be scoreable. |
relighting | — | 75 m 59 s | $5.09 | GATED FAIL | ||||||||||||||||||||
|
In plain words Instead of asking one part of the network to both 'clean up a murky night photo' and 'figure out where it was taken' at the same time, we split those into two separate steps done by two separate small networks. First, a tiny built-in filter looks at the raw camera frame and tries to repaint it to look like a clear daytime photo of that same spot -- it gets to see the real daytime photo as an answer key during training, which our simulator can hand out for free. Only after that cleanup does the place-guessing part of the network look at the picture, so it never has to fight through fog, glare, or night-time grain itself -- it just reads an already-brightened photo. Because the cleanup filter is graded purely on how good its repaint looks (not on whether it also happens to help with geolocation), and the place-guesser is graded purely on getting the location right (not on repainting anything), neither job has to compromise for the other's sake the way earlier attempts (which mashed both into one shared network) may have. Hypothesis Backbone is off-limits this round (mnv3s_features8.pt / mobilenet_v3_small banned), and the last eight rounds (36-43) are jointly refuted or never-scored while sharing one unexamined structural choice: whichever objective targets lighting/appearance invariance (contrastive pretraining in 41/42, a pixel-reconstruction auxiliary in 43) was always bolted onto the SAME feature map that also has to serve localization -- one trunk, one descriptor, two competing gradients fighting over the same bottleneck. Exp 42's contrastive version measurably lost (846.42 m vs champion 743.07 m). Exp 43's reconstruction version was never actually refuted -- it crashed silently on berlin and prignitz (both train logs are 0 bytes, no epoch ever printed) while frankfurt and munich trained cleanly to loss ~5.48-5.83 and exported normal-sized ONNX files (runs/20260722_214356_iter3/train_frankfurt.log, train_munich.log); loop.sh launches all four areas as concurrent background processes sharing one GPU (autoresearch/loop.sh:337-347), so this reads as resource contention from the added reconstruction decoder at 4-way parallel launch, not a refutation of reconstruction-as-a-signal. Rather than resubmit that same architecture and gamble on the same crash, this experiment tests a mechanistically different bet inspired by library L1's flavor A (the flying pre-model, never yet tried -- only flavor B, the training-only auxiliary, was attempted in exp 43): split canonicalization and localization into two SEQUENTIAL stages with separate losses instead of one shared trunk with a blended gradient. A tiny translator network runs first and is graded ONLY on repainting the crop to look like the clean daytime reference (pixel-aligned supervision the frozen relight pipeline already provides for free, per library L1); the from-scratch localization trunk runs second, entirely on the translator's output, and its features are never directly graded against pixel fidelity -- only against location. This removes the shared-bottleneck tug-of-war exp 43 hypothesized (untested) by construction: the translator can spend 100% of its capacity on appearance cleanup and the trunk can spend 100% of its capacity on discarding-lighting/keeping-layout, because neither one's loss reaches into the other's own feature map directly (only indirectly, through the ordinary chain rule of stacking them). Method In model/model.py: delete the ImageNet-pretrained MobileNetV3-Small trunk entirely (import, weights path, loader function) -- no torchvision constructor, no checkpoint file anywhere in model/. Add a new from-scratch DelightTranslator module: two stride-2 conv down-blocks (3->24->48 channels) into one 3x3 bottleneck conv (48 channels) into two stride-2 ConvTranspose2d up-blocks (48->24->3), whose output is added to the ORIGINAL input as a residual delta and clamped to [0,1] -- predicting a correction rather than a full repaint keeps the identity mapping trivial at init and biases the network away from a blurry mean-image collapse, since most of the output pixel value still comes straight from the input. Replace the deleted pretrained trunk with a from-scratch 4-stage plain conv stack (32->64->96->64 channels, stride 16, 8x8 output grid) -- the same shape already proven trainable in exps 41-43 -- but it now reads the translator's canonicalized output, not raw pixels. Delete the dark-expert head and its gate (model.py's self.dark_logits/self.gate from exp 12): with an explicit upstream canonicalization stage, a downstream day/night blend is redundant machinery duplicating the same job. The C4 rotation stack now wraps translator+trunk jointly: each of the 4 pixel-rotated raw copies passes through the translator individually, then through the trunk, then the single unified field head, then the same C4 logit-average and sharpened soft-argmax decode as before. In model/train.py: sample_epoch also extracts, for every sampled crop, the pixel-aligned clean-reference crop from the untouched reference.tif raster at the SAME (cx, cy, angle) already used for the lighting-bucket crop (reference.tif is already opened this way in compute_cell_weights, so this is the same file, a new read path) -- this is the translator's training target, free per library L1. The training loss becomes loss_fn(...) [unchanged CE + coord L2 + conf BCE] + 1.0 * L1(translator_output, clean_reference_crop), both terms active every step from step one, no separate pretraining phase. Optimizer collapses to a single param group / single LR (e.g. 1e-3) for all parameters, since everything is from-scratch and there is no pretrained-feature-preservation reason for a 10x trunk/head LR split anymore. Conclusion at the time REVERTED — metric did not improve (1000000000.0 m vs best 743.07 m); change discarded Not on today's ruler It failed a deployment gate, so there was no working model to score then and none to re-score now. What it cost
by model — claude-sonnet-5 $4.21 · claude-haiku-4-5-20251001 $0.01 The agent lines are the equivalent API cost of the tokens each agent used, as recorded by the harness when it ran. The research ran on a claude.ai Max subscription, so that is what it would have cost billed per token — not money that left the account. The GPU line is real spend. What it was measured against then Optimised: worst-case median error (m), target 20 m, scoring —. Asked over: region holdout, 6 lighting buckets, 4 areas. The long grind: 62 experiments over ten days against a general-purpose, four-area, six-lighting-bucket problem. Scored on a region holdout that asked a memorization model to locate ground it had never been shown. era Four areas, six lighting buckets · ts 2026-07-23T00:35:52 ·
commit f61b14fababd · artifacts runs/20260722_231953_iter4 ·
took 75 m 59 s
The code for this experiment — not retainedSource not retained. Until 2026-07-31 the loop discarded a reverted experiment’s code outright, and this row predates that fix. Its design record, its verdict and its exported model survive; the code does not. It is left blank rather than filled with the parent’s code, which is what this row’s recorded commit actually contains. | |||||||||||||||||||||||||||
| ▸ | 2.43 | Joint reconstruction-canonicalization trunk: a from-scratch encoder that must repaint the clean daytime crop, trained alongside localization every step
498f9a89
Failed a deployment gate: too large, too slow, or it abstained on too many frames to be scoreable. |
architecture | — | 77 m 03 s | $8.41 | GATED FAIL | ||||||||||||||||||||
|
The design under test — technical diagram — gray = frozen contract · ink = the current design · red = this experiment's change In plain words Instead of starting the drone's 'eye' with a pretrained network that learned to recognize everyday objects like dogs and cars (which never had to learn to tell one street from another), we make it learn a new party trick from scratch: given a photo taken at dawn, dusk, or pitch-black night, it has to repaint what that same spot looks like in clean daylight. To repaint convincingly it has to actually remember fine details -- building shapes, road layouts -- not just a vague impression, which is exactly the kind of detail needed to tell two similar-looking neighborhoods apart. We tried a similar from-scratch idea last round (a matching game instead of repainting) and it didn't help; repainting is a stricter test that shouldn't let the network cheat by getting lazy. Hypothesis Backbone is off-limits this round (mnv3s_features8.pt / mobilenet_v3_small banned), and the last seven rounds (36-42) were jointly refuted while leaving one thing unquestioned at a time: 36/37/39/40 churned the loss/decode/field parameterization around the SAME ImageNet-classification trunk, and 41/42 finally replaced that trunk with a from-scratch one but taught it via NT-Xent instance-discrimination contrastive pretraining -- and still lost (846.42 m vs champion 743.07 m). Exp 42's own pre-registered falsification fork (a) named the exact redirect for a flat/worse contrastive result: 'a reconstruction-style auxiliary (library L1) instead of contrastive' -- untested until now. The two objectives target the diagnosed wall (runs/_probe_iter12_field.py: true cell top-1 only ~2% of 1024 despite landing in the top-20 33-58% of the time -- descriptor narrows things down but cannot finish separating lookalikes) very differently: contrastive only pushes different-location embeddings apart in the abstract, with no pressure to preserve WHAT the location looks like, so a lazy shortcut (e.g. collapsing to a few broad texture clusters) can drive the loss down while destroying exactly the fine layout detail that separates confusable lookalike districts. A reconstruction objective cannot take that shortcut: to repaint the clean daytime pixels of a 128x128 crop from an 8x8 feature map, the encoder is forced to keep genuine spatial/appearance detail (building outlines, road widths, texture layout) in every location's descriptor, not just a coarse identity code -- and because the target is always the SAME clean daytime reference regardless of which of the six lighting buckets the input came from, the pressure to canonicalize lighting comes for free as a side effect of the fidelity requirement, rather than being the entire (and, per 41/42, insufficient) point of the objective. This experiment isolates the pretext-task variable cleanly: architecture and training procedure otherwise mirror exp 42's already-implemented (and gate-checked) whole-pipeline shape (from-scratch conv trunk, lighting gate removed, unified field) so any metric difference is attributable to reconstruction vs. contrastive pretraining, not to incidental shape differences. Unlike exp 41/42's staged pretrain-then-finetune schedule, the reconstruction signal here runs as a joint auxiliary loss for the entire training run (library L1 flavor B) rather than a separate up-front phase, so the canonicalization pressure cannot be forgotten during fine-tuning the way a one-shot pretraining phase's benefit can fade. Method Replace model.py's ImageNet-pretrained MobileNetV3-Small trunk with a from-scratch 4-stage conv encoder (32->64->96->64 channels, stride 16, 8x8x64 output). Add a small training-only transpose-conv decoder (mirrors the encoder: 64->96->64->32->3, sigmoid output) that reconstructs the clean daytime reference.tif crop from the encoder's own feature map -- discarded before ONNX export, so it costs nothing in the deployment budget. Every training step, in addition to the existing CE-field + coord-L2 + conf-BCE loss, add an L1 pixel-reconstruction loss (weight 1.0) between the decoder's output and the pixel-aligned reference.tif crop (same cx/cy/rotation angle as the input, extracted via the frozen pipeline.dataset.extract_crop) -- for every lighting bucket, every crop, every step, not a separate pretraining phase. The lighting gate (two-expert day/night blend) is removed and replaced with a single unified field head, since the reconstruction target is always the canonical daytime scene regardless of input lighting bucket, giving the trunk direct pressure to normalize lighting differences into one representation rather than needing a routing mechanism downstream. All parameters (encoder, field head, confidence head, decoder) train under one shared LR/schedule instead of the old pretrained-trunk 10x-slower discount, since nothing here is transferred. Conclusion at the time REVERTED — metric did not improve (1000000000.0 m vs best 743.07 m); change discarded Not on today's ruler It failed a deployment gate, so there was no working model to score then and none to re-score now. What it cost
by model — claude-sonnet-5 $7.52 · claude-haiku-4-5-20251001 $0.01 The agent lines are the equivalent API cost of the tokens each agent used, as recorded by the harness when it ran. The research ran on a claude.ai Max subscription, so that is what it would have cost billed per token — not money that left the account. The GPU line is real spend. What it was measured against then Optimised: worst-case median error (m), target 20 m, scoring —. Asked over: region holdout, 6 lighting buckets, 4 areas. The long grind: 62 experiments over ten days against a general-purpose, four-area, six-lighting-bucket problem. Scored on a region holdout that asked a memorization model to locate ground it had never been shown. era Four areas, six lighting buckets · ts 2026-07-22T23:00:59 ·
commit 498f9a89c0fd · artifacts runs/20260722_214356_iter3 ·
took 77 m 03 s
The code for this experiment — not retainedSource not retained. Until 2026-07-31 the loop discarded a reverted experiment’s code outright, and this row predates that fix. Its design record, its verdict and its exported model survive; the code does not. It is left blank rather than filled with the parent’s code, which is what this row’s recorded commit actually contains. | |||||||||||||||||||||||||||
| ▸ | 2.42 | Domain-native contrastive pretraining replaces the ImageNet trunk (corrected re-attempt)
fed7c60a
The loop discarded this — no improvement under the metric then in force. |
architecture | 1.836 | 69 m 24 s | $4.13 | DISCARDED | ||||||||||||||||||||
|
The design under test — technical diagram — gray = frozen contract · ink = the current design · red = this experiment's change In plain words The model's 'eyes' were borrowed from a network trained to recognize everyday objects like cats and cars -- it was never taught that a rooftop at noon and the same rooftop at midnight are the same place, or that two different-looking places are different. This experiment throws that borrowed trunk away and grows a brand-new one from scratch, first playing a 'same place or different place?' matching game using nothing but the map images this project already generates in every lighting condition, before it ever tries to pinpoint a location. It's a bit like teaching someone to recognize your street by showing them photos of it at dawn and at midnight side by side, instead of handing them a general photo-classification textbook and hoping street-recognition falls out. Hypothesis Exp 41 diagnosed the real wall correctly but was rejected on compliance, not on merit: its implementation still carried the champion's backbone identifiers (mnv3s_features8.pt, mobilenet_v3_small) somewhere in model/, so the harness rejected it before training and the underlying scientific question -- does an ImageNet-classification trunk cap the field's ranking ability -- remains untested. Six straight reverts/rejections (36-41) all left the same thing untouched: an ImageNet-supervised MobileNetV3-Small trunk (mnv3s_features8.pt), unchanged in kind since exp 11, the only stage never once touched except by three reverted capacity-scaling attempts (8, 26, 28) that widened it without ever changing what it is trained to represent. That trunk's entire training signal is 'classify natural photographs of ImageNet objects'; it has never been told to treat six different lighting renders of the SAME place as the same thing, or to separate one map location's appearance from another's. The project's own diagnostic probes (runs/_probe_iter12_field.py, memory champion-failure-mode-probes) show the wall is exactly a discrimination failure: the true cell is the field's top-1 pick only ~2% of the time despite landing in its own top-20 (of 1024) on 33-58% of crops -- the descriptor narrows things down but cannot finish separating confusable lookalikes, and no downstream architecture/loss change (exps 34/36/37/39/40, all reverted) has fixed that because the descriptor handed to them was never trained to make that separation. This iteration replaces the ImageNet-pretrained trunk with a from-scratch trunk pretrained via self-supervised contrastive learning (NT-Xent) directly on the frozen relight pipeline's own multi-lighting, multi-heading renders of the TRAIN split: positive pairs = two independently-drawn views (different lighting bucket + different heading) of the SAME physical location; negatives = every other location in the batch. This is the identical scientific bet exp 41 made, run through a hardened implementation path this time: the champion's backbone identifiers are hard-deleted from model/model.py and model/train.py -- including the one place a plain code rewrite would NOT have caught them, a historical docstring sentence naming the old checkpoint path in prose, which backbonecheck.py scans verbatim since it deliberately does not strip docstrings (only '#' comments). Method In model/model.py: delete `_build_pretrained_trunk`, the `mobilenet_v3_small` import, `PRETRAINED_TRUNK_PATH`, and the ImageNet mean/std buffers; replace the trunk with a brand-new from-scratch 4-stage plain conv stack (32->64->96->64 channels, stride 2 each, 128x128 -> 8x8x64, ~131k params, no borrowed weights, no checkpoint file). Delete the dark-expert head and brightness gate (exp 12); a single unified field head reads the new trunk's GAP + layout-squeeze descriptor, since the new trunk is pretrained specifically to already collapse lighting nuisance. Add a small discard-after-pretraining projection head. In model/train.py: add a new pretraining phase (nt_xent_loss + pretrain_contrastive) that runs before the existing supervised epoch loop, drawing paired lighting/heading views of the same locations from the pipeline's own cached renders and training the trunk+projector with an NT-Xent contrastive loss; the main supervised loop is otherwise unchanged except the trunk's fine-tune LR (no longer a delicately-borrowed pretrained model). Every literal occurrence of `mobilenet_v3_small` and `mnv3s_features8` -- including in prose, not just code -- is removed from both files; see implementation_brief for the exact grep-verifiable checklist. Conclusion at the time REVERTED — metric did not improve (846.42 m vs best 743.07 m); change discarded Re-measured on today's ruler Mission score 1.836 — 2.5% of held-out frames give a usable fix, 86.0% are confident and wrong, 11.5% abstain. Median miss 2.30 km. Measured, not converted: its exported model was re-run through the current scorer for this page. What it cost
by model — claude-sonnet-5 $3.33 · claude-haiku-4-5-20251001 $0.00 The agent lines are the equivalent API cost of the tokens each agent used, as recorded by the harness when it ran. The research ran on a claude.ai Max subscription, so that is what it would have cost billed per token — not money that left the account. The GPU line is real spend. What it was measured against then Optimised: worst-case median error (m), target 20 m, scoring 846.4 m. Asked over: region holdout, 6 lighting buckets, 4 areas. The long grind: 62 experiments over ten days against a general-purpose, four-area, six-lighting-bucket problem. Scored on a region holdout that asked a memorization model to locate ground it had never been shown. Scoreboard — re-scored today Produced by running this experiment's exported model through the current frozen scorer on the current eval set. It is not what this experiment was told at the time.
era Four areas, six lighting buckets · ts 2026-07-22T21:43:34 ·
commit fed7c60aaef4 · artifacts runs/20260722_203410_iter2 ·
took 69 m 24 s
The code for this experiment — not retainedSource not retained. Until 2026-07-31 the loop discarded a reverted experiment’s code outright, and this row predates that fix. Its design record, its verdict and its exported model survive; the code does not. It is left blank rather than filled with the parent’s code, which is what this row’s recorded commit actually contains. | |||||||||||||||||||||||||||
| ▸ | 2.41 | Domain-native contrastive pretraining replaces the ImageNet trunk
c0fda62e
Failed a deployment gate: too large, too slow, or it abstained on too many frames to be scoreable. |
architecture | — | 15 m 38 s | $0.18 | GATED FAIL | ||||||||||||||||||||
|
The design under test — technical diagram — gray = frozen contract · ink = the current design · red = this experiment's change In plain words Every past attempt to fix the model's map-reading skill tinkered with the scorer that ranks candidate locations, but always fed it the same raw description of the photo -- one originally learned for recognizing everyday objects like cats and cars, never for telling one street block apart from a lookalike block across town. This experiment throws that borrowed description out and teaches the model to describe photos from scratch, using a game where it has to recognize that a daytime and a nighttime photo of the SAME spot are 'the same' while a photo of a different spot is 'different' -- practiced over and over using the free synthetic renders we already generate. Only after it gets good at that game does it move on to learning actual GPS coordinates. We also removed a day/night switch that was patched in earlier to work around the old description's blind spot in the dark, since the new description is trained to already see through lighting changes on its own. Hypothesis Five straight reverts (36-40) all attacked the decode/field/loss/parameterization side of the pipeline (margin hinge, K=8 regression, a rejected retinex-channel attempt, a coordinate-generated embedding field, coarse-to-fine field+offset) and every one of them left the SAME thing untouched underneath: an ImageNet-supervised MobileNetV3-Small trunk (mnv3s_features8.pt), unchanged in kind since exp 11, the only stage never once touched in 40 rounds except by three reverted capacity-scaling experiments (8, 26, 28) that widened it without ever changing what it is trained to represent. That trunk's entire training signal is 'classify natural photographs of ImageNet objects' -- it has never once been told to separate one map location's appearance from another's, or to treat six different lighting renders of the SAME place as the same thing. The project's own diagnostic probes (runs/_probe_iter12_field.py, memory champion-failure-mode-probes) show the wall is exactly a discrimination failure: the true cell is the field's top-1 pick only ~2% of the time despite landing in its own top-20 (of 1024) on 33-58% of crops -- the descriptor narrows things down but cannot finish the job of separating confusable lookalikes. No architecture/loss/parameterization change on the classifier side can fix that if the descriptor handed to it was never trained to make that separation in the first place. This experiment replaces the ImageNet-pretrained trunk with a from-scratch trunk pretrained via self-supervised contrastive learning (NT-Xent) directly on the frozen relight pipeline's own multi-lighting, multi-heading renders of the TRAIN split: positive pairs = two independently-drawn views (different lighting bucket / relight realization / heading or C4 rotation) of the SAME physical location; negatives = every other location in the batch. This is a domain-native training signal that explicitly rewards exactly the property the ranking wall needs -- collapse lighting/heading nuisance, keep different locations apart -- which an ImageNet object-classification signal never provided and no downstream architecture change could retrofit. Method Delete model.py's ImageNet-pretrained trunk entirely: remove the torchvision.models.mobilenet_v3_small import, PRETRAINED_TRUNK_PATH, _build_pretrained_trunk(), and model/pretrained/mnv3s_features8.pt (unused after this change; delete the file so its identifier cannot linger in model/). Replace it with a from-scratch _build_trunk(): four stride-2 3x3-conv + BatchNorm + GELU stages, channels 3->24->32->48->64 (128x128 in -> 8x8x64 out, same stride-16 grid shape the field head already expects), ~50k params -- a plain conv stack, deliberately NOT an inverted-residual/depthwise-separable/SE design, so it is a genuinely different family from MobileNetV3, not a reimplementation of it. Add a new phase-0 to train.py: self-supervised NT-Xent pretraining of this trunk plus a throwaway projection head (GAP-64 -> Linear(64,64) -> GELU -> Linear(64,32), L2-normalized, never exported) over TRAIN-split crops only. Reuse the existing confusability-weighted sampler (crop_probs, exp 35) and the existing TRAIN_REALIZATIONS-cached renders (prepare_realizations, exp 17) -- no new expensive data generation, so this cannot repeat the exp27/33/34 ENOSPC/OOM failures. Each sampled anchor location contributes two views drawn from independently-chosen (lighting bucket, realization, heading-or-C4-rotation) combinations. Run 3 epochs, 3000 locations/bucket/epoch, batch 128, temperature 0.15, Adam 1e-3 cosine-to-0. Save the trunk's state_dict to <out_dir>/pretrained_trunk.pt (a per-run training artifact, NOT written into model/pretrained/, which is reserved for checked-in external checkpoints). Phase-2 (the existing supervised loop) then builds a fresh TinyLocNet whose trunk loads that state_dict as its starting point, instead of loading an external ImageNet file. Inside TinyLocNet: delete dark_logits and the gate nn.Sequential entirely; loc_logits becomes the sole field head reading cat([f, layout]) directly (no gated blend). Redirect the now-unused raw-brightness signal into conf_head's input instead: cat([f.detach(), peak, ent, gap, lum]) (was missing lum before). Make DECODE_BETA a learned scalar nn.Parameter, softplus-transformed and initialized to invert to today's constant (3.0), trained jointly with the rest of the field via the existing loss. In train_area()'s phase-2 optimizer, shrink the trunk/head LR ratio from 10x (1e-4 vs 1e-3) to ~3.3x (3e-4 vs 1e-3), since the trunk now starts from a domain-native prior rather than a foreign-photograph one that needed heavier protection. GRID_K=32, TARGET_SIGMA_CELLS, the confusability-sampler math, calibrate_conf_shift, the C4 rotation-vote at inference, the supervised CE + coordinate-L2 + confidence-BCE loss formula, and the ONNX export contract are all otherwise untouched. Conclusion at the time REJECTED IN ERROR — the pivot directive WAS honored. The design proposed a from-scratch domain-native contrastive trunk (init_strategy: from-scratch, no ImageNet, no torchvision checkpoint) and the implementation removed the mobilenet backbone. The gate rejected it over prose in the file header, and its weights pattern was additionally anchored on a path form model.py never writes, so it missed real loads while flagging the docstring. Fixed in f04434d (docstrings stripped via ast; weights matched as a bare filename). This design was never evaluated and remains open. Not on today's ruler It failed a deployment gate, so there was no working model to score then and none to re-score now. What it cost
The agent lines are the equivalent API cost of the tokens each agent used, as recorded by the harness when it ran. The research ran on a claude.ai Max subscription, so that is what it would have cost billed per token — not money that left the account. The GPU line is real spend. What it was measured against then Optimised: worst-case median error (m), target 20 m, scoring —. Asked over: region holdout, 6 lighting buckets, 4 areas. The long grind: 62 experiments over ten days against a general-purpose, four-area, six-lighting-bucket problem. Scored on a region holdout that asked a memorization model to locate ground it had never been shown. era Four areas, six lighting buckets · ts 2026-07-22T20:34:10 ·
commit c0fda62e1282 · artifacts runs/20260722_201832_iter1 ·
took 15 m 38 s
The code for this experiment — not retainedSource not retained. Until 2026-07-31 the loop discarded a reverted experiment’s code outright, and this row predates that fix. Its design record, its verdict and its exported model survive; the code does not. It is left blank rather than filled with the parent’s code, which is what this row’s recorded commit actually contains. | |||||||||||||||||||||||||||
| ▸ | 2.40 | Coarse-to-fine geolocalization: a shared conv field ranks the region, a shared local head regresses the offset
9880938d
The loop discarded this — no improvement under the metric then in force. |
architecture | 1.738 | 81 m 04 s | $8.40 | DISCARDED | ||||||||||||||||||||
|
The design under test — technical diagram — gray = frozen contract · ink = the current design · red = this experiment's change In plain words Every past attempt asked one system to do two hard jobs at once: figure out roughly WHERE a photo was taken among 1,024 tiny map tiles, AND pinpoint the exact spot -- in a single step. The diagnosis says the 'which tile' part is the part that keeps failing (the model narrows it down but usually doesn't pick the right one out of many similar-looking tiles). So this experiment splits the job in two: first, a much simpler 64-tile guess (roomier tiles, easier to get right, and computed straight off the camera model's own picture-shaped map instead of a giant independent answer sheet). Second, a small dedicated helper then nudges that guess to the exact spot within the winning tile -- a much easier fine-tuning problem once you already know the neighborhood. It's like first picking the right city block, then a second glance to find the exact doorway, instead of trying to name the doorway out of a thousand candidates in one look. Hypothesis Four straight reverts (36-39) all attacked the SAME output parameterization the project has used since round 2: a probability FIELD over map cells, decoded by soft-argmax, whether scored by a flat 1,024-independent-row table (2-38) or exp 39's coordinate-generated embedding table (still reverted, 1103.28 vs 743.07 m). Every one of those attempts kept the SAME shape of decode: one soft-argmax pass has to simultaneously (a) rank the true cell above every distant lookalike and (b) deliver metre-level precision, at whatever single grid resolution it commits to. The project's own diagnostic probes (runs/_probe_iter12_field.py, project memory champion-failure-mode-probes) show why that is two incompatible jobs at once: at 1,024 cells (~217 m/cell) the true cell is top-1 only ~2% of the time despite being in the top-20 33-58% of the time -- ranking that many near-identical candidates is the wall, not precision, since a coarser grid should be far easier to rank correctly (distant lookalike districts are usually NOT in the same coarse region as the truth). But a coarser grid alone destroys the precision the 20 m target needs. No experiment in 39 rounds has ever separated these two jobs into two stages. This is a genuine whole-pipeline pivot, not a parameterization tweak within the same one-shot decode: the field itself becomes a coarse, heavily parameter-shared, fully CONVOLUTIONAL grid (a 1x1 conv read directly off the trunk's own 8x8 spatial feature map -- the trunk's own translation structure DOES the parameter sharing, instead of either 1,024 free-standing rows or exp 39's Fourier-MLP generator, which still had to learn spatial structure abstractly), and a SEPARATE, cheap, shared fine-offset head -- reading the trunk's finer stride-8 tap -- regresses a per-cell (dx, dy) correction so the coarse cell's fixed center is nudged to the true location. Point regression was tried twice before (exp 1/2's single point, exp 37's K=8 hypotheses) and both lost specifically because nothing had resolved WHICH place first, so the regressor had to solve ranking and precision at once and collapsed toward the map centroid on ambiguous crops. Here the coarse field resolves ranking first (an easier 64-way problem); the offset regressor only ever has to solve a small, unimodal, well-conditioned local problem within whichever cell won -- a materially different setup from both prior regression failures. Method Replace TinyLocNet's single flat 1024-way field (model/model.py) with a two-stage coarse+fine field: (1) the trunk's existing stride-16 8x8 feature grid is read by a 1x1 conv (2 output channels: bright/dark expert, blended by the existing brightness gate) directly INTO an 8x8=64-cell coarse field -- no flattening through a large Linear layer, the conv IS the field, so nearby coarse cells structurally share features by construction. (2) A NEW stride-8 tap (24ch @ 16x16, exactly 2x the stride-16 grid's resolution) is reshaped into per-coarse-cell 2x2 local blocks and read by ONE small shared conv head (identical weights at all 64 positions) producing a per-cell (Δu, Δv, logσ) correction. (3) Decode becomes a DSNT-style soft-argmax using PER-CELL fine-corrected centers (fixed grid center + that cell's own predicted offset) instead of fixed grid centers -- still fully differentiable, still decode-equals-what-the-loss-optimizes (no new argmax/gather anywhere in the exported graph). (4) Training signal becomes a joint loss: coarse Gaussian-smoothed CE over the 64-way field + a Huber offset-regression term gathered at the ground-truth cell only (a training-time-only gather, not exported) + the existing L2-on-decode and confidence BCE. (5) Confidence combines the coarse field's shape (as today) with the fine field's own predicted log-variance. (6) The confusability-weighted sampler (exp 35, kept) is recomputed at the new 8x8 granularity instead of 32x32. (7) The freshly-initialized offset head gets a 2-epoch LR warmup before joining the shared cosine schedule. This deletes the old loc_logits/dark_logits/layout_squeeze Linear layers entirely (~625k params) and replaces them with a tiny 1x1 conv (~100 params) plus a tiny shared offset conv head (~3k params) -- a large net reduction in output-side parameter count, i.e. far MORE sharing, not less, directly targeting the 'cannot fit its own training locations' signature diagnosed since round 20/25. Conclusion at the time REVERTED — metric did not improve (1011.58 m vs best 743.07 m); change discarded Re-measured on today's ruler Mission score 1.738 — 0.2% of held-out frames give a usable fix, 74.0% are confident and wrong, 25.8% abstain. Median miss 1.32 km. Measured, not converted: its exported model was re-run through the current scorer for this page. What it cost
by model — claude-sonnet-5 $7.46 · claude-haiku-4-5-20251001 $0.01 The agent lines are the equivalent API cost of the tokens each agent used, as recorded by the harness when it ran. The research ran on a claude.ai Max subscription, so that is what it would have cost billed per token — not money that left the account. The GPU line is real spend. What it was measured against then Optimised: worst-case median error (m), target 20 m, scoring 1.01 km. Asked over: region holdout, 6 lighting buckets, 4 areas. The long grind: 62 experiments over ten days against a general-purpose, four-area, six-lighting-bucket problem. Scored on a region holdout that asked a memorization model to locate ground it had never been shown. Scoreboard — re-scored today Produced by running this experiment's exported model through the current frozen scorer on the current eval set. It is not what this experiment was told at the time.
era Four areas, six lighting buckets · ts 2026-07-22T20:05:21 ·
commit 9880938d7742 · artifacts runs/20260722_184417_iter3 ·
took 81 m 04 s
The code for this experiment — not retainedSource not retained. Until 2026-07-31 the loop discarded a reverted experiment’s code outright, and this row predates that fix. Its design record, its verdict and its exported model survive; the code does not. It is left blank rather than filled with the parent’s code, which is what this row’s recorded commit actually contains. | |||||||||||||||||||||||||||
| ▸ | 2.39 | Coordinate-generated map field: replace 1,024 free-parameter cell templates with a shared position-conditioned embedding generator
898d8cb5
The loop discarded this — no improvement under the metric then in force. |
architecture | 1.503 | 71 m 34 s | $7.28 | DISCARDED | ||||||||||||||||||||
|
The design under test — technical diagram — gray = frozen contract · ink = the current design · red = this experiment's change In plain words For 38 rounds the model has picked a location by keeping a giant multiple-choice answer sheet: 1,024 separate, totally independent answers, one per map tile, that it must learn from scratch with no help from each other. That's like memorizing 1,024 unrelated flashcards instead of learning a map. This experiment throws the flashcards away and replaces them with a tiny function that GENERATES an answer for any tile just from its map coordinates -- so learning about one tile automatically improves nearby and similar tiles too, instead of each one starting from zero. The photo still gets turned into its own compact fingerprint, and the guess is just 'which generated tile-fingerprint looks most like mine' -- same idea as before, just with a far smaller, far smarter answer sheet. Hypothesis Three consecutive reverts (36: hardest-impostor margin hinge on the 1024-way field; 37: K=8 multi-hypothesis regression replacing the field; 38: rejected outright for touching only the trunk's input channels while leaving the field, decode, gate, and confidence self-reported unchanged) all left one thing untouched across the entire 38-round history: the map-cell scorer itself is a flat nn.Linear(560, 1024) -- exactly 1,024 completely independent weight rows, unchanged in kind since exp 2/10, each fit from scratch with zero structural relationship to its neighbors. Exp 26 named the resulting signature precisely: since exp 20/25, train-split and eval-split unfiltered medians are EQUAL (~1 km in every area/bucket) -- the model cannot even fit its OWN training locations, which is the textbook signature of a statistically weak output parameterization, not of missing capacity (exps 26/28 tripled trunk depth three times, all reverted, two after coverage was honestly measured), missing optimization (exp 25 trained to a genuine loss plateau and moved the metric only 5%), or a weak objective (exps 34/36's hardest-impostor margin hinge, twice refuted). The one experiment that DID attack the parameterization directly -- exp 31's neural-map correlation field -- was reverted, but its own pre-registered failure mode was orthogonal to weak-parameterization itself: a spatial 4x4 crop kernel cross-correlated against a map raster is sensitive to the residual +/-45 degree heading error left after the C4 vote, which smears correlation peaks regardless of how good the underlying map representation is. That failure mode does not apply to a HOLISTIC embedding comparison (a single 64-d crop vector dotted against a single 64-d cell vector, exactly the same shape of comparison the current flat head already makes) -- so the parameterization hypothesis is genuinely untested in a form that avoids exp 31's specific breakage. This experiment replaces the flat table with a coordinate-conditioned generator: a fixed Fourier positional encoding of each cell's (u, v) center feeds a tiny shared 2-layer MLP that outputs all 1,024 cell embeddings, so a gradient at one map location updates the SAME ~6k shared parameters that determine every other cell's embedding -- an explicit smoothness/parameter-sharing prior across map positions that a bank of 1,024 independent rows structurally cannot have, directly targeting the diagnosed 'cannot fit own training locations' signature. Per the pivot directive this also forces (not as an afterthought, but as a structural consequence of deleting the flat head) a rewrite of every downstream stage that read it: the two-expert brightness gate (rd 12) is replaced by continuous FiLM conditioning of the embedding MLP; the feature extractor now taps the trunk's stride-8 block (16x16x24) as a second GAP summary alongside the stride-16 block, since a metric-learning embedding benefits from a multi-scale descriptor in a way a single flat classifier never got to exploit; decode and confidence are recomputed over the new cosine-similarity field, with confidence now reading a peak-vs-second-peak embedding margin instead of the old hand-crafted shape detector. Method model/model.py: delete loc_logits, dark_logits, and gate (the flat linear head, the dark expert, and the brightness blend -- roughly 625k of the model's 823k parameters). Split the pretrained trunk into features_early = full[:4] (stride-8, 24ch @ 16x16) and features_late = full[4:] (stride-16, 48ch @ 8x8) -- same loaded weights, just referenced as two slices. Add: an embed_hidden/embed_out MLP (584-d descriptor [late GAP 48 + skip GAP 24 + layout 512] -> 128 -> 64, L2-normalized) producing the crop embedding; a film_mlp ([brightness, late GAP] -> gamma/beta, 128-d each) FiLM-modulating the embedding MLP's hidden layer; a fixed Fourier positional encoding of the 1,024 grid-cell centers (registered buffer, L=6 frequencies) feeding a small map_gen MLP (26 -> 64 -> 64, L2-normalized) that generates all 1,024 cell embeddings fresh every forward call; a learned scalar logit_scale (init 10.0) temperature-scaling the cosine-similarity matmul (crop_embedding @ map_embeddings.T) into the same-shaped [N, 1024] logits the rest of the pipeline (C4 vote, Gaussian-CE, sharpened soft-argmax decode) already consumes unchanged. Confidence head's input swaps the old decode-gap feature for a peak-vs-second-peak cosine-similarity margin (torch.topk(2)), kept alongside peak mass and entropy. model/train.py: exactly one line changes -- `trunk_params = list(model.features.parameters())` becomes `list(model.features_early.parameters()) + list(model.features_late.parameters())` -- sampler, calibration, schedule, and loss_fn's math are byte-identical. Conclusion at the time REVERTED — metric did not improve (1103.28 m vs best 743.07 m); change discarded Re-measured on today's ruler Mission score 1.503 — 2.0% of held-out frames give a usable fix, 52.2% are confident and wrong, 45.8% abstain. Median miss 570.2 m. Measured, not converted: its exported model was re-run through the current scorer for this page. What it cost
by model — claude-sonnet-5 $6.45 · claude-haiku-4-5-20251001 $0.01 The agent lines are the equivalent API cost of the tokens each agent used, as recorded by the harness when it ran. The research ran on a claude.ai Max subscription, so that is what it would have cost billed per token — not money that left the account. The GPU line is real spend. What it was measured against then Optimised: worst-case median error (m), target 20 m, scoring 1.10 km. Asked over: region holdout, 6 lighting buckets, 4 areas. The long grind: 62 experiments over ten days against a general-purpose, four-area, six-lighting-bucket problem. Scored on a region holdout that asked a memorization model to locate ground it had never been shown. Scoreboard — re-scored today Produced by running this experiment's exported model through the current frozen scorer on the current eval set. It is not what this experiment was told at the time.
era Four areas, six lighting buckets · ts 2026-07-22T18:43:41 ·
commit 898d8cb5b18b · artifacts runs/20260722_173207_iter2 ·
took 71 m 34 s
The code for this experiment — not retainedSource not retained. Until 2026-07-31 the loop discarded a reverted experiment’s code outright, and this row predates that fix. Its design record, its verdict and its exported model survive; the code does not. It is left blank rather than filled with the parent’s code, which is what this row’s recorded commit actually contains. | |||||||||||||||||||||||||||
| ▸ | 2.38 | Illumination-invariant retinex channel joins raw RGB from the pixel level through the trunk
898d8cb5
Failed a deployment gate: too large, too slow, or it abstained on too many frames to be scoreable. |
architecture | — | 20 m 32 s | $0.24 | GATED FAIL | ||||||||||||||||||||
|
The design under test — technical diagram — gray = frozen contract · ink = the current design · red = this experiment's change In plain words Every version of this model so far has looked at the photo exactly as the camera sees it -- raw colors and brightness. But the same street can look totally different depending on how dark it is or how many lights are on, and we've measured that the model's mental fingerprint of a place gets thrown off by that brightness noise, not just by the actual layout of streets and buildings. This experiment hands the model a second picture alongside the first: a version mathematically flattened to remove slow brightness changes and keep only the sharp local edges -- like squinting to ignore how sunny or dim it is and see only shapes. Both pictures are fed in together from the very first layer, so the network can lean on the shape-only picture whenever plain color is confounded by lighting. We also sprinkle a bit of random blur or sharpening onto training photos so the model can't cheat by reading blur artifacts as if they were a location clue. Hypothesis Two consecutive reverts (exp 36: hardest-impostor margin hinge on the same 1024-way field; exp 37: multi-hypothesis K=8 regression replacing the field) both left the feature extractor, layout summary, rotation fan, training data, and training schedule byte-identical to round 35 (see arch_json for ids 36/37) -- confirming those five stages are the frozen core, not the decode/loss/head churn happening around them. Every change to that core across the project's 37-round history has been a CAPACITY change within the same RGB-only pretrained-CNN family: exp 8 scaled a from-scratch encoder 4.2x (reverted), exp 26/27/28 tripled the pretrained trunk's depth three separate times (all reverted) -- exp 26/28 even confirmed real like-for-like accuracy gains (munich morning 536->347m, frankfurt evening 798->466m) that were wiped out once coverage was measured honestly, or reversed once it was. No experiment in the entire history has ever changed WHAT the trunk's first layer sees: all 37 rounds fed it raw RGB and nothing else. Two independent live probes point at that exact gap. runs/_probe_iter12_heading.py found the champion's descriptor rides low-level resampling-blur statistics that carry zero location information: eval crops that skip PIL's bilinear rotation-resampling blur nearly DOUBLE the error (983->1668m, berlin midday) from a blur artifact alone, not layout. runs/_probe_iter12_field.py found the true map cell is the field's top-1 pick only 2% of the time despite landing in its own top-20 (of 1024) on 33-58% of crops in every bucket -- the descriptor already narrows the field down, but something confounds fine discrimination among the candidates, exactly what lighting/blur nuisance riding through every learned channel would do (it blurs classifier rows for visually similar districts without erasing their coarse similarity). This experiment gives the network, for the first time, an explicit non-learned illumination-invariant geometric signal -- a Retinex-style channel (log(pixel) minus its own large-radius Gaussian blur, per RGB channel, fixed kernel, no gradient path) concatenated with raw RGB before the very first conv layer -- so the trunk, layout summary, and every downstream stage can draw on a signal invariant to ambient brightness/gain by construction, instead of relying on fine-tuning alone to discover that invariance, which the last three capacity experiments show it has not done. Method Concatenate a fixed, non-learned Retinex-style illumination-invariant channel (log(pixel) minus its own large-radius Gaussian blur, per RGB channel, sigma=10px, no gradient path) with raw RGB before the first conv layer; widen the pretrained MobileNetV3-Small stem from 3 to 6 input channels (RGB weights kept verbatim, new channels a 0.1x-scaled copy); L2-normalize the layout-squeeze code per spatial cell before flattening; add exp 33's never-trained per-crop sharpness-jitter augmentation and a short LR warm-up for the newly-initialized stem weights. Files: model/model.py (_retinex, _build_pretrained_trunk, TinyLocNet.__init__/forward), model/train.py (_jitter_sharpness, sample_epoch, train_area's scheduler). Conclusion at the time REJECTED — pivot directive was not honored (partial change, not a complete rethink); iteration discarded without training Not on today's ruler It failed a deployment gate, so there was no working model to score then and none to re-score now. What it cost
The agent lines are the equivalent API cost of the tokens each agent used, as recorded by the harness when it ran. The research ran on a claude.ai Max subscription, so that is what it would have cost billed per token — not money that left the account. The GPU line is real spend. What it was measured against then Optimised: worst-case median error (m), target 20 m, scoring —. Asked over: region holdout, 6 lighting buckets, 4 areas. The long grind: 62 experiments over ten days against a general-purpose, four-area, six-lighting-bucket problem. Scored on a region holdout that asked a memorization model to locate ground it had never been shown. era Four areas, six lighting buckets · ts 2026-07-22T17:32:07 ·
commit 898d8cb5b18b · artifacts runs/20260722_171135_iter1 ·
took 20 m 32 s
The code for this experiment — not retainedSource not retained. Until 2026-07-31 the loop discarded a reverted experiment’s code outright, and this row predates that fix. Its design record, its verdict and its exported model survive; the code does not. It is left blank rather than filled with the parent’s code, which is what this row’s recorded commit actually contains. | |||||||||||||||||||||||||||
| ▸ | 2.37 | Multi-hypothesis coordinate regression replaces the 1024-cell field
5a6771be
The loop discarded this — no improvement under the metric then in force. |
architecture | 1.643 | 66 m 02 s | $4.59 | DISCARDED | ||||||||||||||||||||
|
The design under test — technical diagram — gray = frozen contract · ink = the current design · red = this experiment's change In plain words For twenty-some rounds straight, the model has guessed your location the same way: score 1,024 fixed map tiles and pick whichever one scores highest -- like a multiple-choice test with 1,024 options. We measured that the right tile is in the model's own top-20 guesses most of the time, but wins outright only 2% of the time -- so the model roughly knows the neighborhood but keeps losing the final vote to a lookalike tile. This round throws out the fixed 1,024-tile menu and lets the model instead point to 8 of its own candidate spots anywhere on the map, then simply picks whichever one it's most sure about -- like narrowing that multiple-choice test from 1,024 options down to 8, which should be a much easier pick. Each of the four camera headings picks its own best spot, and the four picks get blended by how confident each heading was. Hypothesis The champion's field/decode mechanism (32x32-cell classification field -> beta-sharpened soft-argmax) has been completely unchanged since round 16 -- twenty rounds (17-36) have only churned satellite modules around it (samplers, auxiliary losses, gates, a margin hinge) while the frozen core keeps producing the exact failure mode diagnosed in runs/_probe_iter12_field.py and recorded in project memory 'champion-failure-mode-probes': the true cell is the field's top-1 pick on only ~2% of eval crops, yet lands in the field's own top-20 (of 1024) on 33-58% of crops in every lighting bucket. That gap means the descriptor already carries enough signal to narrow the answer down to a small neighborhood -- the failure is specifically that a 1024-way ranking problem is too hard for the head to solve, not that the features are uninformative. Round 34's and round 36's margin-hinge losses attacked this by pushing individual impostor cells down within the SAME 1024-way field and both failed to beat the champion (1e9 gated-fail, then 754.22 vs 743.07) -- evidence the bottleneck is the discretization itself, not which cells get extra gradient. Shrinking the ranking problem from 1-of-1024 fixed grid cells to 1-of-8 data-dependent candidate points should make correct-hypothesis selection tractable while a small K also sidesteps exp 5's diagnosed sparse-supervision problem (a rigid 1024-cell grid needs many training locations per cell to learn anything; 8 free-floating points just need to drift toward wherever they're useful, no fixed-cell occupancy requirement). Method In model/model.py, delete the 1024-way classification field entirely (loc_logits, dark_logits, gate, cell_u/cell_v grid, GRID_K, DECODE_BETA, the beta-sharpened soft-argmax decode) and replace it with a K=8 multi-hypothesis regression head: a small MLP reads [GAP descriptor, layout code, raw brightness] and outputs 8 candidate (u,v) points (each an offset added to one of 8 fixed, non-trainable anchor positions spread across the map, purely to break symmetry at init) plus 8 softmax mixture weights. Each of the 4 C4-rotation views independently picks its own highest-weight hypothesis; the 4 views' winning points are blended into the final (u,v) by a softmax over their own weights (replacing the old logit-averaging C4 vote with a point-averaging C4 vote). Confidence is rebuilt from the spread of the 4 views' winning points (tight agreement = confident) and their mean weight, replacing the old field-peak/entropy signal. Training signal changes from Gaussian-smoothed 1024-way cross-entropy + coordinate L2 to a winner-take-all regression loss (only the hypothesis nearest the true (u,v) gets the full L2 gradient each step; the other 7 get an epsilon=0.05 trickle to avoid dead hypotheses) plus an 8-way cross-entropy that trains the weight head to identify which hypothesis was nearest. The exp-35 confusability-weighted sampler (Training data, kept, unchanged) still needs a 32x32 grid to define 'look-alike cells' for oversampling -- since model.py no longer has any grid, that constant moves into model/train.py as a standalone CONFUSABILITY_GRID_K, with identical value and math, so the sampler's actual behavior is byte-for-byte unchanged. Conclusion at the time REVERTED — metric did not improve (1100.35 m vs best 743.07 m); change discarded Re-measured on today's ruler Mission score 1.643 — 1.0% of held-out frames give a usable fix, 65.2% are confident and wrong, 33.8% abstain. Median miss 1.33 km. Measured, not converted: its exported model was re-run through the current scorer for this page. What it cost
by model — claude-sonnet-5 $3.82 · claude-haiku-4-5-20251001 $0.01 The agent lines are the equivalent API cost of the tokens each agent used, as recorded by the harness when it ran. The research ran on a claude.ai Max subscription, so that is what it would have cost billed per token — not money that left the account. The GPU line is real spend. What it was measured against then Optimised: worst-case median error (m), target 20 m, scoring 1.10 km. Asked over: region holdout, 6 lighting buckets, 4 areas. The long grind: 62 experiments over ten days against a general-purpose, four-area, six-lighting-bucket problem. Scored on a region holdout that asked a memorization model to locate ground it had never been shown. Scoreboard — re-scored today Produced by running this experiment's exported model through the current frozen scorer on the current eval set. It is not what this experiment was told at the time.
era Four areas, six lighting buckets · ts 2026-07-22T16:01:49 ·
commit 5a6771be65eb · artifacts runs/20260722_145547_iter1 ·
took 66 m 02 s
The code for this experiment — not retainedSource not retained. Until 2026-07-31 the loop discarded a reverted experiment’s code outright, and this row predates that fix. Its design record, its verdict and its exported model survive; the code does not. It is left blank rather than filled with the parent’s code, which is what this row’s recorded commit actually contains. | |||||||||||||||||||||||||||
| ▸ | 2.36 | Hardest-impostor margin hinge, retested on the confusability-weighted champion
3ee64311
The loop discarded this — no improvement under the metric then in force. |
loss | 1.601 | 59 m 54 s | $3.14 | DISCARDED | ||||||||||||||||||||
|
The design under test — technical diagram — gray = frozen contract · ink = the current design · red = this experiment's change In plain words Right now the model's best two guesses for ‘where is this’ are often a real place and a look-alike place somewhere else on the map, and it only barely favors the real one. Last round we made it practice more on exactly those confusing pairs; this round we also change how hard it gets corrected when it still ranks a look-alike above the truth. Instead of a gentle nudge spread thinly over the whole map, it now gets a direct penalty aimed specifically at its single worst rival guess, every time, until the true spot clearly wins. Hypothesis The §6 metric is bound by ranking quality, not descriptor content or the operating point: iter-12 probes of the exp-25 champion (runs/_probe_iter12_field.py) found the true cell is the field's top-1 on only 2% of eval crops while making the top-20 (of 1024) on 33-58% in every lighting bucket, with decode, rotation, and operating-point mechanisms all ruled out as the wall (see project memory champion-failure-mode-probes). Exp 35 (kept, 797.91 -> 743.07 m) improved this by changing WHICH locations get training exposure (confusability-weighted sampling over cell-descriptor lookalikes) but never touched the training SIGNAL itself: loss_fn's Gaussian-smeared CE (sigma=1.5 cells) still spreads its wrong-cell gradient across all 1024 cells in proportion to softmax mass, so no gradient concentrates specifically on the one distant lookalike currently outranking the truth -- exactly the objective-family gap exp 34 pre-registered (hardest-impostor margin hinge) and never got to test: its run died with ENOSPC inside prepare_realizations before a single gradient step (timings.json score_s: 0, train_wall_s: 268 -- see project memory exp34-enospc-disk-lifecycle). The hypothesis is UNTESTED, not refuted, and the mechanism it targets (the training signal) is a materially different lever from exp 35's (the sampler). The two should compound rather than compete: exp 35 already brings confusable cell pairs to the model more often; the margin hinge is the loss term that actually forces their scores apart once they arrive, rather than relying on CE's diffuse likelihood gradient to do it incidentally. Disk is currently clean (33 GB free per df -h) so this run should not repeat exp 34's infrastructure failure. Method ONE change, entirely inside model/model.py's loss_fn (training-only; forward(), export_onnx(), estimate_position(), and all of train.py -- sampler, prepare_realizations, schedule, calibrate_conf_shift -- are untouched): add a hardest-impostor margin hinge on the same C4-voted logits the decode reads. Per crop: s_pos = max logit within Chebyshev radius 1 of the true cell (the 3x3 block a 128 m crop legitimately straddles across a ~220 m cell boundary); s_neg = max logit over cells at Chebyshev radius > 3 (outside the 7x7 ring the sigma=1.5-cell Gaussian CE bump already covers, so the hinge never fights the CE term over near-misses -- same exclusion radius convention train.py's compute_cell_weights already uses for its confusability lookup, NEIGHBOR_EXCLUDE_R=1, just applied at a wider ring for the negative). loss += 0.5 * relu(1.0 + s_neg - s_pos), added to loss_fn's existing returned sum (ce + coord_loss + 0.3 * conf_loss). Margin 1.0 logit is roughly a 20x mass ratio after the beta=3 decode sharpening already in forward(). CE, coord L2, conf BCE, the sampler, the epoch schedule, and the calibration procedure are all unchanged, so the deployment gates and operating-point comparison to the champion stay apples-to-apples. Conclusion at the time REVERTED — metric did not improve (754.22 m vs best 743.07 m); change discarded Re-measured on today's ruler Mission score 1.601 — 3.0% of held-out frames give a usable fix, 63.0% are confident and wrong, 34.0% abstain. Median miss 889.2 m. Measured, not converted: its exported model was re-run through the current scorer for this page. What it cost
by model — claude-sonnet-5 $2.45 · claude-haiku-4-5-20251001 $0.00 The agent lines are the equivalent API cost of the tokens each agent used, as recorded by the harness when it ran. The research ran on a claude.ai Max subscription, so that is what it would have cost billed per token — not money that left the account. The GPU line is real spend. What it was measured against then Optimised: worst-case median error (m), target 20 m, scoring 754.2 m. Asked over: region holdout, 6 lighting buckets, 4 areas. The long grind: 62 experiments over ten days against a general-purpose, four-area, six-lighting-bucket problem. Scored on a region holdout that asked a memorization model to locate ground it had never been shown. Scoreboard — re-scored today Produced by running this experiment's exported model through the current frozen scorer on the current eval set. It is not what this experiment was told at the time.
era Four areas, six lighting buckets · ts 2026-07-22T12:30:57 ·
commit 3ee64311291b · artifacts runs/20260722_113103_iter2 ·
took 59 m 54 s
The code for this experiment — not retainedSource not retained. Until 2026-07-31 the loop discarded a reverted experiment’s code outright, and this row predates that fix. Its design record, its verdict and its exported model survive; the code does not. It is left blank rather than filled with the parent’s code, which is what this row’s recorded commit actually contains. | |||||||||||||||||||||||||||
| ▸ | 2.35 | Confusability-weighted location sampling: oversample look-alike cell pairs, not just more places
3750ddb6
The loop kept this — it beat the running best under the metric then in force. On today's ruler it scores 1.588 — worse than silence. |
training | 1.588 | 62 m 59 s | $4.20 | KEPT | ||||||||||||||||||||
|
The design under test — technical diagram — gray = frozen contract · ink = the current design · red = this experiment's change In plain words The model has to recognize 1,024 map tiles from a single photo, and we already know its biggest problem: some far-away tile that just happens to look similar often beats the true tile in the ranking. Until now, training showed the model every tile equally often, whether or not it has a lookalike twin somewhere else on the map. This experiment changes nothing about the model or the grading -- only which practice photos get shown more often. Before training starts, we compare the average color and texture of every tile against every other (a one-time, cheap lookup from the daytime map image itself) and flag pairs of far-apart tiles that look alike. During training, places with a lookalike twin get shown about twice as often as places that look nothing like anywhere else -- half the practice still covers the whole map evenly, so nothing is forgotten, but the confusing pairs get extra practice specifically at telling themselves apart. Hypothesis Per the plateau rule this iteration leaves the last nine mechanisms entirely (26-34: trunk capacity x3, stride-8 readout tap, lighting dispatcher, neural-map coordinate reparameterization, fenced/risk calibration, sensor-sharpness augmentation, and a hardest-impostor margin loss -- the last two never actually trained to completion, ENOSPC/OOM-killed before a single score, per runs/_probe_iter12_*.py forensics logged against exps 33/34) and opens a design family CLAUDE.md SS3 invites but the loop has never tried: a training-DATA strategy overhaul, distinct from every loss/architecture/ calibration change already attempted. The iter-12 probes of the exp-25 champion (runs/_probe_iter12_field.py) established the wall precisely: the true cell is the field's top-1 on only 2% of eval crops while making the top-20 (of 1024) on 33-58% in every bucket -- a lookalike cell elsewhere on the map usually outranks the truth. Since exp 20, every cell's classifier row gets the SAME training exposure regardless of how confusable it is (uniform fresh location draw, 6,000/bucket/epoch): a cell with a distant near-duplicate twin gets no more corrective gradient than an entirely distinctive one, even though it is precisely the confusable cells whose classifier rows must be pushed hardest apart to fix the measured top-1 failure. Rural Prignitz (distinctive macro-structure, few self-similar districts) already beats all three self-similar metros in every bucket under this exact uniform scheme -- the metros' worse scores are concentrated in exactly the cells uniform sampling under-serves. Rather than touch the loss (margin family, attempted but crashed untested in exp 34) or the architecture (capacity/dispatcher/coordinate families, all attempted and refuted for real in exps 26-31), this experiment changes WHICH locations the sampler draws: a cheap, one-time-per-area map of each classifier cell's nearest visually-similar DISTANT cell (computed straight from the frozen reference.tif raster, no network, no per-epoch cost) sets a sampling weight, so confusable cells are oversampled relative to distinctive ones -- concentrating the fixed per-epoch gradient budget where the ranking wall actually is. Method ONE change, entirely inside model/train.py (model/model.py untouched -- no architecture or loss change): (1) a new one-time-per-area function computes a GRID_K x GRID_K (32x32=1024) cell-descriptor map by average-pooling reference.tif's RGB into the same grid the field head already scores (mean R,G,B + luminance std per cell, 4 numbers), then for every cell finds its nearest OTHER cell in descriptor space excluding itself and its 8 spatial neighbors (so the weight targets genuine distant lookalikes, not trivial local blur-driven near-duplicates), giving each cell a confusability distance d_i and weight w_i = 1/(d_i + eps); (2) every training crop is mapped to its ground-truth cell and assigned sampling probability p = 0.5 x uniform + 0.5 x normalize(w) over that cell's crops; (3) sample_epoch's uniform rng.choice(..., replace=False) is replaced with the weighted rng.choice(..., p=crop_probs, replace=False) for the TRAINING draw only. calibrate_conf_shift's sampling stays uniform/unchanged (coverage-median-coupling memory: do not let a data-strategy change also alter the calibration distribution, or a metric shift can't be attributed to either mechanism cleanly). Model architecture, loss_fn, epoch count, LR schedule, and the C4/gate/decode/conf machinery are byte-identical. Conclusion at the time KEPT — metric improved (797.91 -> 743.07 m); change committed as 3750ddb Re-measured on today's ruler Mission score 1.588 — 3.0% of held-out frames give a usable fix, 61.8% are confident and wrong, 35.2% abstain. Median miss 1.15 km. Measured, not converted: its exported model was re-run through the current scorer for this page. What it cost
by model — claude-sonnet-5 $3.47 · claude-haiku-4-5-20251001 $0.00 The agent lines are the equivalent API cost of the tokens each agent used, as recorded by the harness when it ran. The research ran on a claude.ai Max subscription, so that is what it would have cost billed per token — not money that left the account. The GPU line is real spend. What it was measured against then Optimised: worst-case median error (m), target 20 m, scoring 743.1 m. Asked over: region holdout, 6 lighting buckets, 4 areas. The long grind: 62 experiments over ten days against a general-purpose, four-area, six-lighting-bucket problem. Scored on a region holdout that asked a memorization model to locate ground it had never been shown. Scoreboard — re-scored today Produced by running this experiment's exported model through the current frozen scorer on the current eval set. It is not what this experiment was told at the time.
era Four areas, six lighting buckets · ts 2026-07-22T11:30:46 ·
commit 3750ddb61f7a · artifacts runs/20260722_102747_iter1 ·
took 62 m 59 s
The code for this experiment — not retainedSource not retained. Until 2026-07-31 the loop discarded a reverted experiment’s code outright, and this row predates that fix. Its design record, its verdict and its exported model survive; the code does not. It is left blank rather than filled with the parent’s code, which is what this row’s recorded commit actually contains. | |||||||||||||||||||||||||||
| ▸ | 2.34 | Hardest-impostor margin loss: the field must outrank its best lookalike, not just light up the truth
bfbca30d
Failed a deployment gate: too large, too slow, or it abstained on too many frames to be scoreable. |
loss | — | 20 m 01 s | $8.03 | GATED FAIL | ||||||||||||||||||||
|
The design under test — technical diagram — gray = frozen contract · ink = the current design · red = this experiment's change In plain words The model scores 1,024 map tiles for each camera frame and we fly toward the highest-scoring one. Until now training only rewarded giving the right tile a decent score — like a teacher handing out partial credit but never checking who came FIRST. Measurements show the right tile usually makes the model's top-20 list, yet almost never ranks #1: some lookalike district across town keeps beating it. The new rule is a police-lineup rule: on every training example, find the one impostor tile the model currently likes best and demand the true tile's neighborhood beat it by a clear gap. The specific impostor that keeps winning gets pushed down, the truth pushed up. Hypothesis The §6 metric is bound by ranking quality, and the objective family is the one lever nobody has pulled. The iter-12 probes of the exp-25 champion show the true cell is the field's top-1 on only 2% of eval crops while making the top-20 (of 1024) on 33–58% — in every lighting bucket including midday — with median peak mass 0.14 and ~2 separated modes; decode, rotation, and operating-point mechanisms are ruled out. The eight consecutive reverts (26–33) all changed what FEEDS the ranking (3× trunk capacity, stride-8 readout tap, lighting dispatcher, learned map raster) or how its output is thresholded (fenced/risk calibration), or the input statistics (exp 33 — whose 1e9 is an infrastructure artifact: munich training was OOM-killed and the run was never scored). The training signal that CREATES the ranking has been pure likelihood since exp 2: Gaussian-smeared CE spreads its wrong-cell gradient across 1024 cells in proportion to softmax mass, and the σ=1.5-cell smearing makes near-misses cheap, so no gradient ever concentrates on the single distant lookalike that currently outranks the truth — which is precisely the measured failure (truth present in the candidate set, outranked at the top). Visual place recognition trains this exact problem with hardest-negative margin objectives (NetVLAD/FaceNet family). A per-crop hinge that demotes the current best-scoring impostor cell should convert top-20 presence into top-1 rank and raise peak mass, improving median kept error across all 24 area×bucket cells rather than gaming one operating point. Method ONE change, entirely inside model/model.py's loss_fn (training-only; forward, export, train.py untouched): add a hardest-impostor margin hinge on the same C4-voted logits the decode uses. Per crop: s_pos = max logit within Chebyshev radius 1 of the true cell (3×3 block — a 128 m crop legitimately straddles ~220 m cells), s_neg = max logit over cells at Chebyshev radius > 3 (outside the 7×7 ring, so the hinge never fights the σ=1.5 CE bump), loss += 0.5 · relu(1.0 + s_neg − s_pos). Margin 1.0 logit ≈ 20× mass ratio after the β=3 decode sharpening. CE, coord L2, conf BCE, sampler, schedule, calibration, and the ONNX graph are unchanged, so the deployment gates and the operating-point calibration procedure are identical to the champion's. Conclusion at the time REVERTED — metric did not improve (1000000000.0 m vs best 797.91 m); change discarded Not on today's ruler It failed a deployment gate, so there was no working model to score then and none to re-score now. What it cost
by model — claude-fable-5 $7.39 · claude-sonnet-5 $0.40 · claude-haiku-4-5-20251001 $0.00 The agent lines are the equivalent API cost of the tokens each agent used, as recorded by the harness when it ran. The research ran on a claude.ai Max subscription, so that is what it would have cost billed per token — not money that left the account. The GPU line is real spend. What it was measured against then Optimised: worst-case median error (m), target 20 m, scoring —. Asked over: region holdout, 6 lighting buckets, 4 areas. The long grind: 62 experiments over ten days against a general-purpose, four-area, six-lighting-bucket problem. Scored on a region holdout that asked a memorization model to locate ground it had never been shown. era Four areas, six lighting buckets · ts 2026-07-22T08:53:43 ·
commit bfbca30d5c51 · artifacts runs/20260722_083342_iter7 ·
took 20 m 01 s
The code for this experiment — not retainedSource not retained. Until 2026-07-31 the loop discarded a reverted experiment’s code outright, and this row predates that fix. Its design record, its verdict and its exported model survive; the code does not. It is left blank rather than filled with the parent’s code, which is what this row’s recorded commit actually contains. | |||||||||||||||||||||||||||
| ▸ | 2.33 | Sensor-sharpness nuisance randomization: per-crop PSF/resampling jitter breaks the blur-texture shortcut
0aaab0f5
Failed a deployment gate: too large, too slow, or it abstained on too many frames to be scoreable. |
augmentation | — | 59 m 18 s | $7.04 | GATED FAIL | ||||||||||||||||||||
|
The design under test — technical diagram — gray = frozen contract · ink = the current design · red = this experiment's change In plain words When a computer rotates a photo, the result comes out a tiny bit softer — like a photocopy of a photocopy. Nearly every training picture we generate goes through one such rotation, so they all share that same subtle softness, and the model quietly learned to lean on it as a crutch: when we handed it a perfectly sharp picture, its guesses got twice as bad — even though softness says nothing about WHERE the picture was taken. This round we make the crutch useless: during training, some pictures now arrive pin-sharp and others extra blurry, completely at random, so the only cue that reliably pays off is the actual arrangement of streets and buildings. It's like training a wine taster with the bottles wrapped in paper: hide the label and they have to learn the taste. Hypothesis The §6 metric is bound by ranking quality, not decode or operating point: the iter-12 probes on the exp-25 champion show the true cell is field top-1 on only 2% of eval crops while argmax, local-window, and exported decodes agree within 3% (runs/_probe_iter12_field.py), and exp 32's per-regime abstention fix — the operating-point attack — was just refuted. The same probe round measured an untested nuisance axis: eval crops that skip PIL bilinear rotation resampling (headings at exact 90° multiples) nearly DOUBLE the champion's median error, 983→1668 m on berlin midday (runs/_probe_iter12_heading.py), so the descriptor demonstrably rides on low-level resampling-blur statistics. That band is identical everywhere on the map — it carries zero location information — yet it varies with heading, so every unit of descriptor sensitivity spent on it both jitters the place code across eval headings and steals encoder capacity from layout, which is exactly the ranking signal that must improve. Exp 17 already validated the mechanism (randomize a train-time nuisance → only nuisance-stable structure stays discriminative between locations) and the training-data family is 4-for-4 kept (exps 7, 17, 20, 25) while the last seven architecture/operating-point experiments (26–32) all reverted. Per-crop randomization of the imaging chain's sharpness — from crisper-than-eval to softer-than-eval — should force the encoder off the blur band and onto ground layout, improving field ranking in every area×lighting cell. Not built on a library entry; built on the iter-12 probe forensics. Method ONE change, entirely inside model/train.py's sample_epoch (the per-epoch fresh-draw sampler): each training crop rolls a random sharpness before entering the tensor. (a) With p=0.25 its heading is snapped to 0° so extract_crop skips rotation resampling entirely and the crop arrives pin-sharp — the model's C4 rotation fan then exposes all four crisp orientations for free. (b) Independently, with p=0.5, the extracted 128×128 uint8 crop gets an extra PIL GaussianBlur with σ ~ U(0.4, 1.4) px. The eval-matched statistic (continuous-heading resample, no extra blur) remains the single most common case (~37.5%), and calibrate_conf_shift stays exactly eval-matched (no jitter there). Model, losses, decode, calibration, EPOCH_MULT schedule, and the frozen pipeline are untouched. Conclusion at the time REVERTED — metric did not improve (1000000000.0 m vs best 797.91 m); change discarded Not on today's ruler It failed a deployment gate, so there was no working model to score then and none to re-score now. What it cost
by model — claude-fable-5 $5.91 · claude-sonnet-5 $0.44 · claude-haiku-4-5-20251001 $0.00 The agent lines are the equivalent API cost of the tokens each agent used, as recorded by the harness when it ran. The research ran on a claude.ai Max subscription, so that is what it would have cost billed per token — not money that left the account. The GPU line is real spend. What it was measured against then Optimised: worst-case median error (m), target 20 m, scoring —. Asked over: region holdout, 6 lighting buckets, 4 areas. The long grind: 62 experiments over ten days against a general-purpose, four-area, six-lighting-bucket problem. Scored on a region holdout that asked a memorization model to locate ground it had never been shown. era Four areas, six lighting buckets · ts 2026-07-22T08:33:30 ·
commit 0aaab0f53186 · artifacts runs/20260722_073412_iter6 ·
took 59 m 18 s
The code for this experiment — not retainedSource not retained. Until 2026-07-31 the loop discarded a reverted experiment’s code outright, and this row predates that fix. Its design record, its verdict and its exported model survive; the code does not. It is left blank rather than filled with the parent’s code, which is what this row’s recorded commit actually contains. | |||||||||||||||||||||||||||
| ▸ | 2.32 | Risk-controlled abstention: six per-lighting-regime thresholds calibrated on never-trained fenced blocks
4871a8c7
The loop discarded this — no improvement under the metric then in force. |
training | 1.438 | 108 m 15 s | $19.07 | DISCARDED | ||||||||||||||||||||
|
The design under test — technical diagram — gray = frozen contract · ink = the current design · red = this experiment's change In plain words The model already grades its own guesses and is allowed to answer 'no fix' on frames it can't place — like a student who may skip questions on a test that only grades the questions they answer, as long as they attempt at least 1 in 5. Until now the skip rule was one global bar, tuned on map areas the model had already flown over in training — so how picky it actually was at night versus midday, in Berlin versus Frankfurt, was essentially accidental. We now rope off a slice of the map the model never trains on, measure there — separately for each lighting condition — how accuracy trades against how often it answers, and bake six lighting-specific bars into the model (it senses the lighting from the frame's brightness). A telling detail from the forensics: the last four 'failed' experiments were actually answering MORE questions than the champion at similar accuracy — they lost on a technicality of where their bar happened to land, which this change fixes for every future round too. Hypothesis The §6 scalar is currently bound not by representation quality but by the selective-prediction operating point, which is uncontrolled per lighting regime and mis-calibrated by construction. The primary metric is the median over KEPT crops with a 0.2 coverage floor, yet the abstention bar is a single global conf_shift chosen as the MINIMUM of six per-bucket thresholds, calibrated on TRAIN-VISITED ground — so each (area, bucket) cell's eval keep rate is an accident of distribution shift (champion: 0.25–0.67 across cells). Forensics on the six-experiment plateau: exps 28–31 matched or beat the champion cell-by-cell while keeping far more crops — exp 28 was better or equal in 17 of 24 cells at coverage 0.52–0.79 vs the champion's 0.42–0.67 (frankfurt evening 649.5 m @ 0.58 vs 797.9 m @ 0.46) and was reverted on ONE cell (berlin night 1057.7 m @ 0.445 vs 632.1 m @ 0.3125); exp 31 kept 30–60% more crops everywhere (0.60–0.87) and landed within 6%. The keep/revert record since exp 26 has therefore mostly measured where each candidate's threshold happened to land, not which candidate localizes better. Two fresh read-only probes justify attacking the confidence path INSTEAD of the representation this round: (a) _probe_iter12_field.py shows the ranking failure is deep and decode-independent (true cell in top-1 only 2%, argmax/local-window/export decodes within 3% of each other, oracle mode selection still ~500 m) — no cheap representation fix is on the table; (b) _probe_iter12_riskcov.py shows the confidence signal RANKS errors in every cell (Spearman conf-vs-error −0.21 to −0.42) and quantifies the exploitable headroom: with each cell run at its own best keep rate the worst §6 cell falls from 797.9 m to 731.1 m oracle (binding cell moves to frankfurt/night; frankfurt evening itself drops 798→~562-590 m, and 20 of 24 cells improve). The same probe also explains exp 28's veto mechanistically: its one global threshold kept 44.5% at berlin night (1057.7 m), while a per-regime risk-optimal pick would have run that cell at ~0.26-0.32 keep (~650-750 m). Making the operating point a designed, per-regime, distribution-matched quantity should both lower the worst cell directly and remove the measurement distortion that has been vetoing genuine representation gains. Method One mechanism changes — the path from confidence score to abstention decision; trunk, heads, gate, field, decode, losses, sampler mechanics, and schedule stay byte-identical. (1) model/model.py: the scalar conf_shift buffer becomes a 6-vector paired with a new conf_lum_anchors buffer; forward() piecewise-linearly interpolates the shift over the frame's own mean brightness (the lum feature the lighting gate already computes), so the exported graph infers the lighting regime from the frame alone: conf = sigmoid(z − shift(lum)). (2) model/train.py: ~10% of train-split blocks (stable-hash selected, bbox-generic) are FENCED — excluded from every training draw with the same window-halo rule the frozen split applies to eval blocks — so their terrain is honestly unseen and per-crop errors are measurable there. (3) Calibration (replaces calibrate_conf_shift): per lighting bucket, run the trained model on fenced crops at fresh random headings, trace the risk–coverage curve (median kept error vs keep rate), pick the keep rate q* in [0.26, 0.58] that minimizes the fenced kept-median (floor 0.26 is safe against the frozen 0.2 coverage gate because exp 28 measured the fenced-to-eval keep transfer as upward-biased, +0.00 to +0.11 — unlike today's train-visited calibration, which transfers DOWN by up to 0.15), and bake the six (median bucket luminance, threshold) pairs into the exported buffers. Conclusion at the time REVERTED — metric did not improve (1078.31 m vs best 797.91 m); change discarded Re-measured on today's ruler Mission score 1.438 — 2.8% of held-out frames give a usable fix, 46.5% are confident and wrong, 50.7% abstain. Median miss 999.0 m. Measured, not converted: its exported model was re-run through the current scorer for this page. What it cost
by model — claude-fable-5 $15.88 · claude-sonnet-5 $1.94 · claude-haiku-4-5-20251001 $0.00 The agent lines are the equivalent API cost of the tokens each agent used, as recorded by the harness when it ran. The research ran on a claude.ai Max subscription, so that is what it would have cost billed per token — not money that left the account. The GPU line is real spend. What it was measured against then Optimised: worst-case median error (m), target 20 m, scoring 1.08 km. Asked over: region holdout, 6 lighting buckets, 4 areas. The long grind: 62 experiments over ten days against a general-purpose, four-area, six-lighting-bucket problem. Scored on a region holdout that asked a memorization model to locate ground it had never been shown. Scoreboard — re-scored today Produced by running this experiment's exported model through the current frozen scorer on the current eval set. It is not what this experiment was told at the time.
era Four areas, six lighting buckets · ts 2026-07-22T07:33:54 ·
commit 4871a8c721c1 · artifacts runs/20260722_054539_iter5 ·
took 108 m 15 s
The code for this experiment — not retainedSource not retained. Until 2026-07-31 the loop discarded a reverted experiment’s code outright, and this row predates that fix. Its design record, its verdict and its exported model survive; the code does not. It is left blank rather than filled with the parent’s code, which is what this row’s recorded commit actually contains. | |||||||||||||||||||||||||||
| ▸ | 2.31 | Neural-map correlation field: localization becomes sliding-window matching of a crop fingerprint against a learned 32 m map raster
1ee201ff
The loop discarded this — no improvement under the metric then in force. |
architecture | 1.771 | 73 m 46 s | $12.11 | DISCARDED | ||||||||||||||||||||
|
The design under test — technical diagram — gray = frozen contract · ink = the current design · red = this experiment's change In plain words Until now the model answered 'where am I?' by comparing one overall impression of the camera frame against about a thousand memorized district flashcards -- and in a city full of lookalike districts, overall impressions collide. Now the model instead paints its own miniature map of the whole area into its weights while training. In flight it turns the camera frame into a small 4x4 fingerprint tile and slides that tile across its painted map to find where the pattern truly lines up, the way you find where a puzzle piece belongs. Because the whole arrangement inside the fingerprint has to line up -- not just the general vibe -- two neighborhoods that merely feel similar no longer fool it, and the answer grid becomes about seven times finer in each direction. Hypothesis The last five reverted experiments (26-30: 3x trunk capacity, coverage calibration, stride-8 readout tap, lighting-specialist dispatch) all attacked what feeds or routes around the SAME output parameterization -- a flat table of 1,024 independent ~217 m cell templates scored by dot product against one holistic 560-d crop vector, unchanged since exp 2 -- and were jointly refuted. Per the plateau rule this iteration replaces that parameterization itself, the one design family CLAUDE.md §3 invites ('a different coordinate parameterization') that the history has never touched. Three signatures say the template table is the binding constraint: (1) since exps 20/25 the converged model's train-split and eval-split medians are EQUAL (~600-800 m) -- it cannot even fit visited locations, the signature of a statistically weak output parameterization, not of missing capacity (exps 26/28 proved bigger trunks don't score) or missing optimization (exp 25); (2) rural prignitz, with distinctive macro-structure, beats all three self-similar metros in every bucket -- lookalike-cell confusion, which holistic-vector template scoring cannot resolve because it enforces no spatial-arrangement consistency between crop and cell; (3) mean/median ~= 2 everywhere -- a heavy tail of long-range aliasing errors, the same confusion. The change (OrienterNet-family matching, encoded entirely in weights -- no reference imagery ships): delete the 1,024-template tables and learn a NEURAL MAP, an 8-channel raster over the area at ~32 m/cell (dimensions derived from meta.json width/height -- no area-specific constants), scoring every raster position by cross-correlating a crop-derived 4x4x8 feature kernel whose taps each summarize 32 m of ground, matching the raster's cell size. One mechanism addresses all three signatures: (a) a position scores high only if the crop's whole 4x4 spatial arrangement matches the map there, so lookalike districts differing in sub-crop layout are suppressed geometrically; (b) the scoring function is shared across all ~45k positions (one matching kernel + one map) instead of 1,024 separately-learned discriminants, and overlapping training crops write coherently into the SAME map cells at their true offsets, multiplying effective supervision per output parameter; (c) the decode grid drops from 217 m to ~32 m pitch. The kept exp-12 bright/dark expert pair and gate survive as two kernel generators (layout-aware conv vs GAP-only linear) blended before a single correlation -- correlation is linear in the kernel, so blending kernels equals blending fields; the C4 vote (exp 16), beta-sharpened decode (exp 14), shape-feature confidence + calibration (exp 15), fresh-draw sampler (exp 20), 3-realization renders (exp 17) and 24-epoch cosine schedule (exp 25) are all byte-identical, and supervision smoothness is preserved exactly (Gaussian-CE sigma stays 1.5/32 in normalized map units). Method model/model.py: remove loc_logits (560->1024), dark_logits (48->1024) and the GRID_K template machinery. Add per-area parameters map_feat [8, mh, mw] (mh=round(height/32), mw=round(width/32), init 0.01*randn) and map_bias [(mh-3)*(mw-3)] (init 0). The trunk's 8x8x48 fmap is projected by a 1x1 conv (48->8) and 2x2 avg-pooled into a 4x4x8 'bright' kernel; a Linear(48->128) forms a 'dark' kernel from the GAP descriptor; the existing lum+GAP gate blends the two KERNELS per example; logits4 = F.conv2d(map_feat.unsqueeze(0), blended_kernels).squeeze(0).flatten(1) + map_bias -- valid cross-correlation, one logit per raster position, C4-averaged exactly as before. Cell centers: u=(j+2)/mw, v=(i+2)/mh, row-major. Decode, confidence features, and loss keep their exact form with TARGET_SIGMA_UV = 1.5/32. model/train.py: build_model(meta) passes area dims; loss_fn additionally receives the model's cell-center buffers. Sampler, calibration, schedule, CLI, export contract untouched. Conclusion at the time REVERTED — metric did not improve (846.09 m vs best 797.91 m); change discarded Re-measured on today's ruler Mission score 1.771 — 2.0% of held-out frames give a usable fix, 79.0% are confident and wrong, 19.0% abstain. Median miss 1.39 km. Measured, not converted: its exported model was re-run through the current scorer for this page. What it cost
by model — claude-fable-5 $9.78 · claude-sonnet-5 $1.47 · claude-haiku-4-5-20251001 $0.00 The agent lines are the equivalent API cost of the tokens each agent used, as recorded by the harness when it ran. The research ran on a claude.ai Max subscription, so that is what it would have cost billed per token — not money that left the account. The GPU line is real spend. What it was measured against then Optimised: worst-case median error (m), target 20 m, scoring 846.1 m. Asked over: region holdout, 6 lighting buckets, 4 areas. The long grind: 62 experiments over ten days against a general-purpose, four-area, six-lighting-bucket problem. Scored on a region holdout that asked a memorization model to locate ground it had never been shown. Scoreboard — re-scored today Produced by running this experiment's exported model through the current frozen scorer on the current eval set. It is not what this experiment was told at the time.
era Four areas, six lighting buckets · ts 2026-07-22T05:45:21 ·
commit 1ee201ffc513 · artifacts runs/20260722_043135_iter4 ·
took 73 m 46 s
The code for this experiment — not retainedSource not retained. Until 2026-07-31 the loop discarded a reverted experiment’s code outright, and this row predates that fix. Its design record, its verdict and its exported model survive; the code does not. It is left blank rather than filled with the parent’s code, which is what this row’s recorded commit actually contains. | |||||||||||||||||||||||||||
| ▸ | 2.30 | Supervised lighting dispatcher: day/dusk/night specialist field heads replace the two-expert blend, int8-paid
92404766
The loop discarded this — no improvement under the metric then in force. |
architecture | 1.696 | 73 m 20 s | $11.12 | DISCARDED | ||||||||||||||||||||
|
The design under test — technical diagram — gray = frozen contract · ink = the current design · red = this experiment's change In plain words The model recognizes places by comparing what it sees against a big internal catalog of how every map cell looks. Until now one shared catalog had to describe each place at noon AND at midnight — like one photo album trying to cover both — so every entry was a blurry compromise, and the model coped in the dark mostly by refusing to answer. We split it into three catalogs — day, dusk, night — and added a tiny receptionist that checks how bright the frame is and hands the image to the right catalog. During practice the receptionist is simply told the real time of day, so it can't learn the routing wrong. Three catalogs would normally be too heavy for the drone's tiny computer, so they're stored in a compact 1-byte number format we measured earlier to be essentially harmless. Hypothesis The last four reverted experiments (26-29) all attacked capacity or readout width and were jointly refuted; per the plateau rule this iteration leaves that family entirely and opens the one design family CLAUDE.md §3 explicitly invites but the history has never tried: dispatcher + lighting-condition specialist models. The evidence it targets: the champion's per-bucket medians LOOK flat (berlin 632-760 m), but that flatness is manufactured by the confidence gate — night keeps only 25-31% of crops vs 50-67% for daytime buckets, so the model abstains its way to parity in low light instead of localizing to parity, and the binding §6 cell is a low-ambient one (frankfurt EVENING, 797.91 m at 0.46 coverage). The mechanism: all six lighting regimes share ONE 560→1024 template table, plus exp 12's deliberately crippled texture-only 'dark expert' (48-d input, no layout access, unsupervised gate). The same map cell must therefore be recognized by one template averaged across appearance regimes with nearly disjoint cues — daytime shadow/texture vs night's active-light constellations — so every template is a compromise, worst exactly where cues are scarcest. Exp 12 is direct evidence the axis works: even its crippled version was kept (-8%). This experiment builds the full version: each regime gets its own complete layout-aware 560→1024 template table, routing is SUPERVISED (bucket labels are free at train time), and the 3x table storage is paid in int8 FC bytes, which exps 26/29 mechanically proved passes the frozen gates at ~7 m accuracy cost. Method model/model.py: delete dark_logits and the sigmoid gate; replace loc_logits with three identical specialist heads Linear(560→1024) in an nn.ModuleList (day/dusk/night) and add a 3-way dispatcher MLP Linear(49→16)-ReLU-Linear(16→3) reading [lum, GAP]; per turned copy, field logits = Σ_k softmax(dispatcher)_k · specialist_k(desc); C4 vote, decode, confidence all unchanged. loss_fn gains a supervised routing term: 0.3 × cross-entropy of the dispatcher logits vs the crop's regime (morning/midday/afternoon→day, early_evening/evening→dusk, night→night). model/train.py: sample_epoch also returns the per-crop regime index; the train step passes it to loss_fn. export_onnx: fp32 export to <area>_fp32_debug.onnx, then onnxruntime.quantization.quantize_dynamic(QInt8, op_types=['MatMul','Gemm']) to the contract path — the exact recipe exps 26/29 validated (convs stay fp32; no ConvInteger); assert size ≤ 4 MiB. Conclusion at the time REVERTED — metric did not improve (808.22 m vs best 797.91 m); change discarded Re-measured on today's ruler Mission score 1.696 — 2.2% of held-out frames give a usable fix, 71.8% are confident and wrong, 26.0% abstain. Median miss 1.34 km. Measured, not converted: its exported model was re-run through the current scorer for this page. What it cost
by model — claude-fable-5 $8.84 · claude-sonnet-5 $1.43 · claude-haiku-4-5-20251001 $0.00 The agent lines are the equivalent API cost of the tokens each agent used, as recorded by the harness when it ran. The research ran on a claude.ai Max subscription, so that is what it would have cost billed per token — not money that left the account. The GPU line is real spend. What it was measured against then Optimised: worst-case median error (m), target 20 m, scoring 808.2 m. Asked over: region holdout, 6 lighting buckets, 4 areas. The long grind: 62 experiments over ten days against a general-purpose, four-area, six-lighting-bucket problem. Scored on a region holdout that asked a memorization model to locate ground it had never been shown. Scoreboard — re-scored today Produced by running this experiment's exported model through the current frozen scorer on the current eval set. It is not what this experiment was told at the time.
era Four areas, six lighting buckets · ts 2026-07-22T04:31:16 ·
commit 92404766e474 · artifacts runs/20260722_031756_iter3 ·
took 73 m 20 s
The code for this experiment — not retainedSource not retained. Until 2026-07-31 the loop discarded a reverted experiment’s code outright, and this row predates that fix. Its design record, its verdict and its exported model survive; the code does not. It is left blank rather than filled with the parent’s code, which is what this row’s recorded commit actually contains. | |||||||||||||||||||||||||||
| ▸ | 2.29 | Stride-8 fine-layout tap: the field scorer reads an 8-m-resolution mid-trunk snapshot, int8-paid
7e6fb3ea
The loop discarded this — no improvement under the metric then in force. |
architecture | 1.618 | 70 m 11 s | $11.05 | DISCARDED | ||||||||||||||||||||
|
The design under test — technical diagram — gray = frozen contract · ink = the current design · red = this experiment's change In plain words Before guessing where it is, the model boils each camera frame down to a short summary — like describing a neighborhood in 560 numbers, where each number can only talk about 16-metre-wide squares. At that coarseness, two similar districts blur together, and our last three experiments proved that a bigger brain and longer studying don't help — the tell-apart detail was already thrown away before the guessing stage ever saw it. This experiment keeps everything else identical but lets the guesser also look at a sharper, earlier snapshot from inside the network, where building edges and narrow roads are still visible at roughly 8-metre precision. To keep the file small enough for the drone's tiny computer, the guesser's bulky lookup tables are stored as compact 1-byte numbers — a trick we measured beforehand to be harmless. Hypothesis The binding constraint is the information content of the head's readout, not trunk capacity, optimization, or memorization — this is exp 26's own pre-registered diagnostic fork (a), now demonstrated by the joint record: exp 20 removed memorization (train-split and eval-split medians are EQUAL), exp 25 trained to a genuine loss plateau and moved the metric only 5.2%, and exps 26+28 tripled trunk depth/channels and made things WORSE at the designed operating point (1057.68 m vs 797.91 m best). Per the plateau rule this iteration leaves the capacity-scaling mechanism entirely and attacks the bottleneck those refutations jointly point at. The field scorer today sees only a 560-d summary of the crop: a 48-d GAP plus a 512-d layout code squeezed from the stride-16 8x8 grid — every layout cell aggregates a 16x16 m patch, so NOTHING finer than 16 m granularity survives to the 1024-way cell scorer, while the ~215 m map cells it must discriminate differ precisely in sub-16-m arrangement (building outlines, road widths, lot patterns) that the 1 m/px input plainly contains. Two signatures say the discarded fine scale is what's missing: midday errors equal night errors in every area (656-839 m metros) even though midday crops carry clean 1-m detail — if the readout could use it, daytime would lead; and rural prignitz (distinctive macro-structure, low self-similarity) beats all three self-similar metros (449-539 m), the signature of lookalike-cell confusion, not lighting. The change adds the one axis never scaled: SPATIAL resolution of the readout. The trunk's stride-8 activation (features block 3, 16x16x24) is tapped, squeezed by a 1x1 conv to 4 channels, and flattened into a 1,024-d fine layout code concatenated into the bright field head's input (560 -> 1,584). Trunk computation, pretrained weights, C4 vote, decode, confidence, and training are all byte-identical — unlike refuted exps 26/28, no capacity is added to the encoder; the head is simply allowed to SEE a scale the encoder already computes and then throws away. The enlarged FC heads fly as int8 (quantize_dynamic on MatMul/Gemm only), which runs/_probe_iter6_quant2.py proved metrically inert on this exact head family (~7 m median decode shift, no ConvInteger, latency fine): estimated export ~2.6 MB against the 4 MiB gate. Method model/model.py only. (1) TinyLocNet.__init__: probe the stride-8 tap shape by running features[:4] on a zero tensor (gives 24x16x16); add self.fine_squeeze = nn.Conv2d(24, 4, 1); widen loc_logits input from feat_ch + layout_ch*8*8 = 560 to feat_ch + layout_ch*8*8 + 4*16*16 = 1584. (2) forward: run the trunk as an enumerated loop over self.features, capturing the activation after block index 3 as the tap; fine4 = self.fine_squeeze(tap).flatten(1); bright head becomes self.loc_logits(torch.cat([f4, layout4, fine4], dim=1)). Dark head, gate, conf head, C4 stack, decode, loss_fn all untouched. (3) export_onnx: export fp32 to <stem>_fp32_debug.onnx, then onnxruntime.quantization.quantize_dynamic(QInt8, op_types_to_quantize=['MatMul','Gemm']) to the contract path <out>/models/<area>.onnx; assert no ConvInteger node and size <= 4 MiB. model/train.py unchanged. Conclusion at the time REVERTED — metric did not improve (798.53 m vs best 797.91 m); change discarded Re-measured on today's ruler Mission score 1.618 — 3.2% of held-out frames give a usable fix, 65.0% are confident and wrong, 31.8% abstain. Median miss 1.11 km. Measured, not converted: its exported model was re-run through the current scorer for this page. What it cost
by model — claude-fable-5 $9.64 · claude-sonnet-5 $0.60 · claude-haiku-4-5-20251001 $0.00 The agent lines are the equivalent API cost of the tokens each agent used, as recorded by the harness when it ran. The research ran on a claude.ai Max subscription, so that is what it would have cost billed per token — not money that left the account. The GPU line is real spend. What it was measured against then Optimised: worst-case median error (m), target 20 m, scoring 798.5 m. Asked over: region holdout, 6 lighting buckets, 4 areas. The long grind: 62 experiments over ten days against a general-purpose, four-area, six-lighting-bucket problem. Scored on a region holdout that asked a memorization model to locate ground it had never been shown. Scoreboard — re-scored today Produced by running this experiment's exported model through the current frozen scorer on the current eval set. It is not what this experiment was told at the time.
era Four areas, six lighting buckets · ts 2026-07-22T03:17:36 ·
commit 7e6fb3ea1ed9 · artifacts runs/20260722_020725_iter2 ·
took 70 m 11 s
The code for this experiment — not retainedSource not retained. Until 2026-07-31 the loop discarded a reverted experiment’s code outright, and this row predates that fix. Its design record, its verdict and its exported model survive; the code does not. It is left blank rather than filled with the parent’s code, which is what this row’s recorded commit actually contains. | |||||||||||||||||||||||||||
| ▸ | 2.28 | Rerun, never scored: unseen-ground confidence calibration + reinstated 3x trunk (iter 7 died on a full disk before training)
83bf925a
The loop discarded this — no improvement under the metric then in force. |
training | 1.568 | 57 m 43 s | $5.51 | DISCARDED | ||||||||||||||||||||
|
The design under test — technical diagram — gray = frozen contract · ink = the current design · red = this experiment's change In plain words Two rounds ago we gave the model a bigger brain and its guesses genuinely got better — but the result was thrown out on a technicality: the bar for when the model may say 'no fix, not sure' was tuned on places it had already visited, and the bigger brain is so much more confident on visited ground that the bar landed wildly wrong for new ground — it went nearly silent on the exam. Last round we designed the honest fix — fence off a slice of the map the model never studies and tune the bar there — but the computer's hard drive filled up before training even began, so that experiment was marked 'failed' without ever actually running. The disk problem is fixed; this round simply runs the same experiment for real. Hypothesis Exp 27's recorded 1e9 is an infrastructure artifact, not a refutation: its train logs end in OSError [Errno 28] No space left on device inside prepare_realizations of the FIRST area (berlin), before a single optimizer step — metrics.json has an empty areas list and timings.json shows score_s=0, train_wall_s=158. The disk was filled by ~5.7 GB/iteration of per-area training scratch that earlier iterations never purged; the harness fix ('Never persist per-area training scratch') landed right after, and the volume now has 42 GB free against a ~6 GB per-run scratch need, so the crash cause is removed. The scientific hypothesis is therefore still the untested, pre-registered exp-27 one, restated: exp 26's 3x-capacity pretrained trunk delivered real accuracy gains (like-for-like medians down 20-35%: munich morning 536->347 m, frankfurt evening 798->466 m, prignitz morning 450->350 m; deployment gates passed at 3.09 MB / 4.6 ms) but was vetoed solely by the coverage gate, because calibrate_conf_shift tunes the abstention threshold on TRAIN-visited ground: the bigger trunk sharpened the confidence head's separation between visited and unseen ground, conf_shift leapt from 0.2-0.7 to 2.5-3.6 at identical calibration-time keep rates, and eval coverage collapsed to 0.08-0.43 (8 of 24 cells under the 0.2 floor). Calibrating on train blocks fenced off from every training draw measures the confidence distribution the scorer actually faces, so the 0.40 keep floor transfers to eval and the real capacity gain becomes scoreable. Precedent for this rerun: exp 24 retested exp 23 after it was OOM-killed mid-harness and never scored — an unmeasured pre-registered design is not a refuted one. Method Verbatim re-registration of exp 27's pre-registered design — no design delta; the only thing that changed between iter 7 and now is the harness disk fix. ONE new mechanism in model/train.py — block-held-out confidence calibration: deterministically hash ~1 in 10 train-split blocks (stable_hash, per area) into a calibration-only set; the per-epoch fresh-draw sampler uses only crops whose 182-px windows touch no calibration block (mirror of the frozen eval-buffer rule, so no calibration pixel is ever trained on); calibrate_conf_shift draws its 400 crops per bucket exclusively from calibration-block crops. MIN_KEEP_RATE=0.40, CAL_CROPS_PER_BUCKET=400, losses, schedule, sampler mechanics unchanged. Alongside, model/model.py reinstates exp 26's pre-registered architecture verbatim (MobileNetV3-Small features[:11] with stride/dilation surgery preserving the 8×8 grid; int8 dynamic quantization of MatMul/Gemm at export), which was refuted only through the calibration artifact this experiment removes. Conclusion at the time REVERTED — metric did not improve (1057.68 m vs best 797.91 m); change discarded Re-measured on today's ruler Mission score 1.568 — 8.5% of held-out frames give a usable fix, 65.2% are confident and wrong, 26.2% abstain. Median miss 297.1 m. Measured, not converted: its exported model was re-run through the current scorer for this page. What it cost
by model — claude-fable-5 $3.58 · claude-sonnet-5 $1.26 · claude-haiku-4-5-20251001 $0.00 The agent lines are the equivalent API cost of the tokens each agent used, as recorded by the harness when it ran. The research ran on a claude.ai Max subscription, so that is what it would have cost billed per token — not money that left the account. The GPU line is real spend. What it was measured against then Optimised: worst-case median error (m), target 20 m, scoring 1.06 km. Asked over: region holdout, 6 lighting buckets, 4 areas. The long grind: 62 experiments over ten days against a general-purpose, four-area, six-lighting-bucket problem. Scored on a region holdout that asked a memorization model to locate ground it had never been shown. Scoreboard — re-scored today Produced by running this experiment's exported model through the current frozen scorer on the current eval set. It is not what this experiment was told at the time.
era Four areas, six lighting buckets · ts 2026-07-22T02:07:02 ·
commit 83bf925a5c35 · artifacts runs/20260722_010919_iter1 ·
took 57 m 43 s
The code for this experiment — not retainedSource not retained. Until 2026-07-31 the loop discarded a reverted experiment’s code outright, and this row predates that fix. Its design record, its verdict and its exported model survive; the code does not. It is left blank rather than filled with the parent’s code, which is what this row’s recorded commit actually contains. | |||||||||||||||||||||||||||
| ▸ | 2.27 | Unseen-ground confidence calibration unlocks the reinstated 3×-capacity trunk
cc3faa2c
Failed a deployment gate: too large, too slow, or it abstained on too many frames to be scoreable. |
training | — | 14 m 55 s | $6.71 | GATED FAIL | ||||||||||||||||||||
|
The design under test — technical diagram — gray = frozen contract · ink = the current design · red = this experiment's change In plain words Last round we gave the model a bigger brain and its guesses genuinely got better — but the result was thrown out on a technicality. The model is allowed to say 'no fix' when unsure, and the rules demand it answers at least 20% of the time. We tune its shyness bar using places from its own training tour, and the bigger brain became so much more confident about places it had already visited than about new ones that the bar landed wildly wrong for new ground: it went nearly silent on the exam. The fix is to fence off a slice of the map the model never gets to study and tune the shyness bar there — on ground exactly as unfamiliar as the exam's. Same bigger brain, bar set honestly. Hypothesis Exp 26's capacity scaling delivered its predicted accuracy gain but was vetoed solely by the coverage gate: its deployment gates passed (3.09 MB, 4.6 ms) and its like-for-like medians fell 20–35% (munich morning 536→347 m, frankfurt evening 798→466 m, prignitz morning 450→350 m), yet 8 of 24 cells dropped below the 0.2 coverage floor → 1e9. This iteration's forensics isolate why: calibrate_conf_shift sets the abstention threshold on TRAIN-split crops at locations the model trained on, and the 3× trunk sharpened the confidence head's separation between trained-on and unseen ground — calibration-time keep rates were IDENTICAL between iter 5 and iter 6 (0.40–0.88 per bucket) while conf_shift leapt from 0.2–0.7 to 2.5–3.6 and eval coverage collapsed from 0.25–0.67 to 0.08–0.43. A threshold that keeps 40% of visited ground kept only 8–19% of held-out ground. Calibrating instead on train blocks fenced off from every training draw measures the confidence distribution the scorer actually faces, so the 40% keep floor transfers to eval and the real capacity gain becomes scoreable. (The same mechanism also explains exp 25's near-floor night coverage of 0.25–0.31 — this fix de-risks every future capacity increase, not just this one.) Method ONE new mechanism in model/train.py — block-held-out confidence calibration: deterministically hash ~1 in 10 train-split blocks (stable_hash, per area) into a calibration-only set; the per-epoch fresh-draw sampler uses only crops whose 182-px windows touch no calibration block (mirror of the frozen eval-buffer rule, so no calibration pixel is ever trained on); calibrate_conf_shift draws its 400 crops per bucket exclusively from calibration-block crops. MIN_KEEP_RATE=0.40, CAL_CROPS_PER_BUCKET=400, losses, schedule, sampler mechanics unchanged. Alongside, model/model.py reinstates exp 26's pre-registered architecture verbatim (MobileNetV3-Small features[:11] with stride/dilation surgery preserving the 8×8 grid; int8 dynamic quantization of MatMul/Gemm at export), which was refuted only through the calibration artifact this experiment removes. Conclusion at the time REVERTED — metric did not improve (1000000000.0 m vs best 797.91 m); change discarded Not on today's ruler It failed a deployment gate, so there was no working model to score then and none to re-score now. What it cost
by model — claude-fable-5 $5.38 · claude-sonnet-5 $1.16 · claude-haiku-4-5-20251001 $0.00 The agent lines are the equivalent API cost of the tokens each agent used, as recorded by the harness when it ran. The research ran on a claude.ai Max subscription, so that is what it would have cost billed per token — not money that left the account. The GPU line is real spend. What it was measured against then Optimised: worst-case median error (m), target 20 m, scoring —. Asked over: region holdout, 6 lighting buckets, 4 areas. The long grind: 62 experiments over ten days against a general-purpose, four-area, six-lighting-bucket problem. Scored on a region holdout that asked a memorization model to locate ground it had never been shown. era Four areas, six lighting buckets · ts 2026-07-21T23:34:48 ·
commit cc3faa2c230b · artifacts runs/20260721_231953_iter7 ·
took 14 m 55 s
The code for this experiment — not retainedSource not retained. Until 2026-07-31 the loop discarded a reverted experiment’s code outright, and this row predates that fix. Its design record, its verdict and its exported model survive; the code does not. It is left blank rather than filled with the parent’s code, which is what this row’s recorded commit actually contains. | |||||||||||||||||||||||||||
| ▸ | 2.26 | Deployment-envelope capacity scaling: 3× pretrained trunk depth paid for by int8 FC-head storage
4fb65e2a
Failed a deployment gate: too large, too slow, or it abstained on too many frames to be scoreable. |
architecture | — | 69 m 41 s | $10.18 | GATED FAIL | ||||||||||||||||||||
|
The design under test — technical diagram — gray = frozen contract · ink = the current design · red = this experiment's change In plain words Last round we gave the model three times longer to study and it finished everything it could hold — yet scores barely moved. That means its brain is full: it isn't failing to study, it's out of room. The drone's flight computer caps the model file at 4 MB and we were already near the cap, but we found that the biggest part of the file — a huge table of connection strengths — works just as well stored as cheap 1-byte numbers instead of exact 4-byte ones (we measured: answers move by ~7 meters on errors of ~700). The space that trick frees pays for a three-times-bigger image-understanding brain, borrowed from the same well-trained library the model already uses. Hypothesis The binding constraint is model capacity, demonstrated by exp 25's own pre-registered fork: convergence-scaled training drove the loss to a genuine plateau (berlin 6.29→4.78, flat for the last 5 epochs at near-zero LR; prignitz 4.51) yet the metric moved only 5.2% (839.12→797.91 m) against the 10–25% predicted if optimization were binding — and train-split and eval-split unfiltered medians are now EQUAL, so the model cannot even fit its training locations. A converged, non-memorizing, underfitting model has exactly one lever left: make it bigger. The only prior capacity experiment (exp 8, reverted) scaled a FROM-SCRATCH encoder in the pre-pretrained era; exp 11 then showed pretrained features are what carry this task, and pretrained capacity has never been scaled since — the trunk has sat at 190k params (MobileNetV3-Small features[:9]) for 15 iterations because the 4 MiB deployment gate appeared full (3.30 MB). Fresh probes (runs/_probe_iter6_quant2.py, _probe_iter6_export.py) break that impasse: int8 dynamic quantization of ONLY the FC heads (which hold 727k of the 823k params) is metrically inert on the current best model — median decode shift 0.001 uv ≈ 7 m against 650–800 m medians, conf shift 0.006, latency fine — while shrinking the export 3.30→1.44 MB. The freed bytes exactly pay for tripling the pretrained trunk: features[:9]→[:11] (190k→576k ImageNet-pretrained params, 48→96 output channels), with block 9's stride-2 depthwise run at stride 1 and block 10's depthwise dilated 2 (the standard DeepLab trick) so the 8×8 feature grid every kept head reads is preserved and all pretrained weights load with unchanged shapes. Dry-run export of the full candidate: 3,091,446 bytes and 4.8 ms single-thread — both frozen gates pass with margin. Method model/model.py: (a) trunk builder loads MobileNetV3-Small features[:11] from a new pretrained snapshot model/pretrained/mnv3s_features10.pt, sets features[9]'s depthwise stride (2,2)→(1,1) and features[10]'s depthwise dilation→2/padding→4 — output 96×8×8; all heads re-width automatically via the existing shape probe. (b) export_onnx exports fp32 to a temp file then writes the final artifact with onnxruntime quantize_dynamic(op_types=['MatMul','Gemm'], QInt8) — conv layers stay fp32. model/train.py: only the init-strategy log string changes. Sampler, losses, decode, calibration, EPOCH_MULT=3, cosine schedule: all untouched; the two new pretrained blocks land in the existing 1e-4 trunk LR group automatically. Conclusion at the time REVERTED — metric did not improve (1000000000.0 m vs best 797.91 m); change discarded Not on today's ruler It failed a deployment gate, so there was no working model to score then and none to re-score now. What it cost
by model — claude-fable-5 $8.69 · claude-sonnet-5 $0.68 · claude-haiku-4-5-20251001 $0.00 The agent lines are the equivalent API cost of the tokens each agent used, as recorded by the harness when it ran. The research ran on a claude.ai Max subscription, so that is what it would have cost billed per token — not money that left the account. The GPU line is real spend. What it was measured against then Optimised: worst-case median error (m), target 20 m, scoring —. Asked over: region holdout, 6 lighting buckets, 4 areas. The long grind: 62 experiments over ten days against a general-purpose, four-area, six-lighting-bucket problem. Scored on a region holdout that asked a memorization model to locate ground it had never been shown. era Four areas, six lighting buckets · ts 2026-07-21T23:18:46 ·
commit 4fb65e2a19d4 · artifacts runs/20260721_220905_iter6 ·
took 69 m 41 s
The code for this experiment — not retainedSource not retained. Until 2026-07-31 the loop discarded a reverted experiment’s code outright, and this row predates that fix. Its design record, its verdict and its exported model survive; the code does not. It is left blank rather than filled with the parent’s code, which is what this row’s recorded commit actually contains. | |||||||||||||||||||||||||||
| ▸ | 2.25 | Convergence-scaled training: 3× optimizer steps with cosine LR decay under the kept fresh-draw sampler
7f8a2986
The loop kept this — it beat the running best under the metric then in force. On today's ruler it scores 1.623 — worse than silence. |
training | 1.623 | 63 m 08 s | $9.15 | KEPT | ||||||||||||||||||||
|
The design under test — technical diagram — gray = frozen contract · ink = the current design · red = this experiment's change In plain words The previous improvement gave the model fresh flashcards every study pass so it could no longer cram answers — but we kept ringing the end-of-class bell at the same early time, set back when cramming made classes short. Its grades were still climbing when the bell rang. This experiment simply lets it study three times longer, and — like slowing down smoothly before parking — makes each learning step gentler toward the end so what it learned settles in place. The model that flies is completely unchanged; only the length and pacing of school changes. Hypothesis The binding constraint is no longer the held-out-location generalization gap — it is plain underfitting, created as a side effect of kept exp 20 and never addressed because the training schedule has been frozen since bootstrap. Evidence from a fresh read-only probe of the kept exp-20 model (runs/_probe_iter5_evalcells.py): the ~3× train-vs-eval gap that exps 18/19/22/23/24 all attacked is GONE — berlin midday train-split 1014 m vs eval-split 1017 m unfiltered median, prignitz midday 580 vs 673 m (pre-exp-20 probes measured 267–400 m on train). Exp 20's single-exposure fresh draws removed memorization as a fitting strategy, which turned the 8-epoch constant-LR budget — sized in the bootstrap era, when 8 passes over one frozen tensor sufficed to memorize — into a truncation: the training loss is still descending ~0.065/epoch at cutoff (berlin 6.29→5.11 over 8 epochs, no plateau) at a never-decayed Adam LR (head 1e-3, trunk 1e-4), and unfiltered errors are flat-mediocre on BOTH splits (train-split medians 580–1889 m) — the signature of an underconverged model, not a transfer failure. This also reinterprets the three consecutive reverts (22, 23/24): their auxiliary losses competed for the same ~4,500 undertrained optimizer steps, diluting an optimization budget that was already the bottleneck. Per the plateau rule this leaves that mechanism family entirely for training-scale — the one plateau-list family never tried (exp 7 scaled data, never optimization). The same probe also tested and rejected an output-side suspicion: the decode does NOT structurally avoid the never-trained eval-block cells (P(decode in eval block) = 0.13–0.21 vs 0.20 base rate), so a head redesign is not the leading term. The change: 3× the optimizer steps (24 fresh-draw epochs from the harness's --epochs 8) with per-step cosine LR annealing to zero, so the kept sampler is finally trained to convergence — near-full coverage of the ~45k train locations per bucket, each still seen only ~1–2×, and a settled endpoint instead of a noisy constant-LR stop. Method model/train.py ONLY (model/model.py untouched — inference path, losses, decode, calibration, export all identical). (1) Module constant EPOCH_MULT = 3; train_area runs total_epochs = epochs × EPOCH_MULT sampled epochs (harness passes --epochs 8 → 24), with the per-epoch fresh-draw sample_epoch and the epoch_rng = default_rng([seed, epoch]) formula unchanged — epochs 8..23 automatically get fresh independent draws. (2) torch.optim.lr_scheduler.CosineAnnealingLR stepped once per optimizer step, T_max = precomputed total step count (6 buckets × min(max_crops_per_bucket, n_train_crops) crops/epoch, batch 64, × total_epochs), eta_min 0 — both param groups (trunk 1e-4, head 1e-3) anneal proportionally. Per-epoch memory profile is byte-identical to the kept baseline (sample_epoch untouched — the exp-23 OOM lesson); wall time ≈3× (~19 → ~58 min for the 4 parallel areas). Conclusion at the time KEPT — metric improved (839.12 -> 797.91 m); change committed as 7f8a298 Re-measured on today's ruler Mission score 1.623 — 3.5% of held-out frames give a usable fix, 65.8% are confident and wrong, 30.8% abstain. Median miss 1.36 km. Measured, not converted: its exported model was re-run through the current scorer for this page. What it cost
by model — claude-fable-5 $7.89 · claude-sonnet-5 $0.52 · claude-haiku-4-5-20251001 $0.00 The agent lines are the equivalent API cost of the tokens each agent used, as recorded by the harness when it ran. The research ran on a claude.ai Max subscription, so that is what it would have cost billed per token — not money that left the account. The GPU line is real spend. What it was measured against then Optimised: worst-case median error (m), target 20 m, scoring 797.9 m. Asked over: region holdout, 6 lighting buckets, 4 areas. The long grind: 62 experiments over ten days against a general-purpose, four-area, six-lighting-bucket problem. Scored on a region holdout that asked a memorization model to locate ground it had never been shown. Scoreboard — re-scored today Produced by running this experiment's exported model through the current frozen scorer on the current eval set. It is not what this experiment was told at the time.
era Four areas, six lighting buckets · ts 2026-07-21T22:07:44 ·
commit 7f8a298620e4 · artifacts runs/20260721_210436_iter5 ·
took 63 m 08 s
The code for this experiment — its code, from the commitRead back out of this experiment’s own commit. It was kept, so the loop committed model/ and then recorded that commit — the tree is this experiment’s code. · on GitHub ↗model/model.py251 lines model/train.py258 lines | |||||||||||||||||||||||||||
| ▸ | 2.24 | Daytime-redraw auxiliary, rerun: exp 23 was OOM-killed mid-harness, never scored — retest it with a memory-lean epoch sampler
5794e840
The loop discarded this — no improvement under the metric then in force. |
loss | 1.678 | 42 m 59 s | $7.56 | DISCARDED | ||||||||||||||||||||
|
The design under test — technical diagram — gray = frozen contract · ink = the current design · red = this experiment's change In plain words Last iteration we designed a promising exercise for the network: from its internal impression of a dark, noisy night photo, it must also sketch what that exact spot looks like in clean daylight — like a student proving they truly saw through the darkness by redrawing the street in daytime colors. The sketching module is thrown away before anything flies, so the aircraft carries nothing extra. But the experiment never actually ran: the training computer ran out of memory and the operating system silently killed one of the four training jobs, so the scoreboard recorded a bogus worst-possible number. This iteration re-runs the exact same exercise after fixing the memory waste (the training data was being copied twice for no reason) — now the whole thing uses LESS memory than the setup that has worked for weeks, and the idea finally gets its fair test. Hypothesis Exp 23's hypothesis was never tested, and this iteration retests it rather than abandoning an untried mechanism. The forensic record is unambiguous: loop.sh line 203 logged 'Killed' (SIGKILL) for the berlin training process, berlin's log is 0 bytes with no model exported, while prignitz/munich/frankfurt trained to completion under the identical code with healthy final losses (4.78-5.05, vs 4.77-5.11 in the kept exp-20 run) and normal calibration keep-rates — so the 1e9 in the DB is the scorer's missing-model sentinel, not a measured error. The recorded REVERTED at 1e9 is an infrastructure artifact, and leaving it unchallenged would poison the history: future iterations would read the reconstruction-auxiliary family as refuted when it was merely crashed. Cause of death: the four areas train IN PARALLEL inside a constrained cgroup (loop.sh's own comment cites the 'small cgroup quota'), and the kept sampler already peaks at ~3.6 GB transient per process (a 36k-crop uint8 list ~1.77 GB plus its np.stack copy ~1.77 GB); exp 23's implementation added a per-process reference raster (~0.14 GB) and a 36k x 64x64x3 uint8 target list plus ITS stack copy (~0.9 GB), pushing four concurrent processes over the quota — the kernel OOM-killer shot the first process (berlin), which freed enough memory for the remaining three to finish, exactly the observed pattern. A SIGKILLed python loses its ~1 KB of block-buffered prints, which is why the log is empty. The scientific hypothesis is unchanged from exp 23 (library L1, flavor B): the binding constraint is the held-out-location generalization gap (train-split 267-400 m vs eval-split 708-1153 m median, ~3x, every bucket), the trunk still passes lighting nuisance into the descriptor because its ImageNet features were never adapted to the sim's night statistics, and a training-only decoder that must redraw the clean daytime reference crop (pixel-aligned pairs free from the frozen sim; same cx/cy/angle) from the encoder's 8x8x48 feature grid can only succeed via features that cancel lighting and noise while preserving ground structure — canonicalization inside the encoder at zero inference cost, location-generic by construction (unlike reverted exp 22's per-patch place labels) and structure-preserving (unlike refuted exp 9's collapsible contrastive objective). The one new engineering fact this rerun adds: the epoch sampler is rewritten to preallocate its uint8 tensors and fill them in place, eliminating the list+stack doubling — peak sampler RSS drops to ~2.4 GB per process INCLUDING the new target tensor and reference raster, ~1.2 GB BELOW the kept baseline that has survived 20+ iterations, so the rerun sits inside a proven memory envelope with margin. The preallocation is numerically inert: identical RNG stream, byte-identical training tensors. Method ONE scientific change, identical to exp 23's pre-registration: attach a training-only reconstruction decoder (~55k params; 1x1 conv 48->64, then 3x [nearest-upsample x2 + 3x3 conv + ReLU] 64->48->32->24, final 1x1 conv ->3 + sigmoid; 8x8x48 in, 64x64x3 out) to the identity-turn feature map via a new return_feat flag on TinyLocNet.forward; add LAMBDA_RECON=0.3 * L1(decoder(feat), daytime_target_64px) to the loss; decoder params train at head LR 1e-3 and are discarded before export. Plus the OOM fix that lets it actually run: model/train.py's sample_epoch is rewritten to preallocate np.empty uint8 arrays for crops and daytime targets and fill them by running index (no list-of-arrays, no np.stack copy), loading the daytime reference raster once per run and reusing the already-drawn per-crop angle for the pixel-aligned target (zero new RNG draws). Both epoch prints gain flush=True so a future SIGKILL can no longer leave an empty log. Exported graph, decode, confidence, calibration, sampling distribution, epochs, and LRs are all byte-identical to the current best. Conclusion at the time REVERTED — metric did not improve (915.68 m vs best 839.12 m); change discarded Re-measured on today's ruler Mission score 1.678 — 2.0% of held-out frames give a usable fix, 69.8% are confident and wrong, 28.2% abstain. Median miss 1.63 km. Measured, not converted: its exported model was re-run through the current scorer for this page. What it cost
by model — claude-fable-5 $6.09 · claude-sonnet-5 $0.97 · claude-haiku-4-5-20251001 $0.00 The agent lines are the equivalent API cost of the tokens each agent used, as recorded by the harness when it ran. The research ran on a claude.ai Max subscription, so that is what it would have cost billed per token — not money that left the account. The GPU line is real spend. What it was measured against then Optimised: worst-case median error (m), target 20 m, scoring 915.7 m. Asked over: region holdout, 6 lighting buckets, 4 areas. The long grind: 62 experiments over ten days against a general-purpose, four-area, six-lighting-bucket problem. Scored on a region holdout that asked a memorization model to locate ground it had never been shown. Scoreboard — re-scored today Produced by running this experiment's exported model through the current frozen scorer on the current eval set. It is not what this experiment was told at the time.
era Four areas, six lighting buckets · ts 2026-07-21T21:02:57 ·
commit 5794e8405c98 · artifacts runs/20260721_201958_iter4 ·
took 42 m 59 s
The code for this experiment — not retainedSource not retained. Until 2026-07-31 the loop discarded a reverted experiment’s code outright, and this row predates that fix. Its design record, its verdict and its exported model survive; the code does not. It is left blank rather than filled with the parent’s code, which is what this row’s recorded commit actually contains. | |||||||||||||||||||||||||||
| ▸ | 2.23 | Daytime-redraw auxiliary: a throwaway decoder must reconstruct the clean daytime crop from the encoder's features
adfc8b91
Failed a deployment gate: too large, too slow, or it abstained on too many frames to be scoreable. |
loss | — | 42 m 08 s | $6.88 | GATED FAIL | ||||||||||||||||||||
|
The design under test — technical diagram — gray = frozen contract · ink = the current design · red = this experiment's change In plain words The network learns to recognize places from noisy night photos. We gave it one extra practice exercise: from its internal impression of a scene, it must also sketch what that exact spot looks like in clean daylight — like a student proving they truly saw through the darkness by redrawing the street in daytime colors. The sketching module is thrown away before anything flies, so the aircraft carries nothing extra. What remains is a network whose mental image of the ground ignores darkness and sensor grain — and if its inner picture is always the daytime one, recognizing the place gets easier in every lighting condition at once. Hypothesis The binding constraint is still the held-out-location generalization gap the exp-18 probe measured (train-split crops 267-400 m median vs eval-split 708-1153 m, ~3x, in every bucket), and after exp 20 the error profile is flat across lighting buckets and areas — flat but mediocre (best 839.12 m vs the ~20 m target). The trunk is the shared weak link that flatness points at: every gradient it receives comes from ONE whole-crop place grade per crop at a gentle 1e-4 LR, while its ImageNet-pretrained features were never adapted to the sim's night-time statistics (sensor gain noise, lamp thinning, washed-out color are far outside ImageNet's input distribution). So lighting nuisance still reaches the descriptor, and the 1024-way head must generalize jointly across location x lighting with only ~30k single-exposure examples (exp 20) to do it. This experiment builds on library entry L1, flavor B (training-only reconstruction auxiliary): a small throwaway decoder must redraw the clean daytime reference crop — same location, same heading, pixel-aligned pairs free from the frozen sim — from the encoder's 8x8x48 feature grid, with an L1 loss at 64x64 weighted 0.3, then be deleted before export. All six lighting renders of a place share ONE daytime answer, so the cheapest way down this loss is features that cancel lighting and noise while preserving ground structure: canonicalization inside the encoder, at zero inference cost. Two mechanisms should move the Sec-6 metric: (a) descriptor variance across buckets collapses, so the head's capacity and exp 20's fresh location draws concentrate on place discrimination alone instead of place x lighting; (b) unlike reverted exp 22, whose dense auxiliary was per-patch PLACE classification — 64 more memorization targets of exactly the kind the transfer gap punishes — night-to-day redrawing is location-generic (the same inversion applies everywhere on the map, and under exp 20's sampling no location is seen often enough to memorize its output), so its gradients transfer to never-trained eval terrain by construction. It is also materially different from refuted exp 9 (cross-lighting NT-Xent): that was a contrastive ranking objective on the GLOBAL descriptor of the old from-scratch encoder, satisfiable by collapsing information; a dense generative target must PRESERVE structure per pixel, and it acts on today's pretrained trunk. Method ONE change: attach a training-only reconstruction decoder to the encoder. model/model.py gains ReconDecoder (~55k params: 1x1 conv 48->64 then 3x [nearest-upsample x2 + 3x3 conv + ReLU] 64->48->32->24, final 1x1 conv ->3 + sigmoid; 8x8x48 in, 64x64x3 out), build_recon_decoder(), constants RECON_SIZE=64 / LAMBDA_RECON=0.3, and an optional return_feat flag on TinyLocNet.forward exposing the identity-turn feature map. model/train.py loads the daytime reference raster once per area, samples for every training crop the pixel-aligned clean reference crop (same cx/cy/angle) downsampled to 64x64, and adds LAMBDA_RECON * L1(decoder(feat), target) to the existing loss. Decoder params train at the head LR (1e-3) and are discarded — never exported. Everything else (architecture, decode, confidence, calibration, sampling, epochs, LRs, ONNX export) is untouched; the exported graph is byte-identical in shape to the current best. Conclusion at the time REVERTED — metric did not improve (1000000000.0 m vs best 839.12 m); change discarded Not on today's ruler It failed a deployment gate, so there was no working model to score then and none to re-score now. What it cost
by model — claude-fable-5 $5.65 · claude-sonnet-5 $0.74 · claude-haiku-4-5-20251001 $0.00 The agent lines are the equivalent API cost of the tokens each agent used, as recorded by the harness when it ran. The research ran on a claude.ai Max subscription, so that is what it would have cost billed per token — not money that left the account. The GPU line is real spend. What it was measured against then Optimised: worst-case median error (m), target 20 m, scoring —. Asked over: region holdout, 6 lighting buckets, 4 areas. The long grind: 62 experiments over ten days against a general-purpose, four-area, six-lighting-bucket problem. Scored on a region holdout that asked a memorization model to locate ground it had never been shown. era Four areas, six lighting buckets · ts 2026-07-21T20:18:28 ·
commit adfc8b919d19 · artifacts runs/20260721_193619_iter3 ·
took 42 m 08 s
The code for this experiment — not retainedSource not retained. Until 2026-07-31 the loop discarded a reverted experiment’s code outright, and this row predates that fix. Its design record, its verdict and its exported model survive; the code does not. It is left blank rather than filled with the parent’s code, which is what this row’s recorded commit actually contains. | |||||||||||||||||||||||||||
| ▸ | 2.22 | Train-only per-patch place supervision: every feature cell places its own patch, the flying decode is untouched
9df5c839
The loop discarded this — no improvement under the metric then in force. |
loss | 1.696 | 40 m 44 s | $8.67 | DISCARDED | ||||||||||||||||||||
|
The design under test — technical diagram — gray = frozen contract · ink = the current design · red = this experiment's change In plain words Until now the model got one graded question per practice photo: 'where is the CENTER of this photo on the map?' A student can pass that test by memorizing whole photos -- which fails on parts of the city held back from practice. Now, during practice only, every small tile of the photo also gets its own graded question: 'where does THIS tile sit on the map?' -- 64 grades per photo instead of one. To score well the network must learn what roads, buildings and field edges actually look like from above, knowledge that transfers to never-practiced places. On the real flight nothing changes: the extra quiz machinery is thrown away before takeoff and the aircraft carries exactly the same network as before. Hypothesis The binding constraint is still the held-out-location generalization gap the exp-18 probe measured (train-split crops 267-400 m median vs eval-split 708-1153 m, ~3x, in every bucket), and after exp 20 the error profile is flat across lighting and areas (metros 656-839 m, prignitz 449-539 m; night is no longer the worst bucket anywhere) -- lighting is solved as a differentiator, transfer to unseen terrain is the whole game. Exp 20 removed repetition-driven whole-crop memorization on the DATA side (kept, 1054.87 -> 839.12 m). The remaining untried side is the SUPERVISION density: the trunk's features are trained through exactly one grade per crop -- a single whole-crop Gaussian-CE at the crop centre -- so nothing ever demands that each local feature column be location-predictive on its own; the cheapest fit is whole-crop gestalt, which the probe shows failing to transfer. Exps 4 and 18 both made per-patch localization the INFERENCE mechanism and were reverted; their joint lesson (recorded in exp 19's design) is that the whole-crop arrangement signal must stay intact at decode time -- but neither ever tested per-patch supervision as a training-only regularizer under the kept whole-crop decode, which is standard deep-supervision practice and is exactly the configuration their failures leave open. A dense auxiliary head (one shared 1x1 conv 48->1024 on the identity-turn feature grid) that makes each of the 64 feature columns predict its OWN Gaussian-CE map field at its patch's true ground position multiplies the supervision 64x per crop, and -- combined with exp 20's single-exposure sampling, where a location is seen only ~1-2 times -- can only be satisfied by local cues that transfer between locations. At eval, ~76% of eval crops contain SOME train-seen terrain at a known offset (exp 19's geometry analysis); locally location-predictive feature columns put that partial familiarity into the 8x8 layout code the kept head already reads spatially -- delivering exp 18's intended benefit through the decode that works, instead of the per-patch fusion that failed. Method model/model.py: add aux_patch_head = nn.Conv2d(48, GRID_K*GRID_K, 1) and a forward_train() that returns (out, logits, aux_logits) where aux_logits [N,1024,8,8] is the aux head applied to the identity-rotation slice of the trunk feature map; forward() and the ONNX export are byte-for-byte unchanged (the aux head is not traced, so exported size stays 3.30 MiB). Add aux_patch_loss(): per-patch Gaussian-CE (same GRID_K/TARGET_SIGMA_CELLS formula as loss_fn) against each patch's true map uv. model/train.py: sample_epoch additionally returns per-crop patch targets [N,64,2] computed as (cx,cy) + R(angle) @ d for each feature cell's crop-plane offset d = ((j+0.5)*16-64, (i+0.5)*16-64) px, with the numerically verified rotation R(angle) = [[cos,-sin],[sin,cos]] (angle in degrees exactly as passed to extract_crop; verified against extract_crop at 0/90/30/217 deg). Training loss becomes loss_fn(...) + 0.5 * aux_patch_loss(...). Nothing else changes: same epochs, sampling, calibration, export. Conclusion at the time REVERTED — metric did not improve (1125.53 m vs best 839.12 m); change discarded Re-measured on today's ruler Mission score 1.696 — 2.0% of held-out frames give a usable fix, 71.5% are confident and wrong, 26.5% abstain. Median miss 1.47 km. Measured, not converted: its exported model was re-run through the current scorer for this page. What it cost
by model — claude-fable-5 $6.78 · claude-sonnet-5 $1.42 · claude-haiku-4-5-20251001 $0.00 The agent lines are the equivalent API cost of the tokens each agent used, as recorded by the harness when it ran. The research ran on a claude.ai Max subscription, so that is what it would have cost billed per token — not money that left the account. The GPU line is real spend. What it was measured against then Optimised: worst-case median error (m), target 20 m, scoring 1.13 km. Asked over: region holdout, 6 lighting buckets, 4 areas. The long grind: 62 experiments over ten days against a general-purpose, four-area, six-lighting-bucket problem. Scored on a region holdout that asked a memorization model to locate ground it had never been shown. Scoreboard — re-scored today Produced by running this experiment's exported model through the current frozen scorer on the current eval set. It is not what this experiment was told at the time.
era Four areas, six lighting buckets · ts 2026-07-21T19:34:42 ·
commit 9df5c83968e9 · artifacts runs/20260721_185358_iter2 ·
took 40 m 44 s
The code for this experiment — not retainedSource not retained. Until 2026-07-31 the loop discarded a reverted experiment’s code outright, and this row predates that fix. Its design record, its verdict and its exported model survive; the code does not. It is left blank rather than filled with the parent’s code, which is what this row’s recorded commit actually contains. | |||||||||||||||||||||||||||
| ▸ | 2.21 | Periodic blind holdout check (hamburg)
cac6ec61
Blind Hamburg holdout check. Logged for honesty, never allowed to drive keep/revert. |
other | — | — | — | HOLDOUT | ||||||||||||||||||||
|
Hypothesis If the pipeline is genuinely generic, holdout error should track the development worst-case. Method Train current best model code on hamburg, score read-only (§5). Not on today's ruler A blind Hamburg holdout check (§5). It measures a different area, so no Berlin mission score exists for it. What it cost No agent accounting survives for this experiment — its run directory was deleted. That is a missing receipt, not a zero. What it was measured against then Optimised: worst-case median error (m), target 20 m, scoring 714.0 m. Asked over: region holdout, 6 lighting buckets, 4 areas. The long grind: 62 experiments over ten days against a general-purpose, four-area, six-lighting-bucket problem. Scored on a region holdout that asked a memorization model to locate ground it had never been shown. era Four areas, six lighting buckets · ts 2026-07-21T18:51:51 ·
commit cac6ec613fa9 · artifacts runs/20260721_175707_iter1/holdout ·
took —
The code for this experiment — not retainedSource not retained. Until 2026-07-31 the loop discarded a reverted experiment’s code outright, and this row predates that fix. Its design record, its verdict and its exported model survive; the code does not. It is left blank rather than filled with the parent’s code, which is what this row’s recorded commit actually contains. | |||||||||||||||||||||||||||
| ▸ | 2.20 | Per-epoch training-set resampling: a fresh 6,000-location draw per bucket every epoch replaces the one frozen 36k-crop tensor
cac6ec61
The loop kept this — it beat the running best under the metric then in force. On today's ruler it scores 1.683 — worse than silence. |
training | 1.683 | 35 m 33 s | $7.80 | KEPT | ||||||||||||||||||||
|
The design under test — technical diagram — gray = frozen contract · ink = the current design · red = this experiment's change In plain words Until now the model studied the exact same 36,000 flashcards eight times over, so it started remembering individual cards instead of learning to read terrain. Cards it had memorized it could place well, but over ground it had never studied -- which is exactly what the test uses -- it was three times worse. Now it gets a brand-new deck drawn from the whole training area on every pass: same study time, eight times more distinct places, each seen only once or twice. Remembering single cards stops working, so the only strategy that pays off is learning what actually makes a place recognizable -- the skill it needs over terrain it has never seen. Hypothesis The binding constraint is the ~3x held-out-location gap the exp-18 probe measured directly: train-split crops localize to 267-400 m median while eval-split crops sit at 708-1153 m, in every lighting bucket (the per-bucket error profile of the kept model is now flat, so lighting is no longer the leading term). Exps 18 and 19 both attacked this gap by making the model handle partially-familiar crops -- one architecturally (64 per-patch votes; discarded the global arrangement signal, 1135.85 m) and one by corrupting training content (off-site distractor patches; made training strictly harder, 1241.24 m) -- and both were reverted. Jointly they say: the whole-crop arrangement signal must stay intact and the training content clean. The third, untouched axis is repetition. The kept trainer builds ONE tensor -- 6,000 locations per bucket, each at one frozen heading and one frozen relight realization -- and passes over it 8 times. Eight identical passes over 6k fixed templates per bucket, with a 573k-param 1024-way FC head on top, make per-crop memorization the cheapest way down the training loss, and memorized templates are exactly what the probe shows failing to transfer; meanwhile the other ~39k enumerable train locations per bucket are never seen at all. Redrawing the 6,000 locations, their headings, and their realization assignment fresh at the start of every epoch keeps per-epoch cost and total gradient-step count identical while raising distinct locations seen to ~30k per bucket (1-(1-0.133)^8 ~= 68% of ~45k), each seen only ~1-2 times and never twice identically (different heading and often different simulator roll on a repeat). Single-exposure data removes template memorization as an available fitting strategy: only cues that transfer between locations can reduce the loss -- precisely the capability an eval crop over never-trained terrain demands. This is materially distinct from refuted exp 6, which resampled only the ROTATIONS of the same 800 locations on the from-scratch encoder, pre-trunk and pre-C4-vote: heading invariance has been architectural since exp 16; what varies here, and what exp 6 never varied, is location coverage. Method model/model.py is untouched. model/train.py only: (1) a one-time prepare_realizations step renders each bucket's two extra relight realizations with the exact seeds exp 17 introduced and caches them as PNGs under <out-dir>/renders/; (2) the single load_training_tensors call before the epoch loop is replaced by a per-epoch sample_epoch(...) that redraws locations, headings, and realization assignment with a fresh rng seeded [seed, epoch]; the minibatch loop, optimizer/param groups, loss, confidence calibration, and ONNX export are byte-identical. Conclusion at the time KEPT — metric improved (1054.87 -> 839.12 m); change committed as cac6ec6 Re-measured on today's ruler Mission score 1.683 — 2.0% of held-out frames give a usable fix, 70.2% are confident and wrong, 27.8% abstain. Median miss 1.27 km. Measured, not converted: its exported model was re-run through the current scorer for this page. What it cost
by model — claude-fable-5 $6.74 · claude-sonnet-5 $0.65 · claude-haiku-4-5-20251001 $0.00 The agent lines are the equivalent API cost of the tokens each agent used, as recorded by the harness when it ran. The research ran on a claude.ai Max subscription, so that is what it would have cost billed per token — not money that left the account. The GPU line is real spend. What it was measured against then Optimised: worst-case median error (m), target 20 m, scoring 839.1 m. Asked over: region holdout, 6 lighting buckets, 4 areas. The long grind: 62 experiments over ten days against a general-purpose, four-area, six-lighting-bucket problem. Scored on a region holdout that asked a memorization model to locate ground it had never been shown. Scoreboard — re-scored today Produced by running this experiment's exported model through the current frozen scorer on the current eval set. It is not what this experiment was told at the time.
era Four areas, six lighting buckets · ts 2026-07-21T18:32:40 ·
commit cac6ec613fa9 · artifacts runs/20260721_175707_iter1 ·
took 35 m 33 s
The code for this experiment — not retainedSource not retained. Until 2026-07-31 the loop discarded a reverted experiment’s code outright, and this row predates that fix. Its design record, its verdict and its exported model survive; the code does not. It is left blank rather than filled with the parent’s code, which is what this row’s recorded commit actually contains. | |||||||||||||||||||||||||||
| ▸ | 2.19 | Off-site distractor patching: half the training crops carry a pasted block of terrain from elsewhere, labels unchanged
d5080d5d
The loop discarded this — no improvement under the metric then in force. |
augmentation | 1.633 | 42 m 51 s | $9.87 | DISCARDED | ||||||||||||||||||||
|
The design under test — technical diagram — gray = frozen contract · ink = the current design · red = this experiment's change In plain words When the drone flies over ground the model never studied, most of the picture is new to it -- but a corner usually shows land it does know. Until now, every practice picture the model ever saw was 100% familiar ground, so it never learned what to do with a half-strange view: one odd patch throws off its whole answer. We now sabotage half of the practice pictures by pasting in a square from somewhere else in the same city, while keeping the answer key unchanged. To keep scoring well, the model must learn to base its answer on the parts of the picture that fit together and shrug off the parts that don't -- like solving a jigsaw with a few pieces from the wrong box mixed in. Hypothesis The binding constraint is the held-out-location generalization gap the exp-18 probe measured directly: train-split crops localize to 267-400 m median while eval-split crops sit at 708-1153 m -- a ~3x structural gap, present in every lighting bucket. The frozen split's geometry says exactly what an eval crop looks like: its center pixel always lies in a 360-px eval block whose pixels were never trained on (train windows are dropped if they touch an eval block), while ~76% of eval crops still contain SOME train-seen terrain entering from an edge or corner of the 182-px window. So the canonical eval crop is unfamiliar terrain around the center with familiar terrain at the periphery. The kept head is a whole-crop template matcher (GAP + 512-d layout code -> FC 1024), and every single crop it has ever been trained on was 100% familiar terrain -- partial familiarity is literally outside its training distribution, so at eval the unfamiliar portion shifts the descriptor away from every learned template: fields go diffuse (night coverage sits at the abstention floor) or commit to a lookalike. Exp 18 attacked this same gap architecturally (64 per-patch field votes) and was reverted (1135.85 m) -- forcing each 16-px patch to localize alone discarded the global-arrangement signal that carries most of the discrimination. The materially new angle: keep the kept architecture untouched and make partial familiarity the TRAINING condition instead. With probability 0.5 a training crop gets a square block (side 48-96 px, i.e. 14-56% of its pixels, uniform position) overwritten with the same-position block from a different training location of the same lighting bucket, while the label and Gaussian-CE target stay at the base crop's true center. The pasted content is a worst-case stand-in for eval's unfamiliar content -- not merely uninformative but actively misleading, since its texture genuinely belongs elsewhere on the map. The only way to fit patched crops is to score map cells from the subset of content consistent with a single location and down-weight content that disagrees -- precisely the computation an eval crop demands and that nothing in the current training distribution has ever required. The confidence head trains on the same crops, so it simultaneously learns to flag conflicted content -- the exact crops it should abstain on at eval. This is the first input-composition augmentation in the project's history (exp 6, the only prior augmentation experiment, resampled rotations; it never altered crop content). Method One change, entirely inside model/train.py's load_training_tensors: collect each bucket's extracted crops into per-bucket lists; after the three-realization extraction loop, for each crop independently with p=0.5 paste a square block (side ~ uniform int in [48,96] px, top-left uniform over valid positions) copied from the SAME position in a different, pre-patching crop of the same bucket (donors read from a clean snapshot). Labels, Gaussian-CE targets, loss_fn, model.py, decode, confidence calibration, CLI, and ONNX export are all untouched. Conclusion at the time REVERTED — metric did not improve (1241.24 m vs best 1054.87 m); change discarded Re-measured on today's ruler Mission score 1.633 — 1.2% of held-out frames give a usable fix, 64.5% are confident and wrong, 34.2% abstain. Median miss 1.49 km. Measured, not converted: its exported model was re-run through the current scorer for this page. What it cost
by model — claude-fable-5 $8.87 · claude-sonnet-5 $0.50 · claude-haiku-4-5-20251001 $0.00 The agent lines are the equivalent API cost of the tokens each agent used, as recorded by the harness when it ran. The research ran on a claude.ai Max subscription, so that is what it would have cost billed per token — not money that left the account. The GPU line is real spend. What it was measured against then Optimised: worst-case median error (m), target 20 m, scoring 1.24 km. Asked over: region holdout, 6 lighting buckets, 4 areas. The long grind: 62 experiments over ten days against a general-purpose, four-area, six-lighting-bucket problem. Scored on a region holdout that asked a memorization model to locate ground it had never been shown. Scoreboard — re-scored today Produced by running this experiment's exported model through the current frozen scorer on the current eval set. It is not what this experiment was told at the time.
era Four areas, six lighting buckets · ts 2026-07-21T16:19:08 ·
commit d5080d5dc73e · artifacts runs/20260721_153617_iter6 ·
took 42 m 51 s
The code for this experiment — not retainedSource not retained. Until 2026-07-31 the loop discarded a reverted experiment’s code outright, and this row predates that fix. Its design record, its verdict and its exported model survive; the code does not. It is left blank rather than filled with the parent’s code, which is what this row’s recorded commit actually contains. | |||||||||||||||||||||||||||
| ▸ | 2.18 | Dense per-patch field voting: 64 local place votes with learned fusion replace the global 1024-way template head
48e17da6
The loop discarded this — no improvement under the metric then in force. |
architecture | 1.623 | 45 m 05 s | $10.24 | DISCARDED | ||||||||||||||||||||
|
The design under test — technical diagram — gray = frozen contract · ink = the current design · red = this experiment's change In plain words Until now the model looked at the whole photo at once and asked 'which spot in the city does this whole picture remind me of?' -- like recognizing a street from a single glance. Our measurement shows it does that three times better on places it studied than on the gaps in between, which is exactly where it gets tested. Now each of 64 small tiles of the photo votes independently -- 'my little piece of ground belongs THERE' -- and a learned referee counts trustworthy tiles more heavily before merging the votes. Even over unfamiliar ground, a few tiles usually catch a road or building the model does know, and those few good votes can carry the answer. Hypothesis A read-only probe of the last kept model (berlin+prignitz, midday, matched random headings, 300 crops/condition) shows train-split crops localize to 267-400 m median while eval-split crops sit at 708-1153 m: a ~3x held-out-location generalization gap now dominates the metric, and it is structural, not a lighting problem. The current head is a single 560-d whole-crop descriptor feeding a 573k-param 1024-way FC -- it memorizes whole-crop gestalt templates, which cannot fire correctly on terrain whose pixels were never seen (eval blocks are fully excluded from training with a buffer). But the geometry helps: eval blocks are 360 px squares and crop windows are 182 px, so ~76% of eval crops contain SOME train-seen pixels at a known in-crop offset -- and a global-template head has no mechanism to exploit partially recognized content. Dense per-patch voting does exactly that: each of the 8x8 feature-grid cells casts its own full-map probability field for its OWN patch of ground, supervised with its own patch-level target; at inference a learned informativeness softmax weights the 64 per-patch log-fields into one fused field. Patches over recognized terrain vote precisely and carry the answer; unrecognizable patches are down-weighted. Because the patch-offset pattern is symmetric about the crop center, the fused field peaks at the crop center with no heading knowledge needed. Weight sharing (one 1x1 MLP serves all 64 positions) also cuts head capacity from ~573k to ~137k params, so per-location gestalt memorization -- the thing the probe shows failing to transfer -- is architecturally discouraged: local features must become location-predictive everywhere in the crop. This is materially different from reverted exp 4, which regressed per-patch COORDINATES (sigmoid + smooth-L1, mean-of-64 decode) on a from-scratch encoder with 4.8k crops: mean-decoding suffers the centroid-shrinkage disease exp 14 later diagnosed and fixed, and the from-scratch trunk had no local features worth reading. This design keeps every kept mechanism -- pretrained trunk (exp 11), calibrated Gaussian-CE field + beta-sharpened decode (exps 2/14), C4 rotation vote (exp 16), shape-based confidence + calibration (exp 15), three-realization renders (exp 17) -- and changes only who produces the field logits. Method model/model.py: delete layout_squeeze, loc_logits, dark_logits and the luminance gate; add a shared per-position head (1x1 conv 48->128, ReLU, 1x1 conv 128->1024) producing per-patch field logits [*,1024,8,8] and a 1x1 conv 48->1 whose softmax over the 64 positions gives fusion weights; fused per-view logits = sum_p w_p * logits_p; everything downstream (C4 logit mean, sharpened decode, conf head, conf_shift calibration, ONNX export) is unchanged. model/train.py: store each training crop's heading angle next to its (u,v) target; the loss adds a dense term -- each patch's logits get Gaussian-CE against the map cell of that patch's own ground content, computed as crop center + R(heading) . patch offset, on the identity C4 view. Conclusion at the time REVERTED — metric did not improve (1135.85 m vs best 1054.87 m); change discarded Re-measured on today's ruler Mission score 1.623 — 2.5% of held-out frames give a usable fix, 64.8% are confident and wrong, 32.8% abstain. Median miss 1.15 km. Measured, not converted: its exported model was re-run through the current scorer for this page. What it cost
by model — claude-fable-5 $8.58 · claude-sonnet-5 $1.14 · claude-haiku-4-5-20251001 $0.00 The agent lines are the equivalent API cost of the tokens each agent used, as recorded by the harness when it ran. The research ran on a claude.ai Max subscription, so that is what it would have cost billed per token — not money that left the account. The GPU line is real spend. What it was measured against then Optimised: worst-case median error (m), target 20 m, scoring 1.14 km. Asked over: region holdout, 6 lighting buckets, 4 areas. The long grind: 62 experiments over ten days against a general-purpose, four-area, six-lighting-bucket problem. Scored on a region holdout that asked a memorization model to locate ground it had never been shown. Scoreboard — re-scored today Produced by running this experiment's exported model through the current frozen scorer on the current eval set. It is not what this experiment was told at the time.
era Four areas, six lighting buckets · ts 2026-07-21T15:36:03 ·
commit 48e17da6b2cf · artifacts runs/20260721_145057_iter5 ·
took 45 m 05 s
The code for this experiment — not retainedSource not retained. Until 2026-07-31 the loop discarded a reverted experiment’s code outright, and this row predates that fix. Its design record, its verdict and its exported model survive; the code does not. It is left blank rather than filled with the parent’s code, which is what this row’s recorded commit actually contains. | |||||||||||||||||||||||||||
| ▸ | 2.17 | Nuisance-randomized training renders: each bucket's training crops drawn from three seeded realizations of the frozen relighting sim
95b4c3a0
The loop kept this — it beat the running best under the metric then in force. On today's ruler it scores 1.753 — worse than silence. |
relighting | 1.753 | 39 m 10 s | $8.60 | KEPT | ||||||||||||||||||||
|
The design under test — technical diagram — gray = frozen contract · ink = the current design · red = this experiment's change In plain words The model learns night navigation from simulated night photos, and the simulator rolls dice for two things: the grainy sensor static, and which streetlights and lit windows happen to be on. Until now, every practice photo of a place used one single dice roll, frozen forever — so the model could ace practice by memorizing the static, like recognizing a photocopy by its smudges instead of reading it. On new places the smudges are different, and it gets lost exactly where the picture is mostly smudge: at night. Now the practice photos come from three different dice rolls of the same city, so the only thing worth memorizing is what is actually there — the roads, the buildings, and where the lit areas sit. Hypothesis Night is now the binding constraint everywhere: it is the worst lighting bucket in all four development areas, the primary metric IS Frankfurt night (1094.59 m), and night coverage has collapsed toward the abstention floor (0.285-0.37 vs 0.46-0.71 in bright buckets) — the confidence head is already throwing away two thirds of night crops and the survivors still miss by ~1 km. The cause this experiment attacks: each (area, bucket) training/eval image is ONE frozen realization of the relighting sim's stochastic nuisances — the seeded shot/read sensor noise and the seeded random thinning that decides which built-up pixels become lamps. Measured directly on prignitz: two different seeds of the night render differ by mean |dpx| 29/255 (79% of pixels differ by >8) against a render mean of only ~60/255 — roughly HALF the night image is realization-specific nuisance — while midday differs by just 6/255. Training crops therefore show each location with a fixed noise/lamp texture for all epochs, and a 1024-way location-memorization head keys each cell's template on the easiest discriminative signal available, which at night SNR~1 is precisely that frozen texture. Held-out eval locations carry unseen realizations of the same nuisances, so the learned templates misfire, fields go diffuse, and the model abstains to the floor or commits wrongly — exactly the observed night signature. Sampling each bucket's training crops from THREE seeded realizations of the frozen sim (the stored eval-matched render plus two fresh-seed re-renders of reference.tif through the untouched pipeline.relight.relight) makes realization texture non-discriminative between locations, forcing templates onto the seed-stable structure (roads, building masses, where the built-up glow sits) — which is exactly what eval crops share with training crops. This is the first experiment in the relighting family in 16 iterations, and it is the training-side relighting that pipeline/relight.py's own NOTE explicitly sanctions; the eval set is untouched. Method model/train.py only. load_training_tensors() partitions each bucket's rng.choice picks into three near-equal parts: part 0 is extracted from the stored data/<area>/relight/<bucket>.png (seed_0 — exactly what eval sees); parts 1 and 2 are extracted from relight(ref, LIGHTING_BUCKETS[bucket], meta['gsd_m'], stable_hash(f'{area}:{bucket}:trainreal:{r}')) for r=1,2, rendered on the fly from the area's reference.tif via the frozen pipeline.relight.relight function (imported, never modified), holding at most one full raster + the reference in memory at a time (~19 s/render measured, +12 renders = ~4 min/area). Everything else — model.py, losses, decode, C4 vote, conf calibration on the stored PNGs, CLI, ONNX export — unchanged. Conclusion at the time KEPT — metric improved (1094.59 -> 1054.87 m); change committed as 95b4c3a Re-measured on today's ruler Mission score 1.753 — 1.5% of held-out frames give a usable fix, 76.8% are confident and wrong, 21.8% abstain. Median miss 1.82 km. Measured, not converted: its exported model was re-run through the current scorer for this page. What it cost
by model — claude-fable-5 $7.50 · claude-sonnet-5 $0.64 · claude-haiku-4-5-20251001 $0.00 The agent lines are the equivalent API cost of the tokens each agent used, as recorded by the harness when it ran. The research ran on a claude.ai Max subscription, so that is what it would have cost billed per token — not money that left the account. The GPU line is real spend. What it was measured against then Optimised: worst-case median error (m), target 20 m, scoring 1.05 km. Asked over: region holdout, 6 lighting buckets, 4 areas. The long grind: 62 experiments over ten days against a general-purpose, four-area, six-lighting-bucket problem. Scored on a region holdout that asked a memorization model to locate ground it had never been shown. Scoreboard — re-scored today Produced by running this experiment's exported model through the current frozen scorer on the current eval set. It is not what this experiment was told at the time.
era Four areas, six lighting buckets · ts 2026-07-21T14:50:42 ·
commit 95b4c3a0c7c7 · artifacts runs/20260721_141132_iter4 ·
took 39 m 10 s
The code for this experiment — not retainedSource not retained. Until 2026-07-31 the loop discarded a reverted experiment’s code outright, and this row predates that fix. Its design record, its verdict and its exported model survive; the code does not. It is left blank rather than filled with the parent’s code, which is what this row’s recorded commit actually contains. | |||||||||||||||||||||||||||
| ▸ | 2.16 | C4 rotation-vote field: average field logits over the crop's four 90° turns
5c5373df
The loop kept this — it beat the running best under the metric then in force. On today's ruler it scores 1.701 — worse than silence. |
architecture | 1.701 | 36 m 53 s | $7.58 | KEPT | ||||||||||||||||||||
|
The design under test — technical diagram — gray = frozen contract · ink = the current design · red = this experiment's change In plain words A drone photo can be taken facing any direction, but during practice the model only ever saw each place from one direction — so at test time it often fails to recognize a spot simply because the picture is turned. The fix: show the model every photo four times — spun by 0°, 90°, 180° and 270° — and let the four looks vote on where the drone is. A wrong guess that only appears at one spin gets outvoted; the right answer keeps winning at every spin. Same brain, four glances, one vote. Hypothesis The frozen eval set is heading-agnostic (dataset.py gives every held-out crop a deterministic random rotation), but train.py's training tensor shows each training location at exactly ONE random heading, frozen for all 8 epochs — so at eval the model must recognize places across up to 180° of heading mismatch with a plain CNN + FC field head that has no rotational structure whatsoever. Invariance-by-data was tried once (exp 6, fresh rotations every epoch) and refuted: making a 1024-way memorization head learn every location at every heading is sample-inefficient, and more rotated examples did not help. The untried alternative is invariance by construction: evaluate the field on the crop's four exact 90° pixel rotations and average the logits (C4 symmetrization). This (a) guarantees one of the four evaluated views lies within 45° of the one trained heading, versus up to 180° today; (b) makes the model exactly invariant over the C4 subgroup, so training capacity only has to cover a ±45° residual; and (c) since a logit mean is a geometric-mean ensemble, a lookalike cell that fires at only one heading is multiplicatively suppressed — directly attacking the committed-wrong 2–4 km error mode that exps 14/15 diagnosed as what pins every cell's median near 1 km. Method model/model.py forward() only: stack the input with its three 90° pixel rotations (transpose+flip — exact permutations, no interpolation, ONNX-safe) into a 4N batch, run the unchanged trunk + layout squeeze + lighting gate + two-expert heads on all four copies, average the four 1024-d field logit vectors (and the four GAP descriptors for the confidence head's input) before the unchanged β-sharpened decode and field-shape confidence. loss_fn, train.py, calibration, and the ONNX export contract untouched. Conclusion at the time KEPT — metric improved (1368.6 -> 1094.59 m); change committed as 5c5373d Re-measured on today's ruler Mission score 1.701 — 1.8% of held-out frames give a usable fix, 71.8% are confident and wrong, 26.5% abstain. Median miss 1.69 km. Measured, not converted: its exported model was re-run through the current scorer for this page. What it cost
by model — claude-fable-5 $6.34 · claude-sonnet-5 $0.81 · claude-haiku-4-5-20251001 $0.00 The agent lines are the equivalent API cost of the tokens each agent used, as recorded by the harness when it ran. The research ran on a claude.ai Max subscription, so that is what it would have cost billed per token — not money that left the account. The GPU line is real spend. What it was measured against then Optimised: worst-case median error (m), target 20 m, scoring 1.09 km. Asked over: region holdout, 6 lighting buckets, 4 areas. The long grind: 62 experiments over ten days against a general-purpose, four-area, six-lighting-bucket problem. Scored on a region holdout that asked a memorization model to locate ground it had never been shown. Scoreboard — re-scored today Produced by running this experiment's exported model through the current frozen scorer on the current eval set. It is not what this experiment was told at the time.
era Four areas, six lighting buckets · ts 2026-07-21T14:11:14 ·
commit 5c5373dff494 · artifacts runs/20260721_133421_iter3 ·
took 36 m 53 s
The code for this experiment — not retainedSource not retained. Until 2026-07-31 the loop discarded a reverted experiment’s code outright, and this row predates that fix. Its design record, its verdict and its exported model survive; the code does not. It is left blank rather than filled with the parent’s code, which is what this row’s recorded commit actually contains. | |||||||||||||||||||||||||||
| ▸ | 2.15 | Selective prediction: field-shape confidence head with per-bucket calibrated abstention
f3b87247
The loop kept this — it beat the running best under the metric then in force. On today's ruler it scores 1.681 — worse than silence. |
architecture | 1.681 | 40 m 01 s | $11.51 | KEPT | ||||||||||||||||||||
|
The design under test — technical diagram — gray = frozen contract · ink = the current design · red = this experiment's change In plain words Until now the drone's navigator shouted out a position guess for every single photo, even the hopeless ones, and those wild guesses dragged its accuracy score down. This change teaches it to look at its own heat map of candidate locations and notice the difference between one crisp hotspot (sure) and a washed-out smear of weak maybes (not sure) — and in the unsure case to say 'no fix this time', letting the drone coast on its last known position for a few extra seconds, which the mission profile explicitly allows. The scoring rules have always worked this way — only answers it commits to count, as long as it doesn't stay silent too often — so we tuned it to keep answering at least 40% of the time in every lighting condition, double the required minimum. Hypothesis The frozen §6 scorer has implemented selective prediction since bootstrap — a prediction with conf < 0.3 is an abstention excluded from that cell's median, and only coverage ≥ 0.2 is required — but the confidence pathway is still the bootstrap placeholder trained toward 'error < half the map extent', so conf saturates near 1 and coverage has been 1.000 in every one of the 24 cells of every experiment ever logged: the abstention mechanism §6 explicitly designed for ('a model that honestly abstains on bad frames is more useful than one that confidently guesses wrong') has never once fired. Meanwhile exp 14 (kept, 1856.69 -> 1638.3 m) established exactly the error-population structure that selective prediction exploits: its mixture analysis put only p ≈ 0.45–0.6 of field mass near truth, and its peak-commit decode splits predictions into a committed-correct subpopulation (a few hundred m) and committed-wrong misses (2–4 km) — confirmed by the predicted rise of the mean/median ratio from ~1.2 to ~1.35–1.4 in every area × bucket cell. Each cell's median therefore sits just on the wrong side of a bimodal error distribution: if the model can RANK its own crops by whether the field committed correctly, abstaining on the worst-ranked ones moves the median into the correct-commit mode. The ranking signal is already computed at inference and thrown away: the SHAPE of the decode distribution — sharpened-field peak mass, field entropy, and the distance between the sharpened and unsharpened decodes (a direct diffuseness/multimodality readout, the same diagnostic exp 14's analysis was built on). The confidence mechanism is a design family untouched in 14 experiments; it is deployment-honest (a UAV that skips an unsure fix coasts on dead reckoning for the 5–10 s until the next frame per §2's adaptive schedule — strictly better than ingesting a 2-km-wrong fix); and it can be made safe against the coverage-FAIL gate by construction: train the head as a calibrated hit predictor (BCE on 'committed decode within τ = 0.05 uv ≈ 350 m ≈ 1.6 grid cells') on detached inputs, then bake a per-area logit offset into the export, calibrated per lighting bucket on fresh-rotation TRAIN-split crops so that ≥40% clear the fixed 0.3 threshold in EVERY bucket — double the 0.2 coverage floor. Method ONE focused change: the deployed confidence pathway. model/model.py — replace the placeholder Linear(48->1) conf head with a small MLP over [48-d GAP descriptor, sharpened-field peak mass, normalized field entropy, distance between sharpened and unsharpened decodes], ALL inputs detached so no confidence gradient can touch trunk/heads; conf = sigmoid(z - conf_shift) with conf_shift a registered buffer (0 during training); loss_fn's conf target tightens from err<0.5 to err<GOOD_ERR_UV=0.05 (weight 0.1->0.3). model/train.py — after the epoch loop, per-area calibration on ~400 fresh-rotation TRAIN-split crops per lighting bucket sets conf_shift so the scorer's fixed 0.3 threshold keeps ≥40% in the worst bucket, then export. Trunk, layout head, dark expert, gate, β=3 decode, Gaussian-CE and coordinate L2: unchanged. Conclusion at the time KEPT — metric improved (1638.3 -> 1368.6 m); change committed as f3b8724 Re-measured on today's ruler Mission score 1.681 — 0.8% of held-out frames give a usable fix, 68.8% are confident and wrong, 30.5% abstain. Median miss 1.41 km. Measured, not converted: its exported model was re-run through the current scorer for this page. What it cost
by model — claude-fable-5 $10.06 · claude-sonnet-5 $0.99 · claude-haiku-4-5-20251001 $0.00 The agent lines are the equivalent API cost of the tokens each agent used, as recorded by the harness when it ran. The research ran on a claude.ai Max subscription, so that is what it would have cost billed per token — not money that left the account. The GPU line is real spend. What it was measured against then Optimised: worst-case median error (m), target 20 m, scoring 1.37 km. Asked over: region holdout, 6 lighting buckets, 4 areas. The long grind: 62 experiments over ten days against a general-purpose, four-area, six-lighting-bucket problem. Scored on a region holdout that asked a memorization model to locate ground it had never been shown. Scoreboard — re-scored today Produced by running this experiment's exported model through the current frozen scorer on the current eval set. It is not what this experiment was told at the time.
era Four areas, six lighting buckets · ts 2026-07-21T13:34:06 ·
commit f3b87247b232 · artifacts runs/20260721_125405_iter2 ·
took 40 m 01 s
The code for this experiment — not retainedSource not retained. Until 2026-07-31 the loop discarded a reverted experiment’s code outright, and this row predates that fix. Its design record, its verdict and its exported model survive; the code does not. It is left blank rather than filled with the parent’s code, which is what this row’s recorded commit actually contains. | |||||||||||||||||||||||||||
| ▸ | 2.14 | Peak-commit decode: β-sharpened softmax replaces mean-of-map soft-argmax
d2a0a2b9
The loop kept this — it beat the running best under the metric then in force. On today's ruler it scores 1.971 — worse than silence. |
architecture | 1.971 | 31 m 13 s | $7.30 | KEPT | ||||||||||||||||||||
|
The design under test — technical diagram — gray = frozen contract · ink = the current design · red = this experiment's change In plain words The model draws a heat map of 'where might this photo be?' over the whole city, then has to give one answer. Until now it answered with the average of every warm spot at once — like aiming a dart at the average of three boards you suspect, which lands you in the wall between them. We now turn up the contrast on the heat map first, so the strongest hotspot dominates and the faint background noise vanishes, and the model answers at that hotspot. It is also graded during training on this committed answer, so it learns to put its hotspot in the right place rather than hedging toward the middle of the map. Hypothesis After exps 10-12 (all kept: layout head, pretrained trunk, gated dark expert; 2489 -> 1857 m) the binding constraint is no longer feature quality but the decode itself: global soft-argmax returns the probability-weighted MEAN of the whole 32x32 field, so any residual diffuse or multimodal mass drags every prediction toward the map centroid. Three lines of evidence converge on this. (1) The error profile has the exact shrinkage signature: medians sit at 1.0-1.9 km against a ~3.2 km center-guess floor with mean/median ratios of only ~1.15-1.25 in every area x bucket cell — a light-tailed, globally-shrunk distribution, not the heavy tail that confident-but-wrong mode commitment would produce. Under a mixture model 'p mass near truth + (1-p) diffuse', median ≈ (1-p) x center-distance gives p ≈ 0.45-0.6 across buckets: the field already places roughly half its mass near the right answer and the expectation decode throws that away. (2) The training loss actively teaches this failure: the coordinate term is an L2 on the decoded expectation, whose minimizer IS the posterior mean, while the §6 metric is a MEDIAN of radial error, which rewards committing to the dominant mode — a direct loss-metric mismatch nothing in the history has addressed. (3) The only prior attack on the decode, exp 3 (argmax-anchored 5x5-window soft-argmax), was refuted in the from-scratch-encoder era, and its own conclusion attributed the failure to near-uniform fields making the argmax pure noise; that failure mode no longer applies now that exps 10-12 produced fields with real structure (first-ever per-lighting-bucket error gradient). The materially new angle vs exp 3: no hard argmax anchor and no window — a fully differentiable global sharpening (decode from softmax(β·logits), inverse-temperature β = 3) that leaves a uniform field exactly uniform (so the untrained model still decodes to the map center — same bounded-downside floor as every kept experiment) but drives any field with a dominant mode toward that mode, and lets the existing coordinate L2 automatically retrain the network on the committed answer. Method One line in model/model.py's TinyLocNet.forward(): the decode distribution becomes p = softmax(DECODE_BETA * logits) with a new module constant DECODE_BETA = 3.0; the soft-argmax expectation over cell centers, the confidence head, and the gated two-head field are untouched. loss_fn is left byte-identical: its cross-entropy consumes the raw logits (log_softmax(logits)), so the field keeps learning the same calibrated Gaussian-bump targets, while its coordinate L2 reads pred[:, :2] and therefore automatically grades the sharpened, committed decode — training and inference stay coupled on exactly what flies. No new parameters, train.py unchanged, ONNX contract and deployment gates unaffected. Conclusion at the time KEPT — metric improved (1856.69 -> 1638.3 m); change committed as d2a0a2b Re-measured on today's ruler Mission score 1.971 — 1.5% of held-out frames give a usable fix, 98.5% are confident and wrong, 0.0% abstain. Median miss 1.74 km. Measured, not converted: its exported model was re-run through the current scorer for this page. What it cost
by model — claude-fable-5 $6.51 · claude-sonnet-5 $0.42 · claude-haiku-4-5-20251001 $0.00 The agent lines are the equivalent API cost of the tokens each agent used, as recorded by the harness when it ran. The research ran on a claude.ai Max subscription, so that is what it would have cost billed per token — not money that left the account. The GPU line is real spend. What it was measured against then Optimised: worst-case median error (m), target 20 m, scoring 1.64 km. Asked over: region holdout, 6 lighting buckets, 4 areas. The long grind: 62 experiments over ten days against a general-purpose, four-area, six-lighting-bucket problem. Scored on a region holdout that asked a memorization model to locate ground it had never been shown. Scoreboard — re-scored today Produced by running this experiment's exported model through the current frozen scorer on the current eval set. It is not what this experiment was told at the time.
era Four areas, six lighting buckets · ts 2026-07-21T12:53:43 ·
commit d2a0a2b9d817 · artifacts runs/20260721_122230_iter1 ·
took 31 m 13 s
The code for this experiment — not retainedSource not retained. Until 2026-07-31 the loop discarded a reverted experiment’s code outright, and this row predates that fix. Its design record, its verdict and its exported model survive; the code does not. It is left blank rather than filled with the parent’s code, which is what this row’s recorded commit actually contains. | |||||||||||||||||||||||||||
| ▸ | 2.13 | Periodic blind holdout check (hamburg)
fe745eb4
Blind Hamburg holdout check. Logged for honesty, never allowed to drive keep/revert. |
other | — | — | — | HOLDOUT | ||||||||||||||||||||
|
Hypothesis If the pipeline is genuinely generic, holdout error should track the development worst-case. Method Train current best model code on hamburg, score read-only (§5). Not on today's ruler A blind Hamburg holdout check (§5). It measures a different area, so no Berlin mission score exists for it. What it cost No agent accounting survives for this experiment — its run directory was deleted. That is a missing receipt, not a zero. What it was measured against then Optimised: worst-case median error (m), target 20 m, scoring 1.29 km. Asked over: region holdout, 6 lighting buckets, 4 areas. The long grind: 62 experiments over ten days against a general-purpose, four-area, six-lighting-bucket problem. Scored on a region holdout that asked a memorization model to locate ground it had never been shown. era Four areas, six lighting buckets · ts 2026-07-21T12:20:37 ·
commit fe745eb4fa91 · artifacts runs/20260721_113214_iter2/holdout ·
took —
The code for this experiment — not retainedSource not retained. Until 2026-07-31 the loop discarded a reverted experiment’s code outright, and this row predates that fix. Its design record, its verdict and its exported model survive; the code does not. It is left blank rather than filled with the parent’s code, which is what this row’s recorded commit actually contains. | |||||||||||||||||||||||||||
| ▸ | 2.12 | Luminance-gated dark-expert field head blended with the existing layout head
fe745eb4
The loop kept this — it beat the running best under the metric then in force. On today's ruler it scores 1.996 — worse than silence. |
architecture | 1.996 | 42 m 10 s | $4.95 | KEPT | ||||||||||||||||||||
|
The design under test — technical diagram — gray = frozen contract · ink = the current design · red = this experiment's change In plain words The last experiment gave the model 'eyes' that already knew how to see from a million daytime photos, and it worked -- but only for well-lit shots. At night, that same borrowed vision is guessing worse, because those eyes never really learned what a dark, sparsely-lit scene looks like. This experiment adds a second, smaller decision-maker that only looks at the crop's overall texture (not its detailed layout, which is mostly invisible in the dark anyway), plus a tiny 'how dark is this shot?' switch that blends the two decision-makers' guesses. In bright shots the switch leans on the original, more detailed guesser; in dark shots it leans on the simpler one built for low light. Nothing about the camera or the final answer changes -- just how the guess gets made inside. Hypothesis Exp 11 (ImageNet-pretrained MobileNetV3-Small trunk, kept, 2299.74 -> 2010.15 m) produced, for the first time in this project's history, a clear per-lighting-bucket error gradient instead of the flat profile every from-scratch experiment showed: night is worst in all four development areas (Berlin 1600 vs 1381 m midday; Prignitz 1604 vs 1007 m morning; Munich 2010 vs 1381 m midday -- Munich night IS the current primary worst-case metric; Frankfurt 1585 vs 1063 m afternoon). This is new, decisive evidence of a kind no prior experiment produced: pretrained ImageNet features, however gently fine-tuned (trunk lr 1e-4), carry a systematic bias toward well-lit natural-image statistics, and the single shared field head (GAP + layout code -> FC -> 1024 logits) is being forced to compute the same cell-scoring function for both regimes. CLAUDE.md SS3 explicitly leaves 'several small lighting-condition-specific models behind a lightweight dispatcher' vs. one unified model as an open, loop-decided question, and it is the one design family the plateau-rule history (exps 1-11) has never tried -- every experiment so far has kept a single head. A full duplicate specialist head sized like the existing one would blow the deployment gate (loc_logits is a 560->1024 FC, ~573k params / ~2.2 MB; exp 11's export is already 2.94 MiB of the 4 MiB ONNX budget), so this experiment tests the dispatcher/specialist hypothesis with a budget-safe mixture-of-experts-lite mechanism instead of a full dispatcher. Method model/model.py TinyLocNet: add a second, cheap 'dark expert' field head -- nn.Linear(48, 1024) reading ONLY the 48-d GAP descriptor (no 512-d layout code, on the theory that spatial layout is the least reliable signal once texture is mostly gone at night) -- and a tiny gate MLP (Linear(49,16)+ReLU+Linear(16,1)+sigmoid) over [raw-pixel mean brightness of the input frame, 48-d GAP], both derived from the frame itself so the frozen (frame in) -> (lat, lon, conf) contract is untouched. Forward pass: logits = (1-g)*bright_logits + g*dark_logits, where bright_logits is the existing, architecturally unchanged layout-aware head and g in [0,1] is the learned blend weight; softmax/soft-argmax decode, confidence head, GRID_K, and both loss terms are identical to exp 11 -- loss_fn already receives the blended logits via forward(return_logits=True), no changes needed there. No explicit lighting-bucket label is used anywhere; the gate learns to route purely from brightness + features. Adds ~51k params (~200 KB): dark head 50,176 params, gate MLP 817 params. model/train.py: no logic changes -- new params are fresh-initialized and fall into the existing 'head' LR group (1e-3) automatically since they are not in model.features. Verified locally: 816,418 total params (was 765,433), forward+backward flows gradients through both heads and the gate, ONNX 3.14 MiB (< 4 MiB gate, 150 KB of headroom left), torch/ONNX parity 6e-8, untrained decode sits at the map center (0.4999, 0.5023) confirming bounded downside. Conclusion at the time KEPT — metric improved (2010.15 -> 1856.69 m); change committed as fe745eb Re-measured on today's ruler Mission score 1.996 — 0.2% of held-out frames give a usable fix, 99.8% are confident and wrong, 0.0% abstain. Median miss 1.68 km. Measured, not converted: its exported model was re-run through the current scorer for this page. What it cost
by model — claude-sonnet-5 $4.46 · claude-haiku-4-5-20251001 $0.00 The agent lines are the equivalent API cost of the tokens each agent used, as recorded by the harness when it ran. The research ran on a claude.ai Max subscription, so that is what it would have cost billed per token — not money that left the account. The GPU line is real spend. What it was measured against then Optimised: worst-case median error (m), target 20 m, scoring 1.86 km. Asked over: region holdout, 6 lighting buckets, 4 areas. The long grind: 62 experiments over ten days against a general-purpose, four-area, six-lighting-bucket problem. Scored on a region holdout that asked a memorization model to locate ground it had never been shown. Scoreboard — re-scored today Produced by running this experiment's exported model through the current frozen scorer on the current eval set. It is not what this experiment was told at the time.
era Four areas, six lighting buckets · ts 2026-07-21T12:14:24 ·
commit fe745eb4fa91 · artifacts runs/20260721_113214_iter2 ·
took 42 m 10 s
The code for this experiment — not retainedSource not retained. Until 2026-07-31 the loop discarded a reverted experiment’s code outright, and this row predates that fix. Its design record, its verdict and its exported model survive; the code does not. It is left blank rather than filled with the parent’s code, which is what this row’s recorded commit actually contains. | |||||||||||||||||||||||||||
| ▸ | 2.11 | ImageNet-pretrained MobileNetV3-Small trunk replaces the from-scratch encoder
d69862d9
The loop kept this — it beat the running best under the metric then in force. On today's ruler it scores 1.986 — worse than silence. |
architecture | 1.986 | 31 m 35 s | $2.64 | KEPT | ||||||||||||||||||||
|
The design under test — technical diagram — gray = frozen contract · ink = the current design · red = this experiment's change In plain words Until now the model's 'eyes' started out completely blind: they had to learn to see edges, corners and textures using only pictures of the one training area -- like learning to read from a single book. This experiment swaps in eyes that were already trained on 1.2 million everyday photographs, so they arrive knowing how to see, and we only gently retune them to our night-time aerial views. Everything after the eyes -- the part that guesses the position on the map -- stays exactly the same. If the guesses were bad because the eyes were weak, this should help a lot; if not, we have learned the eyes were never the problem. Hypothesis The binding constraint is the quality of the conv features themselves, and no within-dataset signal can fix it: exp 8 showed more from-scratch capacity does not help, exp 9 showed a direct place-discrimination objective (InfoNCE) on from-scratch features does not help, and exp 10's layout-aware head — which finally lets the head read WHERE features sit — was kept but recovered only 1.2%, implying the features it reads carry little discriminative content. All ten experiments trained the encoder from random init on ~36k synthetic relit crops of ONE area — far too little visual diversity to learn the general edge/corner/texture/junction vocabulary that makes local patterns separable. CLAUDE.md §3/§9 explicitly reserves permissively-licensed pretrained init as an open call, and it is the one family in the plateau-rule list never tried. Swapping the from-scratch 4-conv stack for the first 9 feature blocks of ImageNet-pretrained MobileNetV3-Small (190k params, BSD-3, output 8x8x48 — the exact spatial grid the kept exp-10 head already reads) imports a feature vocabulary learned from 1.2M real photographs; low/mid-level features are the standard transferable layer even across large domain gaps (ground-level photos -> overhead relit imagery). Method model/model.py: replace the 4x stride-2 from-scratch conv stack with torchvision's actual mobilenet_v3_small().features[:9] module (stem + 8 inverted-residual blocks), loading the verbatim IMAGENET1K_V1 tensors from model/pretrained/mnv3s_features8.pt with strict=True; bake ImageNet mean/std normalization into forward() as buffers so the [0,1] ONNX input contract is unchanged. Head adapts only in width (layout squeeze 48->8, FC 560->1024, conf 48->1, dims probed dynamically from the trunk's actual output shape); GRID_K, losses, decode, export contract all unchanged. model/train.py: two-tier fine-tuning LR (trunk param group 1e-4, fresh-head param group 1e-3) so early large head gradients do not destroy the transferred features; logs 'pretrained:mobilenet_v3_small IMAGENET1K_V1 features[0..8] (torchvision, BSD-3)' as init strategy per §9. Verified locally: strict load OK, 765k total params, forward + backward pass flows gradients into both trunk and head, ONNX 2.94 MiB (< 4 MiB gate), host latency proxy ~40 ms (< 250 ms gate), torch/ONNX parity 9e-8, untrained decode sits at the map center (0.4999, 0.4991) confirming bounded downside. Conclusion at the time KEPT — metric improved (2299.74 -> 2010.15 m); change committed as d69862d Re-measured on today's ruler Mission score 1.986 — 0.2% of held-out frames give a usable fix, 98.8% are confident and wrong, 1.0% abstain. Median miss 1.55 km. Measured, not converted: its exported model was re-run through the current scorer for this page. What it cost
by model — claude-sonnet-5 $2.28 · claude-haiku-4-5-20251001 $0.00 The agent lines are the equivalent API cost of the tokens each agent used, as recorded by the harness when it ran. The research ran on a claude.ai Max subscription, so that is what it would have cost billed per token — not money that left the account. The GPU line is real spend. What it was measured against then Optimised: worst-case median error (m), target 20 m, scoring 2.01 km. Asked over: region holdout, 6 lighting buckets, 4 areas. The long grind: 62 experiments over ten days against a general-purpose, four-area, six-lighting-bucket problem. Scored on a region holdout that asked a memorization model to locate ground it had never been shown. Scoreboard — re-scored today Produced by running this experiment's exported model through the current frozen scorer on the current eval set. It is not what this experiment was told at the time.
era Four areas, six lighting buckets · ts 2026-07-21T11:32:06 ·
commit d69862d9a3b1 · artifacts runs/20260721_110031_iter1 ·
took 31 m 35 s
The code for this experiment — not retainedSource not retained. Until 2026-07-31 the loop discarded a reverted experiment’s code outright, and this row predates that fix. Its design record, its verdict and its exported model survive; the code does not. It is left blank rather than filled with the parent’s code, which is what this row’s recorded commit actually contains. | |||||||||||||||||||||||||||
| ▸ | 2.10 | Layout-aware field head: squeezed 8×8 spatial code ⊕ GAP replaces GAP-only head input
ff271753
The loop kept this — it beat the running best under the metric then in force. On today's ruler it scores 1.996 — worse than silence. |
architecture | 1.996 | 38 m 08 s | $4.27 | KEPT | ||||||||||||||||||||
|
The design under test — technical diagram — gray = frozen contract · ink = the current design · red = this experiment's change In plain words Until now the model summarized each aerial photo as a recipe list — 'this much road, this much rooftop, this much trees' — with no memory of where anything sat in the picture. Two different neighborhoods with the same mix of ingredients looked identical to it, which may be why it kept guessing near the middle of the map. This experiment adds a second, position-preserving summary — roads on the left, courtyard in the middle, park at the top — and lets the location-guessing layer read both summaries. Places are arrangements, not ingredient lists, so keeping the arrangement should finally let it tell lookalike districts apart. Hypothesis Every design so far has fed the 1024-cell field head a spatially averaged descriptor: GAP collapses the 8×8×128 feature map to a bag-of-textures that destroys WHERE features sit in the crop — and the arrangement of roads, buildings and open space is precisely what distinguishes lookalike districts at 1 m/px. This one invariant explains the joint evidence better than any refuted hypothesis: capacity was null (exp 8: a deeper bag-of-textures is still a bag), contrastive descriptor learning was null (exp 9: no training signal can make the descriptor place-discriminative when pooling already destroyed the discriminating information), 7.5× data recovered only 7% (exp 7: more examples do not add information per example), and medians are FLAT across lighting buckets (texture statistics are roughly equally ambiguous in every lighting, while layout would be most readable in bright buckets). Exp 4 is the only non-GAP design ever tried, and it went to the opposite extreme — 64 independent per-patch coordinate regressions with no global read of the arrangement — so a layout-aware GLOBAL decision has never been tested. The fix: a 1×1 conv squeezes the feature map to 8 channels, its flattened 512-d layout code is concatenated with the existing 128-d GAP descriptor, and the FC to 1024 logits reads both — mathematically, the head upgrades from a linear map on the spatial average to a low-rank linear map on the full feature map, a strict superset of every function the old head could compute. Method model/model.py only. TinyLocNet gains self.layout_squeeze = nn.Conv2d(128, 8, 1); forward() keeps the 8×8×128 feature map, computes the usual GAP vector f (128-d) AND layout = layout_squeeze(fmap).flatten(1) (8×8×8 = 512-d), and loc_logits becomes nn.Linear(640, 1024) applied to cat([f, layout]). Confidence head (reads GAP only), softmax + soft-argmax decode, loss_fn, export and train.py are all byte-identical. Params 232k → ~757k, ONNX ≈ 2.9 MiB (< 4 MiB gate), latency delta negligible (one 1×1 conv + a wider FC). Conclusion at the time KEPT — metric improved (2326.54 -> 2299.74 m); change committed as ff27175 Re-measured on today's ruler Mission score 1.996 — 0.2% of held-out frames give a usable fix, 99.8% are confident and wrong, 0.0% abstain. Median miss 2.68 km. Measured, not converted: its exported model was re-run through the current scorer for this page. What it cost
by model — claude-fable-5 $4.27 · claude-haiku-4-5-20251001 $0.00 The agent lines are the equivalent API cost of the tokens each agent used, as recorded by the harness when it ran. The research ran on a claude.ai Max subscription, so that is what it would have cost billed per token — not money that left the account. The GPU line is real spend. What it was measured against then Optimised: worst-case median error (m), target 20 m, scoring 2.30 km. Asked over: region holdout, 6 lighting buckets, 4 areas. The long grind: 62 experiments over ten days against a general-purpose, four-area, six-lighting-bucket problem. Scored on a region holdout that asked a memorization model to locate ground it had never been shown. Scoreboard — re-scored today Produced by running this experiment's exported model through the current frozen scorer on the current eval set. It is not what this experiment was told at the time.
era Four areas, six lighting buckets · ts 2026-07-20T23:08:48 ·
commit ff2717532634 · artifacts runs/20260720_223039_iter4 ·
took 38 m 08 s
The code for this experiment — its code, from the commitRead back out of this experiment’s own commit. It was kept, so the loop committed model/ and then recorded that commit — the tree is this experiment’s code. · on GitHub ↗model/model.py128 lines model/train.py109 lines | |||||||||||||||||||||||||||
| ▸ | 2.9 | Cross-lighting contrastive pairs: NT-Xent metric learning on the place descriptor
76c45e3c
The loop discarded this — no improvement under the metric then in force. |
loss | 1.996 | 23 m 41 s | $4.93 | DISCARDED | ||||||||||||||||||||
|
The design under test — technical diagram — gray = frozen contract · ink = the current design · red = this experiment's change In plain words The model has been describing every photo so vaguely that different places end up with nearly identical descriptions — like describing every street as 'houses and a road'. We now train it flash-card style: each round it gets two photos of the exact same spot — say one at noon and one at night, turned to different compass headings — and is graded on giving both the same description, while its descriptions of the 62 other places in the round must stay clearly different. That pressure forces it to notice what makes each place unique, which is exactly what pointing to the right spot on the map requires. Hypothesis The encoder's 128-d descriptor is not place-discriminative, and no existing training signal ever directly demands that it be: Gaussian-CE against a near-uniform 1024-way target gives each crop a diffuse gradient that shapes the head more than the features. The joint evidence — near-uniform fields (exp 3's argmax test), nulls from supervision form (3-5), rotation resampling (6) and capacity (8), only 7% from 7.5x data (7), and medians flat across lighting buckets at ~2.3 km near the 3.2 km center-guess floor — all points at descriptor quality, not head, data volume, or capacity. An InfoNCE objective on location-paired batches (same place, two different lighting renders, two independent headings, 62 in-batch negatives) supplies exactly the missing signal: per-sample 'same place -> same descriptor, different place -> different descriptor' gradients far stronger than the near-uniform CE, and it builds precisely the two invariances (heading, lighting) the frozen rotated multi-bucket eval demands. Method train.py: sample ONE shared 6,000-location set per area and extract it in all six relight buckets with independent random headings (same 36k-crop, ~1.8 GB uint8 budget; previously each bucket sampled locations independently, so no cross-bucket pairs existed). Each step draws 32 locations x 2 distinct buckets -> a 64-crop batch of 32 positive pairs. model.py: add a train-only 128->128->64 MLP projection head and NT-Xent (temperature 0.2, weight 1.0) on the projected GAP descriptors, added to the unchanged Gaussian-CE + coord L2 + conf BCE. Inference path, decode, and ONNX export are byte-identical; the projection head is never traced. Conclusion at the time REVERTED — metric did not improve (2342.1 m vs best 2326.54 m); change discarded Re-measured on today's ruler Mission score 1.996 — 0.2% of held-out frames give a usable fix, 99.8% are confident and wrong, 0.0% abstain. Median miss 1.73 km. Measured, not converted: its exported model was re-run through the current scorer for this page. What it cost
by model — claude-fable-5 $4.92 · claude-haiku-4-5-20251001 $0.00 The agent lines are the equivalent API cost of the tokens each agent used, as recorded by the harness when it ran. The research ran on a claude.ai Max subscription, so that is what it would have cost billed per token — not money that left the account. The GPU line is real spend. What it was measured against then Optimised: worst-case median error (m), target 20 m, scoring 2.34 km. Asked over: region holdout, 6 lighting buckets, 4 areas. The long grind: 62 experiments over ten days against a general-purpose, four-area, six-lighting-bucket problem. Scored on a region holdout that asked a memorization model to locate ground it had never been shown. Scoreboard — re-scored today Produced by running this experiment's exported model through the current frozen scorer on the current eval set. It is not what this experiment was told at the time.
era Four areas, six lighting buckets · ts 2026-07-20T22:30:37 ·
commit 76c45e3c4850 · artifacts runs/20260720_220655_iter3 ·
took 23 m 41 s
The code for this experiment — not retainedSource not retained. Until 2026-07-31 the loop discarded a reverted experiment’s code outright, and this row predates that fix. Its design record, its verdict and its exported model survive; the code does not. It is left blank rather than filled with the parent’s code, which is what this row’s recorded commit actually contains. | |||||||||||||||||||||||||||
| ▸ | 2.8 | Deployment-envelope residual encoder: 4.2× capacity (232k → 973k params), 11 conv layers with skips
76c45e3c
The loop discarded this — no improvement under the metric then in force. |
architecture | 1.961 | 48 m 45 s | $5.33 | DISCARDED | ||||||||||||||||||||
|
The design under test — technical diagram — gray = frozen contract · ink = the current design · red = this experiment's change In plain words Until now the model was a tiny four-layer network trying to memorize what every corner of a 7 km-wide city looks like — like sketching an entire city map on a sticky note. Recent experiments kept changing how we teach it (better lessons, more flashcards) and nothing broke through, because the student's notebook was simply full: it scored almost equally badly in bright daylight and at night, the classic sign of a model too small for its task rather than a task too hard. The flight computer actually has room for a model four times bigger than the one we've been flying. This experiment keeps the lessons exactly the same and only swaps in a deeper, four-times-larger network — built with 'residual' shortcuts, a standard trick that keeps deep networks easy to train — so it finally has enough room to write the whole map down. Hypothesis The binding constraint is encoder capacity, not supervision, decode, rotation handling, or data volume. Exps 3-5 changed supervision/decode and were all null; exp 6 (rotation resampling) was null; exp 7's 7.5× data scale — the family every reverted mechanism jointly pointed at — recovered only 7% (2489 → 2326 m), meaning the model could not absorb the added coverage. The kept run's error profile is the signature of underfitting: ~1.55-2.33 km medians nearly FLAT across lighting buckets (Berlin night 1760 m vs midday 1642 m), so even the easiest bright-daylight discrimination is not being learned — a lighting- or supervision-side bottleneck would show a bucket gradient. The current encoder is 4 plain stride-2 convs whose 128-d GAP descriptor must separate 1,024 map cells × 6 lightings across ~45k locations at 1 m/px, while using only 0.93 MB of the 4 MiB deployment gate and 1.25 ms of the 250 ms latency budget. Depth/width is the one axis no experiment has ever touched. Method model/model.py only: replace TinyLocNet.features (4 plain stride-2 convs 3→16→32→64→128, GAP→128-d) with an 11-conv residual encoder sized to the deployment envelope — stem 3→32 s2; stage1 down 32→64 s2 + bottleneck residual block (1×1 reduce to 32, 3×3, 1×1 expand); stage2 down 64→128 s2 + bottleneck block (mid 64); stage3 down 128→160 s2 + full 3×3+3×3 residual block; GAP → 160-d. Bottlenecks sit early where 32²/16² resolution makes compute expensive; the full block sits at 8² where parameters are cheap. Field head (Linear→1024 + softmax soft-argmax), conf head, loss, decode, training data, epochs, and lr are untouched, so attribution is purely encoder capacity/depth. Verified pre-registration: 972,993 params, ONNX 3.72 MiB < 4 MiB gate, host latency proxy 2.91 ms < 250 ms. Conclusion at the time REVERTED — metric did not improve (2427.33 m vs best 2326.54 m); change discarded Re-measured on today's ruler Mission score 1.961 — 2.0% of held-out frames give a usable fix, 98.0% are confident and wrong, 0.0% abstain. Median miss 2.19 km. Measured, not converted: its exported model was re-run through the current scorer for this page. What it cost
by model — claude-fable-5 $5.33 · claude-haiku-4-5-20251001 $0.00 The agent lines are the equivalent API cost of the tokens each agent used, as recorded by the harness when it ran. The research ran on a claude.ai Max subscription, so that is what it would have cost billed per token — not money that left the account. The GPU line is real spend. What it was measured against then Optimised: worst-case median error (m), target 20 m, scoring 2.43 km. Asked over: region holdout, 6 lighting buckets, 4 areas. The long grind: 62 experiments over ten days against a general-purpose, four-area, six-lighting-bucket problem. Scored on a region holdout that asked a memorization model to locate ground it had never been shown. Scoreboard — re-scored today Produced by running this experiment's exported model through the current frozen scorer on the current eval set. It is not what this experiment was told at the time.
era Four areas, six lighting buckets · ts 2026-07-20T22:06:53 ·
commit 76c45e3c4850 · artifacts runs/20260720_211808_iter2 ·
took 48 m 45 s
The code for this experiment — not retainedSource not retained. Until 2026-07-31 the loop discarded a reverted experiment’s code outright, and this row predates that fix. Its design record, its verdict and its exported model survive; the code does not. It is left blank rather than filled with the parent’s code, which is what this row’s recorded commit actually contains. | |||||||||||||||||||||||||||
| ▸ | 2.7 | Scale training coverage 7.5x: 6,000 of ~45k available train locations per bucket (was 800)
76c45e3c
The loop kept this — it beat the running best under the metric then in force. On today's ruler it scores 1.986 — worse than silence. |
training | 1.986 | 21 m 44 s | $3.83 | KEPT | ||||||||||||||||||||
|
The design under test — technical diagram — gray = frozen contract · ink = the current design · red = this experiment's change In plain words The model has been studying flashcards covering only about 2% of the map — most neighborhoods were never shown to it at all, in any lighting. The last four experiments all tried smarter study techniques and none helped, which suggests the problem was never the technique: you can't recognize a place you've never seen. This experiment changes nothing about the model or how it learns — it just hands it 7.5 times more flashcards, enough that every part of the map appears in the deck under every lighting condition. Training takes a few minutes longer per area, which was affordable all along. Hypothesis All four consecutive reverted experiments (3: decode commitment, 4: dense per-patch supervision, 5: hierarchical pooling, 6: rotation resampling) changed the supervision/decode mechanism while inheriting the same starved data regime, and all failed by nearly identical margins — the shared substrate, not the mechanisms, is the bottleneck. train.py samples only 800 of the ~45,000 enumerable train locations per lighting bucket (1.8%). At 1 m/px GSD (exp-5's density analysis assumed 10 m/px, so it attacked label-space density when the real scarcity is input-space coverage), a 128x128 crop spans 128 m of a ~7 km map, and 800 random locations put ~0.78 training crops per 220 m field cell per bucket — a quarter of grid cells have zero training example in any given lighting condition, and a typical eval crop shares content with roughly half of one training crop in its bucket. The probability field stays near-uniform because most of the map was literally never shown to the model. Raising the per-bucket sample to 6,000 gives ~5.9 crops per cell per bucket (every cell supervised in every lighting condition), makes neighboring overlapping crops (24 m stride, 80%+ shared content) appear at many independent random headings — rotation robustness through spatial density rather than exp-6's refuted temporal resampling of the same sparse 800 — and raises gradient steps from 600 to ~4,500 as a direct consequence. Method In model/train.py only: (1) raise the --max-crops-per-bucket default from 800 to 6,000 (~36k crops/area); (2) hold the training tensor as uint8 and convert each minibatch to float on device — required because 36k float32 crops would be ~7 GB, uint8 is ~1.8 GB. Model, loss, decode, optimizer (Adam 1e-3), batch size 64, epoch count (harness-set 8), and the one-shot static extraction with one random heading per location all stay byte-identical to the kept exp-2 code. Cost: ~3.5 min/area (21 s extraction + ~4,500 steps) vs ~28 s — iteration wall-clock rises to roughly 15 min. Conclusion at the time KEPT — metric improved (2489.03 -> 2326.54 m); change committed as 76c45e3 Re-measured on today's ruler Mission score 1.986 — 0.8% of held-out frames give a usable fix, 99.2% are confident and wrong, 0.0% abstain. Median miss 2.68 km. Measured, not converted: its exported model was re-run through the current scorer for this page. What it cost
by model — claude-fable-5 $3.82 · claude-haiku-4-5-20251001 $0.00 The agent lines are the equivalent API cost of the tokens each agent used, as recorded by the harness when it ran. The research ran on a claude.ai Max subscription, so that is what it would have cost billed per token — not money that left the account. The GPU line is real spend. What it was measured against then Optimised: worst-case median error (m), target 20 m, scoring 2.33 km. Asked over: region holdout, 6 lighting buckets, 4 areas. The long grind: 62 experiments over ten days against a general-purpose, four-area, six-lighting-bucket problem. Scored on a region holdout that asked a memorization model to locate ground it had never been shown. Scoreboard — re-scored today Produced by running this experiment's exported model through the current frozen scorer on the current eval set. It is not what this experiment was told at the time.
era Four areas, six lighting buckets · ts 2026-07-20T21:18:06 ·
commit 76c45e3c4850 · artifacts runs/20260720_205622_iter1 ·
took 21 m 44 s
The code for this experiment — its code, from the commitRead back out of this experiment’s own commit. It was kept, so the loop committed model/ and then recorded that commit — the tree is this experiment’s code. · on GitHub ↗model/model.py114 lines model/train.py109 lines | |||||||||||||||||||||||||||
| ▸ | 2.6 | Per-epoch rotation resampling replaces the one-shot frozen training tensor
de614d47
The loop discarded this — no improvement under the metric then in force. |
augmentation | 1.981 | 6 m 52 s | $1.67 | DISCARDED | ||||||||||||||||||||
|
The design under test — technical diagram — gray = frozen contract · ink = the current design · red = this experiment's change In plain words Until now the model studied each training photo from one fixed compass direction only -- like memorizing your neighborhood while always facing north, then being quizzed while facing random directions. Unsurprisingly, it froze on the quiz. Now, every time it re-reads its study deck, every photo is re-dealt at a new random rotation, so the only way to get good grades in practice is to recognize places no matter which way it is facing -- which is exactly what the real quiz (and a real drone) demands. Hypothesis The probability field stays near-uniform not because of supervision form (exp-3/4/5 all attacked the head/loss and failed identically) but because the encoder never learns rotation-invariant features: train.py extracts each of the few hundred train locations exactly once, at one fixed random heading, into a static tensor reused every epoch, while the frozen eval rotates every crop by a different deterministic angle. The model can fit 'this place seen at heading 137 degrees' without ever being forced to recognize the same place at another heading, so at eval time features do not fire, the field stays flat, and the soft-argmax decode collapses toward the map center. Re-extracting all training crops with fresh uniform-random angles at the start of every epoch multiplies heading diversity per location by the epoch count and makes rotation invariance the only way to reduce training loss. Method In model/train.py only: split load_training_tensors into a one-time loader (meta, train-crop list, six relight images cached in memory) and a per-epoch sampler that re-runs extract_crop with a fresh rng.uniform(0,360) angle for every (location, bucket) pair; the training loop rebuilds x each epoch from the cached images. Locations stay on the frozen list_crops grid (no position jitter, so the no-eval-leakage guarantee in pipeline/dataset.py is untouched). model/model.py, the loss, and the exported inference path are unchanged. Conclusion at the time REVERTED — metric did not improve (2509.97 m vs best 2489.03 m); change discarded Re-measured on today's ruler Mission score 1.981 — 1.0% of held-out frames give a usable fix, 99.0% are confident and wrong, 0.0% abstain. Median miss 2.19 km. Measured, not converted: its exported model was re-run through the current scorer for this page. What it cost
by model — claude-fable-5 $1.67 · claude-haiku-4-5-20251001 $0.00 The agent lines are the equivalent API cost of the tokens each agent used, as recorded by the harness when it ran. The research ran on a claude.ai Max subscription, so that is what it would have cost billed per token — not money that left the account. The GPU line is real spend. What it was measured against then Optimised: worst-case median error (m), target 20 m, scoring 2.51 km. Asked over: region holdout, 6 lighting buckets, 4 areas. The long grind: 62 experiments over ten days against a general-purpose, four-area, six-lighting-bucket problem. Scored on a region holdout that asked a memorization model to locate ground it had never been shown. Scoreboard — re-scored today Produced by running this experiment's exported model through the current frozen scorer on the current eval set. It is not what this experiment was told at the time.
era Four areas, six lighting buckets · ts 2026-07-20T20:26:38 ·
commit de614d47e3ea · artifacts runs/20260720_201946_iter5 ·
took 6 m 52 s
The code for this experiment — not retainedSource not retained. Until 2026-07-31 the loop discarded a reverted experiment’s code outright, and this row predates that fix. Its design record, its verdict and its exported model survive; the code does not. It is left blank rather than filled with the parent’s code, which is what this row’s recorded commit actually contains. | |||||||||||||||||||||||||||
| ▸ | 2.5 | Hierarchical coarse-to-fine supervision of the probability field via probability pooling
7d02e02d
The loop discarded this — no improvement under the metric then in force. |
loss | 1.991 | 6 m 20 s | $1.91 | DISCARDED | ||||||||||||||||||||
|
The design under test — technical diagram — gray = frozen contract · ink = the current design · red = this experiment's change In plain words The model localizes by painting a heat map over a 32x32 grid of the area — but we only have enough training photos to give most grid squares zero examples, so the heat map never learned to commit and stayed lukewarm everywhere. The fix is to grade the same heat map at several zoom levels at once: first 'did you put the heat in the right quarter of the map?', then the right sixteenth, and so on down to the fine grid. Coarse questions have plenty of examples to learn from, and getting them right automatically steers the fine answer — like learning an address by first nailing the district, then the street, then the house. Hypothesis The kept 32x32 field stays near-uniform (confirmed by exp-3's argmax test and exp-4's failure to beat it) because supervision density in label space is below one example per class: at 10 m/px with a 24 px sampling stride a ~7 km area yields only ~600 distinct train locations per bucket spread over 1,024 fine cells, so the Gaussian-CE mostly teaches 'not here' uniformly and no cell ever accumulates enough positive gradient to stand out. Sum-pooling the same softmax field to 16x16, 8x8, and 4x4 and applying the identical Gaussian-CE at each level gives coarse cells 10-40 positive training locations each — dense enough to learn real discrimination — and because coarse probabilities are exact sums of fine-cell probabilities, that well-conditioned coarse gradient flows directly into the fine logits ('put mass somewhere in this quadrant'), letting the fine level refine a coarse decision instead of learning 1,024-way classification from scratch. Method model/model.py loss_fn only: reshape softmax(logits) to [B,32,32], build coarser fields by 2x2/4x4/8x8 sum-pooling (avg_pool2d * factor^2), and compute the existing Gaussian-smoothed cross-entropy at levels k in {4, 8, 16, 32} with sigma held constant in map units (TARGET_SIGMA_CELLS * k / GRID_K cells), averaging the four CE terms in place of the single fine CE. Forward pass, decode, export, and train.py are untouched — zero new parameters, identical ONNX artifact shape. Conclusion at the time REVERTED — metric did not improve (2516.25 m vs best 2489.03 m); change discarded Re-measured on today's ruler Mission score 1.991 — 0.5% of held-out frames give a usable fix, 99.5% are confident and wrong, 0.0% abstain. Median miss 1.87 km. Measured, not converted: its exported model was re-run through the current scorer for this page. What it cost
by model — claude-fable-5 $1.91 · claude-haiku-4-5-20251001 $0.00 The agent lines are the equivalent API cost of the tokens each agent used, as recorded by the harness when it ran. The research ran on a claude.ai Max subscription, so that is what it would have cost billed per token — not money that left the account. The GPU line is real spend. What it was measured against then Optimised: worst-case median error (m), target 20 m, scoring 2.52 km. Asked over: region holdout, 6 lighting buckets, 4 areas. The long grind: 62 experiments over ten days against a general-purpose, four-area, six-lighting-bucket problem. Scored on a region holdout that asked a memorization model to locate ground it had never been shown. Scoreboard — re-scored today Produced by running this experiment's exported model through the current frozen scorer on the current eval set. It is not what this experiment was told at the time.
era Four areas, six lighting buckets · ts 2026-07-20T20:19:45 ·
commit 7d02e02d9ca2 · artifacts runs/20260720_201325_iter4 ·
took 6 m 20 s
The code for this experiment — not retainedSource not retained. Until 2026-07-31 the loop discarded a reverted experiment’s code outright, and this row predates that fix. Its design record, its verdict and its exported model survive; the code does not. It is left blank rather than filled with the parent’s code, which is what this row’s recorded commit actually contains. | |||||||||||||||||||||||||||
| ▸ | 2.4 | ACE-style dense per-patch scene-coordinate regression replaces global map-cell probability field
7d02e02d
The loop discarded this — no improvement under the metric then in force. |
architecture | 2.001 | 9 m 11 s | $2.92 | DISCARDED | ||||||||||||||||||||
|
The design under test — technical diagram — gray = frozen contract · ink = the current design · red = this experiment's change In plain words Rather than one verdict per photo, the network was made to answer 64 times per photo: the image is split into an 8×8 grid of patches, and each patch must separately name the map coordinate of the ground it shows (the training data knows the right answer for every patch). The final position is the average of those 64 mini-answers. The hope: 64× more feedback per photo forces the network to learn which ground textures give away a location. It scored slightly worse than the heat-map approach (~2.56 km vs ~2.49 km), so it was reverted. Hypothesis The encoder never learns geo-discriminative local features because supervision is one target per 128x128 crop: the GAP bottleneck plus a single global 32x32 map-cell field gives every conv feature the same diffuse gradient, which is why fields stay near-uniform (exp-3 refuted multimodality) and errors sit at ~2.1-2.5 km, barely under the 3.2 km center-guess bound, uniformly across all lighting buckets. Dense scene-coordinate regression (the ACE/DSAC family CLAUDE.md §3 explicitly names) supervises each of the 8x8 stride-16 feature cells with the map coordinate of its own patch — computable at train time from crop center + rotation — multiplying supervision density 64x on the same 4,800-crop budget and forcing local textures to become location-predictive. Because the patch-offset grid is symmetric about the crop center and rotation is linear, the unweighted mean of per-cell predictions is an unbiased crop-center estimate requiring no heading knowledge at inference, so the frozen rotated-eval contract is satisfied unchanged. Method In model/model.py: replace the GAP -> 1024-way map-cell logits head with (a) one 3x3 dilation-2 conv context stage on the 8x8x128 feature map (receptive field ~95 px, block-level context per cell), (b) a 1x1 conv -> sigmoid head emitting per-cell normalized (u,v) map coordinates, (c) forward output = unweighted mean of the 64 per-cell coordinates plus the existing loose sigmoid conf head on GAP'd context features. Loss becomes per-cell smooth-L1 against per-cell targets plus the unchanged 0.1-weighted conf BCE. In model/train.py: load_training_tensors computes the [2,8,8] per-cell target grid per sample by rotating the fixed patch-offset lattice by the sampled crop angle and adding the crop-center pixel coordinate (helper cell_target_grid in model.py). ONNX export contract, CLI contract, and crop sampling untouched. Conclusion at the time REVERTED — metric did not improve (2563.37 m vs best 2489.03 m); change discarded Re-measured on today's ruler Mission score 2.001 — 0.0% of held-out frames give a usable fix, 100.0% are confident and wrong, 0.0% abstain. Median miss 2.35 km. Measured, not converted: its exported model was re-run through the current scorer for this page. What it cost
by model — claude-fable-5 $2.92 · claude-haiku-4-5-20251001 $0.00 The agent lines are the equivalent API cost of the tokens each agent used, as recorded by the harness when it ran. The research ran on a claude.ai Max subscription, so that is what it would have cost billed per token — not money that left the account. The GPU line is real spend. What it was measured against then Optimised: worst-case median error (m), target 20 m, scoring 2.56 km. Asked over: region holdout, 6 lighting buckets, 4 areas. The long grind: 62 experiments over ten days against a general-purpose, four-area, six-lighting-bucket problem. Scored on a region holdout that asked a memorization model to locate ground it had never been shown. Scoreboard — re-scored today Produced by running this experiment's exported model through the current frozen scorer on the current eval set. It is not what this experiment was told at the time.
era Four areas, six lighting buckets · ts 2026-07-20T20:13:25 ·
commit 7d02e02d9ca2 · artifacts runs/20260720_200413_iter3 ·
took 9 m 11 s
The code for this experiment — not retainedSource not retained. Until 2026-07-31 the loop discarded a reverted experiment’s code outright, and this row predates that fix. Its design record, its verdict and its exported model survive; the code does not. It is left blank rather than filled with the parent’s code, which is what this row’s recorded commit actually contains. | |||||||||||||||||||||||||||
| ▸ | 2.3 | Argmax-anchored local soft-argmax decode replaces global expected-coordinate
ff65590e
The loop discarded this — no improvement under the metric then in force. |
architecture | 1.971 | 4 m 58 s | $1.25 | DISCARDED | ||||||||||||||||||||
|
The design under test — technical diagram — gray = frozen contract · ink = the current design · red = this experiment's change In plain words An attempted fix for experiment 2's “dragged toward the middle” problem. Rather than averaging the whole heat map, the model first commits to the single hottest cell, then fine-tunes its answer using only the 5×5 neighborhood around it — decide roughly where first, then refine locally. It backfired: the hottest cell is apparently often wrong (the heat maps are still too scattered), so committing to it made the error worse (~2.9 km vs ~2.5 km) and the change was reverted. Hypothesis Global soft-argmax over the 32x32 field decodes the probability-weighted mean of ALL cells, so any diffuse or multimodal probability field is pulled toward the map centroid — the kept exp-2 result (worst-case median 2489 m) barely beats the 3217 m center-guess bound, consistent with predictions huddling near the map mean rather than committing to the dominant mode. Anchoring the decode at the argmax cell and taking soft-argmax only within a +/-2-cell window forces mode commitment while keeping differentiable sub-cell refinement, and the training coordinate loss is computed on exactly this decode so train and inference stay coupled. Method In model/model.py forward(): after softmax over the 1024 cell logits, take the argmax cell, build a window mask |gx-gx*|<=2 and |gy-gy*|<=2 (5x5 cells, ~1.1 km on a ~7 km map), renormalize probabilities inside the window, and decode (u,v) as the expected coordinate over the window only. The mask is non-differentiable (straight-through-style); gradients flow through the windowed probabilities. CE-against-Gaussian-target term, conf head, train.py, and the ONNX export contract are unchanged — the exported graph uses ArgMax/Mod/floor-Div ops supported at opset 17. Conclusion at the time REVERTED — metric did not improve (2937.73 m vs best 2489.03 m); change discarded Re-measured on today's ruler Mission score 1.971 — 1.5% of held-out frames give a usable fix, 98.5% are confident and wrong, 0.0% abstain. Median miss 2.18 km. Measured, not converted: its exported model was re-run through the current scorer for this page. What it cost
by model — claude-fable-5 $1.25 · claude-haiku-4-5-20251001 $0.00 The agent lines are the equivalent API cost of the tokens each agent used, as recorded by the harness when it ran. The research ran on a claude.ai Max subscription, so that is what it would have cost billed per token — not money that left the account. The GPU line is real spend. What it was measured against then Optimised: worst-case median error (m), target 20 m, scoring 2.94 km. Asked over: region holdout, 6 lighting buckets, 4 areas. The long grind: 62 experiments over ten days against a general-purpose, four-area, six-lighting-bucket problem. Scored on a region holdout that asked a memorization model to locate ground it had never been shown. Scoreboard — re-scored today Produced by running this experiment's exported model through the current frozen scorer on the current eval set. It is not what this experiment was told at the time.
era Four areas, six lighting buckets · ts 2026-07-20T20:04:13 ·
commit ff65590e776f · artifacts runs/20260720_195914_iter2 ·
took 4 m 58 s
The code for this experiment — not retainedSource not retained. Until 2026-07-31 the loop discarded a reverted experiment’s code outright, and this row predates that fix. Its design record, its verdict and its exported model survive; the code does not. It is left blank rather than filled with the parent’s code, which is what this row’s recorded commit actually contains. | |||||||||||||||||||||||||||
| ▸ | 2.2 | DSNT-style spatial probability field over the map replaces direct (u,v) regression
ff65590e
The loop kept this — it beat the running best under the metric then in force. On today's ruler it scores 2.001 — worse than silence. |
architecture | 2.001 | 7 m 08 s | $2.06 | KEPT | ||||||||||||||||||||
|
The design under test — technical diagram — gray = frozen contract · ink = the current design · red = this experiment's change In plain words Instead of blurting out one guess, the network now lays a 32×32 grid of cells over the map and gives each cell a score for “the photo probably came from here” — a heat map of suspicion. Its answer is the balance point (weighted average) of that heat map. Training became much more informative — every cell is told individually whether it should have been hotter or colder — and the error improved from ~3.2 km to ~2.5 km. Remaining weakness: averaging a spread-out heat map drags answers toward the middle of the map. Hypothesis Direct MSE regression to sigmoid (u,v) collapses to the map-mean (center-guess ~3.2 km) because under early-training ambiguity the MSE optimum IS the mean and the gradient gives no per-location signal; the one prior classification attempt (archived exp 5) failed for a different reason — hard argmax cells + a decoupled offset head quantize the decision and decouple training from the metric. A dense probability field over a 32x32 grid of map cells, decoded by differentiable soft-argmax (expected coordinate) and trained with cross-entropy against Gaussian-smoothed cell targets plus an expected-coordinate L2 term, gives per-cell gradient signal that escapes the mean-collapse while remaining trained end-to-end on the exact coordinate the metric measures. Method In model/model.py, replace TinyLocNet's Linear(128->3)+sigmoid head with: Linear(128->1024) map-cell logits over a 32x32 grid (~220 m cells at 7 km), softmax, expected (u,v) = probability-weighted sum of fixed cell centers (registered buffers, ONNX-friendly), plus an unchanged separate sigmoid conf head trained with the same loose BCE target (err < 0.5 extent) so the coverage gate is untouched. Loss becomes CE(softmax logits, Gaussian soft target sigma=1.5 cells) + expected-coord L2 + 0.1*conf BCE. model/train.py changes only the loss call to pass logits through. Conclusion at the time KEPT — metric improved (3216.74 -> 2489.03 m); change committed as ff65590 Re-measured on today's ruler Mission score 2.001 — 0.0% of held-out frames give a usable fix, 100.0% are confident and wrong, 0.0% abstain. Median miss 1.89 km. Measured, not converted: its exported model was re-run through the current scorer for this page. What it cost
by model — claude-fable-5 $2.06 · claude-haiku-4-5-20251001 $0.00 The agent lines are the equivalent API cost of the tokens each agent used, as recorded by the harness when it ran. The research ran on a claude.ai Max subscription, so that is what it would have cost billed per token — not money that left the account. The GPU line is real spend. What it was measured against then Optimised: worst-case median error (m), target 20 m, scoring 2.49 km. Asked over: region holdout, 6 lighting buckets, 4 areas. The long grind: 62 experiments over ten days against a general-purpose, four-area, six-lighting-bucket problem. Scored on a region holdout that asked a memorization model to locate ground it had never been shown. Scoreboard — re-scored today Produced by running this experiment's exported model through the current frozen scorer on the current eval set. It is not what this experiment was told at the time.
era Four areas, six lighting buckets · ts 2026-07-20T19:59:14 ·
commit ff65590e776f · artifacts runs/20260720_195206_iter1 ·
took 7 m 08 s
The code for this experiment — its code, from the commitRead back out of this experiment’s own commit. It was kept, so the loop committed model/ and then recorded that commit — the tree is this experiment’s code. · on GitHub ↗model/model.py114 lines model/train.py102 lines | |||||||||||||||||||||||||||
| ▸ | 2.1 | Starting baseline — naive TinyLocNet, from-scratch, frozen pipeline v1
b47d44a8
The loop kept this — it beat the running best under the metric then in force. On today's ruler it scores 1.991 — worse than silence. |
architecture | 1.991 | — | — | KEPT | ||||||||||||||||||||
|
The design under test — technical diagram — gray = frozen contract · ink = the current design · red = this experiment's change In plain words The deliberately-dumb starting point. A tiny neural network looks at a single 128 m square of aerial photo and, in one shot, blurts out two numbers: how far across and how far up the map that view belongs. With so little capacity and training it effectively learns to always point near the middle of the map, which is why its guesses are ~3.2 km off. Its only job is to be the reference every later experiment must beat. Hypothesis A deliberately naive ~300k-param CNN (strided conv stack, direct coordinate regression, loose confidence head) trained 8 epochs on 4,800 crops/area cannot memorize a ~7 km area at 1 m detail — its error should sit near the center-guess bound (~3 km), establishing the reference point the loop must beat. Method Frozen pipeline: 1 m/px open DOP orthophotos (source registry), 6-bucket relighting with ambient-compensated auto-exposure and golden/blue-hour grading, leakage-guarded 360 m block split (~45k train positions/area). One model per development area (berlin, prignitz, munich, frankfurt), ONNX export, frozen §6 scoring. Conclusion at the time Starting baseline for the autoresearch loop. Every subsequent experiment is measured against this on the same frozen eval data (no further eval-set changes are planned). Bootstrap-phase history (7 experiments incl. two deliberate eval-set revisions) is archived in archive/bootstrap-experiments.sqlite. Re-measured on today's ruler Mission score 1.991 — 0.5% of held-out frames give a usable fix, 99.5% are confident and wrong, 0.0% abstain. Median miss 2.38 km. Measured, not converted: its exported model was re-run through the current scorer for this page. What it cost No agent accounting survives for this experiment — its run directory was deleted. That is a missing receipt, not a zero. What it was measured against then Optimised: worst-case median error (m), target 20 m, scoring 3.22 km. Asked over: region holdout, 6 lighting buckets, 4 areas. The long grind: 62 experiments over ten days against a general-purpose, four-area, six-lighting-bucket problem. Scored on a region holdout that asked a memorization model to locate ground it had never been shown. Scoreboard — re-scored today Produced by running this experiment's exported model through the current frozen scorer on the current eval set. It is not what this experiment was told at the time.
era Four areas, six lighting buckets · ts 2026-07-20T19:50:47 ·
commit b47d44a8fb64 · artifacts runs/bootstrap_baseline_v4 ·
took —
The code for this experiment — its code, from the commitRead back out of this experiment’s own commit. It was kept, so the loop committed model/ and then recorded that commit — the tree is this experiment’s code. · on GitHub ↗model/model.py79 lines model/train.py101 lines | |||||||||||||||||||||||||||
| ▸ | 1.7 | Relight v1 — ambient-compensated auto-exposure + golden/blue-hour grading (eval-set reset)
678dd1e7
The loop kept this — it beat the running best under the metric then in force. On today's ruler it scores 1.991 — worse than silence. |
relighting | 1.991 | — | — | KEPT | ||||||||||||||||||||
|
Hypothesis The v0 sensor model's uncompensated auto-exposure re-brightened every bucket up to the same target, making morning-through-evening nearly indistinguishable (user-observed). Scaling the exposure target with ambient (real AE under-exposes as light falls) plus golden-hour warmth and the existing blue-hour ramp should produce a visible day->dusk->night progression (measured tile brightness 0.56 -> 0.49 -> 0.41 -> 0.35 -> 0.27), giving the model genuinely distinct lighting domains; the naive baseline should stay in the center-guess regime. Method pipeline/relight.py: AE target = TARGET_MEAN * (0.25 + 0.75 * ambient^0.7); ambient color gains a Gaussian golden-hour warm tint peaking at ambient 0.5. All five areas re-rendered; baseline retrained (8 epochs, matching the loop default) and rescored. Deliberate frozen-pipeline revision during the bootstrap window, agreed with the human; marks an era boundary in the dashboard. Conclusion at the time KEPT as the new Phase 2 starting best on relight v1 eval data (era reset — prior scores not comparable). Lighting buckets are now genuinely distinct domains. Re-measured on today's ruler Mission score 1.991 — 0.5% of held-out frames give a usable fix, 99.5% are confident and wrong, 0.0% abstain. Median miss 2.38 km. Measured, not converted: its exported model was re-run through the current scorer for this page. What it cost No agent accounting survives for this experiment — its run directory was deleted. That is a missing receipt, not a zero. What it was measured against then Optimised: worst-case median error (m), scoring 3.22 km. Asked over: region holdout, 6 lighting buckets, 4 areas. Harness proving runs. The imagery itself changed mid-era: experiments 1-2 ran on 10 m/px Sentinel-class tiles, and from experiment 3 on it was 1 m/px orthophoto (commit c0a1bdf, 2026-07-20 18:02 UTC). A 10 m/px model fed 1 m/px crops is not being asked the same question at all, so the first two are the only experiments in the whole project that cannot be placed on today's ruler. Scoreboard — re-scored today Produced by running this experiment's exported model through the current frozen scorer on the current eval set. It is not what this experiment was told at the time.
era Bootstrap · ts 2026-07-20T19:43:59 ·
commit 678dd1e7acd2 · artifacts runs/bootstrap_baseline_v4 ·
took —
The code for this experiment — its code, from the commitRead back out of this experiment’s own commit. It was kept, so the loop committed model/ and then recorded that commit — the tree is this experiment’s code. · on GitHub ↗model/model.py79 lines model/train.py101 lines | |||||||||||||||||||||||||||
| ▸ | 1.6 | Per-epoch crop resampling — train on fresh positions/rotations every epoch instead of a fixed 4,800-crop snapshot
4959f8fb
The loop discarded this — no improvement under the metric then in force. |
training | 1.991 | — | — | DISCARDED | ||||||||||||||||||||
|
Hypothesis The split rebalance (exp 4) exposed ~45k train positions per area, but train.py still pre-extracts one fixed sample of 800 crops/bucket (4,800 total) and iterates it for all 8 epochs, so the model sees ~1.8% of available positions with one fixed rotation each. Exp 5's failure isolated data volume/diversity (not coordinate parameterization) as the suspected binding constraint keeping the model in the center-guess regime (~2968 m): with so few distinct positions the loss-minimizing generalization to unseen eval crops is still 'predict the middle'. Resampling a fresh 800/bucket set (new positions AND new rotation angles) at the start of every epoch raises distinct training crops from 4,800 to up to ~38,400 over 8 epochs, directly attacking that constraint while holding architecture, loss, batch size, learning rate, and epoch count fixed. Method model/train.py only: load the 6 relight bucket images and the train crop list once, then move crop sampling/extraction inside the epoch loop — each epoch draws a fresh rng.choice of positions per bucket and a fresh uniform rotation per crop, builds that epoch's tensors, trains on them, and discards them. model/model.py, loss, optimizer, lr, batch size, and the CLI/ONNX contracts are untouched. Conclusion at the time REVERTED — metric did not improve (3124.07 m vs best 2968.56 m); change discarded Re-measured on today's ruler Mission score 1.991 — 0.5% of held-out frames give a usable fix, 99.5% are confident and wrong, 0.0% abstain. Median miss 2.39 km. Measured, not converted: its exported model was re-run through the current scorer for this page. What it cost No agent accounting survives for this experiment — its run directory was deleted. That is a missing receipt, not a zero. What it was measured against then Optimised: worst-case median error (m), scoring 3.12 km. Asked over: region holdout, 6 lighting buckets, 4 areas. Harness proving runs. The imagery itself changed mid-era: experiments 1-2 ran on 10 m/px Sentinel-class tiles, and from experiment 3 on it was 1 m/px orthophoto (commit c0a1bdf, 2026-07-20 18:02 UTC). A 10 m/px model fed 1 m/px crops is not being asked the same question at all, so the first two are the only experiments in the whole project that cannot be placed on today's ruler. Scoreboard — re-scored today Produced by running this experiment's exported model through the current frozen scorer on the current eval set. It is not what this experiment was told at the time.
era Bootstrap · ts 2026-07-20T19:18:56 ·
commit 4959f8fb8867 · artifacts runs/20260720_191230_iter2 ·
took —
The code for this experiment — not retainedSource not retained. Until 2026-07-31 the loop discarded a reverted experiment’s code outright, and this row predates that fix. Its design record, its verdict and its exported model survive; the code does not. It is left blank rather than filled with the parent’s code, which is what this row’s recorded commit actually contains. | |||||||||||||||||||||||||||
| ▸ | 1.5 | Grid-cell classification + within-cell offset regression replaces direct (u,v) MSE regression
7b7b491a
The loop discarded this — no improvement under the metric then in force. |
loss | 1.976 | — | — | DISCARDED | ||||||||||||||||||||
|
Hypothesis The baseline's direct sigmoid-(u,v) regression under MSE is what pins the score at the center-guess bound (~2968 m): with visually aliased crops across a ~7 km area, the loss-minimizing response to ambiguity is the mean of plausible positions, i.e. the map center, so gradient descent never leaves that regime. Reparameterizing localization as softmax classification over a 16x16 grid of normalized-coordinate cells (cross-entropy) plus a regression head for the fractional offset within the cell gives a loss whose minimum under ambiguity is a multi-modal distribution over candidate cells rather than their average, letting the model commit to discrete place hypotheses; argmax decoding at inference then escapes the center-collapse entirely. Method model/model.py: keep the conv backbone identical; replace the 3-unit head with three heads off the GAP feature — 256 cell logits (16x16 grid over normalized u,v; bbox-generic since cells are defined in [0,1]^2), a 2-unit sigmoid within-cell offset head, and the unchanged loose-BCE confidence head (so coverage does not collapse and attribution stays on the localization mechanism). loss_fn becomes cross-entropy on the true cell + MSE on the true within-cell offset + 0.1*conf BCE. forward() decodes argmax cell + offset to [[u,v,conf]] so the ONNX export contract and frozen scorer are untouched. model/train.py changes only the loss call to use the raw heads. Conclusion at the time REVERTED — metric did not improve (3429.15 m vs best 2968.56 m); change discarded Re-measured on today's ruler Mission score 1.976 — 1.2% of held-out frames give a usable fix, 98.8% are confident and wrong, 0.0% abstain. Median miss 2.95 km. Measured, not converted: its exported model was re-run through the current scorer for this page. What it cost No agent accounting survives for this experiment — its run directory was deleted. That is a missing receipt, not a zero. What it was measured against then Optimised: worst-case median error (m), scoring 3.43 km. Asked over: region holdout, 6 lighting buckets, 4 areas. Harness proving runs. The imagery itself changed mid-era: experiments 1-2 ran on 10 m/px Sentinel-class tiles, and from experiment 3 on it was 1 m/px orthophoto (commit c0a1bdf, 2026-07-20 18:02 UTC). A 10 m/px model fed 1 m/px crops is not being asked the same question at all, so the first two are the only experiments in the whole project that cannot be placed on today's ruler. Scoreboard — re-scored today Produced by running this experiment's exported model through the current frozen scorer on the current eval set. It is not what this experiment was told at the time.
era Bootstrap · ts 2026-07-20T19:12:30 ·
commit 7b7b491a32b5 · artifacts runs/20260720_190612_iter1 ·
took —
The code for this experiment — not retainedSource not retained. Until 2026-07-31 the loop discarded a reverted experiment’s code outright, and this row predates that fix. Its design record, its verdict and its exported model survive; the code does not. It is left blank rather than filled with the parent’s code, which is what this row’s recorded commit actually contains. | |||||||||||||||||||||||||||
| ▸ | 1.4 | Split rebalance — 360 m blocks so the no-leakage buffer stops eating the train area
2e5b3020
The loop kept this — it beat the running best under the metric then in force. On today's ruler it scores 1.996 — worse than silence. |
other | 1.996 | — | — | KEPT | ||||||||||||||||||||
|
Hypothesis With 144 m blocks, the rotation-window buffer around eval blocks rejected most train positions (~11k train vs ~15.6k eval per area — 59% of usable positions were eval). Enlarging blocks to 360 m should cut buffer frequency and roughly quadruple train positions (~45k per area) without weakening the leakage guarantee; the naive baseline's score should stay in the center-guess regime since 3 epochs on 4.8k crops still cannot memorize a 7 km area. Method pipeline/dataset.py BLOCK_PX 144 -> 360 (frozen-pipeline data revision during bootstrap, before Phase 2 starts; eval sets change, so this is a new comparison baseline). Baseline model and training unchanged. Conclusion at the time KEPT as the new Phase 2 starting best (eval sets changed, so prior scores are not comparable). Training-data bottleneck removed: the frozen split now offers ~4x more train positions per area. Re-measured on today's ruler Mission score 1.996 — 0.2% of held-out frames give a usable fix, 99.8% are confident and wrong, 0.0% abstain. Median miss 2.25 km. Measured, not converted: its exported model was re-run through the current scorer for this page. What it cost No agent accounting survives for this experiment — its run directory was deleted. That is a missing receipt, not a zero. What it was measured against then Optimised: worst-case median error (m), scoring 2.97 km. Asked over: region holdout, 6 lighting buckets, 4 areas. Harness proving runs. The imagery itself changed mid-era: experiments 1-2 ran on 10 m/px Sentinel-class tiles, and from experiment 3 on it was 1 m/px orthophoto (commit c0a1bdf, 2026-07-20 18:02 UTC). A 10 m/px model fed 1 m/px crops is not being asked the same question at all, so the first two are the only experiments in the whole project that cannot be placed on today's ruler. Scoreboard — re-scored today Produced by running this experiment's exported model through the current frozen scorer on the current eval set. It is not what this experiment was told at the time.
era Bootstrap · ts 2026-07-20T18:55:57 ·
commit 2e5b3020e8f9 · artifacts runs/bootstrap_baseline_v3 ·
took —
The code for this experiment — its code, from the commitRead back out of this experiment’s own commit. It was kept, so the loop committed model/ and then recorded that commit — the tree is this experiment’s code. · on GitHub ↗model/model.py79 lines model/train.py101 lines | |||||||||||||||||||||||||||
| ▸ | 1.3 | Data v2 baseline — same TinyLocNet on 1 m/px DOP imagery, all 4 dev areas
c0a1bdfe
The loop kept this — it beat the running best under the metric then in force. On today's ruler it scores 2.001 — worse than silence. |
other | 2.001 | — | — | KEPT | ||||||||||||||||||||
|
Hypothesis Switching reference imagery from 10 m Sentinel-2 to 1 m open DOP orthophotos (UAV-scale crops, 128 m footprint) resets the task: the naive baseline should again land near the center-guess bound (~2.5-3 km for ~7 km boxes) since 3 epochs on 4.8k crops cannot memorize a 7 km area at 1 m detail, but the harness must run unchanged across all four development areas, proving the registry-driven fetch and GSD-aware relighting are bbox- and source-generic. Method Frozen pipeline data v2 (pipeline/sources.yaml registry: BB-BE/BY/HE/HH DOP WMS at 1 m/px, Sentinel-2 fallback; GSD-aware relighting with meter-denominated lamp density and glow). Unchanged TinyLocNet + training procedure, 3 epochs, 800 crops/bucket, per-area models for berlin/prignitz/munich/frankfurt. Conclusion at the time Data v2 harness proven across all four development areas on 1 m/px DOP imagery; starting best for Phase 2 set to 2765.67 m. The abstention gate demonstrably works — a real (not hypothetical) failure mode was caught twice. Re-measured on today's ruler Mission score 2.001 — 0.0% of held-out frames give a usable fix, 100.0% are confident and wrong, 0.0% abstain. Median miss 2.22 km. Measured, not converted: its exported model was re-run through the current scorer for this page. What it cost No agent accounting survives for this experiment — its run directory was deleted. That is a missing receipt, not a zero. What it was measured against then Optimised: worst-case median error (m), scoring 2.77 km. Asked over: region holdout, 6 lighting buckets, 4 areas. Harness proving runs. The imagery itself changed mid-era: experiments 1-2 ran on 10 m/px Sentinel-class tiles, and from experiment 3 on it was 1 m/px orthophoto (commit c0a1bdf, 2026-07-20 18:02 UTC). A 10 m/px model fed 1 m/px crops is not being asked the same question at all, so the first two are the only experiments in the whole project that cannot be placed on today's ruler. Scoreboard — re-scored today Produced by running this experiment's exported model through the current frozen scorer on the current eval set. It is not what this experiment was told at the time.
era Bootstrap · ts 2026-07-20T18:02:26 ·
commit c0a1bdfe7738 · artifacts runs/bootstrap_baseline_v2 ·
took —
The code for this experiment — its code, from the commitRead back out of this experiment’s own commit. It was kept, so the loop committed model/ and then recorded that commit — the tree is this experiment’s code. · on GitHub ↗model/model.py79 lines model/train.py101 lines | |||||||||||||||||||||||||||
| ▸ | 1.2 | Harness smoke test (SKIP_AGENT loop.sh iteration, 1 epoch)
0687f20e
The loop kept this — it beat the running best under the metric then in force. It cannot be placed on today's ruler at all — see the row for why. |
other | — | — | — | NO SCORE | ||||||||||||||||||||
|
Hypothesis loop.sh mechanics (train/score/log/keep-revert/gallery) function without an agent Conclusion at the time Loop mechanics verified; metric delta is training-noise, not an improvement in method Not on today's ruler It ran on 10 m/px imagery, before the switch to 1 m/px orthophotos. Feeding it today's crops would not be asking it the same question, so it is left unscored rather than given a number that means nothing. What it cost No agent accounting survives for this experiment — its run directory was deleted. That is a missing receipt, not a zero. What it was measured against then Optimised: worst-case median error (m), scoring 2.19 km. Asked over: region holdout, 6 lighting buckets, 4 areas. Harness proving runs. The imagery itself changed mid-era: experiments 1-2 ran on 10 m/px Sentinel-class tiles, and from experiment 3 on it was 1 m/px orthophoto (commit c0a1bdf, 2026-07-20 18:02 UTC). A 10 m/px model fed 1 m/px crops is not being asked the same question at all, so the first two are the only experiments in the whole project that cannot be placed on today's ruler. era Bootstrap · ts 2026-07-20T17:21:22 ·
commit 0687f20ea168 · artifacts runs/20260720_172115_iter1 ·
took —
The code for this experiment — its code, from the commitRead back out of this experiment’s own commit. It was kept, so the loop committed model/ and then recorded that commit — the tree is this experiment’s code. · on GitHub ↗model/model.py74 lines model/train.py101 lines | |||||||||||||||||||||||||||
| ▸ | 1.1 | Bootstrap baseline — naive TinyLocNet direct-coordinate regressor
15b43a25
The loop kept this — it beat the running best under the metric then in force. It cannot be placed on today's ruler at all — see the row for why. |
architecture | — | — | — | NO SCORE | ||||||||||||||||||||
|
Hypothesis A ~300k-param from-scratch CNN trained for 3 epochs will produce a valid end-to-end harness run but nowhere near the 20 m target; its error should approach the trivial 'predict the area center' bound (~2.9 km for a 7 km box), confirming the metric, gates, and lineage plumbing are trustworthy before the autoresearch loop starts. Method Frozen pipeline run unmodified on two structurally different bboxes (berlin dense-urban, prignitz rural): Sentinel-2 fetch -> 6-bucket relighting -> per-area TinyLocNet training with rotation augmentation -> ONNX export -> frozen §6 scoring with deployment gates. Conclusion at the time Harness proven end-to-end on two areas with unmodified bbox-generic pipeline; baseline behaves as hypothesized (near center-guess bound). Ready for Phase 2 loop. Not on today's ruler It ran on 10 m/px imagery, before the switch to 1 m/px orthophotos. Feeding it today's crops would not be asking it the same question, so it is left unscored rather than given a number that means nothing. What it cost No agent accounting survives for this experiment — its run directory was deleted. That is a missing receipt, not a zero. What it was measured against then Optimised: worst-case median error (m), scoring 2.93 km. Asked over: region holdout, 6 lighting buckets, 4 areas. Harness proving runs. The imagery itself changed mid-era: experiments 1-2 ran on 10 m/px Sentinel-class tiles, and from experiment 3 on it was 1 m/px orthophoto (commit c0a1bdf, 2026-07-20 18:02 UTC). A 10 m/px model fed 1 m/px crops is not being asked the same question at all, so the first two are the only experiments in the whole project that cannot be placed on today's ruler. era Bootstrap · ts 2026-07-20T17:19:49 ·
commit 15b43a251d60 · artifacts runs/bootstrap_baseline ·
took —
The code for this experiment — its code, from the commitRead back out of this experiment’s own commit. It was kept, so the loop committed model/ and then recorded that commit — the tree is this experiment’s code. · on GitHub ↗model/model.py74 lines model/train.py101 lines | |||||||||||||||||||||||||||
Every experiment the project has run, across 5 evaluation eras. The lineage was wiped at each era boundary, so the numbering restarts inside every band — but the vertical scale is one ruler throughout. 53 of these were re‑measured for this chart by running their exported model through today’s frozen scorer on today’s held‑out viewpoints; 4 are native to the current era; 4 had their run artifacts deleted and have their usable- and false-fix rates recovered arithmetically from the logged record; 17 failed a deployment gate and had no working model to score, then or now. Nothing was converted by a fudge factor. Black means the loop kept it — judged at the time, under whatever metric was then in force, which is why kept experiments sit high in the early bands: the old metric was rewarding the wrong thing. The step line is the best mission score anyone had actually achieved, and it is unbroken across the boundaries because the ruler no longer changes at them.