107 Commits
Author SHA1 Message Date
vinihcrosaandClaude Opus 5 09498c024f docs: add MIT license and repository description (#4)
CI / Linux x86_64 (push) Canceled after 0s
CI / macOS ARM64 (push) Canceled after 0s
CI / WASM + JS workspaces (push) Canceled after 0s
Repo had no license file and no description metadata. Adds MIT LICENSE,
a license badge and section in the README, and description/license/
repository fields in package.json.

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-30 17:32:52 -03:00
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
vinihcrosa 2b760d4ed8 Merge pull request #2 from vinihcrosa/server-side-physics-plan
feat: run physics on the server; client connects over WebSocket
2026-07-05 12:39:02 -03:00
vinihcrosaandClaude Opus 4.8 50333cd3f7 ci: drop native Windows job from the C++ matrix
The engine ships as WASM-in-Node (covered by the WASM + JS workspaces job);
there is no native Windows target and CGAL/SUNDIALS aren't provisioned on
the Windows runner, so the job only ever failed at configure. Keep the
Linux + macOS native builds, which validate the C++ that compiles to WASM.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-05 12:33:44 -03:00
vinihcrosaandClaude Opus 4.8 dae5f1df0f ci: fix Node version and native C++ source dir
- wasm-web: bump Node 20 -> 22 so the global WebSocket used by the /ws
  integration test exists (it was ReferenceError on Node 20).
- build-and-test: point cmake at core/ (cmake -S core); there is no root
  CMakeLists.txt, so Configure failed with 'does not contain CMakeLists.txt'
  (pre-existing failure on main).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-05 12:29:27 -03:00
vinihcrosaandClaude Opus 4.8 07b9538a2c fix(web): clear persisted sim_id on reset (review round 2)
reset() now calls socket.clearSimId() so the next play() starts a fresh
simulation (CreateSimulation) instead of re-attaching to the old, reset
sim still alive within its TTL. Test asserts the post-reset play emits
CreateSimulation, not AttachSimulation.

Task: server-side-simulation review round 2, issue 001.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-05 12:23:38 -03:00
vinihcrosaandClaude Opus 4.8 0e736320d3 chore(conductor): add setup + run scripts for the monorepo
Setup installs deps, builds @ymir/types and @ymir/wasm, and compiles the
Node WASM engine (best-effort if Emscripten is present). Run scripts: dev
(api+web via concurrently, wired to CONDUCTOR_PORT), api, web, test, wasm.
Concurrent-safe (per-worktree SQLite, per-workspace ports). Adds
concurrently devDependency.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-05 12:20:10 -03:00
vinihcrosaandClaude Opus 4.8 892a7253c0 docs(review): round 2 — reset() does not clear persisted sim_id (1 medium)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-05 12:20:10 -03:00
vinihcrosaandClaude Opus 4.8 d86116e33f fix(sim): address review round 1 (6 issues)
- manager: surface worker_thread errors/exit to clients and tear the sim
  down (issue 001); cache last status/state and replay on attach so a
  reconnecting client sees current state even when paused (issue 002)
- web socket: capped exponential reconnect backoff + bounded command queue
  (issue 003); derive ws/wss URL from page origin (issue 004)
- runner: build replacement engine before freeing the old one (issue 005)
- remove dead VesselConfig message path across types/api/web (issue 006)

Tests added for each fix. Full monorepo suite green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-05 12:05:05 -03:00
vinihcrosaandClaude Opus 4.8 cd29ee66b2 test(api): replace fixed delays with vi.waitFor to de-flake under load
The fan-out and router roundtrip tests waited a fixed 180ms for the 20 Hz
loop to emit a State, which was flaky under CPU contention (full turbo
run). Poll for the State event with vi.waitFor instead.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-03 20:46:59 -03:00
vinihcrosaandClaude Opus 4.8 ff2e3db3bb refactor(web): remove client-side physics engine
Single engine now lives on the server. Delete the simulation Web Worker,
the JS mock (mock-wasm), apps/web/public/wasm, and retire WorkerMessageDTO
and the wasm|mock SimulationEngine union from @ymir/types. Drop the
COOP/COEP headers and worker.format from vite config (were only for
in-browser WASM).

Task: task_08. Verified: web typecheck + unit suite green (83%), types
100%, api build clean, no dangling references. E2E against a live server
is validated separately (requires the running apps/api + web).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-03 20:43:38 -03:00
vinihcrosaandClaude Opus 4.8 dfbcd28762 feat(web): rewire simulationStore to the server via SimulationSocket
Replace the local WASM Web Worker with the WebSocket client. Public store
API (play/pause/reset/loadScenario/applyEnvironment) is preserved; add
sendActuator + hasSession and a connection state (replacing the wasm/mock
engine badge). Update VesselPanel, AppShell, SimulationControls and their
tests; stub WebSocket in the jsdom test setup.

Task: task_07. Tests: full web suite green; store 95.8%, socket 93.9%.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-03 20:41:05 -03:00
vinihcrosaandClaude Opus 4.8 0e04b3c51a feat(web): add SimulationSocket WebSocket client
Client transport to the server-side simulation: manages the WebSocket
lifecycle with auto-reconnect, (de)serializes ClientMessage/ServerMessage,
persists sim_id in localStorage, and re-attaches to the running sim on
reopen. Injectable WebSocket/storage for testing.

Task: task_06. Tests: 9 passing (FakeWebSocket), simulation-socket.ts
93.9% coverage; full web suite green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-03 20:34:06 -03:00
vinihcrosaandClaude Opus 4.8 4a1727e13e feat(api): WebSocket /ws gateway + message router
Add @fastify/websocket (v8, Fastify 4) and a GET /ws endpoint. MessageRouter
validates inbound ClientMessages (TypeBox), dispatches to SimulationManager,
binds each connection to its sim_id on Create/Attach, and maps worker events
to ServerMessage frames. REST routes coexist unchanged.

Task: task_05. Tests: 31 passing (real Fastify + WebSocket roundtrip,
malformed-message handling, REST coexistence); branches 82.5%.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-03 20:31:36 -03:00
vinihcrosaandClaude Opus 4.8 99b202317d feat(api): add SimulationManager (sim lifecycle, sim_id, TTL, fan-out)
Owns every live simulation on the Fastify main thread: spawns one
worker_thread per sim (ADR-002), maps sim_id -> worker + connections,
fans events out to connected clients, enforces a max-sim limit, and
tears orphaned sims down after a configurable TTL (ADR-006). Make
SimulationRunner.dispose idempotent.

Task: task_04. Tests: 19 passing incl. a real worker_thread booting the
engine; coverage 93.6% stmts / 88% branches.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-03 20:27:18 -03:00
vinihcrosaandClaude Opus 4.8 123180b406 ci(build): compile WASM engine before JS/TS tests
Per ADR-005 tests run against the real WASM engine, so CI must build it
first. Add a wasm-web job: pinned emsdk (cached) -> pnpm build:wasm ->
build -> lint -> test. Wire turbo so 'test' dependsOn build:wasm and
^build:wasm, and point build:wasm outputs at the @ymir/wasm runtime.

Task: task_09. Verified turbo ordering locally (rm runtime -> turbo test
rebuilds it); full pipeline runs on GitHub Actions.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-03 20:23:44 -03:00
vinihcrosaandClaude Opus 4.8 18aeb0a525 feat(api): add SimulationRunner + worker_thread bridge
Server-side simulation runtime: SimulationRunner owns one real WASM
engine and its 20 Hz tick loop, applying commands (loadScenario,
play/pause/reset, actuators, environment) and producing snapshots.
attachRunnerToPort bridges it to a worker_thread port; the thread
entrypoint lives in simulation-worker.entry.ts. Adds vitest to apps/api.

Task: task_03. Tests: 12 passing (real engine + MessageChannel bridge),
coverage 98.6% stmts / 89.5% branches.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-03 20:21:59 -03:00
vinihcrosaandClaude Opus 4.8 8a21003c37 feat(wasm): add @ymir/wasm package with Node-target Emscripten build
Package the physics engine as an internal @ymir/wasm workspace package
that runs in Node (worker_threads). Parametrize the Emscripten build
(YMIR_WASM_ENVIRONMENT=node, output dir override) so the same C++ core
builds for the server. Expose SimulationEngine interface +
createSimulationEngine() loader mirroring YmirBindings.cpp.

Task: task_01. Tests: 8 passing against the real WASM engine, 100% coverage.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-03 19:58:08 -03:00
vinihcrosaandClaude Opus 4.8 c54a73080c feat(types): add ClientMessage/ServerMessage network protocol DTOs
Add-only network contract for the server-side simulation: ClientMessage
(commands) and ServerMessage (events) TypeBox unions, reusing
SimulationStateDTO/VesselConfigDTO. Adds ScenarioDraftVesselDTO and
ScenarioInputDTO. WorkerMessageDTO left intact (removed in a later task).

Task: task_02. Tests: 45 passing, 100% coverage.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-03 19:53:51 -03:00
vinihcrosaandClaude Opus 4.8 994fbc999c docs(planning): server-side simulation PRD, techspec, ADRs, tasks
Plan to move physics from the browser (WASM Web Worker) to the server.
Client becomes a thin remote terminal; single engine; foundation for
persistence. Includes 6 ADRs and a 9-task breakdown.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-03 19:53:51 -03:00
vinihcrosaandClaude Opus 4.8 c97d431b4d Merge branch 'claude/nostalgic-thompson-49408c' — animated Gerstner ocean shader
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 12:42:39 -03:00
vinihcrosaandClaude Opus 4.8 213619b92c chore(graphify): refresh knowledge graph after ocean shader change
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 12:42:19 -03:00
vinihcrosaandClaude Opus 4.8 a5a76f9a47 feat(web): animated Gerstner ocean shader synced to the sim sea state
Replace the flat blue water plane with a Gerstner-wave ShaderMaterial.
Wave components are derived from the environment store's active wave
keyframe (Hs, Tp, dirNaut): wavelength from the deep-water dispersion
relation, amplitude anchored to Hs/2, direction from the nautical
bearing. The detail patch follows the camera (snapped to its grid) with
a flat far-sea plane behind for the horizon; phase animates off the
render clock so the surface moves regardless of sim play/pause.

Add a SceneErrorBoundary around the GLTF-loading area mesh and each
vessel so a missing/4xx .glb (assets are gitignored) renders nothing
instead of throwing up the tree and blanking the whole Canvas.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 12:42:18 -03:00
vinihcrosa 83d53b3f68 Merge branch 'claude/magical-swartz-6c3650' — 3D vessel heading fix 2026-06-30 12:06:12 -03:00
vinihcrosaandClaude Opus 4.8 62454962dc fix(web): correct 3D vessel heading — bow leads instead of moving sideways
The vessel mesh's bow points along local -Z, but the heading was applied with no
offset, so a heading of 0 (East/+X) left the hull beam-on to its motion ("anda de
lado"). Apply a -90° yaw offset so the bow aligns with the travel direction, to
both the model and the onboard cameras (their offsets are in the mesh's -Z-forward
frame). Verified in-browser: surging East now moves bow-first.

Also start the free-fly camera from a closer 3/4 aerial that frames the vessel.

Note: the bay terrain/islands/bridge are placed on consistent axes but the vessel
at sim origin sits in open water far from the coast — that's the geographic
mesh-origin↔sim-origin alignment, still pending calibration.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 12:06:12 -03:00
vinihcrosa 3c41918d9c Merge branch 'claude/magical-swartz-6c3650' — wave forces (roll/pitch/heave)
# Conflicts:
#	core/libs/world/include/ymir/world/wave/WaveForces.h
#	core/libs/world/src/wave/WaveForces.cpp
2026-06-30 02:07:30 -03:00
vinihcrosaandClaude Opus 4.8 8345156a58 feat(physics): wire wave forces into the WASM sim — vessels now roll/pitch/heave
Waves loaded into the timeline but no force model consumed them in the WASM
build, so vessels only translated (current → surge/sway/yaw). Wire the 6-DOF wave
excitation:

- core: WaveForces::setSpectrum() to swap the sea state without rebuilding (the
  domain has no model-replace), keeping the RAO tables.
- core/src/wasm/WaveRaoData.h: embed the default vessel's excitation-force RAO
  (vessel1.json waves, 6 DOF × 60 freq × 25 dir) so the WASM build has wave data
  without file I/O.
- YmirBindings: add a WaveForces (RAO + calm spectrum) per vessel and a
  setWaveConditions(Hs,Tp,dir,gamma,spectrum) that drives every vessel's spectrum.
- worker: parse the scenario wave keyframe and call setWaveConditions on env load.
- VesselPanel telemetry: show live φ (roll) / θ (pitch) / heave.

Verified in-browser: beam seas (Hs 6, Tp 14) → roll oscillates ±~1.3°, pitch ±~0.9°,
heave around equilibrium; head seas → near-zero roll (correct). Small angles because
the default hull is a 450,000 t VLCC. Core 257 tests + web 246 green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 02:05:05 -03:00
vinihcrosaandClaude Opus 4.8 fdadc5420a feat(web): render vessel roll/pitch/heave in 3D (not just yaw)
The 3D vessel only applied yaw, so even when the engine produced roll/pitch/heave
the ship rendered as a rigid block. Apply the full attitude: yaw about scene up,
then pitch about the beam axis and roll about the longitudinal axis in the
heading-aligned frame, plus heave bobbing on the waterline. Signs/axes documented
as calibratable. (Necessary half of the wave-motion fix; the wasm wave force is
wired separately.)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 01:51:37 -03:00
vinihcrosa 8800261a9c Merge branch 'claude/magical-swartz-6c3650' — 3D vessel waterline fix 2026-06-30 01:39:38 -03:00
vinihcrosaandClaude Opus 4.8 d2911b24a4 fix(web): seat 3D vessel at its waterline (mesh origin is the keel)
The celso_furtado mesh origin is at the keel (world Y 0→46 = keel→masthead), so
placing it at the sea plane (Y=0) floated the whole ship above the water — and
near the shallow origin you saw the seabed around/under it. Sink the model by its
design waterline (~4.45 m, from vessel_config waterInteraction points) so the
waterline sits on the sea: hull partly submerged, superstructure above. Verified
in browser (Livre cam): vessel floats correctly.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 01:39:37 -03:00
vinihcrosa c1f02e56eb Merge branch 'claude/magical-swartz-6c3650' — 3D vessel on sea + axis fix 2026-06-30 01:30:36 -03:00
vinihcrosaandClaude Opus 4.8 368d251cdf fix(web): seat 3D vessel on the sea — drop bogus area recentre, real sea plane
The vessel appeared to float because AreaScene recentred the area group on the
combined bounding box, and ilhas.glb contains a stray vertex ~millions of units
out, so the box centre was garbage and shoved the terrain/water away from the
vessel. Verified against the uncompressed source: the meshes are Z-up with the
water surface at Z=0, modelled about the area's reference origin (= sim 0,0), so
the -90° X rotation already lands the waterline at Y=0 — no recentre needed.

- Remove the bbox recentre; trust the native origin (matches the sim frame).
- Enlarge the sea plane (200km) with a proper sea colour at Y=0 so the vessel
  reads as floating on water and the bay sits in it.

Verified in browser (Livre cam): vessel on the sea, not in the air.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 01:30:35 -03:00
vinihcrosa 2b87e21479 Merge branch 'claude/magical-swartz-6c3650' — free-fly 3D camera 2026-06-30 01:19:41 -03:00
vinihcrosaandClaude Opus 4.8 3a9bc13464 feat(web): free-fly camera in the 3D view (Livre)
Add a "Livre" option to the onboard-camera selector. When active, the onboard
CameraRig is bypassed and FlyControls drives the camera: WASD to move, R/F up-down,
drag the mouse to look — go anywhere. On entry it positions above/behind the
selected vessel for an external overview, then hands control to the user.

viewStore CameraId gains 'Free'; CAMERAS map narrowed to the onboard ids with a
guard in CameraRig. CameraSelector + tests updated. 246 web tests green, tsc clean.
Verified in browser: external overview on select, keyboard fly confirmed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 01:19:41 -03:00
vinihcrosa 81732f40ff Merge branch 'claude/magical-swartz-6c3650' — onboard camera selector 2026-06-30 01:04:51 -03:00
vinihcrosaandClaude Opus 4.8 c1d8d10b09 feat(web): onboard camera selector for the 3D view
Add a Proa/Ponte/Popa/Boreste/Bombordo selector (the five celso_furtado onboard
cameras from vessel_config) shown over the 3D scene. Each camera uses its authored
local offset + yaw relative to the selected vessel; switching moves the view.
viewStore tracks the active camera; CameraSelector + tests added. 245 web tests
green, tsc clean. Verified in browser: switching cameras changes the viewpoint.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 01:04:50 -03:00
vinihcrosa 937025fc5f Merge branch 'claude/magical-swartz-6c3650' — 3D scenario view (v1) 2026-06-30 00:57:25 -03:00
vinihcrosaandClaude Opus 4.8 e3ea1fe8fa feat(web): 3D scenario view with Mapa/3D toggle (v1)
New 3D background that swaps with the Leaflet map via a top-left Mapa/3D toggle —
only the background changes; all panels (top bar, sidebar, vessel panel, Play/Pause,
info) stay shared and live.

- Area3DView (react-three-fiber): loads the compressed Baía subset (batimetria +
  ilhas + ponte, Z-up meshes rotated to Y-up and recentred on the sim origin) plus
  a water plane and lighting; every vessel uses the celso_furtado mesh placed at
  its live/draft pose.
- Camera uses the vessel's onboard "Front" camera (from vessel_config), following
  the selected vessel; switching the selected vessel moves the view.
- geo3d util maps sim (x=East, y=North, ψ) → scene (X, -Z, yaw); viewStore holds
  the mode; ViewToggle switches it. MapActions (Leaflet zoom) hidden in 3D.

Tests: geo3d + ViewToggle + viewStore; Area3DView excluded from coverage (WebGL,
e2e-only) like the Leaflet views. 243 web tests green, tsc clean. Verified in
browser: toggle both ways, onboard bridge camera, panels unchanged.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 00:57:25 -03:00
vinihcrosaandClaude Opus 4.8 45506efede build(web): 3D asset pipeline + three/r3f deps (foundation for 3D view)
Add three, @react-three/fiber, @react-three/drei (runtime) and gltf-transform /
gltfpack (dev) plus apps/web/scripts/build-3d-assets.sh, which compresses the
local Baía de Guanabara + celso_furtado source packs into web-ready GLBs
(meshopt geometry + WebP@1024 textures — no KTX2 transcoder needed under our
COOP/COEP). v1 subset = batimetria + ilhas + ponte + vessel (~21MB, down from
~640MB); topografia excluded (pathological per-tile textures). Generated GLBs are
gitignored — run the script once locally before using the 3D view.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 00:46:58 -03:00
vinihcrosa a20fbbb3c1 Merge branch 'claude/magical-swartz-6c3650' — clickable vessels during simulation 2026-06-30 00:24:22 -03:00
vinihcrosaandClaude Opus 4.8 8191490b37 fix(web): keep vessel markers clickable during a running simulation
The vessel DivIcon was rebuilt on every render. While a simulation runs the map
re-renders ~20 Hz, so Leaflet replaced each marker's DOM element every ~50 ms —
a click's mousedown and mouseup landed on different elements and the click was
swallowed, so the vessel panel only opened when stopped. Memoize the icon on the
rounded heading + selection so the element is stable; clicks (and switching
between vessels) now work at any point during the run. Verified in-browser: panel
opens mid-run with live telemetry.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 00:24:21 -03:00
vinihcrosa 6c30ac6bf9 Merge branch 'claude/magical-swartz-6c3650' — map-first UI polish 2026-06-30 00:13:19 -03:00
vinihcrosaandClaude Opus 4.8 1e668dc19e feat(web): map-first UI polish — top-bar add, editable name, cleaner rows, collapsible panel
Context-driven UI pass (not a blind Figma copy):
- Top-bar "+" now opens a VesselPicker popover to add vessels (it was a dead
  button; adding only worked from the sidebar). Removed the stale center
  actionsDisabled gating that greyed "+" out before the first vessel existed.
- Scenario name is edited inline in the breadcrumb; dropped the duplicate name
  field from the sidebar (still editable in the info panel).
- VesselList rows are a readable two-line card (name + remove, then x/y + heading)
  instead of a cramped single line; adding moved to the top bar.
- Sidebar is collapsible so the map can go full-width (map-first focus).

New VesselPicker (+ tests); ScenarioForm/VesselList/Sidebar/AppShell updated.
236 web tests green, web+api tsc clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 00:13:10 -03:00
vinihcrosa f1fc29760a Merge branch 'claude/magical-swartz-6c3650' — Play/Pause simulation control 2026-06-30 00:05:49 -03:00
vinihcrosaandClaude Opus 4.8 9663458d46 feat(web): Play/Pause simulation control that resumes from the frozen state
Drop the "Build simulation" step — the floating control is now just Play/Pause.
Pause freezes the simulation in place (worker keeps the body + elapsed time) and
Play resumes from there instead of restarting, so the instructor can pause, add an
environmental condition or another vessel, and continue. On resume the current
environment is (re)applied; if the vessel set changed, the engine is reseeded with
existing vessels at their live positions so the scene doesn't jump back to t=0.

- simulationStore: start/stop → play/pause; status 'ready' → 'paused'; add
  applyEnvironment(); 'ready' worker msg no longer flips to a transient status.
- SimulationControl primitive reduced to Play/Pause; status strip shows Pausado.
- Remove dead legacy TelemetryPanel. Tests updated; web 237 green, web+api tsc clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 00:05:00 -03:00
vinihcrosa 41ffea26e1 Merge branch 'claude/magical-swartz-6c3650' — fix api/docker build 2026-06-29 23:25:32 -03:00
vinihcrosaandClaude Opus 4.8 6049f3af31 fix(api): type scenario initialConditions backfill explicitly (fixes docker build)
The legacy-row backfill spread `Record<string,number>` which tsc could not narrow
to InitialCondition, breaking `pnpm --filter @ymir/api build` (and the docker api
image). Construct each InitialCondition explicitly with defaults. Verified
`apps/api` tsc + full `docker compose build` + up (api/web healthy).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-29 23:25:18 -03:00
vinihcrosa 505e4b5a3c Merge branch 'claude/magical-swartz-6c3650' — scenario list/info, env tabs, DTO ids 2026-06-29 23:07:56 -03:00
vinihcrosaandClaude Opus 4.8 0c275dfb90 feat(web): scenario info panel toggled from the top bar
Add ScenarioInfoPanel (Figma "Informações do cenário"): a right-docked Panel with
editable scenario name, area metadata (gravity, water density) and vessel count,
toggled by the top-bar info (ⓘ) button which reflects open state. New panel + tests;
240 web tests green. (Vessel AIS/RIPEAM tab deferred — no source data/spec yet.)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-29 22:24:25 -03:00
vinihcrosaandClaude Opus 4.8 f108e81957 feat(web): tabbed environment panel (Correnteza-vento-ondas / Visibilidade)
Wrap the environment editors in the Tabs primitive per the Figma "Condições
ambientais" layout: the current/wind/wave keyframe editors live under the main
tab, and a Visibilidade tab adds Chuva/Neblina sliders (UI-only, not yet wired to
the physics engine — noted in the panel). Test added; 236 web tests green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-29 22:21:39 -03:00