FhSim  3.1.0
Marine systems simulation
Loading...
Searching...
No Matches
Testing report

State of the checked-out commit f42360e, dated 2026-09-24, updated for the FhSim Jacobian checker of the jacobian-checker-coverage branch (trajectory samples, shifted state offsets, per-scope overrides); see the CHANGELOG entry under [Unreleased]. Updated by the 2026-09-24 fix pass for the smoke tests of MARE-0028/0029 and the resolution of MARE-0035 and MARE-0038, by the resolution of MARE-0029 and MARE-0042 on 2026-09-25, and by the RMCable smoke test of MARE-0028 on 2026-09-25, and by the resolution of MARE-0044, 0046, 0056, 0059, 0065, 0067 and 0072 on 2026-09-25, and by the tests of MARE-0030, MARE-0032 (a, c) and MARE-0027 (a) on 2026-09-25, and by the review fixes of MARE-0077 and MARE-0078 on 2026-09-25, and by the resolution of MARE-0048 to 0055 and 0061 to 0063 on 2026-09-25, and by the MARE-0045 and MARE-0079 review follow-ups and the resolution of MARE-0047 on 2026-09-25, and by the resolution of MARE-0057, 0058, 0060, 0066, 0068, 0070, 0071 and 0074 on 2026-09-25 and its review, and by the regression baselines of MARE-0033 and MARE-0032 (b) on 2026-09-25, and by the resolution of MARE-0082, 0083 and 0084 on 2026-09-25, which regenerated three of them, and by the owner's review and approval of all five baselines on 2026-09-25, which resolved MARE-0032 and narrowed MARE-0033 to Cable/Branched, and by the resolution of MARE-0085 on 2026-09-25, which regenerated the NetStructure baseline.

1. How the library is tested

The gtest binary test_fhsim_marine_elements (tests/CMakeLists.txt) builds 24 source files: RunTests.cpp, TestUtils_Test.cpp, one file per Jacobian-tested SimObject (Disk_Test.cpp, Cable_Test.cpp, CableBranched_Jacobian_Test.cpp, CableConnector_Test.cpp, NetStructure_Jacobian_Test.cpp, Sphere_Test.cpp, TrawlBeam_Test.cpp, which also holds TrawlBeam's behaviour checks), two more NetStructure Jacobian files (NetStructure_PortJacobian_Test.cpp, NetStructure_JacobianOptOut_Test.cpp), NetStructure_DiskDrag_Test.cpp, NetStructure_PanelWeight_Test.cpp, NetStructure_MaxAcceleration_Test.cpp (NetStructure, NetStructureArray and NetStructureWakeEffect), three smoke-test files (RbCable_Test.cpp, RigidBarCable_Test.cpp, TrawlMooring_Test.cpp, which also holds the trawl/mooring behaviour checks) and three files of behaviour checks for net siblings (NetStructureWithConstraints_Test.cpp, NetStructureWakeEffect_Test.cpp, NetStructureArray_Test.cpp), NetStructure_Regression_Test.cpp (the NetStructure regression baseline), NetStructure_FreeDrift_Test.cpp (a free panel in a current, NetStructure and NetStructureWakeEffect), NetElement3NForces_Test.cpp (the net panel force core called directly) and CatenarySolve_Test.cpp (MooringCable's catenary solve). tests/CMakeLists.txt also builds the test-only SimObject library marine_elements_test_objects (tests/objects/) into SimObjectLibraries, with two objects for the port-Jacobian tests: Test/ForceSource, a force source with an analytical OutputPortJacobian, and Test/SignalSink, a consumer with an analytical InputPortJacobian. No install rule installs the library, and the bundle component excludes it. The binary is registered as the ctest target fhsim_marine_elements (tests/CMakeLists.txt, add_test(NAME "fhsim_marine_elements" ...)); run it with ctest -R fhsim_marine_elements from the build directory, or invoke the built test_fhsim_marine_elements[_d] binary directly (it accepts a working directory and a gtest filter as its own arguments, see tests/RunTests.cpp).

