# CLAUDE.md — "Masterpiece .. Or not !"

Isometric 3D game. The player is an art buyer loose in a museum. The whole
museum is laid out at the start of the run — **one room per piece** — and the
player **wanders it freely**, in any order, back and forth.

Each room holds one art piece. Standing before it, the player enters
**observation** and delivers a verdict: **"Great, I'll buy it!"** if the piece is
100% faithful to the real-world original, or **"This is fake!"** if there is
*any* difference. **Judging every room correctly wins the run.** A single wrong
verdict ends it.

Judging every room — not merely buying ten — is what makes the fake verdict
worth pressing. With free movement and a "buy 10" win condition, a player would
simply buy what they were sure of and ignore everything else: declaring a fake
would carry risk and pay nothing, and the button would be strictly dominated.
The museum itself is the exam.

The game relies on **cultural knowledge only** — there is no in-game reference
of the original. This is a deliberate, hardcore design: a run is a chain of ~20
correct calls, victory is rare, and the value of a win comes from that rarity.
The lose screen is therefore the game's only teaching surface and must be
excellent.

## Tech stack & targets

- **Godot 4.4.1** (standard build, not .NET — GDScript only), installed at
  `C:\Users\gille_ycz9q4f\tools\godot\Godot_v4.4.1-stable_win64.exe`. Export
  templates are not installed yet; they are only needed at M5.
- Primary distribution target: **Steam** (Windows/macOS/Linux native exports,
  Steamworks via the **GodotSteam** GDExtension — integrated late, keep all
  Steam calls behind a single `Steam` wrapper autoload so web builds compile
  without it).
- Secondary target: **Web (HTML5 export)** for demo/itch.io. Constraints to
  respect from day one:
  - Use the **Compatibility renderer** (GLES3), not Forward+ — required for
    stable web export and fine for a low-poly isometric game.
  - Web export needs cross-origin isolation headers
    (`Cross-Origin-Opener-Policy: same-origin`,
    `Cross-Origin-Embedder-Policy: require-corp`) on the hosting server.
  - Avoid threads-dependent features; keep total asset weight lean.
- Blender → glTF for all 3D assets.

## Art direction

- The museum, characters and props are **cartoon, colourful and funny** —
  low-poly, saturated, readable at isometric distance. The player is a small man
  with a mustache and a fedora; the tone is light.
- The artworks themselves are **faithful photographic reproductions**, unfiltered.
  The contrast between a goofy plasticine museum and a real Vermeer on the wall
  is intentional and is part of the joke.
- No post-processing filter over the artworks: any stylization would hide the
  subtle details the whole game depends on.

## Quality settings

Options exposes a **Quality** setting with four levels: **High / Medium / Low /
Very Low**, persisted in `user://settings.cfg` alongside the volumes. The
binding target is real: **Very Low must hold a stable framerate on a netbook
with no discrete GPU.** The primary dev machine is itself low-end, so this is
testable continuously rather than at the end.

The single hard rule: **quality levels never touch artwork fidelity.** The
artwork texture *is* the gameplay — degrading it makes subtle differences
invisible and the game unfair, given that every mistake is fatal. Everything
else in the frame is negotiable; the painting is not. In particular, at Very Low
**every world texture drops to its low variant and the artwork alone stays
full resolution.**

Levers, from High down to Very Low: shadows (soft → hard → none), MSAA/AA
(off entirely below High), **`scaling_3d_scale` render resolution** (the
strongest single lever on weak hardware — Very Low renders well below native and
upscales), ambient light and reflections, prop density from the dressing set,
and prop density.

**Render scale must snap back to native during inspection.** Since inspection
happens in the 3D world rather than as a 2D overlay, a reduced render scale
would blur the artwork itself — degrading the exact thing the hard rule protects.
So the scale lever applies while exploring and is lifted while inspecting. This
is affordable precisely where it is needed: inspection is the lightest moment in
the game — static camera, no movement, one room — so the netbook can pay for
full resolution at the only moment it truly matters.

World textures ship as **two variants of the shared atlas** (full and low),
swapped at runtime when quality changes. The atlas is shared, so this is one
extra asset, not one per prop. The artwork texture is never part of the atlas
and is never swapped.

