|
FhSim
3.1.0
Marine systems simulation
|
State of the checked-out commit f28798a, dated 2026-09-24.
The gtest binary test_fhsim_base (tests/CMakeLists.txt) is built from src/RunTests.cpp, a handful of engine/system-level test files (System-InitSim_Test.cpp, Template-Input_Test.cpp, Engine-TOutput_Test.cpp, Engine-Consistency_Test.cpp, Sundials-Consistency_Test.cpp) and one SimObj-<Name>_Test.cpp per tested SimObject, plus a few Connect-*_Test.cpp files that wire two or more SimObjects together and two dedicated Jacobian-<Name>_Test.cpp files (Jacobian-LowPass_Test.cpp, Jacobian-Cable_Test.cpp). gtest_discover_tests registers each gtest case as a ctest test prefixed fhsim_base. (tests/CMakeLists.txt:107-117); run the whole suite with ctest -R "^fhsim_base\." from the build directory, or the built test_fhsim_base[_d] binary directly. A separate, non-gtest ctest case, fhsim_base.Cli.Template_Input_Mass (tests/CMakeLists.txt:124-130), runs the FhSim command-line executable itself once against Template-Input_Mass_in.xml, since none of the gtest cases go through fhsim::CreateSimulationManagerFromFile.
Inputs live under tests/in/<ID>/<ID>_in.xml, one folder per test ID, each paired with a _ref.csv regression baseline compared via run.CompareRegression (framework-side). Helpers live in tests/src/TestUtils.h: RunAndCompareRegressionXmlTiming (used by nearly every SimObj-*_Test.cpp) reads <Timing TEnd=...> from the input XML, calls fhsim::test::RunTest, and asserts the regression comparison (tests/src/TestUtils.h:129-159). No test in this suite is DISABLED_. At f28798a one test did not run at all: SimObj-Samplifier_Test.cpp was commented out of tests/CMakeLists.txt:51. Since BASE-0030 (resolved) it runs again and asserts the noise statistics rather than a _ref.csv. At f28798a no visualization-gated test existed. Since the 2026-09-24 issue pass, SimObject.Visual_CameraShot and SimObject.Visual_BillboardArrow in SimObj-Visual_Test.cpp compile only under FH_VISUALIZATION, because without it those two objects register no ports.
fhsim::test::TestSpec.jacobianMode defaults to Auto (fhsim/include/fhsim/testtools/TestSpec.h). Any RunTest() call left at that default runs CheckJacobians 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) — so most regression tests in this library are, without saying so, also 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).
Seven test files in this library explicitly set spec.jacobianMode = TestJacobianMode::No, disabling the check outright: SimObj-Body6d_Test.cpp, SimObj-TorpedoAnchor_Test.cpp, SimObj-FlukeAnchor_Test.cpp, SimObj-NorrbinTanker_Test.cpp, SimObj-Cable_Test.cpp, Connect-BallBox_Test.cpp and Template-Input_Test.cpp. In every one of the anchor/body/cable cases the reason is a documented partial analytic Jacobian that would fail the FD comparison once the omitted force term becomes non-zero — see section 3. Two files instead run a dedicated, explicit jacobianMode = Yes with their own tolerance/sample override: Jacobian-LowPass_Test.cpp (relTol 1e-5, 3 samples) and Jacobian-Cable_Test.cpp (relTol 1e-2, 3 samples, an initial condition chosen to sit clearly in tension, away from the cable's slack/tension kink).
What Auto-mode checking does not cover: JacobianCheckConfig::timePoints changes only T, not the state (fhsim/src/testtools/JacobianChecker.cpp:200-222); every sample checked is the initial condition or a small perturbation of it. A Jacobian term that only switches on away from the initial condition — CBall's wall contact, a cable's slack-to-taut transition, a surface crossing — is never probed by the default check even when HasJacobians()==true and the check nominally runs; CBall was the concrete case in this library (see section 3; Jacobian-Ball_Test.cpp now starts it in contact, BASE-0026, resolved). No test in this library uses PortJacobianBlockChecker.
Update, 2026-09-24: FhSim's Jacobian checker with trajectory samples, shifted offsets and per-scope overrides. The rest of this report describes f28798a. Built against an FhSim that has JacobianCheckConfig::scopeOverrides (fhsim/3.2.0@sintef/jaccheck), the Jacobian checks change in five ways:
jacobianMode = No any more. Each test above now runs the default check with a scopeOverrides entry that exempts only its partial object, citing that object's issue:Anc for the anchors (BASE-0027). Since its resolution only Template-Input exempts them, because it starts the anchor moving in the water;A for Body/6DBody (BASE-0025 and BASE-0034, both resolved since, and the exemption removed);A for the tanker (BASE-0028 and BASE-0036), since removed: the tanker's Jacobian is complete and every tanker test checks it. The cable in SimObj-Cable and Template-Input_Cable was exempted too until BASE-0035 (resolved) added the drag derivatives and moved both inputs off the slack/taut kink; the cable is now checked in full there, at a finite-difference step of 1e-6.Connect-BallBox needed no exemption, and its check passes.Jacobian-Cable_Test.cpp keeps the default tolerances. Its cable scenarios use a finite-difference step of 1e-6: at the default 1e-8 the rounding error of the stiff axial force, about 1e-8 of it, swamps the O(1) entries. Jacobian.Cable_submerged checks the drag derivatives in a 1 m/s current.Jacobian-NonZeroOffset_Test.cpp checks, at a non-zero offset, the entries of each partial object that do agree with finite differences.
Jacobian-GlobalStatePortReads_Test.cpp builds each object whose OdeJacobian reads an input port directly, behind five upstream states, and checks that every port read is handed the whole state vector (BASE-0042). FhSim's checker cannot see this, because it fills the port caches before each analytical evaluation.
One row per ADD_SIMOBJECT_REF line in src/fhsim_base.cpp that is not commented out (69 lines; ExternalLinkStandard is registered twice under a legacy and a current input name for the same class, so the table below counts it once, giving 68 distinct SimObjects, matching the audit total). Six further registrations are commented out and excluded here — CShip, CVessel_1, CLinearDamper, CRetractableHydroCable, CLinearSpringDamper, Bouy — plus COrbit; see BASE-0031 (resolved). 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 |
|---|---|---|---|---|---|---|---|
| CRamp | Src/Ramp | SimObj-Ramp_Test.cpp | Regression vs _ref.csv | None (no state) | N/A | C | |
| CStep | Src/Step | SimObj-Step_Test.cpp | Regression | None | N/A | C | |
| CSine | Src/Sine | SimObj-Sine_Test.cpp | Regression | None | N/A | C | |
| CChirpsignal | Src/Chirpsignal | SimObj-Chirpsignal_Test.cpp | Regression | None | N/A | C | |
| CClock | Src/Clock | SimObj-Clock_Test.cpp; also feeds SimObj-Integrator | Regression | None | N/A | C | |
| CPulse | Src/Pulse | SimObj-Pulse_Test.cpp | Regression | None | N/A | C | |
| CRandom | Src/Random | SimObj-Random_Test.cpp, _1, _2 variants | Regression | None | N/A | C | |
| CConstant | Src/Constant | SimObj-Constant_Test.cpp | Regression | None | N/A | C | |
| CSmoothTrajectory | Src/SmoothTrajectory | SimObj-SmoothTrajectory_Test.cpp; also in Connect-MassSmoothTrajectorySineSum | Regression | None | N/A | C | |
| CSignalList | Src/SignalList | SimObj-SignalList_Test.cpp | Regression | None | N/A | C | BASE-0022 (resolved) |
| CExit | Sink/Exit | SimObj-Exit_Test.cpp | Regression | None | N/A | C | |
| CSVSink | Sink/CSV | SimObj-CSVSink_Test.cpp | Regression | None | N/A | C | |
| CProgress | Sink/Progress | SimObj-Progress_Test.cpp | Regression | None | N/A | C | |
| CProgressBar | Sink/ProgressBar | SimObj-ProgressBar_Test.cpp | Smoke: runs to the end (no outputs) | None | N/A | C | BASE-0022 (resolved) |
| ExternalLinkStandard | ExternalLinkStandard, System/ExternalLinkStandard | SimObj-ExternalLinkStandard_Test.cpp | Regression | None | N/A | C | |
| CPassthrough | System/Passthrough | SimObj-PassThrough_Test.cpp | Regression | None | N/A | C | |
| CCameraController | Camera/Controller | SimObj-Visual_Test.cpp | Smoke without rendering | None | N/A | C | BASE-0023 (resolved) |
| CCameraController2 | Camera/Controller2 | SimObj-Visual_Test.cpp | Smoke without rendering | None | N/A | C | BASE-0023 (resolved) |
| CCameraShot | Camera/Shot | SimObj-Visual_Test.cpp | Smoke without rendering | None | N/A | C | BASE-0023, BASE-0038 (resolved) |
| CBillboardText | Visual/BillboardText | SimObj-Visual_Test.cpp | Smoke without rendering | None | N/A | C | BASE-0023 (resolved) |
| CBillboardArrow | Visual/BillboardArrow | SimObj-Visual_Test.cpp | Smoke without rendering | None | N/A | C | BASE-0023, BASE-0037 (resolved) |
| CSimpleBillboardArrow | Visual/SimpleBillboardArrow | SimObj-Visual_Test.cpp | Smoke without rendering | None | N/A | C | BASE-0023 (resolved) |
| CDistArrow | Visual/DistArrow | SimObj-Visual_Test.cpp | Smoke without rendering | None | N/A | C | BASE-0023 (resolved) |
| C3DBody | Body/3DBody | SimObj-Body3d_Test.cpp | Regression vs _ref.csv | Inherited from CMass (dense+port); gravity term is a state-independent constant, so inheriting it is valid | Yes — Auto/implicit near IC | A | |
| C6DBody | Body/6DBody | SimObj-Body6d_Test.cpp, Template-Input_Test.cpp, Jacobian-NonZeroOffset_Test.cpp | Regression; body-frame spin of a tilted body against the analytic quaternion | Complete dense state Jacobian: pos/quat kinematics matching OmegaToQDot, and the rotation of gravity and the global force/moment through R(q); no port Jacobians | Yes — default check, and the whole block at a non-zero offset | A | BASE-0025, BASE-0034 (resolved) |
| CMass | Body/Mass | SimObj-Mass_Test.cpp; indirectly in Connect-LinearSpringMass*, Connect-MassSmoothTrajectorySineSum, Engine-Consistency_*, Sundials-Consistency_*, Engine-TOutput_* | Regression | Complete dense + port Jacobians (Force/Pos/Vel); exact, trivial-but-correct | Yes — Auto/implicit in every scenario listed | A | BASE-0021 (resolved) |
| CBox | Ballbox/Box | SimObj-Box_Test.cpp, Connect-BallBox_Test.cpp | Regression | None (HasJacobians() unset/false); dynamics trivial (posDot=vel, velDot=0, rotDot=0) | No (FD fallback; Connect-BallBox explicitly sets No too) | C | |
| CBall | Ballbox/Ball | SimObj-Ball_Test.cpp, Connect-BallBox_Test.cpp, Jacobian-Ball_Test.cpp, Jacobian-NonZeroOffset_Test.cpp | Regression + dedicated Jacobian | Complete dense: kinematic term plus the wall and ball-pair contact stiffness | Yes — Jacobian-Ball starts one ball, and three balls, in contact with walls and each other | A | BASE-0026, BASE-0041 (resolved) |
| CLowPass | Math/LowPass | SimObj-LowPass_Test.cpp, Jacobian-LowPass_Test.cpp | Regression + dedicated Jacobian | Dense + sparsity + output/input port Jacobians; closed form of ẋ = (u − x)/τ | Dedicated (jacobianMode=Yes, relTol 1e-5, 3 samples) + implicit Auto | A | |
| CLinearSystem | Math/LinearSystem | SimObj-LinearSystem_Test.cpp, Jacobian-NonZeroOffset_Test.cpp | Regression | Dense + sparsity + port Jacobians; A, B, C, D matrices taken directly from parameters | State: yes, implicit Auto (constant matrix). Port Jacobians: yes, in feedback with an integrator in Jacobian-NonZeroOffset | A | BASE-0024 (resolved) |
| CLinearMapping | Math/LinearMapping | SimObj-LinearMapping_Test.cpp | Regression | None (algebraic) | N/A | C | BASE-0022 (resolved) |
| CAbsolute | Math/Absolute | SimObj-Absolute_Test.cpp | Regression | None (algebraic) | N/A | C | |
| CBias | Math/Bias | SimObj-Bias_Test.cpp | Regression | None | N/A | C | |
| CDerivative | Math/Derivative | SimObj-Derivative_Test.cpp, _ofConstant, _ofLin | Regression | None | N/A | C | |
| CDivide | Math/Divide | SimObj-Divide_Test.cpp | Regression | None | N/A | C | |
| CGain | Math/Gain | SimObj-Gain_Test.cpp | Regression | None | N/A | C | |
| CIntegrator | Math/Integrator | SimObj-Integrator_Test.cpp (3 cases: base, _ofConstant, _ofLin), Jacobian-NonZeroOffset_Test.cpp | Regression | ∂ẋ/∂x = 0 with empty sparsity (CIntegrator.cpp:75-90); port Jacobians carry the input/output identity | State: yes, implicit Auto (trivially zero). Port Jacobians: yes, in feedback with a second integrator in Jacobian-NonZeroOffset | A | BASE-0021 (resolved), BASE-0024 (resolved) |
| CLogic | Math/Logic | SimObj-Logic_Test.cpp | Regression | None | N/A | C | |
| CMathfunction | Math/Mathfunction | SimObj-Mathfunction_Test.cpp | Regression | None | N/A | C | |
| CProduct | Math/Product | SimObj-Product_Test.cpp | Regression | None | N/A | C | |
| CSaturation | Math/Saturation | SimObj-Saturation_Test.cpp | Regression | None | N/A | C | |
| CSign | Math/Sign | SimObj-Sign_Test.cpp | Regression | None | N/A | C | |
| CSum | Math/Sum | SimObj-Sum_Test.cpp | Regression | None | N/A | C | |
| CTrigonometric | Math/Trigonometric | SimObj-Trigonometric_Test.cpp | Regression | None | N/A | C | |
| CVectorNorm | Math/VectorNorm | SimObj-VectorNorm_Test.cpp | Regression | None | N/A | C | |
| And | Boolean/And | SimObj-And_Test.cpp | Regression | None | N/A | C | |
| Equal | Boolean/Equal | SimObj-Equal_Test.cpp | Regression | None | N/A | C | |
| Not | Boolean/Not | SimObj-Not_Test.cpp | Regression | None | N/A | C | |
| Or | Boolean/Or | SimObj-Or_Test.cpp | Regression | None | N/A | C | |
| CSwitch | Signal/Switch | SimObj-Switch_Test.cpp | Regression | None | N/A | C | |
| CDemux | Signal/Demux | SimObj-Demux_Test.cpp | Regression | None | N/A | C | |
| CMux | Signal/Mux | SimObj-Mux_Test.cpp | Regression | None | N/A | C | |
| CSelector | Signal/Selector | SimObj-Selector_Test.cpp | Regression | None | N/A | C | |
| CLinearSpring | Cable/LinearSpring | SimObj-LinearSpring_Test.cpp; indirectly in Connect-LinearSpringMass* | Regression | None at all — stateless, no OdeJacobian/port Jacobian/InputOutputJacobian override | No (Auto skips — object reports no Jacobians; the force-vs-position feedthrough to CMass is FD-only) | C | |
| CCable | Cable | SimObj-Cable_Test.cpp (fine/coarse cable), Jacobian-Cable_Test.cpp | Regression + dedicated analytic-vs-FD Jacobian check | Dense; analytic structural (tension+damping) and hydrodynamic drag per segment + winch-retraction terms. Near-seabed lift/contact and the winch virtual point's node dependence are not differentiated (documented on the class page) | Yes — default check in the regression tests and dedicated in air and submerged, all at FD step 1e-6 | A | BASE-0035 (resolved) |
| TorpedoAnchor | TorpedoAnchor | SimObj-TorpedoAnchor_Test.cpp, Jacobian-NonZeroOffset_Test.cpp | Regression | Dense, complete out of the water: kinematics, quaternion dynamics, inertia and Coriolis, gravity, input force; drag, soil and added-mass derivatives omitted | Yes — default check (in air), and at a non-zero offset; Template-Input exempts it (moving in water) | A | BASE-0027 (resolved) |
| FlukeAnchor | FlukeAnchor | SimObj-FlukeAnchor_Test.cpp, Jacobian-NonZeroOffset_Test.cpp | Regression | Same as TorpedoAnchor (shared CRigidBody::GetSecondDerivativeJacobian) | As TorpedoAnchor | A | BASE-0027 (resolved) |
| CNorrbinTanker | Vessels/Tanker | SimObj-NorrbinTanker_Test.cpp, Template-Input_Test.cpp, Jacobian-NonZeroOffset_Test.cpp | Regression of a powered turn; independent of it: the turn's sign against both rudder signs, speed below 16 knots, finite states, a coasting tanker slowing down, the bernica hull accelerations against Fossen's MSS tanker.m in deep and shallow water, and Engine = 1 holding the service shaft speed and 16 kn | Complete dense: hull, rudder (through c(u,n) and beta), propeller and shaft partials | Whole block, default check in every test, and at a non-zero offset from a slow, turning, powered state | A | BASE-0028 (resolved), BASE-0032 (resolved), BASE-0036 (resolved), BASE-0043 (resolved), BASE-0045 (resolved) |
| Samplifier | Signal/Samplifier | SimObj-Samplifier_Test.cpp | Statistics, not a _ref.csv: sample rate, noise mean and standard deviation within 4 standard errors, bias-only output exact. Delay, outliers and loss untested | No Jacobian (single dummy state) | N/A | B | BASE-0030 (resolved) |
| Pendulum | Example/Pendulum | SimObj-Examples_Test.cpp, Jacobian-NonZeroOffset_Test.cpp | Regression | Complete, exact, simple analytic Jacobian | Yes — default check, and at a non-zero offset | A | BASE-0029 (resolved) |
| HydroBall | Example/HydroBall | SimObj-Examples_Test.cpp, Jacobian-NonZeroOffset_Test.cpp | Regression | Complete analytic Jacobian, including the drag-term derivative | Yes — default check, and at a non-zero offset | A | BASE-0029 (resolved) |
| HydroCable | Example/HydroCable | SimObj-Examples_Test.cpp, Jacobian-NonZeroOffset_Test.cpp | Regression | Complete dense Jacobian: spring, damping and drag per segment; the Current input taken as given | Yes — default check, and the whole block at a non-zero offset, FD step 1e-6/1e-5 | A | BASE-0029, BASE-0039 (resolved) |
| ZeroDynamics | Kalman/ZeroDynamics | SimObj-Examples_Test.cpp | Regression | None; OdeFcn sets XDot=0 identically, so the (absent) Jacobian is trivially correct | N/A (FD fallback, trivially zero) | C | BASE-0029 (resolved) |
| KalmanHydroBall | Kalman/Example/HydroBall | SimObj-Examples_Test.cpp | Regression | Inherits HydroBall's complete Jacobian unmodified (adds only measurement/covariance methods) | Yes — default check | A | BASE-0029 (resolved) |
| KalmanHydroCable | Kalman/Example/HydroCable | SimObj-Examples_Test.cpp | Regression | Inherits HydroCable's Jacobian unmodified | Yes — default check, FD step 1e-6 | A | BASE-0029, BASE-0039 (resolved) |
| KalmanPendulum | Kalman/Example/Pendulum | SimObj-Examples_Test.cpp, Jacobian-NonZeroOffset_Test.cpp | Regression | Inherits Pendulum's Jacobian unmodified | Yes — default check, and at a non-zero offset | A | BASE-0029 (resolved) |
| VirtualCurrent | Adaptive/VirtualCurrent | SimObj-Examples_Test.cpp | Regression (behaviour not validated; the class carries a warning) | None (FD fallback); stateful per-layer current/integral/derivative states | N/A (FD fallback) | C | BASE-0029 (resolved), BASE-0040 (resolved) |
| CablePendulum | Adaptive/CablePendulum | SimObj-Examples_Test.cpp, Jacobian-NonZeroOffset_Test.cpp | Regression | Complete dense Jacobian: spring, damping and drag per segment, and the ball's drag; the current taken as given where it is read | Yes — default check, and the whole block at a non-zero offset, FD step 1e-6/1e-5 | A | BASE-0029, BASE-0039, BASE-0040 (resolved) |
Summary after the 2026-09-24 issue pass (section 1 update and BASE-0022 to 0030, 0032, 0034 to 0036, 0039, 0041): 18 A, 1 B, 49 C, 0 D. The rest of this paragraph describes f28798a: 3 A, 3 B, 42 C, 20 D. 18 of 68 registered SimObjects have an analytic Jacobian; 6 of those 18 are verified against finite differences in some test (C3DBody, CMass, CLowPass, CLinearSystem, CIntegrator — state block only for the last two — and CCable); the other 12 (C6DBody, CBall, TorpedoAnchor, FlukeAnchor, CNorrbinTanker, Pendulum, HydroBall, HydroCable, KalmanHydroBall, KalmanHydroCable, KalmanPendulum, CablePendulum) are not.
src/body/CMass.cpp:66-141) — dense + port Jacobians, complete and exact (constant-coefficient point-mass dynamics). C3DBody (src/body/C3DBody.h:88) derives from CMass and adds no state or Jacobian override, so it inherits the same guarantee; this is safe here because the added gravity term does not depend on state. Both classes are affected by the global-versus-local state-offset bug filed as @ref fhsim_base_issue_0021.src/body/C6DBody.cpp:297-462, Jacobian header at :297-331) — the header comment states "Velocity/omega rows: zero (gravity coupling omitted per task scope)" (line 331). But CalcDerivatives rotates gravity into the body frame via the attitude-dependent matrix R(q) before dividing by mass (:228,232-233,244), so d(velDot,omegaDot)/d(quat) is generally non-zero and is not represented. This is the library's one case where "partial by design" and "actually inconsistent with the ODE" seem to coincide — tests/CMakeLists.txt:17 still carries a # TODO : fix the simobject in fhsimbaselib next to this test's registration. BASE-0025 (resolved) added the missing blocks and removed the TODO.src/filters/CLowPass.cpp:81-...) — dense + sparsity + port Jacobians for ẋ = (u − x)/τ; the only object in this library with a dedicated Jacobian test tight enough (relTol 1e-5) to be a real check rather than a formality.A,B,C,D read straight from parameters; ∂ẋ/∂x=0 for the integrator), so the Auto check near the initial state is as good as any for the state block. Neither object's port Jacobians (OutputPortJacobian/InputPortJacobian) are exercised by any test: each has exactly one single-object scenario (SimObj-Integrator, SimObj-LinearSystem), so no Connect-* scenario ever feeds their ports to a stateful neighbour. See BASE-0024 (resolved).src/ballbox/CBall.cpp): at f28798a the Jacobian had only the kinematic block, and its comment deferred the contact force to a "numerical fallback" that does not exist; the only checked scenario kept the box 1000 units away, and a time-only force cache hid the contact term from every finite difference (BASE-0041). Since BASE-0026 (resolved) OdeJacobian adds the wall contact stiffness ‘-k n n’and the ball-pair stiffness, the latter mirroringForceInterBallstep by step, andJacobian-Ball_Test.cppchecks both in contact.**CLinearSpring** (src/cable/CLinearSpring.h:112) — fully stateless (OdeFcnis a no-op inline), withForceA/ForceBoutput ports that are an algebraic function of the PosA/PosBinput ports, but noInputOutputJacobianoverride. When connected toCMass (Connect-LinearSpringMass*), the spring-to-mass force coupling is FD-only.**CCable/Jacobian-Cable** (src/cable/Cable.cpp:537-761) — analytic Jacobian covers per-segment structural (tension+damping) derivatives and winch-retraction terms; it omits the hydrodynamic drag (computeHydrodynamicDrag,Cable.cpp:408-418) and gravity/buoyancy terms (Cable.cpp:450-454,527-528) thatOdeFcn's force computation includes. Jacobian-Cable_Test.cppargues this is acceptable because drag is O(1) against structural stiffness O(EA/L)~1e7, and its IC is chosen clearly in tension to avoid the non-differentiable slack/tension kink that the regression test's IC sits on. *Since BASE-0035 (resolved):* the drag derivatives are inOdeJacobian, every cable input starts off the kink, and no test exempts the cable.**TorpedoAnchor / FlukeAnchor** — atf28798a, near-identical Jacobians covering kinematics, quaternion dynamics and aω×vterm only, whose velocity rows disagreed with finite differences. Since BASE-0027 (resolved) both call CRigidBody::GetSecondDerivativeJacobian, which differentiates the inertia and Coriolis terms, the input force and gravity exactly, also for a non-unit quaternion. It omits the hydrodynamic drag, soil and added-mass derivatives, so it is complete out of the water.**CNorrbinTanker** (src/vessel/CNorrbinTanker.cpp) — covers most of the nonlinear hydrodynamic force partials (a substantial hand-derivation) but the test comment (tests/src/SimObj-NorrbinTanker_Test.cpp:13-15) states rudder/thruster coupling and beta derivatives are omitted, and the check is disabled entirely. See BASE-0028; the regression baseline itself may also be unvalidated (BASE-0032, resolved: the test now also asserts the turn's direction and a bounded speed). **Update:** the Jacobian now has every partialOdeFcn has, and the whole block is checked (BASE-0028, resolved).**Pendulum, HydroBall, HydroCable, CablePendulum** — each has a complete (or complete-looking) analytic Jacobian (src/kalman/Pendulum.cpp:36,38, src/kalman/HydroBall.cpp:63,65,src/kalman/HydroCable.cpp:321,380, src/adaptive/CablePendulum.cpp:287,348). Atf28798anone of them had any automated test exposure.KalmanHydroBall,KalmanHydroCableandKalmanPenduluminherit these Jacobians unmodified. See BASE-0029 (resolved). Since then,PendulumandHydroBallpass the full check. Since BASE-0039 (resolved), so doHydroCable,KalmanHydroCableand CablePendulum, whose Jacobians now include the drag derivatives.**ZeroDynamics, VirtualCurrent** — no analytic Jacobian at all.ZeroDynamics::OdeFcn (src/kalman/ZeroDynamics.cpp:23-27) sets every state derivative to exactly zero, so its FD Jacobian is trivially and unconditionally correct;VirtualCurrent` has no override and falls back to FD for its adaptive per-layer states.This list was written against f28798a. Items 1, 5, 6 and 9 were done on 2026-09-24 (marked "resolved"). Item 3 is partly done (the rows that agree are checked at a non-zero offset). Items 3 and 4 turned out to depend on model changes and are blocked on the model owner; see their issues.
Pendulum, HydroBall, HydroCable, CablePendulum) a RunTest scenario.** This is the single highest-value item here: Auto mode makes the Jacobian check free once any scenario exists, and example XML to start from already exists for three of the four (examples/input/KalmanPendulum.xml, CablePendulum.xml, HydroBallCable.xml). BASE-0029 (resolved)C6DBody's Jacobian to include the gravity/quaternion coupling its own ODE has, using the dR/dq expressions the header comment already derives one block up, then re-enable its Auto check. This is the one Jacobian gap in this library that looks like a genuine bug rather than an accepted omission. BASE-0025 (resolved)TorpedoAnchor, FlukeAnchor and CNorrbinTanker, following the Jacobian-Cable pattern (relaxed tolerance, an IC that avoids the documented-omitted regime), so the substantial implemented-but-never-checked force-derivative work in these three objects gets verified instead of the check being switched off entirely. BASE-0027 (resolved: the anchors' Jacobian is now complete out of the water), BASE-0028 (resolved)CBall a Jacobian scenario that actually reaches the wall, since the current SimObj-Ball check runs but never leaves the trivial kinematic block. BASE-0026 (resolved), BASE-0041 (resolved)CIntegrator's and CLinearSystem's port Jacobians against a connected neighbour — sequence this after @ref fhsim_base_issue_0021 (the global-state-offset bug), since fixing that issue changes the numeric offsets a new connected test would exercise. BASE-0024 (resolved)CSignalList, CLinearMapping, CProgressBar and the seven camera/billboard objects — 10 of this library's 20 untested SimObjects, none of which needs a Jacobian check. BASE-0022 (resolved), BASE-0023 (resolved)Samplifier's test. Its RNG draws a portable std::mt19937 through std::normal_distribution/std::uniform_real_distribution, whose bits-to-value transform is implementation-defined, which is consistent with the disabling comment's "almost same,
but swapped pairwise." Needs an owner decision on whether to make the transform portable or assert statistically instead. Done: the owner kept the distributions and the test asserts statistics. BASE-0030 (resolved)CNorrbinTanker's regression baseline was checked against an independent expectation; the CMake TODO next to its registration ("difficult to predict") suggests it may not have been. BASE-0032 (resolved)ADD_SIMOBJECT_REF registrations that reference dead or nonexistent classes (CShip, CVessel_1, CLinearDamper, CRetractableHydroCable, CLinearSpringDamper, Bouy), plus COrbit. Zero behavioural risk; purely a readability cleanup. BASE-0031 (resolved)Not re-filed here: the global-versus-local state-offset Jacobian bug affecting CMass, CIntegrator, CLinearSystem and CLowPass is already tracked as @ref fhsim_base_issue_0021.