Inputs live under tests/in/<SimObjectName>/ (one folder per tested object, e.g. tests/in/Disk/Disk_in.xml, Disk_ref.csv, Disk.ref); a handful of auxiliary fixtures referenced by File= attributes are copied to the binary's working directory by tests/CMakeLists.txt (SimpleNet.xml, SimplePanelNet.xml, LowStrainPanelNet.xml, BranchableCable.xml, among others).

Six objects have a regression baseline, a <ID>.ref/<ID>_ref.csv pair compared by RunAndCompareRegressionXmlTiming: Disk, and Sphere_Regression, Cable_Regression, CableConnector_Regression, TrawlBeam_Regression and NetStructure_Regression. The five new ones were reviewed and approved by the owner on 2026-09-25, with plots and hand checks in Regression baselines (doc/user/baselines/); they compare every column at an absolute RMS of 1e-8. Shared helpers: tests/TestUtils.h (RunAndCompareRegressionXmlTiming, timing parsing) and tests/JacobianTestUtils.h (ExpectJacobiansMatch, a thin wrapper around fhsim::test::CheckJacobians that fails if nothing was checked). No DISABLED_ tests and no Ogre/visualization-gated tests exist in this suite.

Auto-mode Jacobian checking

fhsim::test::TestSpec.jacobianMode defaults to Auto (fhsim/include/fhsim/testtools/TestSpec.h). Any RunTest() call left at that default runs CheckJacobians on the whole scenario once the simulation finishes, and fails the test on a mismatch, whenever any SimObject in the scenario reports HasJacobians()==true (fhsim/src/testtools/TestRunner.cpp:328-332) — not only when a test explicitly asks for a Jacobian check. The default check config is relTol=1e-4, absTol=1e-10, central-FD step 1e-8, and it samples the initial condition plus 3 random states perturbed by 1% (fhsim/src/testtools/JacobianChecker.h:22-29).

None of this library's tests set jacobianMode=No. The seven smoke tests leave it at Auto, a no-op for their objects, which have no analytic Jacobian. Six of the other seven RunTest-based tests in this suite set jacobianMode=Yes explicitly with their own tolerance override, so for them "dedicated" and "implicit-Auto" coincide. Disk_Test.cpp's RunAndCompareRegressionXmlTiming (regression test) is the exception: it builds its TestSpec without touching jacobianMode, so it is also, implicitly, a Jacobian check — at the framework's default relTol=1e-4, which the explicit Disk_Jacobian test now uses as well — so Disk's Jacobian is checked twice, at the same tolerances. CableBranched_Jacobian_Test.cpp and NetStructure_Jacobian_Test.cpp instead call ExpectJacobiansMatch (tests/JacobianTestUtils.h), which calls fhsim::test::CheckJacobians directly, bypassing TestSpec/jacobianMode entirely. TrawlBeam_TrawlForce and TrawlBeam_Controller also leave jacobianMode at Auto, so they check TrawlBeam's Jacobian implicitly, at the default tolerances, in the force-saturated branch too.

What the checker covers since the jacobian-checker-coverage FhSim branch: besides the initial condition and its perturbations, it checks at 3 states the scenario's own integrator reaches (trajectorySamples), and repeats every per-SimObject check with a neutral stateful object placed first (shiftStateOffsets), so every object sits at a non-zero state offset. The second found that all seven Jacobians in this library indexed their local block and state slice with the global offset AddState returns; they now subtract their first state. The first found MARE-0036 in Cable/Branched, so CableBranched_Jacobian runs without trajectory samples. NetStructure_PortJacobian_Test.cpp checks NetStructure::InputPortJacobian in the system scope of CheckJacobians, and with PortJacobianBlockChecker, which checks only that a port-Jacobian callback is called and writes inside its buffer.

2. Coverage table

One row per ADD_SIMOBJECT_REF/ADD_SIMOBJECT_REF3 line in src/fhsim_marine_elements.cpp that is not commented out (22 rows; the 23rd registration, RbCable/Collision, is commented out at :22 — see 0001 — `RbCable/Collision` is registered only in a commented-out line). Rating: A, behaviour asserted and the Jacobian verified where one exists; B, partly; C, smoke/regression only or indirect; D, untested.