Honest note: texture size is the lever the player asked for, but on a GPU-less
netbook the bigger wins are `scaling_3d_scale` and killing shadows. Texture
downscaling mostly buys memory bandwidth and VRAM headroom. All levers are worth
having; do not expect the atlas swap alone to carry Very Low.

### Artwork texture fidelity is a technical constraint, not just a policy

The game is decided at the level of small visual details, which puts an unusual
constraint on how artwork textures are imported: **lossy VRAM block compression
(BPTC/S3TC) must not be used on artworks.** Block compression is lossy at
exactly the scale the game plays at — it can smear a subtle difference into
invisibility, or worse, compress the original and the fake differently and
manufacture a difference that the artist never authored. Artworks import
lossless, which costs VRAM and pulls against the netbook target. Only the
current room's piece is ever loaded, which is what makes that affordable.

## Conventions

- Minimal, readable GDScript. No unnecessary classes or abstraction layers;
  a scene + its script is the unit of design.
- Clarity through naming, not comments. No redundant comments.
- Autoload singletons for global systems (see below), scene-local scripts for
  everything else. `settings.gd` is deliberately split out of `audio_manager.gd`
  because quality is not audio: settings owns the file and applies both volumes
  and rendering, audio_manager only plays sound.
- **The game is English-only.** All user-facing text ships in English; there is
  no French locale in v1.
- Code and identifiers in English. All user-facing strings — **including artwork
  titles and fake hints** — still go through Godot's translation system (`.csv` /
  `tr()`), with English as the only locale. The plumbing is free now and keeps
  localization possible later; retrofitting hardcoded strings is not. JSON
  stores translation keys, never literal prose.
- Signals over polling; no node-path spaghetti (`@export` NodePaths or
  `%UniqueNames`).

## Project structure

```
project/
  autoload/
    game_state.gd      # run state: run seed, rng, difficulty level + ramp
    config.gd          # resolves the API base URL / token (setting → env → default)
    api.gd             # the art server: ping, difficulties, run, canvas images
    settings.gd        # owns user://settings.cfg: volumes + quality, applies both
    convos.gd          # what the people say: fetched per language, file as fallback
    audio_manager.gd   # music/SFX buses, plays music/SFX
    steam.gd           # no-op stub on web, GodotSteam calls on desktop
  scenes/
    main.tscn          # root: swaps between menu / game / end screens
    ui/
      menu.tscn        # logo, Start, Options
      options.tscn     # music + SFX sliders, quality
      hud.tscn         # progress, inspect prompt, clipboard
      clipboard.gd     # bottom-left tally: fakes / originals named
      observation.tscn # the two verdicts + their B / F shortcuts
      end_screen.tscn  # win / lose (see End screen spec)
      dialogue.tscn    # talking to the guide (assets/dialogue/guide.json)
    game/
      run.tscn         # builds one room at a time, owns the verdict
      guide.gd         # the museum's own man: desk, uniform, rounds, dialogue
      museum.gd        # lays out the whole museum from the seed
      room.tscn        # one museum room (see Room spec)
      player.tscn      # CharacterBody3D — see Characters
      artwork.tscn     # parametric frame + canvas fetched from the API
    camera/
      iso_camera.tscn  # orthographic isometric rig following the player
  data/                # retired local catalog — git-ignored, out of the build
  assets/              # glb models, audio, fonts
  i18n/
  tests/               # headless suites, each prints "<NAME> OK" or fails:
                       #   smoke_test    MENU→RUN→END→MENU, input map, i18n,
                       #                 audio buses (Api stubbed under headless)
                       #   museum_test   layout, door budget, reachability
                       #   a11y_test     keys/rebinding, focus, contrast, glyphs
                       #                 (see ACCESSIBILITY.md)
                       #   guide_test    the guide: his post, his uniform, the
                       #                 conversation, coming over, and staying
                       #                 inside the walls
                       # `godot --headless res://tests/<name>.tscn`
