# CLAUDE.md

@CLAUDE.determinism.md
@CLAUDE.framework.md
@CLAUDE.pending.md
@CLAUDE.session.md
@CLAUDE.styleguide.md

## Working Principles

These three principles take priority when writing or editing code.

### 1. Think Before Coding
"Don't assume. Don't hide confusion. Surface tradeoffs." Before implementing, state assumptions explicitly and ask if uncertain. When multiple interpretations exist, present them rather than choosing silently. Mention simpler approaches and push back when appropriate. If anything is unclear, stop and name what's confusing.

### 2. Simplicity First
"Minimum code that solves the problem. Nothing speculative." Avoid features beyond what was requested, abstractions for single-use code, unrequested flexibility, or error handling for impossible scenarios. If 200 lines could be 50, rewrite it. Ask yourself whether a senior engineer would call this overcomplicated.

### 3. Surgical Changes
"Touch only what you must. Clean up only your own mess." When editing existing code, don't improve adjacent code or formatting unnecessarily. Match existing style. Remove only imports and variables that YOUR changes made unused—don't delete pre-existing dead code unless asked. Every changed line should trace directly to the user's request.

---

## Document Rules

- All content in this file must be written in **English**.
- The user may give instructions in Korean. When adding or updating content, rephrase into clear, concise English — drop filler words, resolve ambiguity, preserve intent.
- Whenever this file is modified, notify the user that CLAUDE.md has been updated.

---

## Workflow

- **Build verification** is the user's responsibility — never invoke build commands or scripts (`msbuild`, `cmake --build`, IDE builders, etc.). Report completion and wait for the user to run the build.
- **Build file sync** — mirror source file adds/removes in `.vcxproj`, `.vcxproj.filters`, and `CMakeLists.txt`. Windows builds from the vcxproj; WASM and Android both drive the same `CMakeLists.txt` through `android/app/build.gradle`, so there is no third source list to maintain.

---

## Conventions

**Encoding**
- Sources (`src/**.h`, `src/**.cpp`) and text assets (GLSL, `.cratlas`) share one rule: `utf-8` **without a BOM**, **LF** line endings. The `.vcxproj` passes `/utf-8` to every configuration, so MSVC reads them correctly with no BOM, and the asset parsers reject one outright. Edit programmatically in binary — a Windows text-mode write silently turns every LF into CRLF.

