Files
Ymir/scripts/run-all-tests.sh
vinihcrosaandClaude Opus 4.8 d344710305 test(physics): golden validation suite + C++ tests in turbo monorepo (#3)
* test(physics): add TMS Dynamics golden validation suite

Cross-engine validation of Ymir force models against the TMS Dynamics 3.0
golden dataset (vessel1 VLCC). Strategy is state-in -> force-out: the golden
motion trajectory is fed row by row into the matching Ymir force model and the
result is compared against the golden per-force decomposition. This is
integrator-independent, so it validates the force formula, not the time step.

- GoldenCsv.h: header-only CSV reader (column access by name)
- Vessel1Params.h: force configs mapped from vessel1.json, with the tonne->SI
  x1000 unit conversion documented (verified empirically to ~3e-5 on Fr_z)
- TestGolden08Restoring.cpp: restoring (Fr_z, Mr_y) + heave damping (Fd_z)
- Downsampled golden fixtures under tests/fixtures/golden/08_restoring/
- ymir_golden_tests target wired into tests/CMakeLists.txt

Tolerances are set empirically and the residual causes are documented: the
restoring divergence (up to ~6.5% Fr_z / ~13.5% Mr_y as pitch grows) is the
off-diagonal hydrostatic coupling Ymir omits by design. Findings on squat
(diverges ~4 orders — model gap) and the body-frame-flow forces (need TMS
heading-convention mapping) are captured under docs/specs/features/.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* build(tests): run C++ suite through the turbo monorepo

Make core/ a pnpm workspace package so `pnpm test` (turbo run test) runs the
C++ ctest suite alongside the JS vitest suites — one command for everything.

- Add "core" to pnpm-workspace.yaml so turbo discovers @ymir/core
- core/package.json: add a `build` script (cmake configure + compile)
- core/turbo.json: override `test` to dependsOn ["build"] instead of the root's
  build:wasm, so native ctest does not require Emscripten; cache off (native)
- scripts/run-all-tests.sh: standalone runner (cpp/js/e2e flags) for running
  the C++ suite without touching the wasm/emcc toolchain

@ymir/core#build -> @ymir/core#test: 270/270 tests pass via turbo.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* test(physics): add wind golden validation + reconstruct flow convention

Extends the cross-engine golden suite to wind drag (scenario 02_wind) and adds
the earth->body flow conversion needed by flow-dependent forces.

- GoldenFrame.h: nautToBodyFrame mirroring NavalDomain — rotates the scenario
  environment (speed + nautical direction) into the body frame using the golden
  heading, so wind/current vectors can be reconstructed from controlDict + the
  golden trajectory
- Vessel1Params.h: vessel1Wind() config with the full Cd(angle) table
- TestGolden02Wind.cpp: Fwd_x validated to ~4% across the trajectory (residual
  is the rho_air constant: Ymir 1.225 vs reference ~1.27); Fwd_y/Mwd_z ~0 bow-on
- Fixtures for 02_wind

Documents the remaining divergences with measured numbers: current OBOKATA
disagrees in sign and magnitude (Fc_x -83kN vs golden +172kN — sectional-area
approximation + sign convention), squat off by ~4 orders. See
docs/specs/features/golden-validation/.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* chore(graphify): refresh knowledge graph after golden validation work

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(physics): align rho_air, squat sinkage, and damping norm to reference

Cross-engine alignment against the TMS Dynamics 3.0 golden dataset (see
docs/planning/force-model-alignment/). Verified against the reference engine
sources in dynamics/nashville/src/*Forces/.

- rho_air 1.225 -> 1.275 (PhysicalConstants.h): matches PhysicalProps.cpp of the
  reference and closes the ~4% wind gap. Wind golden test tightened to <3%.
- SquatForces: fix double division by (rho*g) — the sinkage numerator is now
  volumetricWeight/(rho*g*L^2) (single division, matching the reference), not
  nabla_/(rho*g*L^2). Also use heave relative to equilibrium (z + draft) for the
  depth/seabed clamp, since the reference q[2] is waterline-referenced. Squat
  goes from ~4 orders of magnitude off to the correct order (partial: <=5% still
  blocked by the artificial 07 scenario depth dynamics).
- DampingForces: the exponential-decay velocity norm now uses all 6 DOF (matching
  dampingForces/DampingForces.cpp); the surge/sway sign was already correct given
  Ymir's speedToWater convention.

Restoring needs no change: the reference restoring is also diagonal-only, so the
residual is a zero-crossing artifact, not a missing off-diagonal term.

Full C++ suite: 271/271 green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs(physics): add force-model-alignment spec/design/tasks + findings

TLC spec-driven planning for aligning all force models to the golden dataset:
- spec.md: 11 requirements (FMA-01..11), tolerances, decisions
- design.md: reference formula per model cross-checked with Ymir; root causes
- tasks.md: atomic tasks + execution status of the first pass

Records the key research corrections: restoring reference is diagonal (no
off-diagonal), squat is a double-division bug, current alignment conflicts with
self-propelled physics (reverted, needs speedToWater-convention resolution).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* chore(graphify): refresh graph after force-model alignment

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-06 08:11:23 -03:00

110 lines
3.7 KiB
Bash
Executable File

#!/usr/bin/env bash
#
# run-all-tests.sh — run the full Ymir test suite.
#
# Suites:
# cpp C++ physics engine — CMake build + ctest (core/)
# js Monorepo unit tests — vitest via turbo (pnpm test)
# e2e Web end-to-end — Playwright (opt-in, needs deps) (@ymir/web)
#
# Usage:
# scripts/run-all-tests.sh # cpp + js
# scripts/run-all-tests.sh --e2e # cpp + js + e2e
# scripts/run-all-tests.sh --cpp # cpp only
# scripts/run-all-tests.sh --js # js only
# scripts/run-all-tests.sh --e2e-only # e2e only
#
# Exit code is non-zero if any selected suite fails. Suites run to completion
# (no fail-fast) so you see every failure in one pass.
set -uo pipefail
# --- locate repo root -------------------------------------------------------
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
cd "$ROOT"
# --- colors (disabled when not a TTY) --------------------------------------
if [[ -t 1 ]]; then
BOLD=$'\033[1m'; RED=$'\033[31m'; GREEN=$'\033[32m'; CYAN=$'\033[36m'; RESET=$'\033[0m'
else
BOLD=''; RED=''; GREEN=''; CYAN=''; RESET=''
fi
section() { printf '\n%s========== %s ==========%s\n' "$BOLD$CYAN" "$1" "$RESET"; }
# --- suite selection --------------------------------------------------------
RUN_CPP=1
RUN_JS=1
RUN_E2E=0
case "${1:-}" in
--cpp) RUN_CPP=1; RUN_JS=0; RUN_E2E=0 ;;
--js) RUN_CPP=0; RUN_JS=1; RUN_E2E=0 ;;
--e2e-only) RUN_CPP=0; RUN_JS=0; RUN_E2E=1 ;;
--e2e) RUN_CPP=1; RUN_JS=1; RUN_E2E=1 ;;
"") ;;
-h|--help) sed -n '2,23p' "${BASH_SOURCE[0]}" | sed 's/^# \{0,1\}//'; exit 0 ;;
*) echo "${RED}Unknown option: $1${RESET}"; exit 2 ;;
esac
declare -a RESULTS
FAILED=0
record() { # name, status
if [[ "$2" -eq 0 ]]; then
RESULTS+=("${GREEN}PASS${RESET} $1")
else
RESULTS+=("${RED}FAIL${RESET} $1")
FAILED=1
fi
}
# --- C++ (CMake + ctest) ----------------------------------------------------
run_cpp() {
section "C++ engine (core/) — CMake + ctest"
if ! command -v cmake >/dev/null 2>&1; then
echo "${RED}cmake not found on PATH${RESET}"; return 1
fi
local gen=()
command -v ninja >/dev/null 2>&1 && gen=(-G Ninja)
cmake -S core -B core/build "${gen[@]}" -DYMIR_BUILD_TESTS=ON -DYMIR_BUILD_APPS=OFF || return 1
cmake --build core/build || return 1
ctest --test-dir core/build --output-on-failure || return 1
}
# --- JS (turbo → vitest) ----------------------------------------------------
run_js() {
section "Monorepo unit tests — pnpm test (vitest)"
if ! command -v pnpm >/dev/null 2>&1; then
echo "${RED}pnpm not found on PATH${RESET}"; return 1
fi
[[ -d node_modules ]] || pnpm install || return 1
pnpm test || return 1
}
# --- E2E (Playwright) -------------------------------------------------------
run_e2e() {
section "Web E2E — Playwright (@ymir/web)"
if ! command -v pnpm >/dev/null 2>&1; then
echo "${RED}pnpm not found on PATH${RESET}"; return 1
fi
[[ -d node_modules ]] || pnpm install || return 1
pnpm --filter @ymir/web test:e2e || return 1
}
# --- run selected suites ----------------------------------------------------
[[ "$RUN_CPP" -eq 1 ]] && { run_cpp; record "C++ (ctest)" $?; }
[[ "$RUN_JS" -eq 1 ]] && { run_js; record "JS (vitest)" $?; }
[[ "$RUN_E2E" -eq 1 ]] && { run_e2e; record "E2E (playwright)" $?; }
# --- summary ----------------------------------------------------------------
section "Summary"
for line in "${RESULTS[@]}"; do printf ' %s\n' "$line"; done
if [[ "$FAILED" -eq 0 ]]; then
printf '\n%sAll selected suites passed.%s\n' "$GREEN$BOLD" "$RESET"
else
printf '\n%sOne or more suites failed.%s\n' "$RED$BOLD" "$RESET"
fi
exit "$FAILED"