```

## State flow

```
MENU ⇄ OPTIONS
MENU → RUN → WIN | LOSE → MENU
```

`main.tscn` instantiates/frees `menu`, `run`, `end_screen`. `game_state`
resets on each new run.

## Camera & rendering

- **Orthographic Camera3D**, classic isometric angle (~45° yaw, ~30–35° pitch).
  It frames the **room**, not the player: he walks about inside a still frame,
  and crossing a doorway slides the frame onto the room he has entered. Only the
  first room of a run is cut to. The same camera tweens to the head-on
  inspection framing and back — one rig, two poses, always orthographic (see
  Inspection).
- Rooms have **no roof** — the camera looks straight into them.
- The room being played is instantiated **together with every room its doorways
  open onto**, so the museum reads as a museum rather than as one lit box. The
  neighbours are scenery: they carry no triggers, and only the room he stands in
  listens to him. Judged state lives in the record, not the scene.
- Nothing is cached between moves — every room is rebuilt on entry, because
  which wall a room drops depends on where the player is standing. Two rooms
  meeting on the grid both wall the line between them, so the **neighbour drops
  its wall on the shared boundary**: otherwise the pair z-fight, and the far
  wall of the nearer room is three units tall and would hide half of the room
  being played. A neighbour whose piece hangs on that dropped wall simply keeps
  its painting until the player walks in — a canvas hanging in mid-air reads as
  a bug, not as a neighbour.
- The **near walls go see-through** when the eye has to cross them into the next
  room. They keep their collision: without it he would stroll between rooms
  without ever crossing a doorway, and the room he is "in" would never change.
- This is the one place the netbook budget is knowingly spent: up to four rooms
  and four full-resolution canvases instead of one.

## Core loop

1. The run seed lays out the whole museum: one room per piece in the run,
   placed on a grid and wired into a freely walkable graph (see Museum).
2. The player walks in and out of rooms at will. Nothing is gated, nothing is
   one-way, and a room can be left undecided and returned to later.
3. Approaching the piece raises a prompt; `Enter` or a click opens
   **observation** (see below).
4. In observation the player commits, and nothing he calls ever ends the run:
   - **"Original"** (`O`) or **"Fake"** (`F`) saves that call on the piece and
     stamps it on the cartel — a red FAKE struck across it, or a green tick in
     the corner. Looking again changes the call.
   - `Esc` steps back out and decides nothing, and so does walking away — left,
     right or back. Not forward: he is already against the canvas.
5. The run begins in a **vestibule**: no piece, a plant in a corner, and a sign
   pointing at the one door into the exhibition. Somewhere deep in the galleries
   is the **exit**, marked EXIT and roped off. Every gallery carries an Exit sign
   pointing down the doorway that leads toward it.
6. **Win by naming every fake and walking out.** The velvet rope lifts the moment
   the last fake is marked fake — with no original wrongly accused. Reach the exit
   before then and a line says so; the rope stays.

   The player never buys anything. He is asked to call the piece, not to want it,
   and he is never punished for a wrong guess — only for leaving with one.
5. A judged room is settled: its piece can no longer be re-judged.
6. **Win when every room has been judged correctly.**

There are no velvet ropes and no clerks. They existed to gate a one-way
corridor; free movement removed the thing they were gating.

## Room spec (`room.tscn`)

Rooms are **built procedurally at spawn**, not handcrafted, and the reason is a
hard geometric constraint rather than a preference.

The camera never moves, so of a room's four walls only the two **far** ones
(`-X`, `-Z`) turn their inner face toward it. A painting on either near wall
would show the player its **back**. Meanwhile the two **near** walls must stay
low, because at ~35° pitch full-height near walls would hide the room's
interior.

So the one rule that survives every layout is: **the artwork always hangs on a
far wall**, beside a doorway if that wall has one. The museum's grid then does
the rest — leaving by the top-left door lands you at the next room's bottom-right
one, because the rooms are neighbours on the grid, not because anything rotates.

- Up to **3 doorways** (`Museum.MAX_DOORS`), each opening onto another room.
- Every doorway opens onto a **landing** — floor continuing past the threshold.
  Without it the player walks out and drops into the void.
- A far wall **with a door in it still carries the artwork**: the piece simply
  hangs beside the opening. A wall is 10 units and a doorway 2.2, so there is
  room to spare. This is what frees the museum's door layout from the camera
  constraint — without it, a 3-door room could be left with no visible wall.
- One artwork anchor: wall mount (paintings) or pedestal (statues).
- Dressing props (bench, plant, spotlight) from a small shared set.
- Doorways are `Area3D` triggers. Crossing one swaps the room — deferred out of
  the physics step, since Godot forbids tearing down collision shapes inside the
  callback that reports the crossing.

## Art from the API (`config.gd` + `api.gd`)

The art now comes from the **Masterpiece-or-Not REST API**
(`D:\dev\masterpieceornot_dashboard\api.md`), not the old `data/` folder. The game
sends a difficulty (and a per-player `uuid`) and the server picks the pieces; the
game only hangs what comes back. `config.gd` resolves the base URL (project
setting → `API_BASE` env → `https://masterpieceornot-api.allweb.fun`) and an optional token, and mints a
stable **player uuid** on first launch (a UUID v4 kept in `user://player.uuid`,
sent on every `/api/run` so the backend can tie a player's runs together); `api.gd` fetches `/api/ping` (the boot
gate), `/api/difficulties` (the ramp order), `/api/run?difficulty=<key>` (one run
of items), and each canvas image (cached by URL). On launch the game pings and
stays behind a "can't reach the server / Retry" gate until it answers. Under
`--headless` `api.gd` serves canned data, so the tests never touch the network. A
self-signed dev cert needs `API_INSECURE_TLS=1` (never in production).

