Discover and Define

The lake polygon is a computational input, not a pin.

Fetch scores eight Minnesota lakes for fishability across seven days by running forecast wind against real shoreline geometry.

Mapping apps already hold the shape of every lake. They render it as a picture, put a wind readout in a panel beside it, and leave the hardest inference to the angler: how today's wind interacts with this specific basin. Fetch treats the shape as arithmetic instead. Fifteen rays fan across an eighty-four degree cone at every point on every shoreline, at thirty-six wind bearings, precomputed. What comes back is a scored week you can take apart, with the age of the depth map behind each number visible on screen.

8 lakes 7 days in 3-hour blocks 448 scored cells 4 craft 92 recorded decisions

Both links open a working prototype, not a shipped product. The visual design is at Phase 1 and a second pass is planned. The footage above is atmosphere; it is not one of the eight lakes and it is not output from the model.

PhaseDiscover and Define Visual designPhase 1 of 2 ForecastRecorded snapshot, 2026-08-03 ArtifactsLive
01 · The gap

Every tool aims you at water, then hands off

Pick any mapping app built for anglers and the funnel is the same. You filter, you narrow, you land on a waterbody page, and there the product stops. It has produced a list. It has not produced a plan.

That would be forgivable if the missing step were hard to compute. It is not. The same page is already holding the polygon, the bathymetry, a seven-day forecast with wind speed and direction, and a barometric trend. Every input needed to answer the actual question is present on screen at the same moment, and none of them are connected to each other.

Three gaps came out of the audit, and the third turned out to be the one worth building against.

Gap 01
The funnel ends at selection

The tool narrows you to a lake and stops. Nothing turns a filtered list into a decision about which day, which hour, or which bank.

Gap 02
The conditions data is inert

Wind, pressure, and moon sit as raw readouts. Nothing connects them to a judgment, and nothing connects wind direction to the shape of the basin it is blowing across.

Gap 03
Confidence is invisible

Every input has a vintage and nothing on screen shows it. In this data set the depth-map fieldwork runs from 1944 to 2001 and all of it renders identically.

Gap three is the largest unaddressed information design problem in the category, and it is the only one that is purely a design problem rather than an engineering one.
02 · The audit

The assets are already there. The connections are not.

Discovery here was not user interviews. It was an inventory: walk the category end to end, list what each product already owns, and mark what it does with each thing. The pattern that fell out is that the raw material for the missing feature is universally present and universally idle.

Assets held versus assets used, from the public product surface
Asset already held What it is used for today What it could also do
Waterbody polygon Drawn as an outline. A picture. Ray casting. It is the only input that encodes exposure.
Bathymetric contours Drawn as a depth map. Carrying its own survey date as a confidence signal.
Wind speed and direction A number and an arrow in a panel. The bearing that decides which bank is working.
Barometric trend A number, sometimes a sparkline. The pre-frontal window, which is the whole reason anglers watch it.
Survey catch data A table, with its survey year printed. Already handled well. This one is not the problem.

That last row matters more than it looks. Catch data is the one input in the category that already ships with its provenance attached. Geometry, which is far more load-bearing, ships with none. The product has the right instinct and has applied it to the wrong asset.

03 · When the plan met the data

The confidence story I planned did not survive acquisition

The original plan was to build the confidence layer around stale fish surveys. Find a lake nobody had netted in fifteen years, render its catch numbers with the age attached, and let the contrast do the work. It was a good plan and it was wrong.

Acquisition killed it in an afternoon. Every one of the eight lakes had been surveyed between 2022 and 2025. Waconia alone has sixteen surveys on record going back to 1973. There is no stale fish survey in this geography, because the state agency here is unusually diligent. The premise was not weak, it was absent.

The depth maps were a different story. Mille Lacs is running on fieldwork completed on the seventeenth of July, 1944. Minnetonka is 1949. Forest Lake 1958, Waconia 1959. Lake Elmo is the outlier at 2001, and it is the only one flagged as a GPS survey.

