What's wrong
engine/cpp/solve_mfem.cpp has two places where a surface (force/traction/pressure) load on the solid path is silently dropped from the solve instead of erroring:
apply_surface_loads, solve_mfem.cpp:563-568 — when a load's face selection matches zero boundary elements (matched == 0), it printfs to stdout and continues, dropping the load entirely.
make_surface_load_coefficient, solve_mfem.cpp:480-484 — when a "force" load's matched area is <= 0, it printfs and returns nullptr; the caller (solve_mfem.cpp:582-583) then continues, again dropping the load.
Both cases produce a solve that "succeeds" with the load simply missing — no exception, no message reaching the UI, only a stdout log line nobody sees outside a local build.
Why this is inconsistent, not just theoretically bad
The shell solve path already treats this exact situation as fatal: distributeShellSurfaceLoad in web/src/workers/solver.worker.ts:963-964 does
if (totalArea <= 0)
throw new Error("shell solve: loaded facets have zero total area");
So the same physical mistake — a load selection that resolves to zero area — is a loud, user-visible error on the shell path and a silent no-op on the solid path. Per CLAUDE.md, a change to the solve payload "almost always has a shell path, a solid path, and a coupled path — cover all three"; here the shell path was hardened but the solid path's equivalent case was not.
It's also the same failure class the project has spent real effort eliminating elsewhere in solve_mfem.cpp — commit 303ba07 (KOF-216) fixed loads silently vanishing via attribute-tag overwrite in this same function, and add_fixed_vertices/apply_point_loads throw kofne::bc::require_valid_vertex/require_min_dofs a few dozen lines away in the same file. The matched == 0 and zero-area cases are the one place in apply_surface_loads that never made the jump from "log it" to "throw it."
(Distinct from #423, which is about a partial face-selection mismatch inside collect_load_elements getting no diagnostic at all — that one's asking for a log to be added; this one is about two cases that already log, but only to stdout, when the shell path throws for the identical situation.)
Suggested fix
Throw (matching the shell path's message and the rest of this file's convention) instead of printf + continue/return nullptr, for both:
matched == 0 in apply_surface_loads (solve_mfem.cpp:563-568)
area <= 0 for a "force" load in make_surface_load_coefficient (solve_mfem.cpp:480-484)
Location
engine/cpp/solve_mfem.cpp:563-568, :480-484 (solid path) vs. web/src/workers/solver.worker.ts:963-964 (shell path, for comparison)
Found during a scheduled codebase health scan.
What's wrong
engine/cpp/solve_mfem.cpphas two places where a surface (force/traction/pressure) load on the solid path is silently dropped from the solve instead of erroring:apply_surface_loads,solve_mfem.cpp:563-568— when a load's face selection matches zero boundary elements (matched == 0), itprintfs to stdout andcontinues, dropping the load entirely.make_surface_load_coefficient,solve_mfem.cpp:480-484— when a"force"load's matched area is<= 0, itprintfs and returnsnullptr; the caller (solve_mfem.cpp:582-583) thencontinues, again dropping the load.Both cases produce a solve that "succeeds" with the load simply missing — no exception, no message reaching the UI, only a stdout log line nobody sees outside a local build.
Why this is inconsistent, not just theoretically bad
The shell solve path already treats this exact situation as fatal:
distributeShellSurfaceLoadinweb/src/workers/solver.worker.ts:963-964doesSo the same physical mistake — a load selection that resolves to zero area — is a loud, user-visible error on the shell path and a silent no-op on the solid path. Per CLAUDE.md, a change to the solve payload "almost always has a shell path, a solid path, and a coupled path — cover all three"; here the shell path was hardened but the solid path's equivalent case was not.
It's also the same failure class the project has spent real effort eliminating elsewhere in
solve_mfem.cpp— commit 303ba07 (KOF-216) fixed loads silently vanishing via attribute-tag overwrite in this same function, andadd_fixed_vertices/apply_point_loadsthrowkofne::bc::require_valid_vertex/require_min_dofsa few dozen lines away in the same file. Thematched == 0and zero-area cases are the one place inapply_surface_loadsthat never made the jump from "log it" to "throw it."(Distinct from #423, which is about a partial face-selection mismatch inside
collect_load_elementsgetting no diagnostic at all — that one's asking for a log to be added; this one is about two cases that already log, but only to stdout, when the shell path throws for the identical situation.)Suggested fix
Throw (matching the shell path's message and the rest of this file's convention) instead of
printf+continue/return nullptr, for both:matched == 0inapply_surface_loads(solve_mfem.cpp:563-568)area <= 0for a"force"load inmake_surface_load_coefficient(solve_mfem.cpp:480-484)Location
engine/cpp/solve_mfem.cpp:563-568,:480-484(solid path) vs.web/src/workers/solver.worker.ts:963-964(shell path, for comparison)Found during a scheduled codebase health scan.