Each API **item** is one gallery: `authentic` is the answer, the item's own image
is the canvas (an original shows the real thing, a fake the altered variant), and
`title`/`author`/`year` feed the cartel and clipboard. `run.gd:_run_from_items`
maps items onto the `{piece, fake, authentic}` records `Museum.generate` consumes,
tagging each piece with its `item_index` — its place in the served `items` array.

**Reporting back.** The run keeps the server's `run_id`, and as the player plays:
the call he settles on per piece (`said_fake`, last word wins if he changes it)
and how long he spent in front of it (accrued across every look). Walking out
posts it all to `POST /api/run/result` — indexed by `item_index`, with the run's
total duration. Correctness is recomputed server-side from the run it served, so
the game never sends a score and cannot skew the stats. Fire-and-forget: the
request lives on the `Api` autoload and outlives the run node.

The retired local model was one folder per piece (kept on disk, git-ignored):

```
data/
  mona-lisa/
    piece.json
    original.jpg
    fake-eyes.jpg
    fake-hands.jpg
```

```json
{
  "id": "mona-lisa",
  "title_key": "ART_MONA_LISA_TITLE",
  "artist": "Leonardo da Vinci",
  "type": "painting",
  "aspect": 0.667,
  "authentic_texture": "original.jpg",
  "fakes": [
    {
      "texture": "fake-eyes.jpg",
      "hint_key": "ART_MONA_LISA_FAKE_EYES",
      "diff_rect": [0.38, 0.22, 0.24, 0.10]
    },
    {
      "texture": "fake-hands.jpg",
      "hint_key": "ART_MONA_LISA_FAKE_HANDS",
      "diff_rect": [0.30, 0.68, 0.40, 0.18]
    }
  ]
}
```

- `aspect` is width/height; it drives the **parametric frame** (`artwork.tscn`
  scales a single shared frame+canvas mesh from it), so any format from portrait
  to panorama is supported by one asset.
- `fakes` entries are just **different versions** of the piece. There is
  deliberately **no difficulty field**: difficulty is not guessed up front, it
  will be measured later from real player error rates per piece `id` and per
  variant (see Telemetry). Nothing in v1 ranks or orders by difficulty.
- `diff_rect` is `[x, y, w, h]` in normalized texture space, used **only** by the
  lose screen to highlight what changed. Never shown during play.
- `hint_key` is the translation key of the one-line explanation of the fake,
  shown **only** on the lose screen. There are no hints during play.
- A piece is **one real artwork + 1..n fake variants**. `fakes` is a list; the
  "2 variants per piece" figure in the definition of done is a content target,
  not a structural constraint. Nothing in the ArtDB assumes a variant count.
- **Paintings** = the cheap, scalable path: the artwork is just a texture and
  fakes are produced in 2D image editing, which makes subtle differences trivial
  to author. **v1 = paintings only.**
- **Statues** (v2): fakes via material swaps, mirrored geometry, added/removed
  props, scaled parts.