Why the replacement is stronger, not a fallback D-27

Catch data never fed the score. Geometry does. The thesis says the polygon is a computational input, so the vintage of that input is the confidence question that actually matters. Moving the beat closed my own loop instead of borrowing someone else's data problem.

Rejected
  • Swap in a remote lake with a genuinely old survey. It would have preserved the original story and cost two others: the four-lakes-in-one-county argument, which needs lakes that share a weather system, and the drive-from-the-metro job that the whole thing is built around. Preserving a narrative by damaging the sample is the wrong trade.
  • Quietly drop the confidence layer. It was the most distinctive part of the concept. Cutting it because the first version of the evidence did not appear would have been the easy call and the wrong one.
Framing discipline, held everywhere

Unverified, not inaccurate. A basin's outline barely moves in eighty years. Littoral contours and vegetation move a great deal. The line is that the fieldwork is eighty-two years old and nobody has checked since, so the interface shows you that. It is never that the data is wrong.

04 · The problem, and what I rejected

One problem statement, and the three I threw away first

Three gaps narrowed to one sentence. The narrowing is the Define phase, and the rejected framings are the part of it worth reading, because each one was plausible enough to have been built.

Anglers are handed a filtered list of lakes and a panel of raw numbers, and left to do the hardest inference themselves: how today's wind interacts with this specific lake's shape.
Rejected framings
  • Anglers need better fishing reports. The leading products in this category deliberately reject the community-report model, and they are right to. Designing against a stated product principle is not boldness, it is a failure to read the room.
  • Anglers need better species data. I am not going to out-fish a state agency's biologists. Not my edge, and more importantly not a design problem. It is a data acquisition problem wearing a design costume.
  • The app needs AI. A solution looking for a problem. There is a natural language entry point in the roadmap and it is genuinely interesting, but leading with it would have meant following a trend instead of following the user.

What survived is a problem statement that names an inference rather than a feature. That distinction is what kept the build from drifting into a wish list, because every subsequent decision could be tested against one question: does this make the inference easier, or does it just add surface?

05 · The two jobs

Two jobs, and the layout falls out of them

The same person has two different jobs on two different days, and they want opposite things from the interface.

Primary · Planning

When a weather window opens and I have one day to fish, I want to know which water will actually be fishable in those conditions, so I can commit to a plan instead of guessing and burning the day.

Secondary · Execution

When I am at the ramp and the wind is not what was forecast, I want to see which shoreline is protected or working right now, so I can re-plan in thirty seconds without leaving the truck.

The anxiety underneath both is the same and it is specific: the fear of driving two hours to unfishable water. The habit being replaced is cross-referencing a weather app, a lake map, and a survey document, then guessing at the relationship between them.

Those two jobs decided the layout, which is the part I would want a design team to interrogate. Above 1024 pixels the app shows a matrix, eight lakes down the side and fifty-six three-hour blocks across, because the planning job is comparative and wants density. Below 1024 it shows a ranked list for one selected day plus the map, because the execution job is not comparative at all. It has one question and thirty seconds.

The claim I would defend D-08

This is not a responsive convenience and it is not a squeezed grid. Two purpose-built surfaces, because two jobs. The map is genuinely responsive at every width, since it serves both.

Rejected
  • One responsive layout. A matrix that reflows to a phone is a matrix nobody can read on a phone. It also happens to be more work than building two honest surfaces, which is worth saying out loud because the lazy option and the correct option were the same option here.
  • A native mobile build. Out of scope for an exploration. Mobile ships as designed screens instead.
06 · Reading the category

Somebody already shipped this, and saying so is the stronger position

The comfortable version of this section claims novelty. The scan does not support it, and claiming it anyway would invite exactly one response from anyone who knows the space.

One competitor ships the mechanic. Deep Dive added a wind fetch layer in June 2025. It is a bass and tournament focused app at $79.99 a year, rated 4.7 across roughly five thousand ratings. It computes wind fetch alongside speed and direction, and it accumulates that exposure over time.

