# Puzzle Quest

Hidden-object game built with **Godot 4.6** (single renderer: Forward+).
Branched from a Godot 3.3 codebase — migration is recent, expect leftover
Godot-3-isms to occasionally surface.

## Run

```
godot --editor   # open project in the editor
godot            # run the main scene (scenes/Main.tscn)
```

The system package `godot` (Arch `extra/godot`) is the project's Godot. There
is no in-repo Godot binary.

## Architecture

**Autoloads** (declared in `project.godot [autoload]`, loaded in this order):

| Name             | Source                                | Role                                                              |
|------------------|---------------------------------------|-------------------------------------------------------------------|
| `Loading`        | `scenes/UI/loading/Loading.tscn`      | Full-screen transition overlay (BG + animated border + progress)  |
| `Global`         | `scripts/Global.gd`                   | Async scene loader (`goto_scene(path)`) + `database` reference    |
| `Setting`        | `scripts/Setting.gd`                  | Reads/writes the settings table; applies locale/resolution/fullscreen |
| `Event`          | `scripts/Event.gd`                    | Static handlers connected to scene buttons (Warcraft / Home / reset / back) |
| `GlobalAnimation`| `scripts/Animation.gd`                | Tween-based dissolve + HUD slide/warning animations               |

**Database (`scripts/Database.gd`)**

- Plain JSON at `db/ahog.json`. Three tables: `settings`, `levels`, `scenes`.
- On Android the file is copied once to `user://database.json` and written
  there (game saves modify lock state per item).
- `Global.database` is a `Database.DB` instance exposing
  `get_table_by_name(name)` and `save_db()`. Each `Table` exposes
  `get_data_at_row_idx(row_id)`, `edit_data(prop_id, row_id, value)`,
  `m_rows_count`, `get_data_by_prop_name_and_data(prop_name, value)`,
  `get_dictionary_by_prop_name_and_data(prop_name, value)`.
- `db/M*.gd` are typed accessors (MBase / MScene / MLevel / MSetting) that
  read rows via the API above — extend these, not the JSON shape directly.

**Scene transitions** (`Global.goto_scene(path)`)

Uses `ResourceLoader.load_threaded_request` + `load_threaded_get_status` +
`load_threaded_get` (Godot 4 API; the original `load_interactive`/`poll()`
was removed). The Loading overlay (autoload) plays `BorderAnim` while the
new scene loads in a background thread; `Event._loading_is_started/finished`
flip `Global.loaded` so the poll only runs once the entry animation has set
the boolean. Don't call `current_scene.queue_free()` outside this function
— it will leak the resource and break the loader state.

**Levels.tscn / level scenes**

`scenes/levels/Levels.tscn` is the shared shell; concrete levels
(`warcraft/WarCraft.tscn`, `home/Home.tscn`) inherit from it. Each scene
has a `HiddenObjectsItems/<Item>` `MeshInstance3D` with an `Area3D`
collision shape; clicking it triggers `Levels._check_collider` →
`_start_dissolve(name)` → `GlobalAnimation.start_dissolve(mesh, material)`
which runs the dissolve shader on `material.dissolve_amount`.

The runtime hidden-object list lives under
`scenes/levels/parts/ListObjects.tscn` and is populated dynamically from
the `scenes` table for the current `Global.current_scene_int`.

## Migration notes (Godot 3 → 4)

The conversion is mostly done but a few classes of mistake keep surfacing
because `godot --convert-3to4` does not handle them:

- **Label**: `align/valign` → `horizontal_alignment/vertical_alignment`.
- **Button**: `pressed` (property) → `button_pressed`; `set_pressed()` →
  assignment to `button_pressed`. The `pressed` *signal* still exists.
- **ScrollContainer**: `scroll_horizontal_enabled = false` →
  `horizontal_scroll_mode = 0` (and same for vertical). ScrollContainer
  also ships a non-empty default panel style in Godot 4 — set
  `theme_override_styles/panel = StyleBoxEmpty` if you need transparency.
- **TextureRect / TextureButton**: `expand = true` → `expand_mode = 1`.
  `stretch_mode` enum values **shifted down by 1** between 3 and 4 (the
  deprecated "Scale on Expand" at index 0 was removed): G3's Tile (2) is
  G4's Tile (1), G3's Keep (3) is G4's Keep (2), etc.
- **Environment**: `background_mode` enum changed (G3 3=Sky → G4 2=Sky;
  G4 3 is Canvas which renders black). `background_sky` → `sky`;
  `background_energy` → `background_energy_multiplier`;
  `fog_color` → `fog_light_color`; `fog_height_min` → `fog_height`;
  `fog_mode = 1` (Depth) needed to keep `fog_depth_*` semantics, otherwise
  Godot 4 falls back to dense `fog_density` exponential fog.