SimObject Input name Tests What is asserted Jacobian implementation Jacobian verified Rating Issues
Disk Net/Disk Disk_Test.cpp: 2 gtest Regression vs .ref/_ref.csv (also, implicitly, an Auto-mode Jacobian check); dedicated Jacobian analytic-vs-numeric Dense, hand-derived: pos-vel identity, smooth-region buoyancy z-gradient, drag with symmetric subgradients at kinks (src/net/Disk.cpp:124-219); seabed friction and the surface step discontinuity omitted Yes, twice: dedicated (default tolerances, 3 samples) + implicit Auto-mode via the regression test A —
Sphere Net/Sphere Sphere_Test.cpp: 3 gtest Jacobian analytic-vs-numeric; a neutrally buoyant sphere in a current follows the closed-form drift (Sphere_Current_AcceleratesTowardsTheCurrent, MARE-0084, resolved); regression baseline Sphere_Regression (sinking to terminal velocity in a current; reviewed and approved by the owner, 2026-09-25) Dense, hand-derived: pos-vel, buoyancy z-gradient, drag on the velocity relative to the water (src/net/Sphere.cpp, OdeJacobian) Yes, dedicated, relTol=1e-4, 3 samples B —
Cable (LumpedMass) Cable/LumpedMass Cable_Test.cpp: 3 gtest Jacobian; the end forces of a cable hanging in still water carry its weight in water (Cable_StillWater_EndForcesCarryTheWeight, MARE-0082, resolved); regression baseline Cable_Regression (hanging in a cross current, no Jacobian check as the Jacobian omits drag; reviewed and approved by the owner, 2026-09-25) Dense, hand-derived via InternalCable::ComputeStructuralJacobian: spring + damping + numerical damping (src/cable/subroutines/InternalCable.cpp:220-345) Yes, dedicated, default tolerances, 3 samples B 0036 — The InternalCable structural-damping Jacobian ignores the compression-speed clamp, so it is wrong wherever that clamp is active
CableBranched Cable/Branched CableBranched_Jacobian_Test.cpp: 1 gtest (ExpectJacobiansMatch, bypasses TestSpec/Auto) Jacobian only Dense; reuses InternalCable blocks plus junction cross-coupling; HasJacobians() gated off unless maxAcceleration>=1e9 (src/cable/CableBranched.cpp:203-207) Yes, dedicated, relTol=1e-2/absTol=0.1 scoped to the object, 3 samples, no trajectory samples (MARE-0036); the IC checks see a slack cable (MARE-0037) B 0033 — Cable/Branched has Jacobian tests only, no behavioural baseline, 0036 — The InternalCable structural-damping Jacobian ignores the compression-speed clamp, so it is wrong wherever that clamp is active, 0037 — Until an output port runs CalcOutput, a CableBranched element keeps its default relaxed length, so the fixture's IC and shifted-offset Jacobian checks see a slack cable
CableConnector RbCable/CableConnector CableConnector_Test.cpp: 2 gtest Jacobian; regression baseline CableConnector_Regression (point mass under Test/ForceSource; reviewed and approved by the owner, 2026-09-25) Dense, trivial exact: pos-vel identity block only (forces are frozen ports, gravity constant) Yes, dedicated, relTol=1e-8, 5 samples B —
NetStructure Net/NetStructure NetStructure_Jacobian_Test.cpp: 2 gtest (ExpectJacobiansMatch x2); NetStructure_PortJacobian_Test.cpp: 4 gtest (Test/ForceSource into a force port, Test/SignalSink on Pos, Vel and VelNorm, each in tag and node-number form); NetStructure_JacobianOptOut_Test.cpp: 5 gtest (baseline and the four HasJacobians() opt-outs); NetStructure_DiskDrag_Test.cpp: disk drag of two short cables, with the disks in the middle and at DiskPos 0.25, a thick cable (NetStructure_DiskDragThickCable, which tells the MARE-0074 face-on area from the former one), and the end shares of a sphere; NetStructure_PanelWeight_Test.cpp: panel weight in water; NetStructure_MaxAcceleration_Test.cpp; NetStructure_Regression_Test.cpp; NetStructure_FreeDrift_Test.cpp; NetElement3NForces_Test.cpp: the force core called directly Jacobian, both dense-state and panel sub-block, and the input and output port blocks; HasJacobians() false exactly for its four opt-outs; disk drag (face-on and edge-on) against its formula and its end shares; sphere mass and weight end shares; panel weight 9.81 × CalcWeight; MaxAcceleration limits the magnitude; a free panel drifts at the current speed (MARE-0083, resolved); the twine drag coefficient is taken at the Reynolds number of the flow through the meshes (MARE-0085, resolved); regression baseline NetStructure_Regression (a panel drifting in a current; reviewed and approved by the owner, 2026-09-25) Dense; cable structural+drag analytic, panel block via forward-mode AD (sfh::ad::Dual<18>) over NetElement3NForces; InputPortJacobian and OutputPortJacobian (node ports, VelNorm) analytic, offered only while HasJacobians() is true (MARE-0077, resolved); StrainEnergy has none (MARE-0078); HasJacobians() (src/net/NetStructure.cpp:821-836) gated off for mass/velocity/acceleration adaptations and cables carrying an attached sphere/disk State: yes, dedicated (relTol 1e-4 cable; panel relTol 1e-3, absTol 1e-3 at step 1e-6). Port Jacobian: yes, system scope B 0018 — The OpenCL sources are never compiled, 0027 — Three follow-ups left by the scalar-templated panel force core, 0064 — The BottomContact output of the NetStructure family is always zero, 0078 — NetStructure reports analytical port Jacobians but has no OutputPortJacobian for its state-dependent output ports
TrawlBeam TrawlBeam TrawlBeam_Test.cpp: 4 gtest (TrawlBeam_Jacobian, TrawlBeam_TrawlForce, TrawlBeam_Controller, TrawlBeam_Regression) Jacobian; a backward warp pull slows the beam at any heading; initial states with StartTravel; a braking force limited by SaturationForce; regression baseline of the controller tracking its speed against a trawl pull (reviewed and approved by the owner, 2026-09-25) Dense; PID-controller ODE, hand-derived branch-by-branch (power-saturated / force-saturated / unsaturated), matching OdeFcn exactly (src/trawl_mooring_interaction/TrawlBeam.cpp) Yes, dedicated, relTol=1e-5, 5 samples B —
RMCable RbCable/RMCable RbCable_Test.cpp: RMCable_Smoke (own fixture, the hard-coded planar chain started on its constraints, 1 s) Run completes, outputs finite; the last link, to the moving end point, keeps unit length to 2e-6 (MARE-0047, resolved) None (full FD fallback; no HasJacobians() override) N/A C 0028 — The whole RbCable family (RMCable, RBCable, Cable6Dof, CableRM, Ring) has zero automated test coverage
RBCable RbCable/RBCable RbCable_Test.cpp: RBCable_Smoke (examples/input/RigidBodyCable.xml, 1 s) Run completes None (FD fallback) N/A C 0010 — `RbCable/RBCable` has no force output for end A, and the RbCable group's force-port case is unsettled, 0028 — The whole RbCable family (RMCable, RBCable, Cable6Dof, CableRM, Ring) has zero automated test coverage
Cable6Dof RbCable/6DofCable RbCable_Test.cpp: Cable6Dof_Smoke (examples/input/C6DofCable.xml, 1 s) Run completes None (FD fallback) N/A C 0028 — The whole RbCable family (RMCable, RBCable, Cable6Dof, CableRM, Ring) has zero automated test coverage
CableRM RbCable/Cable None — None (FD fallback) N/A D 0010 — `RbCable/RBCable` has no force output for end A, and the RbCable group's force-port case is unsettled, 0025 — `RbCable/Cable` documents the `RBCABLE_CLASSIC_INTERFACE` branch, but no build defines that macro, 0028 — The whole RbCable family (RMCable, RBCable, Cable6Dof, CableRM, Ring) has zero automated test coverage
Ring RbCable/Ring RbCable_Test.cpp: Ring_Smoke (examples/input/Ring.xml, 1 s) Run completes None (FD fallback) N/A C 0017 — `RbCable/Ring` reads `NumInputsPerConnector` and `Cm_IsAddedMassHack` without using or documenting them, 0028 — The whole RbCable family (RMCable, RBCable, Cable6Dof, CableRM, Ring) has zero automated test coverage
RigidBarCable Cable/RigidBarCable RigidBarCable_Test.cpp: RigidBarCable_Smoke (examples/input/RigidBarCable.xml, 1 s) Run completes None (FD fallback) N/A C 0014 — Cable/RigidBarCable binds both ForceB\<i\>a and ForceB\<i\>b to the same slot, and reads neither
NetStructureArray Net/NetStructureArray NetStructureArray_Test.cpp: NetStructureArray_Smoke and NetStructureArray_InitialPositions (ArrayNet.xml, two panels and a cable, 0.01 s, nodes placed 2 % apart, velocities unset); NetStructureArray_MaxAcceleration in NetStructure_MaxAcceleration_Test.cpp Run completes, outputs finite; NumExternalPos and the position ports match NodeOutputs; the given positions are kept (MARE-0070, resolved) and the twine tension pulls a node with no other load along the panel diagonal; MaxAcceleration limits the magnitude None; direct SimObject subclass, inherits none of NetStructure's Jacobian (FD fallback) N/A C 0064 — The BottomContact output of the NetStructure family is always zero, 0080 — NetStructureArray gives its cable elements no inertia, so a node on cables only has zero mass
NetStructureWakeEffect Net/NetStructureWakeEffect NetStructureWakeEffect_Test.cpp: NetStructureWakeEffect_ForcePorts, NetStructureWakeEffect_NodeNumberPorts (cable-only SimpleNet.xml, 0.01 s), NetStructureWakeEffect_ExternalAndBottomForces (WakeEffectCableNet.xml, 2 ms); NetStructureWakeEffect_MaxAcceleration in NetStructure_MaxAcceleration_Test.cpp; NetStructureWakeEffect_FreeDrift_DoesNotOutrunTheCurrent in NetStructure_FreeDrift_Test.cpp <tag>Force/ExternalForce<node> outputs echo their inputs exactly, NetTotalForceCheck is zero, UseTagForExternalNodes picks the port names; an input force accelerates its node at F/m and the seafloor pushes a cable up (MARE-0068, resolved); MaxAcceleration limits the magnitude; a free panel drifts at the current speed (MARE-0083, resolved) None; direct SimObject subclass (FD fallback) N/A C 0064 — The BottomContact output of the NetStructure family is always zero
NetStructureWithConstraints Net/NetStructureWithConstraints NetStructureWithConstraints_Test.cpp: 3 input-validation cases, NetStructureWithConstraints_Smoke (SimplePanelNet.xml, 0.01 s), NetStructureWithConstraints_CablesThreadIndependent (CablePanelNet.xml, 116 cables), NetStructureWithConstraints_SolverFrequency Invalid input fails at setup with its message; run completes, outputs finite; one- and eight-thread runs bit-identical; SolverFrequencyCoefficient is read, its default 100 gives the former result None; direct SimObject subclass (FD fallback) N/A C —
MooringCable MooringCable TrawlMooring_Test.cpp: MooringCable_Smoke (own fixture with a TrawlCable and constant end points, 1 s), MooringCable_BottomChainContact, MooringCable_ContactOrder, MooringCable_Buoy, MooringCable_BuoyOutsideMidRope, MooringCable_Reversed, MooringCable_EmptySegment, MooringCable_Straight, MooringCable_NearlySlack; CatenarySolve_Test.cpp: CatenarySolve.* (the initial-layout solve, against fhlib and the small-sag approximation) Run completes, outputs finite; contact radius of the bottom chain; the same contact whatever the <Lib> order; buoy element and its bounds; a line and its reverse give the same end forces; a chain or mid rope without an element fails at setup; a line as long as its span starts straight and fast, and lines 2e-9 and 1e-8 longer start fast with the small-sag shape None (FD fallback) N/A C 0043 — MooringCable segfaults with NumElements="10"
TrawlCable TrawlCable TrawlMooring_Test.cpp: TrawlCable_Smoke (own fixture with constant winch and bridle-end inputs, 1 s), TrawlCable_NoClumpWeight_Smoke (the same without a clump weight), TrawlCable_BottomBridleArea Run completes, outputs finite; the bottom bridle's default area follows its own diameter None (FD fallback) N/A C 0003 — `TrawlCable`'s `Heun_i` branch is commented out while `RundDorg`'s is live
RundDorg RundDorg TrawlMooring_Test.cpp: RundDorg_Smoke (own fixture, clump weight and constant line ends, 1 s; also checks the force vectors), RundDorg_UnknownWeightType, RundDorg_NumCpuCoreZero, RundDorg_StartCpuCoreNegative, RundDorg_Mirrored, RundDorg_StillWater Run completes, outputs finite; an unknown WeightType, a NumCpuCore below 1 or a negative StartCpuCore fails at setup with its message; mirror symmetry with Line1 and Line2 swapped; force vectors against magnitudes and directions; still water against a negligible current None (FD fallback) N/A C —
NetStructureTest Net/NetStructureTest None; Windows-only registration — None (FD fallback) N/A D 0031 — The Windows-only NetStructureTest family (NetStructureTest, NetStructureABAQUS, NetStructureConstrained) has no test coverage, 0081 — NetCableElementTest still puts sphere and disk mass and weight on the wrong end and splits disk drag as −pos and 1 − pos (Windows only)
NetStructureABAQUS Net/NetStructureABAQUS None; Windows-only registration — None (FD fallback) N/A D 0031 — The Windows-only NetStructureTest family (NetStructureTest, NetStructureABAQUS, NetStructureConstrained) has no test coverage, 0081 — NetCableElementTest still puts sphere and disk mass and weight on the wrong end and splits disk drag as −pos and 1 − pos (Windows only)
NetStructureConstrained Net/NetStructureConstrained None; Windows-only registration — None (FD fallback) N/A D 0031 — The Windows-only NetStructureTest family (NetStructureTest, NetStructureABAQUS, NetStructureConstrained) has no test coverage