**Coordinate System — 3D world / view (box3d)**
- **Handedness**: Right-handed, world up = **+Y** (our worldDef gravity is -Y; box3d's solver privileges no axis, though its default gravity `(0,-10,0)` implies the same +Y-up convention).
- **View**: GL convention — the camera looks down **-Z in view space** (crCamera: perspective × orbit lookAt, column-major float4x4, `VP = P * V`).
- **Positive rotation**: right-hand rule about the rotation axis (b3Quat standard). There is no screen-relative "CCW" in 3D — do not reason about rotation signs from the viewer's perspective.

**Coordinate System — 2D screen pipeline (text / post-processing / pixel ortho)**
- **Screen XY**: X-right, Y-up on the engine side; rendering flips Y (SDL Y-down) at the world→pixel boundary (`crCamera::PixelProjection` is bottom-left, Y-up).
- **Shader rotation note (2D pipeline only)**: The correct rotation matrix depends on **which coordinate space the shader rotates in**, not on CPU-vs-GPU. Determine this first when adding rotation to a 2D shader:
  - **Engine space (pre-projection, Y-up)** — use the **standard CCW** matrix, identical to CPU-side rotation.
  - **Y-flipped pixel space (SDL Y-down)** — the standard CCW matrix is visually inverted; apply the **transposed** matrix (or `cos(-r)/sin(-r)` equivalent).

**Explicit parenthesization**
- Parenthesize the RHS of compound assignments (`+=`, `-=`, `*=`, `/=`) and any sub-expression relying on operator precedence: `shakeMagnitude -= ( shakeDecay * delta );`

**Null pointers**
- Prefer `nullptr` over `NULL`.

**Output parameters**
- Return the value unless something blocks it (non-copyable type, caller-owned buffer). Handing back an allocated pointer for the caller to free is correct — state it at the declaration.
- An out-parameter takes a raw pointer, not a reference — `&x` at the call site shows the argument is written. Raw pointers are preferred over smart pointers, and over move semantics for the same job.

**Brace and body style**
- Always use Allman style — opening brace on its own line.
- Single-line bodies (no braces): use indented next-line form, not inline. `else`/`else if` follows on its own line after the body.
  ```cpp
  // correct
  if( rotDiff > B3_PI )
      rotDiff -= ( 2.0f * B3_PI );
  else if( rotDiff < -B3_PI )
      rotDiff += ( 2.0f * B3_PI );

  // wrong — cuddled / inline body
  if( rotDiff > B3_PI )       rotDiff -= ( 2.0f * B3_PI );
  else if( rotDiff < -B3_PI ) rotDiff += ( 2.0f * B3_PI );
  ```

**Enums**
- Always use `enum class`, never plain `enum`.
- Always specify the underlying type explicitly.
- Prefix the enum class name with `E`.
- Prefer `_SIZE` as the sentinel value for enum size (not required).
  ```cpp
  enum class EGameState : uint8_t { BOOT, TITLE, PLAYING, PAUSED, GAME_OVER };
  enum class EIndex     : uint8_t { FLAT_COLOR, SPRITE, _SIZE };
  ```

**C++ restrictions** — prefer simple, explicit code over syntactic sugar; avoid the following unless unavoidable:
- No operator overloading
- No lambdas
- No RTTI (`dynamic_cast`, `typeid`)
- No exceptions (`throw`, `try`/`catch`)
- No **writing** new templates — the ban is on generalizing project code with templates, not on using them. **Using existing templated libraries is fine**: STL (`std::vector`, `std::sort`, …), EnTT API calls (`registry.view<>`, `registry.emplace<>`, …), and the project's own `crArray<T>`.

Third-party libraries under `include/` are exempt from these restrictions — do **not** modify them directly.

**EnTT component rules**
- No pointer members in ECS component structs (cache misses during iteration) — reference external resources by ID/index instead.
- Never treat a zero-initialized `entt::entity` as empty — id 0 is a valid entity (crEcs dummy). Initialize handles to `entt::null` explicitly.

**Third-party file access**
- Never hand a third-party library an asset *path*. They open it with their own `fopen`, which cannot see inside an Android APK — only SDL routes relative paths to the asset manager. Read with `SDL_LoadFile` / `SDL_IOFromFile` and pass bytes or a stream instead (`ktxTexture2_CreateFromMemory`, `TTF_OpenFontIO`, `AddFontFromMemoryTTF`, `CURLOPT_CAINFO_BLOB`, `client_ssl_ca_mem`).
- State the ownership rule at each call site: whether the library copies the buffer (free it at once) or keeps referencing it (outlive the library), and which allocator it frees with.

**Assets**
- SFX are decoded once on load and the buffer **stays resident for the app lifetime** — never stream, never reallocate per play. Streaming was measured and rejected: a resident vorbis decoder per voice (32 of them) plus per-frame decode cost more than the memory it saves. If a clip ever runs to several seconds (ambience, voice), split the policy by clip length rather than making everything stream.
- The font atlas is `TTF_RenderGlyph_Blended` only. Shaded and LCD are structurally impossible on a transparent atlas — if a mode is ever needed, take it as a `LoadFont` argument instead of changing the pipeline.
- Scalable text: single-channel SDF (`TTF_SetFontSDF`) was tried and reverted (2026-07-04). Minification loss was unacceptable; even at 1:1 it was blurrier than coverage (no hinting snap, wide AA band); and glyph surfaces come out spread-padded tight instead of advance×fontHeight, forcing measured ink-offset realignment. `text_sdf.frag` is deleted — this note is the only record. If continuous scaling is ever needed, go MSDF: SDL_ttf/FreeType cannot generate it, so it means an msdfgen offline-prebaked RGB atlas + median-decode shader. Korean bottoms out at ~12px (measured — stroke density); localization will need this number again.

**box3d**
- box3d 0.1.0 shipped in July 2026, **after the assistant's training cutoff**. Read the headers at `C:\workspace\sdk\box3d-0.1.0\include` as the primary reference — never answer a box3d API question from training data.
- Always set `shapeDef.density` explicitly — the default is water (1000 kg/m³), which silently creates near-immovable bodies.

**Math**
- Use `crMath` for trigonometry and shared math — backed by Box3D's deterministic `b3ComputeCosSin`/`b3Atan2`. Never call libm/SDL trig (`sinf`, `SDL_sinf`, …) directly — platform variance breaks lockstep determinism. Exception: `sqrtf`/`roundf` are IEEE-754-exact; `crMath::Sqrt`/`Round` wrap them.