- **AnimationPlayer**: `add_animation(name, anim)` removed — go through
  `get_animation_library("")` (create one with `add_animation_library("")`
  if it returns null) and call `lib.add_animation(name, anim)`.
- **Tween**: no longer a Node — call `create_tween()` from any Node and use
  `tween.tween_method(...)`. Delete any `[node type="Tween"]` from older
  `.tscn` files (the loader will crash otherwise).
- **PhysicsRayQuery**: `space_state.intersect_ray(from, to, ...)` →
  `PhysicsRayQueryParameters3D.create(from, to)`, set fields, then
  `space_state.intersect_ray(query)`.
- **PackedScene.instance()** → `instantiate()`.
- **String / cast operators**: `String(x)` constructor doesn't exist — use
  `str(x)`. `x as int/String/bool` does **not** parse strings — use
  `int(s)`, `str(x)`, `bool(int(s))`.
- **Internationalization**: project setting is `[internationalization]
  locale/translations=...`, not `[locale] translations=...`. Locale codes
  must match `.po` filenames exactly (`fr.po` → `"fr"`; G3-style `fr_FR`
  no longer auto-falls-back).
- **Inherited scenes**: nodes from an instanced child scene need
  `layout_mode = 1` + `anchors_preset = 15` explicitly set in the parent
  scene file, otherwise Godot 4 treats them as Position-mode and zeroes
  the anchors. Watch for stale per-Label `layout_mode = 0` /
  `anchor_right = 0` overrides in Main.tscn-style parent scenes.

## Plugins

- `addons/lod/` — Calinou's Level-of-Detail plugin (Spatial / OmniLight /
  SpotLight / Particles → 3D variants). Class declarations use the Godot 4
  form (`@icon("...") class_name X` + `extends Node3D` separately).
- `addons/godot_db_manager/` — **removed during migration**. Don't restore
  it; it relies on `WindowDialog`/`Tabs`/`PopupPanel` which were dropped in
  Godot 4. The replacement is `scripts/Database.gd` (see above).

## Custom shaders / materials

Drop-in shaders live in `.material` resources (binary `RSCC` format) next
to each prop. `scripts/migrate_shaders.gd` is a one-shot tool that walks
`assets/` and rewrites the embedded shader code (Godot 3 → 4 keyword
renames: `depth_draw_alpha_prepass`, `hint_color`, `NORMALMAP`). Re-run it
with `godot --headless --script scripts/migrate_shaders.gd` after adding
new materials authored in Godot 3.

`assets/fonts/text_outline.material` is a VisualShader graph from Godot 3
that doesn't connect cleanly in 4 (out-of-bounds `p_from_port` errors,
COLOR never written). It has been detached from the Summary menu labels;
re-author it with Godot 4's built-in `theme_override_constants/outline_size`
+ `theme_override_colors/font_outline_color` instead.

## Known visual issues to revisit

- **Baked lightmap is gone.** `scenes/levels/warcraft/WarCraft.lmbake` is
  in Godot 3's binary format (incompatible). The `LightmapGI` node still
  exists in `WarCraft.tscn` but its `light_data` reference is cleared.
  Open the scene in the editor and re-bake (Scene → Bake Lightmaps).
- **`assets/ui/themes/tab_select/UI-level-btn-shadow.png`** is 83% opaque
  black with feathered edges (a drop-shadow texture). In Godot 4 it
  renders stretched and opaque, which looked like a black square below
  each level tile, so its `BackgroundTile` TextureRect is currently
  `visible = false`. Replace with a proper 9-patch / shader if the shadow
  effect is wanted.
- `developers/aurelien/` is a sandbox — files there (`ui_scrolls.tscn`,
  `ui_tile.tscn`, `CheckLightmap.tscn`) still use Godot-3 property names
  in places. Not on the main flow, low priority.

## CI

Gitea Actions workflow at `.gitea/workflows/build.yml`, documented in
`.gitea/workflows/README.md`. Three jobs: GDScript validation
(`godot --headless --import` + error grep), desktop matrix
(Windows / Linux / macOS), and Android. Build only — no Butler / itch.io
deploy currently wired (channels used historically were
`dev-crea/ahog:windows|android|linux|mac`). Drone pipeline removed.

Branches: default `dev`, releases from `main`. Long-running migration work
on `feature/godot-migration`.

## Conventions

- Don't commit `db/ahog.json` runtime mutations (lock progress saved
  during play). The README documents using `git update-index --skip-worktree`
  but the cleaner alternative is just to `git checkout db/ahog.json`
  before staging.
- `android/`, `.godot/`, and `releases/` are in `.gitignore`. The
  `.import` sidecars next to assets **are** committed (they carry the
  stable UIDs Godot 4 generates).
- Commit messages: imperative, English, explain the *why* (especially for
  migration commits — the next person reading the diff won't have the
  Godot 3 context).