Summary: 1 A, 6 B, 10 C, 5 D. 7 of 22 registered SimObjects have an analytic Jacobian; all 7 are verified against finite differences (state Jacobian). NetStructure's port Jacobians, the only ones in the library, are verified through the system Jacobian.

3. Jacobian implementation notes

  • Disk (src/net/Disk.cpp:124-219) — dense, hand-derived. Pos-vel identity block; surface-buoyancy z-gradient included under an explicit smoothRegion guard (position, the min(1,submersion) clip, and the seabed all checked); drag block computes symmetric subgradients at the MagN=0/MagT=0 kinks with an explanatory comment. Seabed friction and the belowSurface step discontinuity at z=DiskD/2 are omitted (documented at :218, a non-smooth term by construction).
  • Sphere (src/net/Sphere.cpp, OdeJacobian) — same pattern as Disk: pos-vel identity, smooth-region buoyancy z-gradient, drag block on the velocity relative to the water. The belowSurface step is likewise omitted.
  • Cable (LumpedMass) — the shared kernel, InternalCable::ComputeStructuralJacobian (src/cable/subroutines/InternalCable.cpp:220-345): pos-vel identity, a numerical-damping diagonal term, and per-segment structural spring + damper blocks (dFA_dposA, dFA_dposB, dFA_dvelA, dFA_dvelB), each contributed only while 0 < totalForce < maxT (the Bound() saturation region is zeroed, matching OdeFcn).
  • CableBranched — reuses the same InternalCable blocks for its segments plus AddEndpointJacobianContributions for junction cross-coupling. HasJacobians() (src/cable/CableBranched.cpp:203-207) is gated to true only when maxAcceleration>=1e9, because the acceleration Bound() clamp is otherwise non-smooth.
  • CableConnector (src/RMCable/CableConnector.cpp:53) — trivial and exact: only the pos-vel identity block, because its forces are frozen input ports and a constant gravity term, neither of which depends on the connector's own state.
  • NetStructure (src/net/NetStructure.cpp) — the most invested Jacobian in the library. Cable blocks are hand-derived (shared InternalCable kernel); panel blocks are computed by forward-mode automatic differentiation (sfh::ad::Dual<18>) over the templated force core NetElement3NForces, evaluating the exact derivative of the force code as written, with no step or truncation error (src/net/JACOBIAN_RECIPE.md records the derivation history, including the MARE-0004/MARE-0026 issue trail that replaced an inexact forward-difference panel block with this AD path). HasJacobians() (:821-836) falls back to full FD whenever a mass/velocity/acceleration adaptation is active or any cable element carries an attached sphere or disk, and HasPortJacobians() follows it (MARE-0077, resolved). InputPortJacobian() (rows local since MARE-0038, resolved) is tested through Test/ForceSource, OutputPortJacobian() (node ports and VelNorm, MARE-0078) through Test/SignalSink. StrainEnergy has no port Jacobian; FhSim's numerical pass fills the zero block that leaves.
  • TrawlBeam (src/trawl_mooring_interaction/TrawlBeam.cpp:126) — dense, hand-derived branch-by-branch to match the PID-controller ODE's own saturation branches (power-saturated / force-saturated / unsaturated), so the Jacobian tracks OdeFcn's regime exactly rather than approximating across the switch.

