Files
Ymir/AGENTS.md
T
vinihcrosaandClaude Sonnet 4.6 292aba6687 chore: initial project scaffold
Add README, AGENTS.md, CLAUDE.md, and .gitignore.

Establishes project identity (general-purpose physics engine, naval domain
first), Ports and Adapters architecture rules, Conventional Commits, and
Semantic Versioning as project standards. Legacy reference docs moved to
.docs/ and gitignored.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-21 22:31:35 -03:00

6.0 KiB

Ymir — Agent Guidelines

Ground rules for AI agents (Claude, Copilot, etc.) working on this codebase.


Project context

Ymir is a general-purpose physics engine written from scratch in C++17. It is designed to simulate the dynamics of physical bodies under arbitrary force systems, and to be extended to any physical domain.

Current scope: naval domain. The first implementation covers 6-DOF hydrodynamics for ships and floating platforms. All architecture decisions must remain domain-agnostic — the naval module is one application of the engine, not the engine itself.

There is a reference implementation in .docs/ (legacy naval project). Use it to understand domain behavior and physics intent — do not copy its code structure or repeat its known problems.


Architecture rules

Ports and Adapters

  • core/ must have zero knowledge of I/O, file formats, or rendering
  • All external dependencies (JSON, CSV, SUNDIALS, CGAL) live in adapters, not core
  • The C API in include/ is the only public surface — everything else is internal

C API boundary

  • No STL types cross the DLL boundary (std::vector, std::string, etc.)
  • No C++ exceptions cross the DLL boundary — functions return error codes
  • All structs at the boundary must be plain C (sizeof must be deterministic)
  • Calling convention: extern "C", no __cdecl attribute needed (use CallingConvention.Cdecl on .NET side)

Physics core

  • No heap allocation inside the integration loop (pre-allocate in constructors)
  • No global mutable state — every simulation is an isolated object
  • Force modules: pure functions of state — receive const VesselState&, return Forces
  • CVODE memory owned by BodyDynamics, cleaned up in destructor

Coding standards

  • C++17 — use std::optional, structured bindings, if constexpr where appropriate
  • SOLID — single responsibility per class, no god objects
  • No raw owning pointers — use std::unique_ptr / std::shared_ptr
  • No using namespace std in headers
  • Doxygen on every public symbol (/** */ style)
  • No magic numbers — named constants or constexpr

What to avoid (lessons from legacy)

Problem Rule
Global state in wave engine (g_waveData, g_components) All state owned by instance objects
rand() without seed — deterministic "random" phases Use std::mt19937 with explicit seed; expose seed as parameter
wave_engine_prune corrupting indices on 2nd call Rebuild index after any mutation of the component list
Inconsistent angle normalization across modules One AngleUtils namespace, used everywhere
Off-diagonal hydrostatic stiffness silently ignored Either implement fully or document explicitly in code
Small-angle approximation for roll/pitch undocumented Document assumptions with a // ASSUMPTION: comment
Mixing coordinate origins in restoring force calc Each formula must name its reference frame in a comment
CSV output coupled to physics loop OutputWriter is an adapter — physics loop calls an observer interface

Testing

  • Every force module has a unit test before it is integrated into RHS
  • Unit tests use analytical solutions or known limits (e.g., zero current → zero current force)
  • Integration tests run a full simulation and assert on conserved quantities or known maneuver behavior
  • Test files mirror source structure: tests/core/forces/TestCurrentForces.cpp

Documentation

  • Architecture decisions go in docs/adr/ as ADR files (Architecture Decision Records)
  • Public API docs generated by Doxygen from source
  • No inline // TODO without a linked issue or a // REASON: explanation

Versioning

Ymir follows Semantic Versioning (semver.org):

MAJOR.MINOR.PATCH
Bump When
MAJOR Breaking change to the public C API or physics model contract
MINOR New capability added in a backward-compatible way
PATCH Bug fix, doc update, internal refactor — no API change

Version is declared in CMakeLists.txt (project(Ymir VERSION x.y.z)) and exposed in the C API header.


Commit discipline

Ymir follows Conventional Commits (conventionalcommits.org):

<type>(<scope>): <short description>

[optional body]

[optional footer: BREAKING CHANGE: ...]

Types

Type Use
feat New feature (bumps MINOR)
fix Bug fix (bumps PATCH)
docs Documentation only
test Adding or fixing tests
refactor Code change that is neither fix nor feat
perf Performance improvement
build Build system, CMake, dependencies
ci CI/CD pipeline changes
chore Maintenance — no production code change

Scopes (examples)

Scope What it covers
core Engine-agnostic physics core
naval Naval-domain force modules
api Public C API (include/)
adapters I/O adapters (JSON, CSV)
tests Test infrastructure
build CMake and toolchain

Rules

  • One logical change per commit
  • Breaking C API or ABI changes require BREAKING CHANGE: footer
  • No commented-out code in commits
  • Tests must pass before committing to main

Examples

feat(naval): add current force model for cross-flow drag

fix(core): correct inverted sign in restoring force heave term

refactor(api): replace raw pointer with unique_ptr in BodyDynamics

BREAKING CHANGE: realtime_create() now returns a typed handle, not void*