# AGENTS.md — stickman (Godot 4.4) ## Project type - **Godot 4.4** 2D/GUI project (Forward Plus renderer) - No CLI build/test/lint commands; open in the Godot editor to run - **Main scene:** `res://scenes/stickman_editor.tscn` (set as `run/main_scene` in `project.godot`) ## Project overview Stickman Studio is an editor tool for drawing and assembling stick figures. It is a `Control`-based GUI (not physics/animation) in its current phase. Body-part vector shapes are authored per-panel (each panel supports **multiple shapes** with Z-ordering) and assembled in a "Whole Stickman" preview that supports translation, rotation, and scale. ## Required addon - **Scalable Vector Shapes 2D** (v2.27.7) at `addons/curved_lines_2d/` - Declared dependency for the project. The current editor UI does not instantiate it directly, but keep it present — it is required for the legacy `stick.tscn` rig. ## Architecture - `scripts/stickman_editor.gd` — `extends Control`; the main controller. Owns the menu bar, save/load/clear flow, JSON (de)serialization, and populates the 10 body-part panels. Writes `FILE_VERSION "1.2"`; auto-migrates `"1.0"`/`"1.1"` files on load. Coordinates cross-panel selection so only one shape is selected at a time (`shape_selected` → deselect others). Collects per-part `{shapes[], position, rotation, scale}` for save/load. - **Phase 6 recent colors:** stores `_recent_colors: Array[String]` (max 8, most-recent-first), loads from `settings.json` (`recent_colors` key) in `_load_settings()`, saves on each color selection via `_save_settings()`, and broadcasts to all 10 panels via `_broadcast_recent_colors()`. - `_on_color_selected(color, part_name)`: deduplicates, inserts the hex string at the front, trims to 8 entries, saves settings, then broadcasts to all panels. - `_broadcast_recent_colors()`: pushes `_recent_colors` to every panel via `BodyPartPanel.set_recent_colors(_recent_colors)`. - **Phase 6 status bar (cursor coords):** `_process()` polls `get_global_mouse_position()` each frame, checks each panel via `is_cursor_over_drawing(global_pos)` and the preview via `is_cursor_over_preview(global_pos)`, converts to world space via `global_to_world(global_pos)`, and writes `"X: ### Y: ###"` to `_status_cursor_coords`. - **Phase 6 snap status:** `_status_snap_status` displays `"SNAP: ON"` / `"SNAP: OFF"`, set in `_ready()` and re-synced when snap is toggled (`_on_edit_menu_id_pressed`). - `scripts/body_part_panel.gd` — `class_name BodyPartPanel`, `extends PanelContainer`. Reusable per-part editor. Public API: - `set_shape_data(data: Variant)` — import shape data (Array or single Dictionary; used on Load/Clear) - `get_shape_data() -> Array[Dictionary]` — export array of `{shape_type, points, color, closed, vertex_flags}` - `clear_shape()` — reset panel, clears all shapes (does **not** emit `shape_changed`) - `select()` — mark the panel's topmost shape as selected (white outline highlight) - `deselect()` — clear selection and cancel any in-progress vertex drag - `signal shape_changed(shapes: Array)` — emitted when any shape is created, modified, deleted, or reordered - `signal shape_selected()` — emitted on left-click; the editor deselects all other panels - `signal color_selected(color: Color)` — emitted when the color is confirmed (OK button) - `set_recent_colors(colors_hex: Array)` — clears existing ColorPicker presets and populates with the given hex colors - `is_cursor_over_drawing(global_pos: Vector2) -> bool` — true if `global_pos` is over the drawing surface - `global_to_world(global_pos: Vector2) -> Vector2` — maps a global position to drawing ("world") space - **Phase 2 vertex editing:** left-click on a shape selects it; drag a vertex handle to reshape in real time; with a shape selected, right-click near an outline edge offers "Create Point" (inserts a vertex flagged `1` at the edge midpoint). Original vertices are filled circles; user-created vertices are hollow rectangles. - **Phase 4 multi-shape:** panels store a `shapes[]` array. Right-click context menu includes "Send Back" (id 7) and "Bring Forward" (id 8) for Z-ordering. "Delete" (id 5) removes the specific shape under the mouse. `_selected_shape_idx` tracks which shape is active for vertex editing. - **Per-panel zoom:** mouse wheel multiplies `_zoom` by 1.10, clamped to `[0.3, 3.0]`; drawing and input hit-testing both run in world space via `draw_set_transform`. - **Phase 6 touchpad:** `_gui_input` handles `InputEventMagnifyGesture` (pinch zoom) and `InputEventPanGesture` (2-finger drag panning), both checked before `InputEventMouseButton`. Pan gesture delta is multiplied by 3.0 for speed parity with mouse panning. - **Phase 6 cursor-centered zoom:** both mouse wheel and pinch zoom adjust `_pan_offset` so the world point under the cursor stays fixed during zoom. - **Selection gizmos always on top:** bounding box, rotation circle, and scale crosses for the selected part are drawn in a second pass after all parts, via `_selected_gizmo_bounds`, so they always render in front. - Scenes: - `scenes/stickman_editor.tscn` — main editor layout; unique-name nodes (`%Prefix`) used for typed `@onready` access: `%MenuBar`, `%StickmanNameEdit`, `%LeftColumn`, `%CenterColumn`, `%WholeStickmanPreview`, `%SaveDialog`, `%LoadDialog`, `%ClearConfirmDialog`, `%ErrorDialog`, `%StatusBar`, `%CursorCoords`, `%SnapStatus`. - **Phase 6 status bar:** `StatusBar` is an `HBoxContainer` bottom-anchored at 28 px height containing `CursorCoords` (left) and `SnapStatus` (right); `MainLayout` `offset_bottom` is `-28.0` to leave room for it. - `scenes/body_part_panel.tscn` — instantiated 10× at runtime (5 per column). Each panel sets `size_flags_vertical = SIZE_EXPAND_FILL` so the panels expand to fill the column height in their parent VBoxContainer. ### Body-part data model - 10 internal part keys (ordered): `head`, `torso`, `left_upper_arm`, `left_lower_arm`, `right_upper_arm`, `right_lower_arm`, `left_upper_leg`, `left_lower_leg`, `right_upper_leg`, `right_lower_leg`. - Shape dictionary: `{ "shape_type": String, "points": Array[{x,y}], "color": "#hex", "closed": bool, "vertex_flags": Array[int] }`. - `shape_type` values: `"line"`, `"rectangle"`, `"circle"`, `""` (empty). Descriptive tag in Phase 2 — rendering uses `closed`. - `closed`: `true` = filled + closed outline, `false` = open outline-only. - `vertex_flags`: same length as `points`; `0` = original vertex (filled circle), `1` = user-created via "Create Point" (hollow rectangle). - **Phase 4:** A panel stores a `shapes[]` array of shape dictionaries. Z-order = array position (first = back, last = front). Per-part data includes `{shapes[], position, rotation, scale}`. - The JSON `.stk` format is defined in `README.md` (versioned `"1.2"`, extensible; `"1.0"`/`"1.1"` files auto-migrate on load). ### settings.json (Phase 6) - Persisted editor preferences written to `settings.json` via `_save_settings()` and loaded in `_load_settings()`. - Keys: `version`, `grid_size`, `snap_to_grid`, `recent_colors`. - `recent_colors: Array[String]` — the last up-to-8 selected hex colors, most-recent-first. Populated on load and flushed on every color selection. ### Legacy scene (do not delete) - `stick.tscn` — the original rigged/animated figure using `Skeleton2D` + IK targets + `RemoteTransform2D` + `Line2D` limbs, plus an embedded `@tool` script drawing the head circle. Animations: "walk", "walk_to", "RESET". **Not** the current main scene; kept for future animation/rigging phases. ## Editor conventions - `.godot/` is gitignored; never edit it manually. - Scene files (`*.tscn`) and `.import` files are text-based; use Godot's editor for complex changes. - UID references (Godot 4 native) exist in scenes; do not change them by hand. - Use standard Godot `Control` nodes where applicable. - Use `class_name` for globally referenced scripts (`BodyPartPanel`, `WholeStickmanPreview`). - Prefer `%UniqueName` access over exported node paths in `stickman_editor.gd` / `body_part_panel.gd`. - Keep the `.stk` JSON format backward compatible — never change the meaning of an existing key.