- **Public-domain artworks only** (old masters, classical sculpture). With
  knowledge-only play the catalog is hard-constrained to **globally famous**
  works — there are roughly 25–30 paintings that qualify, so the v1 target of 24
  is close to the ceiling of what this design can use.

### Placeholder content (until the real pipeline exists)

The authoring pipeline for originals and fakes is **deferred**. Until then the
catalog is filled with throwaway test content, and no engineering decision may
depend on how it was produced:

- Originals are fetched from Wikimedia Commons via
  `https://commons.wikimedia.org/wiki/Special:FilePath/<file>?width=1024`
  (arbitrary `upload.wikimedia.org` thumbnail sizes are rejected with HTTP 400).
- Fakes are generated crudely with a Python + Pillow script — region mirroring,
  hue shifts, clone-stamped removals. Crude is fine; they are placeholders.
- The generator **emits the JSON entry itself**: it knows `aspect` from the
  image and knows `diff_rect` because it chose where to edit. No manual
  annotation. Worth preserving as a property of the real pipeline later.
- Placeholder images and the generator live outside `data/` deliverables and are
  expected to be thrown away wholesale.

### Run generation

Naive per-room 50/50 coin flips make run length unbounded and can exhaust the
catalog, so the run is **pre-built** by `art_db.gd` from the run seed:

- Pick **10 authentic** pieces + **a random 8–14 fakes**, all **distinct** works
  (hence a 24-piece catalog).
- Shuffle with a **max streak of 3** of the same verdict.
- **No difficulty ordering.** The sequence is random. A brutally subtle fake can
  land in room 1; that is accepted for now and is the price of not guessing
  difficulty a priori. Ordering comes back once telemetry gives real numbers.

Randomizing the fake count keeps the player from deducing remaining verdicts by
counting. The run ends on the 10th authentic purchase, so the tail of the list
is usually never reached.

### Telemetry (deferred, but shapes the data now)

Difficulty will be derived from measured error rates per piece `id` and per fake
variant, not authored. Two consequences that bind today even though no telemetry
code exists yet:

- Catalog `id`s (and variant texture names) are **durable data keys**. Renaming
  one orphans its history. Choose them once.
- Collection will eventually need a backend and must work on web and Steam
  alike. Out of scope for v1, but do not design anything that makes it harder.

## Input

- **Move**: arrow keys or ZQSD/WASD. The InputMap uses **physical keycodes**, so
  one binding set yields ZQSD on AZERTY and WASD on QWERTY with no layout
  detection and no duplicate bindings.
- **Judging is two-step**, and the step boundary is what prevents fatal
  misclicks:
  - Facing the artwork, `Enter` or a mouse click opens observation.
  - Inside observation, the verdict is `B` / `F` or a click on either button.
  - So the player can never commit without having looked first.
- `Esc` leaves observation without deciding. Walking through a doorway decides
  nothing — movement is free and carries no verdict.
- Keyboard-first. Click-to-move (`NavigationRegion3D`) can be layered on later
  without disturbing this; the reverse would not be true.

## Observation mode

- Approaching the piece raises a prompt; `Enter` or a click opens observation.
- Observation stays **fully in the 3D world** — no 2D overlay, no hidden scene.
  The camera **visibly travels** from the isometric rig to a head-on framing of
  the piece; the transition is staging, not a cut.
- The camera stays **orthographic**. Head-on and orthographic means the painting
  is shown flat and undistorted — the only honest way to judge it — and zoom is
  simply the camera's `size` shrinking, so the projection never changes mid-tween.
  It also means the camera's *distance* is irrelevant to framing; only `size` is.
- **The player is hidden while observing.** He stands between the camera and the
  canvas and can press right up against it, so no camera distance clears him.
- **No reference, no hint, no catalog.** Observation only lets the player see the
  piece properly; judging it is entirely on their own knowledge.
- The two verdicts are offered here and nowhere else, as buttons and as `B` /
  `F`. `Esc` steps back out without deciding.

## Museum (`museum.gd`)

Laid out once from the run seed, before any room exists:

- One room per piece in the run, grown outward cell by cell on a grid so the
  footprint is an irregular blob rather than a rectangle.