The interesting part is not that they shipped it. It is what happened next.

Within a year they renamed the layer from Wind Effects to Wave Impacts. That rename is the finding. The hard part of this is legibility, not computation.

A team that can compute effective fetch across a continent of waterbodies still had to go back and change what they called it, which means the arithmetic was never the bottleneck. Making a person understand what the number means, in a glance, on a boat, is the bottleneck. That is a design problem, and it is the one this exploration is actually about.

What they do better, stated plainly D-77

Deep Dive accumulates exposure over roughly twelve to twenty-four hours. This model is instantaneous: it reads the wind in a block and computes against it, with no memory of what the wind was doing yesterday. Bait concentrates on a lag, so accumulation is closer to the truth. That is a real capability gap and it is named here rather than buried.

Nobody else in the space computes fetch at all. The charting products map the water and the catch but never run wind against shape. Everything else treating effective fetch seriously is academic literature rather than shipped software.

Rejected D-71
  • Framing the work as a novel idea. A novelty claim invites "prove the idea," which is a conversation about whether the concept is real. Reading the category instead shows the scan is already done and moves the conversation to where the structural advantage sits, which is coverage and breadth rather than the mechanic itself.
  • Omitting the competitor entirely. A product that argues for provenance cannot be evasive about the competitive picture. The whole case rests on showing the reader what is behind each claim, and that has to include the inconvenient ones.
07 · Gray boxes first

The unstyled build is the artifact, not the embarrassment

Nothing got a color until the whole thing worked. Real data, real model, real interaction, rendered in gray boxes and a system font, verified by grep to contain zero color values.

The reason is not aesthetic modesty. If the information architecture only becomes legible once the visual design arrives, the visual design is compensating for a structure that does not stand up. Building the gray version first makes that impossible to hide from yourself. Either you can read the week in plain boxes or you cannot.

The embed below is that state, still deployed, still running the live model. Click any cell and the map flies, the shoreline shades by computed exposure, and the decomposition panel fills with the terms that produced the number.

White-label prototype /?lake=waconia&block=13&craft=tiller&wind=10&panel=decomp
Live prototype loads on scroll Waconia · block 13 · tiller · decomposition open
Interactive. Waconia at a wind of 10 mph with the decomposition panel open. Open full screen

Every embed on this page is the deployed application at a frozen state, addressed by query string. Nothing here is a screenshot.

Rejected D-21
  • Designing and building at the same time. It feels faster and it hides which of the two is carrying the interface. Separating them meant the design layer later became a change to token values in one file rather than a rework.
  • Throwing the gray build away once the design landed. It is frozen at a git tag and still served, because it is the only evidence that the structure stood on its own.
08 · The model

Effective fetch, by a published method, against a real polygon

Fetch is the distance wind travels over open water before it reaches a point on the bank. The naive version casts one ray upwind and measures it. That version is wrong in a way that matters on a lobed lake, because a single ray can slip through a gap that no real wave field would.

The model uses effective fetch by the Shore Protection Manual method, from Saville. Fifteen radials fan across the upwind bearing, terminating at plus and minus forty-two degrees, so the cone spans eighty-four degrees at six degree spacing, cosine weighted toward the center.

15 rays at 6° increments across ±42° of the upwind bearing

F_eff = Σ(Xᵢ · cos θᵢ) / Σ(cos θᵢ)

Ray offsets:  −42 −36 −30 −24 −18 −12 −6  0  6  12  18  24  30  36  42
Why forty-two and not forty-five D-30

The first draft said plus and minus forty-five degrees, which is what everyone writes from memory. Fifteen rays at six degree spacing span eighty-four degrees total, so the correct figure is forty-two. Forty-five with fifteen rays would need 6.43 degree spacing and is not the published geometry. A wrong constant sitting next to a cited source is worse than no citation at all.

