* 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>
110 lines
3.7 KiB
Bash
Executable File
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"
|