FhSim  3.1.0
Marine systems simulation
Loading...
Searching...
No Matches
0004 — TrawlDoorAddedFoil discards its computed foil force and applies a hard-coded roll moment
ID 0004
Class BUG
Severity 3
Status blocked
Models Trawl/TrawlDoorAddedFoil
Found 2026-09-15 audit (a7d080d)
Decision needed Is the tabulated foil polar or the hard-coded roll moment the intended physics?

Evidence

src/trawl/TrawlDoorAddedFoil.cpp:68-98, the whole of AddExternalForces:

void TrawlDoorAddedFoil::AddExternalForces(double dT, const double* const adX)
{
ToTrawlDoor::AddExternalForces(dT, adX);
double foilForce[6];
if (m_foilIsVertical) {
double foilAoa = ma_dAlpha + m_inFoilRelAoa->GetPortValue(dT, adX)[0];
double foilArea = m_foilSpan * m_foilCord * sfh::math::Bound(...);
double foilCL = sfh::math::Interp1D(foilAoa, m_foilHydro_Aoa, m_foilHydro_CL, m_numFoilHydroPoints);
double foilCD = sfh::math::Interp1D(foilAoa, m_foilHydro_Aoa, m_foilHydro_CD, m_numFoilHydroPoints);
double lift = (m_isPort ? -1 : 1) * 0.5 * m_rhoWater * foilCL * pow(ma_dU, 2);
double drag = 0; // 0.5 * m_rhoWater * foilCD * pow(ma_dU, 2);
double c = cos(foilAoa);
double s = sin(foilAoa);
foilForce[0] = -c * drag - s * lift;
foilForce[1] = s * drag + c * lift;
foilForce[2] = 0;
}
double r = sfh::math::Bound(m_inFoilExposedAreaRatio->GetPortValue(dT, adX)[0], 0.0, 1.0);
double foilPressureCenter[3];
sfh::math::ArrayCopy(m_inFoilRelPosition->GetPortValue(dT, adX), foilPressureCenter, 3);
foilPressureCenter[2] -= m_height / 2 + r * m_foilSpan / 2;
// AddExternalForce(foilPressureCenter, foilForce);
sfh::sim::CalcMoment(foilPressureCenter, foilForce, foilForce + 3);
for (int i = 0; i < 6; ++i)
foilForce[i] = 0;
foilForce[3] = -(r - 0.5) * 4000 * 5;
sfh::math::ArrayAdd(ma_adSumForces, foilForce, 6);
}
double m_rhoWater
The density of the surrounding fluid.
Definition TrawlDoorBase.h:187
bool m_isPort
Is this a port trawl door?
Definition TrawlDoorBase.h:165

Four problems in one function:

  • Lines 93-94 zero the entire 6-vector that lines 71-92 just computed. Only line 95 survives: a roll moment -(r - 0.5) * 20000 N·m driven solely by FoilExposedAreaRatio, with the 4000 and the 5 hard-coded and unexplained.
  • Consequently FoilHydro_Aoa, FoilHydro_CL, FoilHydro_CD, FoilHydro_Cord, FoilHydro_Span, FoilHydro_IsVertical and the input ports FoilRelativeAoa and FoilRelPosition have no effect on the simulation. FoilExposedAreaRatio is the only input that does anything (and in a visualization build also moves the foil mesh, :113-118).
  • Line 78 hard-codes drag = 0 with the real expression commented out, so FoilHydro_CD is dead even in the pre-zeroing path.
  • foilForce[0..2] are uninitialized when m_foilIsVertical is false, and line 92 (CalcMoment) reads them. Undefined behaviour, harmless only because line 93 then overwrites everything.

The force-application line that would have used the computed pressure centre is itself commented out at line 90.

Effect

A user configures a foil with a tabulated lift/drag polar, a chord, a span and an angle-of-attack control input, and gets a door whose foil produces a fixed-magnitude roll moment proportional to the exposed-area ratio and nothing else. Changing the polar, the geometry or the commanded angle of attack changes nothing. Since the class exists to score control concepts, every such study is measuring the hard-coded moment, not the foil.

Possible fix

If the tabulated model is the intended physics:

  1. Delete lines 93-95 (the zeroing loop and the hard-coded moment).
  2. Initialize double foilForce[6] = {0}; at line 71, so the non-vertical branch contributes nothing instead of reading uninitialized memory — and report a parameter error on FoilHydro_IsVertical == false in the constructor if the horizontal case is genuinely unsupported.
  3. Restore the drag term at line 78 (0.5 * m_rhoWater * foilCD * pow(ma_dU, 2)), the same form as the lift term, using the FoilHydro_CD table the constructor already validates.
  4. foilArea (line 74) is computed and never used. Dimensionally 0.5 * rho * C * U^2 is a pressure, not a force; the sibling hydrodynamics in TrawlDoorBase multiply by the door area. Multiplying lift and drag by foilArea is what makes an exposed-area ratio of 0 produce no force, and makes the ratio physically meaningful rather than a pure mesh-position input.
  5. Apply the force at its pressure centre — restore line 90's AddExternalForce(foilPressureCenter, foilForce), or keep the explicit CalcMoment + ArrayAdd pair, but not both.

Owner question: is the tabulated polar the intended physics, or is the hard-coded moment? The zeroing plus the hard-coded foilForce[3] look like a deliberate debugging substitution ("ignore the foil model, apply a known roll moment") that was committed. Nothing else in the foil chain can be decided before this is answered.

Test that would prove it

Depends on FISH-0001 and on the owner's decision. Two cases in tests/in/SimObj-TrawlDoorAddedFoil*/, both built from examples/input/6DOFTrawlDoor.xml with the foil parameters added and a 2.5 m/s current. Sensitivity: two runs differing only in FoilHydro_CL (all zeros vs. a real polar) must produce different door trajectories — today they are bit-identical, which is the red test and the one sentence that states the defect. Regression: a _ref.csv baseline of door roll, pitch and angle of attack over 30 s, generated after the fix. Also add SimObj-TrawlDoorAddedFoil-Horizontal with FoilHydro_IsVertical = "0" — under a UBSan/MSan build that is the case exhibiting the uninitialized read today.

Risk

High for anyone using Trawl/TrawlDoorAddedFoil: results change completely, because today's results are a hard-coded moment and tomorrow's are a foil. No shipped input file uses the type (grep -rl TrawlDoorAddedFoil examples/ is empty), so nothing in-repo breaks, and the class is documented as a testbed for control concepts rather than a production model.

Why not solved (fix pass 2026-09-15)

Re-confirmed at HEAD (TrawlDoorAddedFoil.cpp:93-95): the computed foil force is zeroed and a hard-coded roll moment -(r-0.5)*4000*5 is applied instead. Whether the tabulated foil polar or the hard-coded moment is the intended physics cannot be read from the code; the polar tables, the interpolation and the energy port suggest the polar was meant to be live, but the hard-coded moment may be a deliberate simplification the owner still relies on. Changing it moves every TrawlDoorAddedFoil result. This item also gates FISH-0005 and FISH-0006, which are unobservable until the computed force reaches the output. Owner question: tabulated polar or hard-coded moment?