- Rooms are wired by a Prim-style **spanning tree**, so every room is reachable —
  non-negotiable, since winning requires judging all of them — then a few extra
  links are added for loops, because a pure tree is a corridor.
- **Max 3 doors per room.** The door budget must be re-checked when adding loop
  edges: the candidate list is a snapshot and every link spends budget at both
  ends.
- `museum.gd` only *describes* rooms. `run.tscn` builds one at a time from these
  records and frees it on the way out, so wandering back is free and the
  one-room-in-memory budget survives free movement. Judged/visited state lives in
  the record, never in the scene.

## Clipboard

Bottom-left, the visitor's running tally. Two lists — **Fakes** and
**Originals** — of the pieces named so far, by their own-language title, each
indented under its heading. Titles too long for the sheet are trimmed with an
ellipsis, and when the two lists together overrun the sheet the oldest names drop
off behind a "..." so the newest verdicts stay in view.

The board is the `clipboard.glb` model, framed head-on in a small transparent 3D
sub-viewport (thin along `X`, faces `+X`, so the ortho camera sits out front on
the X axis and looks back). The two lists are laid over the paper as flat pixel
text — the `PixelatedPusab` font — so they stay crisp; the paper's own rectangle,
read off the mesh, maps to the screen rectangle the labels live in, so text never
strays onto the board or under the clip.

**Click the board** and it swells to the middle of the screen — the same face
built larger — with every name spelled out in full, the type shrunk just enough
to fit the whole tally on the paper both ways. Click it again, press Esc, or walk
off, and it folds back to the corner.

(Replaced the walked-reveal isometric minimap, which went unused.)

## Difficulty ramp

Each cleared run steps up through the difficulty levels the API lists, in order
(super-easy, easy, … up to the hardest). `GameState.levels` is filled at boot from
`/api/difficulties`; `level_spec()` gives the current level and `run.gd` sends its
`key` to `/api/run`, hanging however many pieces come back. Winning calls
`advance_level()`; the level-clear screen drops the player straight into the next
run. Clearing the last raises the **victory screen** (`victory.tscn`, the game
logo and a word of congratulation) rather than another run; dismissing it resets
progress to the first level. A fresh game or a Restart resets too.

## Debug mode

There is no in-game toggle: "debug mode" is simply how a developer runs it —
**from the editor**, or the exported binary with **`--windowed`** on the command
line. Either keeps the window windowed (Godot marks the title "(DEBUG)");
anything else launches fullscreen for a player. See `main._apply_window_mode`.

## End screen spec (`end_screen.tscn`)

The only place the player learns anything, so it carries the whole retention
loop. Three variants:

- **Lose — bought a fake**: original and fake side by side, `diff_rect`
  highlighted on the fake, `hint_key` explaining the difference in one line.
- **Lose — skipped an authentic**: the piece, stated as genuine, with its title
  and artist. No diff to show; the lesson is that it was real.
- **Win**: 10 pieces bought, run recap.

All three lead back to the menu.

## Characters & assets

- **Player**: `assets/main-character.glb` — low-poly (3.1k tris, 335 KB, one
  atlas material), rigged, and shipping 18 clips of which `Idle` and `Walk` are
  used. Chibi proportions: small body, oversized head. That is readability as
  much as style — a big head stays legible at isometric distance where a
  realistic figure turns to mush. Scaled to 0.6 (~1.8 units tall).
  No fedora and no mustache yet; accepted for now.
  Two things bite anyone swapping this model out:
  - the clips are named `CharacterArmature|CharacterArmature|...|Walk`, so
    `player.gd` resolves them by suffix rather than spelling them out;
  - the model looks down **+Z**, not the -Z a Node3D calls forward, so the
    facing yaw is `atan2(x, z)`. Get it wrong and he moonwalks.
  `CharacterBody3D`, WASD/arrows + optional click-to-move
  (`NavigationRegion3D` per room).
- **Props**: pedestal, door archways, benches. (Clerks and velvet ropes are gone
  — they gated a one-way corridor that no longer exists.)

### Asset brief

Assets are **not needed before M4**: M0–M1 grey-box with Godot primitives, which
is what validates movement, camera and collision without waiting on art.

