Skip to content

feat(core): version fields and cache derived contour geometry#15

Merged
nmrtist merged 1 commit into
mainfrom
feat/field-runtime-contour-cache
Jul 25, 2026
Merged

feat(core): version fields and cache derived contour geometry#15
nmrtist merged 1 commit into
mainfrom
feat/field-runtime-contour-cache

Conversation

@nmrtist

@nmrtist nmrtist commented Jul 25, 2026

Copy link
Copy Markdown
Owner

Summary

Separates a 2D contour into the three things it actually is — the field data, the
level policy resolved against it, and the geometry a worker builds — and gives
each a lifetime of its own. Contour geometry is no longer built on the thread
that asks for a figure: the main thread resolves a ContourSpec into absolute
levels, a worker turns a neutral scalar grid plus those levels into segments, and
style is applied only when the figure is assembled.

The practical effect is that changing a contour colour or line width reuses
cached geometry instead of re-running marching squares over the whole grid, and
that a field's noise or background estimate is measured once and shared rather
than recomputed per plot.

What changed

Three-stage derived geometry

  • A field provider owns the grid and its cheap min/max summary. The main thread
    resolves ContourSpec + summary + estimate into ResolvedContourLevels. The
    worker receives only ScalarGrid2D + those levels — no spec, no summary, no
    estimate crosses the thread boundary.
  • ContourStyle is applied during figure assembly, so it is not part of any
    cache key. Two series that resolve to the same levels on the same field version
    share one geometry regardless of how they are styled.

Runtime field versions

  • FieldVersion is a session-only monotonic token owned by ComputeService. It
    advances for everything that changes marching-squares input — values, shape,
    bounds, sampling — and for nothing that does not. It is neither serialized nor
    part of undo; persisted provenance lives in the field catalog instead.
  • Estimates and geometry are addressed by content, keyed on
    VersionedFieldRef. A worker result whose field has since advanced is
    discarded rather than installed, so a setting changed mid-flight cannot be
    overwritten by the result it superseded, and stale entries need no
    traversal-based invalidation.
  • Both caches are bounded — geometry by an aggregate byte budget and an entry
    cap, estimates and summaries by entry count — and neither evicts a key whose
    work is still in flight.

On-demand typed estimates

  • EstimateField computes noise and background estimates on demand, memoized per
    (field version, kind, estimator selection), so a field that only uses a noise
    σ never pays for a background plane fit. A new encoding freezes the estimator
    identity it was created with, so reopening a project cannot silently change
    levels because the algorithm was upgraded.
  • A zero scale is a valid, cacheable measurement rather than a failure. A flat or
    ideal synthetic field previously re-queued an identically failing job on every
    rebuild and stayed blank; it now resolves once. An estimator that genuinely
    cannot run remains an error that reaches the status line.

Capability derivation

  • Field capabilities derive from a cheap representation query — shape and
    per-axis sampling — so a descriptor lookup no longer materializes the whole
    grid. regular still comes from the field's actual sampling, and an explicitly
    sampled grid still does not qualify for contour or heatmap. A test asserts the
    cheap query and the materialized payload can never disagree.

Blank plots explain themselves

  • An explicit contour threshold above the field's peak is obeyed literally rather
    than rewritten to a value that happens to produce output, and the resulting
    empty half is reported with both the threshold and the peak — so a mistyped
    value is visible instead of leaving a blank plot with no cause to find.

Compatibility

Pre-release: no backward compatibility is kept for older project files. The
project schema version is unchanged. FieldVersion and the derived caches are
runtime state and never appear in a saved project.

Out of scope

DOSY and ILT analysis maps keep their existing generation-based staleness model.
They are independent analysis results rather than field-derived geometry, and
migrating them belongs with giving them a field identity of their own. A unified
parameter catalog and the contour parameter editing UI are likewise not part of
this change.

Testing

cargo pr-check passes: formatting, the source-size limit, dependency policy,
Clippy with warnings denied, and the test suite on both the reference and
DataFusion backends.

New tests cover style edits reusing cached geometry, equivalent levels sharing a
geometry key, a new field version missing old entries and rejecting stale worker
results, estimates coexisting per estimator selection, capability derivation
following actual sampling across every dataset variant, colored rasters carrying
an import version but no scalar summary or contour path, LRU eviction under both
budgets including an oversized entry and re-queueing after eviction, a degenerate
scale estimate resolving once instead of re-queueing, and an unreachable
threshold reporting both numbers.

Two probes back the claims that are otherwise untestable: a thread-local counter
asserts no marching squares runs on the calling thread, with a positive control
that fails if the instrumentation is ever dropped, and a payload counter asserts
a warm geometry cache allocates no full-grid buffer.

Contour geometry was still built on the thread that asked for a figure, so a
colour or line-width edit re-ran marching squares over the whole grid, and the
level policy, the field statistics it depended on, and the geometry itself were
resolved together with no way to reuse any of them.

Split derived geometry into three stages. A field provider owns the grid and its
cheap summary; the main thread resolves a `ContourSpec` into absolute levels; a
worker turns a neutral `ScalarGrid2D` plus those levels into segments and
receives no spec, summary or estimate. Style is applied when the figure is
assembled, so it never enters a cache key and a restyle reuses cached geometry.

`FieldVersion` is a session-only monotonic token owned by `ComputeService`. It
covers everything that changes marching-squares input — values, shape, bounds,
sampling — and nothing that does not, and it is neither persisted nor part of
undo. Estimates and geometry are addressed by content, so a stale worker result
cannot overwrite newer state and no traversal-based invalidation is needed. Both
caches are bounded and never evict a key whose work is still in flight.

Noise and background estimates are computed on demand by `EstimateField` and
memoized per estimator selection, so a field pays only for the estimator it
actually uses. A zero scale is a valid, cacheable measurement rather than a
failure, which stops a flat field from re-queueing an identically failing job on
every rebuild; an estimator that genuinely cannot run stays an error that
reaches the status line.

Field capabilities now derive from a cheap representation query — shape and
per-axis sampling — so a descriptor lookup no longer materializes the whole
grid, and `regular` still comes from the actual sampling rather than a domain
guess. An explicit contour threshold above the field's peak is obeyed literally
and reported with both the threshold and the peak, rather than silently drawing
nothing or being rewritten to a value that happens to produce output.
@nmrtist
nmrtist merged commit 168535b into main Jul 25, 2026
11 checks passed
@nmrtist
nmrtist deleted the feat/field-runtime-contour-cache branch July 25, 2026 05:26
@github-actions github-actions Bot locked and limited conversation to collaborators Jul 25, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant