47 Commits
Author SHA1 Message Date
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 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 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 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 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
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
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
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
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
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
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
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
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
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
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
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
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
vinihcrosaandClaude Opus 4.8 e2b50cde87 feat(web): scenario list screen + routing (Meus cenários)
Add react-router-dom with two routes: "/" → MyScenariosPage (scenario grid with
search, create, open, delete against the scenarios API) and "/editor" → AppShell.
"Sair do cenário" returns to the list. New: use-scenarios hook, ScenarioCard,
MyScenariosPage (+ tests, 235 web tests green).

API: backfill legacy initialConditions rows missing instanceId so GET /scenarios
serializes under the updated schema.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-29 22:19:20 -03:00
vinihcrosaandClaude Opus 4.8 0e28474b6b fix(types): carry both instanceId and vessel type id in scenario initial conditions
InitialCondition.vesselId held the per-scenario instance id, losing which vessel
TYPE each body was — so a saved scenario could not reload the right vessel config.
Add instanceId (engine body id) and keep vesselId as the type id; emit both from
the scenario store. (Resolves the flagged DTO ambiguity.)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-29 22:14:02 -03:00
vinihcrosaandClaude Opus 4.8 494d488648 test(web): stop AppShell test leaking unhandled fetch errors
The AppShell test stubbed fetch to resolve `{}`, but the mounted sidebar's
useAreas/useVessels hooks expect arrays and called `.map` on it asynchronously
after teardown — surfacing as vitest "unhandled errors" and a non-deterministic
exit 1 despite all assertions passing. Resolve list endpoints as `[]`.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-29 21:16:24 -03:00
vinihcrosaandClaude Opus 4.8 7a9280f1fc refactor(web): restyle scenario sidebar onto design tokens
ScenarioForm, VesselList and EnvironmentConditionPanel now read colours, spacing,
radii and fonts from the design tokens instead of hard-coded literals; VesselList
uses the Button primitive and mono numerics. Behaviour, stores and test selectors
(aria-labels, button names) unchanged — 229 tests green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-29 21:02:00 -03:00
vinihcrosaandClaude Opus 4.8 160dda8fb3 refactor(web): rebuild VesselPanel on design-system primitives
Replace VesselPanel's bespoke inline layout with the shared primitives:
Panel (eyebrow "Embarcação Ownship" + close), Tabs (Geral/Controles), Card
sections, Slider and Field. AzimuthDial kept as a custom control. Behaviour and
store wiring unchanged — actuator commands still post via the worker keyed by
instanceId, live u/v/r telemetry preserved.

Panel forwards an overriding data-testid so the existing e2e selector
("vessel-panel") keeps working. Tests updated for the tab role.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-29 20:59:31 -03:00
vinihcrosaandClaude Opus 4.8 c9e7fe1275 feat(web): instructor UI foundation — design system, app shell, real-physics surfacing
Phase 1 of the Figma "[INSTRUTOR]" port plus hardening found during testing.

Design system
- theme/tokens.ts + global.css: Figma palette, type scale, spacing, radii,
  shadows; injected as CSS vars at boot.
- ui/: 11 themed primitives (Button, IconButton, TopBar, Panel, Tabs, Card,
  Slider, Field, Modal/AlertDialog, SimulationControl, MapActions), each tested.

App shell (Layout do instrutor)
- features/shell/AppShell.tsx: TopBar (logo + breadcrumb, gated center actions,
  Sair/Salvar), full-bleed map, bottom-center Build/Play/Stop pill, bottom-right
  zoom, no-ownship AlertDialog. Replaces ScenarioCreatorPage.
- stores/mapStore.ts exposes the Leaflet instance so the zoom pill can drive it;
  AreaMapView registers it and drops Leaflet's default zoom control.

Physics truth
- Worker reports which engine loaded (real WASM vs JS mock); surfaced as a
  status badge/warning so the instructor never mistakes the mock for real
  dynamics. SimulationControls is now a status-only strip (actions moved to the
  pill).

Tests
- 229 passing, coverage gate green. New: tokens, all 11 primitives, AppShell,
  VesselPanel, vesselPanelStore. Fixed GeralTab to read draft by instanceId.

Docs
- docs/specs/features/instructor-ui/: extracted screen specs, reference maps,
  phased PLAN.md, and DYNAMICS-GAP.md (MATLAB↔Ymir physics parity).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-29 20:55:49 -03:00
vinihcrosaandClaude Sonnet 4.6 53ce4a91f6 feat: environmental conditions, vessel instanceId, panel stability, regression tests
Environmental Conditions feature:
- C++ EnvironmentTimeline: time-series current/wind data, JSON serialization
- WASM bindings: loadEnvironment(), World integration
- TypeScript types: CurrentSeries, WindSeries, EnvironmentPayload
- Zustand environmentStore with React panel (EnvironmentConditionPanel)
- Mock WASM current drift physics: nautical→body-frame velocity applied in step()

Bug fixes:
- Vessel instanceId: unique auto-increment ID per scenario instance prevents duplicate
  vesselId crash when adding two vessels of same type
- vesselPanelStore: separate vesselTypeId field so config fetch uses correct type ID
  (not instanceId) after the instanceId/vesselId split
- VesselMarker stable eventHandlers: useMemo + useRef pattern prevents react-leaflet
  from re-registering Leaflet listeners every 50ms tick, fixing panel clicks during
  active simulation

Tests:
- Playwright regression suite (e2e/regression.spec.ts): 8 tests covering panel open
  during simulation, multi-click consistency, two VLCC no error, dual marker render,
  individual panel open, remove-one behavior, current drift, no-drift baseline

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-29 11:29:16 -03:00
vinihcrosaandClaude Sonnet 4.6 4f9afc1132 fix(physics): correct Obokata yaw-rate sign and integrator limits
CurrentForces::computeObokata was computing water velocity at hull sections
as (-vc_x, -(vc_y - r*xi)) instead of the correct (-vc_x, -vc_y - r*xi).
The sign error on the yaw term caused sections at bow/stern to generate
anti-damping yaw moments (positive feedback), producing exponential state
divergence when thruster + rudder were at maximum.

Fix: water_y = -vc_y - r*xi, matching MATLAB's relativeVelSection convention
(water_vel = fluid_vel - vessel_vel_at_section = -vesselVel - r*xi).

Also raise RK45 limits in YmirBindings (reltol=1e-4, abstol=1e-6,
maxStep=0.005, maxSteps=100000) to handle the transient at startup.

fix(web): correct vessel heading arrow direction on map

VesselMarker was applying psi_deg directly as CSS rotation degrees.
CSS rotate(0deg) = arrow North, but psi=0 = East in the physics world frame.
Fix: cssRotation = (90 - headingDeg + 360) % 360.

feat(web): add VesselPanel with live actuator controls

Vessel panel opens on map click, shows vessel info and live state,
exposes thruster power/azimuth and rudder angle sliders during simulation.
Includes vesselPanelStore for actuator state management and e2e tests.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-28 08:22:48 -03:00
vinihcrosaandClaude Sonnet 4.6 99e6f40fb6 fix(wasm): recreate YmirSimulation on loadScenario instead of reset+addVessel
addVessel() after step() is undefined behaviour in the C++ engine — CVODE
integrator is already initialised for existing bodies. Calling reset() does
not clear the body registry, so addVessel() throws a C++ exception.

Fix: store SimulationClass reference from the Module, call .delete() on the
old instance (required by Emscripten Embind heap management), then construct
a fresh YmirSimulation and add vessels to the clean state.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-28 04:01:59 -03:00
vinihcrosaandClaude Sonnet 4.6 0cc5370b1a fix(web): change COEP from require-corp to credentialless
require-corp blocked OpenStreetMap tile loading with
ERR_BLOCKED_BY_RESPONSE.NotSameOriginAfterDefaultedToSameOriginByCoep.
credentialless still enables SharedArrayBuffer for WASM but allows
public cross-origin resources (OSM tiles) to load without credentials.

test(web): add simulation E2E spec covering full instructor flow

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-28 03:54:43 -03:00
vinihcrosaandClaude Sonnet 4.6 ff7cf13d17 chore(web): upgrade React 18 → 19 and react-leaflet 4 → 5
react-leaflet v5 requires React 19 — now fully compatible without
peer dependency warnings. No API changes needed.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-28 03:48:15 -03:00
vinihcrosaandClaude Sonnet 4.6 6ba2755448 fix(web): downgrade react-leaflet to v4 for React 18 compatibility
react-leaflet v5 requires React 19 — caused "render2 is not a function"
runtime crash and white screen. Downgraded to v4.2.1.

fix(docker): separate DB volume mount from API dist directory

Volume was mounted at /app/apps/api, overwriting compiled dist/ with stale
files from previous container runs. Changed mount to /app/data for DB only.

fix(api): copy area.json and vessel1.json into API Docker image

Seed files could not resolve JSON data at container startup.
Added COPY in Dockerfile and corrected path depth from 5 to 4 levels.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-28 03:44:16 -03:00
vinihcrosaandClaude Sonnet 4.6 788f7982f9 feat(web): implement scenario-creator feature
- ScenarioCreatorPage: full-screen layout (sidebar + Leaflet map)
- AreaMapView: Leaflet map with area polygon and draggable vessel markers
- VesselMarker: SVG icon rotated by heading
- ScenarioForm: scenario name input + area dropdown
- VesselList: add/remove vessels with heading editor
- SimulationControls: start/stop buttons + live simulation time display
- Sidebar: composition of all form components
- useScenarioStore: Zustand draft state (area, vessels, positions, headings)
- useAreas / useVessels: fetch hooks with loading/error/retry
- geo.ts: equirectangular lat/lng ↔ meters conversion + isInsidePolygon
- simulationStore: extend with loadScenario + scenarioVessels state
- simulation.worker: support loadScenario message with multi-vessel init
- 59 unit tests, coverage >80% on all metrics (stmts 94%, branches 84%)
- E2E Playwright smoke spec for scenario-creator flow
- gitignore: add apps/web/coverage/

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-28 03:33:11 -03:00
vinihcrosaandClaude Sonnet 4.6 8539f1a46a test(web): configure Playwright E2E + Vitest unit testing with 80% coverage rule
- Add Playwright (Chromium) for E2E tests with webServer auto-start
- Add Vitest + @testing-library/react + coverage-v8 with 80% threshold
- Add smoke E2E spec and jest-dom setup file
- Update turbo.json with test:e2e task
- Enforce 80% coverage rule in AGENTS.md (global testing policy)
- Add full Testes section to docs/conventions/frontend.md
- Add tasks.md for scenario-creator feature (14 tasks, T01–T14)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-28 03:22:21 -03:00
vinihcrosaandClaude Sonnet 4.6 a9061fbf2b feat(persistence): add simulation area model with geographic boundary
Introduces the Area entity: geographic origin (lat/lng = simulation 0,0),
GeoJSON polygon boundary, and environmental constants (gravity, water/air
density, magnetic correction). A simulation cannot run outside its area.

- areas table with origin, polygon (GeoJSON), and environment constants
- Auto-seed from area.json on startup (Baía de Guanabara)
- GET/POST/DELETE /areas endpoints (route → service → repository)
- scenarios.area_id FK — nullable for existing rows, idempotent ALTER migration
- AreaDTO + CreateAreaDTO in @ymir/types; ScenarioDTO gains optional areaId

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-28 02:52:23 -03:00
vinihcrosaandClaude Sonnet 4.6 5d937ca07b feat(persistence): store full vessel physics config from vessel1.json
Adds 6 supplementary SQLite tables (physical_props, current, connection_points,
rudders, thrusters, waves) to hold all hydrodynamic data for the VLCC
VLCC_L350_B63_T230 (L=350m, B=63m, T=23m).

- Auto-seed at startup: reads vessel1.json into all tables when DB is empty
- GET /vessels/:id/config endpoint returns full physics config for simulation
- Refactored vessels route to use service + repository layer pattern
- Expanded @ymir/types with VesselConfigDTO and all sub-component schemas
- Simulation worker fetches vessel config on init, passes to WASM loadConfig()

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-28 02:06:56 -03:00
vinihcrosaandClaude Sonnet 4.6 3630507efa chore: remove build artifacts from tracking, update .gitignore
- Add dist/, *.profraw, .wasm-stage/ to .gitignore
- Remove apps/api/dist/, apps/web/dist/, packages/types/dist/ from git
  (generated on build, should not be versioned)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-28 00:41:24 -03:00
vinihcrosaandClaude Sonnet 4.6 260db7369e feat(web): add JS mock simulation as WASM fallback
When ymir.js is not built (Emscripten not installed), the worker falls back
to a pure-JS MockYmirSimulation that mirrors the Embind API. Vessel moves
with constant u=0.5 m/s and r=0.01 rad/s so the telemetry panel shows
live updating values without requiring the C++ build.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-28 00:31:46 -03:00
vinihcrosaandClaude Sonnet 4.6 eedaeef3ce chore: add Docker Compose setup for core, api, and web services
- Remove SUNDIALS from C++ Dockerfile (replaced by RK45 in T03)
- Fix CMake source path to core/ after T05 reorganization
- Add docker/api/Dockerfile: multi-stage Node 20 build for Fastify API
- Add docker/web/Dockerfile: Vite dev server with WASM volume mount
- Rewrite docker-compose.yml: core (C++ ci build), api, web services
- Add server.host=0.0.0.0 to vite.config.ts for Docker networking

Run everything: docker compose up --build

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-28 00:18:11 -03:00
vinihcrosaandClaude Sonnet 4.6 6295d540a4 feat: migrate to monorepo with WASM core, Fastify API, and React frontend
- Move C++ sources to core/ subdirectory (T05)
- Add pnpm workspaces + Turborepo pipeline (T06)
- Add Emscripten CMake target and build-wasm.sh (T07)
- Implement Embind bindings via YmirBindings.cpp (T08)
- Add packages/types with TypeBox schemas for VesselDTO, ScenarioDTO, SimulationStateDTO (T09)
- Create apps/api: Fastify v4 + Drizzle ORM + SQLite with vessels/scenarios CRUD (T10-T13)
- Create apps/web: Vite + React 18 + COOP/COEP headers (T14)
- Add simulation.worker.ts for WASM loop in Web Worker (T15)
- Add Zustand simulationStore (T16)
- Add TelemetryPanel component (T17)
- Verified: pnpm build 3/3 tasks, 21ms cached; ctest 211/211 pass (T18-T19)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-28 00:08:28 -03:00
vinihcrosaandClaude Sonnet 4.6 b11c4be0c5 refactor: reorganize repo into libs/ domain structure
Move files from flat core/naval/adapters layout into libs/{common,physics,simulation,vessel,world,persistence} with updated namespaces, CMakeLists, tests, README, AGENTS.md, and Doxyfile.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-25 03:34:51 -03:00