## 0.1.0 existed to declare it against. It is not folded into a version here, and none of it is tagged, until a release is deliberately cut.lFriction_Velocity was created from fda, whose dof is 3, while every reader opened it through da, whose dof is 1. PETSc rejects that pairing outright, so enabling wall functions on a case with a WALL face aborted at the first boundary pass with Vector local size N is not compatible with DMDA local sizes N/3. The unit fixtures allocated it correctly and so never saw it. Storage is now created on da as a global/local pair, and tests/c/test_boundaries.c allocates the way production does and checks the allocation against the field catalog descriptor, so a DM or dof mismatch fails in the suite rather than only in a run.Utau, written when a wall model is active. It is not read back on restart, because the first boundary pass of the restarted run recomputes it from the restored velocity.<run.analysis.metrics>/wall_model.csv, one row per step: the friction velocity's mean, RMS and extrema over the corrected cells, the first-cell y+ and wall distance, and the cell count. y+ is formed inside the wall-model dispatch, where the wall distance is still in hand; nothing downstream can recover it. A step that corrects no cell warns and writes nothing rather than emitting zeros. Plottable as picurv summarize --plot wall_model.y_plus_mean.turbulent_channel: a driven periodic plane channel at Re_tau ~ 1000, wall-modelled. It is the wall-model counterpart to decaying_isotropic_turbulence - that case exercises the subgrid closure where there is no wall, this one exercises what only exists because there is: the wall model and its three laws, coefficient averaging over the two homogeneous axes of a wall-normal-inhomogeneous flow, the driven periodic pair, and the near-wall diagnostics. Distinct from periodic_test/driven_channel/les_retau180, which is wall-resolved constant-Smagorinsky at a Reynolds number forty times lower. Its uniform wall-normal grid puts the first cell at y+ ~ 51, which is where a wall model belongs and why the same mesh cannot be reused with the wall model off - a wall-resolved run needs a first cell near y+ = 1, clustered at both walls, which the programmatic generator's one-sided geometric stretch cannot produce. Verified as configuration and plumbing: it validates and runs, and reports y+ between 39 and 50 with the effective wall viscosity matching y+/u+ - 1. It ships a laminar Poiseuille initial condition and will not become turbulent from it; no generator here produces a perturbed field for a wall-bounded domain, since generators/ic.gen projects spectrally and assumes periodicity in all three directions. Its literature anchors - the log law, Dean's correlation, and Lee & Moser (2015) - are what it is built to be compared against, not comparisons that have been run.capability_families.json still marked both LES models known-defective with empty evidence, citing the Germano defect and the inert constant model that were fixed in this campaign, and pointed at capability_scope_records.json for deferred material that file does not contain; and the turbulence.wall_function record still said the integration "dispatches to the log law unconditionally", which stopped being true when the model selector was wired. Both models are now recorded experimental with the decaying-isotropic example as production evidence for the dynamic one - the constant model declares none, because no shipped example selects it. 12_Capabilities_Summary and 56_Field_Identity_and_Layout_Catalog were updated for the stress delivery, the pairing rules, and the two wall-model fields.u+/y+ of the modelled stress was delivered, about a fourteenth of it at y+ = 267. That zeroing is right for a wall-resolved run and backwards for a wall-modelled one. The wall pass now publishes an effective wall eddy viscosity, nu_eff = tau_w y / u, formed where the stress, the distance and the corrected speed are all still in hand, and the viscous flux uses it at that face. Reported as nu_wall_over_nu_mean; a 30-step channel shows the solutions with and without it separating monotonically.cabot or werner under RANS. A wall model on a laminar case is refused for the same reason from the other side. And because whether the first cell lands in the law's valid range depends on the mesh rather than the configuration, a y+ excursion warns on every diagnostic sample and stops the run after ten consecutive ones.observability.logging, at supported. It had been in no subsystem record, no capability family, no contract, no freshness surface, and in neither src/guide.md nor the module map of 13_Code_Architecture - the shape that leaves a module unowned is precisely that everything reports through it and nothing depends on it for a result. A change to what a user sees therefore tripped no staleness suspicion anywhere. Both module maps now carry it, 09_Monitor_Reference documents the append-and-continuation-marker lifecycle its runtime files follow across a restart, and a soft freshness surface watches src/logging.c and include/logging.h. The supported claim is about its behavioural contracts, which tests/c/test_logging.c covers; it is not a claim about any quantity another subsystem hands it to print.u_tau at every wall cell: wall_function() used its Newton initial guess as the answer. It now uses a closed-form taw_Werner()/u_Werner_explicit() pair - the profile integrated across the first cell, which is the Werner-Wengle model proper and inverts without any root-find, so the no-inner-iteration property the model is selected for is now real rather than claimed. The iterative f_Werner/df_Werner/find_utau_Werner helpers are retained but unwired, with the reasons recorded at their definitions: their branch threshold is written through the friction velocity being solved for, and their two branches invert different relations, so they reconstruct the pointwise u_Werner() only below y+ = 11.81 - which is the only range their unit test samples.les || rans block, so enabling a wall model without either of them left ApplyWallFunction dereferencing a null vector. wall_function is their sibling in the schema, not their child; allocation is now gated on the wall model alone. All three laws now vary cell to cell and differ at y+ ~ 10-14, and agree exactly at y+ ~ 1.4, where every wall model must reduce to u+ = y+.les.clipping.max_cs under a mode that imposes no ceiling is now an error rather than a warning. A ceiling that is not in force reads as one that is.07_Case_Reference corrected: -const_cs, -max_cs, -dynamic_freq and -testfilter_ik no longer exist, and les.max_cs is not an accepted key. The list now matches what the CLI emits.tests/c/test_les.c was never listed in tests/guide.md, and the 2026-03-20 coverage snapshot for src/les.c described a file that has since been rewritten. Both corrected; the assembled LES model path has not been re-measured.ApplyWallFunction() had no test. The velocity laws and their friction-velocity root-finds were already covered, but the wiring around them was not, and its plausible failures are silent: feeding the reference and boundary wall distances in the wrong order, or taking the reference velocity from the wrong cell, still produces a plausible corrected velocity. tests/c/test_boundaries.c now checks that the corrected first interior velocity is the modelled profile at that cell's wall distance for the friction velocity the integration stored, and separately that the stored friction velocity inverts the law at the reference cell's distance. Swapping the two distances in production is detected.07_Case_Reference claimed it already did; io.c contained no such line. The claim is made true rather than removed, and the line is pinned by the user-facing reporting contract.turbulence.wall_function has its own subsystem record. It was owned by the turbulence.rans record, which is the wrong home: a wall function is configured independently of both LES and RANS, and the RANS runtime update is incomplete while this treatment is separately usable.-wallfunction previously carried only an enable flag: the configured model was validated by the Python layer and then discarded, so every case ran the log law whatever it asked for, and ApplyWallFunction() dispatched to it unconditionally at all six faces. The flag now carries a WallFunctionModel code, one dispatcher replaces the six hardcoded call sites, and werner and cabot join log_law as selectable. Both were already implemented and unit-tested in src/wallfunction.c and had simply never been reachable. WallFunctionModelToString() reports the choice in the banner. Cabot's non-equilibrium pressure-gradient term is supplied as zero, reducing it to the equilibrium form; that is documented on its capability entry rather than implied.global, the wall plane under homogeneous retaining the wall-normal direction, and that cell alone under local. A cell left with no data takes a zero coefficient, which is what a wall model already supplying the stress calls for. Carried through the averaging kernel's inclusion mask, which was already there for the statistics pipeline.PicurvSpatialRatioAverage() now honours the target mask and the inclusion mask in its pointwise branch as well as its averaged one. It previously applied neither when no direction was selected, so an exclusion silently changed meaning depending on the averaging mode.ComputeScalarFieldDerivatives(). Supplying zero would have reduced it to its equilibrium form - not the model the selector names.roughness_height is rejected under werner and cabot. Neither has a roughness formulation - Werner-Wengle has no such term and Cabot discards the argument - so accepting the key would silently drop a value set on purpose.Contra2Cart rebuilds the Cartesian field at the top of each timestep and discarded the previous solve's near-wall correction, so the eddy viscosity was computed from the uncorrected field while the momentum equation used the corrected one - the two halves of one timestep disagreeing about the velocity. The wall model is re-applied before the strain rates are formed. This changes results for any case combining LES with a wall function. The remaining caveat, that an imposed profile contaminates the dynamic coefficient at the wall-adjacent cell, is documented on the closure page.M_ij previously built both of its terms from separately test-filtered quantities, so they shared an identical factor and the tensor collapsed to a fixed multiple of the filtered strain rate. The scale-similarity relation was therefore never evaluated: the model had no dynamic content, while still producing a positive, dimensionally correct, flow-responsive coefficient — which is why the defect survived. The second term is now the test filter of the product |S| S_ij, which is what the identity requires, and M_ij is projected trace-free so the Leonard stress's trace cannot enter the contraction through discrete divergence error.constant_cs directly each step, which both fixes the defect and removes a global vector, a local vector, a ghost exchange, and a periodic synchronization from that path.averaging.mode selects local, homogeneous, or global; homogeneous derives its directions from the case's periodic boundary pairs unless they are named, so a triply periodic box yields one coefficient and a channel yields a wall-normal profile with no extra configuration. Sums are volume-weighted, exclude solid cells and the periodic duplicate planes, and are reduced over the block's communicator, so the result does not depend on the decomposition.clipping.mode: none keeps the signed coefficient so backscatter survives, bounded instead by a total-viscosity floor that constrains the quantity which actually has to stay positive. The clipping modes remain available and remain biased: discarding only the negative tail raises the mean dissipation. The max_cs default drops from 0.5 to 0.3.ComputeCellCharacteristicLengthScale call that passed one output pointer twice is gone.nvert < 0.1 fluid test; the dynamic pass previously used a different threshold from the eddy-viscosity pass.CS now carries FIELD_AVAILABILITY_LES_DYNAMIC and holds the coefficient C that multiplies Delta^2 |S| — Cs^2 in the classical notation, signed when backscatter is admitted. Restarting from a checkpoint written before this change is not supported for that field.les.gradient_model.enabled. Combined with a Smagorinsky closure it is the mixed model.-les_* and grouped into one LESConfig block, with defaults defined once in LESConfigSetDefaults() so the control-file path and the test fixtures cannot drift. The dead -mixed, -testfilter_1d, and -i/j/k_homo_filter options are removed; the homogeneous-filter intent they carried is served by averaging.directions.<run.analysis.metrics>/les_coefficient.csv, including the pre-clipping backscattering and limited fractions, which no stored field preserves.les.c is rebuilt from named kernels — symmetric-tensor algebra, strain rate, filter width, the two Germano tensors, averaging, limiting, and viscosity assembly — each unit-tested in the new tests/c/test_les.c (make unit-les), with a decomposition-independence case in tests/c/test_mpi_kernels.c.known-defective to experimental, and both capability scope records are retired. It is not supported: the coefficient magnitude has not been validated against a reference flow. The check that would close that gap is examples/decaying_isotropic_turbulence with homogeneous averaging, where Cs(t) should settle near 0.16-0.17.SpatialTargetPlanCreate() and its solid mask through SpatialTargetPlanMaskAllows(), retiring a local range helper and a duplicate fluid threshold;les.c to PicurvSpatialRatioAverage() in statistics_target.*, generalised with an optional denominator, an optional inclusion mask, and a caller-chosen communicator. PicurvWindowSpatialMean() is now a thin caller of it; the statistics pipeline keeps count weighting and its existing communicator, so its output is unchanged. Volume weighting stays the LES caller's choice and is folded into the two contraction fields before the reduction;SimCtx flags rather than a second read of the boundary faces. The loader rejects a case whose opposite faces or whose blocks disagree, so the two cannot differ, and the flags are what both the DMDA and the spatial target were already built from;rhs.c uses SymTensor instead of nine hand-written components repeated in three places;statistics_accumulator.c is tied to SymTensor's member order by a compile-time assertion, so the two cannot drift apart unnoticed.nu + nu_t, using the same face average and wall zeroing Viscous() applies. Omitting the eddy term left the matrix modelling a viscous diagonal smaller than the operator's by the eddy-to-molecular ratio, which on a developed large-eddy simulation is order one or more; a plausible contributor to the recorded momentum-preconditioner bottleneck; The point-block oracle in tests/c/test_momentum_newton_krylov.c carries the term as an independent transcription from the residual's definition, with mutants for omitting it and for reading the cell value instead of the face average; both production mutations are detected;1/h amplification surfaces a loose inner tolerance as a degraded Jacobian action;ComputeRHS() already zeroes their rows, which leaves a solver that marches on the residual nothing to do but leaves a solver that assembles a matrix with an undetermined unknown. MomentumRowIsSolidMasked() gives those rows the same identity treatment as any other row without an unknown. The immersed-boundary method stays unsupported, because its velocity reconstruction does not run inside this solver's residual.les.diagnostics.yoshizawa_ci is now a documented key. It was parsed by the C runtime but had no YAML spelling and no emitter, so it was reachable only through PETSc passthrough — the same hidden-control defect the Clark term had;picurv summarize reads les_coefficient.csv, so --list-plot-series offers the les.* histories and --plot les.cs_effective renders the coefficient curve an LES run is judged on;converged = residual_abs_pass OR (residual_rel_pass AND update_pass). The absolute residual test is sufficient on its own; the relative test keeps the relative_tol update guard. Requiring the guard alongside the absolute test made the latter unable to fire: on the laminar channel at step 793, |R| fell below the floor at pseudo-iteration 8 and the step still ran to 20.residual_absolute_tol is now dimensionless, tested as |R| <= tol * resid_ref with resid_ref = a0*|Ucont|_inf/dt. A raw bound on |R| is not portable: across the shipped cases step-1 |R| spans 1.2e-2 (plane channels) to 2.0 (driven duct), a ~165x spread that normalising collapses to ~4x. Set to 1.0e-8 in all shipped configs.absolute_tol / -mom_atol no longer participates while a residual tolerance is set. |dU| ~ dtau*|R|, so bounding it absolutely is a disguised residual bound |R| <= absolute_tol/dtau that tightens as the controller grows dtau; it was in practice the criterion that gated convergence. It is retained, annotated, for the legacy update-only branch only. It is now deprecated: removed from all shipped configs (commented, with a note, in the master template), still accepted, and the CLI warns when it is set while it cannot take effect.-mom_resid_atol 1e-8, -mom_resid_rtol 1e-3, previously both 0.0). The update-only branch it replaces can converge falsely, since |dU| also goes small when dtau collapses. Setting both non-positive remains an explicit opt-out.U_b 0.999446, u_max 1.497795, u_tau 0.1729983 and profile error 1.470e-03 before and after; fields agree to 5.06e-13 at step 6000 and the gap between the two runs shrinks from 2.2e-07 (step 500) to 3.4e-13, so error does not accumulate. Worst |div u| unchanged (7.457e-11 vs 7.443e-11). Total pseudo-iterations fell 28,247 -> 16,390 (-42%), concentrated in the settled regime (3% over steps 1-500, ~50% thereafter); a flow that never settles should expect the smaller figure.monitor.yml -> solution_monitoring.convergence, preserving its existing flags, every-completed-step behavior, and log format while adding an effective enable switch;control as the single C-ingress artifact and removed the premature observation sidecar, schema/version envelope, exposed cadence, and unsupported-window startup gate;SimCtx::averaging, legacy sum vectors, su0/su1/su2/sp read/write hooks, the dead averaging call, old templates, and the reserved averaged-field postprocessor passthrough; andinclude/statistics_accumulator.h and src/statistics_accumulator.c allocate one accumulator set per window from the vector factory, duplicating every vector from one the factory already built, and apply an accepted state pointwise through the centered kernels. A three-vector's second moment is the six symmetric co-moments between component pairs in fixed (xx, xy, xz, yy, yz, zz) order, not three per-component variances. Per-point occupancy is tracked separately from the moments because the fluid mask can move. Storage is released through the same teardown that releases the block's other vectors, and the runloop driver applies accepted weights to every block. Covered by a new unit-statistics-accumulator suite, including an integration case that drives the runloop entry point and asserts only scheduled states reach the fields.include/statistics_window.h and src/statistics_window.c decide whether a completed state is accepted and what weight it carries, applying right-rectangle weighting, final-interval clipping, and the rule that a zero-length interval is not a sample. Step and physical-time cadences are both supported, with time targets on an absolute grid so the schedule cannot drift, and a step overshooting several targets accepted exactly once. Duplicate offers of the same completed step are rejected. The runloop calls the window update immediately before physical-solution monitoring, after the Lagrangian block and before history rotation, and an optional console snapshot mirroring the particle console reports window-level progress through the logging sink. Covered by a new unit-statistics-window suite whose central case asserts that sample and physical-time weighting agree on a constant-timestep run.KSKE workspace was allocated and freed on every Poisson solve from inside the solver, with a defensive second free in teardown; it is now allocated once by CreateAndInitializeAllVectors and freed once in teardown, which is safe because FullyBlocked guards every read with its own flag array and so carries no state between solves. The corner-staging workspace was created lazily inside the interpolation routine and rebuilt whenever the block size changed; it is now two explicitly typed pairs, CellScalarAtCorner and CellVectorAtCorner, created by the same factory.offsetof requirement, so the hand-rolled global-to-local scatter in the interpolation path is replaced by UpdateLocalGhosts. Corner anatomy logging takes the field identity from its caller rather than inferring the degree of freedom from a cached vector's block size.include/statistics_target.h and src/statistics_target.c resolve, for one field on one block, an iteration domain that excludes PETSc halo storage and solver-layout boundary, dummy, and duplicate-periodic indices, which are distinct categories. Layout classification comes from the typed field catalog: cell-like dimensions span the shifted interior, node-like dimensions carry one more entry, and each face family is node-like only in its own direction. Periodic directions drop the wrapped duplicate plane, which leaves cell-like spans unchanged because their duplicates already sat outside. Component- staggered fields are rejected, since their components live on different face families and cannot share one pointwise domain. SpatialTargetPlan exists with only the pointwise identity mapping so later spatial bins extend rather than retrofit it. Covered by a new unit-statistics-target suite across cell, node, and I/J/K-face layouts in nonperiodic, mixed, and fully periodic domains, plus a multi-rank case asserting the resolved domain is decomposition independent.include/statistics_moments.h and src/statistics_moments.c implement the stable weighted Welford update, the compatible co-moment update, and the weighted parallel-merge formula, plus weighted variance, covariance, and Kish effective sample size. The module is pure: no configuration, no PETSc vectors, and no window or scheduling knowledge. Non-positive sample weights are rejected rather than silently corrupting an accumulator. Covered by a new unit-statistics suite asserting bitwise-zero variance for constant signals, known scalar and two-sample results under equal and unequal weights, all six symmetric velocity components of a three-sample self-product, exact agreement between a self-paired co-moment and the scalar second moment, high-mean/low-fluctuation precision where a raw sum-of-squares would cancel, and merge-equals-sequential including empty-partition no-ops.include_initial control. Also fixes the user contract, cross-field covariance spelling, window identity hash inputs, the indexed control-option scheme with its ingress-audit extension, the statistics/ checkpoint namespace, the postprocessing contract, and the seven implementation stages. Amended page 58 accordingly.Ucont_rm1 preserves the order of the restart, not bitwise identity.FieldId/FieldDescriptor catalog with canonical names, active aliases, DM family, degree of freedom, shifted/staggered layout, synchronization class, availability, capabilities, and existing UserCtx vector bindings;FieldView resolution without changing PETSc vector allocation or teardown;ParticleFieldId catalog for DMSwarm component count, PETSc data type, registration ownership, initialization, model-update, and Eulerian-scatter metadata;run_control.start_step: 0 instead of treating a fresh start as an in-place continuation and appending misleading step-zero separators to existing logs.dtau = pseudo_cfl × dt to dtau = pseudo_cfl / lambda_max, where lambda_max is the global maximum convective spectral radius computed once per physical timestep. This makes pseudo_cfl.* true dimensionless Courant numbers, independent of dt, grid size, and flow speed.pseudo_cfl.initial: 0.5 and pseudo_cfl.maximum: 2.0 (stable 4-stage Jameson range ~0–2.83); existing configs with 0.1/1.0 still run but now produce physically smaller, safer dtau values.lambda_max >= COEF_TIME_ACCURACY/dt so zero-flow startup yields a finite dtau.mom_last_lambda_max to the simulation context.Pseudo-cfl/ cfl_after to dtau/cfl_eff and dtau_after/cfl_eff_after; picurv summarize parsing, JSON payload, console output, and plot series were updated to match.generated and file modes.Ucat and Ucont startup through the existing field reader.ic_gen run/precompute orchestration, defaulting to generators/ic.gen with an optional compatible script override.generators/ic.gen expression engine for grid-aware Ucat and staggered Ucont PETSc vectors.max_iterations, and guaranteed nonfatal exits retain the last accepted finite state.residual_absolute_tol and residual_relative_tol; configurations without enabled residual tolerances retain legacy update-only convergence semantics.step_tol while retaining compatibility ingestion.<run.analysis.metrics>/search_metrics.csv with timestep-level search, traversal, tie-break, boundary-clamp, bbox-guess, pass-depth, per-step loss, run-local cumulative loss, V2 population/outcome counters, and derived search_failure_fraction, search_work_index, and re_search_fraction signals.LOG_SEARCH_METRICS for compact DEBUG-gated console summaries when explicitly allow-listed.examples/search_robustness/ example family with Brownian Cartesian/curvilinear baselines plus deterministic Cartesian/curvilinear UNIFORM_FLOW migration-stress variants, a study starter, and a dedicated metrics-reference docs page.LOG_PROFILE log level from the C logging enum and all code paths.profiling.timestep_output (off, selected, all) and writes timestep rows to a dedicated profiling log file.profiling.final_summary.enabled now controls whether the end-of-run ProfilingSummary_*.log file is written.LOG_LEVEL=PROFILE is no longer a supported runtime setting.profiling.critical_functions compatibility shorthand; monitors must now use profiling.timestep_output.pic.flow to picurv.picsolver to simulator.init now creates config-only case directories; binaries are resolved from bin/ via PATH.init --pin-binaries copies simulator/postprocessor into the case for version-pinning (protects running jobs from concurrent rebuilds).bin/picurv is now a launcher for the picurv_cli/picurv source-tree entrypoint.sync-binaries pins specific binary versions into a case directory (optional, equivalent to --pin-binaries after init).--copy-binaries init flag.programmatic_c.im/jm/km are now treated as cell counts as documented.picurv now converts those values to node counts before emitting -im/-jm/-km.grid_gen remains unchanged: grid.gen still accepts cell counts and writes node counts into .picgrid.programmatic_c case with im=32 previously yielded 31 physical cells; it now yields the documented 32 physical cells.Dual Time Picard RK4 to the more precise Dual Time Picard Jameson RK; deprecated RK4 selector, YAML-block, noise-control, C API, and C CLI spellings remain accepted as compatibility aliases.PICGRID headers are required for file-based grids in C runtime ingestion.grid.gen legacy1d converter and optional grid.legacy_conversion wrapper in picurv for headerless 1D-axis legacy payload migration.run --num-procs now applies to solver and field postprocessor stage sizing.post.sbatch and sweep post arrays reuse the configured cluster resources.cluster.yml Slurm contract support to picurv run (--cluster, --scheduler, --no-submit).picurv submit as the delayed-submit counterpart to --no-submit for existing run/study artifacts.picurv cancel so Slurm jobs can be stopped by --run-dir instead of manual job-id lookup; picurv cancel --stage solve --graceful requests solver final-output shutdown with SIGUSR1.solver.sbatch, post.sbatch, submission.json, manifest.json).picurv sweep for parameter studies using Slurm job arrays with post-stage dependency chaining.picurv sweep --continue --study-dir <path> for resuming partially-completed studies: detects per-case completion status, prepares checkpoint restarts via resolve_restart_source, and submits sparse solver arrays for incomplete cases only.picurv sweep --reaggregate --study-dir <path> for manual metrics re-aggregation on existing study outputs.metrics_aggregate.sbatch, afterany dependency on post array).detect_last_checkpoint_step now falls back to particle checkpoint files (position*.dat) for analytical-mode cases with no eulerian output.metrics_table.csv, results/plots, summary.json).master_cluster.yml, master_study.yml.SIGUSR1, SIGTERM, SIGINT) with launcher-specific Slurm signal guidance.execution.walltime_guard policy and exported batch metadata (PICURV_JOB_START_EPOCH, PICURV_WALLTIME_LIMIT_SECONDS).tests/tooling/audit_function_docs.py as the repository-wide audit gate.picurv summarize for read-only per-step health summaries derived from existing run artifacts.picurv summarize with additive --overview, --case, --solver, and --monitor configuration views that work before timestep artifacts exist.summarize --list-plot-series and --plot time-history workflows backed by standalone generators/plot.gen, with append-order windows, automatic residual/norm log scaling, explicit saves, and headless fallback.matplotlib as an actionable DEPENDENCY_MISSING plotting error instead of a configuration-value error.matplotlib by default in the bootstrap-managed Python environment so plotting works after a standard install.--upgrade-pip for environments that need it.runs/<run_id>/scheduler/ instead of runs/<run_id>/logs/.picurv -> C contract.docs/assets/curv.gif, docs/assets/paraview_flat_channel.png).config/build/.grid.gen profile to config/grids/coarse_square_tube_curved.cfg.sandbox/ into explicit developer-sandbox documentation.guide.md (non-root).docs/assets/.README.md files (single top-level README.md retained).logs/doxygen.warnings.da_processors_* contract with validation.tests/tooling/audit_ingress.pytests/tooling/audit_ingress_manifest.jsonstubs/ archive from repository after extracting useful documentation content.picurv init now writes a fixed, content-addressed workspace: config/, user-owned inputs/, a PICurv-managed assets/objects/{grids,initial_conditions,inlet_profiles}/ store, and runs// studies/. picurv precompute builds Python- or file-backed providers (grid_gen, ic_gen, spectral_random_velocity, file-backed grids/profiles) into immutable, hashed asset objects and refuses atomically, with no dummy output, when a provider is simulator-runtime-only (programmatic_c). Runs materialize shared assets into inputs/ by reflink or hardlink, never a copy, and record exactly what they consumed in inputs/assets.lock.yml. A run's own manifest.json is now the authority on its identity, topology, software provenance, and asset/runtime-provider lock - not its directory name.--restart-from now writes a lineage record into the branch's manifest.json: the parent's own run id (read from the parent's manifest, so a renamed parent is still named correctly), the checkpoint step branched from, and the field-statistics disposition. A fresh run records {"relationship": "root"} rather than omitting the key. Branching a run with field_statistics.enabled: true now requires --statistics-state reset|carry explicitly - neither answer was safe to default, since resetting silently shortens the reported average and carrying silently averages two trajectories together.config, inputs, output, logs, scheduler); an unexpected file is reported, not refused. Previously an unrouted peer directory was silently archived forever and pruned by no storage policy.picurv storage, split into a package (picurv_cli/storage/) with a content-addressed remote blob store (schema 2: identical content uploads once and an interrupted upload resumes), reference-counted local asset pruning (storage prune --assets --unused-locally), and three named offload policies (metadata-only, restart-ready, analysis-ready). offload/plan/protect now take repeatable --retain/--drop to adjust any named policy one component at a time (checkpoints/logs/analysis/visualization/inputs/raw-output), instead of being limited to whichever bundle a preset happens to ship.case_\d+ regex - a renamed or restored run still reports its real identity, and a checkpoint bundle is classified by the step its own checkpoint.meta records, not by the directory name it happens to sit in..picurv-storage.yml it resolved, and warns when the answer came from a directory above the workspace (discovery deliberately searches upward, so one configuration can serve a whole directory of campaigns - but offload prunes local payload once the remote it names has verified the upload, so which file answered has to be visible). storage setup run inside a workspace with no configuration of its own now creates one there instead of silently rewriting the shared file above it; naming that file with --storage-config still edits it deliberately.picurv version status validates that the conductor, simulator, postprocessor, and any workspace software.picurv requirement agree, and exits non-zero on disagreement (picurv version alone stays informational and always exits 0). The built documentation site now stamps its release identity (the plain release, e.g. PICurv 0.1.0, for a clean checkout of that tag; the full dev/dirty build id otherwise) alongside the commit it was built from, rather than the commit alone.sync-binaries; picurv init --pin-binaries is the one remaining way to pin case-local executables. PICurv remains a single shared installation that versions activate rewrites in place - not versioned side-by-side prefixes - so a version-pin failure and versions activate now both say so explicitly: activating changes every other workspace using the installation and any job that did not pin its own binaries.CLI and Docs Quality now builds the Doxygen site before running the test suite, since one regression test exercises the published-site audit as a subprocess and requires the build to already exist.storage setup --workers's default, which is computed from the local CPU count at parser-build time. Either one made generate_cli_reference.py --check disagree with whatever machine last regenerated the committed file - undetectably so when testing under a different Python on the same machine, since CPU count doesn't depend on the interpreter. Both are now rendered in a form that doesn't vary by machine.VERSION had reached 0.3.1 through a pre-push gate that forced a bump on every push to main, conflating "landed on
main" with "released" - no version along the way was ever deliberately declared, and no vX.Y.Z tag was ever cut for any of them. Reset to 0.1.0, the version this repository has always actually shipped.VERSION now changes only for a deliberately declared release, tagged vX.Y.Z separately once the declaring push lands - not on every push to main. An ordinary push instead requires a fragment under docs/changelog.d/ describing its user-visible change; a release consumes every pending fragment into its own dated ## X.Y.Z section here and requires none be left over. docs/changelog.d/README.md states the convention.picurv version already reports <release>.dev<N>+g<commit>[.dirty], computed automatically from VERSION and how far HEAD sits past the nearest vX.Y.Z tag.