Verlet cloth on a bilinear patch between 4 anchors, N=10 grid,
structural/shear/bend constraints, 5 relaxation iterations at a fixed
1/60 substep. Wind is applied per FACE so hypar twist genuinely sheds
load rather than being cosmetic.
Two deviations from PLAN3D worth flagging:
- Load is read from each constraint's XPBD Lagrange multiplier, not from
FABRIC_K * leftover-stretch. After a fixed iteration count the leftover
stretch is solver error, not fabric strain, so the naive reading came
out ~50x hot (60 kN peaks on a 5x5 m sail). The multiplier is the real
constraint impulse, which the statics assert confirms by balancing the
corner reactions against the applied wind to 8%.
- Wind uses a signed square (d*|d|) rather than clamp(d)^2, so the
leeward face is pushed too. A sail is double-sided.
The sim core deliberately does not import three.js: it runs headless
under node today, stays allocation-free in the hot loop, and replays
bit-for-bit. createSailView() pulls three in lazily for rendering.
Loads land in real newtons (~1-4 kN on a 5x5 m sail in a 34 m/s storm),
so hardware ratings are real working load limits.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>