Shoreline is sampled at roughly 150 metre intervals. Islands and interior rings block fetch, which is not a corner case here: six of the eight lakes have them and Minnetonka has forty-seven. A ray whose first five metres leave the polygon returns zero rather than a spurious hit across the county. Everything is precomputed at build time across thirty-six bearings and written as raw integer metres, with no normalization on disk, so the color ramp can change without regenerating a single data file.

Wind dial, sweeping /?lake=waconia&block=13&craft=tiller&auto=sweep
Live prototype loads on scroll Waconia · wind direction sweeping automatically
The wind direction sweeps and the shoreline recolors live. A west wind puts long fetch on the east bank, which is the whole argument in one motion. Open full screen

The zero fraction is a proof, not a statistic

Before running the precompute I worked out what share of the results should come back as exactly zero. Effective fetch is zero only when the entire eighty-four degree cone lies over land, which happens across 180 minus 84, or ninety-six degrees out of three hundred and sixty. That predicts 26.7 percent.

Observed was 26.9 to 28.0 percent, and the deviation tracked shoreline convolution exactly as it should: Waconia, the roundest basin in the set, came in lowest at 26.9, and Upper Prior, the most cut up, came in highest at 28.0. The model was not adjusted to make that land.

Per-lake effective fetch, from the precompute, measured 2026-07-31
Lake Points Median (m) Max (m) Zero %
Waconia1064243,58026.9%
Minnetonka921803,83927.3%
Mille Lacs1,06119924,40927.2%
Upper Prior58881,08528.0%
Big Marine1741172,34227.8%
Lake Elmo4512795227.2%
Forest Lake1691552,23827.5%
White Bear1422872,73427.5%
Totals2,67617.2 s wall · 390 KB · minimum is 0 for every lake

The result that made the thesis true rather than plausible

Minnetonka covers 14,206 acres. Waconia covers 3,080. More than four times the area. Minnetonka's maximum fetch is 3,839 metres against Waconia's 3,580, a difference of 4.7 percent.

Minnetonka's median fetch is 80 metres. Waconia's is 424. Fourteen thousand acres with less exposure than a lake a fifth its size, because forty-seven islands and a lobed basin mean almost nowhere has an open run.

Area tells you nothing. Shape tells you everything. Two numbers, one table row, no setup required. That is the moment the thesis stopped being a nice sentence and became something a reader can check.

09 · The constant that passed

The model passed every automated test and was algebraically a constant

The scoring composition ran clean. The pipeline executed, the data flowed, every check went green, and the numbers looked like numbers. Then I looked at the screen and the entire tiller matrix was fives.

Not mostly fives. Every cell. Four hundred and forty-eight cells reading five.

The first version composed the score from two opposed halves: structure activation, the ninetieth percentile of windblown exposure, and shelter, defined as one minus the twenty-fifth percentile. That second term is where it died. Every lake in this set carries a zero-fetch fraction of roughly twenty-seven percent, so the twenty-fifth percentile always landed inside the zeros. Shelter was therefore identically 1.000, on every lake, at every block, forever.

base01 = craft.structure · structure + craft.control · shelter

tiller weights are 0.50 / 0.50
shelter ≡ 1.000  (p25 always lands in the ~27% zero fraction)

base01 = 0.50 · structure + 0.50 · 1.000
... and after the display stretch, every tiller score rounds to 5
What the tests were actually asserting D-35

That the function returned a number in range. Which it did, flawlessly, every time. No check asked whether the output varied, because it had not occurred to anyone that it might not. A constant is a number in range.

The fix separated the two halves so neither could collapse into the other. Shelter fraction became the share of shoreline points sitting below a raw exposure threshold, which is wind-independent lee geometry. Control became comfort, the headroom below the craft's gust ceiling, mitigated by that lee. And the display stretch stopped being a guess.

