- Added functionality for skeleton IK bone switches, allowing users to dynamically change the bend direction of joints via a context menu. - Introduced facing profiles (LEFT, RIGHT, FORWARD) that modify the bend direction of limbs based on the selected profile. - Implemented body part z-ordering based on the facing profile, ensuring correct rendering order of limbs relative to the torso. - Added a coordinates display panel that shows the position and rotation of the Skeleton2D and its bones, as well as IK target positions. - Created a new script to generate a walk animation for the stickman rig, including keyframe tracks for various IK targets. - Updated documentation to reflect the new features and their specifications.
8.4 KiB
Phase 9 Task 3 — Feature: Coordinates Display
Overview
RIGGING.md Task 3: the test harness needs a show/hide readout on the right side of the screen displaying the current skeleton pose so bone/IK work can be inspected numerically:
- Skeleton2D position and rotation.
- Bone positions and rotations — Torso, Head, Left/RightUpperArm, Left/RightLowerArm, Left/RightUpperLeg, Left/RightLowerLeg.
- IK target positions — Head, Torso, Right_Hand, Left_Hand, Right_Leg, Left_Leg.
The display is easy to read on the right side of the screen and is toggled by a checkbox like the existing "Show Bones" / "Show IK Handles" toggles. It is a non-persistent debug aid: the toggle defaults ON per launch, and the panel is rebuilt from scratch each run.
1. Mechanism
The readout is a PanelContainer + RichTextLabel pair built entirely in code
(_build_coords_panel()), not authored in test_harness.tscn, and added as a child of the
harness root after the viewport container so it renders in front of the SubViewport. It
anchors to the top-right of the window below the top UI bar (offset_top = 40.0) with a fixed
width and auto-height, and the panel is MOUSE_FILTER_IGNORE so it never intercepts viewport
pan/zoom/drag input — the label itself stays interactive: text is selectable and copyable
(mouse drag + Ctrl+C, plus the built-in right-click copy menu).
Values are read live each frame in _process(delta), so the panel always mirrors the current
pose. All values are world-space (global_position / global_rotation). Rotation is
reported in degrees (rad_to_deg); positions are Vector2 formatted to 1 decimal place.
2. Implementation — scripts/test_harness.gd
2a. Constants
const COORDS_PANEL_WIDTH: float = 320.0— fixed panel width (also theoffset_leftextent).const COORD_BONE_PATHS: Array[String]— 10Skeleton2D-relative bone paths, in display order; the display name is the last path segment (path.get_file()):Torso,Torso/Head,Torso/LeftUpperArm,Torso/LeftUpperArm/LeftLowerArm,Torso/RightUpperArm,Torso/RightUpperArm/RightLowerArm,Torso/LeftUpperLeg,Torso/LeftUpperLeg/LeftLowerLeg,Torso/RightUpperLeg,Torso/RightUpperLeg/RightLowerLeg.
2b. State
var _coords_panel: PanelContainer = null— the readout container.var _coords_label: RichTextLabel = null— the monospace, selectable text readout.var _coord_bones: Dictionary = {}— bone display name →Bone2D, resolved per spawn.var _show_coords: bool = true— toggle state (default ON, harness-level, persists across spawns but not to disk).
2c. UI construction — _build_coords_panel()
Called from _build_ui() right after the viewport container. Builds:
_coords_panel(PanelContainer):PRESET_TOP_RIGHTanchors;offset_top = 40.0,offset_right = -8.0,offset_left = -COORDS_PANEL_WIDTH.grow_vertical = GROW_DIRECTION_END+grow_horizontal = GROW_DIRECTION_BEGIN(auto-height/width from label content; grows leftward so text never runs off the right edge of the screen).mouse_filter = MOUSE_FILTER_IGNOREon the panel (input passes through to the viewport; the label itself keeps mouse handling for text selection).StyleBoxFlatoverride "panel": bgColor(0,0,0,0.55), borderColor(1,1,1,0.12)width 1, corner radius 4, content margin 8.
_coords_label(RichTextLabel):selection_enabled = true,context_menu_enabled = true,fit_content = true,autowrap_mode = OFF,scroll_active = false,focus_mode = FOCUS_CLICK; monospace via aSystemFontoverride on thenormal_fonttheme item (Consolas,Menlo,DejaVu Sans Mono,Courier Newfallbacks) atnormal_font_size = 18._coords_paneladded to the harness root (add_child(_coords_panel)).
2d. Toggle — _on_show_coords_toggled(pressed)
CheckBox "Show Coords" (top bar HBox, after "Show IK Handles", before the status label),
button_pressed = true at build. Handler sets _show_coords = pressed and flips
_coords_panel.visible. No persistence to disk.
2e. Update loop — _process(_delta) → _update_coords_display()
_process(delta) early-outs when _show_coords is false or _coords_label is null; otherwise
calls _update_coords_display() each frame. _update_coords_display():
- If
_rigis null/invalid → label text"No rig loaded", return. "Skeleton2D"section: position + rotation (degrees) via_skeleton.global_position/_skeleton.global_rotation(guarded)."Bones"section: iteratesCOORD_BONE_PATHS, looking each bone up in_coord_bonesby display name; position + rotation (degrees). Missing/invalid bones skipped."IK Targets"section: iteratesIK_HANDLE_PATHSand reads each handle from_ik_handles(the same 6 handle set used by the drag handles); position only. Missing/invalid handles skipped.- Lines joined with
\ninto_coords_label.text; the text is only reassigned when the built string differs from the current one, so an active text selection survives idle frames (it refreshes while bones are actively moving). Formatting helpers:_fmt_vec2(v)(%8.1f, %8.1f),_fmt_deg(rad)%7.1f°(1 decimal place).
2f. Resolution & lifecycle
_resolve_rig_nodes()additionally calls_resolve_coord_bones()._resolve_coord_bones()clears_coord_bones, then for eachCOORD_BONE_PATHSentry resolves via_skeleton.get_node_or_null(NodePath(path)) as Bone2D; valid bones are keyed bypath.get_file(), missing ones emitpush_warning._free_current_rig()clears_coord_bones. The_show_coordstoggle itself persists across respawns (it lives at harness level, not per-rig).
3. Files modified
| File | Changes |
|---|---|
scripts/test_harness.gd |
COORDS_PANEL_WIDTH, COORD_BONE_PATHS, _coords_panel, _coords_label, _coord_bones, _show_coords, _build_coords_panel(), _on_show_coords_toggled(), _process(), _update_coords_display(), _resolve_coord_bones(), _fmt_vec2(), _fmt_deg(); "Show Coords" checkbox. |
docs/phase9_task3_coords_spec.md |
This file. |
AGENTS.md |
Test-harness section: "Phase 9 Task 3 coordinates display" bullet; test_harness.tscn scene description now lists the "Show Coords" toggle. |
RIGGING.md |
Mark Task 3 implemented. |
4. Edge cases
- No rig loaded:
_update_coords_display()shows"No rig loaded". - Missing bone nodes (foreign/partial rig):
_resolve_coord_bones()warns once, and the per-frame loop skips missing/invalid bones viais_instance_valid. - Toggle off:
_process()early-outs; panel hidden. - Re-spawn:
_coord_bonesre-resolved per spawn;_free_current_rig()clears it. The_show_coordstoggle stays at its user-set value across spawns (harness-level state). - Input blocking:
MOUSE_FILTER_IGNOREkeeps the panel from intercepting viewport pan/zoom/ drag. - World-space values:
global_position/global_rotationreflect the live pose regardless ofCamera2Dpan/zoom, so the readout tracks the actual rig transforms.
5. Design decisions
| # | Decision | Justification |
|---|---|---|
| D1 | Panel is code-built (_build_coords_panel()), not a scene node |
Mirrors the code-built harness top bar; keeps test_harness.tscn unchanged and the panel trivial to rebuild. |
| D2 | MOUSE_FILTER_IGNORE on the panel |
The readout must never block viewport interaction (pan/zoom/IK dragging). |
| D3 | World-space values + degrees | Matches what the debug overlay and drag handles use; degrees are the convention in the harness and the editor. |
| D4 | IK targets report position only | IK targets carry no meaningful rotation for this readout (they are aim/IK markers). |
| D5 | Toggle non-persistent, default ON | Debug aid only; no settings.json involvement, consistent with the other harness toggles. |
6. Test plan
- Parse check:
..\Godot_v4.7.1-stable_win64_console.exe . --headless --check-only --quit(user-requested; no errors). - Manual F6 check:
- Launch harness; readout visible on the top-right with
"No rig loaded". - Load
stickmen/basic.stk; Skeleton2D, 10 Bones, and 6 IK Target rows populate with numeric values. - Drag an IK handle → position/rotation values update live each frame.
- Uncheck "Show Coords" → panel hides; recheck → reappears with current values.
- Confirm the panel does not block middle-mouse pan / wheel zoom / IK drags beneath it.
- Launch harness; readout visible on the top-right with