- Added draggable handles for the torso and head to the test harness. - Updated `IK_HANDLE_PATHS` to include new entries for "Head" and "Torso". - Implemented distinct colors for the torso (magenta) and head (yellow) markers. - Added a visual aid (aim line) to indicate the head's LookAt target direction. - Ensured that dragging the torso moves only the torso marker, allowing for limb stretching towards stationary targets.
502 lines
49 KiB
Markdown
502 lines
49 KiB
Markdown
# Stickman Studio
|
||
|
||
A Godot 4.4 editor tool for creating and assembling stick figures. Stickman Studio lets you draw individual body-part shapes, edit vertices, assemble them into a whole stickman, and save/load your figure to a JSON file (`.stk`) for later use or animation scripting.
|
||
|
||
## Overview
|
||
|
||
The editor is organized as **11 sub-windows** in a 3-column layout:
|
||
|
||
- **Left column (5)** — Head, Left Upper Arm, Left Lower Arm, Left Upper Leg, Left Lower Leg
|
||
- **Center column (5)** — Torso, Right Upper Arm, Right Lower Arm, Right Upper Leg, Right Lower Leg
|
||
- **Right column (1)** — **Whole Stickman** preview
|
||
|
||
The editor is driven by three menus: **File** (Save / Load / Clear), **Edit** (Configure Grid... / Snap to Grid), and **View** (Reset Views / Hide or Show Pose Guide).
|
||
|
||
Each body-part panel is an independent vector drawing surface with per-panel zoom **and panning (middle-mouse drag)**. A configurable background grid helps align vertices, and snap-to-grid can be enabled for both vertex dragging and whole-figure assembly. **Each panel supports multiple shapes** with Z-ordering controls (Send Back / Bring Forward) and shape-level Copy/Paste and Mirror operations. The Whole Stickman panel assembles every part into one figure — treating all shapes in a panel as a single unit — and lets you reposition, rotate, and scale each part, reorder the parts (Z-order), and mirror them.
|
||
|
||
Drawing surfaces **clip** their content to the panel bounds: shapes and parts no longer render outside a panel's borders when dragged or panned. A selected shape can be **dragged to reposition** it within its panel.
|
||
|
||
## Requirements
|
||
|
||
| Item | Version |
|
||
|---|---|
|
||
| Engine | Godot 4.4 or newer |
|
||
| Addon | Scalable Vector Shapes 2D (`addons/curved_lines_2d/`) |
|
||
|
||
> The legacy `stick.tscn` scene (a rigged walk animation using `Skeleton2D` + IK) remains in the project root for reference but is **no longer the main scene**.
|
||
|
||
## Running the project
|
||
|
||
No CLI build, test, or lint commands are used. Run the project from the Godot editor:
|
||
|
||
1. Open the project folder in **Godot 4.4**.
|
||
2. Press **F5**, or open and run `res://scenes/stickman_editor.tscn`.
|
||
|
||
The main scene is configured as `run/main_scene="res://scenes/stickman_editor.tscn"` in `project.godot`.
|
||
|
||
## Quick Start
|
||
|
||
### 1. Create a body part
|
||
|
||
1. Locate one of the 10 body-part panels (e.g. **Head**).
|
||
2. **Right-click** anywhere inside its drawing area to open the context menu.
|
||
3. Choose a starter shape:
|
||
- **Line** — a 120 px horizontal segment (2 vertices, open shape).
|
||
- **Rectangle** — a 100 × 60 px rectangle (4 vertices, closed shape).
|
||
- **Circle** — a 12-segment polygon (12 vertices, closed shape, radius 40).
|
||
|
||
The shape draws immediately and syncs to the Whole Stickman panel.
|
||
|
||
Rendering behavior:
|
||
|
||
- **Closed shapes** (`closed: true`) fill their interior with the chosen color (`draw_colored_polygon`) and draw a 2 px outline.
|
||
- **Open shapes** (`closed: false`) render as an outline-only segment with no fill.
|
||
|
||
Body-part panels expand vertically to fill their column height.
|
||
|
||
### 2. Edit vertices
|
||
|
||
Each panel supports vertex-level editing:
|
||
|
||
1. **Select a shape** — **Left-click** anywhere inside a shape to select it. The outline turns white.
|
||
2. **Create a vertex** — With a shape selected, **right-click** on its outline and choose "Create Point". A new vertex (shown as a hollow rectangle) is added at the nearest edge midpoint.
|
||
3. **Drag vertices** — **Left-click** on any vertex handle (filled circle = original, hollow rectangle = user-created) and drag to reshape. The shape conforms in real time. Release to apply. With **Snap to Grid** enabled, the dragged vertex jumps to the nearest grid intersection.
|
||
4. **Delete a vertex** — **Right-click** on a vertex handle and choose **Remove Point** (see *Deleting* below).
|
||
5. **Deselect** — Left-click outside the shape or select a different panel. Only one shape is selected at a time.
|
||
|
||
**Move a whole shape (Phase 5):**
|
||
1. **Left-click** a shape **not on a vertex handle** and drag to reposition the entire shape within the panel.
|
||
2. Release to commit. All vertices translate together; the shape's Z-order is unchanged.
|
||
3. With **Snap to Grid** enabled, the dragged shape jumps to the nearest grid intersection on release.
|
||
|
||
### 3. Zoom & Pan
|
||
|
||
Scroll the mouse wheel inside any body-part panel or the Whole Stickman preview to zoom. Pan the view with the middle-mouse button:
|
||
|
||
- **Wheel up** — zoom in by 10%
|
||
- **Wheel down** — zoom out by 10%
|
||
- **Middle-mouse drag** — pan the view in any direction
|
||
- **Range** — 30% (minimum) to 300% (maximum)
|
||
- Vertex handles remain visible at all zoom levels.
|
||
- Each panel has independent zoom and pan offset.
|
||
- Panning takes priority over vertex dragging (middle-mouse press cancels any in-progress drag).
|
||
- **Content is clipped to the panel bounds (Phase 5)** — shapes, grid, and gizmos cannot render outside a panel's drawing area, even when panned or dragged beyond its edge. This keeps each panel self-contained.
|
||
- Use **View → Reset Views** to restore all panels to 100% zoom and the origin pan offset.
|
||
|
||
### 4. Assemble a stickman
|
||
|
||
1. Create shapes for every part you want in the figure.
|
||
2. In the **Whole Stickman** panel, **drag any part** to reposition it in the figure.
|
||
- Parts get a generous hit area and show a yellow highlight while being dragged.
|
||
- A short label (`H`, `T`, `LUA`, `RUA`, `LUL`, `RLL`, …) marks each part.
|
||
- Use the mouse wheel to zoom in/out and middle-mouse to pan for precise positioning.
|
||
- With **Snap to Grid** enabled, the mouse pointer snaps to the nearest grid intersection and the part follows.
|
||
3. Optionally type a name in the **Stickman Name** field at the top.
|
||
4. **Right-click a selected part (Phase 5)** to reorder or mirror it:
|
||
- **Send Back / Bring Forward** — change the part's draw order in the preview.
|
||
- **Mirror X / Mirror Y** — flip the part around its bounding-box center.
|
||
|
||
### 5. Pose Silhouette Guide
|
||
|
||
The Whole Stickman preview can render a **pose silhouette guide**: a semi-transparent, color-coded stick figure that marks the rest-pose pivot joints of the future rig defined by `master_rig.tscn`. Its 13 joint anchors (Head, Neck, Left/Right Shoulder, Left/Right Elbow, Left/Right Wrist, Hips, Left/Right Knee, Left/Right Ankle) correspond to the exact pivot locations a future `Skeleton2D` rig will use, so you can align your body-part shapes to the same pivots.
|
||
|
||
The guide is a **view aid only**: it is drawn above the grid **and in front of user parts** (ghosting over them), moves with pan/zoom, is **not** hit-testable (it never intercepts part dragging or selection), and is **not** saved to the `.stk` file.
|
||
|
||
**Color coding** (both lines and joint dots use these color groups):
|
||
|
||
| Group | Joints | Color | Line alpha | Joint alpha |
|
||
|---|---|---|---|---|
|
||
| Left (cyan/blue) | LeftShoulder, LeftElbow, LeftWrist, LeftKnee, LeftAnkle | `(0.35, 0.70, 1.00)` | `0.45` | `0.65` |
|
||
| Right (orange/red) | RightShoulder, RightElbow, RightWrist, RightKnee, RightAnkle | `(1.00, 0.50, 0.20)` | `0.45` | `0.65` |
|
||
| Central (white) | Hips, Neck, Head | `(1.00, 1.00, 1.00)` | `0.45` | `0.65` |
|
||
|
||
- **Lines** (limbs + spine) render semi-transparent with line alpha `0.45`; the **head outline** is a white semi-transparent circle of radius 100 (1:1 `master_rig.tscn` scale) centered on the Head joint.
|
||
- **Joint dots** are constant **6 px** on screen at every zoom level (`GUIDE_JOINT_RADIUS / _zoom`).
|
||
- The guide is **ON by default**. Toggle it via **View → Hide Pose Guide** / **View → Show Pose Guide** (the menu item is a dynamic, text-only label — no checkmark — reading "Hide Pose Guide" while the guide is visible and "Show Pose Guide" while it is hidden); the setting persists to `user://settings.json` (`show_pose_guide`).
|
||
|
||
**Centered at the default view:** the guide's figure bounding box is centered at the preview-area center, computed at draw time from the live preview size (`GUIDE_FIGURE_CENTER = (0, -93.75)`; `_guide_to_preview()` maps `master_pos` to `(master_pos - GUIDE_FIGURE_CENTER) * GUIDE_SCALE + preview_area.size * 0.5`). It is centered at startup and after **Reset Views** (zoom 1 / pan 0), and re-centers automatically when the window is resized. Because it remains a **world-space fixture**, panning/zooming moves it with the grid and parts.
|
||
|
||
**1:1 master-rig scale (deliberate):** the guide matches `master_rig.tscn` proportions exactly (a figure of ≈ 336 × 940 px, roughly x ∈ [-168, 168], y ∈ [-563.5, 376]). Because the default starter parts are small, the guide intentionally **dwarfs** them. At 1:1 zoom the ~940 px figure is taller than a typical panel, so the head and feet clip equally above/below the fold when centered — accepted. `GUIDE_SCALE`/`GUIDE_FIGURE_CENTER` are the tunables if a different size or placement is wanted later.
|
||
|
||
### 6. Grid & Panning
|
||
|
||
All 10 body-part panels and the Whole Stickman preview share a global background grid (default cell size **15 px**, Phase 5; previously 5 px):
|
||
|
||
1. Click **Edit → Configure Grid...** and set the grid size.
|
||
- **SpinBox** accepts 1–100 px per cell.
|
||
- The setting is applied to every panel simultaneously and persists to `user://settings.json`.
|
||
2. Toggle **Edit → Snap to Grid** to enable snapping (Phase 5: the checkable menu item now reliably shows its ✓ checkmark when toggled):
|
||
- On **vertices** — while dragging a vertex handle, it jumps to the nearest grid intersection.
|
||
- On **assembly** — while dragging a part in the Whole Stickman preview, the pointer snaps and the part follows.
|
||
- On **shape drag** — while dragging a whole shape (Phase 5), it snaps on release.
|
||
- Existing vertices are *not* retroactively snapped; snapping only applies to new drags.
|
||
3. **Pan** — hold the **middle-mouse button** and drag anywhere in a panel to pan the view.
|
||
- Panning uses the same transform as zoom, and each panel keeps an independent offset.
|
||
- **View → Reset Views** restores 100% zoom and the origin offset on all panels.
|
||
4. Grid lines are drawn behind shapes and scale with pan/zoom, so they stay aligned to world coordinates.
|
||
|
||
### 7. Changing Colors
|
||
|
||
Colors are easy to change on any existing shape:
|
||
|
||
1. **Right-click** on a shape (either selected or with the cursor over it) and choose **Color...**.
|
||
2. A **ColorPicker** popup appears with the shape's current color pre-loaded.
|
||
3. Adjust the color — the shape updates live as a preview.
|
||
4. Click **OK** to commit the change or **Cancel** to revert to the original color.
|
||
|
||
The chosen color is stored in the shape's `color` field and is saved/loaded with the `.stk` file.
|
||
|
||
### 8. Deleting
|
||
|
||
Two levels of deletion are available:
|
||
|
||
**Delete an entire shape:**
|
||
1. **Right-click** on a shape (either selected or with the cursor over it) and choose **Delete**.
|
||
- Delete is only offered when a shape exists and the mouse is over it.
|
||
2. The shape is removed from the panel and the Whole Stickman preview.
|
||
|
||
**Remove a single vertex:**
|
||
1. **Right-click** on a vertex handle and choose **Remove Point**.
|
||
2. If 2 vertices remain, the shape becomes an open **line** (`closed = false`).
|
||
3. If 1 vertex or fewer remains, the entire shape is cleared.
|
||
|
||
### 9. Multiple Shapes & Z-Ordering
|
||
|
||
Each body-part panel can contain **more than one shape**. New shapes are created via the standard right-click context menu and are drawn on top of existing shapes. Shapes are drawn in Z-order (last in the list = frontmost).
|
||
|
||
**Z-ordering controls:**
|
||
|
||
- **Right-click** on a shape and choose **Send Back** to move it one step behind (earlier in draw order).
|
||
- **Right-click** on a shape and choose **Bring Forward** to move it one step ahead (later in draw order).
|
||
|
||
Z-order is preserved in the `.stk` save file (shapes are stored in draw order within the `shapes` array).
|
||
|
||
Only one shape within a panel can be selected at a time for vertex editing. Left-click a shape to select it; right-click also selects the shape under the mouse for context menu operations.
|
||
|
||
**Shape Copy & Paste (Phase 5):**
|
||
1. **Right-click** on a shape and choose **Copy** to place a deep copy of the shape on the editor-wide clipboard.
|
||
2. **Right-click** (on empty space or a shape) and choose **Paste** to insert the clipboard shape at the click position.
|
||
- Paste only appears while the clipboard is populated.
|
||
- The clipboard **persists after pasting** and is shared across **all 10 body-part panels** — a shape copied in one panel can be pasted into any other.
|
||
- The pasted shape becomes the newly selected (topmost) shape.
|
||
- **Copy → Clear → Paste** semantics: the clipboard survives figure clear.
|
||
|
||
**Shape Mirroring (Phase 5):**
|
||
1. **Right-click** on a selected shape and choose **Mirror X** or **Mirror Y**.
|
||
2. The shape's vertices are mirrored around the shape's bounding-box center.
|
||
3. Mirroring recomputes the `points` array in place — no extra fields are stored; the result is saved as normal vertex data.
|
||
|
||
### 10. Whole Stickman Manipulation (Selection, Rotation, Scale)
|
||
|
||
The Whole Stickman preview treats all shapes in a body-part panel as **one combined object**. Each part can be independently translated, rotated, and scaled.
|
||
|
||
**Selection:**
|
||
- **Left-click** on any part in the Whole Stickman preview to select it.
|
||
- A **white bounding box** appears around the selected part, along with manipulation gizmos.
|
||
- Click empty space to deselect.
|
||
|
||
**Translation:**
|
||
- **Left-click and drag** a selected part to reposition it. Snap-to-grid applies to the pointer position when enabled.
|
||
|
||
**Rotation:**
|
||
- When a part is selected, a **filled circle** appears centered below the bounding box.
|
||
- **Click and drag** the rotation circle to rotate the part around its center.
|
||
- Hold **Ctrl** while rotating to snap to **15-degree increments** (relative to the original 0° position).
|
||
- Rotation is **absolute**: 0° always means the original orientation the object was created with.
|
||
- The accumulated rotation value is saved in the `.stk` file.
|
||
|
||
**Scale:**
|
||
- When a part is selected, **cross/plus markers** appear at all four corners of the bounding box.
|
||
- **Click and drag** any corner cross to freely scale the part.
|
||
- Hold **Ctrl** while scaling to **lock the aspect ratio** (scale uniformly).
|
||
- Grid snapping snaps the corner position to the nearest grid intersection.
|
||
- Scale is **relative to the created size**: (1.0, 1.0) = original size.
|
||
- The scale value is saved in the `.stk` file. Scale may be **negative** for mirrored parts.
|
||
- **Part mirroring (Phase 5)** — right-click a selected part and choose **Mirror X** or **Mirror Y** to mirror it around its bounding-box center. This is implemented by negating the part's scale factor along the chosen axis (no vertex data is changed).
|
||
|
||
**Part Z-ordering (Phase 5):**
|
||
- Right-click a selected part and choose **Send Back** or **Bring Forward** to move it one step within the preview's draw order.
|
||
- Selection respects Z-order: hit-testing runs **front-to-back**, so the frontmost part under the pointer is selected.
|
||
- Part Z-order is saved in the `.stk` file as the top-level `part_order` array.
|
||
|
||
### 11. Save
|
||
|
||
1. Click **File → Save**.
|
||
2. Choose a location and name. The default extension is `.stk` (appended automatically if omitted).
|
||
3. Click **Save**. The figure is written as JSON.
|
||
|
||
### 12. Load
|
||
|
||
1. Click **File → Load**.
|
||
2. Select a `.stk` file.
|
||
3. On success, all body-part panels and the Whole Stickman preview are populated. On failure, an error dialog reports the problem (missing file, parse error, or unsupported version).
|
||
|
||
> v1.0, v1.1, v1.2, v1.3, and v1.4 files are automatically migrated to v1.5 on load (v1.0/v1.1 single shapes wrapped in a `shapes` array, rotation defaults to 0, scale defaults to (1,1); files without `part_order` fall back to the default part order). `pivot`, `length`, `proportions`, and `guide_offset` are **write-only** metadata recomputed from live shapes on every save — never read back — so old files load unchanged and gain these keys on their next save.
|
||
|
||
### 13. Clear
|
||
|
||
1. Click **File → Clear**.
|
||
2. A confirmation dialog warns that the current stickman will be cleared.
|
||
3. Confirm to reset all panels and the preview (zoom resets to 100%, pan offset to origin).
|
||
|
||
### 14. Test Harness & Stickman Factory (Phase 9)
|
||
|
||
Phase 9 adds the **runtime pipeline** that turns a saved `.stk` file into a live, rigged `master_rig.tscn` instance — plus a standalone staging scene for debugging it in isolation. **Neither is wired into the editor.**
|
||
|
||
**Stickman Factory (`res://scripts/stickman_factory.gd`)** — the runtime entry point:
|
||
|
||
| Method | Signature | Behavior |
|
||
|---|---|---|
|
||
| `load_stk` | `static func load_stk(path: String) -> Dictionary` | Reads a `.stk` file via `FileAccess` + `JSON.parse_string`. Returns `{}` and `push_warning` on failure. |
|
||
| `spawn_from_data` | `static func spawn_from_data(stk_data: Dictionary) -> Node2D` | Instantiates `res://master_rig.tscn`, calls `StkRigAdapter.apply(stk_data, rig)`, returns the rig root. |
|
||
| `spawn` | `static func spawn(path: String) -> Node2D` | Chains `load_stk()` → `spawn_from_data()`. Returns `null` on empty data. |
|
||
|
||
The factory is the intended runtime API: `StickmanFactory.spawn("res://stickmen/basic.stk")` yields a rigged `Node2D` ready to add to the scene tree. `StkRigAdapter` (Phase 8, extended by Phase 9) now also fits the head bone (`Head.position.y = -proportions.torso_length`) and mounts the head as **full geometry** like every other part (clearing its inline `@tool` circle script); the mounted head is dropped `HEAD_CHIN_DROP` (28 px) below the neck so its chin aligns with the pose-guide circle's bottom and overlaps the torso (Phase 9 Round 4).
|
||
|
||
**Test Harness (`res://scenes/test_harness.tscn`)** — run via **F6** on the scene (NOT via the main editor scene). Controls:
|
||
|
||
- **Open .stk…** — `FileDialog` filtered to `*.stk`; quick-select buttons for `res://stickmen/break.stk`, `res://stickmen/basic.stk`, `res://stickmen/test.stk`.
|
||
- **Show Bones / Show IK Handles** — checkboxes toggling the debug overlay.
|
||
- **Loaded filename** — status label showing the currently loaded file.
|
||
- **Pan / zoom** — middle-mouse drag to pan, mouse-wheel to zoom the `Camera2D`; the camera recenters on each spawn.
|
||
- **IK drag** — click and drag any of the **6** `Marker2D` IK handles (`IK_Targets/Left_Hand`, `Right_Hand`, `Left_Leg`, `Right_Leg` flex the limb via TwoBoneIK; `IK_Targets/Torso` translates the whole rig rigidly via its `RemoteTransform2D`; `IK_Targets/Head` drives the head's `SkeletonModification2DLookAt` aim rotation) (Phase 9 Round 7). The harness enables the modification stack after each spawn.
|
||
|
||
Debug overlay (a world-space `Node2D` `_draw()`): true bone **segments** drawn between each `Bone2D` origin and its Bone2D children (color-coded left cyan / right orange / central white, with a joint dot per bone), with limb leaf bones drawn out to their IK targets so the forearm/shin segments and wrist/ankle joints are visible (Phase 9 Round 2) and the **Head** leaf drawn along the bone's own direction (~90 px, since its IK target is a LookAt aim point, not a joint) (Phase 9 Round 3), when **Show Bones** is on; colored markers at the six IK targets — hands green, feet blue, head **yellow**, torso **magenta** (Phase 9 Round 7) — plus a semi-transparent yellow aim line from the Head bone to the head marker, when **Show IK Handles** is on. Each load frees the previous rig and spawns a fresh one.
|
||
|
||
## File format (`.stk`)
|
||
|
||
Files are UTF-8 JSON, pretty-printed with tab indentation. The format is versioned and designed to remain **backward/forward compatible** — new fields can be added without breaking older files.
|
||
|
||
```json
|
||
{
|
||
"version": "1.5",
|
||
"stickman_name": "Bob",
|
||
"part_order": [
|
||
"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"
|
||
],
|
||
"proportions": {
|
||
"upper_arm_length": 168.0,
|
||
"lower_arm_length": 200.0,
|
||
"upper_leg_length": 200.0,
|
||
"lower_leg_length": 200.0,
|
||
"torso_length": 391.5
|
||
},
|
||
"body_parts": {
|
||
"head": {
|
||
"shapes": [
|
||
{
|
||
"shape_type": "circle",
|
||
"closed": true,
|
||
"points": [
|
||
{ "x": 120, "y": 40 },
|
||
{ "x": 124, "y": 39 }
|
||
],
|
||
"color": "#000000",
|
||
"vertex_flags": [0, 0, 1, 0]
|
||
}
|
||
],
|
||
"position": { "x": 150, "y": 40 },
|
||
"rotation": 0.0,
|
||
"scale": { "x": 1.0, "y": 1.0 },
|
||
"pivot": { "x": 122.0, "y": 39.5 },
|
||
"length": 10.0,
|
||
"guide_offset": { "x": 122.0, "y": 431.0 }
|
||
}
|
||
},
|
||
"metadata": {
|
||
"created_at": "2026-08-05T12:00:00",
|
||
"modified_at": "2026-08-05T12:00:00"
|
||
}
|
||
}
|
||
```
|
||
|
||
### Root object
|
||
|
||
| Key | Type | Description |
|
||
|---|---|---|
|
||
| `version` | `string` | Format version. Currently `"1.5"`. Loading supports `"1.0"`–`"1.5"` (auto-migrated). |
|
||
| `stickman_name` | `string` | Optional display name for the figure. |
|
||
| `part_order` | `array[string]` | **Phase 5.** Render/Z-order of parts in the Whole Stickman preview, front-to-back semantics per array position (first = back, last = front). Absent on v1.0–v1.2 files; defaults to the internal part-key order when missing. |
|
||
| `proportions` | `object` | **Phase 8.** Rig bone lengths used by the runtime `StkRigAdapter`. Object with 5 float keys: `upper_arm_length` (168.0), `lower_arm_length` (200.0), `upper_leg_length` (200.0), `lower_leg_length` (200.0), `torso_length` (391.5). Hardcoded **master-rig rest-pose constants** (from `master_rig.tscn`), not measured from the user's shapes. Write-only metadata — never read back on load. |
|
||
| `body_parts` | `object` | Map of `body_part_name → shape` objects. Keyed by the 10 internal part names below. |
|
||
| `metadata.created_at` | `string` | Timestamp written on save. |
|
||
| `metadata.modified_at` | `string` | Timestamp written on save. |
|
||
|
||
### Part keys
|
||
|
||
`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`
|
||
|
||
### Part object
|
||
|
||
Each body part is an object with the following keys:
|
||
|
||
| Key | Type | Description |
|
||
|---|---|---|
|
||
| `shapes` | `array` | Array of shape objects (see below). Draw order = array order (first = back, last = front). Wrapped as a single-element array when migrating v1.0/v1.1 files. |
|
||
| `position` | `object` | `{ "x": float, "y": float }` position of the part within the Whole Stickman preview. |
|
||
| `rotation` | `float` | Rotation in degrees. `0.0` = original orientation. Defaults to `0.0` for v1.0/v1.1 files. |
|
||
| `scale` | `object` | `{ "x": float, "y": float }` scale factors relative to created size. `1.0` = original size. May be **negative** (Phase 5) to represent mirroring along an axis. Defaults to `{ "x": 1.0, "y": 1.0 }` for older files. |
|
||
| `pivot` | `object` | **Phase 8.** `{ "x": float, "y": float }` — the **local rotation origin**, i.e. the bounding-box center of all shape points in the part's local drawing space (before `position` is applied). Always written for all 10 parts; `{ "x": 0.0, "y": 0.0 }` for an empty part. Write-only metadata — never read back on load. |
|
||
| `length` | `float` | **Phase 8.** The part's bounding-box extent along its **segment axis**, in local pixels: **width** (`max_x - min_x`) for the 4 arm parts, **height** (`max_y - min_y`) for torso, legs, and head. `0.0` for an empty part. Write-only metadata — never read back on load. |
|
||
| `guide_offset` | `object` | **Phase 9 Round 5.** `{ "x": float, "y": float }` — the part's **bbox center in preview space minus its silhouette-guide joint in preview space** (a pure master-space delta; the panel-size terms cancel). The adapter uses it to reproduce the editor's guide-relative placement in the harness. Write-only metadata — never read back on load; v1.0–v1.4 files load unchanged and gain the key on their next save. |
|
||
|
||
### Shape object
|
||
|
||
Each entry in the `shapes` array:
|
||
|
||
| Key | Type | Description |
|
||
|---|---|---|
|
||
| `shape_type` | `string` | `"line"`, `"rectangle"`, `"circle"`, or `""` (empty). Descriptive tag only — rendering uses `closed`. |
|
||
| `closed` | `bool` | Whether the polygon is closed (filled) or open (outline-only). |
|
||
| `points` | `array` | Array of `{ "x": float, "y": float }` vertex positions **local to the drawing area**. |
|
||
| `color` | `string` | Hex colour, e.g. `"#000000"`. Changeable in-app via right-click → **Color...** (Phase 3). |
|
||
| `vertex_flags` | `array[int]` | Same length as `points`. `0` = original vertex, `1` = user-created via "Create Point". Controls handle appearance. |
|
||
|
||
> On load, parts missing from the file fall back to built-in default preview positions. v1.0 files have `closed` inferred from `shape_type` and `vertex_flags` default to all zeros. v1.0/v1.1 files have their single shape wrapped in a `shapes` array and default `rotation`/`scale` applied. Files without `part_order` (v1.0–v1.2) use the default part-key order. Note that shape mirroring (Phase 5) rewrites the `points` array in place, so no new shape fields are required — the resulting vertices load identically across all supported versions.
|
||
|
||
## Settings file (`user://settings.json`)
|
||
|
||
Phase 3 introduces a global settings file stored in Godot's **user data** directory (`user://`), separate from the `.stk` figure files. It persists the grid configuration and is versioned for future extensibility:
|
||
|
||
```json
|
||
{
|
||
"version": "1.0",
|
||
"grid_size": 15,
|
||
"snap_to_grid": false,
|
||
"show_pose_guide": true
|
||
}
|
||
```
|
||
|
||
### Settings keys
|
||
|
||
| Key | Type | Default | Description |
|
||
|---|---|---|---|
|
||
| `version` | `string` | `"1.0"` | Settings file version (for future extensibility). |
|
||
| `grid_size` | `int` | `15` | Grid interval in pixels, applied equally to width and height. Default changed from `5` to `15` in Phase 5. Clamped to 1–100 on load. |
|
||
| `snap_to_grid` | `bool` | `false` | Whether snap-to-grid is active. |
|
||
| `show_pose_guide` | `bool` | `true` | Whether the pose silhouette guide is visible in the Whole Stickman preview. |
|
||
|
||
Behavior:
|
||
- **Load** — read on editor startup; if the file is missing or fails to parse, defaults (`grid_size = 15`, `snap_to_grid = false`, `show_pose_guide = true`) are used silently (no error dialog).
|
||
- **Save** — written whenever the user changes the grid size, toggles Snap to Grid, or toggles the pose guide.
|
||
- **Scope** — global; all body-part panels and the Whole Stickman preview share the same grid size and snap setting.
|
||
- **Pan offsets are NOT persisted** — they reset on load, clear, and "Reset Views".
|
||
|
||
## Project structure
|
||
|
||
| Path | Purpose |
|
||
|---|---|
|
||
| `res://project.godot` | Engine config; sets main scene to the editor and enabled features. |
|
||
| `res://scenes/stickman_editor.tscn` | **Main scene** — editor layout, File/Edit/View menu bar, dialogs (`GridConfigDialog` + SpinBox), column containers (unique-name nodes). |
|
||
| `res://scenes/body_part_panel.tscn` | Reusable single body-part editor panel (title, drawing area, context menu, `ColorPickerPopup`); expands vertically in its column. |
|
||
| `res://scripts/stickman_editor.gd` | Editor controller — File/Edit/View menu actions, save/load/clear, JSON v1.5 serialization with multi-shape/rotation/scale, `part_order`, Phase 8 `proportions`/`pivot`/`length`, and Phase 9 Round 5 per-part `guide_offset` export, `settings.json` load/save, editor-wide shape clipboard (Copy/Paste across panels), broadcast of grid/snap settings to panels, Reset Views, populates panels, coordinates selection across panels. |
|
||
| `res://scripts/stk_rig_adapter.gd` | **Phase 8, extended by Phase 9 (Rounds 4–6 bugfix).** Standalone runtime adapter (`class_name StkRigAdapter`, `static func apply(stk_data, rig)`): fits an instantiated `master_rig.tscn` to a loaded `.stk` by re-fitting the 8 limb bones (`Skeleton2D/Torso/...` `Bone2D` lengths + lower-bone origins), recalibrating the IK targets (`IK_Targets/Left|Right_Hand`, `Left|Right_Leg`), and mounting the `.stk` shapes onto the `Body/*` visual nodes (**one node per shape**: closed → single `Polygon2D` fill, open → single `Line2D` width 2). Shape mounting recomputes each part's bounding box at mount time (file `pivot`/`length` are no longer trusted) and derives a mount transform in the rig's **hanging convention** (joint anchor at the local origin, far end along local `+Y`) via `_compute_mount_transform()`: the part's preview transform `E(P) = C + R(rot)·S·(P − C)` (rotation + scale about the bbox center — the editor's exact Whole-Stickman-preview transform) is composed **first**, then the anchor/alignment θ/bone-fit scale are computed on the **transformed geometry**; rotations near ±180° (`|wrapf(rot)| > 0.75π`) swap the attachment to the drawn far end so flips are visible (e.g. the 180° torso shows its drawn neck end at the hip joint). Anchors (raw family rules): head/torso bottom-center `(cx, max_y)`, left horizontal limbs `(max_x, cy)`, right horizontal limbs `(min_x, cy)`, vertically drawn limbs top-center `(cx, min_y)`; alignment rotation θ maps the far end onto `+Y`; scaling is **anisotropic** — only the **auto-detected drawn long axis** (`width >= height`) scales to the bone length (`bone_length/extent`, guard `extent <= 0.0001` → `1.0`), cross-axis thickness stays 1:1. The `RemoteTransform2D` drivers keep `update_rotation = true`, so mounted shapes follow their bones under IK flexing. (Phase 9 Round 5) when a part dict carries `guide_offset`, the mounted geometry is translated by `t = (guide_offset + (A − C)).rotated(−c_node)`; (Phase 9 Round 6) when `guide_offset` is present, the joint anchor is whichever transformed end (`E(J_raw)` or `E(F_pt_raw)`) is nearest the part's guide joint (`center − guide_offset`), replacing the per-side family choice + 180° flip heuristic for that case (fixing the lower-left-leg and lower-right-arm, which were mounted 180° off their bones) — old files without the key keep the family rules + flip heuristic as the fallback in the driver's bone frame (A = mount anchor incl. the 180° flip rule, C = raw bbox center, `c_node` = driver `RemoteTransform2D.global_rotation`), so the harness reproduces the editor's guide-relative placement 1:1; old files without the key keep the offset-0 behavior (head falls back to `HEAD_CHIN_DROP`). Each `Body/*` container's scale is reset to `(1,1)` / rotation `0` (position untouched). (Phase 9) also fits the head bone (`Head.position.y = -proportions.torso_length`) while mounting the head as **full geometry** — it clears the head's inline `@tool` circle script and mounts `.stk` head shapes as `Line2D`/`Polygon2D`, and zeroes the Head driver's local position so the chin sits on the neck joint; the head mounts upright (`θ = 0`, `s = 1`) but still applies the part scale via `E` (face ≈160 px). **Not used by the editor** — consumed by the runtime pipeline. |
|
||
| `res://scripts/stickman_factory.gd` | **Phase 9.** Runtime entry point (`class_name StickmanFactory`, `extends RefCounted`); a static factory that turns a `.stk` file into a live, rigged `master_rig.tscn` instance. `load_stk(path)` reads + parses the file (`{}` + `push_warning` on failure); `spawn_from_data(stk_data)` instantiates `res://master_rig.tscn` and calls `StkRigAdapter.apply(stk_data, rig)`; `spawn(path)` chains them (`null` on empty data). **Not used by the editor.** |
|
||
| `res://scripts/test_harness.gd` | **Phase 9.** Standalone staging scene (run via **F6** on `res://scenes/test_harness.tscn`, not wired into the editor) for debugging bone scales, vector-drawing offsets, and IK limits in isolation. Top UI bar: "Open .stk…" / quick-select buttons (`stickmen/break.stk`, `stickmen/basic.stk`, `stickmen/test.stk`), "Show Bones" / "Show IK Handles" toggles, loaded-filename label. `SubViewport` world + enabled `Camera2D` (middle-mouse pan, wheel zoom, recenter on spawn); each load frees the previous rig and spawns a fresh one via `StickmanFactory.spawn()`. A world-space debug overlay draws true bone segments (joint dots + parent→child lines, with limb leaf bones drawn out to their IK targets so wrist/ankle joints are visible; the **Head** leaf is the exception — its target is a LookAt aim point, not a joint, so it draws a ~90 px segment along the bone's own direction instead) and colored IK-target markers (hands green, feet blue, head yellow, torso magenta) plus a semi-transparent yellow head-aim line; the **6** `Marker2D` IK targets are click-draggable — the 4 limb targets flex limbs live via `SkeletonModificationStack2D` TwoBoneIK (enabled after each spawn), the Torso target translates the whole rig via its `RemoteTransform2D`, and the Head target drives the head's LookAt aim rotation (Phase 9 Round 7). |
|
||
| `res://scenes/test_harness.tscn` | **Phase 9.** Standalone staging scene backing `scripts/test_harness.gd` (run via **F6**; not wired into the editor). |
|
||
| `res://scripts/body_part_panel.gd` | Multi-shape creation, vertex editing, shape dragging, per-panel zoom & pan, grid drawing & snap-to-grid, ColorPicker, shape/vertex delete, Z-ordering (Send Back / Bring Forward), shape Copy/Paste, shape Mirror X/Y, drawing (fill + outline for closed shapes). |
|
||
| `res://scripts/whole_stickman_preview.gd` | Assembly preview, drag-to-reposition, part selection with white bounding box, rotation gizmo (circle below box) with Ctrl 15° snap, scale gizmo (corner crosses) with Ctrl aspect lock, part Z-ordering (Send Back / Bring Forward) via `part_order`, part Mirror X/Y (scale negation), zoom & pan, grid drawing & snap-to-grid, pose silhouette guide (Phase 7), part hit-bounds, labels, and (Phase 9 Round 5) `get_guide_joint_preview()` — the preview-space position of a guide joint, used by the editor to export per-part `guide_offset`. |
|
||
| `res://addons/curved_lines_2d/` | Scalable Vector Shapes 2D addon (v2.27.7) — required dependency. |
|
||
| `res://stick.tscn` | **Legacy** rigged/animated stick figure scene (Skeleton2D + IK). Not used by the editor. |
|
||
| `res://AGENTS.md` | Guidance for AI agents working in this codebase. |
|
||
|
||
### Menu bar structure
|
||
|
||
```
|
||
File Edit View
|
||
──────────── ──────────────────── ───────────
|
||
Save Configure Grid... Reset Views
|
||
Load ───────── ─────────────
|
||
──────── Snap to Grid (check) Hide/Show Pose Guide
|
||
Clear
|
||
```
|
||
|
||
- **File** — Save, Load, Clear (Phase 1).
|
||
- **Edit** — `Configure Grid...` (dialog with a 1–100 SpinBox) and the checkable `Snap to Grid` toggle (Phase 3).
|
||
- **View** — `Reset Views` (resets zoom to 100% and pan offset to origin on every panel) (Phase 3) and the dynamic text-only `Hide/Show Pose Guide` toggle (Phase 7): reads **"Hide Pose Guide"** while the guide is visible and **"Show Pose Guide"** while it is hidden.
|
||
|
||
### Context menu (per body-part panel)
|
||
|
||
The right-click context menu is context-sensitive. Item IDs:
|
||
|
||
| ID | Label | Shown when |
|
||
|----|-------|------------|
|
||
| 0 | Line | Empty space right-click |
|
||
| 1 | Rectangle | Empty space right-click |
|
||
| 2 | Circle | Empty space right-click |
|
||
| 3 | Create Point | Shape under mouse, near outline |
|
||
| 4 | Color... | Shape under mouse |
|
||
| 5 | Delete | Shape under mouse |
|
||
| 6 | Remove Point | Right-click on a vertex handle |
|
||
| 7 | Send Back | Shape under mouse |
|
||
| 8 | Bring Forward | Shape under mouse |
|
||
| 9 | Copy | Shape under mouse (Phase 5) |
|
||
| 10 | Paste | Shape or empty space right-click; shown only when the clipboard is populated (Phase 5) |
|
||
| 11 | Mirror X | Shape under mouse (Phase 5) |
|
||
| 12 | Mirror Y | Shape under mouse (Phase 5) |
|
||
|
||
### Context menu (Whole Stickman preview)
|
||
|
||
Right-clicking a selected part opens the preview's context menu:
|
||
|
||
| ID | Label | Description |
|
||
|----|-------|-------------|
|
||
| 0 | Send Back | Move the selected part one step back in `part_order` (Phase 5) |
|
||
| 1 | Bring Forward | Move the selected part one step forward in `part_order` (Phase 5) |
|
||
| 2 | Mirror X | Mirror the selected part around its bounding-box center (scale X negated) (Phase 5) |
|
||
| 3 | Mirror Y | Mirror the selected part around its bounding-box center (scale Y negated) (Phase 5) |
|
||
|
||
### Signal flow
|
||
|
||
```
|
||
BodyPartPanel.shape_changed(data) ---(bound to part_name)---> stickman_editor
|
||
| |
|
||
| _on_body_part_shape_changed |
|
||
+--------------------------> WholeStickmanPreview |
|
||
.set_body_parts(all_data) |
|
||
^ |
|
||
WholeStickmanPreview.part_moved(name, pos) (drag handling) --+
|
||
|
||
BodyPartPanel.shape_selected() ---(bound to part_name)---> stickman_editor
|
||
|
|
||
_on_body_part_shape_selected
|
||
(deselects all other panels)
|
||
```
|
||
|
||
- `BodyPartPanel` emits `shape_changed(shapes_array)` whenever a shape is created, a vertex is dragged (on release), a color is committed via the ColorPicker (on OK), a shape is deleted, a vertex is removed via "Remove Point", or Z-order changes via "Send Back"/"Bring Forward". The signal carries the full `Array[Dictionary]` of all shapes in the panel. Note: `clear_shape()` does **not** emit the signal; the editor's Clear flow refreshes the preview directly.
|
||
- `BodyPartPanel` emits `shape_selected` when left-clicked; the editor deselects all other panels.
|
||
- `stickman_editor.gd` collects shape data from all 10 panels and forwards it to `WholeStickmanPreview.set_body_parts()`.
|
||
- `WholeStickmanPreview` emits `part_moved(part_name, new_position)` during translation drags. Rotation and scale are stored internally and polled by the editor at save time via `get_part_rotation()` / `get_part_scale()`. Part Z-order is polled the same way via `get_part_order()` and saved to the top-level `part_order` field (Phase 5).
|
||
- Shape Copy/Paste (Phase 5) is owned by the editor: `_copy_shape()` stores a deep duplicate in `_shape_clipboard`, `_broadcast_clipboard_state()` tells every panel whether Paste should be offered, and `_paste_shape()` inserts the clipboard into the target panel at the click position. The clipboard persists after pasting and is shared across all panels.
|
||
|
||
> **Phase 4:** Each body part can have multiple shapes in Z-order. The Whole Stickman preview treats all shapes in a panel as one combined object, with selection (white bounding box), rotation (circle gizmo below box), and scale (cross gizmos at corners). The `.stk` format evolved to v1.2 with a `shapes` array per part plus `rotation` and `scale` fields. Rotation snaps to 15° with Ctrl; scale locks aspect ratio with Ctrl.
|
||
|
||
> **Phase 5:** Adds drawing-surface clipping, shape dragging, an editor-wide shape clipboard (Copy/Paste across panels), shape Mirror X/Y (vertex recompute), and preview object Z-ordering (Send Back/Bring Forward) with Mirror X/Y (scale negation). The format evolved to v1.3: a new top-level `part_order` array stores preview Z-order; scale may be negative for mirrored parts; shape mirroring stores no new fields because it rewrites `points`. The default grid size changed from 5 px to 15 px, and the Snap to Grid checkmark now renders correctly when toggled. v1.0–v1.2 files remain backward compatible and are migrated on load.
|
||
|
||
> **Phase 7:** Adds the pose silhouette guide to the Whole Stickman preview — a semi-transparent, color-coded stick figure (left cyan-blue, right orange-red, central white) whose 13 joint anchors match the rest-pose pivots of the future `master_rig.tscn` rig, drawn above the grid and in front of user parts (ghosting over them, below the selection gizmos and drag highlight), centered in the preview at the default view. Toggleable via the dynamic **View → Hide/Show Pose Guide** label (on by default, persisted to `settings.json`). The guide is a view aid only: **no `.stk` format change** — `FILE_VERSION` stays `"1.3"`.
|
||
|
||
> **Phase 8:** The `.stk` export evolved to **v1.4** with write-only metadata the editor never reads back. A top-level `proportions` object stores the 5 master-rig rest-pose bone lengths (`upper_arm_length` 168.0, `lower_arm_length` 200.0, `upper_leg_length` 200.0, `lower_leg_length` 200.0, `torso_length` 391.5) — hardcoded constants from `master_rig.tscn`, not measured from the user's shapes. Each `body_parts` entry gains `pivot` (local bounding-box center = rotation origin) and `length` (bbox extent along the segment axis: width for arms, height for torso/legs/head). v1.0–v1.3 files load unchanged and gain these keys on their next save. A new standalone `res://scripts/stk_rig_adapter.gd` (`class_name StkRigAdapter`) fits an instantiated `master_rig.tscn` to a loaded `.stk` (bone fitting + IK recalibration + visual shape mount); it is **not** used by the editor and is reserved for a future runtime pipeline.
|
||
|
||
> **Phase 9:** Adds the **runtime pipeline** for turning a `.stk` file into a live, rigged `master_rig.tscn` instance, plus a standalone staging scene to debug it. A new `res://scripts/stickman_factory.gd` (`class_name StickmanFactory`) provides the runtime entry point: `load_stk(path)` reads/parses a `.stk` (`FileAccess` + `JSON.parse_string`, `{}` + `push_warning` on failure), `spawn_from_data(stk_data)` instantiates `master_rig.tscn` and applies `StkRigAdapter.apply(stk_data, rig)`, and `spawn(path)` chains them (`null` on empty data). `StkRigAdapter` is extended to also fit the head bone (`Head.position.y = -proportions.torso_length`) and to mount the head as **full geometry** like every other part — clearing its inline `@tool` circle script and mounting `.stk` head shapes as `Line2D`/`Polygon2D` (removed the dead `_mount_head_circle`, `_compute_shapes_bbox`, and `_first_shape_color` helpers). A new standalone scene/resource pair, `res://scenes/test_harness.tscn` + `res://scripts/test_harness.gd` (run via **F6**), loads `.stk` files (open dialog + quick-select for `stickmen/break.stk`, `stickmen/basic.stk`, `stickmen/test.stk`), toggles a world-space debug overlay (true bone segments — parent→child lines with joint dots, limb leaf bones drawn to their IK targets and the Head leaf along its own direction — plus IK-target markers), pans/zooms a `Camera2D`, and lets you click-drag the 4 limb IK targets with live TwoBoneIK flexing (`SkeletonModificationStack2D` enabled on spawn). Neither the factory nor the harness is wired into the editor. **No `.stk` format change** — `FILE_VERSION` stays `"1.4"`.
|
||
|
||
> **Phase 9 Round 2 bugfix:** `StkRigAdapter._mount_shapes()` no longer trusts the file's `pivot`/`length`; it recomputes each part's bounding box at mount time and mounts geometry in the rig's **hanging convention** (joint anchor at the local origin, far end along local `+Y`) with a per-family alignment rotation (head `0`, torso `π`, left limbs `−π/2`, right limbs `+π/2`, vertical-drawn limbs `0`). Anchors: head/torso bottom-center, left horizontal limbs `max_x`, right horizontal limbs `min_x`, vertical limbs top-center. Scaling is **anisotropic** — only the **auto-detected drawn long axis** (`width >= height`) scales to the bone length (`bone_length/extent`, guard `extent <= 0.0001` → `1.0`), cross-axis thickness stays 1:1. Driver rotation neutralization was **removed** — the 10 `RemoteTransform2D` drivers keep `update_rotation = true`, so mounted shapes follow their bones under IK flexing. `Body/*` container transforms are reset (scale `(1,1)`, rotation `0`, position untouched), the Head driver's local position is zeroed so the chin lands on the neck joint, and `DEFAULT_LINE_WIDTH` is 2.0 (editor's 2 px outline). The harness `_draw_bones()` was rewritten to draw true parent→child bone segments with leaf bones out to their IK targets (wrist/ankle joints now visible). Per docs/phase9_round2_bugfix_spec.md; verified with a 46-assertion headless smoke test.
|
||
|
||
> **Phase 9 Round 3 bugfix:** `StkRigAdapter._mount_shapes()` now reads each part's `rotation` (degrees, default 0.0) and `scale` (`{x,y}`, default `(1,1)`) and composes the preview's part transform `E(P) = C + R(rot)·S·(P − C)` (scale-then-rotate about the bbox center) **before** the hanging-convention mount — the anchor, alignment θ, and bone-fit scale are computed on the transformed geometry, so every rotation/scale the user applied in the Whole Stickman preview renders in the harness. Rotations near ±180° (`|wrapf(rot)| > 0.75π`) swap the attachment to the drawn far end so flips are visible (the 180° torso shows its drawn neck end at the hip joint); the bone-fit scale `s = bone_length/|V|` is measured on the transformed extent so user-scaled parts are not double-fitted. The head mounts upright (`θ = 0`, `s = 1`) but still applies the part scale via `E` (face ≈160 px), chin at the neck joint. `_mount_shape()` mounts **one node per shape** — closed → single `Polygon2D` (fill only), open → single `Line2D` (width 2) — removing the previous Polygon2D + Line2D outline duplication. The harness head-leaf overlay now draws along the bone's own direction (the LookAt aim point is not a joint) instead of a line to `IK_Targets/Head`, eliminating the bone sticking out of the head. Per docs/phase9_round3_bugfix_spec.md; verified with a 26-assertion headless smoke test.
|
||
|
||
> **Phase 9 Round 4 bugfix:** the mounted **head** was too high in the harness — its chin (the local origin) landed on the neck joint at `y = −391.5`, floating above the torso. The editor's silhouette guide draws the head as a radius-100 circle centered at the Head joint `(0, −463.5)` (bottom at `−363.5`), so `StkRigAdapter` now applies a `+28 px` rig-space translation (`const HEAD_CHIN_DROP := 28.0`) to the mounted head points after the part transform and the `(θ = 0, s = 1)` transform. The chin lands at world ≈ `(0, −363.5)` — the guide circle's bottom — overlapping the torso (top at `−391.5`) by 28 px, matching the editor guide. The drop is flip-agnostic (a rig-space fixture applied after part scale/rotation). Per docs/phase9_round4_bugfix_spec.md; verified with a 15-assertion headless smoke test.
|
||
|
||
> **Phase 9 Round 5:** the `.stk` export evolved to **v1.5** with per-part **`guide_offset`** — the part's bbox center minus its silhouette-guide joint (both in preview space, a pure master-space delta). `StkRigAdapter` applies it only when present as a node-frame translation `t = (guide_offset + (A − C)).rotated(−c_node)` (A = the mount anchor incl. the 180° flip rule, C = the raw bbox center, `c_node` = the part's driver `RemoteTransform2D.global_rotation` at apply time), so the harness reproduces the editor's guide-relative placement **1:1**; old files without the key keep the previous offset-0 behavior and the head falls back to the Round 4 `HEAD_CHIN_DROP`. The head maps to the guide **Neck** joint — the head bone's rig attachment origin, **not** the circle center (which sits 72 px above the neck). `guide_offset` is **write-only** metadata like `pivot`/`length` — the load path ignores it, so v1.0–v1.4 files load unchanged and gain the key on their next save. **Note:** `.stk` files must be re-saved from the editor to populate the offsets. Per docs/phase9_round5_bugfix_spec.md.
|
||
|
||
> **Phase 9 Round 6 bugfix:** `StkRigAdapter._compute_mount_transform()` now selects the joint anchor as whichever transformed end (`E(J_raw)` or `E(F_pt_raw)`) is **nearest the part's stored guide joint** (`center − guide_offset`) when a part carries `guide_offset`. This replaces the per-side family choice and the 180° flip heuristic for that case, fixing the **lower left leg** and **lower right arm**, which were mounted 180° off their bones (the far end attached at the joint) because the user's drawn-side conventions are inconsistent across parts — the stored guide placement is the ground truth for which drawn end is the joint. The nearest-end rule naturally preserves the 180° flip behavior (a flipped part's far end lands nearest the joint), the head chin, and every previously-correct case. Old files without the key keep the family rules + flip heuristic exactly as before. `theta`, `s`, the Round 5 offset `t`, and the head `HEAD_CHIN_DROP` fallback are unchanged. Per docs/phase9_round6_bugfix_spec.md; verified with a 32-assertion headless smoke test.
|
||
|
||
> **Phase 9 Round 7:** the test harness (`scripts/test_harness.gd`) now exposes **6** draggable IK handles. `IK_HANDLE_PATHS` gains `"Head"` (`IK_Targets/Head`, the `SkeletonModification2DLookAt` aim point) and `"Torso"` (`IK_Targets/Torso`, whose child `RemoteTransform2D` moves the hip bone). Dragging the **Torso** handle moves **bones only** (no target following) — the marker's `RemoteTransform2D` translates the hip bone, and the whole skeleton + `Body/*` visuals follow rigidly, while the limb/head targets stay put so dragging the figure away from them stretches the limbs toward the stationary targets (per user decision). Dragging the **Head** handle drives the Head bone's LookAt rotation (clamped at the authored ~55° constraint); `Body/Head` follows. `_handle_color()` colors the head marker yellow (`HANDLE_COLOR_HEAD`) and the torso marker magenta (`HANDLE_COLOR_TORSO`); hands stay green, feet blue. The IK overlay additionally draws a null-guarded semi-transparent yellow aim line from the Head bone origin to the head marker (visual aid for the LookAt test). **No `.stk` format change.** Per docs/phase9_round7_feature_spec.md; verified with a 17-assertion headless test.
|