shelterFraction = share of points with t < 0.25        wind independent
comfort         = 1 − clamp(windMph / craft.gate, 0, 1)
control         = comfort + (1 − comfort) · shelterFraction
base01          = craft.structure · structure + craft.control · control

stretched = clamp((base01 − 0.329) / (0.8513 − 0.329), 0, 1)
The bounds are measured, not chosen D-36

0.329 and 0.8513 are the fifth and ninety-fifth percentiles of non-gated base01 across all 448 cells and four craft, from 1,535 samples with a base01 range of 0.1931 to 0.9760. They were derived from the distribution and then not touched. The constants were not tuned after seeing the output, which is the difference between a measurement and a fudge.

Before 1

Distinct tiller score in the entire matrix. Every cell read five.

After 9

Distinct tiller scores, spread across the range with the peak where you would expect it.

Found by 0

Automated checks. It was found by looking at the screen.

The matrix, after the fix /?view=matrix&lake=waconia&block=13&craft=tiller
Live prototype loads on scroll Matrix view · 8 lakes × 56 blocks · tiller
Eight lakes down, fifty-six three-hour blocks across. Four hundred and forty-eight cells, and they are not all fives. Open full screen
Same score, opposite reasons. At block 13, Waconia and Minnetonka both score seven. Waconia earns it through exposure, with a structure term of 0.856. Minnetonka earns it through lee, with a shelter fraction of 0.575. Shape decides how a lake is good, not only how good.
10 · Guarding the premise

Four times a green test was protecting nothing at all

The constant was the first lesson. Four more followed, and together they changed how the whole thing was verified. A passing check is only worth what its premise is worth, and premises rot quietly while the check keeps reporting green.

  1. 01
    A test outlived the thing it was testing, by seventeen and a half hours D-63

    One check asserted that the largest lake always renders a maximum-exposure shore band. Later that same day the render input deliberately changed from raw geometry to geometry multiplied by wind activation, which meant the band now required extreme fetch and active wind together. The map was never wrong. The assertion had simply been describing a version of the product that no longer existed. It was rebuilt as two assertions, one of which exists purely to catch a revert of that change, and both derive their test conditions at runtime rather than hardcoding a wind level that a new forecast would silently invalidate.

  2. 02
    A new cell state was absorbed into the bin it most resembled D-64

    The matrix originally had two kinds of cell, scored and gated, and the check split them two ways. Then a third state arrived: the cell whose forecast direction is too uncertain to score, which renders a question mark. It was not gated, so it fell into scored, and it broke the numeral and background assertions immediately. It stayed hidden for a while because the previous check halted the run before this one could fail. The fix partitions all 448 cells into three disjoint bins and asserts that the three add to 448, so a fourth state fails loudly instead of being quietly adopted by whichever bin looks closest.

  3. 03
    Two different inputs collapsed to the same lake, so the comparison proved nothing D-70

    A check loaded two lakes and compared their map zoom levels. It passed. It also passed for the wrong reason: a permissive slug lookup let both inputs fall back to the same default lake, so the test was comparing a lake to itself. Equality is trivially true when both sides are the same thing. The check now asserts that the two loads resolved to genuinely different lake identifiers before it compares anything, and the permissive alias was removed so a malformed input fails visibly instead of resolving to something plausible.

  4. 04
    The check asserted content existed, never that it was true D-81

    A separate harness verified that every slide of the presentation had content in every register. All green. Reading the deployed page and measuring against the model found four errors sitting underneath that green: two copy claims that contradicted the model they were standing in front of, one number rounded the wrong way against a table printed on the same slide, and one live embed whose map rendered a completely different lake than the two being compared, because the URL omitted a parameter and the app fell back to its default. A new check now asserts that every embed URL names its lake explicitly, since a silent default is exactly how the wrong lake shipped.

Guard the premise, not the result. Before trusting a passing check, ask what would have to be true for it to pass trivially, then assert that too.

