|
FhSim
3.1.0
Marine systems simulation
|
Findings from code review that await an owner decision or an implementation. Each issue is one file in issues/details/, which holds the evidence, the effect, the possible fix, the test that would prove it and the risk. Resolved issues are kept in issues/resolved/ so that their numbers are never reused. Issues that concern a SimObject are also shown on that model's documentation page. Audit notes that are not issues are in issues/notes.md.
file:line given in the detail file, at HEAD.blocked item, get the owner's answer to "Decision needed" first, then set Status to ready.git mv the file to issues/resolved/, add a **Resolved:** <commit>, <date> paragraph after the table, delete the matching \xrefitem reviewissue "Review issues" "Review issues" \ref fhsim_environment_issue_<ID> lines from the headers, rerun the script and commit it all together.New issue: --new <slug> "<title>", or add issues/details/<slug>.md with the metadata table but no ID row; the script assigns the next free number, writes the heading and renames the file to <TAG>-<ID>-<slug>.md.
BUG code does something other than its documentation or sibling code saysKNOWN-LIMITATION behaviour gap by design or omission, document rather than fix silentlyARCHITECTURE structure, dependencies, build layout, dead code and stale artefactsDOCUMENTATION missing or wrong documentation or exampleTEST test coverage or infrastructureAPI public name (port, state, parameter, registration string) whose change breaks input filesSECURITY credentials and supply chain3 wrong numbers, a crash or a secret exposure reaches a user2 a documented interface does not work as documented1 tidiness, readers and tools onlyblocked needs the owner decision named in the fileready fully specified| ID | Class | Severity | Status | |—|—|—|—|
_NEEDS_GUI suffix the ctest filter excludes. Resolved: 7996618, 2026-09-16. File: resolved/ENV-0001-needs-gui-ctest-filter-matches-nothing.mdWaves.Hs documented default is 4, the code default is 1.0. Resolved: 8604406, 2026-09-15. File: resolved/ENV-0002-waves-hs-documented-default-is-4.mdVisual.WorldRadius documented default is 3000, the effective default is 1000. Resolved: 8604406, 2026-09-15. File: resolved/ENV-0003-visual-worldradius-documented-default-is-3000.mdVisual.Sky documented default Clear is not an accepted value. Resolved: 8604406, 2026-09-15. File: resolved/ENV-0004-visual-sky-documented-default-clear.mdVisual.Vertices documented default is 100, 100, the code default is 100, 130. Resolved: 8604406, 2026-09-15. File: resolved/ENV-0005-visual-vertices-documented-default-is-100.mdWaves.Spectrum documents the value Component; the code accepts Components. Resolved: 8604406, 2026-09-15. File: resolved/ENV-0006-waves-spectrum-documents-the-value-component.mdWaves.Theory is documented with a default but read as a mandatory parameter. Resolved: 8604406, 2026-09-15. File: resolved/ENV-0007-waves-theory-is-documented.mdWaves.OmegaNormalizedMax description says "Lower", and the silent 0.5 floor is undocumented. Resolved: 8604406, 2026-09-15. File: resolved/ENV-0008-waves-omeganormalizedmax-description-says-lower.mdVisualFlowPlane range defaults do not match the code. Resolved: b6b6d36, 2026-09-15. File: resolved/ENV-0009-three-visualflowplane-range-defaults.mdCNetCDFField documents NetcdfFile but reads the mandatory NetCDFFileName. Resolved: de2ab83, 2026-09-15. File: resolved/ENV-0010-cnetcdffield-documents-netcdffile-but-reads.mdCNetCDFField unconditionally writes a debug text file at construction. Resolved: 498cbd6, 2026-09-16. File: resolved/ENV-0011-netcdffield-writes-debug-file-unconditionally.mdCurrent.Vector is read but undocumented. Resolved: 8604406, 2026-09-15. File: resolved/ENV-0012-current-vector-is-read-but-undocumented.mdNETCDFCPP_AVAILABLE is defined unconditionally, so the documented condition and the fallback branch are dead. Resolved: f15bebc, 2026-09-16. File: resolved/ENV-0013-netcdfcpp-available-defined-unconditionally.mdTestDynamicWaves prints the whole parameter status map to stdout. Resolved: 154f0a3, 2026-09-15. File: resolved/ENV-0014-testdynamicwaves-prints-the-whole-parameter-status.mdNetCdfSeafloor.SeabedMaterial is read but undocumented. Resolved: a0f188d, 2026-09-15. File: resolved/ENV-0015-netcdfseafloor-seabedmaterial-is-read-but-undocumented.md4151209, 2026-09-15. File: resolved/ENV-0016-two-openwiki-claims-no-longer-match.mdVisualFlowPlane.ComputeFunction silently falls back on an unrecognised value. Resolved: 50c4576, 2026-09-16. File: resolved/ENV-0017-visualflowplane-computefunction-silent-fallback.md94f5ed3, 2026-09-23. File: resolved/ENV-0018-doxygen-comment-style-contradicts-code.mdcdd4165, 2026-09-23. File: resolved/ENV-0019-inline-json-numeric-arrays-mangled.mddd199ff, 2026-09-23. File: resolved/ENV-0020-randomseed-defaults-to-wall-clock.md13034de, 2026-09-23. File: resolved/ENV-0021-flowplane-sets-uniforms-on-shared-material.md13034de and 18afaab, 2026-09-23. File: resolved/ENV-0022-flowplane-destructor-leaks-entity-node-mesh.md1507a3f and 5bf8d11, 2026-09-23. File: resolved/ENV-0023-visual-release-uses-dangling-scenemanager.md6f55e02, 2026-09-24. File: resolved/ENV-0024-mesh-handle-dropped-after-hardwarebuffermanager.md3d35ffa, 2026-09-24. File: resolved/ENV-0025-no-netcdf-field-test-coverage.md1c1f97d, 2026-09-24. File: resolved/ENV-0026-four-test-simobjects-untested.md3d35ffa, 2026-09-24. File: resolved/ENV-0027-cncfield-dead-registration.md555d19f, 2026-09-24. File: resolved/ENV-0028-no-derivative-api-on-fields.md53949b6 (marenv 111571c, 1.0.32), 2026-09-24. File: resolved/ENV-0029-wake-does-not-attenuate-particle-velocity.md3d35ffa, 2026-09-24. File: resolved/ENV-0030-netcdf-handler-static-arrays.md1bcfbd6, 2026-09-24. File: resolved/ENV-0031-netcdf-parser-missing-file-segfault.mda9e2f4b, 2026-09-24. File: resolved/ENV-0032-netcdf-seafloor-axis-convention.mdf3c3c15, 2026-09-24. File: resolved/ENV-0033-netcdf-parser-exception-leak.md16484f4 (marenv 2.0.0 ffef6e5), 2026-09-26. File: resolved/ENV-0034-net-hydro-environment-wake-adaptation.md693cf66 (shared-file edits 2673b81), 2026-09-26. File: resolved/ENV-0035-net-hydro-porous-disc-wake-source.md936438e (shared-file edits a08a2e0), 2026-09-27. File: resolved/ENV-0036-net-hydro-wake-user-documentation.md3f1819c, 2026-09-27. File: resolved/ENV-0037-testnetcagewake-parameter-validation.md25b1a0e, a9fe430, dd764d4, 2026-09-27. File: resolved/ENV-0038-phase-f-lumped-wake-caster.md8667294, 2026-09-28. File: resolved/ENV-0039-freeze-rearms-on-ambient-current.mdresolved/ENV-0040-lumped-caster-ignores-current-status.mdresolved/ENV-0041-relaxation-skipped-without-relative-flow.mdresolved/ENV-0042-wake-settings-accept-infinity.mdresolved/ENV-0043-netcage-wake-field-current-lookup-per-sample.mdresolved/ENV-0044-caster-accepts-non-finite-current-load.mdresolved/ENV-0045-wake-caster-test-gaps.mdresolved/ENV-0046-zero-blend-time-order-dependence-undocumented.mdresolved/ENV-0047-testwakebody-does-not-exclude-own-field.md051c972, 2026-09-28. File: resolved/ENV-0048-external-wakes-per-step.mdresolved/ENV-0049-frozen-wake-drifts-from-its-structure.mdresolved/ENV-0050-marenv-round-2-wiring.md6bb6cb6, 2026-09-28 (R79). File: resolved/ENV-0051-seabed-contact-undamped-at-rest.md8a689e0, 2026-09-28. File: resolved/ENV-0052-marenv-r87-r88-wiring.mdda01468, 2026-09-28. File: resolved/ENV-0053-external-wakes-current-and-cutoff-api.md0141e99, 2026-09-28. File: resolved/ENV-0054-marenv-r95-wiring.mdfhsim_environment 4.0.0 (fhsim 3.3.1, marenv 2.0.0, with_visualization=True; 235/235 tests pass) and of fhsim_marine_elements against it runs NetHydroTowedPanel.xml and NetStructure.xml to FhSim ended successfully.. The crash came from the stale/inconsistent local build/Release tree. No code change.. File: resolved/ENV-0055-wave-components-blend-bad-alloc-integrator-init.mdf5ef5c3, 2026-10-01 (interim fix of SYNTHESIS §0, the root fix is R124: the first PreOdeFcn re-reads SeabedImageDepth and re-sets a fixed axis, logged, when it changed; the Set stays in FinalSetup; test PorousDiscWakeSource.FirstPreOdeFcnTakesTheSeabedInstalledAfterFinalSetup_ENV0056 red before (deficit 0.1256 with the 200 m image against 0.2551 with 12 m), green after). File: resolved/ENV-0056-porous-disc-seabed-image-depth-read-once.md7564438, 2026-10-01 (every parameter is checked finite in the constructor: Position, Area/Diameter, MomentumDeficit/ThrustCoefficient, FlowDirection, SourceVelocity, WakeExpansion, WakeXStartFactor; test PorousDiscWakeSource.RejectsAmbiguousOrMissingStrengthAndSize extended with 8 non-finite cases, 16 expectations red before, green after). File: resolved/ENV-0057-porous-disc-accepts-non-finite-parameters.md20a1de9, 2026-10-01 (R127 validation half (the header move to src/testmodels is a later batch): NetCageWakeField throws std::invalid_argument for a wake angle outside (0, pi/2) or bad geometry; TestNetcageWake reports WakeAngleDeg outside (0, 90) degrees, FullWakeDiameter, InitialBoundaryLayer and FullWakeDepth as parameter errors; test TestNetcageWake.RejectsAWakeAngleOutsideZeroToNinetyDegreesAndBadGeometry_ENV0058 red before (21 failures), green after; the pinned pre-4.0 numbers are unchanged). File: resolved/ENV-0058-netcage-wake-angle-not-validated.md9d168d9, 2026-10-01 (every JSON object is checked against an allow-list of its keys (document incl. version and description, each feature type, each background type, clamp, domain; both centre spellings kept); ReadFixedArray takes the node (F-11); the schema sets additionalProperties false at the document and field level and lists description; test ParametricScalarFieldJson.RejectsUnknownKeysAtEveryLevel_ENV0059 red before (8 of 8 keys accepted), green after; the shipped example documents (env and fishery) pass the allow-lists). File: resolved/ENV-0059-parametric-json-drops-unknown-keys.md035458c, 2026-10-01 (depths and values must be finite and the thickness test is !(thickness > 0) (MakeDepthProfileInverseThickness), ConstantScalarField and the parametric constant background must be finite, the parametric domain box must be finite with min <= max; the DepthVarying size error names Current.Depth; tests DepthLayeredScalarField.RejectsNonFiniteDepthsAndValues_ENV0060, ParametricScalarField.RejectsNonFiniteBackgroundsAndAnInvertedDomain_ENV0060 and EnvironmentDefaults.DepthVaryingSizeErrorNamesTheDepthList_ENV0060 red before (13 failures), green after). File: resolved/ENV-0060-scalar-fields-accept-nan-and-inverted-domain.md560021f, 2026-10-01 (a bathymetry status that is not usable (marenv::IsUsable) is returned as it is with the value 0; OUT_OF_RANGE_INACCURATE keeps clamping, the domain box and the masked zero, documented in ScalarField::GetValue and ParametricScalarField; test ParametricScalarFieldSeafloor.PassesABathymetryErrorThrough_ENV0061 red before (OUT_OF_RANGE_INACCURATE, value 4), green after; the fishery echosounder wording (batch B4) is not changed). File: resolved/ENV-0061-parametric-field-downgrades-bathymetry-error.md699433a, 2026-10-01 (ConstructScalarField wraps every field constructor in one try/catch that reports on <prefix>Model; an unknown model is a parameter error, no longer a bare runtime_error; F-S5 traced read-only in fhsim (an exception from a SimObject constructor exits cleanly through the application's catch, without a log-file line); test EnvironmentDefaults.ScalarFieldFaultsAreParameterErrors_ENV0062 red before (std::invalid_argument escaped GetScalarFields), green after). File: resolved/ENV-0062-scalar-field-xml-errors-reported-three-ways.mdbcd3efb, 2026-10-01 (m_timeOfLastSeaStateChange is initialised to 0, and the doc of TimeOfLastSeaStateChange names the one setter (UpdateSeaState, plus its transition time); test EnvironmentProvider.TimeOfLastSeaStateChangeIsZeroBeforeAnyChange_ENV0063 (default-initialised in 0xFF memory) red before (NaN), green after). File: resolved/ENV-0063-time-of-last-sea-state-change-uninitialised.md05a24e2, 2026-10-01 (owner ruling R122 (i)-(iii): GetWakedCurrent (live and held) keeps the sum of the usable current fields and returns the facade's status, so a net panel gets a clamped or partial current as every other consumer; LumpedWakeCaster takes a usable status (marenv::IsUsable) as a known current and keeps ENV-0040 for an error; tests ExternalWakes.WakedCurrentKeepsAClampedAndAPartialCurrent_ENV0064 and LumpedWakeCaster.ClampedCurrentIsUsableAndAFailingFieldCastsNoWake_ENV0064 red before (zero current, no wake above the first layer), green after; behaviour changes only where a current field is clamped or fails). File: resolved/ENV-0064-waked-current-zeroes-partial-and-clamped-current.mdc8cb563, 2026-10-01 (owner ruling R126: environment::WakeSums, ComposeHeldWakes and the held-sum overloads are removed; ExternalWakes holds one marenv::wake::WakeAccumulator per point from SelectReachingWakeFields and adds the own field live (AddOwn on a copy); the provider applies a given marenv::wake::WakeComposition, GetWakedFlow/GetWakedCurrent(time, pos, const WakeComposition&, out); LumpedWakeCaster publishes with BlendedSourceSetWake(kernel) and Publish(SourceSet, velocity, t0, blendTime, seabedDepth), the seabed depth passed explicitly; the tests use BlendedSourceSetWake for their stand-in fields (t0 = 0, the time the source is at its position; the discs with a source velocity now move, so the ExternalWakes tests sample at the take time); no fhsim_marine_elements, fhsim_base or fhsim_fishery code names the removed API; bit-identity: a probe of 840 held and live flows (cutoff 0, 1e-3, 0.1) and three lumped casters over 40 rebuild steps, hexfloat dump identical before and after; ExternalWakes.AtTheTakenPointAndTimeTheyEqualTheLiveQueryExactly and the caster tests green). File: resolved/ENV-0065-provider-copies-wake-composition-and-uses-deprecated-marenv-api.md7dbae91, 2026-10-01 (internal src/environment/WakeCommon.h holds Require (templated on the exception type, so ReadWakeSettings keeps std::invalid_argument and the SimObjects std::runtime_error, same messages), Norm, Format and FormatVector; the copies in WakeSchedule, LumpedWakeCaster, PorousDiscWakeSource, TestWakeBody and TestNetcageWake are gone; marenv's kMinRelativeFlow and LumpedKernel replace the local constants and RearFaceKernel; LumpedWakeCaster::Rebuild takes the reference and the ambient current from Update (one lookup each per rebuild); no behaviour change: the E1 hexfloat probe is identical and 249/249 suite tests pass). File: resolved/ENV-0066-wake-helpers-and-constants-duplicated.md755e12f, 2026-10-01 (Due(T) throws std::logic_error unless FilterShare was called at the same T (a call that returns share 0 counts); the in-tree callers (LumpedWakeCaster, fhsim_marine_elements NetWakeCaster::FilterNodeVelocities) already call it first; three WakeSchedule tests that called Due alone now call FilterShare first; test WakeSchedule.DueRequiresFilterShareAtTheSameTime_ENV0067 red before, green after; E1 probe identical). File: resolved/ENV-0067-schedule-due-depends-silently-on-filter-share.mdc72f571, 2026-10-01 (ExternalWakes::WaterVelocity and WakedCurrent take const EnvironmentProvider& and SeabedImageDepth is const (the bathymetry's lookup, as GetSeaDepth); test ExternalWakes.ReadersTakeAConstEnvironment_ENV0068 did not compile before, green after; callers with a mutable provider (fhsim_marine_elements) compile unchanged; E1 probe identical). File: resolved/ENV-0068-wake-readers-need-mutable-provider.mdc1a9699, 2026-10-01 (LumpedWakeCaster::OwnQuery() returns the WakeQuery with exclude = its registered field (nothing before Register), documented as the query of every own flow lookup; TestWakeBody uses it; test LumpedWakeCaster.OwnQueryExcludesTheRegisteredField_ENV0069 did not compile before, green after; fhsim_marine_elements' BodyFlowInteraction::OwnWakeQuery can use it (not edited here)). File: resolved/ENV-0069-lumped-caster-has-no-own-query.mdead791a, 2026-10-01 (without CastWake, ReadWakeSettings logs every wake parameter given in the input (GetParamSize > 0) as ignored, one LogParameterInfo line each, not an error; WakeSettings::cutoffGiven records whether WakeCutoff was given so fhsim_marine_elements' NetWakeCaster can drop its second read (ME not edited here); the test stub records LogParameterInfo; test ReadWakeSettings.LogsTheWakeParametersIgnoredWithoutCastWake_ENV0070 did not compile before, green after; log lines only). File: resolved/ENV-0070-wake-attributes-ignored-silently-without-castwake.mdbeb10e8, 2026-10-01 (ScalarField::GetValue and its three fields are const, EnvironmentProvider::GetScalarValue is const and looks the field up by raw pointer (no shared_ptr copy per query); the test doubles add const; test EnvironmentProvider.ScalarValuesAreReadThroughConstReferences_ENV0071 did not compile before, green after; no in-stack subclass outside this repo; fishery's workaround notes (SensorCtd, SensorSilCam) are batch C4). File: resolved/ENV-0071-scalar-field-getvalue-not-const.md1560384, 2026-10-01 (documentation only: wakes.md gives both WakeCutoff roles (grid; with CastWake the R47 receiver tier, 1e-4 without), drops the stale TestWakeBody line (it excludes its own field since ENV-0047, now through OwnQuery), says no shipped receiver uses cutoff 0, the published velocity after the R50 snap, the clamped current (R122), the ignored-parameter log (ENV-0070), the seabed re-read (ENV-0056) and the stand-in recipe (copy M, area and rear centre, WakeXStartFactor 0); EnvironmentProvider.h teaches the LumpedWakeCaster pattern with OwnQuery; NetCageWakeField is marked legacy in its header and in wakes.md; LumpedWakeCaster.h names OwnQuery and the published velocity). File: resolved/ENV-0072-wake-docs-contradict-the-code.md122cbd1, 2026-10-01 (documentation only: Environment.h DepthVarying gives the direction convention (flows towards, from North towards East), strictly increasing depths and the R122 end behaviour (nearest layer, OUT_OF_RANGE_INACCURATE, summed by every consumer); GetScalarValue lists its parameters in signature order; the schema and ParametricScalarField.h say the field uses the Environment's bathymetry at setup (a later NetCdfSeafloor is not seen, W-06/R124); wakes.md and WakeSchedule.h state R121: an N-row farm needs WakeIterations >= N and a net converges on max(max|dR|, |dM|/M), written as true once the fhsim_marine_elements batch lands). File: resolved/ENV-0073-depth-profile-scalar-and-farm-docs.md6c630bd, 2026-10-01 (architect decision in batch E2, after the ENV-0044/ENV-0040 precedent: a step with a non-finite Reference() is skipped and logged once per run of such steps; it does not enter u_s, and a due rebuild builds, publishes and counts nothing, so it runs at the next finite step; test LumpedWakeCaster.NonFiniteReferenceIsSkippedAndTheCasterRecovers_ENV0074 red before (BlendedSourceSetWake::Publish: the source velocity must be finite, u_s NaN), green after). File: resolved/ENV-0074-nan-reference-velocity-poisons-source-velocity-filter.mdb61fba1, 2026-10-01 (owner ruling R125, OD-5: the area is widened to M / 0.96 above C_T = 0.96 and logged once at setup; the source is at Position at the setup time and translates with SourceVelocity; the field is a BlendedSourceSetWake published with blend time 0, a followed axis published again at each step's time and position; a clamped ambient current is usable for the followed axis (R122). Tests PorousDiscWakeSource.ThrustCoefficientAbove096WidensTheAreaAndLogsOnce_R125 (red: deficit 0.3601 against 0.3849 at x = 10 m, no log line), SourceTranslatesWithSourceVelocity_R125 (red: 0.1538 against 0.2621; the towed followed source 0 against 0.2632) and FollowedAxisAcceptsAClampedCurrent_R122 (red: no wake) green after. Characterisation: for C_T ≤ 0.96 and SourceVelocity = 0, 75 600 probe samples (fixed axis, C_T = 0.96 exactly with overridden kernel, followed axis turning and stopping, 12 m seabed; deficit, source velocity, status, waked current at t = 0 and 10 s) are bit-identical before and after; no existing test changed). File: resolved/ENV-0075-porous-disc-caps-ct-fixed-position-own-field-type.md4baaada, 2026-10-01 (owner ruling R124, OD-4: Bathymetry.Model = "NetCDF" with Bathymetry.NetCDFFile makes the NetCDF depth grid the Environment's one bathymetry, fixed at construction, handed to the facade, SeafloorForces and the scalar fields; the NetCdfSeafloor SimObject is deprecated, logs it at setup (a parameter info line on NetcdfFile pointing to Bathymetry.Model = "NetCDF") and is removed in the next major version. Tests EnvironmentBathymetryNetCDF.ReachesTheFacadeSeafloorForcesAndScalarFields_R124 (F-01's sketch: a 30 m file, a seafloor-referenced depth profile 1 → 0 over 10 m, 0.5 at z = 25 m, and SeafloorForces::GetSubmergence = −5 m; red before: Unknown bathymetry model: NetCDF), EnvironmentBathymetryNetCDF.RequiresAReadableFile_R124 and the deprecation check in NetCdfSeafloor.RegistersItsBathymetryOnTheEnvironment (red before: no line) green after). File: resolved/ENV-0076-netcdf-seafloor-reaches-only-the-facade.mdc3519c3, 2026-10-01 (owner ruling R127, OD-7, the move half; the validation half was ENV-0058: NetCageWakeField.h and .cpp move from include/fhsim_environment/ and src/environment/ to src/testmodels/ and the header is no longer installed; the field takes a clamped ambient current (OUT_OF_RANGE_INACCURATE) as usable, as every other consumer (R122 (iii); E1 review P2-5, decided in E2); test_package checks a ConstantScalarField on an EnvironmentProvider instead. Test NetCageWakeField.AClampedCurrentIsUsable_R122 red before (status 1 and deficit 0 instead of OK and 0.19611640360388949), green after). File: resolved/ENV-0077-netcage-wake-field-public-and-drops-clamped-current.md78a0190, 2026-10-01 (owner ruling R127, OD-7: removed now. Deleted: the public header include/fhsim_environment/CNCField.h (environment::CNCField), src/environment/CNCField.cpp, the NetCDFField SimObject (CNetCDFField, src/environment/NetCDFField.h/.cpp, its registration and its NCField shared resource), its reader src/environment/util/netcdf_handler.h/.cpp (NetcdfHandler, no other user) and tests/NetCDFField_Test.cpp (9 cases). A NetCDF current stays available as Current.Model = "NetCDF", which moves the water. Pure removal: no red test; the build, a repo-wide grep and the full suite (252 tests, from 261) are the evidence). File: resolved/ENV-0078-cncfield-second-netcdf-current-api.md6ff27cc, 2026-10-01 (W-30, after R122 and marenv MENV-0053: DepthLayeredScalarField and the depth_profile background of ParametricScalarField hold a marenv::calc::DepthProfile, built by environment::field::MakeDepthProfile (marenv's validation, the message prefixed by the field); the public MakeDepthProfileInverseThickness and EvaluateDepthProfile are deleted. Characterisation probe (temporary test, not committed; log /tmp/nethydro-build/e2-dp-probe.log): 54 profiles (the test profiles and 50 random ones with 2 to 10 layers), 1 080 357 depths over each profile ±20 m plus every layer depth: statuses identical, values at the layers and outside bit-identical, 145 824 interior values differ in the last bits (division against multiplication by the inverse thickness), max |Δ| = 2.27e-13, max relative 1.47e-11. No existing test needed loosening. One behaviour change: a NaN depth now gives ERROR_NOT_SET and the value 0 (before: NaN with status OK), passed through by ParametricScalarField as a bathymetry error is (ENV-0061); test DepthLayeredScalarField.NaNDepthIsAnErrorWithValueZero_W30 red before (status OK, value NaN), green after). File: resolved/ENV-0079-scalar-fields-own-depth-profile-interpolator.md579e09f, 2026-10-01 (SelectFieldConfig rejects any top-level key beside fields other than version and description, naming it, as a parameter error on ScalarField.<Name>.Model (ENV-0062); the schema's wrapper branch of oneOf lists only those three keys with additionalProperties: false, the single-field branch is $defs/field. Test EnvironmentDefaults.ScalarFieldWrapperRejectsKeysBesideFields red before (the field was built, no error), green after. The shipped wrapper documents (examples/input/auv_ocean_fields.json, scalar_field_demo.json, fishery's auv_ocean_fields.json) have only version, description and fields). File: resolved/ENV-0080-parametric-wrapper-drops-top-level-keys.md5100627, 2026-10-01 (util::BakeScalarField and util::ProbeScalarFieldRange share IsDrawn: a non-finite value or a status marenv::IsUsable rejects is masked and left out of the probed range whatever maskInaccurate (ShowExtrapolated) says; maskInaccurate still decides only for OUT_OF_RANGE_INACCURATE. Test ScalarFieldBake.AnErrorStatusIsMaskedEvenWithoutMaskInaccurate red before (mask 255, code 1, range valid), green after). File: resolved/ENV-0081-scalar-bake-draws-error-status.md63066bf, 2026-10-01 (architect decision under owner ruling R122 (ii), the semantics of fhsim_marine_elements MARE-0336 and ENV-0074; supervisor ruling (A) for the recovery: a dropped wake makes a rebuild due in every mode. Tests LumpedWakeCaster.FailedLookupsDoNotFreezeAndTheCasterRecovers_ENV0082 red before (2 rebuilds, frozen at M = 0), FailedLookupDropsTheWakeAfterTheBlendAndRearmsFreeze_ENV0082 red before (no drop or episode line), FailingCurrentCastsNoWakeAndSaysSo and ClampedCurrentIsUsableAndAFailingFieldCastsNoWake_ENV0064 updated to the new semantics; WakeSchedule.RebuildDueAt*_ENV0082 for the new schedule method; all green after). File: resolved/ENV-0082-lumped-caster-counts-failed-lookup-rebuild.md4d79c0f, 2026-10-02 (marenv MENV-0061 adds RelaxLumpedSource, pinned there bit for bit against a verbatim copy of LumpedWakeCaster::Relax; test WakeSchedule.MomentumChangeIsTheRelativeChangeOfM_ENV0083 did not link before (no MomentumChange), green after; a hexfloat dump of LumpedWakeCaster (Off, Freeze and Periodic with WakeRelaxation 1 and 0.3, a moving body with a varying drag coefficient and a near-zero relative flow: every lumped source, |dM|/M, u_s and 4,000 field samples, 35,290 lines) is byte-identical before and after (/tmp/nethydro-build/c1b/probe-env-{before,after}.txt); the type is renamed to marenv's SourceSetWake (MENV-0060) in the same commit; 262/262 tests pass). File: resolved/ENV-0083-lumped-relaxation-and-momentum-change-copied.md040630d, 2026-10-02 (WakeSchedule::BeginLookupFailure, MayDropWake, RecordWakeDrop and EndLookupFailure; tests WakeSchedule.LookupFailureEpisodeDropsOnceAfterTheBlendAndRebuildsAtTheRecovery_ENV0084 and WakeSchedule.LookupFailureWithoutACountedDropMakesNothingDue_ENV0084 did not compile before (no such members), green after; a hexfloat dump of LumpedWakeCaster with failed lookups (Off, Freeze and Periodic, WakeRelaxation 1 and 0.3, WakeBuildTime 0 and 2 s, five failure episodes including one before the first build and one during a blend: every lumped source, |dM|/M, u_s, rebuild count, Freeze state and 108,864 field samples, 109,356 lines) is byte-identical before and after with the log lines left out, which keep their number and order but take the one wording (/tmp/nethydro-build/f1/probe-env-{before,after}.txt); 264/264 tests pass). File: resolved/ENV-0084-failed-lookup-episode-copied.mdc628b0e, 2026-10-02 (test LumpedWakeCaster.UpdateBeforeRegisterIsLoggedOnce_ENV0085 red before (no line), green after; 265/265 tests pass). File: resolved/ENV-0085-update-before-register-silent.mdb0f1afa, 2026-10-02 (owner ruling R129: the SimObject, its registration, rendering and three tests deleted; NetCdfSeafloor.{h,cpp} renamed NetCdfSeafloorBathymetry.{h,cpp} with the bathymetry class only; test SimObject.NetCdfSeafloorIsRemoved_R129 red before (the input loaded and ran, result.ok true), green after; SimObject.EnvironmentBathymetryNetCDF added, green before and after; 264/264 tests pass; no input, example or consumer in the stack names it, only fhsim_insights' models.md and reports/simobject-test-coverage.md). File: resolved/ENV-0086-remove-netcdfseafloor-simobject.mdd852f72, 2026-10-02 (owner ruling R130; test ReadWakeSettings.DefaultsAreTheLeadApprovedChoices red before (2), green after; WakeSchedule.FreezeStopsAfterTheLastIteration, which runs on the default, moves from 3 to 5 rebuilds and is updated in the next commit, "Baselines for R130"). File: resolved/ENV-0087-wake-iterations-default-two.md800d4c6, 2026-10-02 (ExternalWakes::NeedsTake; tests NeedsTakeIsFalseWithOnlyTheOwnLumpedField_ENV0088, NeedsTakeIsFalseWithNoRegisteredField_ENV0088, NeedsTakeIsTrueWithAnotherField_ENV0088, NeedsTakeIsFalseForEvaluation_ENV0088 and WithoutAnyOtherFieldTheLiveQueryEqualsTheTake_ENV0088 did not compile before (no NeedsTake), pass after; the characterisation dumps C1 to C9 are identical before and after). File: resolved/ENV-0088-external-wakes-take-without-other-field.mdb05d054, 2026-10-02 (batch B2, R1: environment::wake::ReceiverCutoff and kNonCastingCutoff in ExternalWakes.h; tests ReceiverCutoff.* did not compile before (no such name), pass after; fhsim_marine_elements keeps a forwarding header). File: resolved/ENV-0089-receiver-cutoff-in-fhsim-marine-elements.mdf36f009, 2026-10-02 (batch B2, R2: BodySubmergence.h, environment::body_submergence, the wet fractions moved verbatim and the gated Submergence, WaveAcceleration, MeanWaveAcceleration, WavesWithElevation and CylinderWetFraction(environment, …) of cable_surface with them; new overloads WaveAcceleration(environment, T, p, a) and SphereWetFraction(environment, T, centre, r). The 7 BodySubmergence tests move from fhsim_marine_elements and compile against the new namespace; the gate tests Submergence/WaveAcceleration/SphereWetFraction …_ENV0090 did not compile before, pass after). File: resolved/ENV-0090-body-submergence-and-surface-queries-in-marine-elements.md21a4ef9, 2026-10-02 (batch B2, R2: SphereHydro.h, environment::sphere_hydro: Sphere, kAddedMassCoefficient, DragForce, DragJacobian, CurrentLoad, AddedMass, DisplacedMass, ExcitationFactor, MomentumDeficit; tests SphereHydro.* did not compile before, pass after. BodyLoad waits for SourceLoad (batch B4/B7)). File: resolved/ENV-0091-sphere-hydro-helper-in-marine-elements.mdeb9d77d, 2026-10-02 (batch B3, owner ruling R135: ExternalWakes::Take holds a WakeComposition per point with the own private field sampled at the take; NeedsTake true with query.own. Tests StepHoldsTheOwnPrivateFieldOfTheTakenPoint_R135 and NeedsTakeIsTrueWithTheOwnPrivateField_R135 failed before (B3-env-red.log), pass after; StepHoldsTheOtherWakesOfTheTakenPoint and WithoutAnyOtherFieldTheLiveQueryEqualsTheTake_ENV0088 move in "Baselines for R135"). File: resolved/ENV-0092-external-wakes-hold-own-private-field.mdf92d415 and 88ff071 (WakeCommon.h installed), 2026-10-02 (batch B4, design R5: WakeSourceSet.h with SourceLoad, WakeCastState and WakeCastingObject; WakeCaster.h with WakeCaster, SourceSetWakeSettings, ReadSourceSetWakeSettings, WakeEvaluation, moved verbatim from fhsim_marine_elements' NetWakeCaster; the ENV-0074 non-finite skip is new for source sets. The six tests WakeCaster.* did not link before (no WakeCaster, B4-env-red.log), pass after; fhsim_marine_elements MARE wraps it). File: resolved/ENV-0093-wake-caster-for-source-sets.md94874d7, 2026-10-02 (batch B5, design §3.3: WakeEvaluation::None, the lumped field only; LumpedWakeCaster is WakeCaster with one source over a single-body adapter, LumpedWakeCaster.cpp 217 → 174 lines with the adapter. No new test: the gate is the unchanged LumpedWakeCaster_Test (29) and TestWakeBody_Test (8), which pass, and the characterisation dumps C5 and C8, identical to /tmp/char/B5-base). File: resolved/ENV-0094-lumped-wake-caster-duplicates-wake-caster.md661cfae, 2026-10-02 (batch B6, design R3 §3.4: BodyWake.h with BodyWake, BodyWakeSettings, BodyWakeKind and ReadBodyWakeSettings(creator, kind); WakeSourceSet gains NumSamplePoints, SamplePointCarriers and CarrierMeanPosition. Owner decision on the API gap: the constructor takes the logger, Register(environment, rho) does not. The six tests BodyWake.* did not compile before (no BodyWake.h, B6-env-red.log), pass after; env ctest 306 tests, 0 failures; characterisation dumps C8 and C9 identical to /tmp/char/B6-base). File: resolved/ENV-0095-body-wake-facade-in-marine-elements.md1fa9225, 2026-10-02 (batch B7, B4 review P2: WakeCaster::Update skips a step in which a source names fewer than 1 or more than 3 carriers, or a carrier outside the set, logged once per run as ENV-0074; CarrierMeanPosition returns not-a-number for such a list. Tests WakeCaster.SourceWithoutCarriersSkipsTheStep_ENV0096 (threw "SourceSetWake::Publish: the source velocity must be finite") and WakeCaster.SourceWithTooManyOrUnknownCarriersSkipsTheStep_ENV0096 (stack smashing) failed before (B7-env-red.log), pass after with CarrierMeanPositionOfAMalformedListIsNotANumber_ENV0096). File: resolved/ENV-0096-wake-caster-trusts-source-carrier-count.mdabbeabd, 2026-10-02 (batch B7, B4 review P2: the WakeLength and WakeGrid setup errors of ReadSourceSetWakeSettings say "source"; WakeSchedule.h names WakeCaster; doc/user/wakes.md says where a source set's four parameters are read and what its change measure is; the ENV-0093 record lists 88ff071. Text only, no test). File: resolved/ENV-0097-generic-wake-caster-keeps-net-wording.md60e595a, 2026-10-02 (batch B7, design R4 §3.1 and §6 B7: IndexedWakeCastingObject and CastState in WakeSourceSet.h; WakeEvaluation="None" read by ReadSourceSetWakeSettings; sphere_hydro::BodyLoad from the existing MomentumDeficit; test model TestWakeCluster; doc/user/wakes.md "Multi-body objects". The tests TestWakeCluster.* and SphereHydro.BodyLoad* did not compile before (B7-env-red2-build.log); with the new code but without the None reader, TestWakeCluster.WithoutAPrivateField* threw "WakeEvaluation is \"None\"" (B7-env-red3.log); all pass after). File: resolved/ENV-0098-no-multi-body-wake-casting-object.md81c8e5f, 2026-10-02 (owner ruling R136, batch B8, with marenv MENV-0066: ExternalWakes::Take holds per point the WakeSampleParts of every selected field and of the own private field while one of them blends, and WaterVelocity/WakedCurrent compose them at the evaluation time (HeldComposition); outside a blend the finished composition as before. Tests ExternalWakes.InsideABlendTheHeldSamplesComposeWithTheLiveBlendWeight_R136 and ATakeAtAPublishInstantHoldsTheFieldThatFadesIn_R136 red before (the held flow kept the weight of the take, 0.6250 against the live 0.7032 at t = 2.5 s; B8-env-red.log), green after; full suite 318 tests passed; characterisation C8 and C9 identical). File: resolved/ENV-0099-held-wake-blend-weight-live.md7fad82f, 2026-10-03 (batch C-perf: CarrierPositions(set, X, positions) reads each carrier once and CarrierMeanPosition(positions, carriers, n, mean) takes the mean from them, bit for bit that of CarrierMeanPosition(set, X, ...); BodyWake::Update and WakeCaster::Update (SkipNonFiniteCarrier, SourcePosition, CurrentLookupFails) read the carrier positions once per step; CarrierMeanPosition(set, ...) and SkipMalformedSource ask the set for its size once; BodyWake::WaterVelocity and WakedCurrent are inline; ExternalWakes::WakedCurrent passes the held composition outside a blend without a copy. Test WakeCaster.CarrierMeanPositionFromHeldPositionsIsTheSetsMean_ENV0100 did not compile before (no CarrierPositions), passes after; characterisation C8 and C9 identical to /tmp/char/B9-base). File: resolved/ENV-0100-source-set-wake-reads-carriers-per-point.md9008207, 2026-10-04 (feature fluid-body-interaction batch B1, ADR 0002: hydrodynamics/ with SphereHydro.h, BodySubmergence.h (+ CylinderWetFractionGradient, MediumDensity, SetSurface, SetWaveAcceleration), DiskHydro.h, CylinderHydro.h, the panel load laws and NetSolidity.h; tests 319 → 462, all pass. ME and fhsim_base delete their copies in B3, B4). File: resolved/ENV-0101-shape-and-panel-laws-outside-the-environment.md7394531, 2026-10-04 (feature fluid-body-interaction batch B2: BodyWake the one public path, casters in environment::wake::detail; SphereCaster, DiskCaster; PanelSourceSet.h and BodyFlowInteraction.h moved from fhsim_marine_elements; density at construction, WaterVelocity before Register throws, one log, one helper, one accessor set; doc/user/wake-parameters.md; tests 462 → 481 (and 13 disabled), all pass. ME switches in B3). File: resolved/ENV-0102-casting-api-has-several-public-paths.md7ee7fce, 2026-10-04 (owner delegation, feature fluid-body-interaction: the annulus π/4 (D² − d²) in DragForceNormal and face on in FrontalArea; DiskHydro_Test red on the old area (7 cases), all 481 tests pass. fhsim_marine_elements' net-cable discs call DragForceNormal instead of their inline annulus). File: resolved/ENV-0103-disc-face-area-not-the-annulus.md35f097d, 2026-10-04 (owner ruling 2026-10-04, feature fluid-body-interaction: Cd on the cross-flow Reynolds number |v_n| D / ν, Ct on |v| D / ν, the lift branch removed; CylinderHydro_Test red on the old derivatives (2 tests) and the old 4-argument API, all 482 tests pass). File: resolved/ENV-0104-cylinder-reynolds-number-blows-up-near-axial-flow.md4664729, 2026-10-04 (owner decision b 2026-10-04, feature fluid-body-interaction: cylinder_hydro::DragForce, DragForceJacobian, DragFormulation { Independence, Eames }, kWaterKinematicViscosity; seven CylinderDrag tests, red (not compiling) before the functions existed, all 489 tests pass). File: resolved/ENV-0105-cylinder-drag-force-has-no-shared-home.md3a2ca11, 2026-10-04 (owner delegation 2026-10-04, feature fluid-body-interaction, fhsim_marine_elements MARE-0331: below the branch edge Cd is White's 1 + 10 Re^(−2/3), the edge moved to its crossing with the middle branch, kLowReynolds = 20.582841580981793, Re clamped to 1 below 1 (Cd = 11, slope 0); CylinderHydro_Test red on 5 tests before the change, all 492 tests pass after). Corrected by 2f4335f, 2026-10-04: the clamp at Re = 1 underestimated Stokes-range drag about 3× (digitised White/Wiley curve 33.3 at Re = 0.2, 16.4 at 0.5); White's correlation now applies down to the floor kMinimumReynolds = 1e-6 (Cd = 1 + 1e5, slope 0), which only keeps Cd finite in axial flow (30.2 at Re = 0.2, 16.9 at 0.5, 11 at 1). CylinderHydro_Test red on 3 tests before (Cd(0.2) = 11 against 33.3), all pass after; new TinyNormalSpeedsGiveFiniteForceAndJacobian_MARE0331 (force and Jacobian finite for v_n down to 0 and 1e-20 m/s). File: resolved/ENV-0106-cylinder-normal-drag-steps-at-reynolds-14.md