The frame is the one asset with a non-obvious brief. **Do not model a complete
frame.** A whole frame stretched to fit `aspect` distorts its mouldings and
corners. Model instead **one moulding segment plus one corner**; the game
assembles four sides at the right lengths — which is exactly what `artwork.gd`
already does with four plain bars. One asset covers every format from portrait to
panorama with no distortion.

Everything else is conventional glTF from Blender: pedestal, archway, bench,
plant, spotlight, and floor/wall modules.
- Low-poly, shared texture atlases, Compatibility renderer budget: 60 fps on
  integrated GPUs and in-browser.
- Artwork textures are the memory hog (inspection needs resolution): cap at
  2048 px on the long edge, compressed, and never keep more than the current
  room's piece loaded.

## Audio (`audio_manager.gd`)

- Buses: `Music`, `SFX`. Volumes from the Options sliders, persisted in
  `user://settings.cfg` (works on both desktop and web).
- Menu music + in-game museum ambience; SFX: footsteps, rope install/remove,
  buy ding, wrong-buy sting, win fanfare.

## Steam integration (later, but planned now)

- All Steamworks calls go through `autoload/steam.gd`, which is a no-op stub
  unless the GodotSteam extension is present → web export never breaks.
- v1 Steam scope: init + achievements (first purchase, flawless run, win).
  Given how rare a win is, the win achievement is the marquee one.
- Export presets: Windows, macOS (notarized), Linux, Web — committed in
  `export_presets.cfg` minus credentials.

## Decided

1. **How does the player know the original?** → **(a) cultural knowledge only.**
   No reference catalog, no hints during play.
2. **Skip penalty.** → Moot: there is no skipping. Movement is free and carries
   no verdict; the verdict is explicit and **symmetric** — buying a fake and
   declaring an authentic piece fake both end the run.
2b. **Navigation.** → The museum is open from the start and walkable in any
   order. Winning means judging **every** room, which is what stops "buy the
   sure ones, ignore the rest" from being the dominant strategy.
3. **Catalog size.** → 24 paintings, no repeats within a run.
4. **Frames.** → One parametric frame driven by `aspect`.
5. **Artwork style.** → Real photographic reproductions inside a cartoon world.
6. **Difficulty.** → Not authored. Measured later from telemetry; no ordering
   in v1.
7. **Input.** → Arrows/ZQSD via physical keycodes, keyboard-first, two-step
   inspect-then-buy, no confirmation on exit.
8. **Language.** → English only.
9. **Art pipeline.** → Deferred; placeholder catalog until then.
10. **Name.** → **"Masterpiece .. Or not !"**. Beta logo at `assets/logo.svg`.

## Open design questions

None blocking. Menu art direction beyond the logo is still open, and is only
needed at M4.

## Definition of done (v1)

- Menu (logo, Start, Options) → full run → win/lose screens → back to menu.
- **24 paintings** with 2 fake variants each; statues deferred to v2.
- English-only text routed through `tr()`; volumes and quality persisted.
- Quality levels High/Medium/Low/Very Low, with Very Low smooth on a GPU-less
  netbook.
- Stable in web export and native Windows export.

## Milestones

- **M0 — Skeleton.** Godot 4.4 project, Compatibility renderer, git init, the
  four autoloads as stubs, `main.tscn` swapping menu/run/end, export presets.
- **M1 — Walk a room.** Greyboxed room, player, iso camera, doorways. Done.
- **M2 — The museum and the verdict.** `data/` loader, seeded run + museum
  layout, parametric `artwork.tscn`, observation mode with its camera travel,
  buy/fake verdicts, minimap, both lose screens, win screen.
- **M3 — Content.** Placeholder catalog first (fetched originals + scripted
  crude fakes) to prove the loop at full run length; real authoring pipeline and
  final 24 pieces × 2 fakes come after, as a separate effort.
- **M4 — Dressing.** Cartoon art direction, animations, audio, i18n FR/EN, menu
  and options, persistence.
- **M5 — Ship.** Web export with COOP/COEP, Windows export, then Steam.

M0–M2 are pure engineering and can be built with placeholder art. The long pole
is the real art authoring — 48 edited images — which is deliberately pushed
behind a placeholder catalog so the loop can be proven first.