The pair below is the check that would catch a regression rather than merely report one. Same lake, same block, two craft with different gust ceilings. The kayak week is shot through with hard gates, 267 of 448 cells across the set. The deep-V week has two.

Kayak · gust ceiling 18 mph /?view=matrix&lake=mille-lacs&block=13&craft=kayak
Live prototype loads on scroll Mille Lacs · block 13 · kayak
A gate is not a low score. It renders as a hatch and a cross with no numeral, because bad and cannot are different semantics. Open full screen
Deep-V · gust ceiling 45 mph /?view=matrix&lake=mille-lacs&block=13&craft=deepv
Live prototype loads on scroll Mille Lacs · block 13 · deep-V
Same water, same week, same weather. The only variable is what the boat can take. Open full screen

The same lesson, in color

The color work failed the same way twice, which is why it belongs in this section rather than in one of its own.

Rejected, and why each failed D-42 · D-43
  • An eleven-step continuous score scale. It cleared every constraint on paper. Eleven cool steps, constant chroma, each one perceptually distinct, all in gamut, all passing contrast. On screen the light half was a near-white smear with adjacent steps measuring a color distance of six, and a nine was indistinguishable from a ten. Four measured bands dissolved the problem, because with a quarter of the steps the perceptual budget per step quadruples.
  • A ramp separated by lightness alone. The perceptual distance formula penalizes lightness differences at high lightness, so the pale end collapsed no matter how the values were placed. The fix was a full re-spread to a deeper end with hue angles held, not a nudge of the offending pair.
  • Trusting the audit's own first numbers. The script sampled color at shoreline vertices, but the map colors each segment by the mean of its two endpoints, so the sampler kept landing on the wrong side of a band boundary and reporting distances that were not real. Sampling at segment midpoints with the matching band fixed it. The measurement tool had the same class of bug as the thing it was measuring.

One incidental finding worth keeping: a three pixel stroke's anti-aliased core sampled about one unit below its own token value, so the stroke width is four pixels. Legibility was a measured result, not a styling preference.

11 · Confidence, and 1944

Two kinds of not knowing, never averaged together

Every value on screen carries confidence from two independent sources, rendered two different ways, and they are never collapsed into a single number.

Axis one · Forecast
Measured, not assumed

Thirty-one ensemble members on a quarter degree grid. For each lake and block the model takes the circular standard deviation of wind direction across members and normalizes it from ten degrees to ninety. Below ten, the fetch model's own angular resolution, a spread is indistinguishable from none. At ninety, a half plane of plausible bearings, the direction is unresolved and the cell is not scored.

Axis two · Geometry
The age of the shape

The vintage of the bathymetric fieldwork behind each lake, running from 1944 to 2001 in this set. It degrades the structure terms, which are the geometry derived half of the score. Rendered as a halo beneath the shoreline, never on it, so age never competes with the exposure reading it qualifies.

Why circular, and why it matters D-11

Compass bearings wrap. 350 degrees and 010 degrees are twenty degrees apart, not three hundred and forty. A linear standard deviation reads that pair as an enormous spread and garbles the confidence of every cell near due north. The spread is computed from the mean resultant vector of the members' bearings, which respects the wrap. This is the sort of thing that is invisible when correct and quietly wrong forever when not.

Why they are never merged D-28

One is how predictable the wind is. The other is how old the shape is. They come from different places and fail in different ways. A single combined confidence number would hide which input is uncertain, and which input is uncertain is the only thing that tells a reader what to distrust.

Mille Lacs, 1944, against Lake Elmo, 2001

The depth map feeding the largest lake in this set is fieldwork completed on the seventeenth of July, 1944. Eighty-two years ago. It carries 531 contours and it renders, in every product I looked at, exactly the same way as Lake Elmo's 2001 GPS survey with fourteen.

The pair below is the same view of both, at the same zoom, so the halo is the only difference. This is the state I would put in front of a design team first, because it is the one that is a design decision rather than an engineering one.