4. Prioritised improvement list

  1. NetStructure test gaps — resolved (MARE-0032): the port-Jacobian test and a fixture per HasJacobians()==false branch exist, and so does a regression baseline, whose plots (Regression baselines) the owner reviewed and approved on 2026-09-25. The port-Jacobian tests found MARE-0077 (resolved) and MARE-0078. 0078 — NetStructure reports analytical port Jacobians but has no OutputPortJacobian for its state-dependent output ports
  2. CableRM has zero test coverage (its fixture waits on MARE-0025); RMCable, RBCable, Cable6Dof and Ring have smoke tests only. 0028 — The whole RbCable family (RMCable, RBCable, Cable6Dof, CableRM, Ring) has zero automated test coverage
  3. MooringCable, TrawlCable and RundDorg had zero test coverage — resolved: they and RigidBarCable now have smoke tests (MARE-0029, resolved).
  4. NetStructureArray, NetStructureWakeEffect and NetStructureWithConstraints get none of NetStructure's Jacobian investment. Each now has a fixture that runs its own code path: behaviour checks of specific fixes for WakeEffect and WithConstraints (MARE-0056, 0059, 0060, 0066, 0067, 0068, 0072) and for NetStructureArray (MARE-0030, 0066, 0070, resolved). An analytic Jacobian for them was out of that issue's scope and has no issue of its own.
  5. Sphere, Cable, CableBranched, CableConnector and TrawlBeam have Jacobian tests only — regression baselines now exist for all but CableBranched, and the owner reviewed and approved their plots (Regression baselines) on 2026-09-25. Their hand checks found defects in the Cable weight, the net's solidity-scaled current and the Sphere's unused current (MARE-0082, 0083, 0084, resolved); the three baselines are regenerated after the fixes. MARE-0033 stays open, narrowed to CableBranched, which waits for the deferred MARE-0036 and MARE-0037. 0033 — Cable/Branched has Jacobian tests only, no behavioural baseline
  6. The Windows-only NetStructureTest family (NetStructureTest, NetStructureABAQUS, NetStructureConstrained) has no test coverage on any platform. The owner decided to keep and document them (MARE-0005, resolved); the tests need a Windows build. 0031 — The Windows-only NetStructureTest family (NetStructureTest, NetStructureABAQUS, NetStructureConstrained) has no test coverage
  7. Stale Jacobian-test tolerance comments on Cable and Disk — resolved: both tests now use the default tolerances (MARE-0034, resolved).
  8. Root JACOBIAN_ANALYSIS.md — refuted: the file is untracked and ignored (.gitignore), not part of the repository, and the Disk defect it describes is fixed in src/net/Disk.cpp:124-219 (MARE-0035, refuted).