Mille Lacs · fieldwork 1944 /?lake=mille-lacs&block=13&craft=tiller&zoom=13&at=-93.632,46.3655
Live prototype loads on scroll Mille Lacs shoreline · bathymetry 1944, 82 years
Framed on a bank rather than the whole basin, so the halo is legible against the shore it qualifies. Open full screen
Lake Elmo · fieldwork 2001, GPS /?lake=lake-elmo&block=13&craft=tiller&zoom=13
Live prototype loads on scroll Lake Elmo · bathymetry 2001, 25 years
The same treatment, at the same zoom, on the newest geometry in the set. Open full screen
All eight lakes, survey year against bathymetric fieldwork date
Lake DOW Survey Bathymetry fieldwork
Waconia1000590020251959-08-12
Minnetonka2701330020221949-01-01
White Bear8201670020241978-05-01
Upper Prior7000720020251976-06-02
Forest Lake8201590020251958-09-03
Big Marine8200520020241981-08-03
Mille Lacs4800020020251944-07-17
Lake Elmo8201060020252001-08-16 (GPS)

Not one stale catch survey in the set. Every geometry input decades old. The confidence problem was never where the category assumed it was.

12 · The Phase 1 design

A first pass, exploring the matrix and the control labels

The designed surface is a parallel route, not a replacement. The gray prototype stays at the root, byte for byte, because the argument rests on both existing at once. If one had been restyled into the other, the process story would collapse into a claim.

This pass is exploring two things and is not finished with either. The first is the matrix itself: whether a score reads better as ink density or as a chromatic scale, and which direction the scale should climb. That question got answered twice in opposite directions before it settled. The achromatic version severed the matrix from the map that teaches the reader what color means; the chromatic version reunited them but had to be re-spaced so lightness climbs monotonically and the top tier is the brightest cell on screen.

The second is the language on the controls. Craft names, the gate string, the caution marker, the labels on both confidence treatments. Naming is where the competitor had to go back and rename their own layer, so it is not a finishing task here either.

Phase 1 design /designed?view=matrix&lake=waconia&block=13&craft=tiller
Live design pass loads on scroll Matrix view · Waconia · block 13 · tiller
The same model and the same data as every embed above. Only the presentation layer differs. Open full screen
Rejected D-57 · D-58 · D-90
  • An achromatic matrix. Reasoning was sound: the map is a measured condition and should be the only chromatic surface, while a score is a ranking and should read as ink density. In practice it cut the matrix off from the color language the map spends the whole session teaching, so a reader had to learn two systems instead of one.
  • Dark equals good. It inverted the mental model the map establishes. The scale now climbs in lightness and the top tier is the brightest thing on screen, measured as a monotonic luminance climb with no reversal.
  • Restyling the root route. It would have made every embed on this page show a designed application while claiming to be the unstyled one.
13 · Methodology and sources

Every constant, every limitation, every source

A product that argues for provenance has to apply the standard to itself. What follows is the full accounting, unsoftened, in the same order it appears on the application's own methodology page.

The score, end to end

Per shoreline point, at the block's wind bearing

  t            = normalizeFetch(fetch)                  raw geometry, per point
  normalizeFetch(m) = clamp((ln m − ln 50) / (ln 6000 − ln 50), 0, 1),  m ≤ 50 → 0
                 FETCH_FLOOR = 50 m      FETCH_CEIL = 6000 m
  activation   = clamp((windMph − 3) / 7, 0, 1)         no windblown effect in dead calm
  e            = t · activation

Per lake / block / craft

  structure       = percentile(e, 90)
  shelterFraction = share of points with t < 0.25
  comfort         = 1 − clamp(windMph / craft.gate, 0, 1)
  control         = comfort + (1 − comfort) · shelterFraction
  base01          = craft.structure · structure + craft.control · control
  stretched       = clamp((base01 − 0.329) / (0.8513 − 0.329), 0, 1)
  score10         = clamp(round(stretched · M · 10), 0, 10)

  M  conditions multiplier: pressure trend over the prior 12 h, cloud cover
     against the light window, solunar at low weight and labelled low evidence.
     Clamped to [0.6, 1.25].

Protected is not good. Structure and control pull in opposite directions. Windblown points concentrate bait; protection is about handling the boat. The craft weights therefore decide how a lake is good, not only how good. And a hard gate is not a low score: wind above a craft's gust ceiling renders a distinct glyph and no numeral, because bad and cannot are different semantics.

Craft

The four craft, exact values. Caution and gate are gust thresholds in mph.
Craft Caution Gate Structure Control
Kayak12180.350.65
Tiller (default)18250.500.50
Console25350.650.35
Deep-V32450.800.20
Not a safety standard

These ceilings are my judgment, tuned so the hard gate is legible across a realistic range of boats. They are not published guidance. Nothing on the application or on this page tells anyone it is safe to go out. A score describes conditions against a chosen craft, it never implies clearance, and the gated and unknown states are designed screens rather than green lights.

What this model does not do

The full list, in order, unsoftened. This list exists because the alternative is asking a reader to trust a number without knowing where it stops being reliable.

  1. Island shorelines are not scored. Exterior rings only.
  2. Fetch is planar, using a local equirectangular projection. Adequate under 60 km at 45 to 47 degrees north.
  3. No refraction, shoaling, or wave modelling. Fetch is exposure distance, not wave height.
  4. Craft ceilings are the author's judgment, not a published standard.
  5. Solunar is an approximation at deliberately low weight, labelled low evidence.
  6. The display stretch bounds were fitted to one forecast snapshot and are deliberately not re-fitted to later ones, because re-fitting weekly would move every score under the reader's feet.
  7. One matrix tier's numeral contrast clears AA for large text rather than full AA.
  8. Shoreline segment shading uses the mean of its two endpoint values, not the maximum.
  9. The polygon source host is stated correctly in the sources below, which corrects an earlier credit to a different endpoint of the same state data portal.
  10. The model is instantaneous. It does not track accumulation, the twelve to twenty-four hour lag between wind starting and bait concentrating. The competitor named in section six does. This is a real gap and it is named rather than hidden.
  11. The ice roadmap concept is modelled from geometry alone. No ice thickness, no temperature history, no safety guidance, and it must never imply ice is safe.

Sources and credits

Public agency data only, non-commercial use, all credited by name.

Minnesota Department of Natural Resources

Lake polygons from DNR Hydrography and Public Waters Inventory basins, retrieved from the ArcGIS REST endpoint at enterprise.gisdata.mn.gov. Lake bathymetry from the Minnesota Geospatial Commons. Fish surveys from the LakeFinder detail endpoint. The DNR retains copyright on the bathymetry; commercial use requires a signed agreement. Bathymetry is display-only here and does not feed the score. Polygons are stored at full source precision, never rounded and never simplified, because they are the model's input.

Open-Meteo

The seven-day forecast and the GFS ensemble behind the forecast confidence axis. No API key. The application defaults to a recorded snapshot captured 2026-08-03 rather than a live call, with a labelled control to switch, so a demonstration never depends on a working network.

OpenStreetMap contributors and CARTO

The Positron basemap. No API key. Chosen partly because a keyless basemap removes an entire class of failure from a live demonstration.

MapLibre GL JS

Map rendering.

The application publishes its own methodology page carrying these same constants, generated from the source rather than transcribed. Read the full methodology and sources page.

What Phase 2 needs

The open questions are the interesting part

Accumulation over twelve to twenty-four hours, because instantaneous is the known gap. A second design pass on the matrix and the control language. And the two roadmap concepts that are presented rather than built: pinning several lakes side by side, and an ice window modelled from geometry with no safety claim attached to it.

The value of this exploration is not that the argument is right. It is that every constant, source, and limitation is on the page, so a team that knows this domain can tell me exactly where it is wrong.