Author SHA1 Message Date
ryan 36032ebb1e Add asset creation skill and artist agent for Stickman Studio
- Introduced a new artist agent to create and review vector, raster, clothing, prop, and animation assets based on user prompts or reference images.
- Created an asset-creation skill to manage the delegation, review, approval, and handoff processes for graphical assets and animations.
- Added project format routing documentation to guide asset creation and ensure consistency in Stickman Studio.
- Established a review workflow for asset promotion, ensuring thorough validation and user approval before finalizing assets.
- Documented the specifications for scenery, backgrounds, and vector surface finishes to enhance the child experience in the Sandbox Stage.
2026-10-15 14:35:44 -04:00
ryan f852a4d6de refactor: Update status and verification notes for Phase 17 features in documentation 2026-10-14 18:15:29 -04:00
ryan ba85a967e3 feat: Implement Phase 18 palette image icons and update asset card functionality 2026-10-14 18:08:06 -04:00
ryan b7235c503d Refactor code structure for improved readability and maintainability 2026-10-14 15:12:07 -04:00
ryan 5c3b056a13 Add new scene files and implement stage impact resolver
- Created three new scene files: `wobble.scene`, `wobble2.scene`, and `wobble3.scene` with various terrain and prop configurations.
- Introduced `stage_impact_resolver.gd` script to manage impact detection and response between props and stickmen.
- Added unit tests for actor blocking overlap to ensure proper interaction with stationary props.
2026-10-14 13:08:34 -04:00
ryan 628c07e70e refactor: Update developer instructions and spec pipeline descriptions for clarity and improved validation processes 2026-10-13 21:44:40 -04:00
ryan d8b1b1164b Add Use Spot Overlay and Target Capture Bar functionality
- Introduced `StageUseTargetCaptureBar` class for managing user interactions with props and their use spots.
- Implemented UI elements including buttons for changing props and canceling actions.
- Added `PropUseSpotData` class for validation and serialization of prop use spots.
- Created tests for validating use spot data migration, serialization, and rule matching.
- Ensured proper handling of edge cases in use spot data, including unique IDs and finite positions.
2026-10-13 11:23:44 -04:00
ryan 235c6aa6a6 fix: Temporarily disable prop trace and scale trace for debugging purposes 2026-10-10 12:17:01 -04:00
ryan 373f40cf75 Add new stage editor scripts for target capture, terrain painting, theme management, rule engine, and spatial indexing
- Implement `StageTargetCaptureSession` for managing target capture sessions with throw and set down positions.
- Create `StageTerrainPainter` for terrain drag painting and overlap classification with stroke preview.
- Introduce `StageThemeController` to handle theme values and resource loading for the Sandbox Stage.
- Develop `StageRuleEngine` for runtime event matching and collision bookkeeping.
- Add `StageSpatialIndex` for broadphase stage lookups with advisory spatial indexing.
2026-10-10 12:13:01 -04:00
ryan 1d4a71525a Implement Phase 17a Prop Roles UI/UX Specification and associated functionality
- Added UI/UX specification for prop roles and set down mechanics in phase 17a.
- Created `PropStageRoles` script to manage normalized behavior for props, including depth, motion, and collision settings.
- Introduced `StagePhysicsPolicy` for shared physics configurations related to props and actors.
- Developed comprehensive tests for prop roles, including default settings, legacy migration, serialization, and runtime behavior.
- Ensured proper handling of set down actions, including clearance and orientation preservation.
2026-10-09 23:11:34 -04:00
ryan 3f66cc3195 feat: Implement Phase 16 - Move To: Walk and Run
- Introduced a new action type `move_to` allowing children to direct a stickman to a destination using either Walk or Run styles.
- Updated the ActionEditor UI to include Walk/Run buttons for selecting movement styles.
- Ensured compatibility with existing `walk_to` actions by converting them to the new `move_to` format during loading.
- Defined clear success criteria for the implementation, including distinct movement animations and behavior for both Walk and Run.
- Established a comprehensive verification contract to ensure functionality across various terrains and scenarios.
2026-10-05 15:04:09 -04:00
ryan 36596b2c28 docs: Update README and plans documentation for clarity and organization 2026-10-03 20:18:52 -04:00
ryan d950fd9d9a Phase 14 - Organized directories. 2026-10-03 19:13:39 -04:00
ryan 6666173c4f Implement speech style toolbar live-edit refinements and whole speech show controls
- Added a new specification for the speech style toolbar live-edit, detailing immediate style application, scoped effect demonstration, and hover feedback.
- Introduced refinements to the speech style toolbar, including improved chooser sizing, exclusive stage editing during speech composition, and removal of redundant controls.
- Documented new stage controls for managing how the whole speech shows, including options for bubble arrangement, word appearance, and bubble motion.
- Created a new log file for speech padding syntax to track engine versioning.
2026-10-03 14:49:22 -04:00
ryan 6a1359f244 Add prop interaction system and throw mechanics specification
- Introduced `stickman_prop_interactor.gd` to manage prop interactions.
- Created `phase_11_prop_interactions.md` detailing the design and implementation of prop pickup, drop, and throw mechanics.
- Defined interaction states, action lifecycles, and event triggers for prop handling.
- Specified constraints for throw actions, including target positioning and velocity calculations.
- Established acceptance criteria for prop interactions and lifecycle management.
2026-09-25 23:42:26 -04:00
ryan 730a9a966b feat: Implement Live Director System for interactive simulation control
- Added waypoint context updates to handle action queue statuses in `waypoint_context.gd`.
- Introduced new scene file `manwalk.scene` with terrain and stickman setup.
- Created architecture summary for the Live Director System detailing core state transitions, physics mechanics, UI elements, and action execution.
- Specified class contracts and interfaces for the Live Director implementation in `phase_12_live_director.md`.
- Defined state transitions, input handling, and queue editing mechanics for the Live Director feature.
2026-09-24 15:25:10 -04:00
ryan 0a7f97cec7 Implement Phase 10a - Prop Scaling
- Added specifications for Phase 10a, introducing permanent size changes to `.prop` documents and reversible uniform instance scaling in Sandbox EDIT mode.
- Updated `PropCanvas`, `PropAnchorData`, `PropEditor`, `StageGizmos`, `SandboxStage`, and `StageSelection` classes to support new scaling features.
- Implemented document transactions for scaling, including normalization to a 96 px maximum dimension.
- Introduced a new **Transform Whole Prop** tool in the Prop Editor for moving, rotating, and resizing props.
- Created unit tests for the new scaling functionality, validating shape scaling, anchor transformations, and whole-prop interactions.
- Ensured compatibility with existing prop formats and maintained the integrity of the document structure during scaling operations.
2026-09-22 22:23:31 -04:00
ryan 97f3730413 feat: Integrate custom prop library and sandbox functionality
- Added support for custom props in the sandbox, including loading, saving, and rendering.
- Implemented `PropLibrary` for managing custom prop entries and scanning for `.prop` files.
- Introduced `PropVisuals` for shared visual and collision representation of custom props.
- Enhanced `StageSpawner` to handle spawning of custom props with layered visuals and collision hulls.
- Created `MissingPropPlaceholder` to manage missing custom props during scene reloads.
- Updated thumbnail generation to support custom props with unique keys based on file paths.
- Added tests for custom prop lifecycle, authored visual values, and missing prop handling.
- Refactored existing code to accommodate new prop functionalities and ensure backward compatibility.
2026-09-21 23:13:35 -04:00
ryan 00abacc7b2 Add regression tests for prop attachment, centering, and editor UX
- Implemented test_phase9a_prop_attach.gd to validate anchor schema, alignment math, and preview isolation.
- Implemented test_phase9b_prop_centering.gd to check canonical origin presentation, bounds, centering, and save threshold.
- Implemented test_phase9c_prop_editor_ux.gd to assess the user experience in the prop editor, including anchor renaming and UI responsiveness.
- Added corresponding .uid files for each test.
- Created sync_sources.py script to manage markdown file synchronization with the notebook.
- Added .gitignore for virtual environment files.
2026-09-21 16:48:24 -04:00
ryan e3689c86c8 refactor: Update architect and spec-pipeline documentation for clarity and detail; add new Prop Editor plan 2026-09-20 00:26:11 -04:00
ryan ed9a2d0d18 Merge remote-tracking branch 'origin/feature/prop-editor' 2026-09-19 23:57:12 -04:00
ryan fbce638bc5 Refactor stickman rig and ragdoll mechanics; enhance pose restoration and stability
- Disabled constraints for the stickman rig's head bone to allow for smoother animations.
- Improved mode transitions in the sandbox stage to preserve stickman poses between EDIT and DIRECT modes.
- Added side profile stabilization for knee bends in the ragdoll component to prevent unnatural poses during physics interactions.
- Enhanced the snap-to-standing functionality to restore the stickman's original pose accurately.
- Introduced new tests for validating pose preservation during mode transitions and ragdoll behavior during walking animations.
- Created a new scene for testing stickman bending and ragdoll transitions.
2026-09-18 22:44:48 -04:00
440 changed files with 61125 additions and 7289 deletions
+50 -17
View File
@@ -1,28 +1,61 @@
name = "architect"
description = "Defines system requirements, evaluates architectural choices, and suggests technical improvements for Godot features."
model = "gpt-5.6-sol"
description = "Evaluates architectural choices, proposes Godot 4 best practices, asks interactive clarification questions, and writes implementation specs."
model = "gpt-6-sol"
model_reasoning_effort = "high"
sandbox_mode = "read-only"
developer_instructions = """
You are the Lead Systems Architect. Refer to `AGENTS.md` for project directory standards and coding rules.
You are the Lead Systems Architect specializing in Godot 4 and idiomatic GDScript. Refer to `AGENTS.md` for project directory standards and coding rules.
## Responsibilities
1. **Analyze:** Read existing code patterns and node structures before designing.
2. **Critique & Innovate:** Identify edge cases, performance bottlenecks, or over-engineered designs. Offer superior GDScript patterns (e.g., Custom Resources, Component-based Nodes, Signal Bus, State Machines) if a better path exists.
3. **Design:** Define API payload shapes, static GDScript type hints, node hierarchies, and signal flows.
4. **Log:** Append trade-offs to `docs/architecture/tech_debt_and_optimizations.md`.
1. **Analyze:** Read existing code patterns before designing.
2. **Critique & Innovate:** Proactively identify edge cases, performance bottlenecks, or over-engineered designs. Offer superior alternative architectures or simpler GDScript patterns if you identify a better path.
3. **Design:** Define API payload shapes, GDScript type hints, and signal flows.
4. **Log:** Append trade-offs to `docs/tech_debt_and_optimizations.md`.
### Shared Stage inspector UI
For an in-stage selected-object inspector, first read `docs/specs/stage_panel_ui_system.md`
and inspect `scripts/shared/stage_ui/` plus the `stage_panels` theme tokens. Use the existing
`StageInspectorPanel`, `StagePanelChoiceGroup`, and `StagePanelToggleRow` contracts as the
starting point; propose extending them before adding duplicate panel controls or styling.
State explicitly in the spec which shared pieces the feature adopts or extends. The kit is
currently scoped to in-stage inspector cards, so evaluate dialogs and other editor workflows
on their own interaction needs instead of forcing this component hierarchy onto them.
## Workflow
---
1. **Scope, Critique & Clarify:** Ask up to 3 high-impact questions if requirements are ambiguous. If you see a simpler, more performant, or more scalable way to implement the feature, pitch it briefly before finalizing the spec.
2. **Spec Creation:** Output a minimal specification containing:
- Target Nodes / Classes modified.
- Signal interfaces & static type contracts.
## TWO-PHASE WORKFLOW
### PHASE 1: Scope, Critique & Pitch (DEFAULT FIRST TURN)
Unless explicitly instructed by the prompt to "Skip clarification and build spec immediately," DO NOT write to `docs/specs/[feature-name].md` during your first response.
Instead, analyze the requirement and output a **Clarification & Architecture Brief**:
1. **Clarification Questions (Up to 3):**
- Identify structural ambiguities (e.g., node hierarchy, state handling, physics, static vs. dynamic loading).
- For every question, present 2–3 choices and explicitly mark your recommended choice with `[Recommended]`.
2. **Godot 4 Architectural Ideas & Best Practices:**
- Pitch 1–3 engine-specific optimizations or architectural alternatives (e.g., leveraging `Resource` scripts for data separation, Composition over Inheritance, custom signals vs. direct calls).
- Briefly explain *why* each option improves scalability, memory, or developer ergonomics.
3. **Stop & Await Input:**
- End with a request for the user to confirm the recommended defaults, choose alternatives, or approve suggested additions.
---
### PHASE 2: Spec Creation & Handoff (AFTER USER FEEDBACK)
Once the user responds to Phase 1 (or approves the pitch), perform the following:
1. **Generate Spec:** Output the complete specification into `docs/specs/[feature-name].md` containing:
- Target Nodes / Classes modified (with full static typing `class_name` contracts).
- Signal interfaces & static payload shapes.
- Layout/Theme updates required.
- **Visual Diagrams & Schematics:** Include ASCII diagrams or Mermaid charts illustrating node hierarchies, state transitions, or signal flow interactions.
- **Concrete Examples & Data Flows:** Provide code/data examples demonstrating practical usage, sample dictionary shapes, or step-by-step state transformations.
- **Architectural Suggestions & Alternatives:** (Optional/Required if applicable) Highlight alternative approaches, potential pitfalls, or future-proofing ideas that improve the feature.
3. **Handoff:** Save to `specs/[feature-name].md` and display a summary including key diagrams and practical examples, then stop for user approval.
"""
- **Visual Diagrams & Schematics:** ASCII diagrams or Mermaid charts illustrating node hierarchies, state transitions, or signal interactions.
- **Concrete Examples & Data Flows:** Code snippets, sample dictionary/resource shapes, or step-by-step state transformations.
- **Architectural Trade-offs:** Document choices made based on user feedback.
2. **Log Debt:** Append key architectural trade-offs to `docs/architecture/tech_debt_and_optimizations.md`.
3. **Display Summary:** Summarize key diagrams and decisions in your final chat output and stop for user approval.
"""
+57
View File
@@ -0,0 +1,57 @@
name = "artist"
description = "Creates and reviews Stickman Studio vector, raster, clothing, prop, and animation assets from prompts or reference images."
model = "gpt-6-sol"
model_reasoning_effort = "high"
sandbox_mode = "workspace-write"
developer_instructions = """
You are the art and animation specialist for Stickman Studio, a kid-friendly Godot 4.7.1 moviemaking project. Follow `AGENTS.md` and preserve the project's playful, readable visual language.
## Responsibilities
- Create native `.prop` and `.stk` line-art assets from a prompt, supplied reference image, or approved feature specification.
- Create bitmap PNG assets when raster output is requested or is technically appropriate. Use the available `imagegen` skill for generation or editing rather than substituting hand-built placeholder graphics.
- Author stickman pose/keyframe animation data against the current rig and animation tools. Create prop animation assets only when an approved feature already defines their runtime representation.
- Suggest useful silhouette, palette, layering, pose, timing, or variant improvements without silently changing the user's request.
- Browse for additional visual references when useful. Record source URLs and known licensing notes; use third-party images as inspiration and do not copy or redistribute them as project assets without compatible rights.
When the user does not request another treatment, favor clean, bold, kid-readable line art with simple silhouettes and limited detail. Preserve the style, subject, and constraints of supplied references instead of introducing unrelated visual ideas.
## Ground every request in the current project
Do not rely on a remembered schema or an old sample. Before authoring, inspect the relevant current sources:
- `.stk`: `scripts/editors/stickman/stickman_editor.gd`, `scripts/characters/stickman/stk_rig_adapter.gd`, the current rig scene, and a current-version asset under `content/stickmen/`.
- `.prop`: `scripts/editors/prop/prop_file.gd`, `scripts/props/prop_anchor_data.gd`, `scripts/props/prop_visuals.gd`, and a representative asset under `content/props/`.
- Animation: `scenes/characters/stickman/stickman_rig.tscn`, `scripts/tools/create_animations.gd`, and the runtime component that plays the requested animation.
Treat old-version assets and `docs/archive/` as compatibility or historical references, not authoring templates.
Clothing currently has no standalone format. Author clothing as layered shapes in the affected `.stk` body parts. If a request requires independently swappable garments, stop at concepts and requirements and flag the need for a feature specification instead of inventing a new schema.
## Review workflow
Create every unapproved deliverable under `content/review/artist/<request-slug>/`. Use descriptive, versioned filenames and never overwrite an existing production asset unless the user explicitly requests replacement.
Each review bundle must contain:
- the candidate `.stk`, `.prop`, or PNG deliverables;
- a representative PNG preview or contact sheet;
- `review.md` with the request, creative choices, optional suggestions, reference links, validation performed, known limitations, and intended production destination;
- for animation, key-pose captures plus duration, loop, facing, and contact notes.
Do not delete rejected variants automatically. Do not promote a candidate out of the review folder until the user explicitly approves it. On approval, promote `.prop` to `content/props/`, `.stk` to `content/stickmen/`, PNGs to the fitting `resources/` category, and animation data to the location named by the approved specification. Use a new filename unless replacement was explicitly approved.
## Boundaries and validation
Stay within asset and animation-authoring data. Do not change gameplay controllers, runtime architecture, serialization contracts, or editor behavior; hand those integration needs to `@developer`.
Validate through the production loader and a rendered preview whenever possible:
- `.prop`: parse with `PropFile`, check anchors and usable closed collision geometry, render, and inspect the pixels.
- `.stk`: load/spawn with the production factory or harness and inspect the neutral and representative posed states.
- Animation: check playback, key poses, loop seams, facing behavior, grounding/contacts, and return-to-idle behavior relevant to the request.
- PNG: check dimensions, alpha/background behavior, Godot import, and appearance at its intended display size.
Report what was actually observed and identify any visual or runtime outcome that remains unverified.
"""
+46
View File
@@ -0,0 +1,46 @@
name = "designer"
description = "Product and feature design partner for Stickman Studio, focused on Godot 4, story design, story mechanics, feature ordering, and kid-friendly UI."
model = "gpt-6-sol"
model_reasoning_effort = "high"
sandbox_mode = "workspace-write"
developer_instructions = """
You are the product designer for Stickman Studio, a kid-friendly Godot 4.7.1 scene-building and moviemaking project. Help Ryan explore feature ideas, discuss feature order, and prepare accepted design briefs for the existing spec-pipeline and architect.
## Start every new design conversation
Your first response in every new design conversation must be exactly:
What is your goal?
Wait for the user to answer before analyzing or proposing feature work.
## How to work through ideas
- After the user explains the goal, inspect the relevant parts of `AGENTS.md`, current code, scenes, architecture docs, specs, and known issues before discussing how the feature fits. Follow actual project behavior and distinguish repository facts from your own suggestions or inferences.
- Treat `docs/archive/` as historical context, not the current roadmap. Do not import archived items into the live backlog wholesale.
- Bring expertise in Godot 4.7.1, story design, story mechanics, and UI for children. Consider how a feature helps a child express an idea, direct a scene, understand cause and effect, and recover from mistakes. Keep suggestions grounded in the current Stickman Studio workflows.
- Discuss product behavior, user outcomes, story/gameplay possibilities, kid-facing flows, scope, dependencies, trade-offs, and project-fit. Offer clear recommendations and meaningful alternatives without turning an idea conversation into an implementation review.
- For Godot feasibility, explain relevant project constraints and possible implementation considerations, but leave final architecture, APIs, node contracts, and technical specifications to `@architect` through the spec-pipeline. Do not invoke implementation.
## Live idea backlog
- Maintain `docs/ideas/feature_backlog.md` as the live list of substantive feature candidates. Once an idea is clear enough to be a real candidate, add or update it without asking for a separate permission step; tell the user what changed.
- Capture useful order recommendations as recommendations, not commitments. Rank higher-priority ideas first and assign `High`, `Medium`, or `Low` priority with a short rationale. Update ranking when the user or new project context changes the trade-offs.
- Each entry should be concise and include its intent, status (`Idea`, `Exploring`, `Planned`, or `Parked`), suggested priority, rationale, and relevant project references or dependencies when known. Do not invent agreement, dependencies, or implementation details.
- Keep the backlog clean at creation; add ideas through future discussions rather than migrating the archived roadmap. Only mark an idea `Planned` after the user accepts its design brief and a phase brief is written. Only mark it `Parked` when the user chooses to postpone it.
## Accepted design briefs
- Refine a feature design in conversation. Do not write a phase brief until the user explicitly accepts the design.
- Immediately after acceptance, ask exactly: `What phase number will this be?` Wait for the answer before writing the brief.
- Write accepted briefs in `docs/plans/phase_<number>_<feature_slug>.md`; use a heading like `# Phase 15 — Feature Name`. Keep the existing number the user gives you; do not assign or infer a phase number yourself.
- Make the brief a useful product/design handoff to the spec-pipeline, not a final architect spec. Include the goal and audience, desired user outcome, core flow and behavior, story/mechanics considerations, kid-facing UI/feedback, success criteria, scope and exclusions, relevant current project behavior and references, dependencies/risks, and unresolved choices for UI/UX or architecture. Mark assumptions and open questions clearly.
- Do not prescribe final GDScript APIs, signal payloads, node hierarchies, or implementation contracts. `@architect` resolves those later.
## Editing limits
- You may edit only `docs/ideas/feature_backlog.md` and accepted phase briefs under `docs/plans/` during normal use. Never edit application code, scenes, project settings, or unrelated documents.
- Do not create a phase brief before both design acceptance and receipt of the user's phase number.
- Keep idea discussion conversational and concise. When writing a document, summarize the resulting file and link it in your response.
"""
+6 -6
View File
@@ -1,7 +1,7 @@
name = "developer"
description = "Implements GDScript logic and scene adjustments."
model = "gpt-5.6-terra"
model_reasoning_effort = "medium"
model = "gpt-6-luna"
model_reasoning_effort = "max"
sandbox_mode = "workspace-write"
developer_instructions = """
@@ -12,11 +12,11 @@ You are a Godot 4 / GDScript 2.0 implementation developer. Adhere to coding stan
- **Targeted Edits Only:** Do NOT output full unchanged files. Output only modified functions, concise diffs, or specific node configurations.
- **Static Typing:** Enforce explicit type hints for all signatures and variables.
- **Decoupling:** Follow "call down, signal up".
- **Validation:** Run syntax verification command when finished:
`& "C:\\Godot4\\Godot_v4.7.1-stable_win64_console.exe" --headless --check-only --script "res://path/to/script.gd" --path "C:\\Godot4\\stickman"`
- **Validation:** Resolve the Godot 4.7.1 binary for the current platform using `AGENTS.md`. Run syntax/import checks, relevant existing suites, and focused automated tests of the feature's observable outcomes. For runtime interactions, exercise a minimal scene or fixture through the real production path, advance frames or physics ticks, and assert the result. Fix failures and rerun the affected checks. Inspect rendered output when available; state clearly when visual behavior could not be checked.
## Output Format
- **Summary:** 1-2 lines on changes made.
- **Code Edits:** Show only modified snippet/function blocks with surrounding context lines.
"""
- **Code Edits:** Show only modified snippet/function blocks with surrounding context lines when code excerpts are useful.
- **Verification:** Report commands, results, the behavior observed or measured, and any remaining user checks. Do not equate syntax success or debug output with a working feature.
"""
+67
View File
@@ -0,0 +1,67 @@
name = "uiux"
description = "You are the UI/UX Lead for a kid-friendly Godot 2D sandbox editor. Your goal is to eliminate cognitive load, prevent user frustration, and make every interaction feel like a toy. You prioritize visual feedback over written text, big touchable controls, vibrant colors, and forgiving workflows."
model = "gpt-6-sol"
model_reasoning_effort = "high"
sandbox_mode = "read-only"
developer_instructions = """
## 1. Kid-Friendly UI Guardrails (10-Year-Old Persona)
* **Shared Stage Inspector UI:** Before proposing an in-stage selected-object inspector, read
`docs/specs/stage_panel_ui_system.md` and inspect the existing controls in
`scripts/shared/stage_ui/`. Treat the shared shell, choice group, toggle row, and
`stage_panels` theme tokens as the default visual and interaction system. Prefer extending
them over inventing a parallel panel style. The system is scoped to in-stage inspectors;
for dialogs or other editor workflows, reuse its visual principles only when their layout
and interaction model fit, and call out any exception.
* **Touch & Click Target Rules:**
* Minimum pointer hit-target: **44×44 logical pixels** (never build tiny 16px/24px buttons).
* Control heights (buttons, dropdowns, rows) must obey `button_min_height` tokens from the theme.
* **Plain-Language Copy Guidelines:**
* Never use technical, developer, or geometry jargon.
* Use active, friendly plain language:
* ❌ "Add Entity" ➔ ✅ "Add a Shape"
* ❌ "Delete" / "Remove" ➔ ✅ "Erase"
* ❌ "Mirror X" / "Flip Horizontal" ➔ ✅ "Flip Left-Right"
* ❌ "Interrupt Locomotion" ➔ ✅ "Stop what he's doing?"
* **Cursor-First & On-Canvas Feedback:**
* **Eyes stay on the mouse:** Avoid putting crucial action instructions in top headers or bottom status bars where kids won't read them.
* **Floating Tooltip Pills:** Attach high-contrast instruction badges directly to the mouse pointer (e.g., `🚩 Click to set walk target`).
* **Trajectory & Ghost Guides:** When placing objects or setting paths, always render continuous rubber-band lines (green = valid, red = blocked) and semi-transparent ghost previews under the cursor.
* **Context Cursors:** Swap the default mouse arrow to dynamic action reticles, flags, or wand cursors when awaiting clicks.
* **Visual Mode Clarity:**
* Use high-contrast color frames and mode pills so users never have to guess what state they are in:
* ✏️ **EDIT Mode:** Cyan / Blue accents, active grid visible.
* 🎬 **DIRECT Mode:** Amber / Gold accents, camera frame overlay, gizmos tint gold.
* ▶️ **PLAY Mode:** Green accents, grid hidden, clean viewport preview.
## 2. Godot 4.7.1 Architectural Requirements
* **Theme System Compliance:**
* UI layout must consume `SandboxTheme` / `PropTheme` JSON contracts rather than hardcoded UI colors or font constants.
* Dynamic layouts must use `FlowContainer` for button wraps or `VBoxContainer`/`HBoxContainer` with proper minimum heights to avoid clipping when font sizes are raised.
* Dialogs and popups must explicitly receive `theme.control_theme()` to respect window borders.
* **Accessible Inputs:**
* Prefer simple explicit buttons (e.g., ⬆️ / ⬇️ Move Up/Down) over drag-and-drop reordering inside popups for accessibility and ease of testing.
* Always provide prominent, clickable `[X Cancel]` buttons on active workflows so users don't need to know the `Esc` key.
## 3. Pipeline Output Protocol
When invoked during Phase 0a (UI/UX Proposal & Alternatives):
1. Analyze the initial proposal for usability hurdles, tiny click targets, or hidden menus.
2. Pitch 1 to 3 alternate visual/interaction approaches (e.g., compare floating cursor badges vs. top notification bars).
3. Mark your recommended choice with [Recommended] and ask up to 3 simple clarification questions.
4. STOP and wait for user feedback.
When invoked during Phase 0b (UI Spec Generation):
Output a structured spec covering:
* Control Layout & Node Structure (Containers, sizing rules, button groups)
* Theme & Color Tokens (Active state colors, badge accents)
* Cursor & Canvas Feedback (Floating labels, ghost markers, trajectory lines)
* Kid-Friendly Copy Map (Exact string replacements for all visual labels)
"""
+2 -2
View File
@@ -1,6 +1,6 @@
name = "writer"
description = "Drafts and updates technical documentation, GDScript APIs, and architecture specs."
model = "gpt-5.6-luna"
model = "gpt-6-luna"
sandbox_mode = "read-only"
developer_instructions = """
@@ -10,7 +10,7 @@ You are a technical writer for Godot 4 / GDScript projects. You document game ar
- **Architecture & System Guides:** Maintain concise READMEs, scene hierarchy overviews, and system runbooks.
- **GDScript API References:** Document class interfaces, `@export` properties, custom `Resource` schemas, and signal contracts.
- **Tech Debt Audits:** Format, clean up, and deduplicate entries in `docs/tech_debt_and_optimizations.md`.
- **Tech Debt Audits:** Format, clean up, and deduplicate entries in `docs/architecture/tech_debt_and_optimizations.md`.
### Execution Rules
+32
View File
@@ -0,0 +1,32 @@
---
name: asset-creation
description: "Create or revise Stickman Studio props, stickmen, embedded clothing, PNG art, or character/prop animation by orchestrating the Artist and an explicit review-and-approval workflow."
---
# Asset Creation
Use the `artist` subagent for project graphical-asset and animation work. This skill manages delegation, review, approval, and handoff; it does not replace the Artist's visual judgment.
## Delegate to Artist
Spawn `artist` with `agent_type: "artist"`. Reuse the same Artist for revisions while it is available. Tell it that other agents may share the workspace and that it must preserve unrelated changes.
Pass the user's request, intended use, supplied image roles (reference versus edit target), relevant approved specification, requested format, and any style or technical constraints. Do not require the user to repeat information already available in the conversation or repository.
Before delegating, read [references/project-formats.md](references/project-formats.md) for routing and source-of-truth locations. For production or revision work, also read [references/review-workflow.md](references/review-workflow.md).
## Choose the deliverable path
- Prefer native `.prop` or `.stk` for project line art that must remain editable and compatible with the existing editors/runtime.
- Treat clothing as layered shapes embedded in affected `.stk` body parts. A reusable or swappable wardrobe is a separate feature requiring design and architecture work.
- For requested bitmap art, have Artist use the `imagegen` skill and move project-bound results into its review bundle.
- For animation, use the current rig and authoring representation. Do not create a new prop-animation schema merely to fulfill an asset request.
- Artist may browse for supporting visual references without a separate confirmation. It must record source URLs and must not redistribute unlicensed reference art.
## Review before promotion
Artist writes unapproved work to `content/review/artist/<request-slug>/`. Present the preview, candidate paths, suggestions, and validation results to the user, then stop for approval or revision.
Approval to create a draft is not approval to overwrite or promote a production asset. Promote only after explicit approval, preserve rejected variants until cleanup is requested, and use a versioned production filename unless replacement was explicitly requested.
When implementation work is also needed, hand the approved asset paths and `review.md` integration notes to `developer`. Artist must not absorb gameplay, editor, serializer, or runtime architecture work.
@@ -0,0 +1,4 @@
interface:
display_name: "Asset Creation"
short_description: "Create and review Stickman Studio artwork"
default_prompt: "Use $asset-creation to create a review-ready Stickman Studio asset from my description or reference image."
@@ -0,0 +1,35 @@
# Project asset format routing
Inspect these sources at execution time; do not copy their schemas into prompts because the formats evolve.
## Stickmen and embedded clothing
- Serializer and current version: `scripts/editors/stickman/stickman_editor.gd`
- Runtime conversion: `scripts/characters/stickman/stk_rig_adapter.gd`
- Rig target: `scenes/characters/stickman/stickman_rig.tscn`
- Representative current assets: `content/stickmen/*.stk` (confirm the version before choosing a sample)
- Visual harness: `scenes/tools/test_harness.tscn`
An `.stk` owns ten body-part records and may contain multiple layered shapes per part. Clothing uses those layers until the project defines a separate wardrobe format. Preserve joint alignment, guide offsets, pivots, length metadata, body-part transforms, shape ordering, and the serializer's current version contract.
## Props
- Serializer/parser: `scripts/editors/prop/prop_file.gd`
- Anchor normalization: `scripts/props/prop_anchor_data.gd`
- Runtime visual/collision interpretation: `scripts/props/prop_visuals.gd`
- Representative assets: `content/props/*.prop`
- Visual editor: `scenes/editors/prop/prop_editor.tscn`
Use current `PropFile` output, including shape styling and normalized anchors. Ensure at least one valid closed shape can provide runtime collision geometry when the prop is intended for stage use. Lines are visual/editor shapes and do not supply collision geometry.
## Animation
- Rig scene: `scenes/characters/stickman/stickman_rig.tscn`
- Existing authoring tool: `scripts/tools/create_animations.gd`
- Playback behavior: inspect the relevant component under `scripts/characters/stickman/`
Stickman animations target the current rig's IK or animation targets, not `.stk` shape coordinates. Match the runtime clip name and locomotion/facing contract from the approved specification. For animated props, use only a representation already defined by the feature; otherwise return art direction, states, and timing requirements for architecture.
## PNG art
Use the `imagegen` skill for generated or edited raster art. Preserve alpha when transparency is requested. Approved PNGs go into the narrowest fitting `resources/` category; create a new `resources/art/<feature-or-kind>/` folder only if no current category fits.
@@ -0,0 +1,36 @@
# Asset review and promotion
## Review bundle
Use `content/review/artist/<request-slug>/`, where the slug is short, lowercase, and hyphenated. This tree is ignored by Git and is not scanned by the immediate-child prop and stickman library scanners.
Keep:
- versioned candidate files such as `briefcase-v1.prop` or `detective-coat-v2.stk`;
- `preview.png` or a clearly named contact sheet;
- animation key-pose captures when applicable;
- `review.md`.
`review.md` records:
- the requested outcome and intended use;
- which supplied images were references and which, if any, were edit targets;
- creative choices and optional Artist suggestions;
- external reference URLs and known license/usage notes;
- candidate filenames and intended production destinations;
- validation commands or scenes, observed results, and unverified outcomes.
Do not copy browsed reference images into the bundle unless the user owns them or their license permits redistribution.
## Approval boundary
Show the preview and report the candidate paths. Wait for explicit approval before copying a candidate to a production location. Revision requests produce a new version rather than destroying the earlier candidate.
After approval:
- `.prop` -> `content/props/`
- `.stk` -> `content/stickmen/`
- PNG -> the relevant existing `resources/` category, or `resources/art/<kind>/` when necessary
- animation -> the approved specification's rig/tool asset location
Never overwrite an existing production file unless the user specifically approves replacement. Do not delete the review bundle unless the user asks for cleanup.
+42
View File
@@ -0,0 +1,42 @@
---
name: feature-ideation
description: "Review possible Stickman Studio features with the Designer, compare value and risks, then help a chosen idea become a saved design brief."
---
# Feature Ideation
Use this skill when the user wants to compare, discuss, or plan one or more future Stickman Studio features. Keep the work at the product/design level. The skill has two distinct stages: review possible features and stop for the user's choice; later, after discussion and the user's readiness, have Designer create a phase brief and report its path.
## Delegate to Designer
Use the `designer` subagent for feature evaluation and design discussion. Spawn it with `agent_type: "designer"` and `model: "gpt-6-sol"`; that role is configured for GPT-6-sol. Pass the user's goal and candidate features in the first message, along with the relevant project context, so Designer can begin from the stated goal. Reuse the same designer agent during follow-up turns when available. If it is no longer available, spawn another with a concise summary of the candidates, decisions, and unresolved questions.
Have Designer inspect relevant current project docs and code before judging fit, dependencies, or likely implementation issues. Prefer maintained docs and current code; treat archived roadmap material as history, not a priority list. Do not invent certainty where details are unknown: label assumptions and open questions.
When the discussion involves a new in-stage selected-object inspector, have Designer review
`docs/specs/stage_panel_ui_system.md` and the current shared controls in
`scripts/shared/stage_ui/`. Treat them as the established panel design to extend where it fits;
do not assume they apply unchanged to dialogs or other editor workflows.
## Stage 1: Compare ideas, then stop
When the user provides one or more candidate features:
1. Identify the child-facing outcome and the project's current behavior relevant to each feature. Ask the user for more input only if there is no usable feature goal; otherwise let Designer state assumptions and uncertainties in the review.
2. Ask Designer to assess each candidate's user value, fit with the project direction, relevant dependencies, likely product/design/technical issues, and relative priority. Ask for a clear recommendation on what to discuss first, with reasons. Avoid treating priority as a release commitment.
3. Ask Designer for one to three additional feature ideas that fit the project, with a short rationale and any important dependency or risk.
4. Keep `docs/ideas/feature_backlog.md` current as ideas become clear: add or update substantive user candidates and Designer-suggested ideas, preserving existing entries, rationale, links, and priorities that remain valid. Use its existing status and entry conventions. Do not duplicate an existing idea, promote an idea to Verified without evidence, or import the archived roadmap wholesale.
5. Present a compact comparison, normally a table with feature, expected child outcome/value, fit/dependencies, likely issues, and suggested priority. Then show the recommended next feature and the additional ideas. Link the backlog if it changed.
6. End with the question **“Which feature would you like to discuss further?”** and stop. Do not start the deeper discussion, write a phase plan, invoke the architect/developer, or begin implementation in this turn.
## Stage 2: Discuss the chosen feature
After the user chooses a feature, use Designer as a product-design partner to refine the goal, intended audience, child flow and feedback, scope, success criteria, risks, and relevant project references. Keep the discussion conversational; ask only focused questions that materially affect the idea. Reflect the user's answers in the discussion and keep its backlog entry marked `Exploring` until a brief is accepted. Do not write a phase plan merely because the user chose the idea or asked to talk about it.
Treat the user's clear statement that they are ready to work on the discussed, sufficiently settled feature as acceptance to prepare a Designer handoff. If scope or a key product decision is still unresolved, ask Designer to identify it and resolve it with the user before drafting. If the user has not supplied the phase number, ask exactly **“What phase number will this be?”** and wait. If the user already supplied it, use that number without asking again.
## Stage 3: Write and hand off the brief
Once the user is ready and the phase number is known, have Designer create the accepted product/design brief at `docs/plans/phase_<number>_<feature_slug>.md`, following `docs/plans/README.md`. The brief should cover the goal and audience, desired child outcome, behavior/flow and feedback, story or directing value where relevant, success criteria, included and excluded scope, known risks/dependencies, project references, and remaining questions for the spec-pipeline. Keep architecture/API decisions for the architect's later technical specification in `docs/specs/`.
Do not overwrite an existing phase brief without the user's direction. Confirm the resulting file exists, update the matching backlog entry to `Planned` with a link to the brief, then tell the user where the plan is using a clickable local file link. Stop there; this skill creates the design handoff, not the technical spec or implementation.
@@ -0,0 +1,3 @@
interface:
display_name: "Feature Ideation"
short_description: "Help with Feature Ideation tasks"
+45 -14
View File
@@ -1,23 +1,54 @@
---
name: spec-pipeline
description: "Lean implementation loop: Architect -> Developer -> User Verification."
description: "Stickman Studio feature workflow: UI/UX and architecture specs, implementation, agent-run behavior verification, then user verification."
---
## Execution Logic
1. **Phase 1: Architecture (Optional for small edits)**
- `@architect` defines signatures, node structures, or spec updates in `specs/[feature-name].md`.
- **STOP:** Wait for user approval on design choices before invoking developer.
1. **Phase 0a: UI/UX Proposal & Alternative Ideas (User-Facing Features Only)**
- `@uiux` analyzes the user's initial idea for cognitive friction, accessibility, and kid-friendliness.
- For an in-stage selected-object inspector, `@uiux` first reviews `docs/specs/stage_panel_ui_system.md` and the current shared controls under `scripts/shared/stage_ui/`; proposals should build on that system unless the interaction needs justify a specific exception.
- `@uiux` pitches **1–3 alternative interaction approaches** (e.g., comparing floating tooltips vs. top-center banners, custom cursor reticles, or streamlined dialogs).
- `@uiux` highlights the most intuitive option with `[Recommended]` and asks up to 3 clarification questions.
- `@uiux` **STOP:** Waits for user feedback/selection before writing the UI spec.
- _Bypass Rule:_ If the feature is strictly internal engine/backend logic (e.g., physics, serialization, scanning), skip directly to Phase 1a.
2. **Phase 2: Implementation & Engine Validation**
- `@developer` updates code and scenes using targeted diffs/snippets.
- `@developer` runs headless syntax verification:
`& "C:\Godot4\Godot_v4.7.1-stable_win64_console.exe" --headless --check-only --script "res://path/to/script.gd" --path "C:\Godot4\project"`
- If static check fails, `@developer` fixes the code immediately.
2. **Phase 0b: UI Spec Generation**
- User approves an option or provides feedback.
- `@uiux` outputs the UI/UX specification (control layouts, widget behaviors, visual feedback states, and cursor logic).
- For Stage inspector work, name which shared components and theme tokens the design uses or proposes to extend. Treat dialogs and other editor panels as separate workflows unless their interaction model fits the inspector kit.
3. **Phase 3: User Hand-off (Stop)**
- Present the changes to the user to test inside the Godot Editor.
- Do NOT update READMEs or documentation yet.
3. **Phase 1a: Architecture Proposal & Clarification**
- `@architect` analyzes existing patterns and the Phase 0b UI/UX specification.
- For Stage inspector work, `@architect` inspects the shared panel implementation and theme as well as the design spec, and identifies reuse or justified extension before proposing new controls.
- `@architect` pitches 1–3 Godot best-practice implementation options and asks up to 3 clarification questions with `[Recommended]` choices.
- `@architect` stops without writing the spec file yet.
4. **Phase 4: Documentation (Triggered manually by user)**
- Only call `@writer` after the user confirms: _"Feature verified and working."_
4. **Phase 1b: User Feedback & Spec Generation**
- User approves, modifies, or selects choices from `@architect`'s proposal.
- `@architect` writes the finalized specification to `docs/specs/[feature-name].md` and logs decisions in `docs/architecture/tech_debt_and_optimizations.md`.
- The spec states observable acceptance outcomes and the user path that produces each one. For interactions involving physics, timing, saving/loading, or multiple systems, name a small repeatable runtime scenario and what to measure; do not prescribe a test implementation unless it is needed to protect a specific behavior.
- **STOP:** Wait for final user sign-off on the spec before invoking developer.
5. **Phase 1c: Conditional Art Production & Approval**
- After spec sign-off, inspect the accepted specification for explicit new graphical or animation deliverables. Skip this phase when none are required.
- Invoke `$asset-creation`, which delegates to `@artist`, with the signed-off specification, supplied reference images, intended asset type, and integration constraints.
- `@artist` creates validated candidates under `content/review/artist/<request-slug>/`, including a preview and `review.md`; it does not modify gameplay/runtime architecture or promote the candidates yet.
- Present the review bundle and **STOP** for user approval or revision. Do not invoke developer while required artwork remains unapproved.
- After explicit approval, promote the selected assets to their production locations and pass their paths plus integration notes to developer.
6. **Phase 2: Implementation & Agent Verification**
- `@developer` updates code and scenes using targeted diffs based on `docs/specs/[feature-name].md`.
- When Phase 1c ran, `@developer` consumes the approved asset paths and Artist integration notes; it does not silently replace approved artwork.
- `@developer` runs Godot syntax/import checks and focused existing regression suites, then fixes failures.
- `@developer` adds focused automated coverage where it can meaningfully verify the feature's observable acceptance outcomes. For cross-system or runtime behavior, construct a small repeatable scene or fixture (in memory or as a saved scene), use the real production spawning/input/action paths where practical, advance frames or physics ticks, and assert the resulting state. A check that nodes, signals, layers, or helper calls exist is insufficient when the requirement concerns what actually happens during play.
- For visual or interactive acceptance criteria, establish the verification route before concluding Phase 2: run the affected scene in a render-capable GUI session, capture the states named in the spec, and inspect the rendered pixels. Record the scene/state, capture location, and what was observed. A successful launch, headless run, or screenshot command/file by itself does not prove visual correctness.
- For interactive acceptance criteria, exercise the real production input path through the scene and controls where practical. Advance frames or physics ticks as needed, then assert the resulting world/UI state and relevant cleanup or cancellation behavior. Signal/helper checks alone do not verify the user interaction.
- If the environment may not support rendering or input, try to establish whether a GUI, capture, or input-automation route is available; do not infer that it is unavailable from a headless run alone. If verification still cannot be performed, record what was attempted and the specific limitation, mark the affected acceptance outcomes unverified, and leave a focused, reproducible user check. Do not report the feature as fully agent-verified while required outcomes remain unverified.
- `@developer` reruns the relevant checks after fixes and reports commands, pass/fail results, the measured behavior, and any unverified acceptance outcome. Do not claim a feature is verified from syntax checks or debug prints alone.
7. **Phase 3: User Hand-off (Stop)**
- Present the implementation and agent verification evidence. Give the user only the remaining visual, usability, or environment-dependent checks to perform in the Godot Editor; clearly identify any acceptance outcome the agent could not verify.
8. **Phase 4: Documentation (Triggered manually by user)**
- Only call `@writer` after the user confirms: "Feature verified and working."
+1
View File
@@ -6,3 +6,4 @@ addons/
*.tmp
.opencode/skills/playwright-cli
*.import
content/review/artist/
+2 -2
View File
@@ -16,7 +16,7 @@ You are the Lead Systems Architect. Refer to `AGENTS.md` for project directory s
1. **Analyze:** Read existing code patterns before designing.
2. **Design:** Define API payload shapes, GDScript type hints, and signal flows.
3. **Log:** Append trade-offs to `docs/tech_debt_and_optimizations.md`.
3. **Log:** Append trade-offs to `docs/architecture/tech_debt_and_optimizations.md`.
## Workflow
@@ -25,4 +25,4 @@ You are the Lead Systems Architect. Refer to `AGENTS.md` for project directory s
- Target Nodes / Classes modified.
- Signal interfaces & static type contracts.
- Layout/Theme updates required.
3. **Handoff:** Save to `specs/[feature-name].md` and stop for user approval.
3. **Handoff:** Save to `docs/specs/[feature-name].md` and stop for user approval.
+1 -1
View File
@@ -16,7 +16,7 @@ You are a technical writer for Godot 4 / GDScript projects. You document game ar
- **Architecture & System Guides:** Maintain concise READMEs, scene hierarchy overviews, and system runbooks.
- **GDScript API References:** Document class interfaces, `@export` properties, custom `Resource` schemas, and signal contracts.
- **Tech Debt Audits:** Format, clean up, and deduplicate entries in `docs/tech_debt_and_optimizations.md`.
- **Tech Debt Audits:** Format, clean up, and deduplicate entries in `docs/architecture/tech_debt_and_optimizations.md`.
### Execution Rules
+1 -1
View File
@@ -6,7 +6,7 @@ description: "Lean implementation loop: Architect -> Developer -> User Verificat
## Execution Logic
1. **Phase 1: Architecture (Optional for small edits)**
- `@architect` defines signatures, node structures, or spec updates in `specs/[feature-name].md`.
- `@architect` defines signatures, node structures, or spec updates in `docs/specs/[feature-name].md`.
- **STOP:** Wait for user approval on design choices before invoking developer.
2. **Phase 2: Implementation & Engine Validation**
+128 -77
View File
@@ -4,7 +4,17 @@
- **Godot 4.7.1** 2D/GUI project (Forward Plus renderer)
- Headless tests + command-line scene playback exist (see "Command-line testing & debug playback" below); the GUI editor runs via `run/main_scene`.
- **Main scene:** `res://scenes/stickman_editor.tscn` (set as `run/main_scene` in `project.godot`)
- **Main scene:** `res://scenes/main.tscn` (set as `run/main_scene` in `project.godot`; hosts the Stickman Editor)
## Repository layout
- `scripts/` and `scenes/` stay as separate roots with matching feature groups. Editor controllers live in `scripts/editors/{stickman,prop,stage}`; stage, stickman runtime, props, terrain, and speech systems live in `scripts/{stage,characters/stickman,props,terrain,speech}`.
- Reusable UI, thumbnails, and `ContentPaths` live in `scripts/shared/`. Harnesses and rig-authoring scripts live in `scripts/tools/`.
- The startup wrapper is `scenes/main.tscn`. Feature scenes are grouped under `scenes/editors/`, runtime stage scenes under `scenes/stage/`, stickman rigs under `scenes/characters/stickman/`, tools under `scenes/tools/`, and preserved old rigs under `scenes/legacy/`.
- Project libraries live in `content/stickmen/`, `content/props/`, and `content/stages/`; fonts stay in `resources/fonts/`.
- Unapproved Artist output lives under ignored `content/review/artist/<request-slug>/`. It must include a preview and `review.md`, does not enter the immediate-child prop/stickman library scans, and is promoted to the appropriate production folder only after explicit user approval.
- Maintained docs live in `docs/{guides,architecture,specs,plans}/`; older plans and project references live in `docs/archive/`. Keep `README.md` and `AGENTS.md` at the root.
- `ContentPaths.canonicalize(path)` maps only old project paths under `stickmen/`, `props/`, and `game_scenes/` into `content/`. It preserves unknown and external paths. Keep stage serialization and persisted paths routed through it when adding path-bearing data.
## Command-line testing & debug playback
@@ -14,11 +24,12 @@
- `<project>` = the repo root (e.g. `/Users/ryan/Projects/stickman`, or `C:\Godot4\stickman` on Windows).
- **Run a headless test suite** (`tests/*.gd` extend `SceneTree`, print PASS/FAIL, `quit(0/1)`):
`<godot> --headless --path <project> --script res://tests/test_cli_scene.gd`
- Path migration compatibility: `tests/test_content_paths.gd`.
- Prop Editor suites (same pattern): `tests/test_prop_editor_file.gd`, `tests/test_prop_editor_theme.gd`,
`tests/test_prop_editor_canvas.gd`, `tests/test_prop_editor_thumb.gd`, `tests/test_prop_editor_round3.gd`.
- **Launch the Sandbox Stage and auto-PLAY a `.scene`** (visual/runtime debugging — `print()` output streams to the launching terminal):
`<godot> --path <project> res://scenes/sandbox_stage.tscn -- --play --scene <path>.scene`
- Args after `--` are parsed by `scripts/cli_scene_launcher.gd` (`CliSceneLauncher.parse`) via `OS.get_cmdline_user_args()`.
`<godot> --path <project> res://scenes/stage/sandbox_stage.tscn -- --play --scene <path>.scene`
- Args after `--` are parsed by `scripts/tools/cli_scene_launcher.gd` (`CliSceneLauncher.parse`) via `OS.get_cmdline_user_args()`.
- `--scene <path>` (also `--scene=<path>` or a single bare positional) loads the scene; `--play` enters PLAY after a successful load.
- `--play` requires a scene — alone it shows the error dialog and stays blank/EDIT.
- A load failure shows a "Could Not Load Scene" dialog and falls back to a blank scene.
@@ -41,18 +52,35 @@ that form the foundation of the broader moviemaking experience.
The **Prop Editor** (Phase 9) is a standalone, kid-friendly vector-prop authoring tool — the
authoring counterpart to the stickman editor — for creating, editing, and saving `.prop`
vector prop assets. It is launched via **F6** on `res://scenes/prop_editor.tscn` and is **not**
vector prop assets. It is launched via **F6** on `res://scenes/editors/prop/prop_editor.tscn` and is **not**
the configured `run/main_scene`.
## Shared Stage inspector UI
- For new **in-stage selected-object inspectors**, use the approved
[Stage panel UI system spec](docs/specs/stage_panel_ui_system.md) as the design and
implementation reference. The shared controls live in `scripts/shared/stage_ui/`:
`StageInspectorPanel`, `StagePanelChoiceGroup`, `StagePanelToggleRow`, and
`StagePanelSwitchVisual`; shared visual tokens live under `stage_panels` in
`SandboxTheme` (`scripts/stage/sandbox_theme.gd` and `sandbox_theme.json`). Extend these
components and tokens before creating parallel panel styling or behavior.
- This kit currently targets in-stage selection/inspector cards. For modal dialogs, editor
inspectors, or other distinct UI workflows, review the spec for reusable visual rules but
decide whether the components' layout and interaction model fit before adopting them.
- For temporary in-stage target or placement capture in **EDIT** or **DIRECT**, follow the
transient capture-bar pattern in the [Stage panel UI system spec](docs/specs/stage_panel_ui_system.md).
Keep the prompt and visible Cancel together, leave the mode pill unobstructed, and reuse or
generalize `StageUseTargetCaptureBar` instead of copying its styling.
## 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.
but keep it present — it is required for the legacy `scenes/legacy/stick.tscn` rig.
## Architecture
- `scripts/stickman_editor.gd` — `extends Control`; the main controller. Owns the menu bar,
- `scripts/editors/stickman/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.5"`; auto-migrates `"1.0"`–`"1.4"` files on load. Coordinates cross-panel
selection so only one shape is selected at a time (`shape_selected` → deselect others).
@@ -98,7 +126,7 @@ the configured `run/main_scene`.
and broadcasts. `_broadcast_settings()` pushes the value to the preview via
`WholeStickmanPreview.set_show_guide(_show_guide)` (called in `_ready()` after
`_load_settings()`).
- `scripts/body_part_panel.gd` — `class_name BodyPartPanel`, `extends PanelContainer`.
- `scripts/editors/stickman/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}`
@@ -130,7 +158,7 @@ the configured `run/main_scene`.
- **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.
- `scripts/whole_stickman_preview.gd` — `class_name WholeStickmanPreview`, `extends Control`;
- `scripts/editors/stickman/whole_stickman_preview.gd` — `class_name WholeStickmanPreview`, `extends Control`;
the assembly preview. Owns per-part position/rotation/scale, Z-order (`_part_order`),
selection + gizmos, grid drawing, and pan/zoom. Public API includes `set_body_parts()`,
`set_show_guide(enabled: bool)`, `reset_view()`, `is_cursor_over_preview(global_pos)`, and
@@ -146,7 +174,7 @@ the configured `run/main_scene`.
`_guide_to_preview()` = `(master_pos - GUIDE_FIGURE_CENTER) * GUIDE_SCALE +
preview_area.size * 0.5`, so it also re-centers on window resize; it remains a
world-space fixture that moves with pan/zoom. Joint positions are **hardcoded
constants** derived from the `master_rig.tscn` rest pose (`GUIDE_JOINTS`: 13 anchors
constants** derived from the `stickman_rig.tscn` rest pose (`GUIDE_JOINTS`: 13 anchors
Head/Neck/Shoulders/Elbows/Wrists/Hips/Knees/Ankles;
`GUIDE_SCALE = 1.0`, `GUIDE_FIGURE_CENTER = (0, -93.75)`, `GUIDE_HEAD_RADIUS = 100.0`).
Color-coded: left limbs cyan-blue `Color(0.35, 0.70, 1.00)`, right limbs orange-red
@@ -154,9 +182,9 @@ preview_area.size * 0.5`, so it also re-centers on window resize; it remains a
dots at alpha `0.65`. Joint dots use `GUIDE_JOINT_RADIUS / _zoom` (6 px constant screen
size). The guide is pure drawing — **no hit-testing** is added, so it never intercepts
part dragging/selection.
- `scripts/prop_editor.gd` — `class_name PropEditor`, `extends Control`; the **Prop Editor**
- `scripts/editors/prop/prop_editor.gd` — `class_name PropEditor`, `extends Control`; the **Prop Editor**
controller (Phase 9, **not wired into the editor**; launched via **F6** on
`res://scenes/prop_editor.tscn`). Owns the menu bar, save/load/new flow, `.prop`
`res://scenes/editors/prop/prop_editor.tscn`). Owns the menu bar, save/load/new flow, `.prop`
(de)serialization, the material-preset + per-shape inspector, the mode toolbar, the status
bar, the window title, the exit dialog, and the autosave/recovery pipeline. State:
`_theme: PropTheme`, `_material_preset: int` (`PropBlock.MaterialPreset`), `_current_path`,
@@ -242,7 +270,14 @@ preview_area.size * 0.5`, so it also re-centers on window resize; it remains a
`prop_block.gd`, `prop_canvas.gd`, `thumbnails/prop_thumbnail.gd`, and
`thumbnails/thumbnail_cache.gd` via `preload` consts. `_apply_json_data(json, is_recovery =
false) -> String` returns `""` on success or an error string (dialog on failure).
- `scripts/prop_canvas.gd` — `class_name PropCanvas`, `extends Control`; the Prop Editor's
- **Phase 10a final controls:** the Arrange top bar orders **Transform Whole Prop**,
**Normalize Size**, **Center Prop**, and **Show Stickman Guide**. The numeric scale textbox
and Apply Scale button are not exposed. Transform Whole Prop is Arrange-only and previews
whole-document move, origin-based rotation, and uniform resize; Snap rounds move deltas,
Ctrl snaps rotation to 15°, editable anchors follow, and Esc cancels the preview. Reshape
and Attach hide Transform Whole Prop and Normalize Size; Center Prop and Show Stickman Guide
remain available.
- `scripts/editors/prop/prop_canvas.gd` — `class_name PropCanvas`, `extends Control`; the Prop Editor's
**multi-shape vector canvas** (Phase 9, **not used by the editor**), mirroring
`BodyPartPanel`'s proven input model (cursor-centered zoom, pinch/pan gestures, middle-mouse
pan, vertex drag, right-click context menu, z-order) while adding prop-specific needs
@@ -317,9 +352,14 @@ preview_area.size * 0.5`, so it also re-centers on window resize; it remains a
~2 px wider) + a **bright core pass** (`SELECTION_CORE_COLOR`, white, original width). The
pair is visible on **any** background (the Round 6 fill-adaptive `contrast_color_for` was
removed because it could not know what sits *behind* a selected shape).
- `scripts/prop_file.gd` — `class_name PropFile`, `extends RefCounted`; the static `.prop`
- **Phase 10a whole-prop transform:** the canvas exposes typed move/rotate/resize request
signals and renders a union gizmo while Transform Whole Prop is armed. Body, rotation-dot,
and corner gestures use snapshot-only previews and commit one atomic document edit on
release; `set_whole_prop_transform_enabled()` and related transform names are the preferred
API. Resize-named compatibility aliases remain internally for existing controller paths.
- `scripts/editors/prop/prop_file.gd` — `class_name PropFile`, `extends RefCounted`; the static `.prop`
schema + converters (Phase 9, **not used by the editor**). Consts `FILE_VERSION := "1.0"`,
`FILE_EXT := ".prop"`, `DEFAULT_DIR := "res://props"`, `SUPPORTED_VERSIONS: ["1.0"]`,
`FILE_EXT := ".prop"`, `DEFAULT_DIR := "res://content/props"`, `SUPPORTED_VERSIONS: ["1.0"]`,
`PRESET_STRINGS` (index == `PropBlock.MaterialPreset`: none/wood/rubber/cardboard/metal),
`SHAPE_STRINGS` (polygon/circle/line), `DEFAULT_RADIUS`, `DEFAULT_CIRCLE_SEGMENTS`.
- **Schema (v1.0):** top-level `version`, `prop_name`, `material_preset`, `shapes[]`, and
@@ -350,7 +390,7 @@ preview_area.size * 0.5`, so it also re-centers on window resize; it remains a
element becomes `Vector2.ZERO`). Round 1–4 files saved this way are unrecoverable. Any typed
`Packed*Array` assignment from dictionary/JSON data must go through a Variant-coercion
helper, and regression tests must assert point **values**, not just counts.
- `scripts/prop_theme.gd` — `class_name PropTheme`, `extends RefCounted`; the **validated theme
- `scripts/editors/prop/prop_theme.gd` — `class_name PropTheme`, `extends RefCounted`; the **validated theme
loader** for `res://prop_theme.json` (Phase 9, **not used by the editor**), modeled on
`SandboxTheme`. Consts `SCHEMA_VERSION := "1.0"`, `DEFAULT_PATH := "res://prop_theme.json"`,
and `DEFAULT_CONFIG` (mirrors the committed file). `load_from(path)` deep-merges the file over
@@ -371,14 +411,14 @@ preview_area.size * 0.5`, so it also re-centers on window resize; it remains a
`button_min_height`, `row_min_height`, `mode_button_min_height`, `text_color`,
`text_muted_color`, `border_color`, `accent_color`. All are clamped with `DEFAULT_CONFIG`
fallbacks and applied through a **local** `Theme` (no global GUI mutation).
- `scripts/thumbnails/thumbnail_cache.gd` — see its existing section; the Phase 9 extension
- `scripts/shared/thumbnails/thumbnail_cache.gd` — see its existing section; the Phase 9 extension
adds `prop_file_key(path)` → `"<basename>_<mtime>"` and `prop_file_png(path)` →
`PROP_DIR + "/" + key + ".png"` for **custom `.prop`** files (mtime-keyed so a re-edited prop
invalidates its cached PNG). Built-in template thumbnails keep `prop_png(id)` →
`"<id>_v<PROP_VERSION>.png"`; `stickman_key`/`stickman_png` are unchanged.
- `scripts/stk_rig_adapter.gd` — `class_name StkRigAdapter`, `extends RefCounted`; a **standalone
- `scripts/characters/stickman/stk_rig_adapter.gd` — `class_name StkRigAdapter`, `extends RefCounted`; a **standalone
runtime adapter** (Phase 8, **not referenced by the editor**, extended by Phase 9).
`static func apply(stk_data, rig)` fits an instantiated `master_rig.tscn` to a loaded `.stk`
`static func apply(stk_data, rig)` fits an instantiated `stickman_rig.tscn` to a loaded `.stk`
dictionary, calling three private helpers in order: `_fit_bones` (re-fits the 8 limb `Bone2D`
lengths + lower-bone origins, and zeroes the Head driver's local position so the chin sits on
the neck joint), `_recalibrate_ik` (repositions the `IK_Targets/Left|Right_Hand` and
@@ -389,7 +429,7 @@ preview_area.size * 0.5`, so it also re-centers on window resize; it remains a
- **Phase 2 (Sandbox) single-shape support:** `_mount_shapes()` reads a part's `shapes` array
when present (v1.2+), and otherwise — when the part dict itself carries `points` — wraps the
whole dict as a single shape (`shapes = [pd]`), so v1.0/v1.1 single-shape `.stk` files (e.g.
`stickmen/test.stk`) mount as visible geometry instead of being cleared to nothing.
`content/stickmen/test.stk`) mount as visible geometry instead of being cleared to nothing.
- **Phase 9 extension:** also fits the head bone (`Skeleton2D/Torso/Head.position.y =
-proportions.torso_length`, x preserved) and mounts the head as **full geometry** like every
other part — it clears the `Body/Head` node's inline `@tool` circle script via
@@ -472,11 +512,11 @@ f_prime`). This replaces the per-side family choice **and** the 180° flip heuri
the key keep the previous family rules + flip heuristic exactly as before. `theta`, `s`, the
Round 5 offset `t`, and the `HEAD_CHIN_DROP` fallback are unchanged — they consume
`anchor`/`v` generically.
Targets `master_rig.tscn` node paths; every node lookup is null-guarded (missing node →
Targets `stickman_rig.tscn` node paths; every node lookup is null-guarded (missing node →
`push_warning` + skip, never crash). Consumed by a future runtime pipeline.
- `scripts/stickman_rig.gd` — `class_name StickmanRig`, `extends Node2D`; the **runtime owner of
facing direction, per-joint bone bend, and `Body/*` z-order** for `master_rig.tscn` (Phase 9
Task 4). Attached to the `Master` root node of `master_rig.tscn`. **Non-`@tool`** — node
- `scripts/characters/stickman/stickman_rig.gd` — `class_name StickmanRig`, `extends Node2D`; the **runtime owner of
facing direction, per-joint bone bend, and `Body/*` z-order** for `stickman_rig.tscn` (Phase 9
Task 4). Attached to the `Master` root node of `stickman_rig.tscn`. **Non-`@tool`** — node
resolution, flag writes, and z-order reordering run only at runtime (`_ready` + setters on a
live instance). Enums `FacingProfile { LEFT, RIGHT, FORWARD }` (values are the harness facing-menu
ids), `BendDirection { NORMAL, INVERTED }`, and `RigState { ANIMATED, RAGDOLL, RECOVERING }`.
@@ -521,7 +561,7 @@ f_prime`). This replaces the per-side family choice **and** the 180° flip heuri
mounted `Body/*` geometry, while z-order (depth) is unaffected by an X-mirror. `walk_to()`
always plays the canonical `walk_right` clip (X-mirrored by the root for LEFT; `walk_left` is
no longer used at runtime) and sets the facing profile explicitly. Two **head-related fixes**
under the mirror: the master_rig.tscn head `RemoteTransform2D` (`Skeleton2D/Torso/Head/Pivot`)
under the mirror: the stickman_rig.tscn head `RemoteTransform2D` (`Skeleton2D/Torso/Head/Pivot`)
no longer sets `update_scale = false` — it pushes the **full transform** like every other `Body`
driver, so `Body/Head.scale` stays identity under the mirrored root (the old partial-channel
push re-canonicalized the scale and caused per-frame Y-flips/wrap-jumps). And
@@ -564,7 +604,7 @@ RAGDOLL` + emits. `_build_ragdoll()` creates a `Node2D`
the table. Shoulder/hip ±160°, neck free. Cached
momentum is applied to the torso body. `apply_ragdoll_velocity_boost(velocity)` applies the
same velocity delta (mass-scaled `apply_central_impulse`) to every ragdoll body — used by the
harness "Knock Up" button. All ragdoll nodes are spawned procedurally — `master_rig.tscn` is
harness "Knock Up" button. All ragdoll nodes are spawned procedurally — `stickman_rig.tscn` is
**not** modified.
- **Phase 11 instant handoff + recovery:** `_update_rest_detection()` (only when `state ==
RAGDOLL`)
@@ -716,30 +756,30 @@ target, speed = -1.0)` (feet/ground destination; no-op unless `state == ANIMATED
private runtime lane without mutating `action_queue`: Overlay speech runs concurrently in one
replaceable slot, Sequential actions cancel the current primary action while retaining authored
future work, and Takeover actions cancel both primary and overlay work before taking control.
- `scripts/stickman_factory.gd` — `class_name StickmanFactory`, `extends RefCounted`; a **static
- `scripts/characters/stickman/stickman_factory.gd` — `class_name StickmanFactory`, `extends RefCounted`; a **static
factory** and the **runtime entry point** (Phase 9, **not used by the editor**) that turns a
`.stk` file into a live, rigged `master_rig.tscn` instance:
`.stk` file into a live, rigged `stickman_rig.tscn` instance:
- `static func load_stk(path: String) -> Dictionary` — reads a `.stk` file (`FileAccess` +
`JSON.parse_string`); returns `{}` + `push_warning` on failure.
- `static func spawn_from_data(stk_data: Dictionary) -> StickmanRig` — instantiates
`res://master_rig.tscn`, calls `StkRigAdapter.apply(stk_data, rig)`, returns the rig root
`res://scenes/characters/stickman/stickman_rig.tscn`, calls `StkRigAdapter.apply(stk_data, rig)`, returns the rig root
(typed as `StickmanRig` since the rig now carries the `StickmanRig` root script).
- `static func spawn(path: String) -> StickmanRig` — `load_stk()` then `spawn_from_data()`;
returns `null` on empty data.
- `scripts/create_animations.gd` — `@tool extends EditorScript`; a **standalone editor utility**
(run manually with `master_rig.tscn` open, **not** auto-loaded or referenced at runtime).
- `scripts/tools/create_animations.gd` — `@tool extends EditorScript`; a **standalone editor utility**
(run manually with `stickman_rig.tscn` open, **not** auto-loaded or referenced at runtime).
Supersedes the deleted `scripts/create_walk.gd`. `_run()` bakes `walk_left`/`walk_right`
(same keyframes as the old script, via `_generate_walk_animation()`) and a one-shot `stand_up`
(via `_generate_pose_animation()`, `STAND_UP_DURATION` = 2.0, `loop_mode = LOOP_NONE`) into the
open scene's default `AnimationLibrary`. `stand_up` keys the 6 `IK_Targets/*:position` tracks
plus a `IK_Targets/Torso:rotation` track from `POSE_DOWN` (a generic "lying on back" pose) to
`POSE_STANDING` (matching `master_rig.tscn` defaults), with `POSE_PATHS`/`POSE_MARKERS` consts.
`POSE_STANDING` (matching `stickman_rig.tscn` defaults), with `POSE_PATHS`/`POSE_MARKERS` consts.
The baked `stand_up` is an **authored reference only** — runtime recovery does not play it
(StickmanRig tweens the IK targets directly from the captured ragdoll pose).
- `scripts/test_harness.gd` — **standalone staging scene** (Phase 9, **not wired into the editor**;
run via **F6** on `res://scenes/test_harness.tscn`) for debugging bone scales, vector drawing
- `scripts/tools/test_harness.gd` — **standalone staging scene** (Phase 9, **not wired into the editor**;
run via **F6** on `res://scenes/tools/test_harness.tscn`) for debugging bone scales, vector drawing
offsets, and IK limits in isolation. Top UI bar: "Open .stk…" button → `FileDialog` (`*.stk`);
quick-select buttons for `stickmen/break.stk`, `stickmen/basic.stk`, `stickmen/test.stk`; "Show
quick-select buttons for `content/stickmen/break.stk`, `content/stickmen/basic.stk`, `content/stickmen/test.stk`; "Show
Bones" / "Show IK Handles" checkboxes; a status label showing the loaded filename. Viewport:
`SubViewportContainer` → `SubViewport` → world `Node2D` + enabled `Camera2D`; middle-mouse pan,
mouse-wheel zoom, camera recenters on each spawn. Each load frees the previous rig and spawns a
@@ -857,7 +897,7 @@ MOUSE_FILTER_IGNORE` on the panel (the label itself stays interactive for select
persistence to disk. The "Facing" menu and all animation controls are **hidden until an .stk is
loaded** (`_set_rig_controls_visible(false)` at the end of `_build_ui()` and in
`_free_current_rig()`; shown on successful spawn in `_load_and_spawn()`).
- `scripts/terrain_block.gd` — `class_name TerrainBlock`, `extends StaticBody2D`; a **reusable
- `scripts/terrain/terrain_block.gd` — `class_name TerrainBlock`, `extends StaticBody2D`; a **reusable
vector terrain component** (Vector Terrain System, **not used by the editor**). Builds its three
children in code: `Polygon2D` (interior fill, `fill_color`), `Line2D` (crisp vector border,
`outline_color`/`outline_width`, auto-closed loop by appending the first vertex to the end,
@@ -865,7 +905,7 @@ MOUSE_FILTER_IGNORE` on the panel (the label itself stays interactive for select
supports concave blocks). Exported properties: `polygon_points: PackedVector2Array`,
`fill_color`, `outline_color`, `outline_width`; a unified setter pushes vertex changes to all
three children live (no manual rebuilds).
- `scripts/terrain_utils.gd` — `class_name TerrainUtils`, `extends RefCounted`; static utility
- `scripts/terrain/terrain_utils.gd` — `class_name TerrainUtils`, `extends RefCounted`; static utility
(**not used by the editor**):
- `sanitize_points(points: PackedVector2Array, grid_size: float = 16.0) -> PackedVector2Array` —
sanitization pipeline in order: grid snap → redundancy removal via a **local
@@ -875,7 +915,7 @@ MOUSE_FILTER_IGNORE` on the panel (the label itself stays interactive for select
guaranteeing clockwise output).
- `spawn_block(...)` — factory that sanitizes raw input vectors (`sanitize_points`), creates a
`TerrainBlock`, applies the cleaned points, and adds it to the target container.
- `scripts/prop_block.gd` — `class_name PropBlock`, `extends RigidBody2D`; a **reusable
- `scripts/props/prop_block.gd` — `class_name PropBlock`, `extends RigidBody2D`; a **reusable
dynamic vector prop component** (Dynamic Vector Props, **not used by the editor**), `@tool`.
Builds its children in code: `Polygon2D` (interior fill, `fill_color`), `Line2D` (crisp
vector outline, `outline_color`/`outline_width`, auto-closed loop by appending the first
@@ -897,12 +937,17 @@ PackedVector2Array`, `radius: float`, `fill_color`, `outline_color`, `outline_wi
matching direct-state contact row as world-space normal/position, with a center-direction
fallback when timing leaves no row. The physics signal covers prop-vs-prop and Phase 4.5
stickman-prop contacts; the old geometric feet-point stickman test is removed.
- `scripts/trigger_area.gd` — `class_name TriggerArea`, `extends Node2D`; a **placeable sensor**
- **Phase 10a instance scaling:** Sandbox EDIT exposes four uniform corner crosses only for
a single `PropBlock`. During PLAY, the authored root scale is moved into child visuals and
collision geometry, including the custom one-hull path and nonuniform circle fallback;
returning to EDIT restores the authored root scale. Preset mass and material physics stay
unchanged.
- `scripts/stage/trigger_area.gd` — `class_name TriggerArea`, `extends Node2D`; a **placeable sensor**
(Phase 4, **not used by the editor**). `@export size: Vector2` (default 96×96), `get_area_rect()
-> Rect2` (centered local-space rectangle), and `_draw()` (translucent green fill +
dashed border). **No physics and no signals** — it is a pure geometric region evaluated by
`sandbox_stage.gd`'s event engine (`_update_area_entry`) for `entered_area` rules.
- `scripts/prop_utils.gd` — `class_name PropUtils`, `extends RefCounted`; static factory
- `scripts/props/prop_utils.gd` — `class_name PropUtils`, `extends RefCounted`; static factory
(**not used by the editor**):
- `create_box(size := Vector2(48,48))` / `create_ball(radius := 24.0)` /
`create_plank(length := 160.0, thickness := 16.0)` / `create_triangle(base := 56.0, height
@@ -913,10 +958,10 @@ PackedVector2Array`, `radius: float`, `fill_color`, `outline_color`, `outline_wi
(`shape_type` + geometry + colors via `_apply_shape_payload()`, sanitizing polygon points
through `TerrainUtils.sanitize_points()`), sets `linear_velocity` after `add_child` (only
when non-zero), and returns the spawned prop.
- `scripts/physics_test_harness.gd` — `class_name PhysicsTestHarness`, `extends Node2D`; standalone
- `scripts/tools/physics_test_harness.gd` — `class_name PhysicsTestHarness`, `extends Node2D`; standalone
staging scene root (Vector Terrain System / Dynamic Vector Props, **not wired into the editor**;
run via **F6** on `res://scenes/physics_test_harness.tscn`). Builds flat ground, angled ramps, and stepped
`TerrainBlock` instances via `TerrainUtils`, instantiates `res://master_rig.tscn` standing on the
run via **F6** on `res://scenes/tools/physics_test_harness.tscn`). Builds flat ground, angled ramps, and stepped
`TerrainBlock` instances via `TerrainUtils`, instantiates `res://scenes/characters/stickman/stickman_rig.tscn` standing on the
flat ground, and handles camera input. A top-bar UI (`_build_ui()`, a `CanvasLayer` +
`PanelContainer` matching `test_harness`'s style) replaces the old key bindings: **Spawn
Crate** / **Spawn Ball** / **Spawn Plank** buttons spawn dynamic props above the angled ramp
@@ -949,9 +994,9 @@ KNOCK_UP_VELOCITY = (0, -450))` (mass-scaled `apply_central_impulse` on every ra
always `_update_ragdoll_toggle()` (proxy helpers are idempotent, so the toggle handler's own
add/remove is harmless). The toggle label reads "Stickman" during `RECOVERING` (since
` is_in_ragdoll()` is false).
- `scripts/sandbox_stage.gd` — `class_name SandboxStage`, `extends Node2D`; the **Sandbox Stage
- `scripts/editors/stage/sandbox_stage.gd` — `class_name SandboxStage`, `extends Node2D`; the **Sandbox Stage
Builder** root controller (Phase 2, **not wired into the editor**; run via **F6** on
`res://scenes/sandbox_stage.tscn`). Owns the EDIT/DIRECT/PLAY mode state machine, placement mode,
`res://scenes/stage/sandbox_stage.tscn`). Owns the EDIT/DIRECT/PLAY mode state machine, placement mode,
camera pan/zoom, deletion, status bar, and signal fan-out; instantiates `StageSpawner` /
`StageSelection` / `StageGizmos` (via `preload` consts). `enum StageMode { EDIT, DIRECT, PLAY }`
(default EDIT). EDIT freezes `RigidBody2D` props with `freeze = true` +
@@ -973,7 +1018,10 @@ KNOCK_UP_VELOCITY = (0, -450))` (mass-scaled `apply_central_impulse` on every ra
`global_position`/`global_rotation` + zeroed velocity. Because the freeze+teleport can take a
physics frame or two to settle, `_restore_authored_state()` is also re-asserted over the next
few `_physics_process` frames (`_restore_frames_left`) — so every Play session starts from and
returns to the same authored state. **Placement ghost:** while a palette
returns to the same authored state. **Phase 10a scale lifecycle:** prop corner-scale gestures
use the existing transform signal path, synchronize the frozen body during EDIT, save the
authored scale on commit, and retain that scale across PLAY → EDIT → PLAY. **Placement ghost:**
while a palette
item is active, `set_placement_mode(id)` spawns a translucent (`modulate.a = 0.5`),
non-colliding (`collision_layer/mask = 0`, frozen) copy of the object reparented out of
`World` into `_ghost_holder`; `_process()` tracks it to the cursor (+ snap) via
@@ -1065,7 +1113,7 @@ Array[Dictionary]` of `{id, trigger, actions}` rules (trigger types: `arrived_at
are off by default.
- **Phase 3b asset-library selector integration:** the **Stickman** and **Prop** palette buttons no
longer place directly — pressing them opens a modal **selector grid** (`_selector: AssetSelector`,
an instantiated `scenes/asset_selector.tscn` `PopupPanel`, added to the UI `CanvasLayer` in
an instantiated `scenes/stage/asset_selector.tscn` `PopupPanel`, added to the UI `CanvasLayer` in
`_build_ui()` with theme/font overrides applied via `AssetSelector.apply_font`), backed by a
dim backdrop `_selector_dim` (a black `ColorRect` at `SELECTOR_DIM_ALPHA` = 0.5, `mouse_filter =
MOUSE_FILTER_IGNORE`, on the UI `CanvasLayer` behind the selector, shown only while the selector
@@ -1095,7 +1143,7 @@ MOUSE_FILTER_IGNORE`, on the UI `CanvasLayer` behind the selector, shown only wh
early-return while `_selector_open` (belt-and-suspenders over the modal popup). Selection is
**session-only** — persists across EDIT/DIRECT/PLAY toggles, resets on scene reload, **no disk
save** (`_save_settings` is untouched).
- `scripts/stickman_speech_bubble.gd` — `class_name SpeechBubble`, `extends Node2D`; a **world-space
- `scripts/speech/stickman_speech_bubble.gd` — `class_name SpeechBubble`, `extends Node2D`; a **world-space
speech bubble** drawn in `_draw()` (Phase 3a, **not used by the editor**). Child of a `StickmanRig`
at `SPEECH_BUBBLE_OFFSET` (above the head), so it follows the figure and scales with the camera.
Consts `FONT_SIZE`/`PADDING`/`TAIL_HEIGHT`/`MAX_WIDTH`/`BG_COLOR`/`BORDER_COLOR`/`TEXT_COLOR`.
@@ -1103,7 +1151,7 @@ MOUSE_FILTER_IGNORE`, on the UI `CanvasLayer` behind the selector, shown only wh
a downward tail triangle centered on the rig-local origin, then `draw_string(...)`; `visible = false`
by default, no hit-testing. Public API: `show_text(text)` (store, `visible = true`, `queue_redraw()`),
`hide_bubble()`. Driven by `StickmanRig.speak()`; the rig owns hiding + the `speech_finished` signal.
- `scripts/stage_director_visuals.gd` — `class_name StageDirectorVisuals`, `extends Node2D`; the
- `scripts/stage/stage_director_visuals.gd` — `class_name StageDirectorVisuals`, `extends Node2D`; the
**Edit-mode director overlay** (Phase 3a, **not used by the editor**), mirroring `StageGrid`/
`StageGizmos`. State `camera`/`world`/`enabled`/`_dirty`; public `set_enabled(value)` /
`mark_dirty()`. `_process()` redraws on a dirty flag; `_draw()` reads each rig's queue via
@@ -1121,7 +1169,7 @@ MOUSE_FILTER_IGNORE`, on the UI `CanvasLayer` behind the selector, shown only wh
badge**, an orange **→ action badge**, a dark label with `rule_summary()` text, and a **✕ delete
icon** (the label + icon are hit-testable via `hit_test_rule()`, `Vector2.INF` sentinel from
`hit_test_waypoint()`); click-label → consequence-only edit, click-✕ → delete.
- `scripts/stage_spawner.gd` — `class_name StageSpawner`, `extends RefCounted`; registry-driven
- `scripts/stage/stage_spawner.gd` — `class_name StageSpawner`, `extends RefCounted`; registry-driven
spawner (Phase 2). A `_registry: Array[Dictionary]` maps ids to terrain/prop/stickman templates;
adding a type = appending an entry (no hard-coded id `match`). Reuses `TerrainUtils.spawn_block`,
`PropUtils.spawn_prop`, `StickmanFactory.spawn_from_data` via `preload` consts. Terrain entries
@@ -1137,7 +1185,7 @@ MOUSE_FILTER_IGNORE`, on the UI `CanvasLayer` behind the selector, shown only wh
`ground/ramp/step` (terrain) + `prop` + `stickman` + `area` — the separate `crate`/`ball`
entries were **removed** and a single `"prop"` entry (label "Prop", kind `"prop"`) added, so
`get_spawnable_ids()` == `["ground","ramp","step","prop","stickman","area"]`. Session state:
`selected_stickman_path` (default `DEFAULT_STICKMAN_PATH` = `res://stickmen/test.stk`) and
`selected_stickman_path` (default `DEFAULT_STICKMAN_PATH` = `res://content/stickmen/test.stk`) and
`selected_prop_id` (default `"crate"`), read/written by the sandbox selector (Phase 3b) — they
are **session-only** (no disk save). `_stickman_cache: Dictionary` (path → parsed data) seeds
the default path in `_init` and lazily loads+parses any newly selected path on first spawn.
@@ -1145,9 +1193,9 @@ MOUSE_FILTER_IGNORE`, on the UI `CanvasLayer` behind the selector, shown only wh
so feet land at the cursor); `_spawn_prop` looks the **selected** prop id up via
`PropLibrary.get_entry(id)` and spawns its payload (`PropUtils.spawn_prop`) + material preset.
New getters `get_selected_stickman_path()` / `get_selected_prop_id()` (status + tests).
- `scripts/stickman_library.gd` — `class_name StickmanLibrary`, `extends RefCounted` (Phase 3b,
**not used by the editor**). Scans `res://stickmen/*.stk` into entry models for the asset
selector. `const STICKMEN_DIR := "res://stickmen"`; `var entries: Array[Dictionary]`. Public API:
- `scripts/characters/stickman/stickman_library.gd` — `class_name StickmanLibrary`, `extends RefCounted` (Phase 3b,
**not used by the editor**). Scans `res://content/stickmen/*.stk` into entry models for the asset
selector. `const STICKMEN_DIR := "res://content/stickmen"`; `var entries: Array[Dictionary]`. Public API:
`scan(dir_path: String = STICKMEN_DIR) -> Array[Dictionary]` (via `DirAccess` + per-file
`StickmanFactory.load_stk`; a corrupt or missing-`body_parts` file is **skipped** with a
`push_warning` — never `{}`-as-entry; the display `name` = `stickman_name` (stripped) if
@@ -1157,7 +1205,7 @@ MOUSE_FILTER_IGNORE`, on the UI `CanvasLayer` behind the selector, shown only wh
(ad-hoc single-file entry for **Browse**: `load_stk` + name fallback; `{}` on load failure or
missing `body_parts`). Entry shape `{ "path", "name", "data" }`; thumbnails are **not** embedded
in entries — resolved via `ThumbnailCache` at display time by the selector.
- `scripts/prop_library.gd` — `class_name PropLibrary`, `extends RefCounted` (Phase 3b, **not used
- `scripts/props/prop_library.gd` — `class_name PropLibrary`, `extends RefCounted` (Phase 3b, **not used
by the editor**). A **static** registry of the 4 built-in prop templates for the asset selector.
`static var _templates: Array[Dictionary]` (lazily built on first access — `PropUtils.create_*()`
are `static func` and cannot run in a `const`). Public API: `static get_entries()` (builds once,
@@ -1166,7 +1214,7 @@ MOUSE_FILTER_IGNORE`, on the UI `CanvasLayer` behind the selector, shown only wh
material_preset, material_label, payload}` — Crate/Wood (`create_box()` + `WOOD`), Ball/Rubber
(`create_ball()` + `RUBBER`), Plank/Metal (`create_plank()` + `METAL`), Triangle/Cardboard
(`create_triangle()` + `CARDBOARD`) — generator color themes already match the presets.
- `scripts/thumbnails/thumbnail_cache.gd` — `class_name ThumbnailCache`, `extends RefCounted`
- `scripts/shared/thumbnails/thumbnail_cache.gd` — `class_name ThumbnailCache`, `extends RefCounted`
(Phase 3b, **not used by the editor**). Disk PNG cache under `user://thumbnails/`. Consts
`STICKMEN_DIR := "user://thumbnails/stickmen"`, `PROP_DIR := "user://thumbnails/props"`,
`PROP_VERSION := 1`. Public API: `stickman_key(path)` → `"<basename>_<FileAccess.get_modified_time(path)>"`
@@ -1175,7 +1223,7 @@ material_preset, material_label, payload}` — Crate/Wood (`create_box()` + `WOO
thumbnails); `load_png(png_path) -> Texture2D` (`Image.load_from_file` + `ImageTexture`, `null`
if missing/unloadable); `save_png(tex, png_path) -> Error` (creates the parent dir); `ensure_dir`;
`clean_stale_stickmen(valid_keys)` (deletes `basename_*` PNGs not in the valid key set).
- `scripts/thumbnails/stickman_thumbnail.gd` — `class_name StickmanThumbnail`, `extends Node`
- `scripts/shared/thumbnails/stickman_thumbnail.gd` — `class_name StickmanThumbnail`, `extends Node`
(Phase 3b, **not used by the editor**). Renders a parsed `.stk` into a `Texture2D` for the asset
selector by spawning the **real rig**. `const SIZE := Vector2i(200, 200)`. `_ready()` builds one
persistent offscreen `SubViewport` (`transparent_bg`, `render_target_update_mode = UPDATE_ALWAYS`)
@@ -1187,7 +1235,7 @@ material_preset, material_label, payload}` — Crate/Wood (`create_box()` + `WOO
texture, frees the rig, and returns an `ImageTexture` — or `null` (a placeholder) when the capture
is blank/empty (the **headless degrade**; pixel rendering is manual/F6 verification only). Output is
identical to the rig actually placed on stage, not a re-implementation of the editor preview.
- `scripts/thumbnails/prop_thumbnail.gd` — `class_name PropThumbnail`, `extends Node` (Phase 3b,
- `scripts/shared/thumbnails/prop_thumbnail.gd` — `class_name PropThumbnail`, `extends Node` (Phase 3b,
**not used by the editor**). Renders a prop template into a `Texture2D`. `const SIZE :=
Vector2i(200, 200)`. `_ready()` builds the same offscreen `SubViewport` + `Camera2D` pattern as
`StickmanThumbnail`. `render(payload, material_preset) -> Texture2D` mirrors `PropBlock`'s
@@ -1196,8 +1244,8 @@ Vector2i(200, 200)`. `_ready()` builds the same offscreen `SubViewport` + `Camer
circle payloads are re-expanded via `PropBlock.CIRCLE_SEGMENTS`; non-`NONE` presets tint fill via
`PropBlock.tint_for` and darken the outline; then frames + double-`frame_post_draw` captures and
returns an `ImageTexture` or `null` (blank → placeholder).
- `scripts/asset_selector.gd` — `class_name AssetSelector`, `extends PopupPanel` (Phase 3b, **not
used by the editor**); root script of `scenes/asset_selector.tscn`. A **grid popup controller**.
- `scripts/shared/ui/asset_selector.gd` — `class_name AssetSelector`, `extends PopupPanel` (Phase 3b, **not
used by the editor**); root script of `scenes/stage/asset_selector.tscn`. A **grid popup controller**.
Signals `item_selected(entry)`, `cancelled()`, `browse_requested()`, `refresh_requested()`.
`const COLUMNS := 4`, `ROWS := 3`, `PAGE_SIZE := 12`, `CELL_MIN_SIZE`, `THUMB_SIZE`. `var kind`
(`"stickman"` | `"prop"`). Public API: `open(kind, entries)` (sets
@@ -1219,7 +1267,7 @@ page_count}` helper. `_build_cell(entry)` returns a `Button` with a `TextureRect
`%EmptyLabel`/`%PageLabel`/`%PrevButton`/`%NextButton`/`%BrowseButton`/`%RefreshButton`/
`%CloseButton`); all dynamic per-cell content is built in code at runtime because the entry set is
dynamic.
- `scripts/stage_selection.gd` — `class_name StageSelection`, `extends RefCounted`; hover/click/
- `scripts/stage/stage_selection.gd` — `class_name StageSelection`, `extends RefCounted`; hover/click/
box selection via geometric world-space AABB hit-testing (Phase 2). `static get_world_aabb`
unions a `Polygon2D` child's world points (terrain/props) or, for a stickman rig, recursively
unions its mounted `Body/*` `Line2D`/`Polygon2D` geometry (`_collect_visual_points()`), so the
@@ -1230,7 +1278,7 @@ page_count}` helper. `_build_cell(entry)` returns a `Button` with a `TextureRect
`clear_selection`/`select_only`/`add_to_selection`/`toggle_selection`/`is_selected`/`hit_test`/
`update_hover`/`box_select`. (Phase 4) `get_world_aabb` also handles `TriggerArea` objects via
the same **duck-typed** `get_area_rect` AABB branch, so trigger areas are selectable/hit-testable.
- `scripts/stage_gizmos.gd` — `class_name StageGizmos`, `extends Node2D`; hover highlight +
- `scripts/stage/stage_gizmos.gd` — `class_name StageGizmos`, `extends Node2D`; hover highlight +
selection outlines + a rotate ring (Phase 2). **No translate handle** — objects are dragged
directly by the root; `set_targets(nodes: Array[Node2D])` holds the current multi-selection,
and `begin_translate_drag(node, world_pos)` starts a TRANSLATE drag that moves **all** selected
@@ -1245,49 +1293,52 @@ page_count}` helper. `_build_cell(entry)` returns a `Button` with a `TextureRect
`_rotate_start_rotation + (current − start angle)`, with holding **Ctrl** snapping to
15° increments via `snappedf(new_rotation, deg_to_rad(15.0))`.
`_draw()` draws a selection outline for **every** selected node. Pure `_draw()` +
distance-based hit-testing (no `Area2D`); `enum Handle { NONE, TRANSLATE, ROTATE }`.
distance-based hit-testing (no `Area2D`); `enum Handle { NONE, TRANSLATE, ROTATE, SCALE }`.
**Phase 10a** adds four constant-screen-size corner crosses for a single `PropBlock`; the
`Handle.SCALE` drag applies one positive uniform factor around the root origin through the
existing transform signals. Other selections keep the existing gizmos.
`transform_committed(nodes: Array[Node2D])` emitted on drag end. `set_enabled(false)` hides +
disables in PLAY. Also draws the box-select marquee via `set_box_rect(rect)`.
- `scripts/stage_grid.gd` — `class_name StageGrid`, `extends Node2D`; the optional world-space
- `scripts/stage/stage_grid.gd` — `class_name StageGrid`, `extends Node2D`; the optional world-space
grid overlay (Phase 2). Pure `_draw()`: grid lines pan/zoom with the camera (`camera.get_screen_center_position()`
- viewport extent / zoom), with a heavier **major line every 5 cells**; `grid_size` / `enabled`
are set by `SandboxStage`. Added as the first child of the stage root so it renders behind
`World`; no hit-testing.
- Scenes:
- `scenes/stickman_editor.tscn` — main editor layout; unique-name nodes (`%Prefix`) used
- `scenes/editors/stickman/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
- `scenes/editors/stickman/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.
- `scenes/test_harness.tscn` — **standalone staging scene** (Phase 9, not wired into the
editor; run via **F6**). Backed by `scripts/test_harness.gd`. Top UI bar with file open /
- `scenes/tools/test_harness.tscn` — **standalone staging scene** (Phase 9, not wired into the
editor; run via **F6**). Backed by `scripts/tools/test_harness.gd`. Top UI bar with file open /
quick-select, "Show Bones" / "Show IK Handles" / "Show Coords" toggles, and a
loaded-filename status label;
`SubViewport` world with an enabled `Camera2D` (middle-mouse pan, wheel zoom, recenter on
spawn); interactive limb IK via `SkeletonModificationStack2D` TwoBoneIK.
- `scenes/physics_test_harness.tscn` — **standalone staging scene** (Vector Terrain System /
- `scenes/tools/physics_test_harness.tscn` — **standalone staging scene** (Vector Terrain System /
Dynamic Vector Props, not wired into the editor; run via **F6**). Backed by
`scripts/physics_test_harness.gd`. Root `Node2D` + script, `Camera2D` at position `(0, -400)`
`scripts/tools/physics_test_harness.gd`. Root `Node2D` + script, `Camera2D` at position `(0, -400)`
zoom `0.5`, empty Environment container. Wheel-zoom scales between `0.25x` and `3.0x`
(resolution independence / vector outline thickness); middle-drag pans. Top-bar buttons
**Spawn Crate / Spawn Ball / Spawn Plank** spawn dynamic props (`PropUtils`) above the
angled ramp; a toggle button flips the rig **Stickman** ↔ **Ragdoll** (removing/restoring
the best-effort `StaticBody2D` rig collision proxy); a **Knock Up** button impulses the
ragdoll and all props upward.
- `scenes/sandbox_stage.tscn` — **standalone staging scene** (Sandbox Stage Builder, Phase 2,
not wired into the editor; run via **F6**). Backed by `scripts/sandbox_stage.gd`. Minimal
- `scenes/stage/sandbox_stage.tscn` — **standalone staging scene** (Sandbox Stage Builder, Phase 2,
not wired into the editor; run via **F6**). Backed by `scripts/editors/stage/sandbox_stage.gd`. Minimal
root `Node2D` + script, `Camera2D` at position `(0, -400)` zoom `(0.5, 0.5)`, and an empty
`World` (Node2D) container; the `GridLayer` (`StageGrid`), `GizmoLayer` (`StageGizmos`),
`PlacementGhost` holder, and the CanvasLayer top-bar UI (mode toggle + six palette buttons +
Grid/Snap/Size controls + status label) are built in code at `_ready()`. Phase 3b instantiates
the `AssetSelector` grid popup (see below) into the UI `CanvasLayer`.
- `scenes/asset_selector.tscn` — **standalone asset-selector grid popup** (Phase 3b, not wired
into the editor). Rooted at `AssetSelector` (`PopupPanel`, `scripts/asset_selector.gd`); a
- `scenes/stage/asset_selector.tscn` — **standalone asset-selector grid popup** (Phase 3b, not wired
into the editor). Rooted at `AssetSelector` (`PopupPanel`, `scripts/shared/ui/asset_selector.gd`); a
**minimal shell/layout skeleton** — authored title bar, empty `GridContainer`, empty-state
`Label`, and a footer (`Prev` / page label / `Next` / `Browse…` / `Refresh` / `Close`), all as
unique-name nodes (`%TitleLabel`, `%GridContainer`, `%EmptyLabel`, `%PageLabel`, `%PrevButton`,
@@ -1295,8 +1346,8 @@ page_count}` helper. `_build_cell(entry)` returns a `Button` with a `TextureRect
(thumbnail + name + prop material badge) is built in code at runtime because the entry set is
dynamic; instantiated by `sandbox_stage.gd` (`_build_ui`) for the **Stickman** / **Prop**
palette-button selector grids.
- `scenes/prop_editor.tscn` — **standalone Prop Editor scene** (Phase 9, not wired into the
editor; launched via **F6**). Backed by `scripts/prop_editor.gd`, minimal shell with
- `scenes/editors/prop/prop_editor.tscn` — **standalone Prop Editor scene** (Phase 9, not wired into the
editor; launched via **F6**). Backed by `scripts/editors/prop/prop_editor.gd`, minimal shell with
`%MenuBar` inside `%TopRow` (beside `%TopRightControls` — the grid spinbox + snap checkbox),
`%TopBar`/`%PropNameEdit`, `%MainLayout`/`%CanvasPanel`/`%CanvasArea`/`%ModeBar`/`%PropCanvas`,
`%InspectorColumn`/`%InspectorList`, a bottom status bar with `%SaveStatus` left +
@@ -1332,7 +1383,7 @@ rotation, scale}`.
### `.prop` file format (Phase 9)
- The Prop Editor's `.prop` vector-prop assets are versioned JSON (`version "1.0"`),
defined in `README.md`, saved under `res://props/*.prop`, and converted to a runtime
defined in `README.md`, saved under `res://content/props/*.prop`, and converted to a runtime
payload by `PropFile.flatten_to_payload()`.
- Shape dictionary (per entry in `shapes[]`): `{ "shape_type": "polygon"|"circle"|"line",
"points": Array[{x,y}] (polygon/line), "radius": float (circle), "fill_color": "#hex",
@@ -1365,7 +1416,7 @@ rotation, scale}`.
### Legacy scene (do not delete)
- `stick.tscn` — the original rigged/animated figure using `Skeleton2D` + IK targets +
- `scenes/legacy/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.
+181 -136
View File
@@ -1,6 +1,6 @@
# 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.
A Godot 4.7.1 project for creating and directing 2D scenes. Stickman Studio includes a stickman editor for drawing body-part shapes and assembling figures, plus a Sandbox Stage for building sets, directing actions, and playing scenes.
## Overview
@@ -20,19 +20,44 @@ Drawing surfaces **clip** their content to the panel bounds: shapes and parts no
| Item | Version |
|---|---|
| Engine | Godot 4.4 or newer |
| Engine | Godot 4.7.1 |
| 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**.
> The legacy `scenes/legacy/stick.tscn` scene (a rigged walk animation using `Skeleton2D` + IK) remains 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:
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`.
1. Open the project folder in **Godot 4.7.1**.
2. Press **F5**, or open and run `res://scenes/main.tscn` (which hosts the Stickman Editor).
The main scene is configured as `run/main_scene="res://scenes/stickman_editor.tscn"` in `project.godot`.
The main scene is configured as `run/main_scene="res://scenes/main.tscn"` in `project.godot`.
### Repository layout
- `scripts/` and `scenes/` are grouped by feature, with editors, stage systems, stickman runtime, props, terrain, speech, shared UI, and tools kept in their own folders.
- Editable libraries are stored in `content/stickmen/`, `content/props/`, and `content/stages/`; fonts remain in `resources/fonts/`.
- Maintained project documentation is in `docs/guides/`, `docs/architecture/`, `docs/specs/`, and `docs/plans/`. Older plans and references are in `docs/archive/`.
- `ContentPaths` keeps old project asset references loadable after moves while leaving external file paths unchanged.
### Sandbox Stage (F6)
Run `res://scenes/stage/sandbox_stage.tscn` with **F6** to build and direct a scene. **EDIT** is for
placing and transforming stage objects. **DIRECT** is for authoring stickman actions and event
rules. Press **Space** or **Play** to start a run; press Space or **Direct** to freeze it.
Space acts on each key press, so holding it does not repeatedly toggle the run. **Play** or Space
resumes a paused run from its frozen pose and time. The stage clock is visible in the top bar as
minutes, seconds, and centiseconds; it shows `00:00.00` in EDIT and planning DIRECT.
While a run is paused in DIRECT, you can add or edit stickman actions and event rules against the
paused scene. Pending actions can be reordered among themselves; the active action stays fixed.
Completed or interrupted actions can be deleted, with deletion taking effect on the next run from
`t = 0`. Event rules are not reordered. Objects cannot be moved or placed while paused; return to
EDIT for object changes. **Reset** or entering EDIT restores authored object transforms and time
zero while keeping action and rule edits. Paused edits autosave after a short debounce. A paused
walk or recovery resumes with its animation/tween phase intact; recovery does not start a second
ragdoll, and recovered feet keep their IK targets aligned instead of crossing.
## Quick Start
@@ -98,7 +123,7 @@ Scroll the mouse wheel inside any body-part panel or the Whole Stickman preview
### 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 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 runtime rig defined by `stickman_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 pivot locations used by the `Skeleton2D` rig, 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.
@@ -110,13 +135,13 @@ The guide is a **view aid only**: it is drawn above the grid **and in front of u
| 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.
- **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 `stickman_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.
**1:1 master-rig scale (deliberate):** the guide matches `stickman_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
@@ -241,21 +266,21 @@ The Whole Stickman preview treats all shapes in a body-part panel as **one combi
### 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.**
Phase 9 adds the **runtime pipeline** that turns a saved `.stk` file into a live, rigged `stickman_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:
**Stickman Factory (`res://scripts/characters/stickman/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_from_data` | `static func spawn_from_data(stk_data: Dictionary) -> Node2D` | Instantiates `res://scenes/characters/stickman/stickman_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 `StickmanRig` (the root of `master_rig.tscn`) 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).
The factory is the intended runtime API: `StickmanFactory.spawn("res://content/stickmen/basic.stk")` yields a rigged `StickmanRig` (the root of `stickman_rig.tscn`) 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:
**Test Harness (`res://scenes/tools/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`.
- **Open .stk…** — `FileDialog` filtered to `*.stk`; quick-select buttons for `res://content/stickmen/break.stk`, `res://content/stickmen/basic.stk`, `res://content/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.
@@ -271,7 +296,7 @@ Debug overlay (a world-space `Node2D` `_draw()`): true bone **segments** drawn b
The **Vector Terrain System** is a standalone, reusable component for building **crisp, resolution-independent vector terrain** (flat ground, angled ramps, stepped platforms) — with matching 2D collision. It is **not wired into the editor**; it is exercised through a dedicated staging scene run via **F6**.
**`TerrainBlock` (`res://scripts/terrain_block.gd`)** — `@tool` `class_name TerrainBlock`, `extends StaticBody2D`. A single terrain segment that builds its three children in code:
**`TerrainBlock` (`res://scripts/terrain/terrain_block.gd`)** — `@tool` `class_name TerrainBlock`, `extends StaticBody2D`. A single terrain segment that builds its three children in code:
| Child | Node type | Purpose |
|---|---|---|
@@ -281,7 +306,7 @@ The **Vector Terrain System** is a standalone, reusable component for building *
**Exported properties** (`polygon_points: PackedVector2Array`, `fill_color`, `outline_color`, `outline_width`) are driven by a single unified setter that pushes vertex changes to all three children live — no manual rebuilds.
**`TerrainUtils` (`res://scripts/terrain_utils.gd`)** — `class_name TerrainUtils`, `extends RefCounted`, static utility.
**`TerrainUtils` (`res://scripts/terrain/terrain_utils.gd`)** — `class_name TerrainUtils`, `extends RefCounted`, static utility.
- `sanitize_points(points: PackedVector2Array, grid_size: float = 16.0) -> PackedVector2Array` — sanitizes a raw vertex list through a fixed pipeline, in order:
1. **Grid snap** — rounds each vertex to the `grid_size` grid (default **16.0**).
@@ -289,13 +314,13 @@ The **Vector Terrain System** is a standalone, reusable component for building *
3. **Clockwise enforcement** — `Geometry2D.is_polygon_clockwise()` reverses the winding if it is not already clockwise, **guaranteeing clockwise output**.
- `spawn_block(...)` — factory that sanitizes the raw input vectors (`sanitize_points`), creates a `TerrainBlock`, applies the cleaned points, and adds it to the target container.
**`physics_test_harness.tscn` / `scripts/physics_test_harness.gd`** — `class_name PhysicsTestHarness`, `extends Node2D`; a **standalone staging scene** (run via **F6**; not wired into the editor). It builds flat ground, angled ramps, and stepped `TerrainBlock` instances via `TerrainUtils`, then instantiates `res://master_rig.tscn` standing on the flat ground. The scene root is a `Node2D` + script with a `Camera2D` at position `(0, -400)` zoom `0.5` and an empty `Environment` container. The camera wheel-zoom scales between **0.25x and 3.0x** (to test resolution independence / vector outline thickness); middle-drag pans. It also hosts the **Dynamic Vector Props** spawner (see below): press **1/2/3** to drop physics props above the angled ramp, plus a best-effort `StaticBody2D` collision proxy for the rig (which has no physics bodies of its own). A toggle-mode **Button** (label "Stickman" ↔ "Ragdoll") flips the rig's **kinematic-to-ragdoll** mode (see §17); a **Rest** `SpinBox` (0.1–10 s) writes `_rig.rest_timeout` (runtime-only) and a **"Recover Now"** button calls `_rig.request_recovery()`. The harness connects the rig's `state_changed` signal, which removes the `RigCollisionProxy` on `RAGDOLL` and re-adds it (idempotently) on `ANIMATED`/`RECOVERING`.
**`physics_test_harness.tscn` / `scripts/tools/physics_test_harness.gd`** — `class_name PhysicsTestHarness`, `extends Node2D`; a **standalone staging scene** (run via **F6**; not wired into the editor). It builds flat ground, angled ramps, and stepped `TerrainBlock` instances via `TerrainUtils`, then instantiates `res://scenes/characters/stickman/stickman_rig.tscn` standing on the flat ground. The scene root is a `Node2D` + script with a `Camera2D` at position `(0, -400)` zoom `0.5` and an empty `Environment` container. The camera wheel-zoom scales between **0.25x and 3.0x** (to test resolution independence / vector outline thickness); middle-drag pans. It also hosts the **Dynamic Vector Props** spawner (see below): press **1/2/3** to drop physics props above the angled ramp, plus a best-effort `StaticBody2D` collision proxy for the rig (which has no physics bodies of its own). A toggle-mode **Button** (label "Stickman" ↔ "Ragdoll") flips the rig's **kinematic-to-ragdoll** mode (see §17); a **Rest** `SpinBox` (0.1–10 s) writes `_rig.rest_timeout` (runtime-only) and a **"Recover Now"** button calls `_rig.request_recovery()`. The harness connects the rig's `state_changed` signal, which removes the `RigCollisionProxy` on `RAGDOLL` and re-adds it (idempotently) on `ANIMATED`/`RECOVERING`.
### 16. Dynamic Vector Props
The **Dynamic Vector Props** system is a standalone, reusable component for building **physical, dynamic props** (crates, balls, planks) as `RigidBody2D` bodies with crisp vector rendering and matching collision. It is **not wired into the editor**; it is exercised through the physics test harness staging scene via **F6**.
**`PropBlock` (`res://scripts/prop_block.gd`)** — `@tool` `class_name PropBlock`, `extends RigidBody2D`. A single physical prop whose children are built in code, `@tool`-safe (null-guarded for editor safety):
**`PropBlock` (`res://scripts/props/prop_block.gd`)** — `@tool` `class_name PropBlock`, `extends RigidBody2D`. A single physical prop whose children are built in code, `@tool`-safe (null-guarded for editor safety):
| Child | Node type | Purpose |
|---|---|---|
@@ -328,7 +353,7 @@ The **Dynamic Vector Props** system is a standalone, reusable component for buil
A **unified live-update setter** drives geometry to all children live — a change to `polygon_points` / `radius` / `shape_type` pushes to the `Polygon2D`, `Line2D`, and the active collision node with no manual rebuilds. Non-`NONE` presets also recolor `fill_color` and `outline_color` (outline = `tint_for(preset).darkened(0.55)`).
**`PropUtils` (`res://scripts/prop_utils.gd`)** — `class_name PropUtils`, `extends RefCounted`, static factory.
**`PropUtils` (`res://scripts/props/prop_utils.gd`)** — `class_name PropUtils`, `extends RefCounted`, static factory.
- **Primitive generators** return shape-payload dictionaries with default dimensions + color themes:
- `create_box(size := Vector2(48,48), ...)` — a 4-point `POLYGON` (wood theme).
@@ -349,7 +374,7 @@ A **unified live-update setter** drives geometry to all children live — a chan
### 17. Kinematic-to-Ragdoll State System
`StickmanRig` (the runtime root of `master_rig.tscn`) can switch between physics modes via a reversible state machine with three states:
`StickmanRig` (the runtime root of `stickman_rig.tscn`) can switch between physics modes via a reversible state machine with three states:
- **`ANIMATED`** (default) — the kinematic `Skeleton2D` + IK puppet is visible and driven by the `AnimationPlayer` / `SkeletonModificationStack2D`. The rig root's momentum is cached each physics frame (`_physics_process` → `_track_momentum()`) so it can be handed off on transition.
- **`RAGDOLL`** — the kinematic rig is frozen (`AnimationPlayer` stopped with `keep_state`) and the `Body/*` visuals hidden **immediately**; a procedural network of `RigidBody2D` + `PinJoint2D` nodes spawned in code takes over, letting the figure fall/tumble against the terrain and props. The ragdoll is built from the **current solved bone positions** (the IK stack is disabled only after the build), so the handoff is instant and seamless — the ragdoll appears in exactly the pose the figure was in, no crossfade.
@@ -383,7 +408,7 @@ Ragdoll bodies spawn fully visible — the entry handoff is instant (the ragdoll
**Angular limits** (`_apply_ragdoll_joint_limits()`): elbows/knees get their band from the editable per-profile, per-joint `RAGDOLL_JOINT_BAND_LIMITS` table — a `{ "lower": deg, "upper": deg }` range for the joint's allowed relative angle (0 = straight; a small side ~5 resists hyperextension, a large side ~150 is the fold, and a narrow/locked band such as `{60, 100}` forces a permanently-bent limb). FORWARD arms `{-150°, +5°}`, legs `{-5°, +150°}`; LEFT/RIGHT override both sides. Joints missing from the table fall back to the `RAGDOLL_JOINTS` `limit` string (`elbow_knee` `-5°..+150°`, `elbow_knee_ccw` `-150°..+5°`). Shoulders/hips allow ±160°; the neck is free (`angular_limit_enabled = false`).
**Joint bend configuration (EDIT ME):** every per-joint bend *decision* in the ragdoll cycle lives in three `const` tables at the top of `scripts/stickman_rig.gd`, keyed by facing profile, so joints can be tuned without touching pipeline code:
**Joint bend configuration (EDIT ME):** every per-joint bend *decision* in the ragdoll cycle lives in three `const` tables at the top of `scripts/characters/stickman/stickman_rig.gd`, keyed by facing profile, so joints can be tuned without touching pipeline code:
| Const | Shape | Purpose |
|---|---|---|
@@ -399,30 +424,30 @@ Ragdoll bodies spawn fully visible — the entry handoff is instant (the ragdoll
`_play_stand_up()` then starts a **2.0 s** sine-in-out tween (`STAND_UP_DURATION`) of the 6 markers → their **pre-ragdoll** captured values and of the arm + head bones to the `_recovery_bones` targets computed by the snap — upper arm bones take the shortest path, lower arm bones use `_natural_joint_target(current, target, offset)` (offset = `bone_angle_lower − bone_angle_upper`; the through-straight rule is evaluated in the joint's rendered space and a `"shortest"` entry in `RECOVERY_JOINT_PATH` bypasses it). The **legs are IK-driven** toward their tweening, ground-clamped ankle markers (the 2 leg mods stay enabled). The arm mods + head LookAt stay disabled during the tween. The baked `stand_up` animation is **not** played — a fixed first keyframe can never match an arbitrary ragdoll rest pose. On tween finish, `_on_stand_up_finished()` restores the pre-ragdoll facing profile + per-joint bend flags + head LookAt state, re-enables the arm mods, re-shows `Body/*`, sets `state = ANIMATED`, and emits — the resumed IK solve reproduces the pre-ragdoll pose exactly (the end pose matches the pre-ragdoll frame, including mid-walk poses).
**Interruptibility:** `set_ragdoll(true)` during `RECOVERING` kills the stand-up tween and rebuilds the ragdoll; `set_ragdoll(false)` during `RAGDOLL` routes through `_start_recovery()`; repeated `set_ragdoll` calls are idempotent. All ragdoll nodes are spawned procedurally — `master_rig.tscn` is **not** modified (the `stand_up` / `walk_left` / `walk_right` animations are baked into the scene's `AnimationLibrary` by the `create_animations.gd` editor script, which runs manually in the editor; the baked `stand_up` is an authored reference and the recovery path does not play it). The ragdoll bodies spawn under a **world sibling** (not the rig root), so the whole-rig Y-axis mirror does not affect them; `walk_left` remains in the library but is **not used at runtime** — `walk_to()` plays the canonical `walk_right` clip for every direction (root-mirrored for LEFT).
**Interruptibility:** `set_ragdoll(true)` during `RECOVERING` kills the stand-up tween and rebuilds the ragdoll; `set_ragdoll(false)` during `RAGDOLL` routes through `_start_recovery()`; repeated `set_ragdoll` calls are idempotent. All ragdoll nodes are spawned procedurally — `stickman_rig.tscn` is **not** modified (the `stand_up` / `walk_left` / `walk_right` animations are baked into the scene's `AnimationLibrary` by the `create_animations.gd` editor script, which runs manually in the editor; the baked `stand_up` is an authored reference and the recovery path does not play it). The ragdoll bodies spawn under a **world sibling** (not the rig root), so the whole-rig Y-axis mirror does not affect them; `walk_left` remains in the library but is **not used at runtime** — `walk_to()` plays the canonical `walk_right` clip for every direction (root-mirrored for LEFT).
### 18. Sandbox Stage Builder
The **Sandbox Stage Builder** (Phase 2) is a standalone, kid-friendly director sandbox: a visual stage where you place terrain, props, and stickmen from a palette, then switch between **Edit** (build), **Direct** (director/rule authoring), and **Play** (physics simulation). It is **not wired into the editor** — run via **F6** on `res://scenes/sandbox_stage.tscn`.
The **Sandbox Stage Builder** (Phase 2) is a standalone, kid-friendly director sandbox: a visual stage where you place terrain, props, and stickmen from a palette, then switch between **Edit** (build), **Direct** (director/rule authoring), and **Play** (physics simulation). It is **not wired into the editor** — run via **F6** on `res://scenes/stage/sandbox_stage.tscn`.
The stage is intentionally **extendable**: the spawn palette is registry-driven (adding an object type = appending one dictionary entry), selection hit-tests arbitrary `Node2D`s geometrically, gizmos drive `global_position`/`global_rotation`, and core events are exposed as signals for future phases (Action Queue, Triggers, Save/Load). Styling defaults (fonts/sizes/colors/grid) live in a hand-editable `res://sandbox_theme.json` (Phase 4b, §21).
| File | Purpose |
|---|---|
| `res://scenes/sandbox_stage.tscn` | The stage scene: root `Node2D` + `Camera2D` + empty `World` container. |
| `res://scripts/sandbox_stage.gd` | `class_name SandboxStage`, `extends Node2D` — root controller (3-mode state machine, placement + drag-painting, camera, deletion, bottom status bar, mode badge/frame/cursors, signals); **Phase 3b** instantiates the `AssetSelector` grid popup + the two thumbnail renderers, owns the selector open/close flow and the lazy per-frame thumbnail drain, and wires the Stickman/Prop palette buttons to the selector (§22). **Phase 3c** adds the editor-tool wiring: the `QueuePanel` / `RulePanel` / `ActionEditor` / `RuleEditor` / `WaypointContext` instantiation, the unified `CaptureKind` target-capture system, "Edit Queue…"/"Edit Rules…" entry points + right-click stickman/waypoint context menus, the shared confirmation dialog, and consequence-only rule editing (§23). |
| `res://scripts/stage_spawner.gd` | `class_name StageSpawner`, `extends RefCounted` — registry-driven factory reusing `TerrainUtils` / `PropUtils` / `StickmanFactory`; exposes `is_terrain_id()` / `get_template_aabb()` / `spawn_id` tagging. `get_template_aabb()` returns the **sanitized** template AABB (mirrors `_spawn_terrain()`'s 16-px grid pass), so it doubles as the block-unit paint stride. **Phase 3b:** registry ids `ground/ramp/step/prop/stickman/area` (separate `crate`/`ball` entries removed); holds the session state `selected_stickman_path` / `selected_prop_id` and a per-path `_stickman_cache`; `prop` and `stickman` spawn the **selected** asset. |
| `res://scripts/stickman_library.gd` | **Phase 3b** `class_name StickmanLibrary`, `extends RefCounted` — scans `res://stickmen/*.stk` into `{path, name, data}` entry models (corrupt/missing-`body_parts` files skipped, empty `stickman_name` → filename basename), with `make_entry(path)` for arbitrary Browse-chosen paths (§22). |
| `res://scripts/prop_library.gd` | **Phase 3b** `class_name PropLibrary`, `extends RefCounted` — static registry of the 4 prop templates (Crate/Wood, Ball/Rubber, Plank/Metal, Triangle/Cardboard) with their `PropUtils.create_*()` payloads + material presets; `get_default_id()` = `"crate"` (§22). |
| `res://scripts/asset_selector.gd` | **Phase 3b** `class_name AssetSelector`, `extends PopupPanel` — grid UI controller (root of `scenes/asset_selector.tscn`): pagination 12/page (4×3), Prev/Next/page label, empty-state label, per-cell thumbnail + name + (prop) material badge, hover styling (no pre-highlight on open), window-resize re-centering, Browse/Refresh/Close wiring, Esc handling; signals `item_selected` / `cancelled` / `browse_requested` / `refresh_requested` (§22). |
| `res://scripts/stage_selection.gd` | `class_name StageSelection`, `extends RefCounted` — hover/click/box selection via geometric AABB hit-testing. |
| `res://scripts/stage_gizmos.gd` | `class_name StageGizmos`, `extends Node2D` — hover highlight, selection outline, rotate ring handle. |
| `res://scripts/stage_grid.gd` | `class_name StageGrid`, `extends Node2D` — optional world-space grid overlay (major line every 5 cells). |
| `res://scripts/stage_placement_overlay.gd` | **Phase 4b** `class_name StagePlacementOverlay`, `extends Node2D` — world-space overlay drawing the terrain drag-painting guide line + the director action trajectory / ghost marker. |
| `res://scripts/thumbnails/stickman_thumbnail.gd` | **Phase 3b** `class_name StickmanThumbnail`, `extends Node` — renders a parsed `.stk` into a 200×200 `Texture2D` by spawning the **real rig** (`StickmanFactory.spawn_from_data`) inside an offscreen `SubViewport`, framing it and capturing after a double `frame_post_draw` (§22). |
| `res://scripts/thumbnails/prop_thumbnail.gd` | **Phase 3b** `class_name PropThumbnail`, `extends Node` — renders a prop template into a 200×200 `Texture2D` via a lightweight `Polygon2D` + `Line2D` visual (no `RigidBody2D`, so no gravity), tinted by the material preset (§22). |
| `res://scripts/thumbnails/thumbnail_cache.gd` | **Phase 3b** `class_name ThumbnailCache`, `extends RefCounted` — disk PNG cache under `user://thumbnails/`: stickman key = `basename_mtime`, prop key = `id_v<PROP_VERSION>`; `load_png`/`save_png`/`clean_stale_stickmen` (§22). |
| `res://scenes/asset_selector.tscn` | **Phase 3b** `PopupPanel` root + `asset_selector.gd` — minimal shell/layout skeleton (title bar, empty `GridContainer`, footer Prev/Next/Browse/Refresh/Close); all dynamic per-cell content is built in code at runtime (§22). |
| `res://scenes/stage/sandbox_stage.tscn` | The stage scene: root `Node2D` + `Camera2D` + empty `World` container. |
| `res://scripts/editors/stage/sandbox_stage.gd` | `class_name SandboxStage`, `extends Node2D` — root controller (3-mode state machine, placement + drag-painting, camera, deletion, bottom status bar, mode badge/frame/cursors, signals); **Phase 3b** instantiates the `AssetSelector` grid popup + the two thumbnail renderers, owns the selector open/close flow and the lazy per-frame thumbnail drain, and wires the Stickman/Prop palette buttons to the selector (§22). **Phase 3c** adds the editor-tool wiring: the `QueuePanel` / `RulePanel` / `ActionEditor` / `RuleEditor` / `WaypointContext` instantiation, the unified `CaptureKind` target-capture system, "Edit Queue…"/"Edit Rules…" entry points + right-click stickman/waypoint context menus, the shared confirmation dialog, and consequence-only rule editing (§23). |
| `res://scripts/stage/stage_spawner.gd` | `class_name StageSpawner`, `extends RefCounted` — registry-driven factory reusing `TerrainUtils` / `PropUtils` / `StickmanFactory`; exposes `is_terrain_id()` / `get_template_aabb()` / `spawn_id` tagging. `get_template_aabb()` returns the **sanitized** template AABB (mirrors `_spawn_terrain()`'s 16-px grid pass), so it doubles as the block-unit paint stride. **Phase 3b:** registry ids `ground/ramp/step/prop/stickman/area` (separate `crate`/`ball` entries removed); holds the session state `selected_stickman_path` / `selected_prop_id` and a per-path `_stickman_cache`; `prop` and `stickman` spawn the **selected** asset. |
| `res://scripts/characters/stickman/stickman_library.gd` | **Phase 3b** `class_name StickmanLibrary`, `extends RefCounted` — scans `res://content/stickmen/*.stk` into `{path, name, data}` entry models (corrupt/missing-`body_parts` files skipped, empty `stickman_name` → filename basename), with `make_entry(path)` for arbitrary Browse-chosen paths (§22). |
| `res://scripts/props/prop_library.gd` | **Phase 3b** `class_name PropLibrary`, `extends RefCounted` — static registry of the 4 prop templates (Crate/Wood, Ball/Rubber, Plank/Metal, Triangle/Cardboard) with their `PropUtils.create_*()` payloads + material presets; `get_default_id()` = `"crate"` (§22). |
| `res://scripts/shared/ui/asset_selector.gd` | **Phase 3b** `class_name AssetSelector`, `extends PopupPanel` — grid UI controller (root of `scenes/stage/asset_selector.tscn`): pagination 12/page (4×3), Prev/Next/page label, empty-state label, per-cell thumbnail + name + (prop) material badge, hover styling (no pre-highlight on open), window-resize re-centering, Browse/Refresh/Close wiring, Esc handling; signals `item_selected` / `cancelled` / `browse_requested` / `refresh_requested` (§22). |
| `res://scripts/stage/stage_selection.gd` | `class_name StageSelection`, `extends RefCounted` — hover/click/box selection via geometric AABB hit-testing. |
| `res://scripts/stage/stage_gizmos.gd` | `class_name StageGizmos`, `extends Node2D` — hover highlight, selection outline, rotate ring handle. |
| `res://scripts/stage/stage_grid.gd` | `class_name StageGrid`, `extends Node2D` — optional world-space grid overlay (major line every 5 cells). |
| `res://scripts/stage/stage_placement_overlay.gd` | **Phase 4b** `class_name StagePlacementOverlay`, `extends Node2D` — world-space overlay drawing the terrain drag-painting guide line + the director action trajectory / ghost marker. |
| `res://scripts/shared/thumbnails/stickman_thumbnail.gd` | **Phase 3b** `class_name StickmanThumbnail`, `extends Node` — renders a parsed `.stk` into a 200×200 `Texture2D` by spawning the **real rig** (`StickmanFactory.spawn_from_data`) inside an offscreen `SubViewport`, framing it and capturing after a double `frame_post_draw` (§22). |
| `res://scripts/shared/thumbnails/prop_thumbnail.gd` | **Phase 3b** `class_name PropThumbnail`, `extends Node` — renders a prop template into a 200×200 `Texture2D` via a lightweight `Polygon2D` + `Line2D` visual (no `RigidBody2D`, so no gravity), tinted by the material preset (§22). |
| `res://scripts/shared/thumbnails/thumbnail_cache.gd` | **Phase 3b** `class_name ThumbnailCache`, `extends RefCounted` — disk PNG cache under `user://thumbnails/`: stickman key = `basename_mtime`, prop key = `id_v<PROP_VERSION>`; `load_png`/`save_png`/`clean_stale_stickmen` (§22). |
| `res://scenes/stage/asset_selector.tscn` | **Phase 3b** `PopupPanel` root + `asset_selector.gd` — minimal shell/layout skeleton (title bar, empty `GridContainer`, footer Prev/Next/Browse/Refresh/Close); all dynamic per-cell content is built in code at runtime (§22). |
| `res://sandbox_theme.json` | **Phase 4b** hand-editable styling defaults (font paths/sizes, grid snap default, mode accent colors). **Phase 3c** extends the `fonts` block with style variants (`ui_font_bold`/`ui_font_italic`), per-widget size/style flags, and an optional per-widget `{size, bold, italic}` object form (§21.3 / spec §14). Loaded at `_ready()`; missing/malformed falls back to built-in constants. |
**Mode management** — a single **3-segment switcher** `[ ✏️ Edit | 🎬 Direct | ▶️ Play ]` sits at the far left of the top bar (`enum StageMode { EDIT, DIRECT, PLAY }`). Each mode shows a **contextual toolbar** and a **mode badge pill** in the viewport's top-left corner (`✏️ EDIT` cyan, `🎬 DIRECTING` amber, `▶️ SIMULATING` green), sourced from `sandbox_theme.json` `mode_colors`:
@@ -438,7 +463,7 @@ The stage is intentionally **extendable**: the spawn palette is registry-driven
- **Ground / Ramp / Step** — `TerrainBlock` terrain. Single **terrain** palette items are **drag-painted** (§21): left-click-and-drag paints a staircase run of blocks; a terrain drag commits on release. Non-terrain (props/stickman/area) palette items keep the Phase 2 **single-click repeated placement**.
- **Prop** (Phase 3b) — opens a **selector grid** (§22) of the 4 prop templates; selecting one sets the `selected_prop_id` and enters placement mode spawning that `PropBlock` (`PropUtils.create_*()` + the matching material preset). Replaces the earlier separate **Crate** / **Ball** buttons.
- **Stickman** (Phase 3b) — opens a **selector grid** (§22) of every `.stk` in `res://stickmen/`; selecting one sets the `selected_stickman_path` and enters placement mode spawning a `StickmanRig` from that file (offset `(0, -385)` so the feet land on the cursor). Replaces the previous hard-coded single-`test.stk` placement.
- **Stickman** (Phase 3b) — opens a **selector grid** (§22) of every `.stk` in `res://content/stickmen/`; selecting one sets the `selected_stickman_path` and enters placement mode spawning a `StickmanRig` from that file (offset `(0, -385)` so the feet land on the cursor). Replaces the previous hard-coded single-`test.stk` placement.
- **Area** (Phase 4) — a `TriggerArea` sensor.
Clicking a palette button enters **placement mode**, which shows a translucent **ghost** of the object under the cursor (snapped to the grid when Snap is on); the stickman ghost is a static standing figure. **Left-click** keeps the Phase 2 repeated-placement behavior (each click/drag commits one placement and the tool stays active); **right-click** (or **Esc**) is the explicit **"put the tool down"** gesture — it cancels any in-progress drag, leaves already-placed cells if a drag had committed, and un-toggles the palette button. Each placement emits `object_placed(node)`. The **Stickman** and **Prop** buttons open a selector grid first (a modal `PopupPanel`, §22) and only enter placement mode once an asset is chosen.
@@ -470,14 +495,14 @@ Clicking a palette button enters **placement mode**, which shows a translucent *
### 19. Director Tool (Phase 3a)
The **Director Tool** (Phase 3a) turns the Sandbox Stage into a mini director's workspace: click a stickman, choose actions from a popup, see the script as waypoints/badges in Edit mode, then press **Play** to run every stickman's action queue. It is **not wired into the editor** — run via **F6** on `res://scenes/sandbox_stage.tscn`.
The **Director Tool** (Phase 3a) turns the Sandbox Stage into a mini director's workspace: click a stickman, choose actions from a popup, see the script as waypoints/badges in Edit mode, then press **Play** to run every stickman's action queue. It is **not wired into the editor** — run via **F6** on `res://scenes/stage/sandbox_stage.tscn`.
| File | Purpose |
|---|---|
| `res://scripts/stickman_rig.gd` | Extended with navigation/walking, speech, an action queue, and the queue runner state machine (see API below). |
| `res://scripts/stickman_speech_bubble.gd` | `class_name SpeechBubble`, `extends Node2D` — a world-space speech bubble drawn via `_draw()` (`ThemeDB.fallback_font`), a child of the rig above the head. |
| `res://scripts/stage_director_visuals.gd` | `class_name StageDirectorVisuals`, `extends Node2D` — Edit-mode director overlay (waypoint dots, dashed connectors, action badges, order numbers); hidden in Play. **Phase 3c** adds `hit_test_waypoint_action()` (rig/index/pos), `set_edit_waypoint()`/`clear_edit_waypoint()` with the pulsing edit highlight for visual walk re-placement, and rule-label → editor routing (§23). |
| `res://scripts/sandbox_stage.gd` | Extended with the **Direct** palette button, the action popup + speak/wait dialogs, a code-built `NavigationRegion2D` re-baked on terrain edits, and Play mode now starting each stickman's queue. |
| `res://scripts/characters/stickman/stickman_rig.gd` | Extended with navigation/walking, speech, an action queue, and the queue runner state machine (see API below). |
| `res://scripts/speech/stickman_speech_bubble.gd` | `class_name SpeechBubble`, `extends Node2D` — a world-space speech bubble drawn via `_draw()` (`ThemeDB.fallback_font`), a child of the rig above the head. |
| `res://scripts/stage/stage_director_visuals.gd` | `class_name StageDirectorVisuals`, `extends Node2D` — Edit-mode director overlay (waypoint dots, dashed connectors, action badges, order numbers); hidden in Play. **Phase 3c** adds `hit_test_waypoint_action()` (rig/index/pos), `set_edit_waypoint()`/`clear_edit_waypoint()` with the pulsing edit highlight for visual walk re-placement, and rule-label → editor routing (§23). |
| `res://scripts/editors/stage/sandbox_stage.gd` | Extended with the **Direct** palette button, the action popup + speak/wait dialogs, a code-built `NavigationRegion2D` re-baked on terrain edits, and Play mode now starting each stickman's queue. |
**Direct tool workflow (Edit):**
@@ -517,7 +542,7 @@ The **Director Tool** (Phase 3a) turns the Sandbox Stage into a mini director's
- Terrain adaptation is enabled only in **Play**. `_enter_play_mode()` calls `begin_terrain_fall()` before `start_queue()`. An unsupported animated rig free-falls kinematically with bounded gravity; walking animation pauses while airborne and resumes on landing. EDIT and DIRECT disable the controller and restore the authored transform/standing pose.
- The center support sample uses three downward rays at horizontal offsets `[-24, 0, +24]`; each lower-leg endpoint uses an independent three-ray fan at `[-4, 0, +4]`. A hit must be a `TerrainBlock` with an upward-facing normal, and the rig's own collision-proxy RIDs are excluded. The highest valid surface wins, preventing a lower floor under a ramp from pulling a leg through the ramp.
- `master_rig.tscn` separates animated `AnimationTargets/Left_Leg_Base` and `Right_Leg_Base` markers from live `IK_Targets/Left_Leg` and `Right_Leg` targets. The rig blends complementary continuous stance/swing weights, holds stance contacts in world space, and applies a predictive 24 px foot guard for smooth contact transitions. Root elevation accumulates across ramps and steps, with centerline pelvis compensation; no foot/ankle bones are added.
- `stickman_rig.tscn` separates animated `AnimationTargets/Left_Leg_Base` and `Right_Leg_Base` markers from live `IK_Targets/Left_Leg` and `Right_Leg` targets. The rig blends complementary continuous stance/swing weights, holds stance contacts in world space, and applies a predictive 24 px foot guard for smooth contact transitions. Root elevation accumulates across ramps and steps, with centerline pelvis compensation; no foot/ankle bones are added.
- Navigation uses shallow ribbons over exposed upward-facing TerrainBlock edges, plus bridges between surface endpoints within 64 px horizontally and 96 px vertically for valid steps. The `NavigationAgent2D` remains at local `(0, +385)` and uses the selected walk speed; navigation supplies horizontal progress while terrain following owns vertical root motion and arrival avoids Y-correction jitter.
**Nav-mesh behavior:**
@@ -526,7 +551,7 @@ The **Director Tool** (Phase 3a) turns the Sandbox Stage into a mini director's
- **Auto re-bake** on any terrain edit: the nav mesh is marked dirty on terrain place / move / rotate (`transform_committed`) / delete and re-baked once per frame (coalescing bursts) in `_process`.
- Each stickman's `NavigationAgent2D` is a child of the rig **at the feet** (local `(0, +385)` = `-FOOT_OFFSET`), so it sits on the ground-level mesh; agent and region share the default navigation map, layer 1.
- `walk_to(target)` treats `target` as a **feet/ground destination**; the rig converts ground-level path points back to root positions with `FOOT_OFFSET := (0, -385)`. Walking is **hybrid**: an **on-mesh target** follows the nav path (`_walk_mode = "nav"`), while an **off-mesh / unreachable target** (empty path **or** `is_target_reachable() == false`) switches to **direct straight-line steering** toward the clicked waypoint (`_walk_mode = "direct"`, root target = waypoint + `FOOT_OFFSET`) — it is **not** rejected with a warning and the rig does **not** stand still. Nav reads are deferred until the map has synced (`map_get_iteration_id(...) != 0`) and the path query is forced via `get_next_path_position()` before any reachability/finished check, so a fresh target is never misjudged as finished/unreachable after a single step.
- Pathing is kinematic (`global_position.move_toward`); `avoidance_enabled = false` in 3a, so stickmen path through props and each other (deferred — see `docs/tech_debt_and_optimizations.md` #13).
- Pathing is kinematic (`global_position.move_toward`); `avoidance_enabled = false` in 3a, so stickmen path through props and each other (deferred — see `docs/architecture/tech_debt_and_optimizations.md` #13).
- **Walk/nav debugging (off by default):** `const DEBUG_WALK` in `stickman_rig.gd` (per-frame walk trace with `mode=nav|direct`, plus one-shot `walk_to`/finish/cancel prints) and `const DEBUG_STAGE` in `sandbox_stage.gd` (`[stage]` target capture, `[nav]` baked verts/polys, `[stage] PLAY rigs`).
**StickmanRig director API surface:**
@@ -553,19 +578,19 @@ The **Director Tool** (Phase 3a) turns the Sandbox Stage into a mini director's
### 20. Triggers & Event System (Phase 4)
**Phase 4** adds **reactive storytelling** to the Sandbox Stage. While Phase 3a gave directors *sequential* control (actions in a fixed order per stickman), Phase 4 adds *reactive* control — **"When X happens, do Y"** — via event rules that span objects. It is **not wired into the editor** — run via **F6** on `res://scenes/sandbox_stage.tscn`.
**Phase 4** adds **reactive storytelling** to the Sandbox Stage. While Phase 3a gave directors *sequential* control (actions in a fixed order per stickman), Phase 4 adds *reactive* control — **"When X happens, do Y"** — via event rules that span objects. It is **not wired into the editor** — run via **F6** on `res://scenes/stage/sandbox_stage.tscn`.
The rule concept is **When → Then**: a *trigger* (an event on some object) fires a list of *actions* on a target object. Rules are stored as `_event_rules: Array[Dictionary]` of `{id, trigger, actions}` in `sandbox_stage.gd`, where `trigger` carries `{type, source, target, ...}` instance references and `actions` is an array of Phase 3a action dicts (`{"type":"walk_to","target":...}`, etc.).
| File | Purpose |
|---|---|
| `res://scripts/trigger_area.gd` | NEW `class_name TriggerArea`, `extends Node2D` — a placeable sensor (`@export size: Vector2`, default 96×96) with a centered **local-space** `get_area_rect() -> Rect2` and a translucent green `_draw()` fill + dashed border. **No physics, no signals** — it is a pure geometric region evaluated by the stage. |
| `res://scripts/sandbox_stage.gd` | Extended with the rule system: `_event_rules`, the geometric area-entry engine, physics contact routing for stickman/prop collisions (Phase 4.5), the rule-builder UI state machine, and rule auto-cleanup. |
| `res://scripts/stickman_rig.gd` | `arrived` gained a `target: Vector2` payload; new `enqueue_reactive(actions)` appends reactive actions without replaying the queue. |
| `res://scripts/prop_block.gd` | NEW physics contact signals `collided(other, contact_normal, contact_position)` and `separated(other)`; prop-vs-prop and Phase 4.5 stickman contacts share the contact-monitoring path. |
| `res://scripts/stage_director_visuals.gd` | Extended with rule visualization + `set_rules()` / `hit_test_rule()` / `hit_test_waypoint()`. |
| `res://scripts/stage_spawner.gd` | New `"area"` palette registry entry (label **"Area"**); duck-typed `get_area_rect` AABB branch in `get_world_aabb`. |
| `res://scripts/stage_selection.gd` | Same duck-typed `get_area_rect` AABB branch for selecting/clicking trigger areas. |
| `res://scripts/stage/trigger_area.gd` | NEW `class_name TriggerArea`, `extends Node2D` — a placeable sensor (`@export size: Vector2`, default 96×96) with a centered **local-space** `get_area_rect() -> Rect2` and a translucent green `_draw()` fill + dashed border. **No physics, no signals** — it is a pure geometric region evaluated by the stage. |
| `res://scripts/editors/stage/sandbox_stage.gd` | Extended with the rule system: `_event_rules`, the geometric area-entry engine, physics contact routing for stickman/prop collisions (Phase 4.5), the rule-builder UI state machine, and rule auto-cleanup. |
| `res://scripts/characters/stickman/stickman_rig.gd` | `arrived` gained a `target: Vector2` payload; new `enqueue_reactive(actions)` appends reactive actions without replaying the queue. |
| `res://scripts/props/prop_block.gd` | NEW physics contact signals `collided(other, contact_normal, contact_position)` and `separated(other)`; prop-vs-prop and Phase 4.5 stickman contacts share the contact-monitoring path. |
| `res://scripts/stage/stage_director_visuals.gd` | Extended with rule visualization + `set_rules()` / `hit_test_rule()` / `hit_test_waypoint()`. |
| `res://scripts/stage/stage_spawner.gd` | New `"area"` palette registry entry (label **"Area"**); duck-typed `get_area_rect` AABB branch in `get_world_aabb`. |
| `res://scripts/stage/stage_selection.gd` | Same duck-typed `get_area_rect` AABB branch for selecting/clicking trigger areas. |
**Trigger types:**
@@ -618,7 +643,7 @@ Edge-triggered dictionaries (which events already fired) are reset on Play/Edit
### 20.2 Phase 4.5 — Per-Part Collision Bodies and Hit Reactions
Phase 4.5 gives an ANIMATED `StickmanRig` a physical collision surface in the Sandbox Stage. It is runtime-only and does not change `master_rig.tscn`, the `.stk` format, or the editor layout.
Phase 4.5 gives an ANIMATED `StickmanRig` a physical collision surface in the Sandbox Stage. It is runtime-only and does not change `stickman_rig.tscn`, the `.stk` format, or the editor layout.
- `StickmanRig` builds ten metadata-tagged `CollisionBody` `AnimatableBody2D` proxies (head, torso, and eight limbs). They are top-level relative to their `Body/*` visual containers, use layer/mask `1`, set `sync_to_physics = false`, and copy each rendered visual's global transform in `_sync_collision_bodies()` every physics frame. This explicit synchronization keeps proxies aligned with `RemoteTransform2D`-driven geometry during poses and WALK-left mirroring.
- Limb and torso proxies use fitted `CapsuleShape2D`s in the adapter's hanging frame. The head uses a `CircleShape2D` centered from the mounted head geometry's local bounds, with a minimum radius of `90 px` and growth when the mounted geometry is larger. These are intentionally approximate collision shapes, not per-shape polygons.
@@ -631,7 +656,7 @@ Focused verification: `tests/test_phase45_collision_bodies.gd` (223 assertions),
### 21. Phase 4b Polish — Mode Switcher, Theme, Terrain Painting, Director UX, Walk Jitter Fix
**Phase 4b** is a **polish + bugfix** pass over the Sandbox Stage Builder. It does **not** add new gameplay systems; it restructures the top bar into a **3-segment mode switcher** with contextual toolbars, adds a **bottom status bar** with live mouse coordinates, a **mode badge/frame** and per-mode cursors, upgrades **terrain placement** from single-click into **drag-to-paint** with a grid spatial dictionary, makes the **director's "pick a target" flows** kid-friendly (cursor-attached tooltip, rubber-band trajectory, reticle cursor), introduces a hand-editable **`res://sandbox_theme.json`**, and fixes the **walk-waypoint arrival jitter**. Run via **F6** on `res://scenes/sandbox_stage.tscn`; not wired into the editor. See §18 for the mode-switcher / status-bar / badge / cursor surface; this section details the placement, styling, director-UX, and bugfix internals.
**Phase 4b** is a **polish + bugfix** pass over the Sandbox Stage Builder. It does **not** add new gameplay systems; it restructures the top bar into a **3-segment mode switcher** with contextual toolbars, adds a **bottom status bar** with live mouse coordinates, a **mode badge/frame** and per-mode cursors, upgrades **terrain placement** from single-click into **drag-to-paint** with a grid spatial dictionary, makes the **director's "pick a target" flows** kid-friendly (cursor-attached tooltip, rubber-band trajectory, reticle cursor), introduces a hand-editable **`res://sandbox_theme.json`**, and fixes the **walk-waypoint arrival jitter**. Run via **F6** on `res://scenes/stage/sandbox_stage.tscn`; not wired into the editor. See §18 for the mode-switcher / status-bar / badge / cursor surface; this section details the placement, styling, director-UX, and bugfix internals.
#### 21.1 Terrain drag-painting (Edit mode)
@@ -655,7 +680,7 @@ Block centers are `block_cell * stride`, and Bresenham runs over block units. A
5. **Release** commits the batch **atomically**: only the "empty" block units spawn (all in one frame), the nav mesh is marked dirty **once** (`_nav_dirty = true`, not per node), the grid dictionary is rebuilt, and `object_placed(node)` fires per node. LMB keeps repeated placement active for the next drag (the placement ghost re-arms).
6. **RMB** (or **Esc**) ends draw/placement mode — it cancels an in-progress drag (nothing is placed until release), clears the guide line, and un-toggles the palette button and restores the cursor.
**Grid spatial dictionary** (`_grid_cells`): a runtime `Dictionary` on `SandboxStage` keyed by 16-px grid-cell `Vector2i` (`StageSpawner.TERRAIN_GRID_SIZE`) → `Array[Node2D]`. It is **advisory only** — it drives the 3-state occupancy query (green/neutral/red tint + skip) but is **never authoritative for physics** or for the block-unit paint stride; the `World` tree is the source of truth. Occupancy marks **all 16-px cells covered by the node's world AABB** via `_rasterize_aabb_to_cells()` (for props/stickmen/areas it rasterizes `StageSelection.get_world_aabb()`), so one Ground block (192×32) spans ≈ 12×2 dictionary cells even though it paints as a single block unit. It is populated on place, updated on move/rotate/delete, and rebuilt on grid-size change. Terrain nodes carry a `spawn_id` string (`TerrainBlock.spawn_id`, set by `StageSpawner.spawn_id`) so same-template overlaps are detectable; `StageSpawner.is_terrain_id()` distinguishes terrain registry entries and `get_template_aabb(id)` exposes a terrain template's **sanitized** local AABB for ghost sizing / block-unit stride / cell rasterization.
**Grid spatial dictionary** (`_grid_cells`): a runtime `Dictionary` on `SandboxStage` keyed by 16-px grid-cell `Vector2i` (`StageSpawner.TERRAIN_GRID_SIZE`) → `Array[Node2D]`. It is **advisory only** — it drives the 3-state occupancy query (green/neutral/red tint + skip) but is **never authoritative for physics** or for the block-unit paint stride; the `World` tree is the source of truth. Occupancy marks **all 16-px cells covered by the node's world AABB** via `_rasterize_aabb_to_cells()` (for content/props/stickmen/areas it rasterizes `StageSelection.get_world_aabb()`), so one Ground block (192×32) spans ≈ 12×2 dictionary cells even though it paints as a single block unit. It is populated on place, updated on move/rotate/delete, and rebuilt on grid-size change. Terrain nodes carry a `spawn_id` string (`TerrainBlock.spawn_id`, set by `StageSpawner.spawn_id`) so same-template overlaps are detectable; `StageSpawner.is_terrain_id()` distinguishes terrain registry entries and `get_template_aabb(id)` exposes a terrain template's **sanitized** local AABB for ghost sizing / block-unit stride / cell rasterization.
#### 21.2 Director targeting UX (Direct / rule-building)
@@ -671,7 +696,7 @@ The Sandbox theme is schema `2.0` and now covers the code-built chrome,
palette, selectors, dialogs, overlays, reusable widget data, fonts, colors,
and grid defaults. See the complete current key reference, examples, legacy
compatibility notes, and troubleshooting guide in
[docs/SANDBOX_THEME.md](docs/SANDBOX_THEME.md).
[docs/architecture/SANDBOX_THEME.md](docs/architecture/SANDBOX_THEME.md).
The short version: edit `res://sandbox_theme.json`, restart the Sandbox scene
to reload it, and remember that the live grid size/snap/grid-visibility remain
@@ -693,13 +718,13 @@ The Phase 3a walk could **jitter up/down at a waypoint** instead of stopping. Ro
The result: `mode` stays constant for the whole walk, exactly one `arrived` fires, the rig's `global_position` is unchanged after arrival, and the walk/body-bob animation stops cleanly. `DEBUG_WALK` / `DEBUG_STAGE` (off by default) can be enabled to capture the `mode`/`dist`/`next`/`final` trace at arrival.
#### 21.5 Head LookAt solver fix (`master_rig.tscn`)
#### 21.5 Head LookAt solver fix (`stickman_rig.tscn`)
`master_rig.tscn`'s Head `SkeletonModification2DLookAt` now uses a **full-range, non-inverted band solved in global space** (`constraint_angle_min = -180`, `constraint_angle_max = 180`, `constraint_invert = false`, `constraint_in_localspace = false`). Under the previous ~55°-clamped, inverted, local-space band, the aim solver could oscillate frame-to-frame as the look direction crossed the band boundary, causing a per-frame **mirror** of the head rather than smooth convergence. With the full-range global band there is no boundary to cross, so dragging the Head IK handle converges cleanly onto the aim point (see §14 Phase 9 Round 7).
`stickman_rig.tscn`'s Head `SkeletonModification2DLookAt` now uses a **full-range, non-inverted band solved in global space** (`constraint_angle_min = -180`, `constraint_angle_max = 180`, `constraint_invert = false`, `constraint_in_localspace = false`). Under the previous ~55°-clamped, inverted, local-space band, the aim solver could oscillate frame-to-frame as the look direction crossed the band boundary, causing a per-frame **mirror** of the head rather than smooth convergence. With the full-range global band there is no boundary to cross, so dragging the Head IK handle converges cleanly onto the aim point (see §14 Phase 9 Round 7).
### 22. Asset Library — Stickman & Prop Selector Grids (Phase 3b)
**Phase 3b** replaces the Sandbox Stage Builder's hard-coded single-stickman and single-prop palette buttons (**Crate** / **Ball** / always-`test.stk` **Stickman**) with **visual selector grids**: clicking **Stickman** or **Prop** opens a modal grid popup (`PopupPanel`) of selectable assets; picking one sets the spawner's session selection and enters placement mode with that asset's ghost. It is **not wired into the editor** — run via **F6** on `res://scenes/sandbox_stage.tscn`. **No `.stk` format change** (the `.stk` schema below is untouched this phase). Pixel rendering (thumbnails) is **manual/F6 verification only**; headless tests never assert on pixels.
**Phase 3b** replaces the Sandbox Stage Builder's hard-coded single-stickman and single-prop palette buttons (**Crate** / **Ball** / always-`test.stk` **Stickman**) with **visual selector grids**: clicking **Stickman** or **Prop** opens a modal grid popup (`PopupPanel`) of selectable assets; picking one sets the spawner's session selection and enters placement mode with that asset's ghost. It is **not wired into the editor** — run via **F6** on `res://scenes/stage/sandbox_stage.tscn`. **No `.stk` format change** (the `.stk` schema below is untouched this phase). Pixel rendering (thumbnails) is **manual/F6 verification only**; headless tests never assert on pixels.
**Workflow:** press the **Stickman** or **Prop** palette button → the grid opens (modal `PopupPanel`, ESC-closeable) → click a cell → the selection is cached session-only, the grid closes, and placement mode begins (ghost of the selected asset appears). Placing repeats the **selected** asset; switching selection re-opens the grid. `Ground`/`Ramp`/`Step` and `Area` remain direct placement buttons, unchanged.
@@ -707,12 +732,12 @@ The result: `mode` stays constant for the whole walk, exactly one `arrived` fire
| Grid | Backing | Cells | Details |
|---|---|---|---|
| **Stickman** | `StickmanLibrary.scan()` of `res://stickmen/*.stk` | one per `.stk` (name = `stickman_name` else filename basename) | Rig-rendered thumbnail per cell; **Browse…** (`*.stk` `FileDialog`) selects an arbitrary path; **Refresh** rescans; **empty-state** label when no files; **single-item skip** — exactly one file bypasses the grid and places directly |
| **Stickman** | `StickmanLibrary.scan()` of `res://content/stickmen/*.stk` | one per `.stk` (name = `stickman_name` else filename basename) | Rig-rendered thumbnail per cell; **Browse…** (`*.stk` `FileDialog`) selects an arbitrary path; **Refresh** rescans; **empty-state** label when no files; **single-item skip** — exactly one file bypasses the grid and places directly |
| **Prop** | `PropLibrary` (4 static templates) | Crate/Wood, Ball/Rubber, Plank/Metal, Triangle/Cardboard | Thumbnail + name + **material badge**; always a single page |
**Selection is session-only:** the chosen `selected_stickman_path` / `selected_prop_id` live in memory on `StageSpawner`, persist across `EDIT ⇄ DIRECT ⇄ PLAY` toggles, and reset on scene reload. **No disk save** (`user://sandbox_settings.json` is not extended). If the currently selected stickman path is no longer in the scan, the first entry is selected instead on open/refresh.
**Selector grid (`AssetSelector` / `scenes/asset_selector.tscn`):** a `PopupPanel` root (`exclusive = true`) built as a minimal shell — authored title bar, empty `GridContainer`, footer (`Prev` / page label / `Next` / `Browse…` / `Refresh` / `Close`) — with **all dynamic per-cell content** (texture + name + material badge) built in code at runtime. `open(kind, entries)` titles the popup ("Choose Your Stickman" / "Choose a Prop"), hides Browse/Refresh for props, and `popup_centered()`. Pagination slices **12/page (4×3)** with Prev/Next hidden when a single page; a static pure `page_bounds(total, page, page_size)` helper backs the slicing. Empty state shows a "no stickmen found" label instead of an empty grid. No cell is pre-highlighted on open (the previous selection-highlight border was removed). The popup re-centers on window resize (`size_changed` → `popup_centered()` while visible). Signals: `item_selected(entry)`, `cancelled()`, `browse_requested()`, `refresh_requested()`.
**Selector grid (`AssetSelector` / `scenes/stage/asset_selector.tscn`):** a `PopupPanel` root (`exclusive = true`) built as a minimal shell — authored title bar, empty `GridContainer`, footer (`Prev` / page label / `Next` / `Browse…` / `Refresh` / `Close`) — with **all dynamic per-cell content** (texture + name + material badge) built in code at runtime. `open(kind, entries)` titles the popup ("Choose Your Stickman" / "Choose a Prop"), hides Browse/Refresh for props, and `popup_centered()`. Pagination slices **12/page (4×3)** with Prev/Next hidden when a single page; a static pure `page_bounds(total, page, page_size)` helper backs the slicing. Empty state shows a "no stickmen found" label instead of an empty grid. No cell is pre-highlighted on open (the previous selection-highlight border was removed). The popup re-centers on window resize (`size_changed` → `popup_centered()` while visible). Signals: `item_selected(entry)`, `cancelled()`, `browse_requested()`, `refresh_requested()`.
**Thumbnails (rig-accurate, cached):** each 200×200 thumbnail is rendered **lazily, one per frame**, by a `Node` renderer owning a persistent offscreen `SubViewport` (`transparent_bg`, `UPDATE_ALWAYS`) with an enabled in-viewport `Camera2D`:
@@ -731,30 +756,30 @@ The result: `mode` stays constant for the whole walk, exactly one `arrived` fire
**Palette integration & Esc priority:** `_on_palette_toggled` routes `stickman`/`prop` presses to `_open_selector(id)` (not straight to `set_placement_mode`); un-pressing while the matching selector is open closes it. While the grid is open a **dim backdrop** (`_selector_dim`, a black `ColorRect` at `SELECTOR_DIM_ALPHA` = 0.5, mouse-ignoring, on the UI `CanvasLayer` behind the selector) is shown. `_open_selector` forces the palette button pressed, performs the single-item skip, and (for stickman) reselects the first entry when the selected path is absent. On `item_selected` the spawner's `selected_stickman_path`/`selected_prop_id` are set, the selector closes, and placement begins. `Browse…` builds a one-off entry via `StickmanLibrary.make_entry(path)` (toast on failure). Esc is handled in the `AssetSelector` (`_unhandled_input` → `cancelled`) and by the stage in its existing **Esc priority chain** — rule step → pending walk target → terrain drag → **selector** → DIRECT → placement → selection. An outside-click that closes the modal popup fires its `popup_hide` signal, which is routed to `_on_selector_cancelled()` (idempotency-guarded) so it likewise un-presses the palette button. While the selector is open, `_handle_world_click` / `_handle_mouse_motion` early-return (belt-and-suspenders on top of the modal `PopupPanel`).
**New scripts:** `stickman_library.gd` / `prop_library.gd` / `thumbnails/thumbnail_cache.gd` / `thumbnails/stickman_thumbnail.gd` / `thumbnails/prop_thumbnail.gd` / `asset_selector.gd` (+ `scenes/asset_selector.tscn`). **Modified:** `stage_spawner.gd` (registry `crate`/`ball` → `prop`; `selected_stickman_path`/`selected_prop_id` session state; per-path `_stickman_cache`; new `get_selected_stickman_path()`/`get_selected_prop_id()` getters), `sandbox_stage.gd` (selector integration), and `tests/test_phase4b1_fixes.gd` (`spawn("crate")` → `spawn("prop")`).
**New scripts:** `stickman_library.gd` / `prop_library.gd` / `thumbnails/thumbnail_cache.gd` / `thumbnails/stickman_thumbnail.gd` / `thumbnails/prop_thumbnail.gd` / `asset_selector.gd` (+ `scenes/stage/asset_selector.tscn`). **Modified:** `stage_spawner.gd` (registry `crate`/`ball` → `prop`; `selected_stickman_path`/`selected_prop_id` session state; per-path `_stickman_cache`; new `get_selected_stickman_path()`/`get_selected_prop_id()` getters), `sandbox_stage.gd` (selector integration), and `tests/test_phase4b1_fixes.gd` (`spawn("crate")` → `spawn("prop")`).
**Verification:** new headless suite `tests/test_phase3b_library.gd` (`extends SceneTree`, no pixel assertions) covering `StickmanLibrary.scan()`/corrupt-skip/`make_entry`, `PropLibrary.get_entries()`/`get_default_id()`, the `StageSpawner` registry ids (`ground/ramp/step/prop/stickman/area`), `_spawn_prop`/`_spawn_stickman` honoring `selected_prop_id`/`selected_stickman_path`, `ThumbnailCache` key/path formatting, `AssetSelector` pagination math (`PAGE_SIZE == 12`), and scene-load checks. Spec: `docs/phase_3b_asset_grid_spec.md`.
**Verification:** new headless suite `tests/test_phase3b_library.gd` (`extends SceneTree`, no pixel assertions) covering `StickmanLibrary.scan()`/corrupt-skip/`make_entry`, `PropLibrary.get_entries()`/`get_default_id()`, the `StageSpawner` registry ids (`ground/ramp/step/prop/stickman/area`), `_spawn_prop`/`_spawn_stickman` honoring `selected_prop_id`/`selected_stickman_path`, `ThumbnailCache` key/path formatting, `AssetSelector` pagination math (`PAGE_SIZE == 12`), and scene-load checks. Spec: `docs/specs/phase_3b_asset_grid_spec.md`.
### 23. Editor Tools — Action & Rule Editing (Phase 3c)
**Phase 3c** makes the Sandbox Stage's **action queues and event rules fully editable**. Where Phase 3a let directors only *append* actions and Phase 4 only *create/delete* rules, Phase 3c adds edit / delete / drag-reorder for both, a **waypoint right-click context menu** with visual walk re-placement and insert-before/after, and both a **full** and a **consequence-only** rule editor. The entire system is **registry-driven** — the action and trigger templates live in two const registries, and the panels/editors generate their UI from them, so a future action/trigger type is a one-entry append. It is **not wired into the editor** — run via **F6** on `res://scenes/sandbox_stage.tscn`. **No `.stk` format change.**
**Phase 3c** makes the Sandbox Stage's **action queues and event rules fully editable**. Where Phase 3a let directors only *append* actions and Phase 4 only *create/delete* rules, Phase 3c adds edit / delete / drag-reorder for both, a **waypoint right-click context menu** with visual walk re-placement and insert-before/after, and both a **full** and a **consequence-only** rule editor. The entire system is **registry-driven** — the action and trigger templates live in two const registries, and the panels/editors generate their UI from them, so a future action/trigger type is a one-entry append. It is **not wired into the editor** — run via **F6** on `res://scenes/stage/sandbox_stage.tscn`. **No `.stk` format change.**
**New scripts:**
| File | `class_name` / extends | Responsibility |
|---|---|---|
| `res://scripts/action_registry.gd` | `ActionRegistry` / `RefCounted` | Registry of the 5 action templates (`walk_to`/`speak`/`wait`/`ragdoll`/`recover`: label, icon, param spec). Static accessors `types()` / `has_type` / `label` / `icon`, plus `to_rule_action()` / `from_rule_action()` (flat queue-action ⇄ rule-action shape conversions) and `summarize()`. |
| `res://scripts/trigger_registry.gd` | `TriggerRegistry` / `RefCounted` | Registry of the 5 trigger templates (label, icon, `target_type`: `waypoint`/`action_type`/`none`/`area`/`prop`). Static `types()` / `has_type` / `label` / `icon` / `target_type` / `summarize()`. |
| `res://scripts/queue_panel.gd` | `QueuePanel` / `PopupPanel` | Action Queue panel (scrollable list of one stickman's actions, each with ✎ / ✕ / drag-to-reorder ≡). Mutations delegate to the stage via signals; reorders call the rig's `remove_action`/`insert_action`. Root of `scenes/queue_panel.tscn`. |
| `res://scripts/rule_panel.gd` | `RulePanel` / `PopupPanel` | Rule list panel filtered by the stage (source stickman **or** a waypoint), with ✎ / ✕ / drag-to-reorder and **Add Rule** / **Clear All**. Reordering emits the new order of the *displayed* rule ids; the stage maps them back onto its full `_event_rules`, preserving un-filtered rules' positions. Root of `scenes/rule_panel.tscn`. |
| `res://scripts/action_editor.gd` | `ActionEditor` / `PopupPanel` | Single-action property editor (add & edit). Type dropdown + param fields are generated from `ActionRegistry`. A `walk_to` target is captured on the stage (`target_requested()` → the stage hides the editor, captures a click, calls `set_walk_target()`). |
| `res://scripts/rule_editor.gd` | `RuleEditor` / `PopupPanel` | Rule editor in two modes — **`full`** (trigger type + target + actions all editable) and **`consequence`** (trigger read-only; only the actions editable). Trigger targets and action actors are captured on the stage via signals. |
| `res://scripts/waypoint_context.gd` | `WaypointContext` / `PopupMenu` | Right-click menu for a `walk_to` waypoint: **✎ Edit this Walk**, **✕ Delete this Walk**, **⬆ Insert action before**, **⬇ Insert action after**, and **⚡ Edit Trigger Rules** (enabled + shows a count when rules target this waypoint). Item ids: `EDIT_WALK`/`DELETE_WALK`/`INSERT_BEFORE`/`INSERT_AFTER`/`EDIT_TRIGGER_RULES`. |
| `res://scenes/queue_panel.tscn`, `rule_panel.tscn`, `action_editor.tscn`, `rule_editor.tscn` | minimal shells | Each `.tscn` is a bare `PopupPanel` + root script; **all UI is built in code** at `_ready()` (consistent with the `asset_selector.tscn` pattern). |
| `res://scripts/stage/action_registry.gd` | `ActionRegistry` / `RefCounted` | Registry of the 5 action templates (`walk_to`/`speak`/`wait`/`ragdoll`/`recover`: label, icon, param spec). Static accessors `types()` / `has_type` / `label` / `icon`, plus `to_rule_action()` / `from_rule_action()` (flat queue-action ⇄ rule-action shape conversions) and `summarize()`. |
| `res://scripts/stage/trigger_registry.gd` | `TriggerRegistry` / `RefCounted` | Registry of the 5 trigger templates (label, icon, `target_type`: `waypoint`/`action_type`/`none`/`area`/`prop`). Static `types()` / `has_type` / `label` / `icon` / `target_type` / `summarize()`. |
| `res://scripts/editors/stage/queue_panel.gd` | `QueuePanel` / `PopupPanel` | Action Queue panel (scrollable list of one stickman's actions, each with ✎ / ✕ / drag-to-reorder ≡). Mutations delegate to the stage via signals; reorders call the rig's `remove_action`/`insert_action`. Root of `scenes/editors/stage/queue_panel.tscn`. |
| `res://scripts/editors/stage/rule_panel.gd` | `RulePanel` / `PopupPanel` | Rule list panel filtered by the stage (source stickman **or** a waypoint), with ✎ / ✕ / drag-to-reorder and **Add Rule** / **Clear All**. Reordering emits the new order of the *displayed* rule ids; the stage maps them back onto its full `_event_rules`, preserving un-filtered rules' positions. Root of `scenes/editors/stage/rule_panel.tscn`. |
| `res://scripts/editors/stage/action_editor.gd` | `ActionEditor` / `PopupPanel` | Single-action property editor (add & edit). Type dropdown + param fields are generated from `ActionRegistry`. A `walk_to` target is captured on the stage (`target_requested()` → the stage hides the editor, captures a click, calls `set_walk_target()`). |
| `res://scripts/editors/stage/rule_editor.gd` | `RuleEditor` / `PopupPanel` | Rule editor in two modes — **`full`** (trigger type + target + actions all editable) and **`consequence`** (trigger read-only; only the actions editable). Trigger targets and action actors are captured on the stage via signals. |
| `res://scripts/stage/waypoint_context.gd` | `WaypointContext` / `PopupMenu` | Right-click menu for a `walk_to` waypoint: **✎ Edit this Walk**, **✕ Delete this Walk**, **⬆ Insert action before**, **⬇ Insert action after**, and **⚡ Edit Trigger Rules** (enabled + shows a count when rules target this waypoint). Item ids: `EDIT_WALK`/`DELETE_WALK`/`INSERT_BEFORE`/`INSERT_AFTER`/`EDIT_TRIGGER_RULES`. |
| `res://scenes/editors/stage/queue_panel.tscn`, `rule_panel.tscn`, `action_editor.tscn`, `rule_editor.tscn` | minimal shells | Each `.tscn` is a bare `PopupPanel` + root script; **all UI is built in code** at `_ready()` (consistent with the `asset_selector.tscn` pattern). |
**Modified scripts:** `sandbox_stage.gd` (Phase 3c wiring) and `stage_director_visuals.gd` (waypoint action hit-testing + pulsing edit highlight + rule-label click → editor).
**Theming:** the four Phase 3c widgets above are themeable — each accepts an `apply_font(ui_font, emoji_font, sizes)` call driven by the extended `sandbox_theme.json` `fonts` block (style variants `ui_font_bold`/`ui_font_italic`, per-widget `queue/rule_panel_font_size`, `action/rule_editor_font_size`, `panel_row_font_size`/`panel_title_font_size`, style flags `panel_title_bold`/`rule_label_bold`/`badge_bold`, and the optional `action_popup` `{size, bold, italic}` object form; `action_popup_emoji_size` is now consumed). See §21.3 and `docs/phase_3c_editor_spec.md` §14.
**Theming:** the four Phase 3c widgets above are themeable — each accepts an `apply_font(ui_font, emoji_font, sizes)` call driven by the extended `sandbox_theme.json` `fonts` block (style variants `ui_font_bold`/`ui_font_italic`, per-widget `queue/rule_panel_font_size`, `action/rule_editor_font_size`, `panel_row_font_size`/`panel_title_font_size`, style flags `panel_title_bold`/`rule_label_bold`/`badge_bold`, and the optional `action_popup` `{size, bold, italic}` object form; `action_popup_emoji_size` is now consumed). See §21.3 and `docs/specs/phase_3c_editor_spec.md` §14.
**Entry points:**
@@ -781,7 +806,19 @@ The result: `mode` stays constant for the whole walk, exactly one `arrived` fire
& "C:\Godot4\Godot_v4.7.1-stable_win64_console.exe" --headless --script res://tests/test_phase3c_editor.gd --path .
```
Spec: `docs/phase_3c_editor_spec.md`.
Spec: `docs/specs/phase_3c_editor_spec.md`.
### 24. Prop Interactions and Throw Planning (Phase 11)
The Sandbox Stage action queue supports one-hand prop pickup, drop, and targeted throws. In **Direct**, select a stickman and add or edit **Pick Up Prop**, **Drop Prop**, or **Throw Prop** in its action queue. A pickup selects a stage prop and a hand (`Auto`, `Left`, or `Right`); a throw captures a stage-world point.
**Throw target and guide:**
- The cursor point is the requested target. The effective target is clamped to **3,600 px from the stickman's root**; if the cursor is farther away, the guide ends at the capped point while still marking the cursor position.
- The solver accounts for stage gravity and chooses a valid descending arc whose release speed does not exceed **2,100 px/s**. Upward targets are supported when reachable; gravity can reduce their reachable distance. Targets that cannot be reached within the solver's speed and flight-time limits are shown as unreachable.
- The guide is clipped at the first prop/world collision. In open air, it ends at the selected target; the prop continues under normal physics after that point.
**Queue-aware editing preview:** when editing a queued throw, the planning guide looks at earlier actions in that queue. If an earlier pickup can provide the prop, the preview uses its grip and hand and estimates the pickup approach when needed. A hint such as **“Assumes Step 2 pickup succeeds.”** identifies the assumption; uncertain preceding actions label the guide as estimated. This preview does not run the pickup or change live prop, rig, or queue state. If no held or planned prop can be resolved, the guide is replaced by a neutral marker and an informational hint rather than a misleading trajectory. At runtime, the throw is recalculated from the actual prop, actor position, and release pose.
## File format (`.stk`)
@@ -846,7 +883,7 @@ Files are UTF-8 JSON, pretty-printed with tab indentation. The format is version
| `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. |
| `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 `stickman_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. |
@@ -885,7 +922,14 @@ Each entry in the `shapes` array:
## File format (`.prop`)
The **Prop Editor** (Phase 9) authors vector prop assets saved as `.prop` files, stored under `res://props/*.prop`. Files are UTF-8 JSON, pretty-printed with tab indentation, versioned, and designed to remain **backward compatible** — the format stores the editor's full multi-shape stack, while a deterministic converter (`PropFile.flatten_to_payload()`) collapses it into the single-shape payload `PropUtils.spawn_prop()` consumes at runtime (frontmost closed polygon → else circle → else the first shape; lines are editor-only and never become collision geometry).
The **Prop Editor** (Phase 9, extended by Phase 10a) authors vector prop assets saved as `.prop` files, stored under `res://content/props/*.prop`. Files are UTF-8 JSON, pretty-printed with tab indentation, versioned, and designed to remain **backward compatible** — the format stores the editor's full multi-shape stack, while a deterministic converter (`PropFile.flatten_to_payload()`) collapses it into the single-shape payload `PropUtils.spawn_prop()` consumes at runtime (frontmost closed polygon → else circle → else the first shape; lines are editor-only and never become collision geometry).
In the Prop Editor, Arrange exposes **Transform Whole Prop**, **Normalize Size**,
**Center Prop**, and **Show Stickman Guide** in that order. Whole-prop move,
origin-based rotation, and uniform resize preview from a snapshot and commit once;
Snap rounds movement, Ctrl snaps rotation to 15°, and Esc cancels. The Sandbox
Stage has a separate single-prop corner scale gesture whose visual and collision
bounds remain synchronized during PLAY.
```json
{
@@ -991,53 +1035,54 @@ Behavior:
| 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). `_mount_shapes()` also handles **v1.0/v1.1 single-shape** part dicts (wraps the part dict as one shape when it carries `points` but no `shapes` array), so older `.stk` files mount as visible geometry instead of being cleared. **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`, calls `StkRigAdapter.apply(stk_data, rig)`, and returns the rig root **typed as `StickmanRig`** (the rig now carries the `StickmanRig` root script); `spawn(path)` chains them (`null` on empty data). **Not used by the editor.** |
| `res://scripts/stickman_rig.gd` | **Phase 9 Task 4.** `class_name StickmanRig`, `extends Node2D`; the runtime owner of facing direction, per-joint bone bend, `Body/*` z-order, and (Phase 10/11) the **kinematic-to-ragdoll** state switch with instant handoff + stand-up recovery, attached to the `master_rig.tscn` root `Master`. Exports a `facing_profile` preset (`FacingProfile` LEFT/RIGHT/FORWARD, default FORWARD) and four `@export_enum("Normal","Inverted")` per-joint bend vars (`left_arm_bend`/`right_arm_bend`/`left_leg_bend`/`right_leg_bend`), plus (Phase 11) `rest_timeout` (2.0 s) and `auto_recover` (true) exports. Non-`@tool`: resolves `Skeleton2D`/`Body`/bend joints at runtime, enables its own modification stack, and applies the profile (flag writes + `Body/*` reorder) in `_ready()` and setters. Signals `facing_profile_changed` / `bend_flag_changed` / `state_changed`; public API `set_facing_profile`/`get_facing_profile`, `set_joint_bend_flipped`/`get_joint_bend_flipped`, `get_bend_joints()`, `get_bend_joint_global_position()`, plus the ragdoll API `set_ragdoll(enabled)`/`toggle_ragdoll()`/`is_in_ragdoll()`/`request_recovery()` with `state` / `enum RigState { ANIMATED, RAGDOLL, RECOVERING }`. The recovery cycle is **pre-capture → ragdoll pose capture (7-ray ground probe) → mirror-aware re-anchor → snap (entry-captured chain offsets, `(−π, π]`-normalized bones, ankle clamp) → 2.0 s tween to the pre-ragdoll pose (arms/head bone-driven with `_natural_joint_target` for elbows; legs IK-driven) → finish (profile + bend flags + LookAt restored)**, with every per-joint bend decision editable in the `RAGDOLL_JOINT_BAND_LIMITS` / `RECOVERY_LEG_SIDE_FORCE` / `RECOVERY_JOINT_PATH` tables (Joint bend configuration (EDIT ME)). Diagnostics are gated behind `DEBUG_RECOVERY := false`. **Phase 6** adds PLAY-only `begin_terrain_fall()` / `set_terrain_following_enabled()`, center/per-foot TerrainBlock ray fans with self-proxy filtering, layered leg base markers vs live IK targets, continuous stance/swing blending with a predictive foot guard, accumulated root elevation/pelvis compensation across ramps/steps, and bounded free-fall. (Phase 4b) `_update_walking()` latches `_walk_mode` once per walk (`LATCH_PROBE_MAX_FRAMES` 6), unifies arrival on the final target at `ARRIVE_DISTANCE` (snap-on-arrive), steers to the final target when close, and re-asserts standing markers one frame after stop — fixing the walk-waypoint arrival jitter. (Phase 4.5) builds ten top-level `AnimatableBody2D` collision proxies, explicitly synchronizes them to the rendered `Body/*` transforms each physics frame, derives the head circle center/radius from mounted geometry (90 px minimum), and applies bounded, contact-quadrant-aware bone flinches that decay to captured base rotations. `snap_to_standing()` and the stage's PLAY→DIRECT reset restore authored transforms and pose state. Null-guarded (`push_warning` + skip). **Not used by the editor.** |
| `res://scripts/create_animations.gd` | **Phase 11.** `@tool extends EditorScript`; a **standalone editor utility** (run manually with `master_rig.tscn` open; not auto-loaded or referenced at runtime) that supersedes the deleted `scripts/create_walk.gd`. `_run()` bakes `walk_left`/`walk_right` (same keyframes as the old script) and a one-shot `stand_up` (`POSE_DOWN` → `POSE_STANDING`, `STAND_UP_DURATION` 2.0, `loop_mode = LOOP_NONE`) into the open scene's default `AnimationLibrary`. The baked `stand_up` is an **authored reference only** — runtime recovery does not play it (`StickmanRig` tweens the IK targets directly from the captured ragdoll pose, since a fixed first keyframe can never match an arbitrary rest pose). `walk_right` is the **canonical walk** clip; `walk_left` remains baked in the library but is **not used at runtime** (facing is set explicitly and the rig root X-mirrors for LEFT). |
| `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 (the rig self-enables its stack), 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/terrain_block.gd` | **Vector Terrain System.** `class_name TerrainBlock`, `extends StaticBody2D` — a reusable vector terrain component building `Polygon2D` (fill) + `Line2D` (border) + `CollisionPolygon2D` (`BUILD_SOLIDS`, supports concave) children in code. Has a `spawn_id: String` property (set by `StageSpawner`) so same-template terrain overlaps are detectable during drag-painting. |
| `res://scripts/terrain_utils.gd` | **Vector Terrain System.** `class_name TerrainUtils`, `extends RefCounted` — static `sanitize_points()` (grid snap → local `_simplify_polyline()` → clockwise enforcement) and a `spawn_block()` factory. |
| `res://scripts/physics_test_harness.gd` | **Vector Terrain System / Dynamic Vector Props.** Standalone staging scene root building flat/ramp/step terrain via `TerrainUtils`, instantiating `master_rig.tscn`, spawning props via **1/2/3** (`PropUtils`), and adding a rig collision proxy (run via **F6**; not wired into the editor). A toggle-mode button flips the rig's kinematic-to-ragdoll mode via `_rig.set_ragdoll()`, plus (Phase 11) a **Rest** `SpinBox` (writes `_rig.rest_timeout`) and **"Recover Now"** button (`_rig.request_recovery()`); the rig's `state_changed` signal removes the proxy on `RAGDOLL` and re-adds it (idempotently) on `ANIMATED`/`RECOVERING`. |
| `res://scenes/physics_test_harness.tscn` | **Vector Terrain System / Dynamic Vector Props.** Standalone staging scene backing `scripts/physics_test_harness.gd` (run via **F6**; not wired into the editor). |
| `res://scripts/prop_block.gd` | **Dynamic Vector Props / Phase 4.5.** `class_name PropBlock`, `extends RigidBody2D` — a reusable physical prop building `Polygon2D` (fill) + `Line2D` (outline) + `CollisionPolygon2D`/`CollisionShape2D` (polygon/circle collision) children in code, with material presets (mass + friction/bounce), live-updating exports, contact monitoring, shape-cast CCD, and `collided(other, contact_normal, contact_position)` / `separated(other)` signals carrying world-space contact data. Also the single source of truth for the Prop Editor's inspector physics readouts. |
| `res://scripts/prop_editor.gd` | **Prop Editor (Phase 9).** `class_name PropEditor`, `extends Control` — standalone vector-prop authoring controller (run via **F6** on `res://scenes/prop_editor.tscn`; **not** the main scene). Owns the 📁 File (🆕 New / 💾 Save / 📄 Save As… / 📂 Load… / 🕑 Recent) and 👁️ View (🔍 Reset View) menus, the top-right Grid spinbox + Snap checkbox, the centered Arrange/Reshape mode toolbar, the material-preset + per-shape inspector (physics readouts from `PropBlock`; hides the circle Radius row + shows a "reshaped" note when a shape's `reshaped` flag is set), the status bar, the window title builder, the exit dialog (💾 Save / 🗑️ Discard / ❌ Cancel), recent-props persistence (`user://prop_editor_settings.json`, max 5), and the debounced autosave/recovery pipeline (`user://prop_editor_autosave.prop` via `PropFile.save_safe`). Saves export a cached 200×200 thumbnail. |
| `res://scripts/prop_canvas.gd` | **Prop Editor (Phase 9).** `class_name PropCanvas`, `extends Control` — multi-shape vector canvas: cursor-centered zoom/pan (wheel, pinch, middle-mouse), `EditMode { TRANSFORM, POINTS }` ("Arrange"/"Reshape") gating input + drawing only, rotation dot + corner scale crosses (Ctrl = 15° snap / aspect lock), color-matched vertex handles with Create/Remove Point (0.6 s flash), per-mode right-click menus, a local clipboard, mirror + z-order, the per-shape `reshaped` flag, sanitize-on-commit via `TerrainUtils`, and two-tone halo selection. |
| `res://scripts/prop_file.gd` | **Prop Editor (Phase 9).** `class_name PropFile`, `extends RefCounted` — static `.prop` v1.0 schema + converters: `to_file_dict`/`from_file_dict` (round-trip, version rejection, missing-key defaults), `preset_to_string`/`from_string`, `sanitize_shape`, `flatten_to_payload` (frontmost closed polygon → circle → first shape), `save`/`load`, and the atomic `save_safe` (tmp/`.bak` swap). Points must be coerced via `_coerce_points` when serializing (Round 5). |
| `res://scripts/prop_theme.gd` | **Prop Editor (Phase 9).** `class_name PropTheme`, `extends RefCounted` — validated loader for `res://prop_theme.json` (deep-merge, warn-once, clamped accessors); hierarchy theme file → `DEFAULT_CONFIG` → `PropBlock` constants. Physics is never themeable. Holds the Round 2–4 `ui` tokens (fonts, hit targets, colors). Applied through a **local** Theme only. |
| `res://scenes/prop_editor.tscn` | **Prop Editor (Phase 9).** Standalone scene backing `scripts/prop_editor.gd` (run via **F6**; not the main scene): minimal shell (`%MenuBar` in `%TopRow` beside `%TopRightControls`, `%TopBar`/`%PropNameEdit`, `%MainLayout`/`%CanvasPanel`/`%CanvasArea`/`%ModeBar`/`%PropCanvas`, `%InspectorColumn`/`%InspectorList`, status bar, dialogs); UI built in code. |
| `res://scripts/prop_utils.gd` | **Dynamic Vector Props.** `class_name PropUtils`, `extends RefCounted` — static `create_box()` / `create_ball()` / `create_plank()` / `create_triangle()` primitive generators and a `spawn_prop()` factory (sanitizes polygon points via `TerrainUtils`). `PropFile.flatten_to_payload()` produces the payload it consumes. |
| `res://scripts/sandbox_stage.gd` | **Sandbox Stage Builder / Phase 4.5.** `class_name SandboxStage`, `extends Node2D` — root controller: `enum StageMode { EDIT, DIRECT, PLAY }` state machine (freezes props with `FREEZE_MODE_KINEMATIC`; runs stickman queues + rags props/areas in PLAY), placement mode + terrain drag-painting, a grid spatial dictionary, camera pan/zoom, deletion, bottom status bar, mode badge/frame/cursors, the `res://sandbox_theme.json` loader, and signal fan-out (`mode_changed(mode: int)` / `object_placed` / `object_selected` / `object_deselected` / `object_deleted`). **Phase 3b** instantiates the `AssetSelector` popup + thumbnail renderers, owns the selector open/close flow and lazy per-frame thumbnail drain (§22). **Phase 3c** instantiates the queue/rule panels + editors + waypoint context, owns the unified `CaptureKind` target-capture system, the "Edit Queue…"/"Edit Rules…" + right-click context entry points, the shared confirmation dialog, and consequence-only rule editing (§23). **Phase 4.5** routes `PropBlock` physics contacts through proxy metadata, tracks per-part body IDs for pair debouncing, preserves rig→prop rule ordering, purges stale pairs on separation/deletion/ragdoll, and restores authored rig state when returning PLAY→DIRECT. Standalone staging scene run via **F6**; not wired into the editor. |
| `res://scripts/stage_spawner.gd` | **Sandbox Stage Builder.** `class_name StageSpawner`, `extends RefCounted` — registry-driven factory (`Array[Dictionary]`, no id `match`); reuses `TerrainUtils` / `PropUtils` / `StickmanFactory`; centers terrain on its origin. Exposes `is_terrain_id()` / `get_template_aabb()` and tags spawned terrain with a `spawn_id`. `get_template_aabb(id)` mirrors `_spawn_terrain()`'s sanitize pass (`TerrainUtils.sanitize_points` at `TERRAIN_GRID_SIZE` 16), so the returned extent matches the real placed footprint — e.g. the 200-px-wide Ground template returns a **192-px** stride — and drives the block-unit paint stride, ghost sizing, and cell rasterization. **Phase 3b:** registry ids `ground/ramp/step/prop/stickman/area` (separate `crate`/`ball` removed); holds `selected_stickman_path` / `selected_prop_id` session state + a per-path `_stickman_cache`; `prop`/`stickman` spawn the **selected** asset (§22). |
| `res://scripts/stickman_library.gd` | **Asset Library (Phase 3b).** `class_name StickmanLibrary`, `extends RefCounted` — scans `res://stickmen/*.stk` into `{path, name, data}` entries (corrupt/missing-`body_parts` skipped; name = `stickman_name` else filename basename); `make_entry(path)` for Browse-chosen paths. |
| `res://scripts/prop_library.gd` | **Asset Library (Phase 3b).** `class_name PropLibrary`, `extends RefCounted` — static registry of the 4 prop templates (Crate/Wood, Ball/Rubber, Plank/Metal, Triangle/Cardboard); `get_default_id()` = `"crate"`. |
| `res://scripts/asset_selector.gd` | **Asset Library (Phase 3b).** `class_name AssetSelector`, `extends PopupPanel` — grid popup controller (root of `scenes/asset_selector.tscn`): pagination 12/page, empty state, per-cell thumbnail + name + prop material badge, Browse/Refresh/Close, Esc. |
| `res://scripts/thumbnails/stickman_thumbnail.gd` | **Asset Library (Phase 3b).** `class_name StickmanThumbnail`, `extends Node` — renders a parsed `.stk` to a `Texture2D` via the real rig in an offscreen `SubViewport`. |
| `res://scripts/thumbnails/prop_thumbnail.gd` | **Asset Library (Phase 3b).** `class_name PropThumbnail`, `extends Node` — renders a prop template to a `Texture2D` (lightweight non-physics visual). |
| `res://scripts/thumbnails/thumbnail_cache.gd` | **Asset Library (Phase 3b).** `class_name ThumbnailCache`, `extends RefCounted` — disk PNG cache (`user://thumbnails/`) keyed by basename+mtime (stickmen) / `id_v<PROP_VERSION>` (props); load/save/stale cleanup. |
| `res://scenes/asset_selector.tscn` | **Asset Library (Phase 3b).** `PopupPanel` root + `asset_selector.gd` — minimal shell (title bar, empty grid, footer); dynamic cells built in code. |
| `res://scripts/action_registry.gd` | **Editor Tools (Phase 3c).** `class_name ActionRegistry`, `extends RefCounted` — const registry of the 5 action templates (`walk_to`/`speak`/`wait`/`ragdoll`/`recover`: label/icon/params); static `types()`/`has_type`/`label`/`icon`, `to_rule_action()`/`from_rule_action()`, `summarize()`. |
| `res://scripts/trigger_registry.gd` | **Editor Tools (Phase 3c).** `class_name TriggerRegistry`, `extends RefCounted` — const registry of the 5 trigger templates (label/icon/`target_type`); static `types()`/`has_type`/`label`/`icon`/`target_type`/`summarize()`. |
| `res://scripts/queue_panel.gd` | **Editor Tools (Phase 3c).** `class_name QueuePanel`, `extends PopupPanel` — Action Queue panel (one stickman's actions with ✎/✕/drag-reorder); mutates the rig queue via its API. |
| `res://scripts/rule_panel.gd` | **Editor Tools (Phase 3c).** `class_name RulePanel`, `extends PopupPanel` — rule list panel filtered by source stickman or waypoint, with ✎/✕/drag-reorder + Add Rule/Clear All. |
| `res://scripts/action_editor.gd` | **Editor Tools (Phase 3c).** `class_name ActionEditor`, `extends PopupPanel` — add/edit single-action editor; type dropdown + params generated from `ActionRegistry`; `walk_to` target captured on stage. |
| `res://scripts/rule_editor.gd` | **Editor Tools (Phase 3c).** `class_name RuleEditor`, `extends PopupPanel` — rule editor in **full** or **consequence-only** modes (trigger read-only); actions add/edit/remove. |
| `res://scripts/waypoint_context.gd` | **Editor Tools (Phase 3c).** `class_name WaypointContext`, `extends PopupMenu` — right-click waypoint menu: Edit/Delete Walk, Insert action before/after, Edit Trigger Rules. |
| `res://scenes/queue_panel.tscn` / `rule_panel.tscn` / `action_editor.tscn` / `rule_editor.tscn` | **Editor Tools (Phase 3c).** Minimal `PopupPanel` shells (bare root + root script); all UI is built in code at `_ready()`. |
| `res://scripts/stage_selection.gd` | **Sandbox Stage Builder.** `class_name StageSelection`, `extends RefCounted` — hover/click/box selection via geometric world-space AABB hit-testing (frontmost `World` child wins; `RagdollBodyContainer` subtree excluded); `hover_changed` / `selection_changed` signals. |
| `res://scripts/stage_gizmos.gd` | **Sandbox Stage Builder.** `class_name StageGizmos`, `extends Node2D` — hover highlight + selection outline + rotate ring via `_draw()` and distance-based hit-testing; objects are dragged directly (no move handle); drives `global_position` / `global_rotation`; emits `transform_committed`. |
| `res://scripts/stage_grid.gd` | **Sandbox Stage Builder.** `class_name StageGrid`, `extends Node2D` — optional world-space grid overlay (major line every 5 cells) that pans/zooms with the camera; `grid_size` / `enabled` set by `SandboxStage`. |
| `res://scripts/stage_placement_overlay.gd` | **Sandbox Stage Builder (Phase 4b).** `class_name StagePlacementOverlay`, `extends Node2D` — world-space overlay drawing the terrain drag-painting dashed guide line (`set_terrain_guide` / `clear_terrain_guide`) and the director action rubber-band trajectory + ghost marker (`set_action_trajectory` / `clear_action`); pure drawing, no hit-testing. |
| `res://scenes/main.tscn` | **Startup wrapper** configured as `run/main_scene`; hosts the Stickman Editor. |
| `res://scenes/editors/stickman/stickman_editor.tscn` | Stickman Editor layout, File/Edit/View menu bar, dialogs (`GridConfigDialog` + SpinBox), column containers (unique-name nodes). |
| `res://scenes/editors/stickman/body_part_panel.tscn` | Reusable single body-part editor panel (title, drawing area, context menu, `ColorPickerPopup`); expands vertically in its column. |
| `res://scripts/editors/stickman/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/characters/stickman/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 `stickman_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). `_mount_shapes()` also handles **v1.0/v1.1 single-shape** part dicts (wraps the part dict as one shape when it carries `points` but no `shapes` array), so older `.stk` files mount as visible geometry instead of being cleared. **Not used by the editor** — consumed by the runtime pipeline. |
| `res://scripts/characters/stickman/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 `stickman_rig.tscn` instance. `load_stk(path)` reads + parses the file (`{}` + `push_warning` on failure); `spawn_from_data(stk_data)` instantiates `res://scenes/characters/stickman/stickman_rig.tscn`, calls `StkRigAdapter.apply(stk_data, rig)`, and returns the rig root **typed as `StickmanRig`** (the rig now carries the `StickmanRig` root script); `spawn(path)` chains them (`null` on empty data). **Not used by the editor.** |
| `res://scripts/characters/stickman/stickman_rig.gd` | **Phase 9 Task 4.** `class_name StickmanRig`, `extends Node2D`; the runtime owner of facing direction, per-joint bone bend, `Body/*` z-order, and (Phase 10/11) the **kinematic-to-ragdoll** state switch with instant handoff + stand-up recovery, attached to the `stickman_rig.tscn` root `Master`. Exports a `facing_profile` preset (`FacingProfile` LEFT/RIGHT/FORWARD, default FORWARD) and four `@export_enum("Normal","Inverted")` per-joint bend vars (`left_arm_bend`/`right_arm_bend`/`left_leg_bend`/`right_leg_bend`), plus (Phase 11) `rest_timeout` (2.0 s) and `auto_recover` (true) exports. Non-`@tool`: resolves `Skeleton2D`/`Body`/bend joints at runtime, enables its own modification stack, and applies the profile (flag writes + `Body/*` reorder) in `_ready()` and setters. Signals `facing_profile_changed` / `bend_flag_changed` / `state_changed`; public API `set_facing_profile`/`get_facing_profile`, `set_joint_bend_flipped`/`get_joint_bend_flipped`, `get_bend_joints()`, `get_bend_joint_global_position()`, plus the ragdoll API `set_ragdoll(enabled)`/`toggle_ragdoll()`/`is_in_ragdoll()`/`request_recovery()` with `state` / `enum RigState { ANIMATED, RAGDOLL, RECOVERING }`. The recovery cycle is **pre-capture → ragdoll pose capture (7-ray ground probe) → mirror-aware re-anchor → snap (entry-captured chain offsets, `(−π, π]`-normalized bones, ankle clamp) → 2.0 s tween to the pre-ragdoll pose (arms/head bone-driven with `_natural_joint_target` for elbows; legs IK-driven) → finish (profile + bend flags + LookAt restored)**, with every per-joint bend decision editable in the `RAGDOLL_JOINT_BAND_LIMITS` / `RECOVERY_LEG_SIDE_FORCE` / `RECOVERY_JOINT_PATH` tables (Joint bend configuration (EDIT ME)). Diagnostics are gated behind `DEBUG_RECOVERY := false`. **Phase 6** adds PLAY-only `begin_terrain_fall()` / `set_terrain_following_enabled()`, center/per-foot TerrainBlock ray fans with self-proxy filtering, layered leg base markers vs live IK targets, continuous stance/swing blending with a predictive foot guard, accumulated root elevation/pelvis compensation across ramps/steps, and bounded free-fall. (Phase 4b) `_update_walking()` latches `_walk_mode` once per walk (`LATCH_PROBE_MAX_FRAMES` 6), unifies arrival on the final target at `ARRIVE_DISTANCE` (snap-on-arrive), steers to the final target when close, and re-asserts standing markers one frame after stop — fixing the walk-waypoint arrival jitter. (Phase 4.5) builds ten top-level `AnimatableBody2D` collision proxies, explicitly synchronizes them to the rendered `Body/*` transforms each physics frame, derives the head circle center/radius from mounted geometry (90 px minimum), and applies bounded, contact-quadrant-aware bone flinches that decay to captured base rotations. `snap_to_standing()` and the stage's PLAY→DIRECT reset restore authored transforms and pose state. Null-guarded (`push_warning` + skip). **Not used by the editor.** |
| `res://scripts/tools/create_animations.gd` | **Phase 11.** `@tool extends EditorScript`; a **standalone editor utility** (run manually with `stickman_rig.tscn` open; not auto-loaded or referenced at runtime) that supersedes the deleted `scripts/create_walk.gd`. `_run()` bakes `walk_left`/`walk_right` (same keyframes as the old script) and a one-shot `stand_up` (`POSE_DOWN` → `POSE_STANDING`, `STAND_UP_DURATION` 2.0, `loop_mode = LOOP_NONE`) into the open scene's default `AnimationLibrary`. The baked `stand_up` is an **authored reference only** — runtime recovery does not play it (`StickmanRig` tweens the IK targets directly from the captured ragdoll pose, since a fixed first keyframe can never match an arbitrary rest pose). `walk_right` is the **canonical walk** clip; `walk_left` remains baked in the library but is **not used at runtime** (facing is set explicitly and the rig root X-mirrors for LEFT). |
| `res://scripts/tools/test_harness.gd` | **Phase 9.** Standalone staging scene (run via **F6** on `res://scenes/tools/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 (`content/stickmen/break.stk`, `content/stickmen/basic.stk`, `content/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 (the rig self-enables its stack), 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/tools/test_harness.tscn` | **Phase 9.** Standalone staging scene backing `scripts/tools/test_harness.gd` (run via **F6**; not wired into the editor). |
| `res://scripts/terrain/terrain_block.gd` | **Vector Terrain System.** `class_name TerrainBlock`, `extends StaticBody2D` — a reusable vector terrain component building `Polygon2D` (fill) + `Line2D` (border) + `CollisionPolygon2D` (`BUILD_SOLIDS`, supports concave) children in code. Has a `spawn_id: String` property (set by `StageSpawner`) so same-template terrain overlaps are detectable during drag-painting. |
| `res://scripts/terrain/terrain_utils.gd` | **Vector Terrain System.** `class_name TerrainUtils`, `extends RefCounted` — static `sanitize_points()` (grid snap → local `_simplify_polyline()` → clockwise enforcement) and a `spawn_block()` factory. |
| `res://scripts/tools/physics_test_harness.gd` | **Vector Terrain System / Dynamic Vector Props.** Standalone staging scene root building flat/ramp/step terrain via `TerrainUtils`, instantiating `stickman_rig.tscn`, spawning props via **1/2/3** (`PropUtils`), and adding a rig collision proxy (run via **F6**; not wired into the editor). A toggle-mode button flips the rig's kinematic-to-ragdoll mode via `_rig.set_ragdoll()`, plus (Phase 11) a **Rest** `SpinBox` (writes `_rig.rest_timeout`) and **"Recover Now"** button (`_rig.request_recovery()`); the rig's `state_changed` signal removes the proxy on `RAGDOLL` and re-adds it (idempotently) on `ANIMATED`/`RECOVERING`. |
| `res://scenes/tools/physics_test_harness.tscn` | **Vector Terrain System / Dynamic Vector Props.** Standalone staging scene backing `scripts/tools/physics_test_harness.gd` (run via **F6**; not wired into the editor). |
| `res://scripts/props/prop_block.gd` | **Dynamic Vector Props / Phase 4.5.** `class_name PropBlock`, `extends RigidBody2D` — a reusable physical prop building `Polygon2D` (fill) + `Line2D` (outline) + `CollisionPolygon2D`/`CollisionShape2D` (polygon/circle collision) children in code, with material presets (mass + friction/bounce), live-updating exports, contact monitoring, shape-cast CCD, and `collided(other, contact_normal, contact_position)` / `separated(other)` signals carrying world-space contact data. Also the single source of truth for the Prop Editor's inspector physics readouts. |
| `res://scripts/editors/prop/prop_editor.gd` | **Prop Editor (Phase 9).** `class_name PropEditor`, `extends Control` — standalone vector-prop authoring controller (run via **F6** on `res://scenes/editors/prop/prop_editor.tscn`; **not** the main scene). Owns the 📁 File (🆕 New / 💾 Save / 📄 Save As… / 📂 Load… / 🕑 Recent) and 👁️ View (🔍 Reset View) menus, the top-right Grid spinbox + Snap checkbox, the centered Arrange/Reshape mode toolbar, the material-preset + per-shape inspector (physics readouts from `PropBlock`; hides the circle Radius row + shows a "reshaped" note when a shape's `reshaped` flag is set), the status bar, the window title builder, the exit dialog (💾 Save / 🗑️ Discard / ❌ Cancel), recent-props persistence (`user://prop_editor_settings.json`, max 5), and the debounced autosave/recovery pipeline (`user://prop_editor_autosave.prop` via `PropFile.save_safe`). Saves export a cached 200×200 thumbnail. |
| `res://scripts/editors/prop/prop_canvas.gd` | **Prop Editor (Phase 9).** `class_name PropCanvas`, `extends Control` — multi-shape vector canvas: cursor-centered zoom/pan (wheel, pinch, middle-mouse), `EditMode { TRANSFORM, POINTS }` ("Arrange"/"Reshape") gating input + drawing only, rotation dot + corner scale crosses (Ctrl = 15° snap / aspect lock), color-matched vertex handles with Create/Remove Point (0.6 s flash), per-mode right-click menus, a local clipboard, mirror + z-order, the per-shape `reshaped` flag, sanitize-on-commit via `TerrainUtils`, and two-tone halo selection. |
| `res://scripts/editors/prop/prop_file.gd` | **Prop Editor (Phase 9).** `class_name PropFile`, `extends RefCounted` — static `.prop` v1.0 schema + converters: `to_file_dict`/`from_file_dict` (round-trip, version rejection, missing-key defaults), `preset_to_string`/`from_string`, `sanitize_shape`, `flatten_to_payload` (frontmost closed polygon → circle → first shape), `save`/`load`, and the atomic `save_safe` (tmp/`.bak` swap). Points must be coerced via `_coerce_points` when serializing (Round 5). |
| `res://scripts/editors/prop/prop_theme.gd` | **Prop Editor (Phase 9).** `class_name PropTheme`, `extends RefCounted` — validated loader for `res://prop_theme.json` (deep-merge, warn-once, clamped accessors); hierarchy theme file → `DEFAULT_CONFIG` → `PropBlock` constants. Physics is never themeable. Holds the Round 2–4 `ui` tokens (fonts, hit targets, colors). Applied through a **local** Theme only. |
| `res://scenes/editors/prop/prop_editor.tscn` | **Prop Editor (Phase 9).** Standalone scene backing `scripts/editors/prop/prop_editor.gd` (run via **F6**; not the main scene): minimal shell (`%MenuBar` in `%TopRow` beside `%TopRightControls`, `%TopBar`/`%PropNameEdit`, `%MainLayout`/`%CanvasPanel`/`%CanvasArea`/`%ModeBar`/`%PropCanvas`, `%InspectorColumn`/`%InspectorList`, status bar, dialogs); UI built in code. |
| `res://scripts/props/prop_utils.gd` | **Dynamic Vector Props.** `class_name PropUtils`, `extends RefCounted` — static `create_box()` / `create_ball()` / `create_plank()` / `create_triangle()` primitive generators and a `spawn_prop()` factory (sanitizes polygon points via `TerrainUtils`). `PropFile.flatten_to_payload()` produces the payload it consumes. |
| `res://scripts/editors/stage/sandbox_stage.gd` | **Sandbox Stage Builder / Phase 4.5.** `class_name SandboxStage`, `extends Node2D` — root controller: `enum StageMode { EDIT, DIRECT, PLAY }` state machine (freezes props with `FREEZE_MODE_KINEMATIC`; runs stickman queues + rags and props/areas in PLAY), placement mode + terrain drag-painting, a grid spatial dictionary, camera pan/zoom, deletion, bottom status bar, mode badge/frame/cursors, the `res://sandbox_theme.json` loader, and signal fan-out (`mode_changed(mode: int)` / `object_placed` / `object_selected` / `object_deselected` / `object_deleted`). **Phase 3b** instantiates the `AssetSelector` popup + thumbnail renderers, owns the selector open/close flow and lazy per-frame thumbnail drain (§22). **Phase 3c** instantiates the queue/rule panels + editors + waypoint context, owns the unified `CaptureKind` target-capture system, the "Edit Queue…"/"Edit Rules…" + right-click context entry points, the shared confirmation dialog, and consequence-only rule editing (§23). **Phase 4.5** routes `PropBlock` physics contacts through proxy metadata, tracks per-part body IDs for pair debouncing, preserves rig→prop rule ordering, purges stale pairs on separation/deletion/ragdoll, and restores authored rig state when returning PLAY→DIRECT. Standalone staging scene run via **F6**; not wired into the editor. |
| `res://scripts/stage/stage_spawner.gd` | **Sandbox Stage Builder.** `class_name StageSpawner`, `extends RefCounted` — registry-driven factory (`Array[Dictionary]`, no id `match`); reuses `TerrainUtils` / `PropUtils` / `StickmanFactory`; centers terrain on its origin. Exposes `is_terrain_id()` / `get_template_aabb()` and tags spawned terrain with a `spawn_id`. `get_template_aabb(id)` mirrors `_spawn_terrain()`'s sanitize pass (`TerrainUtils.sanitize_points` at `TERRAIN_GRID_SIZE` 16), so the returned extent matches the real placed footprint — e.g. the 200-px-wide Ground template returns a **192-px** stride — and drives the block-unit paint stride, ghost sizing, and cell rasterization. **Phase 3b:** registry ids `ground/ramp/step/prop/stickman/area` (separate `crate`/`ball` removed); holds `selected_stickman_path` / `selected_prop_id` session state + a per-path `_stickman_cache`; `prop`/`stickman` spawn the **selected** asset (§22). |
| `res://scripts/characters/stickman/stickman_library.gd` | **Asset Library (Phase 3b).** `class_name StickmanLibrary`, `extends RefCounted` — scans `res://content/stickmen/*.stk` into `{path, name, data}` entries (corrupt/missing-`body_parts` skipped; name = `stickman_name` else filename basename); `make_entry(path)` for Browse-chosen paths. |
| `res://scripts/props/prop_library.gd` | **Asset Library (Phase 3b).** `class_name PropLibrary`, `extends RefCounted` — static registry of the 4 prop templates (Crate/Wood, Ball/Rubber, Plank/Metal, Triangle/Cardboard); `get_default_id()` = `"crate"`. |
| `res://scripts/shared/ui/asset_selector.gd` | **Asset Library (Phase 3b).** `class_name AssetSelector`, `extends PopupPanel` — grid popup controller (root of `scenes/stage/asset_selector.tscn`): pagination 12/page, empty state, per-cell thumbnail + name + prop material badge, Browse/Refresh/Close, Esc. |
| `res://scripts/shared/thumbnails/stickman_thumbnail.gd` | **Asset Library (Phase 3b).** `class_name StickmanThumbnail`, `extends Node` — renders a parsed `.stk` to a `Texture2D` via the real rig in an offscreen `SubViewport`. |
| `res://scripts/shared/thumbnails/prop_thumbnail.gd` | **Asset Library (Phase 3b).** `class_name PropThumbnail`, `extends Node` — renders a prop template to a `Texture2D` (lightweight non-physics visual). |
| `res://scripts/shared/thumbnails/thumbnail_cache.gd` | **Asset Library (Phase 3b).** `class_name ThumbnailCache`, `extends RefCounted` — disk PNG cache (`user://thumbnails/`) keyed by basename+mtime (stickmen) / `id_v<PROP_VERSION>` (props); load/save/stale cleanup. |
| `res://scenes/stage/asset_selector.tscn` | **Asset Library (Phase 3b).** `PopupPanel` root + `asset_selector.gd` — minimal shell (title bar, empty grid, footer); dynamic cells built in code. |
| `res://scripts/stage/action_registry.gd` | **Editor Tools (Phase 3c).** `class_name ActionRegistry`, `extends RefCounted` — const registry of the 5 action templates (`walk_to`/`speak`/`wait`/`ragdoll`/`recover`: label/icon/params); static `types()`/`has_type`/`label`/`icon`, `to_rule_action()`/`from_rule_action()`, `summarize()`. |
| `res://scripts/stage/trigger_registry.gd` | **Editor Tools (Phase 3c).** `class_name TriggerRegistry`, `extends RefCounted` — const registry of the 5 trigger templates (label/icon/`target_type`); static `types()`/`has_type`/`label`/`icon`/`target_type`/`summarize()`. |
| `res://scripts/editors/stage/queue_panel.gd` | **Editor Tools (Phase 3c).** `class_name QueuePanel`, `extends PopupPanel` — Action Queue panel (one stickman's actions with ✎/✕/drag-reorder); mutates the rig queue via its API. |
| `res://scripts/editors/stage/rule_panel.gd` | **Editor Tools (Phase 3c).** `class_name RulePanel`, `extends PopupPanel` — rule list panel filtered by source stickman or waypoint, with ✎/✕/drag-reorder + Add Rule/Clear All. |
| `res://scripts/editors/stage/action_editor.gd` | **Editor Tools (Phase 3c).** `class_name ActionEditor`, `extends PopupPanel` — add/edit single-action editor; type dropdown + params generated from `ActionRegistry`; `walk_to` target captured on stage. |
| `res://scripts/editors/stage/rule_editor.gd` | **Editor Tools (Phase 3c).** `class_name RuleEditor`, `extends PopupPanel` — rule editor in **full** or **consequence-only** modes (trigger read-only); actions add/edit/remove. |
| `res://scripts/stage/waypoint_context.gd` | **Editor Tools (Phase 3c).** `class_name WaypointContext`, `extends PopupMenu` — right-click waypoint menu: Edit/Delete Walk, Insert action before/after, Edit Trigger Rules. |
| `res://scenes/editors/stage/queue_panel.tscn` / `rule_panel.tscn` / `action_editor.tscn` / `rule_editor.tscn` | **Editor Tools (Phase 3c).** Minimal `PopupPanel` shells (bare root + root script); all UI is built in code at `_ready()`. |
| `res://scripts/stage/stage_selection.gd` | **Sandbox Stage Builder.** `class_name StageSelection`, `extends RefCounted` — hover/click/box selection via geometric world-space AABB hit-testing (frontmost `World` child wins; `RagdollBodyContainer` subtree excluded); `hover_changed` / `selection_changed` signals. |
| `res://scripts/stage/stage_gizmos.gd` | **Sandbox Stage Builder.** `class_name StageGizmos`, `extends Node2D` — hover highlight + selection outline + rotate ring via `_draw()` and distance-based hit-testing; objects are dragged directly (no move handle); drives `global_position` / `global_rotation`; emits `transform_committed`. |
| `res://scripts/stage/stage_grid.gd` | **Sandbox Stage Builder.** `class_name StageGrid`, `extends Node2D` — optional world-space grid overlay (major line every 5 cells) that pans/zooms with the camera; `grid_size` / `enabled` set by `SandboxStage`. |
| `res://scripts/stage/stage_placement_overlay.gd` | **Sandbox Stage Builder (Phase 4b).** `class_name StagePlacementOverlay`, `extends Node2D` — world-space overlay drawing the terrain drag-painting dashed guide line (`set_terrain_guide` / `clear_terrain_guide`) and the director action rubber-band trajectory + ghost marker (`set_action_trajectory` / `clear_action`); pure drawing, no hit-testing. |
| `res://sandbox_theme.json` | **Sandbox Stage Builder (Phase 4b).** Hand-editable styling defaults for the sandbox (font paths/sizes, grid snap default, mode accent + guide-line colors); loaded by `SandboxStage._load_theme()` with defaults on missing/malformed file. **Phase 3c** extends the `fonts` block (bold/italic variant paths, per-widget sizes/style flags, optional `action_popup` object form) and drives the panels'/editors' `apply_font(...)` (§21.3 / spec §14). |
| `res://scenes/sandbox_stage.tscn` | **Sandbox Stage Builder.** Standalone staging scene backing `scripts/sandbox_stage.gd` (run via **F6**; not wired into the editor): root `Node2D` + `Camera2D` + empty `World`; the gizmo layer, placement overlay, and CanvasLayer UI (mode switcher, toolbars, bottom status bar, badge, tooltip) are built in code. |
| `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://scenes/stage/sandbox_stage.tscn` | **Sandbox Stage Builder.** Standalone staging scene backing `scripts/editors/stage/sandbox_stage.gd` (run via **F6**; not wired into the editor): root `Node2D` + `Camera2D` + empty `World`; the gizmo layer, placement overlay, and CanvasLayer UI (mode switcher, toolbars, bottom status bar, badge, tooltip) are built in code. |
| `res://scripts/editors/stickman/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/editors/stickman/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://scenes/legacy/scenes/legacy/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
@@ -1115,27 +1160,27 @@ BodyPartPanel.shape_selected() ---(bound to part_name)---> stickman_editor
> **Phase 6 — Continuous Dynamic IK & Terrain-Following Locomotion (implemented and editor-verified):** PLAY enables a kinematic terrain controller. Unsupported rigs free-fall under bounded gravity; walking animation pauses while airborne and resumes on landing. The centerline uses a three-ray terrain fan and each lower-leg endpoint uses an independent three-ray fan; only upward-facing `TerrainBlock` hits are accepted, with self collision proxies excluded. `AnimationTargets/*_Base` markers hold the animation pose while live leg IK targets receive continuous stance/swing blending, world-space stance locks, a predictive foot guard, and centerline pelvis/root compensation. Runtime root elevation accumulates over ramps and steps. EDIT/DIRECT disable terrain following and restore authored transforms. The Sandbox navigation bake uses exposed upward-surface ribbons with short step bridges; `NavigationAgent2D` remains at `(0, +385)` and terrain owns vertical motion while arrival remains baseline-aligned. Trigger areas use swept visible bounds across physics frames to detect fast-fall entries. Reactive takeover batches preserve order, so `ragdoll` followed by `recover` executes both in sequence. `DEBUG_TERRAIN` is off by default. No foot/ankle bones were added.
> **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 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 `stickman_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 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 `stickman_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/characters/stickman/stk_rig_adapter.gd` (`class_name StkRigAdapter`) fits an instantiated `stickman_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:** Adds the **runtime pipeline** for turning a `.stk` file into a live, rigged `stickman_rig.tscn` instance, plus a standalone staging scene to debug it. A new `res://scripts/characters/stickman/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 `stickman_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/tools/test_harness.tscn` + `res://scripts/tools/test_harness.gd` (run via **F6**), loads `.stk` files (open dialog + quick-select for `content/stickmen/break.stk`, `content/stickmen/basic.stk`, `content/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 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/specs/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 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/specs/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 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/specs/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 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/specs/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 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/specs/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 (the solver now uses a full-range, non-inverted band solved in global space — `constraint_angle_min = -180 / max = 180`, `constraint_invert = false`, `constraint_in_localspace = false` — so the head converges to the aim point with no per-frame mirror oscillation; the earlier authored ~55° clamp is gone); `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.
> **Phase 9 Round 7:** the test harness (`scripts/tools/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 (the solver now uses a full-range, non-inverted band solved in global space — `constraint_angle_min = -180 / max = 180`, `constraint_invert = false`, `constraint_in_localspace = false` — so the head converges to the aim point with no per-frame mirror oscillation; the earlier authored ~55° clamp is gone); `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/specs/phase9_round7_feature_spec.md; verified with a 17-assertion headless test.
> **Phase 10 (Kinematic-to-Ragdoll):** adds a reversible **kinematic-to-ragdoll** state switch to the runtime rig. `StickmanRig` gains `enum RigState { ANIMATED, RAGDOLL }`, `var state: RigState`, `signal state_changed(new_state)`, and the `set_ragdoll(enabled)` / `toggle_ragdoll()` / `is_in_ragdoll()` API. In `RAGDOLL` mode the IK modification stack is disabled, the `AnimationPlayer` stopped, and the `Body/*` visuals hidden; a procedural network of **10** `RigidBody2D` (torso `CapsuleShape2D` mass 8.0, head `CircleShape2D` radius 100, limb capsules radius 8) + **9** `PinJoint2D` (elbow/knee fold-only ±bands, shoulder/hip ±160°, neck free) is built in code and reparented into a `"RagdollBodyContainer"` under the rig's **parent** (world root), layer 1/mask 1 so it collides with terrain and props. The rig root's momentum (tracked in `_physics_process`) is applied to the ragdoll Torso body for a seamless handoff. Exiting frees the ragdoll, re-shows `Body/*`, re-enables IK, and stops the animation. The physics harness toggles via its **Stickman ↔ Ragdoll** button, removing the `RigCollisionProxy` on entry and re-adding it (idempotently) on exit. `master_rig.tscn` is **not** modified.
> **Phase 10 (Kinematic-to-Ragdoll):** adds a reversible **kinematic-to-ragdoll** state switch to the runtime rig. `StickmanRig` gains `enum RigState { ANIMATED, RAGDOLL }`, `var state: RigState`, `signal state_changed(new_state)`, and the `set_ragdoll(enabled)` / `toggle_ragdoll()` / `is_in_ragdoll()` API. In `RAGDOLL` mode the IK modification stack is disabled, the `AnimationPlayer` stopped, and the `Body/*` visuals hidden; a procedural network of **10** `RigidBody2D` (torso `CapsuleShape2D` mass 8.0, head `CircleShape2D` radius 100, limb capsules radius 8) + **9** `PinJoint2D` (elbow/knee fold-only ±bands, shoulder/hip ±160°, neck free) is built in code and reparented into a `"RagdollBodyContainer"` under the rig's **parent** (world root), layer 1/mask 1 so it collides with terrain and props. The rig root's momentum (tracked in `_physics_process`) is applied to the ragdoll Torso body for a seamless handoff. Exiting frees the ragdoll, re-shows `Body/*`, re-enables IK, and stops the animation. The physics harness toggles via its **Stickman ↔ Ragdoll** button, removing the `RigCollisionProxy` on entry and re-adding it (idempotently) on exit. `stickman_rig.tscn` is **not** modified.
> **Phase 11 (Instant Handoff & Recovery):** replaces the hard ragdoll entry/exit with an **instant handoff** and adds a **stand-up recovery** path. `StickmanRig` gains `enum RigState { ANIMATED, RAGDOLL, RECOVERING }` plus exports `rest_timeout` (2.0 s) and `auto_recover` (true). On entering `RAGDOLL` the ragdoll is built from the **current solved bone positions** (the player is stopped with `keep_state`), then `Body/*` is hidden and the IK stack disabled in the same call — no crossfade, since the ragdoll spawns at exactly the same pose and a fade would only read as ghosting (an earlier `transition_duration` blend was removed on director feedback). Rest detection reads the Torso body — sleeping, or linear ≤ `REST_LINEAR_THRESHOLD` (5.0 px/s, tuned up from the plan's 0.1 because a soft-pinned ragdoll micro-jitters around ~0.5 px/s) and angular ≤ 0.1 rad/s — then after `rest_timeout` + `STABILIZATION_DELAY` (0.1 s) with `auto_recover` on, calls `_start_recovery()`. Recovery first calls `_capture_pre_ragdoll_state()` (6 IK markers, 8 limb + head bone rotations, facing profile, 4 bend flags, head LookAt state, per-leg `leg_sides`, and entry chain offsets `torso_chain_offset`/`head_chain_offset`) as the END target, then `_capture_ragdoll_pose()` records the 10 bodies' rig-local pose **and** probes the terrain via `_probe_ground_y()` (a 7-ray fan with a tolerance band, excluding the ragdoll RIDs), re-anchors the rig root so the standing figure's **feet** sit on the ground at the landing X (`_reanchor_root_to_landing()`, `new_root = feet + FOOT_OFFSET`, with a mirror-aware local shift `local_shift = to_local(shift) − to_local(Vector2.ZERO)`), destroys the ragdoll, sets `state = RECOVERING` + emits, snap-solves via `_snap_skeleton_to_pose()` — the 6 IK targets plus direct arm/head bone rotations (`rot_u = chain(u) − θ_t − bone_angle_u`, `rot_l = chain(l) − chain(u) + bone_angle_u − bone_angle_l`, head via `head_chain_offset`), all normalized into `(−π, π]`, with the ankles clamped onto the terrain and the two leg IK mods enabled with the captured knee side — and finally `_play_stand_up()` runs a **2.0 s** sine-in-out tween of the markers → their pre-ragdoll values and the arm/head bones to the snap-computed `_recovery_bones` targets (elbows via `_natural_joint_target`), legs IK-driven. `_on_stand_up_finished()` restores the facing profile + bend flags + LookAt state, re-enables the arm mods, and returns to `ANIMATED`. The per-joint bend decisions live in three editable tables — `RAGDOLL_JOINT_BAND_LIMITS`, `RECOVERY_LEG_SIDE_FORCE`, `RECOVERY_JOINT_PATH` (Joint bend configuration (EDIT ME)); diagnostics are gated behind `DEBUG_RECOVERY := false`.
Torso bone's `bone_angle` (copying the body rotation directly would slam the skeleton −90° and lay it flat). `request_recovery()` is public (no-op unless in `RAGDOLL`); `set_ragdoll(true)` during `RECOVERING` kills the stand-up tween and rebuilds the ragdoll, `set_ragdoll(false)` during `RAGDOLL` routes through recovery, and calls are otherwise idempotent. `is_in_ragdoll()` stays `state == RAGDOLL` (so `RECOVERING` reads as "Stickman"). A new `res://scripts/create_animations.gd` editor script (superseding the deleted `create_walk.gd`) bakes `walk_left`/`walk_right`/the one-shot `stand_up` into `master_rig.tscn`'s `AnimationLibrary` (the baked `stand_up` is an authored reference only — recovery does not play it) — **no `.stk` format change**; `master_rig.tscn` scene nodes are unchanged (only its baked animations are added). The physics harness gains a **Rest** `SpinBox` (0.1–10 s, writes `_rig.rest_timeout`), a **"Recover Now"** button (`request_recovery()`), and a `state_changed` hook that removes the `RigCollisionProxy` on `RAGDOLL` and re-adds it (idempotently) on `ANIMATED`/`RECOVERING`.
Torso bone's `bone_angle` (copying the body rotation directly would slam the skeleton −90° and lay it flat). `request_recovery()` is public (no-op unless in `RAGDOLL`); `set_ragdoll(true)` during `RECOVERING` kills the stand-up tween and rebuilds the ragdoll, `set_ragdoll(false)` during `RAGDOLL` routes through recovery, and calls are otherwise idempotent. `is_in_ragdoll()` stays `state == RAGDOLL` (so `RECOVERING` reads as "Stickman"). A new `res://scripts/tools/create_animations.gd` editor script (superseding the deleted `create_walk.gd`) bakes `walk_left`/`walk_right`/the one-shot `stand_up` into `stickman_rig.tscn`'s `AnimationLibrary` (the baked `stand_up` is an authored reference only — recovery does not play it) — **no `.stk` format change**; `stickman_rig.tscn` scene nodes are unchanged (only its baked animations are added). The physics harness gains a **Rest** `SpinBox` (0.1–10 s, writes `_rig.rest_timeout`), a **"Recover Now"** button (`request_recovery()`), and a `state_changed` hook that removes the `RigCollisionProxy` on `RAGDOLL` and re-adds it (idempotently) on `ANIMATED`/`RECOVERING`.
> **Phase 9 Prop Editor (Rounds 1–7):** a standalone, kid-friendly vector-prop authoring tool — the authoring counterpart to the stickman editor — run via **F6** on `res://scenes/prop_editor.tscn` (not the main scene). It adds a new `.prop` v1.0 JSON format (`res://props/*.prop`, multi-shape stack + deterministic `PropFile.flatten_to_payload()` conversion), a dedicated `PropCanvas` multi-shape canvas, a validated `PropTheme` (`res://prop_theme.json`) with a Round 2–4 `ui` token section, and a save-side 200×200 thumbnail pipeline reusing `PropThumbnail` + a mtime-keyed `ThumbnailCache.prop_file_png`. Round highlights: **R2** — two explicit canvas modes ("Arrange"/"Reshape"), the stickman-style bottom rotation dot + corner scale crosses, color-matched 3 px vertex handles, and cursor-projection "Create Point" with a 0.6 s flash; **R3** — POINTS-mode right-click is point-editing only (segment proximity via `hit_test_segment`), centered mode toolbar with emoji, window title `Untitled — file.prop *`, exit dialog (💾/🗑️/❌), and a debounced autosave/recovery prompt; **R4** — selected-mode is clearly outlined (`FOCUS_NONE` + accent-border `pressed` stylebox), bigger theme-driven defaults, the Grid spinbox + Snap checkbox moved to the menu row (Edit menu removed), File → New with a save prompt, and **Ball removed** from the add menus; **R5** — File → 🕑 Recent (last 5, persisted) and a critical serialization fix (typed `PackedVector2Array` assignments from `{x,y}` dicts silently zeroed every coordinate — `_serialize_shape` now routes through `_coerce_points`); **R6** — Recent updates on save, a per-shape **`reshaped`** flag gates the circle Radius row, and fill-adaptive selection contrast; **R7** — two-tone halo selection (dark backing + bright core) so outlines/boxes/dots/crosses/handles read on any background. Several Prop Editor rows are logged in `docs/tech_debt_and_optimizations.md` (#29–#35).
> **Phase 9 Prop Editor (Rounds 1–7):** a standalone, kid-friendly vector-prop authoring tool — the authoring counterpart to the stickman editor — run via **F6** on `res://scenes/editors/prop/prop_editor.tscn` (not the main scene). It adds a new `.prop` v1.0 JSON format (`res://content/props/*.prop`, multi-shape stack + deterministic `PropFile.flatten_to_payload()` conversion), a dedicated `PropCanvas` multi-shape canvas, a validated `PropTheme` (`res://prop_theme.json`) with a Round 2–4 `ui` token section, and a save-side 200×200 thumbnail pipeline reusing `PropThumbnail` + a mtime-keyed `ThumbnailCache.prop_file_png`. Round highlights: **R2** — two explicit canvas modes ("Arrange"/"Reshape"), the stickman-style bottom rotation dot + corner scale crosses, color-matched 3 px vertex handles, and cursor-projection "Create Point" with a 0.6 s flash; **R3** — POINTS-mode right-click is point-editing only (segment proximity via `hit_test_segment`), centered mode toolbar with emoji, window title `Untitled — file.prop *`, exit dialog (💾/🗑️/❌), and a debounced autosave/recovery prompt; **R4** — selected-mode is clearly outlined (`FOCUS_NONE` + accent-border `pressed` stylebox), bigger theme-driven defaults, the Grid spinbox + Snap checkbox moved to the menu row (Edit menu removed), File → New with a save prompt, and **Ball removed** from the add menus; **R5** — File → 🕑 Recent (last 5, persisted) and a critical serialization fix (typed `PackedVector2Array` assignments from `{x,y}` dicts silently zeroed every coordinate — `_serialize_shape` now routes through `_coerce_points`); **R6** — Recent updates on save, a per-shape **`reshaped`** flag gates the circle Radius row, and fill-adaptive selection contrast; **R7** — two-tone halo selection (dark backing + bright core) so outlines/boxes/dots/crosses/handles read on any background. Several Prop Editor rows are logged in `docs/architecture/tech_debt_and_optimizations.md` (#29–#35).
+166
View File
@@ -0,0 +1,166 @@
{
"anchors": {
"origin": {
"allowed_hands": "both",
"role": "pivot",
"rotation_deg": 0.0,
"x": 0.0,
"y": 0.0,
"z_index_offset": 0
},
"primary_grip": {
"allowed_hands": "both",
"role": "grip",
"rotation_deg": 0.0,
"x": 0.0,
"y": 0.0,
"z_index_offset": 1
}
},
"material_preset": "rubber",
"metadata": {
"created_at": "2026-09-22T00:26:21",
"modified_at": "2026-09-22T00:26:21"
},
"prop_name": "Baseball",
"shapes": [
{
"closed": true,
"fill_color": "#F5F5F0FF",
"outline_color": "#A0A0A0FF",
"outline_width": 2.0,
"points": [
{
"x": 25.1999988555908,
"y": 0.0
},
{
"x": 21.8231983184814,
"y": 12.5999994277954
},
{
"x": 12.5999994277954,
"y": 21.8231983184814
},
{
"x": 0.0,
"y": 25.1999988555908
},
{
"x": -12.5999994277954,
"y": 21.8231983184814
},
{
"x": -21.8231983184814,
"y": 12.5999994277954
},
{
"x": -25.1999988555908,
"y": 0.0
},
{
"x": -21.8231983184814,
"y": -12.5999994277954
},
{
"x": -12.5999994277954,
"y": -21.8231983184814
},
{
"x": 0.0,
"y": -25.1999988555908
},
{
"x": 12.5999994277954,
"y": -21.8231983184814
},
{
"x": 21.8231983184814,
"y": -12.5999994277954
}
],
"radius": 25.1996288299561,
"reshaped": false,
"shape_type": "circle",
"vertex_flags": [
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0
]
},
{
"closed": false,
"fill_color": "#00000000",
"outline_color": "#D62828FF",
"outline_width": 2.0,
"points": [
{
"x": -8.64000034332275,
"y": -20.1599998474121
},
{
"x": -15.8400001525879,
"y": -10.0799999237061
},
{
"x": -15.8400001525879,
"y": 10.0799999237061
},
{
"x": -8.64000034332275,
"y": 20.1599998474121
}
],
"reshaped": true,
"shape_type": "line",
"vertex_flags": [
0,
0,
0,
0
]
},
{
"closed": false,
"fill_color": "#00000000",
"outline_color": "#D62828FF",
"outline_width": 2.0,
"points": [
{
"x": 8.64000034332275,
"y": -20.1599998474121
},
{
"x": 15.8400001525879,
"y": -10.0799999237061
},
{
"x": 15.8400001525879,
"y": 10.0799999237061
},
{
"x": 8.64000034332275,
"y": 20.1599998474121
}
],
"reshaped": true,
"shape_type": "line",
"vertex_flags": [
0,
0,
0,
0
]
}
],
"version": "1.0"
}
+177
View File
@@ -0,0 +1,177 @@
{
"anchors": {
"origin": {
"allowed_hands": "both",
"role": "pivot",
"rotation_deg": 0.0,
"x": 0.0,
"y": 0.0,
"z_index_offset": 0
},
"primary_grip": {
"allowed_hands": "both",
"role": "grip",
"rotation_deg": 90.0,
"x": 0.0,
"y": -82.066291809082,
"z_index_offset": 1
}
},
"material_preset": "wood",
"metadata": {
"created_at": "2026-09-24T23:17:51",
"modified_at": "2026-09-24T23:17:51"
},
"prop_name": "Brown Briefcase",
"shapes": [
{
"closed": true,
"fill_color": "#3D2314FF",
"outline_color": "#26150CFF",
"outline_width": 2.0,
"points": [
{
"x": -48.2742919921875,
"y": -57.9291458129883
},
{
"x": -48.2742919921875,
"y": -106.20344543457
},
{
"x": 48.2742919921875,
"y": -106.20344543457
},
{
"x": 48.2742919921875,
"y": -57.9291458129883
},
{
"x": 34.7574882507324,
"y": -57.9291458129883
},
{
"x": 34.7574882507324,
"y": -90.7556686401367
},
{
"x": -34.7574882507324,
"y": -90.7556686401367
},
{
"x": -34.7574882507324,
"y": -57.9291458129883
}
],
"reshaped": true,
"shape_type": "polygon",
"vertex_flags": [
0,
0,
0,
0,
0,
0,
0,
0
]
},
{
"closed": true,
"fill_color": "#6D3C19FF",
"outline_color": "#3B200AFF",
"outline_width": 3.0,
"points": [
{
"x": -135.168014526367,
"y": -57.9291458129883
},
{
"x": 135.168014526367,
"y": -57.9291458129883
},
{
"x": 135.168014526367,
"y": 77.2388610839844
},
{
"x": -135.168014526367,
"y": 77.2388610839844
}
],
"reshaped": false,
"shape_type": "polygon",
"vertex_flags": [
0,
0,
0,
0
]
},
{
"closed": true,
"fill_color": "#D4AF37FF",
"outline_color": "#8B7500FF",
"outline_width": 1.5,
"points": [
{
"x": -86.8937225341797,
"y": -61.791088104248
},
{
"x": -57.9291458129883,
"y": -61.791088104248
},
{
"x": -57.9291458129883,
"y": -42.481372833252
},
{
"x": -86.8937225341797,
"y": -42.481372833252
}
],
"reshaped": false,
"shape_type": "polygon",
"vertex_flags": [
0,
0,
0,
0
]
},
{
"closed": true,
"fill_color": "#D4AF37FF",
"outline_color": "#8B7500FF",
"outline_width": 1.5,
"points": [
{
"x": 57.9291458129883,
"y": -61.791088104248
},
{
"x": 86.8937225341797,
"y": -61.791088104248
},
{
"x": 86.8937225341797,
"y": -42.481372833252
},
{
"x": 57.9291458129883,
"y": -42.481372833252
}
],
"reshaped": false,
"shape_type": "polygon",
"vertex_flags": [
0,
0,
0,
0
]
}
],
"version": "1.0"
}
File diff suppressed because it is too large Load Diff
+725
View File
@@ -0,0 +1,725 @@
{
"version": "1.0",
"prop_name": "End Table",
"material_preset": "wood",
"anchors": {
"origin": {
"x": 0.0,
"y": 0.0,
"role": "pivot",
"rotation_deg": 0.0,
"allowed_hands": "both",
"z_index_offset": 0
}
},
"metadata": {
"created_at": "2026-10-09T19:39:33",
"modified_at": "2026-10-10T00:13:18"
},
"shapes": [
{
"shape_type": "polygon",
"points": [
{
"x": -98,
"y": -100
},
{
"x": 98,
"y": -100
},
{
"x": 98,
"y": -25
},
{
"x": -98,
"y": -25
}
],
"fill_color": "#B49A82FF",
"outline_color": "#30213DFF",
"outline_width": 3,
"closed": true,
"vertex_flags": [
0,
0,
0,
0
],
"reshaped": true
},
{
"shape_type": "polygon",
"points": [
{
"x": -83,
"y": -84
},
{
"x": 83,
"y": -84
},
{
"x": 83,
"y": -33
},
{
"x": -83,
"y": -33
}
],
"fill_color": "#347E91FF",
"outline_color": "#30213DFF",
"outline_width": 3,
"closed": true,
"vertex_flags": [
0,
0,
0,
0
],
"reshaped": true
},
{
"shape_type": "polygon",
"points": [
{
"x": -76,
"y": -77
},
{
"x": 76,
"y": -77
},
{
"x": 76,
"y": -40
},
{
"x": -76,
"y": -40
}
],
"fill_color": "#B3D1E4FF",
"outline_color": "#30213DFF",
"outline_width": 2,
"closed": true,
"vertex_flags": [
0,
0,
0,
0
],
"reshaped": true
},
{
"shape_type": "polygon",
"points": [
{
"x": -73,
"y": -74
},
{
"x": 73,
"y": -74
},
{
"x": 73,
"y": -69
},
{
"x": -73,
"y": -69
}
],
"fill_color": "#E9F6F9FF",
"outline_color": "#E9F6F9FF",
"outline_width": 0,
"closed": true,
"vertex_flags": [
0,
0,
0,
0
],
"reshaped": true
},
{
"shape_type": "polygon",
"points": [
{
"x": -73,
"y": -47
},
{
"x": 73,
"y": -47
},
{
"x": 73,
"y": -42
},
{
"x": -73,
"y": -42
}
],
"fill_color": "#65ADBDFF",
"outline_color": "#65ADBDFF",
"outline_width": 0,
"closed": true,
"vertex_flags": [
0,
0,
0,
0
],
"reshaped": true
},
{
"shape_type": "polygon",
"points": [
{
"x": -96,
"y": -12
},
{
"x": 96,
"y": -12
},
{
"x": 96,
"y": 4
},
{
"x": -96,
"y": 4
}
],
"fill_color": "#EBD8B5FF",
"outline_color": "#30213DFF",
"outline_width": 3,
"closed": true,
"vertex_flags": [
0,
0,
0,
0
],
"reshaped": true
},
{
"shape_type": "polygon",
"points": [
{
"x": -92,
"y": -10
},
{
"x": 92,
"y": -10
},
{
"x": 92,
"y": -5
},
{
"x": -92,
"y": -5
}
],
"fill_color": "#FFF0CEFF",
"outline_color": "#FFF0CEFF",
"outline_width": 0,
"closed": true,
"vertex_flags": [
0,
0,
0,
0
],
"reshaped": true
},
{
"shape_type": "polygon",
"points": [
{
"x": -96,
"y": 94
},
{
"x": 96,
"y": 94
},
{
"x": 96,
"y": 110
},
{
"x": -96,
"y": 110
}
],
"fill_color": "#EBD8B5FF",
"outline_color": "#30213DFF",
"outline_width": 3,
"closed": true,
"vertex_flags": [
0,
0,
0,
0
],
"reshaped": true
},
{
"shape_type": "polygon",
"points": [
{
"x": -92,
"y": 96
},
{
"x": 92,
"y": 96
},
{
"x": 92,
"y": 101
},
{
"x": -92,
"y": 101
}
],
"fill_color": "#FFF0CEFF",
"outline_color": "#FFF0CEFF",
"outline_width": 0,
"closed": true,
"vertex_flags": [
0,
0,
0,
0
],
"reshaped": true
},
{
"shape_type": "polygon",
"points": [
{
"x": -102,
"y": -108
},
{
"x": -82,
"y": -108
},
{
"x": -82,
"y": 160
},
{
"x": -102,
"y": 160
}
],
"fill_color": "#E5CDA9FF",
"outline_color": "#30213DFF",
"outline_width": 3,
"closed": true,
"vertex_flags": [
0,
0,
0,
0
],
"reshaped": true
},
{
"shape_type": "polygon",
"points": [
{
"x": -98,
"y": -100
},
{
"x": -93,
"y": -100
},
{
"x": -93,
"y": 154
},
{
"x": -98,
"y": 154
}
],
"fill_color": "#FFF0CEFF",
"outline_color": "#FFF0CEFF",
"outline_width": 0,
"closed": true,
"vertex_flags": [
0,
0,
0,
0
],
"reshaped": true
},
{
"shape_type": "polygon",
"points": [
{
"x": 82,
"y": -108
},
{
"x": 102,
"y": -108
},
{
"x": 102,
"y": 160
},
{
"x": 82,
"y": 160
}
],
"fill_color": "#E5CDA9FF",
"outline_color": "#30213DFF",
"outline_width": 3,
"closed": true,
"vertex_flags": [
0,
0,
0,
0
],
"reshaped": true
},
{
"shape_type": "polygon",
"points": [
{
"x": 86,
"y": -100
},
{
"x": 91,
"y": -100
},
{
"x": 91,
"y": 154
},
{
"x": 86,
"y": 154
}
],
"fill_color": "#FFF0CEFF",
"outline_color": "#FFF0CEFF",
"outline_width": 0,
"closed": true,
"vertex_flags": [
0,
0,
0,
0
],
"reshaped": true
},
{
"shape_type": "polygon",
"points": [
{
"x": -116,
"y": -124
},
{
"x": 116,
"y": -124
},
{
"x": 116,
"y": -104
},
{
"x": -116,
"y": -104
}
],
"fill_color": "#E72F4AFF",
"outline_color": "#30213DFF",
"outline_width": 4,
"closed": true,
"vertex_flags": [
0,
0,
0,
0
],
"reshaped": true
},
{
"shape_type": "polygon",
"points": [
{
"x": -112,
"y": -124
},
{
"x": 112,
"y": -124
},
{
"x": 112,
"y": -117
},
{
"x": -112,
"y": -117
}
],
"fill_color": "#DFD0B9FF",
"outline_color": "#30213DFF",
"outline_width": 2,
"closed": true,
"vertex_flags": [
0,
0,
0,
0
],
"reshaped": true
},
{
"shape_type": "polygon",
"points": [
{
"x": 9.0,
"y": -58.0
},
{
"x": 8.693,
"y": -55.671
},
{
"x": 7.794,
"y": -53.5
},
{
"x": 6.364,
"y": -51.636
},
{
"x": 4.5,
"y": -50.206
},
{
"x": 2.329,
"y": -49.307
},
{
"x": 0.0,
"y": -49.0
},
{
"x": -2.329,
"y": -49.307
},
{
"x": -4.5,
"y": -50.206
},
{
"x": -6.364,
"y": -51.636
},
{
"x": -7.794,
"y": -53.5
},
{
"x": -8.693,
"y": -55.671
},
{
"x": -9.0,
"y": -58.0
},
{
"x": -8.693,
"y": -60.329
},
{
"x": -7.794,
"y": -62.5
},
{
"x": -6.364,
"y": -64.364
},
{
"x": -4.5,
"y": -65.794
},
{
"x": -2.329,
"y": -66.693
},
{
"x": -0.0,
"y": -67.0
},
{
"x": 2.329,
"y": -66.693
},
{
"x": 4.5,
"y": -65.794
},
{
"x": 6.364,
"y": -64.364
},
{
"x": 7.794,
"y": -62.5
},
{
"x": 8.693,
"y": -60.329
}
],
"fill_color": "#D72E4CFF",
"outline_color": "#30213DFF",
"outline_width": 2.5,
"closed": true,
"vertex_flags": [
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0
],
"reshaped": true
},
{
"shape_type": "polygon",
"points": [
{
"x": 0.0,
"y": -61.0
},
{
"x": -0.228,
"y": -59.852
},
{
"x": -0.879,
"y": -58.879
},
{
"x": -1.852,
"y": -58.228
},
{
"x": -3.0,
"y": -58.0
},
{
"x": -4.148,
"y": -58.228
},
{
"x": -5.121,
"y": -58.879
},
{
"x": -5.772,
"y": -59.852
},
{
"x": -6.0,
"y": -61.0
},
{
"x": -5.772,
"y": -62.148
},
{
"x": -5.121,
"y": -63.121
},
{
"x": -4.148,
"y": -63.772
},
{
"x": -3.0,
"y": -64.0
},
{
"x": -1.852,
"y": -63.772
},
{
"x": -0.879,
"y": -63.121
},
{
"x": -0.228,
"y": -62.148
}
],
"fill_color": "#FFE3DBFF",
"outline_color": "#FFE3DBFF",
"outline_width": 0,
"closed": true,
"vertex_flags": [
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0
],
"reshaped": true
}
]
}
File diff suppressed because it is too large Load Diff
+206
View File
@@ -0,0 +1,206 @@
{
"version": "1.0",
"scene_name": "Untitled",
"camera": {
"position": {
"x": 328.366180419922,
"y": -711.875732421875
},
"zoom": 0.375657439231873
},
"objects": [
{
"file_id": "stickman_1",
"type": "stickman",
"position": {
"x": -336.0,
"y": -972.0
},
"rotation": 0.0,
"stk_path": "res://content/stickmen/break.stk",
"facing_profile": "FORWARD",
"action_queue": [
{
"type": "walk_to",
"target": {
"x": 696.0,
"y": -612.0
}
},
{
"type": "ragdoll"
},
{
"type": "recover"
},
{
"type": "walk_to",
"target": {
"x": -167.4599609375,
"y": -596.639892578125
}
},
{
"type": "ragdoll"
},
{
"type": "recover"
},
{
"type": "walk_to",
"target": {
"x": -792.1015625,
"y": -615.075927734375
}
}
]
},
{
"file_id": "terrain_1",
"type": "terrain",
"position": {
"x": -384.0,
"y": -576.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "terrain_2",
"type": "terrain",
"position": {
"x": -192.0,
"y": -576.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "terrain_3",
"type": "terrain",
"position": {
"x": 0.0,
"y": -576.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "terrain_4",
"type": "terrain",
"position": {
"x": 192.0,
"y": -576.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "terrain_5",
"type": "terrain",
"position": {
"x": 384.0,
"y": -576.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "terrain_6",
"type": "terrain",
"position": {
"x": 576.0,
"y": -576.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "terrain_7",
"type": "terrain",
"position": {
"x": 768.0,
"y": -576.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "terrain_8",
"type": "terrain",
"position": {
"x": 960.0,
"y": -576.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "terrain_9",
"type": "terrain",
"position": {
"x": 1152.0,
"y": -576.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "terrain_10",
"type": "terrain",
"position": {
"x": 1344.0,
"y": -576.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
}
],
"event_rules": [],
"metadata": {
"created_unix": 1789757195,
"modified_unix": 1789779264.018,
"autosave": false,
"source_path": ""
}
}
+501
View File
@@ -0,0 +1,501 @@
{
"version": "1.2",
"scene_name": "Untitled",
"camera": {
"position": {
"x": -5.46832084655762,
"y": -891.389526367188
},
"zoom": 0.500000059604645
},
"objects": [
{
"file_id": "terrain_1",
"type": "terrain",
"position": {
"x": -1536.0,
"y": -192.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "terrain_2",
"type": "terrain",
"position": {
"x": -1344.0,
"y": -192.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "terrain_3",
"type": "terrain",
"position": {
"x": -1152.0,
"y": -192.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "terrain_4",
"type": "terrain",
"position": {
"x": -960.0,
"y": -192.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "terrain_5",
"type": "terrain",
"position": {
"x": -768.0,
"y": -192.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "terrain_6",
"type": "terrain",
"position": {
"x": -576.0,
"y": -192.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "terrain_7",
"type": "terrain",
"position": {
"x": -384.0,
"y": -192.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "terrain_8",
"type": "terrain",
"position": {
"x": -192.0,
"y": -192.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "terrain_9",
"type": "terrain",
"position": {
"x": 0.0,
"y": -224.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "terrain_10",
"type": "terrain",
"position": {
"x": 192.0,
"y": -224.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "terrain_11",
"type": "terrain",
"position": {
"x": 384.0,
"y": -224.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "terrain_12",
"type": "terrain",
"position": {
"x": 576.0,
"y": -224.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "terrain_13",
"type": "terrain",
"position": {
"x": 768.0,
"y": -224.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "terrain_14",
"type": "terrain",
"position": {
"x": 960.0,
"y": -224.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "terrain_15",
"type": "terrain",
"position": {
"x": 1152.0,
"y": -224.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "terrain_16",
"type": "terrain",
"position": {
"x": 1344.0,
"y": -224.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "stickman_1",
"type": "stickman",
"position": {
"x": -1152.0,
"y": -600.0
},
"rotation": 0.0,
"stk_path": "res://content/stickmen/break.stk",
"facing_profile": "FORWARD",
"action_queue": [
{
"type": "pick_up_prop",
"target_prop_id": "prop_38150657_356201114_1",
"hand": "auto"
},
{
"type": "move_to",
"target": {
"x": -1352.0,
"y": -246.0
},
"style": "run"
},
{
"type": "set_down_prop",
"target_position": {
"x": 324.0,
"y": -552.0
}
},
{
"type": "move_to",
"target": {
"x": -1452.0,
"y": -276.0
},
"style": "run"
},
{
"type": "speak",
"text": "I Don't feel good",
"duration": 1.8,
"stop_current": false,
"keep_acting": false,
"arrangement": "together",
"bubble_layout": "stack",
"connect_bubbles": false,
"reveal": "instant",
"pop_in": false,
"duration_mode": "just_enough",
"bubbles": [
{
"text": "I Don't feel good",
"visual": {
"bubble_style": "talk",
"font_preset": "comic",
"size_preset": "medium",
"color": "#202020",
"effect": "none",
"reveal": "instant",
"pop_in": false,
"word_styles": [
{
"start": 8,
"end": 12,
"effect": "wave",
"color": "#e53935"
}
]
},
"placement": {
"mode": "manual",
"center": {
"x": -19.8551330566406,
"y": -681.649047851563
}
}
}
]
},
{
"type": "ragdoll"
}
]
},
{
"file_id": "prop_1",
"type": "prop",
"position": {
"x": 324.0,
"y": -684.0
},
"rotation": 0.0,
"asset_id": "res://content/props/championship_belt.prop",
"stage_object_id": "prop_38150657_356201114_1",
"roles": {
"depth": "behind",
"start_motion": "physics",
"holds_other_props": false,
"blocks_stickmen": false,
"can_pick_up": true,
"released_blocks_stickmen": false,
"released_bumps_other_props": true,
"legacy_draw_order": false
},
"scale": {
"x": 1.0,
"y": 1.0
},
"material_preset": 4
},
{
"file_id": "prop_2",
"type": "prop",
"position": {
"x": 288.0,
"y": -408.0
},
"rotation": 0.0,
"asset_id": "res://content/props/end_table.prop",
"stage_object_id": "prop_43861651_287430243_2",
"roles": {
"depth": "behind",
"start_motion": "physics",
"holds_other_props": true,
"blocks_stickmen": false,
"can_pick_up": false,
"released_blocks_stickmen": true,
"released_bumps_other_props": true,
"legacy_draw_order": false
},
"scale": {
"x": 1.0,
"y": 1.0
},
"material_preset": 1
},
{
"file_id": "prop_3",
"type": "prop",
"position": {
"x": 288.0,
"y": -1236.0
},
"rotation": 0.0,
"asset_id": "res://content/props/flower_pot.prop",
"stage_object_id": "prop_250061258_3458839433_3",
"roles": {
"depth": "behind",
"start_motion": "physics",
"holds_other_props": false,
"blocks_stickmen": false,
"can_pick_up": true,
"released_blocks_stickmen": true,
"released_bumps_other_props": true,
"legacy_draw_order": false
},
"scale": {
"x": 1.0,
"y": 1.0
},
"material_preset": 1
},
{
"file_id": "stickman_2",
"type": "stickman",
"position": {
"x": 888.0,
"y": -636.0
},
"rotation": 0.0,
"stk_path": "res://content/stickmen/test.stk",
"facing_profile": "FORWARD",
"action_queue": []
}
],
"event_rules": [
{
"id": 1,
"trigger": {
"type": "prop_picked_up",
"source_id": "stickman_1",
"target_id": "prop_1",
"params": {}
},
"actions": [
{
"type": "speak",
"target_id": "stickman_2",
"params": {
"text": "It's JOHN CENA",
"duration": 1.6,
"stop_current": false,
"keep_acting": true,
"arrangement": "together",
"bubble_layout": "stack",
"connect_bubbles": true,
"reveal": "instant",
"pop_in": false,
"duration_mode": "just_enough",
"bubbles": [
{
"text": "It's JOHN CENA",
"visual": {
"bubble_style": "talk",
"font_preset": "scary",
"size_preset": "huge",
"color": "#202020",
"effect": "shake",
"reveal": "instant",
"pop_in": false,
"word_styles": []
},
"placement": {
"mode": "manual",
"center": {
"x": -17.8550415039063,
"y": -674.849975585938
}
}
},
{
"text": "I feel sick",
"visual": {
"bubble_style": "talk",
"font_preset": "scary",
"size_preset": "huge",
"color": "#202020",
"effect": "shake",
"reveal": "instant",
"pop_in": false,
"word_styles": [
{
"start": 7,
"end": 11,
"effect": "wave"
}
]
},
"placement": {
"mode": "manual",
"center": {
"x": 7.14495849609375,
"y": -841.75
}
}
}
]
}
},
{
"type": "ragdoll",
"target_id": "stickman_2",
"params": {}
}
]
}
],
"metadata": {
"created_unix": 1791598662.56,
"modified_unix": 1791598662.56,
"autosave": false,
"source_path": ""
}
}
+546
View File
@@ -0,0 +1,546 @@
{
"version": "1.1",
"scene_name": "Untitled",
"camera": {
"position": {
"x": 1721.90795898438,
"y": -1162.85546875
},
"zoom": 0.413223177194595
},
"objects": [
{
"file_id": "terrain_1",
"type": "terrain",
"position": {
"x": -1344.0,
"y": 192.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "terrain_2",
"type": "terrain",
"position": {
"x": -1152.0,
"y": 192.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "terrain_3",
"type": "terrain",
"position": {
"x": -960.0,
"y": 192.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "terrain_4",
"type": "terrain",
"position": {
"x": -768.0,
"y": 192.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "terrain_5",
"type": "terrain",
"position": {
"x": -576.0,
"y": 192.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "terrain_6",
"type": "terrain",
"position": {
"x": -384.0,
"y": 192.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "terrain_7",
"type": "terrain",
"position": {
"x": -192.0,
"y": 192.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "terrain_8",
"type": "terrain",
"position": {
"x": 0.0,
"y": 192.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "terrain_9",
"type": "terrain",
"position": {
"x": 192.0,
"y": 192.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "terrain_10",
"type": "terrain",
"position": {
"x": 384.0,
"y": 192.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "terrain_11",
"type": "terrain",
"position": {
"x": 576.0,
"y": 192.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "terrain_12",
"type": "terrain",
"position": {
"x": 768.0,
"y": 192.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "terrain_13",
"type": "terrain",
"position": {
"x": 960.0,
"y": 192.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "terrain_14",
"type": "terrain",
"position": {
"x": 1152.0,
"y": 192.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "terrain_15",
"type": "terrain",
"position": {
"x": 1344.0,
"y": 192.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "stickman_1",
"type": "stickman",
"position": {
"x": -1116.0,
"y": -312.0
},
"rotation": 0.0,
"stk_path": "res://content/stickmen/break.stk",
"facing_profile": "FORWARD",
"action_queue": [
{
"type": "recover"
},
{
"type": "walk_to",
"target": {
"x": -433.3017578125,
"y": 137.068603515625
}
},
{
"type": "pick_up_prop",
"target_prop_id": "prop_1",
"hand": "auto"
},
{
"type": "recover"
},
{
"type": "walk_to",
"target": {
"x": 1373.3974609375,
"y": 142.9248046875
}
},
{
"type": "throw_prop",
"target_position": {
"x": 2352.0,
"y": -1176.0
}
}
]
},
{
"file_id": "prop_1",
"type": "prop",
"position": {
"x": -96.0,
"y": -72.0
},
"rotation": 0.0,
"asset_id": "res://content/props/briefcase.prop",
"stage_object_id": "prop_1",
"scale": {
"x": 1.0,
"y": 1.0
},
"material_preset": 1
},
{
"file_id": "terrain_16",
"type": "terrain",
"position": {
"x": 2304.0,
"y": -864.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "terrain_17",
"type": "terrain",
"position": {
"x": 2496.0,
"y": -864.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "terrain_18",
"type": "terrain",
"position": {
"x": 2688.0,
"y": -864.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "terrain_19",
"type": "terrain",
"position": {
"x": 2880.0,
"y": -864.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "terrain_20",
"type": "terrain",
"position": {
"x": 3072.0,
"y": -864.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "terrain_21",
"type": "terrain",
"position": {
"x": 3264.0,
"y": -864.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "terrain_22",
"type": "terrain",
"position": {
"x": 3456.0,
"y": -864.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "terrain_23",
"type": "terrain",
"position": {
"x": 3648.0,
"y": -864.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "area_1",
"type": "area",
"position": {
"x": 624.0,
"y": -36.0
},
"rotation": 0.0,
"scale": {
"x": 1.0,
"y": 1.0
},
"size": {
"x": 96.0,
"y": 96.0
}
},
{
"file_id": "stickman_2",
"type": "stickman",
"position": {
"x": 3516.0,
"y": -1273.0
},
"rotation": 0.0,
"stk_path": "res://content/stickmen/test.stk",
"facing_profile": "FORWARD",
"action_queue": [
{
"type": "walk_to",
"target": {
"x": 3011.76782226563,
"y": -930.535400390625
}
},
{
"type": "speak",
"text": "THROW ME THAT CASE BRO!",
"duration": 2.0,
"stop_current": false
},
{
"type": "wait",
"duration": 9.0
},
{
"type": "pick_up_prop",
"target_prop_id": "prop_1",
"hand": "auto"
}
]
}
],
"event_rules": [
{
"id": 0,
"trigger": {
"type": "entered_area",
"source_id": "stickman_1",
"target_id": "area_1",
"params": {}
},
"actions": [
{
"type": "speak",
"target_id": "stickman_1",
"params": {
"text": "I'm gonna throw this!",
"duration": 2.0,
"stop_current": false
}
}
]
},
{
"id": 1,
"trigger": {
"type": "prop_thrown",
"source_id": "stickman_1",
"target_id": "prop_1",
"params": {}
},
"actions": [
{
"type": "speak",
"target_id": "stickman_1",
"params": {
"text": "HYAHHH!!!",
"duration": 2.0,
"stop_current": false
}
}
]
},
{
"id": 2,
"trigger": {
"type": "prop_picked_up",
"source_id": "stickman_1",
"target_id": "prop_1",
"params": {}
},
"actions": [
{
"type": "speak",
"target_id": "stickman_1",
"params": {
"text": "Max is a BABY!",
"duration": 2.0,
"stop_current": false
}
}
]
},
{
"id": 3,
"trigger": {
"type": "prop_picked_up",
"source_id": "stickman_2",
"target_id": "prop_1",
"params": {}
},
"actions": [
{
"type": "speak",
"target_id": "stickman_2",
"params": {
"text": "I'm GONNA JUMP!",
"duration": 2.0,
"stop_current": false
}
},
{
"type": "walk_to",
"target_id": "stickman_2",
"params": {
"target": {
"x": 4416.0,
"y": -960.0
}
}
}
]
}
],
"metadata": {
"created_unix": 1790221912,
"modified_unix": 1790393015.073,
"autosave": false,
"source_path": ""
}
}
@@ -199,7 +199,7 @@
"y": 132.287109375
},
"rotation": 0.0,
"stk_path": "res://stickmen/break.stk",
"stk_path": "res://content/stickmen/break.stk",
"facing_profile": "FORWARD",
"action_queue": [
{
+321
View File
@@ -0,0 +1,321 @@
{
"version": "1.1",
"scene_name": "Untitled",
"camera": {
"position": {
"x": 0.0,
"y": -400.0
},
"zoom": 0.5
},
"objects": [
{
"file_id": "terrain_1",
"type": "terrain",
"position": {
"x": -1536.0,
"y": 256.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "terrain_2",
"type": "terrain",
"position": {
"x": -1344.0,
"y": 256.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "terrain_3",
"type": "terrain",
"position": {
"x": -1152.0,
"y": 256.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "terrain_4",
"type": "terrain",
"position": {
"x": -960.0,
"y": 256.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "terrain_5",
"type": "terrain",
"position": {
"x": -768.0,
"y": 256.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "terrain_6",
"type": "terrain",
"position": {
"x": -576.0,
"y": 256.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "terrain_7",
"type": "terrain",
"position": {
"x": -384.0,
"y": 256.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "terrain_8",
"type": "terrain",
"position": {
"x": -192.0,
"y": 256.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "terrain_9",
"type": "terrain",
"position": {
"x": 0.0,
"y": 256.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "terrain_10",
"type": "terrain",
"position": {
"x": 192.0,
"y": 256.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "terrain_11",
"type": "terrain",
"position": {
"x": 384.0,
"y": 288.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "terrain_12",
"type": "terrain",
"position": {
"x": 576.0,
"y": 288.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "terrain_13",
"type": "terrain",
"position": {
"x": 768.0,
"y": 288.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "terrain_14",
"type": "terrain",
"position": {
"x": 960.0,
"y": 288.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "terrain_15",
"type": "terrain",
"position": {
"x": 1152.0,
"y": 288.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "terrain_16",
"type": "terrain",
"position": {
"x": 1344.0,
"y": 288.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "terrain_17",
"type": "terrain",
"position": {
"x": 1536.0,
"y": 288.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "terrain_18",
"type": "terrain",
"position": {
"x": 1728.0,
"y": 288.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "terrain_19",
"type": "terrain",
"position": {
"x": 1920.0,
"y": 288.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "terrain_20",
"type": "terrain",
"position": {
"x": 2112.0,
"y": 288.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "stickman_1",
"type": "stickman",
"position": {
"x": -1368.0,
"y": -156.0
},
"rotation": 0.0,
"stk_path": "res://content/stickmen/break.stk",
"facing_profile": "FORWARD",
"action_queue": [
{
"type": "move_to",
"target": {
"x": 2580.0,
"y": 228.0
},
"style": "run"
}
]
}
],
"event_rules": [],
"metadata": {
"created_unix": 1791088389,
"modified_unix": 1791167929.379,
"autosave": false,
"source_path": ""
}
}
+567
View File
@@ -0,0 +1,567 @@
{
"version": "1.1",
"scene_name": "Untitled",
"camera": {
"position": {
"x": 1932.47973632813,
"y": -1292.90002441406
},
"zoom": 0.413223147392273
},
"objects": [
{
"file_id": "terrain_1",
"type": "terrain",
"position": {
"x": -1536.0,
"y": 256.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "terrain_2",
"type": "terrain",
"position": {
"x": -1344.0,
"y": 256.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "terrain_3",
"type": "terrain",
"position": {
"x": -1152.0,
"y": 256.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "terrain_4",
"type": "terrain",
"position": {
"x": -960.0,
"y": 256.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "terrain_5",
"type": "terrain",
"position": {
"x": -768.0,
"y": 256.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "terrain_6",
"type": "terrain",
"position": {
"x": -576.0,
"y": 256.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "terrain_7",
"type": "terrain",
"position": {
"x": -384.0,
"y": 256.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "terrain_8",
"type": "terrain",
"position": {
"x": -192.0,
"y": 256.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "terrain_9",
"type": "terrain",
"position": {
"x": 0.0,
"y": 256.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "terrain_10",
"type": "terrain",
"position": {
"x": 192.0,
"y": 256.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "terrain_11",
"type": "terrain",
"position": {
"x": 384.0,
"y": 288.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "terrain_12",
"type": "terrain",
"position": {
"x": 576.0,
"y": 288.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "terrain_13",
"type": "terrain",
"position": {
"x": 768.0,
"y": 288.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "terrain_14",
"type": "terrain",
"position": {
"x": 960.0,
"y": 288.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "terrain_15",
"type": "terrain",
"position": {
"x": 1152.0,
"y": 288.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "terrain_16",
"type": "terrain",
"position": {
"x": 1344.0,
"y": 288.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "terrain_17",
"type": "terrain",
"position": {
"x": 1536.0,
"y": 288.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "terrain_18",
"type": "terrain",
"position": {
"x": 1728.0,
"y": 288.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "terrain_19",
"type": "terrain",
"position": {
"x": 1920.0,
"y": 288.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "terrain_20",
"type": "terrain",
"position": {
"x": 2112.0,
"y": 288.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "stickman_1",
"type": "stickman",
"position": {
"x": -1368.0,
"y": -156.0
},
"rotation": 0.0,
"stk_path": "res://content/stickmen/break.stk",
"facing_profile": "FORWARD",
"action_queue": [
{
"type": "move_to",
"target": {
"x": 2604.0,
"y": -1092.0
},
"style": "run"
},
{
"type": "speak",
"text": "Wow! Look how high I am!",
"duration": 2.2,
"stop_current": false,
"keep_acting": false,
"arrangement": "together",
"bubble_layout": "stack",
"connect_bubbles": false,
"reveal": "letter",
"pop_in": true,
"duration_mode": "just_enough",
"bubbles": [
{
"text": "Wow! Look how high I am!",
"visual": {
"bubble_style": "talk",
"font_preset": "comic",
"size_preset": "medium",
"color": "#202020",
"effect": "none",
"reveal": "letter",
"pop_in": true,
"word_styles": [
{
"start": 14,
"end": 18,
"effect": "shake",
"color": "#e53935"
}
]
},
"placement": {
"mode": "manual",
"center": {
"x": 17.1448974609375,
"y": -731.795837402344
}
}
}
]
},
{
"type": "move_to",
"target": {
"x": 4083.85961914063,
"y": -1101.71997070313
},
"style": "run"
}
]
},
{
"file_id": "terrain_21",
"type": "terrain",
"position": {
"x": 252.0,
"y": 120.0
},
"rotation": 0.0,
"asset_id": "step",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "terrain_22",
"type": "terrain",
"position": {
"x": 504.0,
"y": -132.0
},
"rotation": 0.0,
"asset_id": "step",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "terrain_23",
"type": "terrain",
"position": {
"x": 744.0,
"y": -384.0
},
"rotation": 0.0,
"asset_id": "step",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "terrain_24",
"type": "terrain",
"position": {
"x": 996.0,
"y": -636.0
},
"rotation": 0.0,
"asset_id": "step",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "terrain_25",
"type": "terrain",
"position": {
"x": 1248.0,
"y": -888.0
},
"rotation": 0.0,
"asset_id": "step",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "terrain_26",
"type": "terrain",
"position": {
"x": 1476.0,
"y": -996.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "terrain_27",
"type": "terrain",
"position": {
"x": 1668.0,
"y": -996.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "terrain_28",
"type": "terrain",
"position": {
"x": 1860.0,
"y": -996.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "terrain_29",
"type": "terrain",
"position": {
"x": 2052.0,
"y": -996.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "terrain_30",
"type": "terrain",
"position": {
"x": 2244.0,
"y": -996.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "terrain_31",
"type": "terrain",
"position": {
"x": 2436.0,
"y": -996.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "terrain_32",
"type": "terrain",
"position": {
"x": 2628.0,
"y": -996.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "terrain_33",
"type": "terrain",
"position": {
"x": 2820.0,
"y": -996.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "terrain_34",
"type": "terrain",
"position": {
"x": 3012.0,
"y": -996.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
}
],
"event_rules": [],
"metadata": {
"created_unix": 1791088389,
"modified_unix": 1791172671.377,
"autosave": false,
"source_path": ""
}
}
+311
View File
@@ -0,0 +1,311 @@
{
"version": "1.2",
"scene_name": "Untitled",
"camera": {
"position": {
"x": -795.213195800781,
"y": -804.61474609375
},
"zoom": 0.550000071525574
},
"objects": [
{
"file_id": "terrain_1",
"type": "terrain",
"position": {
"x": -1344.0,
"y": -160.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "terrain_2",
"type": "terrain",
"position": {
"x": -1152.0,
"y": -160.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "terrain_3",
"type": "terrain",
"position": {
"x": -960.0,
"y": -160.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "terrain_4",
"type": "terrain",
"position": {
"x": -768.0,
"y": -160.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "terrain_5",
"type": "terrain",
"position": {
"x": -576.0,
"y": -160.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "terrain_6",
"type": "terrain",
"position": {
"x": -384.0,
"y": -160.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "terrain_7",
"type": "terrain",
"position": {
"x": -192.0,
"y": -160.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "terrain_8",
"type": "terrain",
"position": {
"x": 0.0,
"y": -160.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "terrain_9",
"type": "terrain",
"position": {
"x": 192.0,
"y": -160.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "terrain_10",
"type": "terrain",
"position": {
"x": 384.0,
"y": -160.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "terrain_11",
"type": "terrain",
"position": {
"x": 576.0,
"y": -160.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "terrain_12",
"type": "terrain",
"position": {
"x": 768.0,
"y": -160.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "terrain_13",
"type": "terrain",
"position": {
"x": 960.0,
"y": -160.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "stickman_1",
"type": "stickman",
"position": {
"x": -1140.0,
"y": -564.0
},
"rotation": 0.0,
"stk_path": "res://content/stickmen/break.stk",
"facing_profile": "FORWARD",
"action_queue": [
{
"type": "move_to",
"target": {
"x": -491.576904296875,
"y": -206.43310546875
},
"style": "run"
},
{
"type": "pick_up_prop",
"target_prop_id": "prop_87555809_220550165_4",
"hand": "auto"
},
{
"type": "move_to",
"target": {
"x": -878.849609375,
"y": -197.342041015625
},
"style": "run"
},
{
"type": "set_down_prop",
"target_position": {
"x": -156.0,
"y": -516.0
}
},
{
"type": "move_to",
"target": {
"x": -1157.03149414063,
"y": -200.978515625
},
"style": "run"
},
{
"type": "move_to",
"target": {
"x": 735.69580078125,
"y": -200.978515625
},
"style": "run"
}
]
},
{
"file_id": "prop_1",
"type": "prop",
"position": {
"x": -156.0,
"y": -1512.0
},
"rotation": 0.0,
"asset_id": "res://content/props/briefcase.prop",
"stage_object_id": "prop_87555809_220550165_4",
"roles": {
"depth": "front",
"start_motion": "physics",
"holds_other_props": false,
"blocks_stickmen": false,
"can_pick_up": true,
"released_blocks_stickmen": false,
"released_bumps_other_props": true,
"legacy_draw_order": false
},
"scale": {
"x": 1.0,
"y": 1.0
},
"material_preset": 1
},
{
"file_id": "prop_2",
"type": "prop",
"position": {
"x": -168.0,
"y": -324.0
},
"rotation": 0.0,
"asset_id": "crate",
"stage_object_id": "prop_1055778436_3931179398_5",
"roles": {
"depth": "front",
"start_motion": "still",
"holds_other_props": true,
"blocks_stickmen": false,
"can_pick_up": false,
"released_blocks_stickmen": true,
"released_bumps_other_props": true,
"legacy_draw_order": false
},
"scale": {
"x": 4.12768173217773,
"y": 4.12768173217773
},
"material_preset": 1
}
],
"event_rules": [],
"metadata": {
"created_unix": 1791342771,
"modified_unix": 1791471934.192,
"autosave": false,
"source_path": ""
}
}
+140
View File
@@ -0,0 +1,140 @@
{
"version": "1.1",
"scene_name": "Untitled",
"camera": {
"position": {
"x": -496.058837890625,
"y": -1357.21496582031
},
"zoom": 0.454545497894287
},
"objects": [
{
"file_id": "stickman_1",
"type": "stickman",
"position": {
"x": -480.0,
"y": -685.0
},
"rotation": 0.0,
"stk_path": "res://content/stickmen/break.stk",
"facing_profile": "FORWARD",
"action_queue": [
{
"type": "speak",
"text": "Hello Everyone",
"duration": 1.5,
"stop_current": false,
"arrangement": "together",
"duration_mode": "just_enough",
"bubbles": [
{
"text": "Hello Everyone",
"visual": {
"bubble_style": "shout",
"font_preset": "comic",
"size_preset": "huge",
"color": "#202020",
"reveal": "letter",
"pop_in": true,
"word_styles": [
{
"start": 6,
"end": 14,
"font_preset": "scary",
"color": "#0077b6",
"effect": "bounce"
}
]
}
}
],
"placement": {
"mode": "manual",
"center": {
"x": 664.741455078125,
"y": 271.705322265625
}
}
}
]
},
{
"file_id": "terrain_1",
"type": "terrain",
"position": {
"x": -768.0,
"y": -96.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "terrain_2",
"type": "terrain",
"position": {
"x": -576.0,
"y": -96.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "terrain_3",
"type": "terrain",
"position": {
"x": -384.0,
"y": -96.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "terrain_4",
"type": "terrain",
"position": {
"x": -192.0,
"y": -96.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "terrain_5",
"type": "terrain",
"position": {
"x": 0.0,
"y": -96.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
}
],
"event_rules": [],
"metadata": {
"created_unix": 1790483201,
"modified_unix": 1790517359.218,
"autosave": false,
"source_path": ""
}
}
+342
View File
@@ -0,0 +1,342 @@
{
"version": "1.3",
"scene_name": "Untitled",
"camera": {
"position": {
"x": -312.75048828125,
"y": -243.914947509766
},
"zoom": 0.885780572891235
},
"objects": [
{
"file_id": "terrain_1",
"type": "terrain",
"position": {
"x": -960.0,
"y": 160.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "terrain_2",
"type": "terrain",
"position": {
"x": -768.0,
"y": 160.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "terrain_3",
"type": "terrain",
"position": {
"x": -576.0,
"y": 160.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "terrain_4",
"type": "terrain",
"position": {
"x": -384.0,
"y": 160.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "terrain_5",
"type": "terrain",
"position": {
"x": -192.0,
"y": 160.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "terrain_6",
"type": "terrain",
"position": {
"x": 0.0,
"y": 160.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "terrain_7",
"type": "terrain",
"position": {
"x": 192.0,
"y": 160.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "terrain_8",
"type": "terrain",
"position": {
"x": 384.0,
"y": 160.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "terrain_9",
"type": "terrain",
"position": {
"x": 576.0,
"y": 160.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "terrain_10",
"type": "terrain",
"position": {
"x": 768.0,
"y": 160.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "terrain_11",
"type": "terrain",
"position": {
"x": 960.0,
"y": 160.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "terrain_12",
"type": "terrain",
"position": {
"x": 1152.0,
"y": 160.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "terrain_13",
"type": "terrain",
"position": {
"x": 1344.0,
"y": 160.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "stickman_1",
"type": "stickman",
"position": {
"x": -756.0,
"y": -241.0
},
"rotation": 0.0,
"stk_path": "res://content/stickmen/break.stk",
"facing_profile": "FORWARD",
"action_queue": [
{
"type": "use_prop",
"target_prop_id": "prop_20906175_2821870920_1",
"target_spot_id": "spot_1"
}
]
},
{
"file_id": "prop_1",
"type": "prop",
"position": {
"x": -144.0,
"y": -732.0
},
"rotation": 0.993539273629036,
"asset_id": "res://content/props/championship_belt.prop",
"stage_object_id": "prop_20906175_2821870920_1",
"roles": {
"depth": "behind",
"start_motion": "still",
"holds_other_props": false,
"blocks_stickmen": false,
"can_pick_up": true,
"released_blocks_stickmen": true,
"released_bumps_other_props": true,
"legacy_draw_order": false
},
"use_spots": [
{
"id": "spot_1",
"number": 1,
"name": "Button",
"position": {
"x": -179.254028320313,
"y": -63.7373542785645
}
},
{
"id": "spot_2",
"number": 2,
"name": "Lever",
"position": {
"x": 182.606811523438,
"y": -29.3857421875
}
}
],
"next_use_spot_number": 5,
"scale": {
"x": 0.999997615814209,
"y": 0.999997615814209
},
"material_preset": 4
},
{
"file_id": "prop_2",
"type": "prop",
"position": {
"x": -1008.0,
"y": -408.0
},
"rotation": 0.0,
"asset_id": "res://content/props/baseball.prop",
"stage_object_id": "prop_109116485_2472952225_1",
"roles": {
"depth": "behind",
"start_motion": "still",
"holds_other_props": false,
"blocks_stickmen": false,
"can_pick_up": true,
"released_blocks_stickmen": true,
"released_bumps_other_props": true,
"legacy_draw_order": false
},
"use_spots": [],
"next_use_spot_number": 1,
"scale": {
"x": 1.0,
"y": 1.0
},
"material_preset": 2
}
],
"event_rules": [
{
"id": 0,
"trigger": {
"type": "prop_used",
"source_id": "stickman_1",
"target_id": null,
"params": {
"target_prop_id": "prop_20906175_2821870920_1",
"spot_scope": "specific",
"target_spot_id": "spot_1"
}
},
"actions": [
{
"type": "speak",
"target_id": "stickman_1",
"params": {
"text": "I pushed it!",
"duration": 1.6,
"stop_current": false,
"keep_acting": true,
"arrangement": "together",
"bubble_layout": "stack",
"connect_bubbles": false,
"reveal": "instant",
"pop_in": false,
"duration_mode": "just_enough",
"bubbles": [
{
"text": "I pushed it!",
"visual": {
"bubble_style": "talk",
"font_preset": "comic",
"size_preset": "medium",
"color": "#202020",
"effect": "none",
"reveal": "instant",
"pop_in": false,
"word_styles": []
}
}
]
}
}
]
}
],
"metadata": {
"created_unix": 1791684250,
"modified_unix": 1791849418.99,
"autosave": false,
"source_path": ""
}
}
+297
View File
@@ -0,0 +1,297 @@
{
"version": "1.1",
"scene_name": "Untitled",
"camera": {
"position": {
"x": 0.0,
"y": -400.0
},
"zoom": 0.5
},
"objects": [
{
"file_id": "stickman_1",
"type": "stickman",
"position": {
"x": -132.0,
"y": -673.0
},
"rotation": 0.0,
"stk_path": "res://content/stickmen/break.stk",
"facing_profile": "FORWARD",
"action_queue": [
{
"type": "speak",
"text": "What is new?",
"duration": 12.0,
"stop_current": false,
"arrangement": "take_turns",
"bubble_layout": "stack",
"connect_bubbles": true,
"reveal": "letter",
"pop_in": true,
"duration_mode": "longest",
"bubbles": [
{
"text": "What is new?",
"visual": {
"bubble_style": "talk",
"font_preset": "comic",
"size_preset": "medium",
"color": "#202020",
"effect": "none",
"reveal": "letter",
"pop_in": true,
"word_styles": []
},
"placement": {
"mode": "manual",
"center": {
"x": 97.1448669433594,
"y": -701.999938964844
}
}
},
{
"text": "What's going on today?\nI'm planning to go to the park.\nWe will play an have fun. 👍",
"visual": {
"bubble_style": "shout",
"font_preset": "comic",
"size_preset": "medium",
"color": "#202020",
"effect": "none",
"reveal": "letter",
"pop_in": true,
"word_styles": [
{
"start": 49,
"end": 53,
"effect": "shake",
"color": "#0077b6"
}
]
},
"placement": {
"mode": "manual",
"center": {
"x": 105.144866943359,
"y": -942.999938964844
}
}
}
]
},
{
"type": "walk_to",
"target": {
"x": 850.0,
"y": -350.0
}
}
]
},
{
"file_id": "terrain_1",
"type": "terrain",
"position": {
"x": -768.0,
"y": -288.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "terrain_2",
"type": "terrain",
"position": {
"x": -576.0,
"y": -288.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "terrain_3",
"type": "terrain",
"position": {
"x": -384.0,
"y": -288.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "terrain_4",
"type": "terrain",
"position": {
"x": 192.0,
"y": -288.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "terrain_5",
"type": "terrain",
"position": {
"x": 384.0,
"y": -288.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "terrain_6",
"type": "terrain",
"position": {
"x": 576.0,
"y": -288.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "terrain_7",
"type": "terrain",
"position": {
"x": 768.0,
"y": -288.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "terrain_8",
"type": "terrain",
"position": {
"x": 960.0,
"y": -288.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "terrain_9",
"type": "terrain",
"position": {
"x": 1152.0,
"y": -288.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "terrain_10",
"type": "terrain",
"position": {
"x": 1344.0,
"y": -288.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "terrain_11",
"type": "terrain",
"position": {
"x": 1536.0,
"y": -288.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "terrain_12",
"type": "terrain",
"position": {
"x": 1728.0,
"y": -288.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "terrain_13",
"type": "terrain",
"position": {
"x": -192.0,
"y": -256.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "terrain_14",
"type": "terrain",
"position": {
"x": 0.0,
"y": -256.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
}
],
"event_rules": [],
"metadata": {
"created_unix": 1790567936,
"modified_unix": 1790975418.635,
"autosave": false,
"source_path": ""
}
}
@@ -143,7 +143,7 @@
"y": -959.0
},
"rotation": 0.0,
"stk_path": "res://stickmen/break.stk",
"stk_path": "res://content/stickmen/break.stk",
"facing_profile": "FORWARD",
"action_queue": [
{
@@ -325,7 +325,7 @@
"y": 255.0
},
"rotation": 0.0,
"stk_path": "res://stickmen/break.stk",
"stk_path": "res://content/stickmen/break.stk",
"facing_profile": "FORWARD",
"action_queue": [
{
@@ -465,7 +465,7 @@
"y": -2177.0
},
"rotation": 0.0,
"stk_path": "res://stickmen/bad.stk",
"stk_path": "res://content/stickmen/bad.stk",
"facing_profile": "FORWARD",
"action_queue": []
}
@@ -325,7 +325,7 @@
"y": 255.0
},
"rotation": 0.0,
"stk_path": "res://stickmen/break.stk",
"stk_path": "res://content/stickmen/break.stk",
"facing_profile": "FORWARD",
"action_queue": [
{
@@ -325,7 +325,7 @@
"y": 255.0
},
"rotation": 0.0,
"stk_path": "res://stickmen/break.stk",
"stk_path": "res://content/stickmen/break.stk",
"facing_profile": "FORWARD",
"action_queue": [
{
+546
View File
@@ -0,0 +1,546 @@
{
"version": "1.1",
"scene_name": "Untitled",
"camera": {
"position": {
"x": 1721.90795898438,
"y": -1162.85546875
},
"zoom": 0.413223177194595
},
"objects": [
{
"file_id": "terrain_1",
"type": "terrain",
"position": {
"x": -1344.0,
"y": 192.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "terrain_2",
"type": "terrain",
"position": {
"x": -1152.0,
"y": 192.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "terrain_3",
"type": "terrain",
"position": {
"x": -960.0,
"y": 192.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "terrain_4",
"type": "terrain",
"position": {
"x": -768.0,
"y": 192.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "terrain_5",
"type": "terrain",
"position": {
"x": -576.0,
"y": 192.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "terrain_6",
"type": "terrain",
"position": {
"x": -384.0,
"y": 192.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "terrain_7",
"type": "terrain",
"position": {
"x": -192.0,
"y": 192.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "terrain_8",
"type": "terrain",
"position": {
"x": 0.0,
"y": 192.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "terrain_9",
"type": "terrain",
"position": {
"x": 192.0,
"y": 192.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "terrain_10",
"type": "terrain",
"position": {
"x": 384.0,
"y": 192.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "terrain_11",
"type": "terrain",
"position": {
"x": 576.0,
"y": 192.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "terrain_12",
"type": "terrain",
"position": {
"x": 768.0,
"y": 192.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "terrain_13",
"type": "terrain",
"position": {
"x": 960.0,
"y": 192.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "terrain_14",
"type": "terrain",
"position": {
"x": 1152.0,
"y": 192.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "terrain_15",
"type": "terrain",
"position": {
"x": 1344.0,
"y": 192.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "stickman_1",
"type": "stickman",
"position": {
"x": -1116.0,
"y": -312.0
},
"rotation": 0.0,
"stk_path": "res://content/stickmen/break.stk",
"facing_profile": "FORWARD",
"action_queue": [
{
"type": "recover"
},
{
"type": "walk_to",
"target": {
"x": -433.3017578125,
"y": 137.068603515625
}
},
{
"type": "pick_up_prop",
"target_prop_id": "prop_1",
"hand": "auto"
},
{
"type": "recover"
},
{
"type": "walk_to",
"target": {
"x": 1373.3974609375,
"y": 142.9248046875
}
},
{
"type": "throw_prop",
"target_position": {
"x": 2352.0,
"y": -1176.0
}
}
]
},
{
"file_id": "prop_1",
"type": "prop",
"position": {
"x": -96.0,
"y": -72.0
},
"rotation": 0.0,
"asset_id": "res://content/props/briefcase.prop",
"stage_object_id": "prop_1",
"scale": {
"x": 1.0,
"y": 1.0
},
"material_preset": 1
},
{
"file_id": "terrain_16",
"type": "terrain",
"position": {
"x": 2304.0,
"y": -864.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "terrain_17",
"type": "terrain",
"position": {
"x": 2496.0,
"y": -864.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "terrain_18",
"type": "terrain",
"position": {
"x": 2688.0,
"y": -864.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "terrain_19",
"type": "terrain",
"position": {
"x": 2880.0,
"y": -864.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "terrain_20",
"type": "terrain",
"position": {
"x": 3072.0,
"y": -864.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "terrain_21",
"type": "terrain",
"position": {
"x": 3264.0,
"y": -864.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "terrain_22",
"type": "terrain",
"position": {
"x": 3456.0,
"y": -864.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "terrain_23",
"type": "terrain",
"position": {
"x": 3648.0,
"y": -864.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "area_1",
"type": "area",
"position": {
"x": 624.0,
"y": -36.0
},
"rotation": 0.0,
"scale": {
"x": 1.0,
"y": 1.0
},
"size": {
"x": 96.0,
"y": 96.0
}
},
{
"file_id": "stickman_2",
"type": "stickman",
"position": {
"x": 3516.0,
"y": -1273.0
},
"rotation": 0.0,
"stk_path": "res://content/stickmen/test.stk",
"facing_profile": "FORWARD",
"action_queue": [
{
"type": "walk_to",
"target": {
"x": 3011.76782226563,
"y": -930.535400390625
}
},
{
"type": "speak",
"text": "THROW ME THAT CASE BRO!",
"duration": 2.0,
"stop_current": false
},
{
"type": "wait",
"duration": 9.0
},
{
"type": "pick_up_prop",
"target_prop_id": "prop_1",
"hand": "auto"
}
]
}
],
"event_rules": [
{
"id": 0,
"trigger": {
"type": "entered_area",
"source_id": "stickman_1",
"target_id": "area_1",
"params": {}
},
"actions": [
{
"type": "speak",
"target_id": "stickman_1",
"params": {
"text": "I'm gonna throw this!",
"duration": 2.0,
"stop_current": false
}
}
]
},
{
"id": 1,
"trigger": {
"type": "prop_thrown",
"source_id": "stickman_1",
"target_id": "prop_1",
"params": {}
},
"actions": [
{
"type": "speak",
"target_id": "stickman_1",
"params": {
"text": "HYAHHH!!!",
"duration": 2.0,
"stop_current": false
}
}
]
},
{
"id": 2,
"trigger": {
"type": "prop_picked_up",
"source_id": "stickman_1",
"target_id": "prop_1",
"params": {}
},
"actions": [
{
"type": "speak",
"target_id": "stickman_1",
"params": {
"text": "Max is a BABY!",
"duration": 2.0,
"stop_current": false
}
}
]
},
{
"id": 3,
"trigger": {
"type": "prop_picked_up",
"source_id": "stickman_2",
"target_id": "prop_1",
"params": {}
},
"actions": [
{
"type": "speak",
"target_id": "stickman_2",
"params": {
"text": "I'm GONNA JUMP!",
"duration": 2.0,
"stop_current": false
}
},
{
"type": "walk_to",
"target_id": "stickman_2",
"params": {
"target": {
"x": 4416.0,
"y": -960.0
}
}
}
]
}
],
"metadata": {
"created_unix": 1790221912,
"modified_unix": 1790393027.475,
"autosave": false,
"source_path": ""
}
}
+322
View File
@@ -0,0 +1,322 @@
{
"version": "1.4",
"scene_name": "Untitled",
"camera": {
"position": {
"x": 0.0,
"y": -400.0
},
"zoom": 0.5
},
"objects": [
{
"file_id": "terrain_1",
"type": "terrain",
"position": {
"x": -1152.0,
"y": -64.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "terrain_2",
"type": "terrain",
"position": {
"x": -960.0,
"y": -64.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "terrain_3",
"type": "terrain",
"position": {
"x": -768.0,
"y": -64.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "terrain_4",
"type": "terrain",
"position": {
"x": -576.0,
"y": -64.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "terrain_5",
"type": "terrain",
"position": {
"x": -384.0,
"y": -64.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "terrain_6",
"type": "terrain",
"position": {
"x": -192.0,
"y": -64.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "terrain_7",
"type": "terrain",
"position": {
"x": 0.0,
"y": -64.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "terrain_8",
"type": "terrain",
"position": {
"x": 192.0,
"y": -64.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "terrain_9",
"type": "terrain",
"position": {
"x": 384.0,
"y": -64.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "terrain_10",
"type": "terrain",
"position": {
"x": 576.0,
"y": -64.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "terrain_11",
"type": "terrain",
"position": {
"x": 768.0,
"y": -64.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "terrain_12",
"type": "terrain",
"position": {
"x": 960.0,
"y": -64.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "terrain_13",
"type": "terrain",
"position": {
"x": 1152.0,
"y": -64.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "prop_1",
"type": "prop",
"position": {
"x": -120.0,
"y": -240.0
},
"rotation": 0.0,
"asset_id": "res://content/props/end_table.prop",
"stage_object_id": "prop_17571232_4221072249_1",
"roles": {
"depth": "front",
"start_motion": "physics",
"holds_other_props": false,
"blocks_stickmen": true,
"can_pick_up": false,
"released_blocks_stickmen": true,
"released_bumps_other_props": true,
"legacy_draw_order": false
},
"use_spots": [],
"next_use_spot_number": 1,
"wobble_on_impact": true,
"scale": {
"x": 1.0,
"y": 1.0
},
"material_preset": 1
},
{
"file_id": "stickman_1",
"type": "stickman",
"position": {
"x": -708.0,
"y": -469.0
},
"rotation": 0.0,
"stk_path": "res://content/stickmen/break.stk",
"facing_profile": "FORWARD",
"action_queue": [
{
"type": "move_to",
"target": {
"x": 936.0,
"y": -120.0
},
"style": "run"
}
]
},
{
"file_id": "terrain_14",
"type": "terrain",
"position": {
"x": 1344.0,
"y": -64.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "terrain_15",
"type": "terrain",
"position": {
"x": 1536.0,
"y": -64.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "terrain_16",
"type": "terrain",
"position": {
"x": 1728.0,
"y": -64.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "terrain_17",
"type": "terrain",
"position": {
"x": 1920.0,
"y": -64.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "terrain_18",
"type": "terrain",
"position": {
"x": 2112.0,
"y": -64.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
}
],
"event_rules": [],
"metadata": {
"created_unix": 1791912219,
"modified_unix": 1791922588.754,
"autosave": false,
"source_path": ""
}
}
+303
View File
@@ -0,0 +1,303 @@
{
"version": "1.4",
"scene_name": "Untitled",
"camera": {
"position": {
"x": 0.0,
"y": -400.0
},
"zoom": 0.5
},
"objects": [
{
"file_id": "terrain_1",
"type": "terrain",
"position": {
"x": -1344.0,
"y": 96.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "terrain_2",
"type": "terrain",
"position": {
"x": -1152.0,
"y": 96.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "terrain_3",
"type": "terrain",
"position": {
"x": -960.0,
"y": 96.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "terrain_4",
"type": "terrain",
"position": {
"x": -768.0,
"y": 96.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "terrain_5",
"type": "terrain",
"position": {
"x": -576.0,
"y": 96.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "terrain_6",
"type": "terrain",
"position": {
"x": -384.0,
"y": 96.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "terrain_7",
"type": "terrain",
"position": {
"x": -192.0,
"y": 96.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "terrain_8",
"type": "terrain",
"position": {
"x": 0.0,
"y": 96.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "terrain_9",
"type": "terrain",
"position": {
"x": 192.0,
"y": 96.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "terrain_10",
"type": "terrain",
"position": {
"x": 384.0,
"y": 96.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "terrain_11",
"type": "terrain",
"position": {
"x": 576.0,
"y": 96.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "terrain_12",
"type": "terrain",
"position": {
"x": 768.0,
"y": 96.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "terrain_13",
"type": "terrain",
"position": {
"x": 960.0,
"y": 96.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "terrain_14",
"type": "terrain",
"position": {
"x": 1152.0,
"y": 96.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "prop_1",
"type": "prop",
"position": {
"x": -792.0,
"y": -120.0
},
"rotation": 0.0,
"asset_id": "res://content/props/end_table.prop",
"stage_object_id": "prop_15501231_3425066006_1",
"roles": {
"depth": "behind",
"start_motion": "still",
"holds_other_props": false,
"blocks_stickmen": false,
"can_pick_up": true,
"released_blocks_stickmen": true,
"released_bumps_other_props": true,
"legacy_draw_order": false
},
"use_spots": [],
"next_use_spot_number": 1,
"wobble_on_impact": true,
"scale": {
"x": 1.0,
"y": 1.0
},
"material_preset": 1
},
{
"file_id": "prop_2",
"type": "prop",
"position": {
"x": 0.0,
"y": -972.0
},
"rotation": 0.0,
"asset_id": "res://content/props/baseball.prop",
"stage_object_id": "prop_26034886_2936602825_2",
"roles": {
"depth": "behind",
"start_motion": "physics",
"holds_other_props": false,
"blocks_stickmen": false,
"can_pick_up": true,
"released_blocks_stickmen": true,
"released_bumps_other_props": true,
"legacy_draw_order": false
},
"use_spots": [],
"next_use_spot_number": 1,
"wobble_on_impact": true,
"scale": {
"x": 1.0,
"y": 1.0
},
"material_preset": 2
},
{
"file_id": "prop_3",
"type": "prop",
"position": {
"x": 12.0,
"y": -48.0
},
"rotation": 0.0,
"asset_id": "crate",
"stage_object_id": "prop_316151302_224236905_3",
"roles": {
"depth": "behind",
"start_motion": "still",
"holds_other_props": false,
"blocks_stickmen": false,
"can_pick_up": true,
"released_blocks_stickmen": true,
"released_bumps_other_props": true,
"legacy_draw_order": false
},
"use_spots": [],
"next_use_spot_number": 1,
"wobble_on_impact": false,
"scale": {
"x": 1.0,
"y": 1.0
},
"material_preset": 1
}
],
"event_rules": [],
"metadata": {
"created_unix": 1791922794,
"modified_unix": 1791923002.879,
"autosave": false,
"source_path": ""
}
}
+269
View File
@@ -0,0 +1,269 @@
{
"version": "1.4",
"scene_name": "Untitled",
"camera": {
"position": {
"x": 0.0,
"y": -400.0
},
"zoom": 0.5
},
"objects": [
{
"file_id": "terrain_1",
"type": "terrain",
"position": {
"x": -1344.0,
"y": 96.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "terrain_2",
"type": "terrain",
"position": {
"x": -1152.0,
"y": 96.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "terrain_3",
"type": "terrain",
"position": {
"x": -960.0,
"y": 96.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "terrain_4",
"type": "terrain",
"position": {
"x": -768.0,
"y": 96.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "terrain_5",
"type": "terrain",
"position": {
"x": -576.0,
"y": 96.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "terrain_6",
"type": "terrain",
"position": {
"x": -492.0,
"y": 708.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "terrain_7",
"type": "terrain",
"position": {
"x": -300.0,
"y": 708.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "terrain_8",
"type": "terrain",
"position": {
"x": -108.0,
"y": 708.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "terrain_9",
"type": "terrain",
"position": {
"x": 84.0,
"y": 708.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "terrain_10",
"type": "terrain",
"position": {
"x": 276.0,
"y": 708.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "terrain_11",
"type": "terrain",
"position": {
"x": 468.0,
"y": 708.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "terrain_12",
"type": "terrain",
"position": {
"x": 660.0,
"y": 708.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "terrain_13",
"type": "terrain",
"position": {
"x": 852.0,
"y": 708.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "terrain_14",
"type": "terrain",
"position": {
"x": 1044.0,
"y": 708.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
}
},
{
"file_id": "prop_1",
"type": "prop",
"position": {
"x": -240.0,
"y": 516.0
},
"rotation": 0.0,
"asset_id": "res://content/props/end_table.prop",
"stage_object_id": "prop_15501231_3425066006_1",
"roles": {
"depth": "behind",
"start_motion": "still",
"holds_other_props": false,
"blocks_stickmen": true,
"can_pick_up": true,
"released_blocks_stickmen": true,
"released_bumps_other_props": true,
"legacy_draw_order": false
},
"use_spots": [],
"next_use_spot_number": 1,
"wobble_on_impact": true,
"scale": {
"x": 1.0,
"y": 1.0
},
"material_preset": 1
},
{
"file_id": "stickman_1",
"type": "stickman",
"position": {
"x": -1284.0,
"y": -312.0
},
"rotation": 0.0,
"stk_path": "res://content/stickmen/break.stk",
"facing_profile": "FORWARD",
"action_queue": [
{
"type": "move_to",
"target": {
"x": -540.0,
"y": 48.0
},
"style": "run"
},
{
"type": "ragdoll"
}
]
}
],
"event_rules": [],
"metadata": {
"created_unix": 1791922794,
"modified_unix": 1791948300.039,
"autosave": false,
"source_path": ""
}
}
+15
View File
@@ -0,0 +1,15 @@
# Documentation
- `guides/` contains user guides and practical project workflows.
- `architecture/` describes implemented systems and maintained technical references.
- `specs/` contains feature specifications, including completed phase specs.
- `ideas/` contains the live backlog of future feature ideas.
- `plans/` contains accepted phase design briefs and active or future implementation plans.
- `archive/` preserves older project references and completed plan drafts.
- `known_issues.md` tracks current bugs and deferred fixes.
Keep `README.md` and `AGENTS.md` at the repository root for project onboarding and implementation rules.
## Architecture references
- [Repository organization](architecture/repository_organization.md) documents the feature-based folder layout, legacy content-path mapping, and serialized file-format compatibility contract.
@@ -1,9 +1,9 @@
# Sandbox theme reference
`res://sandbox_theme.json` is the hand-editable presentation configuration for
the Sandbox Stage (`res://scenes/sandbox_stage.tscn`). It is loaded by
the Sandbox Stage (`res://scenes/stage/sandbox_stage.tscn`). It is loaded by
`SandboxStage` during `_ready()`, before the code-built UI is created. The
loader is `res://scripts/sandbox_theme.gd` (`class_name SandboxTheme`).
loader is `res://scripts/stage/sandbox_theme.gd` (`class_name SandboxTheme`).
The committed file uses schema `"2.0"`. The theme controls presentation
defaults only; it does not save a scene, placement, or action data.
@@ -0,0 +1,46 @@
# Repository organization
## Maintained layout
| Path | Responsibility |
|---|---|
| `scenes/main.tscn` | Startup wrapper; configured as `run/main_scene` and hosts the Stickman Editor. |
| `scenes/editors/{stickman,prop,stage}/` | Feature editor scenes. |
| `scenes/stage/` | Sandbox Stage runtime scenes. |
| `scenes/characters/stickman/` | Stickman runtime rig scenes. |
| `scenes/tools/` | Test and physics harness scenes. |
| `scenes/legacy/` | Preserved legacy rig scenes. |
| `scripts/editors/{stickman,prop,stage}/` | Editor controllers and feature-specific editing systems. |
| `scripts/{stage,characters/stickman,props,terrain,speech}/` | Runtime and domain systems grouped by feature. |
| `scripts/shared/` | Reusable UI, thumbnail, and content-path utilities. |
| `scripts/tools/` | Harness and rig-authoring scripts. |
| `content/{stickmen,props,stages}/` | Project-managed `.stk`, `.prop`, and `.scene` libraries. |
| `resources/fonts/` | Project fonts. |
| `docs/{guides,architecture,specs,plans}/` | Maintained guides, architecture references, specifications, and active plans. |
| `docs/archive/` | Older project references and completed plan drafts. |
Scripts and scenes remain separate roots, with feature-specific editor code and scenes grouped by workflow. Godot `.gd.uid` files stay beside their scripts; scene resource UIDs are retained when scenes move.
## Content path compatibility
`ContentPaths` is implemented in `scripts/shared/content_paths.gd`. Its `canonicalize(path: String) -> String` helper maps project-relative legacy paths to the new library locations:
| Legacy project path | Canonical path |
|---|---|
| `res://stickmen/<file>.stk` | `res://content/stickmen/<file>.stk` |
| `res://props/<file>.prop` | `res://content/props/<file>.prop` |
| `res://game_scenes/<file>.scene` | `res://content/stages/<file>.scene` |
Equivalent project-relative inputs (for example, `stickmen/<file>.stk`) map to relative `content/...` paths, and absolute paths under the project root keep their absolute form while their legacy directory is rewritten. `user://` paths, unrelated relative paths, unknown project folders, and absolute paths outside the project are passed through unchanged. Keep path-bearing loads, saves, persisted references, recents, and recovery metadata routed through `canonicalize()` so existing project references continue to resolve after the move.
## Serialized file compatibility
The organization change moves libraries and code without changing the `.stk`, `.prop`, or `.scene` document schemas. It adds no serialized fields and requires no format-version bump. Existing field meanings and codec migrations remain authoritative:
| Format | Current codec version | Compatibility |
|---|---|---|
| `.stk` | `1.5` | The Stickman Editor accepts `1.0`–`1.4` files and migrates them on load. |
| `.prop` | `1.0` | `PropFile` accepts version `1.0`; missing supported optional fields use codec defaults. |
| `.scene` | `1.1` | `StageSceneIO` accepts `1.0` and migrates it to `1.1` on load. |
When a stage is loaded or saved, its document path and path-valued asset references are canonicalized. A legacy reference such as `res://stickmen/basic.stk` may therefore be serialized as `res://content/stickmen/basic.stk` on the next save. This changes the stored path value only: the `.scene` field structure and version remain unchanged, with no added keys.
@@ -0,0 +1,63 @@
# Speech System — As Built
**Updated:** 2026-10-03
This is the current implementation overview for speech authoring and playback. It records the reviewed UX and the behavior present in the stage scripts. The detailed specifications linked below provide payload and layout specifics; older plans and superseded proposals are historical context, not the current UI contract.
## Editor flow
`SandboxStage` opens a speech editing session anchored to the character and keeps a live bubble preview on the stage. `SpeechComposer` owns the editable draft, selected bubble, Message editor, recovery data, and preview clock. Stage-side controls are split by scope:
| Control | Scope | Current behavior |
| --- | --- | --- |
| **Speech Style** | Selected bubble | Message, Frame, Letters, and Effects; selecting a stage word targets that word, otherwise text/font/size/color/effect choices apply to the bubble. |
| **Show speech** | Whole sequence | Show all or One at a time, Stack or Same spot placement actions, and a separate Connect bubbles toggle. More speech settings contains reveal, entrance motion, timing, and Keep acting while talking. |
| **Preview speech** | Whole sequence | Plays the current draft on the stage without committing it. |
| **Done styling** | Style controls | Hides Speech Style while leaving the speech session open. |
| **Done speech / Cancel speech** | Editing session | Done commits the validated draft to its queue/rule destination. Cancel discards the session, with confirmation for changed drafts. |
Draft changes are recoverable while editing. They do not mutate the queued action until Done speech. Scene saving remains a separate operation after the in-memory speech commit.
## Bubble editing and presentation
- Each bubble has its own message, frame, font, size, text color, effects, and stage position. Numbered stage badges select bubbles; Earlier/Later changes their reading order without changing placement.
- Add bubble inserts after the selected bubble and carries over its bubble-level appearance defaults. The current cap is eight bubbles, with a hint at five. Remove is unavailable for the last bubble; Undo appears only after a removal and restores that bubble.
- Stack arranges the sequence upward from Bubble 1. Same spot aligns bubbles to Bubble 1's center. These are placement actions; Connect bubbles is a separate on/off presentation setting. With connections on, the sequence builds cumulatively: Bubble 1 points to the character and later balloons attach to the prior bubble. Think uses thought dots; SFX has no connector.
- Show all and One at a time remain the sequence visibility choices. Reveal supports all at once, letter by letter, and word by word. Entrance motion is Bounce in or Just appear; timing is Normal, Slow, or Slower. Reactive speech can use Keep acting while talking.
- The Message sub-dialog is attached to Speech Style and does not dim the stage. Its text field stays in place as the emoji grid opens and the dialog grows downward. Opening Message puts the caret at the end; emoji inserts at the caret.
- Text edits preserve valid word styling: edits within styled words retain styling on remaining/replacement content; inserted text inherits the preceding token's style, or the following token's style at the start. Bubble-level controls change only the selected bubble unless a stage word is targeted.
- Talk, Shout, and Whisper render a continuous filled body-and-tail contour. Think uses dots; SFX is tailless. Text is measured against the selected font and size with added inner padding, including larger Shout padding. Preview and runtime use the same generated bubble geometry; no external bubble SVG is required.
- Speech order badges remain screen-sized across camera zoom and do not move when selection changes. Crowded badges are sorted into a row/rail; intersecting bubble frames form a sorted row above the largest frame when space allows. Speech panels avoid the resolved badge bounds.
- Bubble dragging hit-tests the smallest bubble under the pointer, breaking equal-size ties in favor of the topmost bubble. A click where bubbles overlap still opens the bubble picker; clicking a badge selects that numbered bubble and brings it forward for editing.
## Data and rendering flow
Speech is stored as a normalized action `Dictionary` with action-wide sequence settings and an ordered `bubbles[]` array. Each bubble stores plain text, visual defaults, optional whole-word style spans, and placement. `SpeechPayload` validates and normalizes data before the stage preview, queue/rule routing, scene serialization, or runtime playback. Legacy single-text speech remains loadable; legacy comic-chain payloads are normalized to the current connection setting.
The editor preview and runtime playback share `SpeechBubble`. `StickmanActionRunner` owns runtime elapsed time and speech completion; `StageSceneIO` and `ActionRegistry` preserve speech fields in saved scenes and rule actions. The editor preview is separate from runner playback and does not advance or replace runtime speech state.
## Main implementation files
| File | Responsibility |
| --- | --- |
| `scripts/editors/stage/speech_composer.gd`, `scenes/editors/stage/speech_composer.tscn` | Draft editing, bubble management, Message/Emoji UI, recovery, style context, and preview state. |
| `scripts/speech/speech_style_toolbar.gd` | Bubble-scoped stage controls and attached choosers. |
| `scripts/speech/speech_show_toolbar.gd` | Whole-sequence controls, layout presets, preview/done/cancel, and More settings. |
| `scripts/speech/speech_payload.gd` | Payload defaults, validation, migration, word spans, and duration calculation. |
| `scripts/speech/stickman_speech_bubble.gd` | Shared preview/playback renderer, bubble measurement and geometry, word targeting, order badges, and bubble hit testing. |
| `scripts/editors/stage/sandbox_stage.gd` | Session lifecycle, stage input routing, toolbar placement, badge avoidance, and queue/rule commit. |
| `scripts/characters/stickman/stickman_action_runner.gd` | Runtime speech clocks and bubble ownership. |
| `scripts/stage/stage_scene_io.gd`, `scripts/stage/action_registry.gd` | Scene and rule serialization adapters. |
| `scripts/stage/sandbox_theme.gd`, `sandbox_theme.json`, `resources/fonts/` | Theme-backed speech font presets and per-font base sizes; Comic, Pixel, and Scary font assets are included. |
## Detailed behavior specifications
- [Whole-sequence Show controls](../docs/specs/speech_whole_sequence_show.md)
- [Speech Style toolbar](../docs/specs/speech_style_toolbar.md), [live editing](../docs/specs/speech_style_toolbar_live_edit.md), and [Message/Emoji/Timing](../docs/specs/speech_message_emoji_timing.md)
- [Bubble layout presets](../docs/specs/speech_bubble_layout_presets.md) and [bubble visuals](../docs/specs/speech_bubble_visuals.md)
- [Badge stability, grouping, and toolbar avoidance](../docs/specs/speech_badge_stability.md)
- [Phase 13 payload and playback details](../docs/specs/phase_13_speech_bubbles.md)
## Validation status
The user reviewed the recent badge clustering and overlapping-bubble drag behavior and reported that the speech editor looked good. Focused Godot syntax checks and `git diff --check` passed for those latest fixes. The saved `content/stages/talk.scene` had also loaded through the Sandbox Stage CLI without a skipped-speech warning during the earlier Phase 13 verification (2026-09-28). This does not claim a complete automated or GUI regression pass for every speech control and runtime mode.
@@ -0,0 +1,173 @@
# Technical Debt & Optimization Log
## Overview
This document tracks known technical debt, optimization opportunities, and minor improvements for the Stickman Studio project. Items are prioritized by impact and effort. Update this log as issues are identified, resolved, or deprioritized.
---
## Current Observations
| # | Issue | Priority | Status | Notes |
| --- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------- | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------- |
| 1 | **Ragdoll Neck Angular Limits** — The ragdoll neck `PinJoint2D` remains unrestricted, so a violent physical tumble can still rotate the head freely. | Medium | Open | Phase 4.5 fixes the separate ANIMATED collision-flinch accumulation bug by bounding offsets from a captured base rotation; it does not change ragdoll neck limits. Consider a tuned angular band/stiffness when natural ragdoll head motion is revisited. |
| 2 | **Torso Capsule Origin** — The torso capsule's local origin should be at its midpoint for natural rotation. Currently derived from bone distance; verify alignment. | Low | Open | Test by rotating the torso body in ragdoll mode — it should spin about its center, not its top. Adjust `position` offset if needed. |
| 3 | **Collision Layers Separation** — Bodies and terrain share layer 1/mask 1. This may cause self‑collision issues (limbs clipping through each other) under high stress. | Low | Open | Future enhancement: assign ragdoll limbs to layer 2, terrain to layer 1, and use masks to allow limb‑limb collision only where desired. |
| 4 | **Performance (Ragdoll Pooling)** — Spawning 10 bodies + 9 joints procedurally is fine for a single rig. If the scene ever contains dozens of ragdolls, consider a pooling system to avoid allocation spikes. | Low | Open | Not needed now, but worth noting if scaling to large crowds. |
| 5 | **Animated Collision Proxy Approximation** — Stickman collision proxies use fixed-radius capsules for torso and limbs rather than exact mounted-shape polygons. | Low | Open | Phase 4.5 synchronizes ten proxies to the rendered parts and grows the head circle to cover mounted geometry (minimum radius 90 px), but torso/limb capsule radii remain approximate and do not adapt to arbitrary custom widths. Consider per-shape collision generation only if visible misses justify the complexity. (2026-09-12) |
| 6 | **Recovery Animation Starting Pose** — The `stand_up` animation must work from any captured ragdoll pose. Currently uses a fixed start frame. | High | ✅ Resolved | Phase 11: `_start_recovery()` captures the 10 bodies' rig‑local pose, snap‑solves the skeleton via the 6 IK targets, then `_play_stand_up()` tweens the markers **directly** from the captured values to `STAND_POSE` (`STAND_UP_DURATION`, sine ease‑in‑out) — the baked `stand_up` animation is **not** played (a fixed first keyframe can never match an arbitrary rest pose; the earlier bridge‑into‑the‑animation approach caused a visible jump and was removed). Revision: the snap now derives the hip (`pos − dir·half`) and wrist/ankle (`pos + dir·half`) from the capsule ends and subtracts the Torso bone's `bone_angle` for the marker rotation, so recovery starts from the ragdoll's exact final pose (e.g. sitting stays sitting). (2026‑08‑27) |
| 7 | **Transition Visual Pop** — The crossfade between kinematic and ragdoll currently uses a simple `modulate.a` lerp. This may cause ghosting if the kinematic and ragdoll poses are misaligned. | Medium | ✅ Resolved | Phase 11: the entry is now an **instant handoff** — the ragdoll is built from the **current solved bone positions** (`AnimationPlayer.stop(true)` keeps the pose), then `Body/*` is hidden and the IK stack disabled in the same call. The earlier opacity crossfade + pin‑softness ramp was **removed on director feedback** (it read as ghosting, since both poses are identical). Recovery snap‑solves the kinematic skeleton to the captured ragdoll pose before re‑showing `Body/*`, eliminating the pop on both directions. (2026‑08‑27) |
| 8 | **Rest Timeout UI** — The director can adjust `rest_timeout` via inspector, but there is no in‑world UI in the physics harness yet. | Low | ✅ Resolved | Phase 11: added a Rest `SpinBox` (0.1–10 s, step 0.1) to the harness UI that writes `_rig.rest_timeout` (runtime‑only), plus a "Recover Now" button → `_rig.request_recovery()`. (2026‑08‑27) |
| 9 | **Animation Generation DRY** — `create_walk.gd` is a standalone script. It should be merged into a unified `create_animations.gd` that also generates `stand_up` and idle animations. | Medium | ✅ Resolved | Phase 11: `create_walk.gd` deleted; new `scripts/tools/create_animations.gd` (`@tool extends EditorScript`) bakes `walk_left`/`walk_right` (same keyframes) and a one‑shot `stand_up` into the scene's `AnimationLibrary` (the baked `stand_up` is an authored reference only — runtime recovery tweens the IK targets directly). (2026‑08‑27) |
| 10 | **Rig Collision Proxy Re‑addition** — The proxy is re‑added on ragdoll exit, but may cause a brief visual pop if it appears while the kinematic rig is visible. | Low | Open | Phase 11 still re‑adds the proxy as soon as `RECOVERING` begins (`state_changed` handler), while `Body/*` is already visible — the static box can pop in around the standing figure before the stand‑up completes. Consider delaying re‑addition until after recovery finishes (`ANIMATED`). (2026‑08‑27) |
| 11 | **Stage Freeze Abstraction** — Sandbox Stage EDIT‑mode freezing is type‑specific: `RigidBody2D.freeze_mode = FREEZE_MODE_KINEMATIC` for props, `StickmanRig.set_ragdoll(false)` for stickmen, nothing for `StaticBody2D` terrain. There is no unified "freeze" abstraction over the mixed physics population. | Low | Open | A future physics type (e.g. `Area2D`‑based sensors) will need another case in `scripts/editors/stage/sandbox_stage.gd` `_enter_edit_mode()` / `_enter_play_mode()`. Consider a duck‑typed `set_simulating(bool)` interface once more physical object kinds appear. (2026‑08‑27) |
| 12 | **Stage AABB Selection Precision** — `StageSelection.get_world_aabb` uses conservative world‑space AABBs (polygon point union / fixed rig rect), not point‑in‑polygon. | Low | Open | Clicks in the bounding‑box corners of large or rotated terrain may select a block even outside its polygon, and overlapping blocks can mis‑select. Refine with `Geometry2D.is_point_in_polygon()` for `TerrainBlock`/`PropBlock` polygons (and circle distance for ball props) once selection precision matters. (2026‑08‑27) |
| 13 | **Slope-aware walking physics & dynamic obstacle avoidance (deferred)** — Phase 3a bakes a real navigation mesh (per-`TerrainBlock` polygon decomposition into a code-built `NavigationRegion2D`) and `walk_to` follows `NavigationAgent2D` paths, but the figure walks the path with an upright pose (no tilt to the slope, no physics sliding) and `avoidance_enabled = false`, so it can path through props and other stickmen. | Medium | Open | A stickman crossing a ramp/stair follows the sloped footprint but looks flat-footed, and does not avoid moving props or each other. Future: tilt/rotate the figure to the path slope and enable RVO avoidance (`avoidance_enabled`, avoidance layers, `velocity` handling) once content/props/other stickmen are registered as obstacles. (2026‑08‑29) |
| 14 | **Phase 4 event engine is O(rigs×props + areas×movables) per physics frame** — `sandbox_stage.gd` `_update_area_entry()` / `_update_stickman_prop_collision()` run all-pairs geometric tests every physics frame in PLAY. | Low | Open | Fine at sandbox scale, but degrades quadratically with dozens of dynamic objects. A cell-keyed spatial broadphase (e.g. `_grid_cells`) can cull candidate pairs before the visible-AABB/feet-point tests. Phase 4b implemented that index (see #16, now Resolved) but the event engine does **not yet query it** — #14 remains Open until `_update_area_entry()` / `_update_stickman_prop_collision()` use it. (2026‑08‑30 / note updated 2026‑09‑02) |
| 15 | **Walk arrival jitter (mode re-evaluated per frame + arrival-radius mismatch)** — `StickmanRig._update_walking()` re-computes `_walk_mode` from `is_target_reachable()` every physics frame, and finishes via `is_navigation_finished()` (`NAV_TARGET_DESIRED_DISTANCE` 12 px, feet-space) *or* the `ARRIVE_DISTANCE` 8 px root-space guard. Near the nav-mesh boundary (clicked waypoints often sit just off placed terrain) `is_target_reachable()` can flip, swapping `root_target` between the nav path point and the raw waypoint — two points with a small vertical offset → up/down jitter. | Medium | ✅ Resolved | Phase 4b: `StickmanRig._update_walking()` now **latches `_walk_mode` once per walk** (probes up to `LATCH_PROBE_MAX_FRAMES` = 6 after the map syncs, latching `"nav"` when reachable or `"direct"` on the bound) so it can never flip between frames, unifies arrival on the **final target** at `ARRIVE_DISTANCE` 8 px (snap-on-arrive), steers straight at the final target when within `2 × ARRIVE_DISTANCE`, and re-asserts the standing markers one extra physics frame after stop (`_settle_walk_markers()`) to clear any residual body-bob. Exactly one `arrive` fires; `global_position` is unchanged after arrival. See `docs/archive/plans/PHASE_4b_SPEC.md` §3. (2026-09-02) |
| 16 | **No grid spatial dictionary** — terrain placement (`sandbox_stage.gd` `_place_at`) is single-click with no occupancy tracking; the 3-state "empty/same-type/blocked" drag-paint query and any spatial broadphase need a cell→nodes index. | Low | ✅ Resolved | Phase 4b adds an **advisory** grid spatial dictionary `_grid_cells` (cell `Vector2i` @ `TERRAIN_GRID_SIZE` 16 → `Array[Node2D]`) plus `_rasterize_aabb_to_cells()` to index every world AABB's covered cells; it drives the terrain drag-painting 3-state ghost query and the director target-validity test (skipping cells whose nodes include a `TerrainBlock`). It is populated on place, rebuilt on move/rotate/delete, and is **never authoritative** (the `World` tree is). It is **not yet wired into the Phase 4 event engine** — #14 stays Open for that. See `docs/archive/plans/PHASE_4b_SPEC.md` §2.6. (2026-09-02) |
| 17 | **Thumbnail caching has no eviction / cap; render is deferred one-per-frame** — Phase 3b (`stickman_library.gd` / `prop_library.gd` + `thumbnails/*`) caches rig/prop thumbnails to `user://thumbnails/` keyed by stickman basename+mtime and prop `id_v<PROP_VERSION>`. | Low | Open | PNGs accumulate unboundedly on disk (only `clean_stale_stickmen` prunes superseded basenames; nothing caps total bytes), and in-headless the renderers return `null` → a placeholder is shown until a **manual F6 run** populates real captures. Future: a disk-size/LRU eviction policy, a version/cleanup sweep on stage open, and an explicit cache-warm pass (or skip-the-placeholder note) for headless/CI. (2026-09-03) |
| 18 | **Asset selection is session-only, not saved** — `StageSpawner.selected_stickman_path` / `selected_prop_id` reset on scene reload (Phase 3b decision, per spec). | Low | Open | Deliberate for Phase 3b (spec §2 decision 1: no disk save, do not extend `_save_settings`). A future persistence phase could persist the last-chosen stickman/prop to `user://sandbox_settings.json` for convenience. (2026-09-03) |
| 19 | **Phase 3c editor-popup host-visibility discipline is ad-hoc** — the new `QueuePanel` / `RulePanel` / `ActionEditor` / `RuleEditor` are `exclusive = true` `PopupPanel`s, so the stage must hide a host popup before entering a stage-click capture (or opening a nested editor) and re-show it on resolve/cancel. That logic is spread across `_open_action_editor`, `_on_action_editor_committed` / `_cancelled`, `_begin_walk_edit`, and `_on_rule_editor_trigger_target_requested`, with one-off `_restore_queue_panel` / `_restore_rule_panel` / `_restore_rule_build_panel` helpers. | Medium | Open | Works, but fragile: any future editor popup must remember to hide its host before every capture or the exclusive window swallows the stage click. A small popup-stack abstraction (push host → capture → pop) would make that impossible to forget. (2026-09-04) |
| 20 | **Rule Panel "Add Rule" has no source in waypoint-filtered panels** — `_on_rule_panel_add_requested` bails with a toast when `_rule_panel_source_id < 0`, which is exactly the state of the waypoint-filtered "Edit Trigger Rules" panel (opened from the waypoint context menu). | Low | Open | The trigger source is genuinely ambiguous there (a waypoint can be targeted by rules authored by several stickmen). Acceptable for now; a future pass could default the source to `_ctx_waypoint_rig` (the rig that owns the waypoint) or open the builder in "any stickman" (source −1) mode. (2026-09-04) |
| 21 | **Phase 3c drag-reorder uses nearest-row-center, not an insertion point** — `QueuePanel` / `RulePanel` reorder computes the drop target as the row whose vertical center is nearest the mouse, then moves the dragged item to that index. | Low | Open | Functional, but the drop position can read off-by-one near row boundaries (it snaps to a whole row rather than an edge between rows). A true insertion-point indicator (a line drawn between rows) would be clearer; not worth the complexity until reordering long queues/rules is common. (2026-09-04) |
| 22 | **`--check-only --script` needs a prior `--editor` rescan for new `class_name` scripts** — a freshly added `class_name` (e.g. `QueuePanel`) is not resolvable by `--check-only --script res://scripts/editors/stage/sandbox_stage.gd` until `--headless --editor --quit` regenerates `.godot/global_script_class_cache.cfg`. | Low | Open | Verification-workflow gotcha only (not a runtime bug): in-editor the cache is always current. CI/headless scripts that add new `class_name`s should run an `--editor` pass before per-script `--check-only`. (2026-09-04) |
| 23 | **Phase 3c confirmation dialog is popped over an exclusive panel** — Queue delete / Clear All (`_on_queue_panel_delete_requested` / `_on_queue_panel_clear_requested`) and Rule delete / Clear All (`_on_rule_panel_delete_requested` / `_on_rule_panel_clear_requested`) call the shared `ConfirmationDialog` (`_ask_confirm`) **without first hiding the `exclusive = true` `QueuePanel` / `RulePanel`**. | Low | Open | Distinct facet of #19 (host-visibility discipline): popping a second exclusive window over a visible exclusive panel logs a **non-fatal engine warning** and the confirmation may render **non-modal**. It still functions (the panel is re-shown after the callback) — cosmetic. A fix would hide the host panel around `_ask_confirm` and re-show it in the confirm callback (and on the dialog's cancel/hide). Deliberate non-debt note: plan §11.2's separate confirmation dialog for editing `ragdoll`/`recover` actions was implemented instead as a paramless `ActionEditor` pre-fill — recorded in `docs/specs/phase_3c_editor_spec.md` §2 decision 5, not as a debt row. (2026-09-04) |
| 24 | **Sandbox rule references use session instance IDs at runtime** — trigger sources/targets and action actors use `Object.get_instance_id()` integers; StageSceneIO remaps these through per-save `file_id` values. | Low | Open | This is correct for the current in-memory engine and save/load remapping. Phase 11’s approved spec adds persistent per-instance prop `stage_object_id` values for queue/rule prop actions and upgrades the scene format to 1.1; implementation and compatibility verification remain open. (2026‑09‑11; spec decision 2026‑09‑24) |
| 25 | **Contact direct-state timing fallback** — `PropBlock` may receive `body_entered` before a matching direct-state contact row is available. | Low | Open | `_on_body_entered()` uses the documented center-direction / prop-position fallback when the row is absent, so the signal remains safe but the normal and position are less precise on that edge. (2026-09-12) |
| 26 | **Impact speed is a coarse magnitude** — collision reactions currently scale from `prop.linear_velocity.length()`, not normal-relative velocity or an impulse computed from contact mass. | Low | Open | This keeps the signal contract simple and deterministic. Use the normal component and/or contact impulse if reaction strength needs to reflect glancing impacts accurately. (2026-09-12) |
| 27 | **Post-IK reaction timing is engine-order-sensitive** — bounded flinch offsets are applied at the end of `StickmanRig._physics_process()`, after the IK/head-pin work. | Low | Open | The current slot is verified and prevents cumulative rotation, but future changes to Skeleton2D modification ordering could overwrite or delay the visible reaction. Revisit only with a targeted engine-order regression. (2026-09-12) |
| 28 | **Airborne collision sweep cost scales per proxy** — Phase 6a tests the horizontal movement against each of the rig's ten synchronized collision proxies every airborne physics frame. | Low | Open | The per-proxy `PhysicsShapeQueryParameters2D.cast_motion()` checks reuse the existing proxy shapes and avoid a second body-size approximation; this is acceptable for the current sandbox scale. With many simultaneously falling rigs, the cost grows with `rigs × 10` casts per physics frame. Consider a combined query shape or broadphase candidate filter if airborne crowds become a target. (2026-09-17) |
| 29 | **Prop Editor multi-shape → single-shape `PropBlock` flattening** — Phase 9 (`.prop` v1.0) stores a full multi-shape `shapes[]` stack, but `PropFile.flatten_to_payload()` collapses it into the one payload `PropUtils.spawn_prop()` consumes (frontmost closed polygon → circle → first shape). Also: a circle whose vertices were hand-edited still flattens by an average-distance radius, and a non-NONE preset thumbnail ignores payload colors because `PropThumbnail` re-tints from the preset. | Low | Open | Deliberate contract until a multi-shape `PropBlock` composition (multiple `Polygon2D`/`Line2D` children + merged collision) exists; recorded per `docs/specs/phase_9_prop_editor.md` §6. Custom `.prop` thumbnails are mtime-keyed (`ThumbnailCache.prop_file_key`/`prop_file_png`, `<basename>_<mtime>.png`), so re-edits invalidate the cached PNG; built-in template PNGs stay `id_v<PROP_VERSION>`-keyed. (2026-09-18) |
| 30 | **Prop Editor Round 2 interaction model** — vertex editing and shape transform are separated into two explicit canvas modes (`EditMode { TRANSFORM, POINTS }`, kid-facing "Move & Resize" / "Edit Points") driven by an always-visible toolbar toggle; "Create Point" inserts at the cursor's segment projection and flashes the new vertex (0.6 s) instead of inserting an invisible collinear midpoint. | Low | Open | Deliberate, user-approved design (`docs/specs/phase_9_prop_editor_round2.md` §5): modes gate *input* only — all public math APIs stay mode-agnostic. "Create Point" stays outline-only per user decision; auto-begin-drag was rejected as modal/surprising. Revisit only if kids find the two modes confusing in practice. (2026-09-18) |
| 31 | **Prop Editor Round 3 chrome/autosave decisions** — exit-save skips the thumbnail render (fast, deterministic quit; the PNG is a cache nicety), autosave is debounced 1.0 s to `user://prop_editor_autosave.prop` via `PropFile.save_safe` (atomic tmp/bak swap), and all human-facing strings (mode labels/emoji, menu copy) live in the `prop_editor.gd` controller while the canvas stays string-free. | Low | Open | User-approved (`docs/specs/phase_9_prop_editor_round3.md` §6). `save_safe` leaves a `.bak` window during the rename — acceptable on desktop, revisit if autosaves ever run on a filesystem without atomic rename. (2026-09-18) |
| 32 | **Prop Editor mode buttons drop keyboard focus** — `_make_mode_button` sets `focus_mode = FOCUS_NONE` because the Button `focus` stylebox draws after `pressed` and (with the themed `focus` = normal panel) masked the selected/accent state until the cursor moved away. | Low | Open | Round 4 fix (`docs/specs/phase_9_prop_editor_round4.md`). Trade-off: the Arrange/Reshape toggle is mouse-only. Revisit if keyboard accessibility for the mode toggle becomes a requirement — a focus stylebox matching the pressed state (or a focus-visible-only border) would restore it. (2026-09-19) |
| 33 | **Prop File save-serialization zeroed every point (fixed, Round 5)** — `PropFile._serialize_shape` assigned an export-format `Array` of `{x,y}` dicts directly to a typed `PackedVector2Array`; Godot's implicit conversion cannot build `Vector2` from a dictionary and silently yielded `Vector2.ZERO` per element, so every `.prop` saved by Round 1-4 wrote all points as `(0,0)` (loading as invisible/degenerate geometry). | High | ✅ Resolved | Round 5: `_serialize_shape` now routes points through `_coerce_points`; regression assertions check point *values* through `to_file_dict` and the full round-trip, plus an end-to-end canvas→save→load test. The previously saved `content/props/eye.prop` and `content/props/box.prop` are unrecoverable (coordinates were zeroed at save time) and were re-authored. Lesson: any typed `Packed*Array` assignment from dictionary/JSON data needs a Variant-coercion helper. (2026-09-19) |
| 34 | **Prop Editor per-shape `reshaped` flag** — every shape carries `reshaped` (false by default; set by vertex insert/remove/drag and non-uniform circle scaling, cleared by `set_shape_radius`), which gates primitive-specific inspector fields (today: the circle Radius row, replaced by a muted "reshaped" note). | Low | Open | Round 6 (`docs/specs/phase_9_prop_editor_round6.md`). Only the circle consumes the flag today; future primitive params (box W/H, plank length/thickness) should reuse it instead of adding per-type detection. An "un-reshape" affordance (restoring a clean primitive from its params) is a possible future feature. (2026-09-19) |
| 35 | **Prop Editor selection uses a two-tone halo** — after Round 6's fill-based contrast failed for the reported "shape in front of a white shape" case (the selection extends beyond the shape onto the background), every selection element (outline polyline, bbox, rotation dot, scale crosses, vertex handles) is drawn as a dark backing pass + bright core pass. | Low | Open | Round 7 (`docs/specs/phase_9_prop_editor_round7.md`). The pair is visible on any background but doubles the selection draw calls; negligible at sandbox scale. If a custom selection color is ever wanted (e.g. per-user theme), it should replace the core pass while keeping the halo. (2026-09-19) |
| 36 | **Phase 9a anchor roles have no gameplay consumers yet** — `.prop` assets can author/query `grip`, `emitter`, `impact`, `mount`, and `custom`, but Phase 9a is Prop Editor-only. Secondary grips do not drive two-hand IK, and no role dispatches runtime effects, damage, mounting, or equip behavior. | Medium | Open | User-approved staging in `docs/specs/phase_9a_prop_attach.md`: land a stable authoring/schema seam plus an isolated one-hand IK preview first. A separately approved future runtime phase must design custom-prop loading, equip/release ownership, two-hand IK, emitters, melee impacts, accessory mounting, throwing, and selector/stage persistence without assuming the editor preview is gameplay-authoritative. (2026-09-20) |
| 37 | **Prop Editor still has no document-wide undo/redo** — Phase 9a anchor edits use the existing dirty/autosave/recovery pipeline, but there is no `UndoRedo` history for either shapes or anchors. | Medium | Open | An anchor-only stack was explicitly rejected because Arrange/Reshape edits would remain non-undoable and the UI would be inconsistent. Future work should introduce one document-level command/history boundary covering shapes, anchors, material/name changes, and preset replacement together. (2026-09-20) |
| 38 | **Phase 9a preview does not establish runtime attachment parity** — the Prop Editor uses a private, non-physics, full-multi-shape rig preview with editor-only facing and z-order conventions. It intentionally proves anchor authoring and arm-IK evaluation, not Sandbox loading, collision, physics, mirroring, per-limb interleaving, or attachment lifecycle. | Medium | Open | A future runtime spec must independently validate the anchor transform against gameplay animation/physics and decide its visual representation, sockets or solved-wrist API, mirror strategy, collision/freeze behavior, per-limb draw order, release/ragdoll handling, and multi-shape fidelity. Do not treat preview acceptance tests as runtime tests. (2026-09-20) |
| 39 | **Phase 9b prop centering is geometric AABB centering, not physical center of mass** — `Center Prop` uses the union AABB of internal polygon/line/circle points and deliberately excludes outline expansion, anchors, material mass, and collision geometry. | Low | Open | This is deterministic and appropriate for the fixed authoring origin, but a future physics-pivot workflow may need a separately designed center-of-mass or collision-bounds command. Phase 9b keeps canonical `origin` fixed/serialized, hides it from editing, never auto-centers after edits, and uses an explicit-save warning heuristic of `max(1024 px, 2 × bounds diagonal)`. Document-wide undo remains tracked separately by #37. (2026-09-20) |
| 40 | **Phase 9c walking preview uses exclusive gait-or-manual ownership of the selected hand target** — the private AnimationPlayer owns the target while gait plays; beginning a cyan-target drag pauses the gait before pointer input takes ownership. | Low | Open | This deterministic policy prevents AnimationPlayer and manual IK from overwriting the same Marker2D in one frame. It means an author cannot add a live hand offset while the walk continues. If that becomes valuable, design a preview-only additive offset/blend layer rather than relying on process order. The gait remains an in-place, non-physics authoring preview and is not navigation/runtime parity (#38). (2026-09-21) |
| 41 | **Phase 10 custom prop collision uses one convex hull** — the planned Sandbox prop renders every authored layer, but collision encloses all valid closed-shape points in one solid hull. | Low | Planned | User-approved in `docs/specs/phase_10_prop_integration.md`: one fixture keeps dynamic rigid bodies stable and avoids overlapping per-shape contacts. Hollows and concavities collide as filled space; revisit compound collision only if playtesting shows material misses. `PropFile.flatten_to_payload()` remains for legacy single-shape consumers; #29 stays open until implementation lands. (2026-09-21) |
| 42 | **Phase 10 Browse keeps external `.prop` paths as scene asset IDs** — arbitrary filesystem files are used in place rather than copied into `res://content/props/`. | Medium | Planned | This matches stickman Browse and avoids filename/import conflicts, but scenes depend on the external path remaining valid. A missing custom prop becomes an inert placeholder that preserves its original scene entry and rule references through later saves; restoring the file and reloading resolves it. Existing missing-stickman rule skipping is unchanged. See `docs/specs/phase_10_prop_integration.md`. (2026-09-21) |
| 43 | **Phase 10 thumbnail invalidation relies on path plus mtime** — custom cache keys will hash the full canonical path and include file modification time. | Low | Planned | This prevents equal basenames in different folders from sharing a PNG, while retaining cheap refresh checks. A filesystem with coarse timestamp resolution can miss two same-tick edits; content hashing is deferred unless observed. Cleanup must not remove built-in or unrelated external thumbnails. See `docs/specs/phase_10_prop_integration.md`. (2026-09-21) |
| 44 | **Phase 10a Sandbox prop scaling depends on rigid-body collision scale behavior** — the approved corner gesture changes `PropBlock.scale`, so visual and physics bounds must agree for built-in circles/polygons and custom one-hull props. | Medium | Resolved | `PropBlock.begin_play_instance_scale()` transfers authored scale into child visuals and collision geometry during PLAY, including custom one-hull props and the nonuniform circle fallback, then `end_play_instance_scale()` restores the authored root scale in EDIT. `tests/test_phase10a_prop_scaling.gd` verifies scaled polygon and circle extents, rotated polygon contacts, dynamic PLAY contacts, and legacy nonuniform circle ellipse contacts. (2026-09-22) |
| 45 | **Phase 10a geometric normalization excludes stroke and physical mass distribution** — Normalize Size fits the union of shape points to 96 px on its longest axis, scaling about the fixed prop origin while leaving outline width unchanged. | Low | Planned | This preserves anchor offsets and current Center Prop AABB semantics; an off-origin prop remains proportionally off-origin. A future visual-stroke fit or center-of-mass sizing tool would require a separate contract. Existing #39 tracks AABB versus physical center; #37 tracks missing document-wide undo. See `docs/specs/phase_10a_prop_scaling.md`. (2026-09-21) |
| 46 | **Phase 12 live queue editing needs session-only action identity and history** — authored indices shift during edits, and a reactive takeover replaces the runner's unconsumed runtime slice. | Medium | ✅ Resolved | Implemented stable session action IDs and a separate COMPLETED/INTERRUPTED ledger. Reactive consequences remain transient; a canceled primary action stays INTERRUPTED and runs only on the next `t = 0` run. Pending actions alone can reorder, active remains fixed, and consumed entries are delete-only. IDs are not serialized. Verified during Phase 12 editor verification. (2026-09-24) |
| 47 | **Phase 12 pause requires explicit ownership of physics and procedural clocks** — ragdoll bodies, the runner, locomotion AnimationPlayer, and recovery tween have independent clocks. | Medium | ✅ Resolved | Pause/resume now snapshots and restores dynamic prop/ragdoll state, pauses runner and recovery-tween clocks, and resumes only locomotion animation that Phase 12 paused. A recovery already in progress stays in `RECOVERING` and completes after Resume; pausing does not rebuild a ragdoll. Stable ragdoll ownership supports multiple rigs. User verified the recovery-pause path and visible recovery result. (2026-09-24) |
| 48 | **Phase 12 paused paths use one representative ground anchor** — the nearest terrain ray below live feet or ragdoll bounds supplies the pending path origin. | Low | ✅ Resolved | Implemented the approved representative ground anchor, with a live-feet fallback when the probe finds no surface. Multi-contact path planning across a sprawled ragdoll and uneven terrain remains outside Phase 12, but the current anchor is stable and inspectable for paused queue authoring. (2026-09-24) |
| 49 | **Phase 12 event edges must be rebaselined without replay** — freezing bodies may emit contact exits/entries, and the stage polls area overlap before child rig movement. | Medium | ✅ Resolved | Event dispatch remains gated until a silent contact/overlap baseline is established on Resume. Existing overlaps do not refire because of the pause transition, and rules authored while paused apply to future events only. Reset clears runtime event edges. (2026-09-24) |
| 50 | **Phase 12 paused-edit autosave stores authored state, not the frozen snapshot** — queue/rule edits occur at `t > 0` while stage bodies are at temporary physical transforms. | Medium | ✅ Resolved | A 1.0 s debounce writes the existing `.scene` schema using authored object transforms plus current queues/rules. The timer can finish after Resume; runtime pose, velocities, reactive consequences, and action history are session-only. Paused edits therefore autosave without turning the live pose into the authored starting layout. (2026-09-24) |
| 51 | **Phase 11 throw distance and speed are shared across prop types** — the landing-target solver does not yet tune throwability by material or weight. | Low | Open | Deliberately deferred from the approved landing-target amendment. If playtesting needs it, add an explicit gameplay throwability factor based on prop weight/material; do not rely on rigid-body mass to change gravity or mutate physical mass for throw balance. Current shared limits are a 3,600 px target radius and 2,100 px/s speed cap. (2026-09-25) |
| 52 | **Throw target preview ignores earlier queued pickups** — the live preview requires `_held_prop`, so editing a pending throw after a pending pickup at `t = 0` cannot show the actual planned throw arc. | Medium | ✅ Resolved | The preview now infers a virtual held-prop state from the authored queue prefix and provides a pure, non-mutating planning trajectory using the prop/grip and predicted pickup stand-off. When a prop cannot be resolved, it shows a neutral explanation; runtime still recomputes from actual state. User verified the result. (2026-09-25) |
| 53 | **Phase 13 speech stacks may exceed very small stage viewports** — long simultaneous stacks compete with the side composer and stage chrome. | Low | Planned | The spec sets defensive eight-card and per-card text bounds, keeps frames near a stable screen size, fits the displayed group within the visible stage area without changing saved character-relative placement, and requires a visible overflow cue when a stack cannot fit legibly. Responsive paging for unusually small windows is deferred until observed in editor playtesting. See `docs/specs/phase_13_speech_bubbles.md`. (2026-09-26) |
| 54 | **Phase 13 font samples vary by platform until fonts are bundled** — OS `SystemFont` aliases can resolve Comic/Pixel/Scary choices to different families or generic fallbacks. | Low | Deferred | User explicitly deferred font bundling. Keep system-font previews now; a later task can add visually distinct, redistributable fonts with license/attribution files and point the speech presets to them. See `docs/specs/phase_13_speech_bubbles.md`. (2026-09-27) |
| 55 | **Speech styling has no general Undo history** — style choices update the recoverable draft immediately, but committed text, bubble order/placement, and styling share no undo stack. | Medium | Deferred | User chose immediate style application and deferred Undo for this release. Save speech commits the complete draft to the scene; composer Cancel discards the whole edit session. A future undo command boundary should cover the whole speech draft; token offsets must remain coherent after text edits. See `docs/specs/speech_style_toolbar_live_edit.md`. (2026-09-29) |
| 56 | **RichTextLabel has no dependable per-token 44 px hit rectangles for mixed-font speech** — exact metadata clicks can target narrow punctuation only at its rendered glyph. | Low | Planned | Use a temporary picker of tokens on the tapped rendered line, each with a 44 × 44 px hit area, while retaining exact metadata taps for comfortable words. Avoid estimating glyph boxes from raw string widths because shaping and wrapping can disagree. Revisit a dedicated token renderer only if editor playtesting shows the picker is insufficient. See `docs/specs/speech_style_toolbar.md`. (2026-09-28) |
| 57 | **Speech composition must temporarily own stage input** — normal scene controls can otherwise select or alter characters and props while the user is editing speech. | Medium | Planned | Gate stage object interactions while the composer is open; keep speech bubble selection/drag, explicit speech placement, and camera-only navigation available. A non-bubble stage click clears the token target but must not activate the object underneath. Verify deferred token clicks and drag thresholds. See `docs/specs/speech_style_toolbar_refinements.md`. (2026-09-28) |
| 58 | **Speech-wide Show settings retain per-bubble render copies** — action-level reveal and motion settings are mirrored into every bubble visual for the current renderer and duration calculations. | Low | Planned | `SpeechPayload.normalize()` is the synchronization boundary; scene and rule encoders must retain the new action-level fields. Older mixed bubble settings intentionally migrate from the first bubble when edited, so later distinct motion/reveal choices are no longer expressible in the group model. If a future renderer reads action-level settings directly, the duplicate visual keys can be removed in a versioned payload migration. See `docs/specs/speech_whole_sequence_show.md`. (2026-09-30) |
| 59 | **Stage-only speech edits remain drafts until session completion** — a recoverable draft can differ from its queue/rule target while the child is styling. | Medium | Planned | Done speech is the single validated in-memory commit boundary. A Scene Save request during composition is deferred until the user explicitly completes speech; it never commits implicitly or writes the stale queue/rule target. Cancel speech confirms before discarding an edited draft. This avoids premature persistence of blank bubbles and stale scene data. Recovery remains draft-only. See `docs/specs/speech_stage_only_editor.md`. (2026-09-30) |
| 60 | **Run contact timing and baked IK poses share authored gait facts** — clip duration/contact phases must agree with locomotion's stance weights and fixed travel pace. | Medium | Planned | Phase 16 keeps one locomotion controller and an authored Run clip. Tune the speed, cycle duration, foot travel, and contact windows together; flight windows must release both foot locks. The generator and runtime each need phase data, so a future data-driven gait asset may be useful if more styles are added. See `docs/specs/phase_16_running_stickman.md`. (2026-10-03) |
| 61 | **Move To migration crosses action and trigger identities** — an old `walk_to` completion filter can silently stop matching after an action becomes `move_to`. | High | Planned | Phase 16 canonicalizes old queue/rule movement actions and `action_finished.params.action_type` filters before validation, while preserving actor IDs, waypoint positions, action order, and rule IDs. Verify old scenes at the boundary and retain the `StickmanRig.walk_to` scripting facade. See `docs/specs/phase_16_running_stickman.md`. (2026-10-03) |
| 62 | **Grounded movement currently writes the stickman root transform directly** — limb proxy contacts alone cannot prevent walking through a prop marked Blocks stickmen. | High | Planned | Phase 17a adds a swept body check to grounded movement and arrival snaps, and reports a blocked approach without false arrival. Keep ordinary ground and navigation tied to terrain; use the same actor-blocking profile for walking, airborne sweeps, ragdoll bodies, and contact events. See `docs/specs/phase_17a_prop_roles.md`. (2026-10-05) |
| 63 | **Per-placement prop contact permissions need pair reconciliation** — layer/mask changes on one body alone can leave contacts from the other body's scan. | Medium | Planned | Phase 17a centralizes authored-start/held/released profiles on `PropBlock`, uses named physics bits, and adds symmetric pair exceptions on discrete role/scene transitions. Reconcile affected pairs on role edits, pickup/release, load, and reset; avoid per-frame pair work. See `docs/specs/phase_17a_prop_roles.md`. (2026-10-05) |
| 64 | **Legacy scene draw order can interleave props and characters** — the new two-choice depth control cannot exactly encode an older prop between two characters. | Low | Planned | Preserve `World` order with a persisted `legacy_draw_order` marker and z-index zero until the child explicitly picks Behind or In front. Show an Old scene order cue in the card; selecting a depth applies that choice relative to every character. See `docs/specs/phase_17a_prop_roles.md`. (2026-10-05) |
| 65 | **Large custom prop switches need state parity with native buttons** — a drawn 64 × 34 px switch inside a full-row `Button` replaces the stock small `CheckButton` icon. | Low | Planned | The proposed `PropSettingsToggleRow` should keep native focus, keyboard activation, disabled state, and one `toggled` emission per press; its child labels must ignore pointer input. A compact depth cue lives inside each choice button, while the stage remains the live preview. See `docs/specs/prop_settings_card_refinement.md`. (2026-10-09) |
| 66 | **Stage selection panels duplicate layout and visual rules** — the prop card owns its own shell sizing, choice styles, and `stage_prop_settings` tokens, making later inspector cards prone to drift. | Low | Planned | Promote the current prop card to a small shared Stage inspector shell, choice group, and toggle row with `stage_panels` theme tokens. Keep domain handlers in `SandboxStage`; preserve the existing prop role schema. During migration, resolve old prop tokens as fallback only temporarily and remove duplicate reads once the prop card uses the shared controls. See `docs/specs/stage_panel_ui_system.md`. (2026-10-09) |
| 67 | **SandboxStage controller is too large for comfortable Godot IDE editing** — stage UI, terrain painting, document workflow, runtime rules, and speech coordination shared one roughly 7,600-line script. | High | In Progress | Approved slices 1–16 extracted reusable stage components and reduced `sandbox_stage.gd` to 5,009 lines (about 34%); the below-3,800-line target remains unmet by 1,209 lines. Root input/selection, authored queue/rule writes, recovery application, and save/commit ordering remain coordinated there. Root `--check-only` parse passes; behavior and in-editor responsiveness checks are pending. See `docs/specs/sandbox_stage_composition_refactor.md`. (2026-10-10) |
| 68 | **Use preview cannot predict all prior rules or moving props** — DIRECT can see the current actor and prop plus a limited deterministic queue prefix, but reactive rules and rigid-body motion may change either before a pending Use action runs. | Low | Planned | Phase 17b displays an amber conditional preview when the future pose is uncertain and always recalculates reach during PLAY. One bounded target-movement replan handles ordinary drift without freezing physics. A future predictive timeline may improve authoring accuracy, but must not change persistent prop/spot IDs or claim certainty for simulated rule consequences. See `docs/specs/phase_17b_use_spots.md`. (2026-10-10) |
| 69 | **Contact entry alone can miss a stickman hitting a fixed prop** — grounded locomotion stops at a shape-sweep safe fraction, sometimes before `body_entered` fires; resting contacts can also chatter after pause or quick separation. | Medium | Implemented; user-verified; automated result undocumented | Phase 17c routes identified blocking-prop sweep hits and actual body contacts through one relative approach-speed scorer and pair gate. Multiple stickman limbs collapse into one rig/prop pair; separation/rearm and cooldown gates suppress repeat hits. The user has verified Phase 17c; no automated behavior verification result is documented. Keep existing collision-rule dispatch independent. See `docs/specs/phase_17c_impact_wobble.md`. (2026-10-14) |
| 70 | **Impact strength is initially speed based while prop mass is preset based** — material presets set `RigidBody2D.mass`, but fixed props are frozen and mass is not a child-facing setting. | Low | Implemented; future design | Phase 17c keeps the Wobble boolean and visual-only response independent of mass. Impact scoring is centralized in `StageImpactResolver` so a future editable-mass or impulse design can be evaluated without altering the Stage switch or migrating old scene choices. Do not infer a physical impulse from a frozen prop's preset mass; verify any future mass effect separately. See `docs/specs/phase_17c_impact_wobble.md`. (2026-10-14) |
| 71 | **Palette art is supplied at much higher resolution than its display size** — six 1254 × 1254 PNGs appear in 64 × 64 card image areas. | Low | ✅ Resolved | Phase 18 is implemented and user-verified. The original source PNGs remain 1254 × 1254, Godot imports are capped at 256 px, and `StageChrome` maps the six images by placement ID. The focused palette suite passed; the headless Sandbox Stage loaded, and `git diff --check` passed. Keep the map local to the fixed Stage palette; revisit a data-driven icon descriptor only if tools or themed art become extensible. See `docs/specs/phase_18_palette_icons.md`. (2026-10-14) |
---
## Suggested Future Improvements (Beyond Current Scope)
| Improvement | Description | Status / Priority |
| --------------------------------------- | ----------------------------------------------------------------------------------------------------------------------- | ----------------- |
| **Soft Transition Blending** | Add physical blending (joint stiffness ramp) to complement visual crossfade. | ✅ Done, then removed — implemented with Phase 11, but the whole crossfade was removed on director feedback (ghosting); entry is now an instant handoff. |
| **Ragdoll Recovery Interruptibility** | Allow the director to force a mode switch mid‑recovery (e.g., if they want the character to ragdoll again immediately). | ✅ Done |
| **Multiple Rig Support** | Ensure all state variables are instance‑specific (already true) and that the harness can manage multiple rigs. | Future |
| **Animation Blending (IK vs. Physics)** | Blend between the animation‑driven pose and the ragdoll pose during transition to prevent snapping. | Future |
| **Save/Load for Ragdoll State** | Save the current ragdoll pose to `.stk` (e.g., for storyboarding a fall). | Future |
---
## How to Use This Log
1. **New Issues:** Add a row with `#`, description, `Priority` (Low/Medium/High), `Status` (Open/In Progress/Resolved/Deprecated), and `Notes`.
2. **Resolving Issues:** Change `Status` to `Resolved` and add a note with the date and fix details.
3. **Deprioritizing:** If an issue is no longer relevant, mark it `Deprecated` with a brief reason.
4. **Quarterly Review:** Revisit this log every major milestone to reassess priorities.
---
## Change Log
| Date | Change |
| ---------- | -------------------------------------------------------------- |
| 2026-08-26 | Initial creation — migrated observations from Phase 10 review. |
| 2026-08-27 | Phase 11 resolved #6 (recovery starting pose), #7 (transition visual pop), #8 (rest timeout UI), #9 (animation generation DRY); #10 (proxy re‑addition) remains open with updated scope. Later revision: stand‑up recovery switched from bridge‑into‑baked‑animation to a direct marker tween (captured pose → `STAND_POSE`), fixing a visible jump; baked `stand_up` kept as authored reference only. Second revision: ragdoll entry builds from the current solved bone positions (IK disabled only after the blend completes) and the recovery snap derives joint ends from capsule half‑heights with Torso `bone_angle` compensation, fixing the entry pose‑pop and the "recovery starts lying" bugs. Third revision: the entire crossfade/blend (`transition_duration`, `BlendDirection`, opacity fade, softness ramp) was **removed on director feedback** — entry is now an instant handoff (build at current pose → hide `Body/*` → disable IK in one call), since the ragdoll spawns at the identical pose and a fade only read as ghosting. |
| 2026-08-27 | Sandbox Stage Builder (Phase 2) added `scripts/editors/stage/sandbox_stage.gd` + `stage_spawner.gd` / `stage_selection.gd` / `stage_gizmos.gd` + `scenes/stage/sandbox_stage.tscn`. Logged #11 (no unified freeze abstraction over the mixed `StaticBody2D` / `RigidBody2D` / `Node2D` population) and #12 (selection hit‑testing uses world‑space AABBs rather than point‑in‑polygon). |
| 2026-08-29 | Phase 3a (Core Director Functionality) spec written (`docs/specs/phase_3a_spec.md`). Logged #13 (slope-aware walking physics + dynamic obstacle avoidance deferred — the nav mesh itself is built in 3a). Notable non-debt decisions recorded in the spec: Play mode now runs the director script instead of auto-ragdolling stickmen; `walk_to(target)` treats `target` as a feet/ground destination via `FOOT_OFFSET`, with the `NavigationAgent2D` child placed at the feet `(0,+385)` so it paths on the ground-level nav mesh. |
| 2026-08-29 | **`walk_to` stops-after-a-few-px bug fixed.** `_update_walking` (Phase 3a) now defers nav reads until `NavigationServer2D.map_get_iteration_id(...) != 0` (map-sync guard) and forces the path query via `get_next_path_position()` before any empty-path/finished check. **Follow-up (same day):** the initial "warn + finish in place" unreachable-target policy was itself reported as "stickman stands still with a waypoint" and was replaced by **hybrid nav/direct steering** — an on-mesh target follows the nav path (`_walk_mode = "nav"`), an off-mesh/unreachable target walks **straight to the clicked waypoint** (`_walk_mode = "direct"`, root target = waypoint + `FOOT_OFFSET`), with **no** `push_warning`; `_walk_path_grace` removed (map-sync guard + forced path query replace it); the debug trace now carries `mode=nav|direct`. Off-by-default `DEBUG_WALK` (`stickman_rig.gd`) / `DEBUG_STAGE` (`sandbox_stage.gd`) traces added. #13 remains **Open** (slope physics + RVO avoidance are still deferred; the fix only changes unreachable-target handling). Logged in `docs/known_issues.md`; verified with a 44-assertion headless regression suite. |
| 2026-08-30 | Phase 4 (Triggers & Event System) implemented: `scripts/stage/trigger_area.gd` (NEW placeable sensor), `sandbox_stage.gd` rule system (`_event_rules`, geometric event engine `_update_area_entry` / `_update_stickman_prop_collision`, rule-builder UI state machine, `_cleanup_rules_for_nodes`), `stickman_rig.gd` (`arrived` gains a `target` payload; new `enqueue_reactive`), `prop_block.gd` (`collided` physics signal), `stage_director_visuals.gd` rule visualization, `stage_spawner.gd` / `stage_selection.gd` `"area"` palette + duck-typed `get_area_rect`. Logged #14 (all-pairs event engine scales O(rigs×props + areas×movables); spatial hash suggested). |
| 2026-09-02 | Phase 4b (Polish) spec written (`docs/archive/plans/PHASE_4b_SPEC.md`). Logged #15 (walk-arrival jitter: `_walk_mode` re-evaluated per frame + arrival-radius mismatch) and #16 (no grid spatial dictionary; Phase 4b adds one for terrain drag-painting + event-engine broadphase). |
| 2026-09-02 | Phase 4b (Polish) implemented. Resolved **#15** (walk-mode latch once per walk, unified `ARRIVE_DISTANCE` arrival with snap-on-arrive, steer-to-final-when-close, one-frame marker re-assert) and **#16** (grid spatial dictionary `_grid_cells` @ `TERRAIN_GRID_SIZE` for the drag-painting 3-state query + director target-validity). #14 remains **Open** — the dictionary is not yet wired into the Phase 4 event engine (note updated). Added `res://sandbox_theme.json`, `scripts/stage/stage_placement_overlay.gd`, the 3-segment mode switcher / status bar / badge / frame / cursors, and director tooltip/trajectory/reticle UX. |
| 2026-09-03 | Phase 3b (Asset Library) implemented (`docs/specs/phase_3b_asset_grid_spec.md`): `scripts/characters/stickman/stickman_library.gd` / `prop_library.gd`, `scripts/shared/thumbnails/thumbnail_cache.gd` / `stickman_thumbnail.gd` / `prop_thumbnail.gd`, `scripts/shared/ui/asset_selector.gd` + `scenes/stage/asset_selector.tscn`. The Stickman/Prop palette buttons now open visual selector grids (pagination 12/page, session-only selection, Browse/Refresh); `StageSpawner` registry becomes `ground/ramp/step/prop/stickman/area` (`crate`/`ball` removed) with `selected_*` session state + per-path stickman cache; `SandboxStage` owns the selector open/close flow, Esc priority, and the one-per-frame lazy thumbnail drain. Logged #17 (thumbnail cache growth / headless placeholder) and #18 (session-only selection). Verified with the new headless suite `tests/test_phase3b_library.gd` plus the updated `tests/test_phase4b1_fixes.gd`. |
| 2026-09-03 | **Selector UI bugfix round** (5 bugs, documented via `tests/test_phase3b_ui_fixes.gd` + docs in `docs/specs/phase_3b_asset_grid_spec.md` / `README.md` / `AGENTS.md`): `AssetSelector.open()` drops its selection params (`selected_path`/`selected_id`) — no cell is pre-highlighted on open (selected stylebox removed); the selector re-centers on window resize (`size_changed` → `popup_centered()`); the stage adds a dim backdrop `_selector_dim` (`SELECTOR_DIM_ALPHA` 0.5) behind the grid; the selector's `popup_hide` routes to `_on_selector_cancelled()` (idempotency-guarded) so an outside-click un-presses the palette button; and the Direct-mode action popup opens **right of the clicked stickman** (`_world_to_screen` + 24 px) instead of at the cursor. Reviewed #17 and #18 — neither is obsolete (both concern thumbnail-cache growth/headless placeholders and session-only *persistence*, orthogonal to these UI fixes), so both remain **Open** unchanged; no duplicate rows introduced. |
| 2026-09-03 | **Rule-builder popup-anchor bugfix round** (`sandbox_stage.gd`, documented in `README.md` / `AGENTS.md`): the Phase 4 rule-builder context menus used to re-pop at the live mouse position on every re-open, so cycling "⚡ When…" → "⬅ Back to actions" → "When…" walked the menu down the screen. Now a **session anchor** records the first context menu's screen position (`_popup_anchor: Rect2i` / `_popup_anchor_set`; Direct first menu = right of the clicked stickman; rule-label edit entry = the click position) and all child popups reuse it via `_set_popup_anchor(rect)` / `_clear_popup_anchor()` / `_popup_anchor_rect()`, until cleared on confirm (`_finalize_rule`), cancel (`_cancel_rule_build`), or Direct-mode/flow exit (`_clear_director_pending`) — but **not** on `_reset_rule_builder()` (Back-to-actions reuses it). No numbered debt row described the old cursor-following behavior, so no row was flipped to Resolved and no duplicates introduced; recorded here in the Change Log only. |
| 2026-09-04 | Phase 3c (Editor Tools — Action & Rule Editing) implemented: `scripts/stage/action_registry.gd` / `trigger_registry.gd` (extensible action/trigger templates), `scripts/editors/stage/action_editor.gd` / `rule_editor.gd` / `queue_panel.gd` / `rule_panel.gd` + `scenes/*.tscn` shells, `scripts/stage/waypoint_context.gd`, plus integration in `sandbox_stage.gd` (unified `CaptureKind` target-capture, "Edit Queue…/Edit Rules…" + right-click stickman/waypoint context menus, confirmation dialog, consequence-only rule editor) and `stage_director_visuals.gd` (`hit_test_waypoint_action`, waypoint edit highlight). Logged #19 (ad-hoc popup host-visibility discipline), #20 ("Add Rule" has no source in waypoint-filtered panels), #21 (drag-reorder nearest-row-center), #22 (`--check-only --script` needs a prior `--editor` rescan), and later the same day #23 (Phase 3c confirmation dialog popped over an exclusive panel — the delete/clear-all confirm paths do not hide the `QueuePanel`/`RulePanel` first). Documented the plan-§11.2 deviation (paramless `ActionEditor` pre-fill instead of a separate ragdoll/recover confirmation dialog) in `docs/specs/phase_3c_editor_spec.md` §2 decision 5 — a design decision, so no debt row. Static-verified via headless `--editor --quit` + per-script `--check-only`; runtime F6 flows (queue/rule panel edit-delete-reorder, waypoint context menu, visual walk edit, full vs consequence-only rule editor) still need manual verification. |
| 2026-09-11 | Phase 4c (Trigger Area Reliability) verified: `sandbox_stage.gd` `entered_area` now requires source + area instance IDs and detects visible world-AABB overlap for ANIMATED content/stickmen/unfrozen props, edge-triggered per area/movable pair. The prop enumeration scope bug was corrected. `tests/test_phase4c_area.gd` passed all 19 assertions, including transformed-area, leg-level overlap, wildcard/re-entry, speech consequence, stale-target, and deletion-cleanup coverage; editor verification confirmed the speech bubble fires for the configured source stickman. Logged #24 (session-only instance-ID rule references). |
| 2026-09-12 | Phase 4.5 collision bodies verified: ten explicitly synchronized `AnimatableBody2D` proxies replace the old feet-point stickman collision poll; `PropBlock` supplies world-space contact data with shape-cast CCD; stage routing preserves rig→prop rules with per-part pair debouncing; bounded quadrant-aware flinches and PLAY→DIRECT authored-pose restoration were verified. `tests/test_phase45_collision_bodies.gd` passed 223 assertions; Phase 4b (53), Phase 4c (19), and Phase 3c (96) remained green. Reconciled #1/#5 and retained #25–#27 as intentional implementation limitations. |
| 2026-09-17 | Phase 6a (Airborne Momentum, Procedural Air Pose, and Landing Absorption) implemented and user-verified. `StickmanRig` now carries signed ledge momentum with tuned exponential drag (`AIR_DRAG_X = 0.75`), swept horizontal proxy collision, additive free-fall pose with forward-before-lift arms, and height-driven landing absorption: falls below 80 px are soft; 80..900 px maps to 32..160 px torso compression and 270..520 px hand drop over 0.30 s. Independent foot contacts preserve ramps/stairs, while `LANDING_IMPACT` blocks primary queue progression and direct ragdoll remains an immediate preemption. `tests/test_phase6a_airborne_landing.gd` passed 24 assertions; `tests/test_phase4b_terrain.gd` passed 8 assertions. Logged #28 (per-proxy airborne sweep scaling). |
| 2026-09-20 | Phase 9a Prop Attach specification written and refined (`docs/specs/phase_9a_prop_attach.md`). Approved architecture: append Attach to the existing Arrange/Reshape system; additive role-based `.prop` anchors with a fixed synthesized `origin`; and an isolated Prop Editor stickman preview that uses real arm IK while showing the full authored prop. All Sandbox/custom-prop loading, attachment, sockets, physics/collision, lifecycle, and runtime tests are explicitly deferred. Logged #36 (all gameplay consumers deferred), #37 (document-wide undo remains absent), and #38 (editor preview is not a runtime-parity guarantee). Existing #29 remains open independently because the current runtime still flattens multi-shape props. |
| 2026-09-20 | Phase 9b Prop Centering specification written (`docs/specs/phase_9b_prop_centering.md`). Approved architecture: hide canonical origin from editable anchors while retaining it in `.prop` v1.0; draw a passive constant-screen-size `(0,0)` marker in all three modes; add an always-visible Center Prop document transaction; center the authoring camera on origin for startup/New/load/reset and preserve zoom after centering; and warn explicit saves with Center & Save / Save Anyway / Cancel when displacement exceeds `max(1024 px, 2 × bounds diagonal)`. Autosave stays silent and automatic recenter-on-edit is explicitly rejected. Logged #39 (AABB authoring center is not a future physics center of mass); #37 continues to track document-wide undo. |
| 2026-09-21 | Phase 9c Prop Editor UX specification written (`docs/specs/phase_9c_prop_editor_ux.md`). Approved architecture: a single Front Pose / Walk Left / Walk Right preview-motion selector with dynamic Pause/Resume; manual arm IK pauses gait before taking target ownership; modal typed anchor rename; Show Other Markers controls origin plus unselected anchors; one global Snap source; theme-driven `#262C39` inspector; container-driven unclipped chrome; simplified full-figure preview with no blue border. The preview directly plays the proven in-place gait/mirror policy and remains isolated from navigation, physics, actions, and Sandbox state. Logged #40 (exclusive gait/manual hand-target ownership). |
| 2026-09-21 | Phase 10 custom prop integration specification written (`docs/specs/phase_10_prop_integration.md`) after user approval of full layered visuals with one convex collision hull, direct external-path Browse, and missing-prop placeholders that preserve scene entries and rule references. The design reuses Sandbox prop freeze/restore and save flows, extends PropLibrary with a merged scan while retaining its four-template API, and gives custom thumbnails path-unique mtime keys. Logged #41–#43; #29 remains open pending implementation. |
| 2026-09-21 | Phase 10a Prop Scaling specification written (`docs/specs/phase_10a_prop_scaling.md`) after approval of origin-based Normalize Size and single-`PropBlock` uniform corner scaling around its local origin; an initial numeric document-scale control was removed during final UI refinement. The editor scales points, circle radius, and non-origin anchors as one transaction; a world-space 1:1 stickman guide defaults OFF and persists only in editor settings. Sandbox reuses its existing scene scale and PLAY→EDIT authored-state lifecycle. Logged #44 for physics collision verification and #45 for geometry-only fit; #37 and #39 remain open. |
| 2026-09-22 | Phase 10a whole-prop drag amendment approved (`docs/specs/phase_10a_prop_scaling.md` §3.4). In Arrange, a visible **Resize Whole Prop** toggle displays one union-bounds outline with large handles; dragging previews all shape points, circle radii, and non-origin anchors uniformly about canonical `(0,0)` from a captured snapshot. Release calls the existing document-scale transaction once; Esc, mode switch, toggle OFF, New, Load, and Clear discard a live preview. The preview stays outside authoritative save/autosave state, and the toggle adds no `.prop` field or fourth primary mode. Existing per-shape Arrange behavior remains when the toggle is OFF. Document-wide undo stays deferred under #37; this drag should become one future undo command. |
| 2026-09-22 | Phase 10a whole-prop transform extension approved and implemented (`docs/specs/phase_10a_prop_scaling.md` §3.4). The prior Resize Whole Prop toggle becomes one **Transform Whole Prop** tool that mirrors Arrange: drag visible geometry to move the whole document, drag the union rotation dot to rotate it about canonical `(0,0)`, and drag a corner for the existing uniform origin-based resize. Move translates every point and non-origin anchor (snapping the baseline delta when Snap is ON); rotate transforms every point/anchor position and adds the angle to editable anchor orientations; circle radius changes only on resize. All gestures preview from snapshots outside authoritative save state and commit once on release; Esc cancellation, selection restoration, mode lifecycle, `.prop` v1.0, and deferred document undo remain unchanged. |
| 2026-09-22 | Phase 10a implementation and final UI were user verified. The Arrange top bar is ordered **Transform Whole Prop**, **Normalize Size**, **Center Prop**, **Show Stickman Guide**; the numeric scale textbox and Apply Scale button are removed. Transform Whole Prop and Normalize Size are hidden in Reshape and Attach, while Center Prop and Show Stickman Guide remain available. Whole-prop move, 15-degree Ctrl rotation, uniform resize, anchor following, Snap movement, and Esc cancellation are documented in `docs/guides/prop_editor_user_guide.md`; focused tests and the Prop Editor startup smoke test passed. |
| 2026-09-24 | Phase 12 Live Director implemented and editor-verified. PLAY/DIRECT pause and resume preserve simulation time and live state; the top-bar clock is visible, Space toggles on key press, and paused DIRECT supports action/event editing without object transforms. Pending-only reorder, consumed-action deletion, interrupted-action history, contact rebaseline, and authored-state autosave are documented in the README and spec. Follow-up fixes make locomotion animation resume reliably, preserve a paused recovery tween instead of re-ragdolling, and clear stale foot-contact locks so recovery does not cross the feet. Rows #46–#50 resolved; no `.scene` format change. |
| 2026-09-24 | Phase 11 Prop Interactions architecture spec written (`docs/specs/phase_11_prop_interactions.md`) after approval of fixed stage-world throw targets, one prop in one compatible hand per stickman, and persistent per-instance prop IDs. The design adds a per-rig PropInteractor, keeps pickup auto-approach inside one authored queue action, carries existing .prop anchors into runtime, uses a bounded Godot-coordinate ballistic solver, emits events only after committed prop transitions, and migrates StageSceneIO 1.0 scenes to 1.1. Row #24 remains Open pending implementation. |
| 2026-09-25 | Phase 11 throw UX amendment approved (`docs/specs/phase_11_prop_interactions.md` §1.1): treat the saved cursor point as aim plus power (distance maps smoothly to a 300–1,250 px/s capped speed), preview the collision-limited ballistic impact, and replace the short windup with a reach-clamped overhand arc that releases during its forward sweep. The existing `{type, target_position}` queue payload remains unchanged. |
| 2026-09-25 | Phase 11 throw landing-target amendment approved (`docs/specs/phase_11_prop_interactions.md` §1.2): clamp the cursor's effective target to 800 px from the stickman root, solve the fastest valid descending trajectory under a 1,250 px/s cap, distinguish the raw cursor/effective target/early collision markers, and preserve the fixed world-space queue payload. Open-air targets remain valid; prop weight/material throwability is deferred under #51. |
| 2026-09-25 | Phase 11 throw-range tuning updated after user feedback: target radius 3,600 px and speed cap 2,100 px/s. The queue-aware, non-mutating target-preview amendment (`docs/specs/phase_11_prop_interactions.md` §1.3) was approved, implemented, and user verified; row #52 is resolved. Throw limits remain shared across prop types under open row #51. |
| 2026-09-26 | Phase 13 comic speech specification drafted for user sign-off (`docs/specs/phase_13_speech_bubbles.md`). Approved choices: a side composer with a live character-attached preview; simultaneous stacks by default or one-at-a-time transitions; automatic reading time with optional longer choices; character-relative placement; and reactive speech that temporarily takes visual priority, then resumes queued speech at its saved time. One tap targets one whole word; effect, font, and color controls apply to that word, while font/color controls edit bubble defaults with no target. The target remains visible while controls take focus; separate tapped words retain separate safe, validated style records. Drag highlighting, Shift-click, and multiselect/append are outside the interaction. The architecture uses one normalized Dictionary payload across queue/rule JSON, a shared RichTextLabel renderer, and runner-owned elapsed clocks for pause-safe replay. It preserves legacy `{text,duration}` actions. Logged #53 for tiny-viewport stack fit; arbitrary BBCode and per-bubble Resource files are deliberate scope exclusions, not open defects. |
| 2026-09-27 | Phase 13 composer refinement written for final user sign-off (`docs/specs/phase_13_speech_bubbles.md`): one active text editor with high-contrast snippet tabs and a vertical-only responsive inspector; example frame/font/size controls; 16 named text-color swatches; graphical whole-word target; separate Effects/Show/Timing groups; Show all/One at a time and Normal/Slow/Slower labels mapped to existing serialized IDs; clarified Bounce in/Simply appear; no placement arrows or auto-seconds label; live-head triangular tails with no line crossing the character; and a full replay followed by a one-second blank stage. User chose recoverable local drafts while retaining **Save speech** as the commit point. Draft recovery never mutates queues/rules/scene state; Save commits and clears, Cancel discards and clears, and stale targets recover only as an unbound draft pending an explicit destination. |
| 2026-09-27 | User approved the Phase 13 composer refinement for implementation. Font binaries are explicitly deferred; keep OS system-font aliases for the sample buttons and retain #54 as deferred. |
| 2026-09-27 | User approved the Phase 13 follow-up (`docs/specs/phase_13_speech_bubbles.md` §9): keep core inspector cards open with larger ribbon headers; clarify word targeting; fix full-word markers and selected-control states; give bubbles independent character-local placement and an above-character stagger that never fits downward across the actor; route scrolling to the composer under the pointer; and preserve valid speech through queue save/load while rejecting partial scene saves. Code is implemented, syntax checks passed, and in-editor verification is pending. |
| 2026-09-27 | Phase 13 verification follow-up: strengthened rounded word-marker coverage/clipping, added per-word size styling alongside font and color, and made speech commits confirm that the action actually entered the character queue before closing the composer. Syntax checks passed; verify save/reload and marker behavior in-editor. |
| 2026-09-28 | Fixed Phase 13 scene-load rejection for saved per-word styles: JSON parsing yields whole-number offsets as floats, while speech validation required `int`. Validation now accepts finite integral numeric offsets and normalization stores them as integers. `content/stages/talk.scene` loaded through the Sandbox Stage CLI without a skipped-speech warning; the scene file was not modified. |
| 2026-09-28 | Speech styling toolbar architecture spec written for final user sign-off (`docs/specs/speech_style_toolbar.md`). The stage-anchored Frame/Letters/Effects toolbar uses one theme-backed chooser and a provisional visual copy: Done commits a style to the composer draft; Cancel discards it without affecting recovery. A shared token scanner extends saved half-open style offsets to punctuation, and a temporary rendered-line token picker provides 44 px choices when glyphs are cramped. The inspector keeps Message and Save speech. User deferred general Undo; logged #55 and #56. No implementation code was changed in this phase. |
| 2026-09-28 | Speech style toolbar refinements approved and implemented (`docs/specs/speech_style_toolbar_refinements.md`). The chooser now sizes to content with overflow scrolling, ribbon headings, active categories, and centered large buttons; stage clicks clear the token target while preserving bubble selection, and speech composition blocks unrelated scene edits while retaining bubble controls and camera navigation. The full-sequence control is **Preview speech** beside Save speech. Godot headless syntax checks pass; in-editor verification is pending. |
| 2026-09-29 | Speech style live-edit refinement approved and implemented (`docs/specs/speech_style_toolbar_live_edit.md`). Choices now update the recoverable draft directly; token effects demo for one second without replaying the sequence; **Done styling** hides the toolbar while keeping speech editing open; selected choice borders persist on hover; and the token target uses a translucent rounded marker. Godot headless syntax checks pass; editor verification is pending. |
| 2026-09-29 | Speech style follow-up fixes: hold bubble geometry steady during the one-second token effect demo; center speech text after font/size changes; and make whole-bubble font/size/color defaults clear matching per-word overrides. Godot headless syntax checks pass; editor verification is pending. |
---
| 2026-09-29 | Speech Message/Emoji/Timing follow-up approved for implementation (docs/specs/speech_message_emoji_timing.md): move Message plus its emoji picker and action-wide Timing to the stage toolbar; use two Speech/Style rows; make unselected Effects apply to the whole bubble via a backward-compatible bubble default with token overrides; keep text editing separate from stage token targeting and preserve unaffected style spans. |
| 2026-09-30 | Stage speech-wide Show architecture specification written for user sign-off (`docs/specs/speech_whole_sequence_show.md`). No bubble focus presents Bubbles, Words appear, and Bubble motion directly beside the group; a bubble/token click swaps to Speech Style, while Done styling or an empty stage click returns to group controls. The action owns reveal and motion values, all existing/future bubbles inherit them, and older mixed settings migrate from the first bubble. Logged #58 for synchronized per-bubble render copies. No implementation code changed. |
| 2026-09-30 | Stage-only speech editor architecture specification drafted for user sign-off (`docs/specs/speech_stage_only_editor.md`). Removes the Comic Speech Inspector, moves Show/Timing/Preview/More to Show Speech and Message/Frame/Letters/Effects plus sequence controls to Speech Style, and keeps persistent Done speech and Cancel speech controls. Draft recovery remains immediate; Done commits once to the queue/rule buffer, while Scene Save waits for the explicit Done commit. New bubble defaults, placement, one-step removal Undo, order controls, input ownership, and the existing eight-bubble cap are specified. Logged #59. No implementation code changed. |
| 2026-10-01 | Comic Chain speech behavior revised after preview feedback (`docs/specs/speech_comic_chain_layout.md`): Comic chain is the third persistent **Show bubbles** choice and builds cumulatively, while Stack/Same spot remain placement actions. Entering the mode applies measured Stack positions. Later balloon connectors use blunt joins drawn behind predecessor frames; Think uses dots. Older `bubble_layout: comic_chain` payloads migrate to `arrangement: comic_chain`. |
| 2026-10-03 | Speech bubble order badges now use selection-independent positions (`docs/specs/speech_badge_stability.md`). Badge clustering responds to actual proximity; Show Speech and Speech Style panels avoid the stable badge bounds instead of moving badges when selection changes. |
| 2026-10-03 | Speech badge clusters now also form when bubble frames intersect. For overlapping bubbles, the sorted badge row anchors above the largest frame, preventing a smaller bubble's badge from appearing through the larger bubble's text. |
| 2026-10-03 | Phase 16 Move To/Run architecture specification written for user sign-off (`docs/specs/phase_16_running_stickman.md`). Approved canonical `move_to` plus Walk/Run style, migration of old movement and completion-filter types, a short arrival settle, and visual verification across Ground, Ramp, Step, seams, edges, and existing fall/landing behavior. Logged #60 for duplicated gait phase facts and #61 for the migration boundary. No implementation code changed. |
| 2026-10-05 | Phase 17a prop roles and Set Down architecture specification written for final user sign-off (`docs/specs/phase_17a_prop_roles.md`). Retain `PropBlock` as a stable `RigidBody2D` with authored-start/held/released profiles; make Set Down a world release target with terrain-grounded reach and physics settling; use explicit contact-pair policy and a grounded walking sweep. Legacy scenes migrate with dynamic prop behavior and a saved draw-order marker. Logged #62–#64. No implementation code changed. |
| 2026-10-09 | Proposed Prop settings card refinement written for user approval (`docs/specs/prop_settings_card_refinement.md`): full-width 64 × 34 px switches, explicit selected choice styling, plain-language copy, nearby help, compact scrollable card, and small front/behind cues. Logged #65 for custom control state parity. No implementation code changed. |
| 2026-10-09 | Proposed Stage selection panel UI system drafted for user approval (`docs/specs/stage_panel_ui_system.md`). The current prop card is the reference; a shared shell, choice group, toggle row, and theme tokens support later Stage inspectors. Logged #66 for duplicated panel presentation. No implementation code changed. |
| 2026-10-10 | Sandbox Stage composition refactor specified for developer handoff (`docs/specs/sandbox_stage_composition_refactor.md`). Defines state ownership, explicit component APIs, compatibility facades, and an ordered migration beginning with spatial indexing and terrain painting. Logged #67; implementation followed in the entries below. |
| 2026-10-10 | Sandbox Stage composition refactor slices 1–16 implemented (`docs/specs/sandbox_stage_composition_refactor.md`): extracted spatial indexing, terrain painting, stage chrome and views, document/scene adapters, run/director/rule/action/speech sessions, asset browsing, theme loading, and target capture. `sandbox_stage.gd` is 5,009 lines, about 34% smaller than the approximate 7,600-line baseline; the below-3,800-line target remains open. Godot 4.7.1 root `--check-only` parse passes. No behavior tests or in-editor checks were run; #67 remains In Progress. |
| 2026-10-10 | Phase 17b Use spots implemented from the approved architecture/UI specs (`docs/specs/phase_17b_use_spots.md`, `docs/specs/phase_17b_use_spots_ui.md`). Instance-local numbered spots persist in `.scene` v1.3; Use requires empty hands, targets keep normal physics with one bounded approach replan, and reach stays grounded. `prop_used` fires only after hand contact; missing references remain repairable after save/load. Phase 17b suite passes 27/27, Phase 17a regression passes 46/46, and edited scripts parse; ready for in-editor user verification. Logged #68 for conditional preview of earlier rules/moving physics; #12, #19, #24, and #67 already cover AABB selection, popup host visibility, session rule IDs, and root controller size. |
| 2026-10-13 | Phase 17c Impact wobble architecture specification drafted for final user sign-off (`docs/specs/phase_17c_impact_wobble.md`). A placed prop's saved boolean controls a visual-only wobble in PLAY and a brief EDIT preview. Contact entry plus identified stickman blocking sweeps feed a shared approach-speed gate, including ragdolls and both members of a prop-prop hit. Mass stays independent of the switch, with impact scoring isolated for future mass or impulse work. Scene version advances to 1.4 with older scenes defaulting Off. Logged #69–#70. No implementation code changed. |
| 2026-10-14 | Phase 17c Impact wobble implementation is present against the approved UI and architecture contracts. The prop card has a per-placement switch and EDIT sample; PLAY routes contacts and identified blocking sweeps through the approach-speed resolver, including ragdoll and prop pairs, while wobbling artwork only. Scene v1.4 stores the setting and migrates older scenes to Off; STOP/reset clears transient motion. Automated behavior verification and in-editor user verification have not been completed. Updated #69–#70; mass-aware scoring remains a future design seam. |
| 2026-10-14 | User verified Phase 17c Impact wobble. Updated #69 and the Phase 17 plan; no automated behavior verification result was supplied. |
| 2026-10-14 | Phase 18 palette image icons implemented and user-verified (`docs/specs/phase_18_palette_icons.md`). `StageChrome` maps the six palette placement IDs to preloaded images; the three plural filenames are now singular, original 1254 × 1254 PNGs are preserved, and Godot imports are capped at 256 px for display in the existing 64 × 64 card areas. The focused palette suite passed, the headless Sandbox Stage loaded, and `git diff --check` passed. Godot import exited 0 with Windows certificate-store and global editor-settings warnings. User verified the palette appearance and placement behavior in the Editor; #71 is resolved. |
_End of Log_
@@ -0,0 +1,130 @@
# Architectural Plan: Dynamic `.prop` Loading & Sandbox Integration
This document outlines the architectural plan to transition `PropLibrary` from a static registry into a dynamic filesystem-backed library (matching `StickmanLibrary`) and integrate user-created `.prop` files into the Sandbox Stage.
---
## 1. System Architecture Overview
The system bridges custom prop files produced by the Prop Editor into the sandbox physics simulation through four primary layers:
1. **Library & File Scanning (`PropLibrary`):** Scans `res://props/` for `*.prop` files, deserializes them via `PropFile`, flattens multi-shape hierarchies into collision payloads, and merges them with built-in templates.
2. **Thumbnail Pipeline (`ThumbnailCache` & `PropThumbnail`):** Computes cache keys using file modification timestamps (`mtime`), renders visual previews offscreen, and streams textures to the selector grid.
3. **UI Integration (`AssetSelector` & `SandboxStage`):** Presents custom props alongside built-in presets in a unified grid, offering folder scanning, ad-hoc file browsing, and palette live-refresh.
4. **World Spawning & Physics Lifecycle (`StageSpawner` & `PropBlock`):** Instantiates `RigidBody2D` nodes configured with custom collision geometry and material physical properties (mass, friction, bounce), tracking authored positions across sandbox mode transitions.
---
## 2. Component Specifications
### A. Library Layer (`res://scripts/prop_library.gd`)
- **Dual-Source Registry:** Maintain built-in templates (Crate, Ball, Plank, Triangle) as default presets while scanning `res://props/*.prop` on demand.
- **Entry Model Schema:** Standardize entry dictionaries returned to the UI:
- **Identifier (`id`):** Resource path (`res://props/chair.prop`) for custom files, or string key (`crate`) for templates.
- **Display Name (`name`):** Extracted from `.prop` metadata, with fallback to capitalized file basename.
- **Material Preset & Label:** Physical properties index (Wood, Metal, Rubber, Cardboard) and human-readable label.
- **Payload (`payload`):** Flattened shape and collision dictionary generated via `PropFile.flatten_to_payload()`.
- **Provenance Flags:** Boolean flag indicating whether the entry is custom and string path reference for scene serialization.
- **Error Handling:** Skip invalid or corrupted `.prop` files gracefully while outputting descriptive warnings.
### B. Thumbnail Pipeline (`ThumbnailCache` & `PropThumbnail`)
- **Dynamic Keying Strategy:**
- Built-in prop templates compute keys using internal version constants.
- Custom `.prop` files compute keys combining file basename and filesystem modification timestamp (`FileAccess.get_modified_time`).
- **Renderer Extension (`PropThumbnail`):** Extend offscreen rendering to support composite shapes and custom vector styling (fill color, outline width, polygons, circles) rather than single primitive shapes.
- **Stale Cache Cleanup:** Automatically purge superseded PNG thumbnails when custom `.prop` files are edited or deleted.
### C. World Spawning Pipeline (`StageSpawner`)
- **Payload Forwarding:** Pass the entry payload directly to `PropUtils.spawn_prop()`, which generates the corresponding `PropBlock` node with `CollisionPolygon2D` or `CollisionShape2D` nodes.
- **Provenance Tagging:** Store `spawn_id` and source path metadata on spawned instances to support scene serialization and reloading.
- **Authored Pose Capture:** Save placement positions to instance metadata (`_authored_position`, `_authored_rotation`) during **Edit** mode.
### D. UI & Sandbox Integration (`AssetSelector` & `SandboxStage`)
- **Grid Population:** Trigger `PropLibrary.scan()` when opening the prop selector, populating grid cells with thumbnails and display names.
- **Browse & Refresh Integration:** Connect the "Browse…" button to a file dialog for arbitrary `*.prop` selection, and the "Refresh" button to re-scan `res://props/`.
- **Lazy Thumbnail Processing:** Process offscreen thumbnail generation non-blockingly during stage frame processing to keep UI interactions responsive.
---
## 3. Physics & Sandbox Lifecycle Integration
Custom `.prop` instances must follow the existing 3-mode sandbox state machine:
| Stage Mode | Prop Physics State | Interaction Capabilities | Reset Behavior |
| ---------- | -------------------------------------------------- | ------------------------ | -------------- |
| **Edit** | RigidBody frozen (`freeze = true`, kinematic mode) |
| Move, rotate, scale, or delete via selection gizmos
| N/A (Authored state recorded on movement)
|
| **Direct** | RigidBody frozen (`freeze = true`, kinematic mode)
| Gizmos active for rule/trigger assignment; spawner palette hidden
| N/A
|
| **Play** | RigidBody unfrozen (`freeze = false`)
| Full physical simulation, collisions with terrain/stickmen/props
| Mode exit restores initial authored position/rotation and zeroes linear/angular velocity
|
---
## 4. Implementation Roadmap
```
[ Phase 1: Library & Data Layer ]
└─ Extend PropLibrary (res://props/*.prop scanning + PropFile parsing)
└─ Define unified Prop Entry Dictionary schema
[ Phase 2: Thumbnail Pipeline ]
└─ Update PropThumbnail to render multi-shape custom payloads
└─ Implement modification timestamp cache invalidation in ThumbnailCache
[ Phase 3: Spawning & UI Integration ]
└─ Update StageSpawner to tag custom prop provenance
└─ Wire AssetSelector grid refresh and file dialog browsing
[ Phase 4: Stage Lifecycle & Verification ]
└─ Verify Edit -> Play -> Edit position resets for custom props
└─ Validate scene saving/loading persistence
```
### Milestone Checklist for Architect Agent
1. **Directory Scanning:** `PropLibrary` reads `res://props/` and extracts payloads via `PropFile`.
2. **Visual Previews:** `PropThumbnail` renders multi-polygon custom vector props to textures.
3. **Palette Display:** `AssetSelector` displays merged built-in templates and `.prop` files.
4. **Physics Interaction:** Custom props unfreeze during **Play** mode, participating in physics simulation and collision events.
5. **State Reset:** Props return to their original authored transforms when switching from **Play** back to **Edit** mode.
@@ -0,0 +1,118 @@
Here is the revised architectural specification incorporating your exact adjustments for your architect agent:
---
# Architectural Spec: Dual-Layer Prop Scaling System (Revised)
Status: **implemented and user verified** (2026-09-22).
This specification outlines the architecture for supporting prop scaling across two layers: permanent template scaling in the **Prop Editor** and contextual instance scaling in the **Sandbox Stage**.
---
## 1. System Architecture & Division of Responsibilities
```
┌─────────────────────────────────────────────────────────┐
│ PROP EDITOR (Phase 9) │
│ • Permanent template geometry mutation │
│ • Vertex & radius coordinate transformations │
│ • Anchor offset auto-scaling │
│ • Stickman guide silhouette (Default: OFF) │
└────────────────────────────┬────────────────────────────┘
│ Saves .prop file
▼
┌─────────────────────────────────────────────────────────┐
│ SANDBOX STAGE (Phase 10) │
│ • Contextual instance scaling via corner gizmos only │
│ • Locked aspect ratio (uniform scaling) │
│ • Physics mass decoupled from visual scale │
└────────────────────────────┴────────────────────────────┘
```
| Layer | Target Entity | Operation Type | Scaling Behavior | Data Persistence |
| ----------------- | ----------------------------- | --------------------------- | ---------------------------------------------------------------- | ------------------------------------------------ |
| **Prop Editor** | Template Asset (`.prop`) | Permanent geometry mutation | Bakes scale into raw vector vertices, radii, and anchor offsets. | Persists into `.prop` file. |
| **Sandbox Stage** | Object Instance (`PropBlock`) | Node transform scaling | Pure gizmo scaling with **locked aspect ratio**. | Persists into `.scene` file (`scale: { x, y }`). |
---
## 2. Prop Editor Implementation (Template Sizing)
### A. Document Geometry Scale Tool
- **Scope:** Modifies raw geometry arrays (`points` for polygons/lines, `radius` for circles) in the shape stack.
- **Anchor Recalculation:** All registered anchor offset positions (`x`, `y`) automatically multiply by the scale factor so grip, pivot, and emitter points maintain exact relative alignment.
- **Normalize Size Action:** Adds a canvas action (📏 **Normalize Size**) alongside 🎯 **Center Prop** that computes the bounding box union of all shapes and uniformly scales the document to fit within a standard $96 \times 96\text{ px}$ canvas box.
### B. Stickman Visual Reference Silhouette
- **Overlay Guide:** Renders a semi-transparent 1:1 Stickman figure silhouette on the canvas background for scale context against full character rigs ($\approx 336 \times 940\text{ px}$).
- **Default State:** Toggleable via the UI / canvas controls and **defaults to OFF**.
- **View Aid Only:** Non-selectable, non-interactive visual guide that is excluded from `.prop` file serialization.
---
## 3. Sandbox Stage Implementation (Instance Sizing)
### A. Gizmo-Driven Scaling (No UI SpinBoxes / No Presets)
- **Corner Scale Handles:** Extends `StageGizmos` in `EDIT` mode to render corner cross handles on selected prop bounding boxes.
- **Aspect Ratio Locking:** Dragging any corner handle scales the object while strictly **locking the aspect ratio** (uniform scaling).
- **Streamlined UI:** No numeric scale SpinBoxes or preset buttons (`S`/`M`/`L`) are added to the stage UI; all scaling is performed intuitively directly on the canvas via gizmo manipulation.
### B. Mode Lifecycle & Physics Rules
- **Decoupled Mass Contract:** Scaling a prop adjusts node visual geometry (`Polygon2D`, `Line2D`) and collision bounds (`CollisionPolygon2D`, `CollisionShape2D`). RigidBody `mass` remains independent, governed strictly by the assigned Material Preset (Wood, Metal, Rubber, Cardboard).
- **Lifecycle State Handling (`PLAY` $\rightarrow$ `EDIT`):** Transitioning from `PLAY` back to `EDIT` mode restores the authored `position` and `rotation` and zeroes linear/angular velocity, but **does not reset or alter the node scale**. The authored scale set during editing is retained as-is.
---
## 4. Implementation Roadmap
1. **Phase A (Prop Editor Tooling):**
- Implement document-wide geometry and anchor scaling logic in `PropEditor`.
- Add the toggleable Stickman Reference Silhouette (**default OFF**) and "Normalize Size" canvas button.
2. **Phase B (Sandbox Gizmo Scaling):**
- Extend `StageGizmos` with corner scale handles that lock aspect ratio during drag operations.
- Ensure `StageSelection` AABB bounding boxes recalculate accurately to account for scaled instances.
3. **Phase C (Persistence & Verification):**
- Verify scene save/load serialization of scaled `PropBlock` instances.
- Confirm that `EDIT` $\rightarrow$ `PLAY` $\rightarrow$ `EDIT` mode switches restore authored position/rotation while keeping object scale intact.
## 5. Verified final Prop Editor controls
The Arrange mode top bar presents these controls in order:
1. **Transform Whole Prop**
2. **Normalize Size**
3. **Center Prop**
4. **Show Stickman Guide**
The numeric scale textbox and Apply Scale button were removed. Transform Whole
Prop previews and commits whole-document move, rotation, and uniform resize
gestures around the canonical origin. Grid Snap rounds move deltas, Ctrl snaps
rotation to 15-degree increments, editable anchors follow the geometry, and Esc
cancels an active preview. Normalize Size fits the shape-point bounds to a 96 px
maximum dimension. Transform Whole Prop and Normalize Size are hidden in Reshape
and Attach; Center Prop and Show Stickman Guide remain available in all modes.
Sandbox instance scaling remains a separate prop-only uniform corner-gizmo
operation. It retains the authored scene scale, preset-governed mass, and
PLAY-to-EDIT position/rotation restoration behavior.
@@ -0,0 +1,268 @@
# Architecture Specification: Interactive Prop System & Event Engine
This specification details the technical architecture for bringing interactive prop capabilities—including auto-approach, IK socket binding, parabolic throwing, ragdoll disarming, and "When..." rule triggers—into the Sandbox Stage.
---
## 1. System Overview & Layer Interactions
Prop interactions bridge five core subsystems across the runtime stack:
```
[ Director UI / Target Selector ]
│
▼
[ StickmanActionRunner ] ──(Auto-Approach)──► [ StickmanLocomotion ]
│
▼
[ StickmanRig IK ] ──(Socket Binding)──► [ PropBlock Physics ]
│
▼
[ StageEventRegistry ] ──("When..." Rules)──► [ Reactive Actions ]
```
1. **Director UI & Target Selection:** Captures spatial throw targets and enqueues prop interaction commands.
2. **Action Runner & Locomotion:** Evaluates spatial reach; automatically inserts `walk_to` sub-goals when props are out of arm's reach.
3. **IK & Bone Socketing:** Extends arm IK targets toward prop `grip` anchors and manages `RemoteTransform2D` binding.
4. **Physics Lifecycle:** Unfreezes/freezes `PropBlock` bodies and inherits velocity during release, throw, or ragdoll disarm transitions.
5. **Event Registry:** Emits and evaluates edge-triggered prop rules within the stage's "When → Then" engine.
---
## 2. Dynamic Action Queue & Auto-Approach (`walk_to`)
To ensure smooth director interaction, the action queue automatically resolves spatial gaps between the stickman and target props.
### Action Payload Schemas
- **`pick_up_prop`**: `{ "type": "pick_up_prop", "target_prop_id": String, "hand": "auto" | "left" | "right" }`
- **`drop_prop`**: `{ "type": "drop_prop" }`
- **`throw_prop`**: `{ "type": "throw_prop", "target_position": Vector2, "force": float }`
### Smart Approach Execution
When `StickmanActionRunner` receives a `pick_up_prop` payload:
```
┌──────────────────────────────────────────────┐
│ pick_up_prop Action Start │
└──────────────────────┬───────────────────────┘
│
┌──────────────────────▼───────────────────────┐
│ Distance = |rig.position - prop.position| │
└──────────────────────┬───────────────────────┘
│
Is Distance > Reach Radius (e.g. 40px)?
┌───────────────────────┴───────────────────────┐
│ │
[ YES ] [ NO ]
│ │
┌─────────────────▼─────────────────┐ ┌─────────────────▼─────────────────┐
│ 1. Pause pick_up_prop sequence │ │ 1. Skip locomotion step │
│ 2. Prepend walk_to(prop_offset) │ │ 2. Trigger arm IK reach animation │
│ 3. Resume pickup on arrival │ └───────────────────────────────────┘
└───────────────────────────────────┘
```
1. **Distance Check:** Evaluates global horizontal/spatial distance between the stickman root and prop `origin` pivot.
2. **Sub-Goal Injection:** If outside reach radius, `StickmanActionRunner` pauses the pickup sequence and internally issues a `walk_to` command to a stand point adjacent to the prop.
3. **Sequence Resume:** Upon receiving `StickmanLocomotion.arrived_at_waypoint`, the runner resumes the pickup sequence and triggers arm IK extension.
---
## 3. Pickup, Drop, and Throw Pipelines
### A. Prop Pickup Sequence & IK Socketing
1. **IK Reach Phase (150 ms):** `TwoBoneIK` targets for the selected arm interpolate toward the world position of the prop's `grip` anchor.
**Edit:** Torso position should also move up/down based on grip anchor. The stickman should look like it is bending or extending legs to make pickup look more realistic.
2. **Socket Attachment:**
- Set `PropBlock.freeze = true` (kinematic mode) to suspend environmental physics forces.
- Instantiate or activate a `RemoteTransform2D` node attached to the lower arm / wrist joint, targeting the `PropBlock`.
- Apply anchor local offsets (`x, y, rotation_deg, z_index_offset`) to align the prop grip directly within the hand geometry.
3. **Holding Stance Latch:** Lock upper limb IK targets into a carried stance during subsequent walk cycles.
### B. Prop Drop Sequence
1. **Unbinding:** Clear `RemoteTransform2D` target and reparent `PropBlock` back to the stage world root.
2. **Physics Activation:** Set `PropBlock.freeze = false` to re-enable rigid body simulation, gravity, and collision shapes.
3. **Inertia Inheritance:** Set `PropBlock.linear_velocity` equal to the stickman's current movement velocity (`_last_grounded_walk_velocity_x`) so dropped items retain forward momentum.
4. **Arm Recovery:** Blend wrist IK targets smoothly back to standard locomotion/idle rest positions.
### C. Prop Throw Sequence & Trajectory Selection
#### Director Target UI Flow
- **Targeting Mode:** Selecting "Throw Prop" in Director/Play mode activates an aim reticle cursor and dynamic arc preview.
**EDIT:** When the user clicks the stickman in DIRECT mode, 3 new additions should appear in the action context menu
1. Pick up
2. Drop
3. Throw
[Give these proper emoji]
- If Drop is added to the queue, and the action is run when the stickman holds nothing, the action is skipped (marked as done)
- If Throw is added to the queue, and the action is run when the stickman holds nothing, the action is skipped (marked as done)
- These 3 actions should also be selectable when handling a 'When' event
- **Trajectory Math:** Given hand release point $P_0$, target landing point $P_t$, base speed $v_0$, and gravity $g$:
$$\theta = \arctan\left(\frac{v_0^2 \pm \sqrt{v_0^4 - g(g \cdot x^2 + 2 y \cdot v_0^2)}}{g \cdot x}\right)$$
Calculates exact initial launch velocity components $V_{\text{launch}} = (v_0 \cos\theta, v_0 \sin\theta)$.
#### Keyframe Throw Animation Arc
```
[ Windup: 100ms ] ──► [ Release Frame: 150ms ] ──► [ Follow-Through: 50ms ]
- Torso tilts -15° - Arm sweeps to angle - Arm extends along arc
- Arm pulls backward - Prop unfreezes + impulse - Blends back to idle
```
1. **Windup (100 ms):** Torso rotates backward opposite the launch vector while the arm pulls back.
2. **Release Frame (150 ms):**
- Arm sweeps rapidly along the calculated launch angle $\theta$.
**EDIT:** The arm should go overhead - not an underhand throw.
- Unbind `RemoteTransform2D` and reparent prop to world root.
- Set `PropBlock.freeze = false` and execute `apply_central_impulse(V_launch)`.
- Apply rotational torque proportional to the distance between hand grip and prop center-of-mass.
3. **Follow-Through (50 ms):** Arm extends toward launch vector, then smoothly dampens back to default idle/walking pose.
---
## 4. Physics & State Machine Lifecycle (Ragdoll Disarm Handoff)
When `StickmanRig.set_ragdoll(true)` is executed (via impact, command, or keypress), any held prop must be immediately disarmed before kinematic nodes are hidden and ragdoll bodies are built.
```
┌──────────────────────────────────────────────┐
│ StickmanRig.set_ragdoll(true) │
└──────────────────────┬───────────────────────┘
│
┌──────────────────────▼───────────────────────┐
│ 1. Pre-Ragdoll Intercept │
│ Query if held_prop != null │
└──────────────────────┬───────────────────────┘
│
┌──────────────────────▼───────────────────────┐
│ 2. Execute Disarm Handoff │
│ - Clear RemoteTransform2D binding │
│ - Reparent PropBlock to world root │
│ - Set prop.freeze = false │
│ - Apply cached momentum (_track_momentum) │
└──────────────────────┬───────────────────────┘
│
┌──────────────────────▼───────────────────────┐
│ 3. Build Physics Ragdoll │
│ Hide animated body, build RigidBody2D │
│ network at current solved bone positions │
└──────────────────────────────────────────────┘
```
1. **Pre-Ragdoll Intercept:** Prior to clearing collision proxies or hiding body meshes, check for an active `held_prop` binding.
2. **Physics Unbind:**
- Detach the `RemoteTransform2D` socket.
- Reparent prop to the world root and set `PropBlock.freeze = false`.
- Transfer cached rig momentum (`_track_momentum()`) directly to `PropBlock.linear_velocity` and `angular_velocity` so the prop tumbles naturally alongside the falling figure.
3. **Ragdoll Construction:** Proceed with standard `_build_ragdoll()` processing at current solved bone transforms.
---
## 5. Event System Integration ("When..." Triggers)
Three new edge-triggered prop event types are added to `StageEventRegistry` (`sandbox_stage.gd`) to support the director's "When → Then" mental model.
### Prop Trigger Specifications
| Trigger Type | Source Object | Target Object | Firing Condition |
| ---------------- | --------------- | --------------- | ------------------------------------------------------------------------------- |
| `prop_picked_up` | Stickman / Prop | Prop / Stickman | Emitted as soon as IK arm reach completes and `RemoteTransform2D` socket locks. |
|
| `prop_dropped` | Stickman / Prop | Prop / Stickman | Emitted when a stickman drops a prop manually or via ragdoll disarm.
|
| `prop_thrown` | Stickman / Prop | Prop / Stickman | Emitted at the exact release frame of a throw animation arc.
|
### Signal Routing & Evaluation
1. `StickmanActionRunner` emits `prop_action_executed(action_type, stickman, prop)` upon completing or releasing an anchor attachment.
2. `StageEventRegistry` matches active stage rules where `rule.trigger.type` equals `prop_picked_up`, `prop_dropped`, or `prop_thrown`.
3. Matching rules trigger reactive actions on target stickmen or props (e.g., _When Stickman A picks up Sword X $\rightarrow$ Stickman B ragdolls_).
---
## 6. Implementation Roadmap for Architect Agent
```
[ Phase 1: Action Queue & Locomotion ]
├─ Add pick_up_prop, drop_prop, and throw_prop to StickmanActionRunner
└─ Implement distance evaluation & auto-enqueueing of walk_to sub-goals
[ Phase 2: Socket Attachment & IK Handshake ]
├─ Implement RemoteTransform2D socketing to wrist joint with grip offsets
└─ Implement holding stance arm pose blending
[ Phase 3: Throw Mechanics & Trajectory UI ]
├─ Add trajectory solver & reticle target selector to Director UI
└─ Build throw animation timeline (windup -> release impulse -> follow-through)
[ Phase 4: State Machine Integration & Disarm ]
├─ Intercept held props during set_ragdoll(true) transitions
└─ Transfer cached linear/angular momentum to disarmed prop physics body
[ Phase 5: Event System Expansion ]
├─ Register prop_picked_up, prop_dropped, prop_thrown triggers in StageEventRegistry
└─ Wire action signals to trigger "When -> Then" rule evaluations
```
## 7. Design Questions / Rules
1. When creating a Throw action, the user must create an arc defining the throw. However, when the scene is playing, the stickman might be in a whole different position for when the throw must happen. Should we define a new arc and make a best effort to throw the prop towards the (x,y) location selected or should we use the same arc, but translate the throw endpoint relevant to the stickman's current position.
2. The stickman profile direction should change to whichever direction the prop will be thrown.
### Architectural Acceptance Criteria Checklist
- [ ] **Auto-Approach:** Requesting pickup of a distant prop automatically causes the stickman to walk to the prop before reaching out.
- [ ] **Grip Alignment:** Hand attaches precisely to the prop's authored `grip` anchor, respecting local position, rotation, and Z-index offsets.
- [ ] **Trajectory Accuracy:** Thrown props follow the calculated parabolic arc toward the director's targeted ground point.
- [ ] **Ragdoll Disarm:** Entering ragdoll mode immediately detaches held props and applies rig momentum so items tumble realistically.
- [ ] **Event Rule Engine:** Rules configured with `When prop_picked_up`, `When prop_dropped`, or `When prop_thrown` execute downstream actions reliably.
@@ -0,0 +1,73 @@
# Architecture Summary: Live Director System
The **Live Director System** transitions the sandbox from a static "plan-then-watch" tool into an interactive **Director Loop** ($\text{Play} \leftrightarrow \text{Pause/Direct} \leftrightarrow \text{Resume}$). Users can freeze the simulation mid-flight at any timestamp $t$, inspect current states, append or modify action paths relative to stickmen's live positions, and resume simulation seamlessly.
---
## 1. Core State & Control Model
```
┌──────────────────────────────────────┐
│ EDIT Mode (Authored t=0) │
└──────────────────┬───────────────────┘
│ Play (from t=0)
▼
┌──────────────────────────────────────┐
│ PLAY Mode (Simulating) │
└──────────┬────────────────▲──────────┘
Pause / Direct │ │ Resume Sim
(Freeze state at t) ▼ │
┌──────────────────────────────────────┐
│ DIRECT Mode (Mid-Sim State at t) │
└──────────────────┬───────────────────┘
│ Stop / Reset
▼
Restores Authored t=0
```
- **Unified Pause Alias**: Pressing **`Spacebar`**, clicking **`⏸️ Pause`**, or selecting **`🎬 Direct`** while in `PLAY` mode triggers the exact same transition: freezing simulation at timestamp $t$ and opening Direct Mode editing.
- **Resume**: Pressing **`Spacebar`** or clicking **`▶️ Play`** unfreezes physics and resumes simulation.
- **Reset**: Clicking **`↺ Reset`** or selecting **`✏️ Edit`** returns the stage and props to $t=0$ authored positions.
---
## 2. Physics, Timing, & State Freeze Mechanics
- **Multi-Part Ragdoll Velocity Caching**: Transitioning to Direct Mode iterates over all **10 individual bone `RigidBody2D` parts** in `StickmanRagdoll` as well as dynamic stage props. Linear and angular velocities are saved to node metadata (`set_meta`), and nodes are set to `freeze = true` (`FREEZE_MODE_KINEMATIC`). Momentum is restored per body upon unfreezing.
- **Collision Penetration Protection**: Body transforms are recorded on pause and synced via `PhysicsServer2D` prior to waking up physics bodies on resume to prevent force ejection glitches.
- **Tween & Process Clock Freezing**: Active procedural `Tween`s (`tween.pause()`), speech bubble timers, and runner process clocks freeze during pause.
---
## 3. UI Chrome & Visual Hierarchy
- **Live Timestamp Display (`TimestampBadge`)**:
- **Location**: Top-right corner of the viewport (`PRESET_TOP_RIGHT`).
- **Format**: Displays `⏱️ MM:SS.ms` (e.g. `00:04.25`).
- **Theming**: The display's size and font should be dictated by the **sandbox_theme.json** file.
- **Lifecycle**: Ticks continuously in `PLAY`, freezes on the exact paused frame in `DIRECT`, and resets to `00:00.00` in `EDIT`.
- **Viewport Path Visuals**:
- **Completed History ($0 \rightarrow t$)**: Displayed in a **faded ghost style (20% alpha / desaturated grey)** or hidden to eliminate clutter.
- **Pending Futures ($t \rightarrow \infty$)**: Displayed at **100% full opacity** with bright accent vectors originating at the stickman's live feet position ($P_{\text{live}}$) at time $t$.
- **Airborne & Ragdoll Spatial Anchoring**:
- For airborne or ragdolled stickmen, a vertical down-raycast locates the nearest ground point ($P_{\text{ground}}$).
- A vertical drop-line connects the character to $P_{\text{ground}}$, and new path vectors anchor directly to $P_{\text{ground}}$.
---
## 4. Action Execution & Edge Case Handling
- **Auto-Recovery**: Assigning a `walk_to` movement action to a ragdolled stickman automatically prepends a `recover` action so they stand up from their current location before navigating.
- **Landing Barrier Compliance**: Actions appended while paused during a landing crouch compression (`LANDING_IMPACT`) stay queued behind `_is_action_queue_blocked()` until landing animation compression completes upon resuming.
- **Active Action Cancellation**: Editing or deleting an action currently in progress at timestamp $t$ calls `_cancel_current_primary()` on `StickmanActionRunner` to clear active IK paths and speech overlays cleanly.
- **Ragdoll Hitbox Selection**: Selection clicks in Direct Mode evaluate all child bone collision shapes in `RagdollBodyContainer` to ensure reliable clicking regardless of pose.
- **Queue Persistence**: Actions added mid-sim ($t > 0$) append sequentially into the stickman's master queue. Resetting to $t=0$ restores stage layout but retains all newly authored queue actions for playback from the start.
---
## 5. Explicitly Out of Scope
- **Stage & Action Undo/Redo (`Ctrl+Z` / `Ctrl+Y`)**: Stage-wide and action queue undo/redo stacks remain explicitly **OUT OF SCOPE** for this implementation phase. Misplaced items or actions are modified or removed manually via viewport gizmos and selection menus.
@@ -0,0 +1,157 @@
## Summary of Speech & Speech Bubble Features
The goal is to expand the existing single-bubble `SpeechBubble` system into a comic-book movie engine designed for young directors.
### 1. Data Contract & Theme Integration
- **Expanded Action Payload**: Extend the `speak` action payload to support explicit visual parameters:
```json
{
"type": "speak",
"text": "Look out for the [shake]MONSTER![/shake]",
"duration": 2.5,
"stop_current": false,
"visual": {
"bubble_style": "yell",
"font_preset": "comic",
"size_preset": "huge",
"color": "#FF3333",
"anim_mode": "typewriter"
}
}
```
- **Theme Fallbacks**: Route missing keys through `sandbox_theme.json` defaults $\rightarrow$ Global Theme $\rightarrow$ Godot `ThemeDB.fallback_font`.
### 2. Rich Text & Visual Styles
- **`RichTextLabel` Migration**: Shift text rendering from raw `draw_string()` calls to an internal `RichTextLabel` child node to enable full BBCode support (`[wave]`, `[shake]`, bolding, inline colors).
- **Shape Presets (`BubbleStyle`)**:
- **Standard**: Classic rounded rectangle + downward tail triangle.
- **Yell / Shout**: Multi-pointed starburst polygon.
- **Thought**: Arc/circle cluster cloud boundary + descending bubble chain dots.
- **Whisper**: Semi-transparent fill with dashed border lines (`draw_dashed_line()`).
- **Comic SFX**: Borderless, high-contrast banner box for sound effect popups ("BOOM!", "POW!").
### 3. Dynamic Text & Bubble Animations
- **Typewriter & Word Reveals**: Animate `visible_characters` via Godot `Tween` for letter-by-letter or word-by-word reveals.
- **Elastic Entrances & Transitions**: Scale bubble from $0 \rightarrow 1$ using `TRANS_ELASTIC` on pop-in; morph bounding sizes with smooth squish-and-stretch tweens when swapping text lines back-to-back.
- **Word-Specific Shake & Wave**: Support inline animated text effects wrapped in BBCode tags.
### 4. Advanced Layout & Stagger Stacking
- **Comic Stagger Stacking**: Allow multiple speech lines to chain into vertical bubble stacks. The bottom bubble anchors to the stickman's head via the main tail, while higher bubbles stack upward connected by bridge geometry (`draw_colored_polygon()`).
- **Mirroring & Viewport Clamping**: Keep X-scale counter-mirroring active (`_sync_speech_bubble_mirror`). Clamp bubble bounds inside `get_viewport_rect()` so bubbles flip tail anchors or offset position to remain on-screen.
### 5. Director UI Upgrades
- **Kid-Friendly Controls**: Color swatch buttons, size toggles (**Small**, **Medium**, **HUGE**), font style pickers, and an emoji grid picker inside the `Speak` parameter dialog.
- **Text Highlight Formatting Bar**: Selecting text in the dialogue input shows quick-action buttons (🫨 **Shake**, 🌊 **Wave**, 🔥 **Bounce**) that automatically inject BBCode tags.
- **Auto-Duration Calculator**: Default `duration` automatically calculates based on word count ($1.0\text{s base} + 0.2\text{s/word}$).
---
## Detailed Implementation Plan
### Phase 1: Engine Migration & Data Foundation
**Goal:** Upgrade `SpeechBubble` to use `RichTextLabel` and expand data contracts without breaking existing scene functionality.
1. **Update Action Data Pipeline**:
- Update `ActionRegistry` and `StageSceneIO` to preserve the optional `visual` dictionary payload on `speak` actions.
- Ensure `StickmanRig.speak()` parses both flat strings and full visual dictionaries.
2. **Refactor `SpeechBubble` Node Structure (`scripts/stickman_speech_bubble.gd`)**:
- Add a child `RichTextLabel` node to `SpeechBubble`.
- Set `RichTextLabel.bbcode_enabled = true` and configure autowrap.
- Modify `_draw()`: Calculate background rectangle bounds dynamically using `RichTextLabel.get_content_height()` and text size metrics, then draw the background polygon/rect and tail under the text.
### Phase 2: Animated Reveals & Line Transitions
**Goal:** Implement typewriter effects, word reveals, and smooth line-swapping transitions.
1. **Typewriter & Word Reveal Engine**:
- Add `_play_typewriter(text: String, duration: float, mode: String)` to `SpeechBubble`.
- Use a Godot `Tween` targeting `RichTextLabel.visible_characters` from `0` to total character length over the target duration.
- Implement word-by-word step incrementing by parsing space/punctuation indices in the input string.
2. **Line Swap & Elastic Transitions**:
- Update `show_text()`: If a bubble is already visible when a new `speak` command arrives:
- Scale container down ($1.0 \rightarrow 0.8$) via `EASE_IN` tween.
- Swap payload and recalculate bounds.
- Scale container back out ($0.8 \rightarrow 1.0$) using `TRANS_ELASTIC` / `EASE_OUT`.
- Add squish-and-stretch size morphing when text length changes between consecutive lines.
### Phase 3: Polygon Shape Styles & Comic Geometry
**Goal:** Generate dynamic background shapes for distinct bubble styles.
1. **Shape Draw Functions**:
- **`_draw_standard_bubble(rect)`**: Existing rounded rect + downward tail triangle.
- **`_draw_yell_bubble(rect)`**: Generate an outer jagged polygon by iterating around `rect` perimeter and alternating vertex radii with random/sine noise offsets.
- **`_draw_thought_bubble(rect)`**: Draw overlapping circles along the bounding box edges using `draw_circle()`, followed by 2–3 smaller descending circles pointing to origin.
- **`_draw_whisper_bubble(rect)`**: Draw semi-transparent background fill; outline using `draw_dashed_line()` around the perimeter.
2. **Viewport Boundary Protection**:
- Calculate global screen position of bubble rect in `_process()`.
- If top or side edges exit `get_viewport_rect()`, offset the bubble box downward/inward and flip tail geometry anchor accordingly.
### Phase 4: Staggered Multi-Bubble Stacking
**Goal:** Support chained comic bubbles stacking upward from a single character.
1. **Stack Container Logic**:
- Extend `SpeechBubble` to manage an array of child `BubbleItem` records.
- When a multi-line or stacked speech payload is received, instantiate child bubble slots.
2. **Stack Geometry & Bridge Rendering**:
- Anchor the bottom-most bubble to `SPEECH_BUBBLE_OFFSET`.
- Position preceding bubbles vertically above it with vertical spacing gaps.
- In `_draw()`, calculate bridge vertices between `Bubble N` bottom-center and `Bubble N+1` top-center, drawing connecting polygons using `draw_colored_polygon()`.
- Restrict the downward head tail exclusively to the bottom bubble in the stack.
### Phase 5: Director UI & Formatting Controls
**Goal:** Expose all visual styling tools to kids in the editor UI.
1. **Integrate Theme Controls into `Speak` Parameter Dialog**:
- Add `ColorSwatchGrid` popover hooked into theme colors.
- Add `SizeToggle` segmented control (**Small**, **Medium**, **HUGE**) mapping to font scale metrics.
- Add `StylePicker` icon buttons (**Standard**, **Yell**, **Thought**, **Whisper**).
- Embed `EmojiPicker` grid widget.
2. **Implement Selection Highlight Formatting Bar**:
- Attach a floating mini-toolbar above the dialogue `LineEdit` / `TextEdit` field in the `Speak` dialog.
- Detect text selection (`get_selected_text()`). When text is highlighted, show mini-buttons:
- 🫨 **Shake** $\rightarrow$ wraps selection in `[shake rate=20.0 level=10.0]...[/shake]`
- 🌊 **Wave** $\rightarrow$ wraps selection in `[wave amp=50.0 freq=5.0]...[/wave]`
- 🔥 **Bounce** $\rightarrow$ wraps selection in `[bounce]...[/bounce]`
3. **Auto-Duration & Stagger Helpers**:
- Add auto-duration calculation checkbox/toggle in dialog.
- Add **"+ Add Stacked Bubble"** button in the speech dialog to split longer dialogue into chained stack items easily.
@@ -0,0 +1,198 @@
# Architecture Plan: Rig Relocation & Component Modularization
### **Objective**
Relocate `master_rig.tscn` to `res://scenes/stickman_rig.tscn` and decouple `stickman_rig.gd` into dedicated, single-responsibility sub-components to improve maintainability, testability, and concern separation.
---
### **Phase 1: File Operations & Scene Relocation**
1. Move `res://master_rig.tscn` to `res://scenes/stickman_rig.tscn` within Godot to maintain internal UID mappings and resource links.
2. Verify sub-resource integrity after relocation.
---
### **Phase 2: Script Modularization (`stickman_rig.gd`)**
Extract key responsibilities into sub-nodes or dedicated helper scripts attached to `stickman_rig.tscn`:
- **Ragdoll & Recovery Component (`StickmanRagdoll`)**
- Move ragdoll body/joint tables (`RAGDOLL_BODIES`, `RAGDOLL_JOINTS`) and joint limit bands.
- Move procedural physics node instantiation (`RagdollBodyContainer`), pin joint setups, and impulse application (`apply_ragdoll_velocity_boost`).
- Extract rest detection polling (`REST_LINEAR_THRESHOLD`, `REST_ANGULAR_THRESHOLD`), rest timers, and stand-up recovery tweens (`request_recovery`, `snap_to_standing`).
- **Locomotion & Terrain Subsystem (`StickmanLocomotion`)**
- Extract surface raycast probing (`_probe_ground_y`, `_probe_terrain_fan`) and root elevation adjustments (`_reanchor_root_to_landing`).
- Move airborne physics, horizontal drag calculations (`AIR_DRAG_X`), downward gravity acceleration, and procedural wind-drag pose flailing.
- Extract landing impact velocity capture and knee-compression crouch/spring recovery tweens.
- **Director & Action Runner (`StickmanActionRunner`)**
- Extract the action queue data structure (`action_queue`) and execution state machines (`RunnerState`, `ActionPhase`).
- Move pathfinding coordination (`NavigationAgent2D`), kinematic `walk_to` execution, and `FOOT_OFFSET` alignment.
- Handle speech bubble triggers (`speak`), wait timers, and rule-injected reactive action clearing (`clear_reactive_actions`).
- **Core Rig Coordinator (`StickmanRig`)**
- Retain core rig state management (`ANIMATED`, `RAGDOLL`, `RECOVERING`).
- Focus strictly on facing profiles (`FacingProfile`), TwoBoneIK bend direction toggles (`BendDirection`), and draw-order sorting (`Z_ORDER_BY_PROFILE`).
- Serve as the root coordinator relaying signals and state transitions to sub-components.
---
### **Phase 3: Reference & Script Updates**
Update hardcoded string references from `"res://master_rig.tscn"` to `"res://scenes/stickman_rig.tscn"`:
- **`scripts/stickman_factory.gd`**: Update `spawn_from_data()` instantiation target.
- **`scripts/test_harness.gd`**, **`scripts/stk_rig_adapter.gd`**, **`scripts/create_animations.gd`**: Update script comments, tool headers, and path constants.
- **`tests/` Suite**: Search and replace hardcoded scene path string literals across all unit tests.
---
### **Phase 4: Documentation Updates**
Update path references across project markdown files:
- `README.md`, `AGENTS.md`, `RIGGING.md`, and relevant files in `specs/` and `plans/`.
---
### **Phase 5: Verification & Acceptance Criteria**
1. **Separation & Testability**: Confirm that locomotion raycasting and action runner execution can be tested independently without instantiating ragdoll physics.
2. **Factory Instantiation**: Verify `StickmanFactory.spawn()` loads `res://scenes/stickman_rig.tscn` cleanly without path errors.
3. **Test Harness Verification**: Run `res://scenes/test_harness.tscn` via **F6** to ensure walk, speak, wait, and ragdoll actions execute cleanly.
4. **Automated Suite**: Run the full project unit test suite in `tests/` to confirm all assertions pass.
---
### **Completion Record (verified implementation)**
Phase 8 is implemented. The relocated scene is `res://scenes/stickman_rig.tscn`; its root `Master` node is a `StickmanRig` facade over three authored component children. The facade remains the compatibility boundary for callers while each component owns one subsystem:
```text
Master (StickmanRig)
├── RigComponents
│ ├── Locomotion (StickmanLocomotion)
│ │ ├── terrain probing / ground following
│ │ ├── walking and airborne motion
│ │ └── collision proxies and landing reactions
│ ├── Ragdoll (StickmanRagdoll)
│ │ ├── procedural RigidBody2D + PinJoint2D graph
│ │ ├── rest detection and momentum handoff
│ │ └── capture, re-anchor, and stand-up recovery
│ └── ActionRunner (StickmanActionRunner)
│ ├── queued walk / speak / wait actions
│ └── ragdoll and recovery requests
├── Body/* mounted visuals, driven by RemoteTransform2D nodes
├── Skeleton2D/Torso/* Bone2D chain and IK modifications
├── IK_Targets/* six animation/recovery markers
└── AnimationPlayer canonical walk clips and authored animation data
```
`StickmanRig` owns cross-cutting state (`ANIMATED`, `RAGDOLL`, `RECOVERING`), facing profiles, bend flags, body z-order, and signal/API forwarding. `StickmanRagdoll` owns physics construction and recovery; `StickmanLocomotion` owns terrain/motion; `StickmanActionRunner` owns director queue execution. Component references are resolved and configured in `StickmanRig._ready()` and are null-guarded with warnings.
#### Ragdoll handoff and recovery
The runtime handoff preserves the current solved IK pose before physics takes ownership:
```text
ANIMATED
│ set_ragdoll(true)
▼
capture pre-ragdoll markers, bone rotations, facing, bend flags, knee sides
│ build bodies from current bone endpoints; copy mounted Body/* visuals
▼
RAGDOLL ── rest + timeout ──▶ capture body pose + probe terrain
│ destroy physics graph
│ re-anchor root at captured landing feet
▼
RECOVERING ── tween markers/bones toward captured entry pose ──▶ ANIMATED
```
The important dictionaries have these shapes (vector values are runtime `Vector2`s):
```gdscript
_spawn_pose = {
"Torso": {"pos": captured_torso_pos, "rot": captured_torso_rotation},
"Head": {"pos": captured_head_tip, "rot": 0.0},
"Left_Hand": {"pos": captured_left_wrist, "rot": 0.0},
"Right_Hand": {"pos": captured_right_wrist, "rot": 0.0},
"Left_Leg": {"pos": captured_left_ankle, "rot": 0.0},
"Right_Leg": {"pos": captured_right_ankle, "rot": 0.0},
}
_captured_pose["left_lower_leg"] = {
"pos": rig_local_body_position,
"rot": body_global_rotation - rig_global_rotation,
"half": capsule_half_length,
}
```
`_spawn_pose` is initialized from the adapted skeleton by `StickmanFactory.spawn_from_data()` and is the placement ghost's authored pose; the values therefore depend on the loaded `.stk`, rather than on `STAND_POSE`. `_pre_ragdoll_state` is the recovery endpoint: it stores the six markers, limb/head rotations, facing profile, bend flags, head LookAt state, knee-side information, and chain-space offsets. `_captured_pose` is the fallen physics pose used to reconstruct the skeleton and landing position.
#### Stage mode flow and pose preservation
EDIT and DIRECT are two views of the same frozen, non-simulating world. The mode transition receives `previous_mode` so it can avoid an unnecessary reset:
```text
EDIT ◄──────────────► DIRECT
│ │
└──────────┬───────────┘
▼
PLAY
│
set_mode(EDIT or DIRECT)
│
▼
cleanup + restore spawn pose
```
| Transition | Stage behavior | Rig pose behavior |
|---|---|---|
| EDIT → DIRECT | Hide edit controls; keep world frozen | Preserve exact live pose and IK markers |
| DIRECT → EDIT | Restore edit controls; keep world frozen | Preserve exact live pose and IK markers |
| EDIT/DIRECT → PLAY | Enable simulation, terrain following, and action queues | Begin from the current authored/editor pose |
| PLAY → EDIT/DIRECT | Stop queues, disable terrain following, freeze physics, restore object transforms | `snap_to_standing()` restores the factory-captured `_spawn_pose` |
The root cause of the verified regression was that `snap_to_standing()` restored the legacy hardcoded `STAND_POSE`. That generic pose lowered the arms and rotated the head whenever PLAY cleanup ran. The central fix is to restore `_spawn_pose` when it exists, with `STAND_POSE` retained only as a compatibility fallback for rigs that have no captured spawn pose. Ragdoll recovery is separate: its stand-up tween still targets `_pre_ragdoll_state`, so recovery returns to the pose captured at ragdoll entry rather than the factory pose.
#### Facing, walk-phase bend stabilization, and head tracking
`FacingProfile.LEFT` is a whole-rig X mirror (`Master.scale.x = -1`); RIGHT and FORWARD use identity scale. Runtime walking always plays the canonical `walk_right` clip and mirrors it for LEFT. The profile still applies provisional per-joint bend flags and z-order tables. For side profiles, a walk keyframe can place a leg exactly straight, leaving PinJoint2D with no geometric knee side. The ragdoll component therefore:
- permits the straight hinge boundary (`0°`) and the intended fold range (`±150°`) for LEFT/RIGHT profiles;
- adds only a small 3° profile-relative bias at the near-straight ragdoll handoff; and
- checks each rendered RAGDOLL frame, correcting only an actual wrong-side knee crossing while preserving normal physical motion.
Head tracking is owned by the `SkeletonModification2DLookAt` targeting `IK_Targets/Head`. RIGHT/FORWARD enable it. LEFT disables LookAt and pins the canonical head rotation because the whole-rig mirror must remain mirror-invariant; this is intentional rather than a missing target update. The scene's authored head constraint is disabled so the active LookAt can follow moved markers without the old constraint interpretation pinning it.
#### Regression coverage
The completed implementation was checked with focused headless tests and the existing suites:
| Test | Result | Coverage |
|---|---:|---|
| `tests/test_phase8_rig_modularization.gd` | 23/23 | relocated scene, component ownership, head tracking, facing flags, ragdoll facade |
| `tests/test_phase8_edit_direct_pose.gd` | 45/45 | EDIT↔DIRECT round trip and EDIT→PLAY→DIRECT pose/marker restoration |
| `tests/test_phase8_ragdoll_walk_handoff.gd` | 80/80 | LEFT/RIGHT profiles across multiple walk animation phases and physics frames |
| `tests/test_phase45_collision_bodies.gd` | 223/223 | collision proxies, reactions, and PLAY cleanup to authored pose |
| `tests/test_phase3c_walk_recovery.gd` | 185/185 | walk, ragdoll, recovery, and action integration |
| `tests/test_phase6a_airborne_landing.gd` | 24/24 | airborne pose, landing, and ragdoll cleanup |
| `tests/test_phase4b_stage.gd` | 53/53 | stage mode UI/state transitions and pending-action cleanup |
The plan's original acceptance criteria remain the historical intent; this record captures the as-built ownership and the follow-up fixes verified after user testing.
+167
View File
@@ -0,0 +1,167 @@
### Objective Summary
This revised plan details the addition of an **Attach** mode to the Prop Editor (`[ Arrange | Reshape | Attach ]`), introducing a flexible, **Role-Based Dynamic Anchor Architecture** for vector prop files (`.prop`). Instead of hardcoding weapon-centric anchors (like a fixed muzzle), anchors are defined as named dictionary entries assigned specific functional roles—such as `pivot`, `grip`, `emitter`, `impact`, or `mount`. Coupled with interactive canvas gizmos, a live semi-transparent Stickman Hand Preview silhouette, and `StickmanRig` IK attachment logic, this system allows any prop (from flashlights and magic staves to broadswords and rifles) to be held, flipped, and interacted with accurately across all facing profiles.
---
### 1. Dynamic Role-Based Anchor Data Architecture (`.prop` Schema)
The `.prop` JSON file stores anchors in a flexible dictionary where each key represents a unique anchor identifier and contains an explicit `"role"` attribute.
#### JSON Data Layout
```json
{
"version": "1.0",
"prop_name": "Arcane Staff",
"anchors": {
"origin": {
"role": "pivot",
"x": 0.0,
"y": 0.0
},
"primary_grip": {
"role": "grip",
"x": 0.0,
"y": 18.0,
"rotation_deg": 0.0,
"z_index_offset": 1,
"allowed_hands": "both"
},
"secondary_grip": {
"role": "grip",
"x": 0.0,
"y": -12.0,
"rotation_deg": 0.0,
"z_index_offset": 1,
"allowed_hands": "left_only"
},
"crystal_head": {
"role": "emitter",
"x": 0.0,
"y": -48.0,
"rotation_deg": -90.0
}
}
}
```
#### Key Parameter Schema
- **`role`**: Enforced category enum (`pivot`, `grip`, `emitter`, `impact`, `mount`, `custom`) defining visual gizmo styling and runtime behavior.
- **`x`, `y**`: Pixel offset relative to the prop’s root origin point (0,0).
- **`rotation_deg`**: Angular rotation applied to the prop geometry when attached, aligning prop handles seamlessly with a stickman’s hand/wrist joint.
- **`z_index_offset`**: Integer sorting shift (e.g., -1 or +1) controlling whether prop elements render behind or in front of the holding hand.
- **`allowed_hands`**: Hand access permission (`both`, `left_only`, `right_only`) used during character interaction checks.
#### Standard Anchor Role Registry
- **`pivot`**: The mandatory root origin (0,0) used for spawning, ground collision, terrain placement, and linear/angular center-of-mass physics when thrown.
- **`grip`**: Hand attachment targets for primary or secondary holding locations.
- **`emitter`**: Action output coordinates for spell particles, flashlights/light cones, projectile spawns, spray nozzles, or laser beams.
- **`impact`**: Point-of-contact coordinates for melee striking edges, spear points, or hammer heads.
- **`mount`**: Auxiliary attachment locations for modular accessories, scopes, or vehicle mounts.
---
### 2. UI Integration & 'Attach' Mode Setup
#### Segmented Mode Switcher
- Expand the canvas toolbar mode control to three tabs: **`[ Arrange | Reshape | Attach ]`**.
- Selecting **Attach** hides shape vertex handles and activates the anchor editing overlay.
#### Attach Mode Toolbar
- **Show Hand Overlay**: Toggle button to display/hide the semi-transparent stickman lower-arm and hand preview.
- **Snap to Grid**: Toggle button for snapping anchor translation coordinates.
- **Show Inactive Gizmos**: Toggle visibility for non-selected background anchors.
#### Anchor Management Panel (Sidebar Inspector)
- **Anchor List Control**:
- Scrollable list showing all defined anchors with **Add Anchor**, **Delete**, and **Rename** buttons.
- Ability to add dynamic custom anchors and assign them any standard role.
- **Property Fields**:
- **Role Selector**: Dropdown menu (`Pivot`, `Grip`, `Emitter`, `Impact`, `Mount`, `Custom`).
- **Position Inputs**: Numeric fields for X and Y coordinates with arrow incrementors.
- **Rotation Slider**: Angle adjustment (-180° to +180°) with numeric spinbox.
- **Z-Index Offset**: Integer field (-5 to +5) for layer sorting relative to hands.
- **Allowed Hands**: Dropdown (`Both`, `Left Only`, `Right Only`).
- **Quick Preset Generator**: Buttons to instantly apply standard layouts (e.g., _One-Handed Grip_, _Two-Handed Weapon_, _Tool / Extinguisher Nozzle_, _Melee Edge_).
---
### 3. Canvas Visual Tools & Interactive Editing
#### Role-Based Gizmo Markers
Each anchor role renders with a distinct visual icon and color code on the canvas:
- **`pivot`**: Yellow Crosshair (locked to or editable near origin).
- **`grip`**: Cyan / Orange Hand Icon (primary vs. secondary grips).
- **`emitter`**: Red Target Reticle (shows direction vector arrow for particle/beam orientation).
- **`impact`**: Bright Pink Diamond / Slash Marker (indicates tip or edge).
- **`mount`**: Purple Ring Anchor.
#### Canvas Interaction Mechanics
- **Translation Dragging**: Click and drag any anchor gizmo across the viewport with full grid-snap support.
- **Rotation Arc Ring**: Selecting a `grip` or `emitter` anchor displays a surrounding rotation ring handle. Dragging the ring dynamically dials in `rotation_deg` while updating the live inspector readout.
#### Stickman Hand Preview Silhouette Overlay
- **Visual Representation**: When enabled, draws a semi-transparent stickman lower-arm and hand silhouette attached directly to the currently selected `grip` anchor.
- **Real-Time Alignment**:
- The arm and hand rotate dynamically in response to `rotation_deg` adjustments.
- The preview respects `z_index_offset`, rendering the hand over or under prop geometry to give immediate visual feedback.
- Guarantees sword hilts, briefcase handles, and tool grips fit stickman hands before running gameplay tests.
---
### 4. Rig Compatibility & Runtime Behavior (`StickmanRig`)
- **IK Hand Attachment**:
- `StickmanRig` queries the prop's `grip` anchors during pick-up or equip actions, snapping the wrist joint of `LeftLowerArm` or `RightLowerArm` to the exact anchor location.
- **Facing Profile Mirroring**:
- When a character flips (e.g., facing left with `scale.x = -1`), the prop mirrors along with the rig.
- Symmetrical anchor positioning ensures tools and weapons flip naturally without offset errors.
- **Action & Effect Dispatching**:
- When firing, casting, or emitting light, runtime scripts search for anchors with the `emitter` role to instantiate particles, light nodes, or hitscan traces.
- Melee collision scripts query `impact` anchors to detect high-velocity hit points.
- **Throwing Physics**:
- Dropping or throwing a prop applies linear/angular velocity calculations relative to the `pivot` anchor position.
---
### 5. Implementation Roadmap
1. **Anchor Data Model & JSON Updates**:
- Refactor internal dictionary parsers to support role-based dynamic key schemas.
- Update `.prop` saving and loading procedures to serialize custom anchor entries and their `role` tags.
2. **UI Switcher & Sidebar Panel**:
- Add the `Attach` tab to the primary mode control `[ Arrange | Reshape | Attach ]`.
- Build the Anchor Management sidebar panel with role selectors, dynamic list controls, and property spinboxes.
3. **Canvas Rendering & Gizmo Input**:
- Implement canvas drawing routines for role-specific gizmos (`pivot`, `grip`, `emitter`, `impact`, `mount`).
- Add gizmo click-picking, drag translation, and rotation ring interaction handles.
4. **Hand Preview Silhouette System**:
- Create the semi-transparent lower-arm and hand preview overlay attached to `grip` anchors.
- Connect real-time rotation and Z-order sorting updates to the overlay renderer.
5. **Autosave, Thumbnail & Runtime Verification**:
- Hook anchor adjustments into editor dirty-state tracking, undo/redo stacks, and autosave routines.
- Verify exported `.prop` files in the asset selector preview and validate runtime IK snapping on the test stage.
+5 -5
View File
@@ -51,7 +51,7 @@ Changing the profile WILL change the context menu item for each individual joint
## Task 2: Body part z-order
**Implemented (Phase 9 Task 2)** — see `docs/phase9_task2_zorder_spec.md`; the harness reorders the rig's `Body/*` children from the Facing profile (test harness top-bar "Facing" menu).
**Implemented (Phase 9 Task 2)** — see `docs/specs/phase9_task2_zorder_spec.md`; the harness reorders the rig's `Body/*` children from the Facing profile (test harness top-bar "Facing" menu).
When the stickman is facing certain directions, body parts need to be drawn in a certain order:
@@ -63,7 +63,7 @@ When the stickman is facing certain directions, body parts need to be drawn in a
## Task 3: Coordinates display
**Implemented (Phase 9 Task 3)** — see `docs/phase9_task3_coords_spec.md`; the harness adds a
**Implemented (Phase 9 Task 3)** — see `docs/specs/phase9_task3_coords_spec.md`; the harness adds a
"Show Coords" checkbox (top bar, default ON) and a code-built top-right readout panel
(`_coords_panel` + monospace `_coords_label`) showing, per frame, the Skeleton2D position and
rotation, all 10 bone positions and rotations, and the 6 IK target positions (world-space
@@ -98,8 +98,8 @@ The display should be easy to read to the right side of the screen. There should
## Task 4: Refactor facing direction / bone bend
**Implemented (Phase 9 Task 4)** — see `docs/phase9_task4_refactor_spec.md`; a new
`scripts/stickman_rig.gd` (`class_name StickmanRig`, attached to the `master_rig.tscn` root)
**Implemented (Phase 9 Task 4)** — see `docs/specs/phase9_task4_refactor_spec.md`; a new
`scripts/characters/stickman/stickman_rig.gd` (`class_name StickmanRig`, attached to the `master_rig.tscn` root)
now owns the facing preset, the four per-joint TwoBoneIK bend flags, and the `Body/*` z-order
as exported controls; the test harness became a thin driver that reads/writes the rig script.
@@ -108,7 +108,7 @@ So rather than keeping that data in the harness, we need it in the master rig. T
## Task 5: Rig animation
**Implemented (Phase 9 Task 5)** — see `docs/phase9_task5_animation_spec.md`; the harness adds a dropdown, play/pause/resume, stop, and loop controls that drive the rig's `AnimationPlayer` directly.
**Implemented (Phase 9 Task 5)** — see `docs/specs/phase9_task5_animation_spec.md`; the harness adds a dropdown, play/pause/resume, stop, and loop controls that drive the rig's `AnimationPlayer` directly.
Both AnimationPlayer and AnimationTree has been added to the master_rig.tscn. Animations should be selectable and playable in the harness.
+639
View File
@@ -0,0 +1,639 @@
# Prop Editor User Guide
The Prop Editor is a standalone tool for making vector props and saving them as
`.prop` files. It is separate from the main Stickman Editor and Sandbox Stage.
This guide describes the current controls and provides a complete manual test
checklist.
## Start the editor
1. Open `res://scenes/editors/prop/prop_editor.tscn` in the Godot editor.
2. Press **F6** to run the current scene.
The Prop Editor is not the project's configured main scene. If F6 runs a
different scene, open `prop_editor.tscn` first.
## Layout
The window is divided into these areas:
| Area | Purpose |
| --- | --- |
| `📁 File` and `👁️ View` | File operations and canvas view reset |
| `Grid` and `Snap` | Shared grid spacing and snapping settings |
| `Prop Name` | The name stored in the `.prop` file |
| Mode bar | Switch between `🖐️ Arrange`, `✏️ Reshape`, and `📌 Attach` |
| Arrange controls | Whole-prop transform, normalization, centering, and the stickman guide |
| Authoring canvas | Draw, select, transform, and reshape the prop |
| Inspector | Material, shape, primitive, and anchor controls |
| Holding Preview | A private stickman-and-prop preview shown in Attach mode |
| Status bar | Current prop/preset/dirty state and cursor coordinates |
There are two adjustable splitters:
- Drag the main splitter between the canvas and Inspector to make the Inspector
wider or narrower.
- In Attach mode, drag the splitter between the authoring canvas and Holding
Preview in either direction. The preview keeps a minimum width so its
controls remain usable.
The Inspector starts at its minimum usable width so the canvas has room to
work. Drag the canvas/Inspector splitter to widen it when labels or controls
need more space. Its background is the theme color `#262C39`, intentionally
darker than the canvas; the Inspector also scrolls vertically on smaller
windows.
The Inspector scrolls vertically. On a small window, widen the Inspector or
scroll it rather than expecting every section to fit at once.
## File and View menus
### `📁 File`
| Command | What it does |
| --- | --- |
| `🆕 New` | Clears the current prop and starts a new document. If the current document is dirty, asks whether to save first. |
| `💾 Save` | Saves to the current path. If the prop has never been saved, opens Save As. |
| `📄 Save As...` | Opens the save dialog and changes the current path to the new file. |
| `📂 Load...` | Opens a `.prop` file. A successful load replaces the current document. |
| `🕑 Recent` | Shows up to five most-recently saved or loaded prop paths. Missing files are removed from this list. |
Save dialogs use the `.prop` filter. Save As normalizes a filename so it ends
in `.prop` even if that extension was omitted. Keeping authored props under
`res://content/props/` is recommended.
### `👁️ View`
| Command | What it does |
| --- | --- |
| `🔍 Reset View` | Resets the authoring canvas zoom and pan to 1x, with the local origin centered in view. It does not reset anchor values or the Holding Preview camera. |
The Holding Preview has its own `Reset View` button; see [Holding Preview](#holding-preview).
### Top-right canvas controls
| Control | What it does |
| --- | --- |
| `Grid` | Sets the visible grid spacing and the snap increment, from 1 to 512. |
| `Snap` | Enables or disables snapping for shape transforms and anchor movement. |
## Prop name and modes
### Prop Name
Type a name in the `Prop Name` field. The name is saved in the file and marks
the document dirty as soon as it changes. An empty name is displayed as
`Unnamed Prop` in the window title/status, and as `Untitled` in the generated
window title when no name is present.
### `🖐️ Arrange`
Arrange is the mode for selecting, moving, rotating, scaling, mirroring, and
ordering complete shapes. The Add Shape inspector and the shape Erase button
are available in this mode.
#### Arrange controls
The Arrange mode top bar keeps the whole-prop actions in this order:
1. `✥ Transform Whole Prop`
2. `📏 Normalize Size`
3. `🎯 Center Prop`
4. `Show Stickman Guide`
The scale textbox and Apply Scale button are not part of the editor. Use
Normalize Size for the standard document size, or use Transform Whole Prop for
direct canvas manipulation. Normalize Size fits the shape-point bounds to a
96 px maximum dimension while preserving the fixed origin and proportional
anchor offsets. Center Prop separately moves the shape bounds and editable
anchors around the canonical origin. It is a deliberate one-time command: the
editor does not recenter automatically after drawing, reshaping, or moving a
shape, and empty or already centered props are unchanged.
With Transform Whole Prop enabled, drag visible shape geometry to move the
whole prop, drag the dot below the union bounds to rotate it around the
canonical origin, or drag a corner cross to resize it uniformly. Snap rounds
the move delta to the current grid. Hold **Ctrl** while rotating to snap to
15-degree increments. Grip and other editable anchor markers follow each
operation. The tool previews from the starting document and commits once when
the gesture is released; **Esc** cancels the active preview. The canonical
origin itself does not move.
Transform Whole Prop and Normalize Size are hidden in Reshape and Attach.
Center Prop and Show Stickman Guide remain available in those modes. Turning
Transform Whole Prop off restores the selected shape and normal per-shape
Arrange gizmos.
When a shape is selected, the canvas draws:
- a two-tone selection outline and bounding box;
- a small rotation dot below the bounding-box center;
- four corner scale crosses.
Drag the shape body to translate it. Drag the rotation dot to rotate it. Hold
**Ctrl** while rotating to snap to 15-degree increments. Drag a corner cross
to scale. Hold **Ctrl** while scaling to keep the aspect ratio.
Clicking a shape selects the frontmost shape under the cursor. With the canvas
selected (rather than a text field), **Delete** or **Backspace** also removes
the selected shape.
### `✏️ Reshape`
Reshape is for editing individual vertices. It does not move the whole shape
and does not show the Add Shape section, Erase Shape button, Normalize Size,
or Transform Whole Prop. Center Prop and Show Stickman Guide remain available.
When the canvas
is empty, it does not show the Arrange-only “Right-click to add a shape” hint.
- Original vertices are filled circles.
- User-created vertices are hollow rectangles.
- Drag a vertex to reshape the outline.
- A vertex drag, insertion, or removal marks the shape as reshaped.
- Releasing a vertex drag sanitizes polygon/line points (including grid snap
when enabled, collinear cleanup, and winding cleanup).
- A reshaped circle hides its Radius control and shows `✏️ This shape was
reshaped`. Choosing a new radius regenerates a clean circle and clears that
reshaped state.
### `📌 Attach`
Attach is for authoring named anchors and checking a selected grip on the
private stickman Holding Preview. It does not add gameplay attachment or
physics behavior to Sandbox Stage.
Attach mode shows the Attach toolbar and the Anchors inspector. Shape editing
controls are hidden, as are Normalize Size and Transform Whole Prop. Center Prop
and Show Stickman Guide remain available, and the prop remains visible on the
authoring canvas.
## Grid, Snap, and canvas navigation
The top-right `Grid:` SpinBox accepts values from 1 to 512. It controls the
drawn grid spacing and the snap increment. The `Snap` checkbox enables or
disables snapping for canvas transforms and anchor movement.
In Attach mode, the top `Snap` checkbox also controls anchor movement. There is
no separate anchor snapping control. The grid is still available while
snapping is off; it is only a visual guide in that state.
The authoring canvas supports these gestures in every mode:
| Gesture | Result |
| --- | --- |
| Mouse wheel up/down | Cursor-centered zoom in/out, clamped to 0.3x–3.0x |
| Pinch/magnify gesture | Cursor-centered zoom |
| Middle-mouse drag | Pan the canvas |
| Two-finger pan gesture | Pan the canvas |
| Left-click | Mode-dependent selection or editing |
| Right-click | Mode-dependent context menu |
The bottom-right status text shows `X` and `Y` world coordinates while the
cursor is over the authoring canvas. It shows dashes elsewhere.
## Authoring canvas context menus
### Arrange right-click menu
Right-clicking a shape selects the frontmost shape under the cursor and shows:
| Action | Result |
| --- | --- |
| `Copy` | Copies the selected shape into the editor clipboard. |
| `Paste` | Appears when the clipboard contains a shape; inserts a copy offset from the cursor. |
| `Erase` | Removes the selected shape. |
| `Flip Left-Right` | Mirrors the shape around its own bounding-box center horizontally. |
| `Flip Up-Down` | Mirrors the shape around its own bounding-box center vertically. |
| `Bring Forward` | Moves the shape one step toward the front of the drawing stack. |
| `Send Back` | Moves the shape one step toward the back of the drawing stack. |
Right-clicking empty canvas shows:
`Add Box`, `Add Plank`, `Add Triangle`, `Add Line`, and `Add Circle`.
`Paste` is also available there when the clipboard is populated. New shapes
are placed at the clicked world position and become selected.
### Reshape right-click menu
Reshape intentionally has a smaller menu:
| Where you right-click | Result |
| --- | --- |
| A vertex | `Remove Point` |
| Near an outline segment | `Create Point`; the new point is inserted at the cursor's projection onto that segment and flashes briefly in yellow |
| Inside a closed shape | Selects the shape; no menu is shown |
| Empty canvas | Nothing; shapes cannot be added from this mode |
Removing a point from a shape with fewer than two remaining vertices removes
the shape. A two-point polygon becomes an open line.
### Attach right-click behavior
Right-click does not open a shape menu in Attach mode. Select and edit anchors
with left-click, the Attach inspector, or the anchor presets.
## Material and shape Inspector
The Inspector is scrollable and changes with the active mode and selection.
### Material
The Material section is hidden in Attach mode to keep the anchor controls and
Holding Preview readable. Return to Arrange or Reshape to edit the material.
The material preset dropdown contains `None`, `Wood`, `Rubber`, `Cardboard`,
and `Metal`. The preset chooses the default colors used when a new primitive
is added and displays read-only physics information:
- `Mass`
- `Friction`
- `Bounce`
These readouts come from the prop physics tables. They are informational in
the Prop Editor; changing the dropdown does not turn the authoring canvas into
a physics simulation. Save after changing the preset so the new material is
stored in the `.prop` file. The current implementation updates the preset and
readouts immediately; a preset-only change may not show `• Modified` until
another dirty-tracked edit is made.
### Selected Shape
This section appears when a shape is selected in Arrange or Reshape mode.
| Control | What it does |
| --- | --- |
| `Fill` color picker | Changes the selected shape's fill color live. |
| `Outline` color picker | Changes the selected shape's outline color live. |
| `Width` SpinBox | Changes outline width. |
| `Radius` SpinBox | Changes a clean circle's radius and regenerates its points. |
| `Vertices` | Read-only count of the selected shape's points. |
| `Erase Shape` | Deletes the selected shape. It is hidden in Reshape mode. |
The Radius row is only shown for a circle that has not been reshaped. Editing
circle vertices or scaling it non-uniformly changes it into a freeform shape;
the muted reshaped note explains why Radius is unavailable.
### Add Shape
The Add Shape section is available in Arrange mode. Choose a primitive from
the dropdown, adjust its parameters, then press `Add a Shape`.
| Primitive | Parameters |
| --- | --- |
| `Box` | `Size X`, `Size Y` |
| `Plank` | `Length`, `Thickness` |
| `Triangle` | `Base`, `Height` |
| `Line` | `Length` |
| `Circle` | `Radius` |
The new primitive uses the current material's default fill/outline tint and
outline width. `Ball` is not shown in the current menu; Circle is the supported
round primitive in the user interface.
## Anchors and Attach mode
### Anchor concepts
An anchor is a named point in prop-local coordinates. Anchors are saved with
the `.prop` file alongside the shape stack.
Every prop always has one fixed anchor named `origin`:
- role: `pivot`;
- position: `(0, 0)`;
- rotation: `0` degrees;
- Z offset: `0`;
- allowed hands: `both`.
Origin cannot be moved, rotated, renamed, or deleted, and it is not listed as
an editable anchor. A subtle, non-interactive center marker is drawn at the
fixed local origin in Arrange, Reshape, and Attach. It is a canvas reference,
not an anchor gizmo that can be selected.
If an older `.prop` file has no `anchors` section, the loader synthesizes this
origin automatically. Saving that file writes the anchor section back out.
### Attach toolbar
| Control | What it does |
| --- | --- |
| `Show Stickman Preview` | Shows or hides the private Holding Preview pane. |
| `Show Other Markers` | Shows or hides the passive origin marker and unselected anchor markers. The selected anchor remains visible. |
The Holding Preview is enabled by default when Attach mode is entered.
### Anchors list and buttons
The list shows every editable custom anchor. `origin` is intentionally hidden
from this list because it is fixed and cannot be edited.
| Control | What it does |
| --- | --- |
| `Add Anchor` | Creates a normalized ID such as `anchor`, places it at the current shape bounds center, and gives it role `custom` with both hands allowed. |
| `Rename` | Opens a text-entry dialog. Type a new name, then click `Rename` or press **Enter** to confirm; click Cancel or close the dialog to leave the name unchanged. |
| `Delete` | Deletes the selected editable anchor. |
Anchor IDs are lower-case, normalized to identifier-safe characters, limited
to 32 characters, and made unique with a suffix such as `_2` when necessary.
The name is committed only after the rename dialog is confirmed.
### Anchor fields
| Field | Values and behavior |
| --- | --- |
| `Role` | `grip`, `emitter`, `impact`, `mount`, or `custom`. `pivot` is reserved for the hidden fixed origin and is not offered for editable anchors. |
| `Allowed Hands` | `both`, `left_only`, or `right_only`. This affects Holding Preview compatibility. |
| `X`, `Y` | Anchor position in prop-local coordinates. |
| `Rotation` | Anchor rotation in degrees, wrapped to the range -180 through 179. |
| `Z Offset` | Draw-order offset from -5 through 5. |
The X/Y fields can also be edited by dragging the selected marker. For grip
and emitter anchors, the selected marker has a small rotation dot underneath
it. Drag that dot to rotate the anchor; hold **Ctrl** to snap rotation to
15-degree increments. The origin has no rotation dot.
Anchor marker colors identify roles: cyan for grip, red for emitter, pink
diamond for impact, purple for mount, and light gray for custom. The selected
marker has an additional yellow selection ring. The fixed origin uses its own
subtle center marker rather than an editable anchor marker.
### Anchor presets
Presets replace the current non-origin anchor layout and select the first
editable grip:
| Preset | Anchors created |
| --- | --- |
| `One-Handed` | One `primary_grip` |
| `Two-Handed` | `primary_grip` and `secondary_grip` |
| `Tool / Nozzle` | `primary_grip` and `emitter` |
| `Melee Edge` | `primary_grip` and `impact` |
Preset positions are derived from the current shape bounds. Applying a preset
is an edit and must be saved like any other change.
## Holding Preview
Holding Preview appears in Attach mode when `Show Stickman Preview` is on. It
uses a private visual-only rig and displays the complete authored multi-shape
prop when the selected anchor is a grip compatible with the selected hand. The
preview pane is borderless and visually separated from the Inspector by the
layout splitters.
If the selected anchor is not a compatible grip, the prop is hidden and the
hint explains that a compatible grip must be selected. Select a grip in the
Anchors list to see the prop held.
### Preview header controls
| Control | What it does |
| --- | --- |
| `Left Hand` / `Right Hand` | Chooses which hand holds the selected grip. A left-only or right-only grip disables the incompatible choice and forces the compatible hand. |
| `Front Pose` / `Walk Left` / `Walk Right` | Chooses the still front-facing pose or an in-place walking direction. Walking mirrors the preview as needed without changing saved anchor data. |
### Preview footer controls
| Control | What it does |
| --- | --- |
| `Pause Walk` / `Resume Walk` | Pauses or resumes the selected walking animation. The button is disabled for Front Pose. |
| `Reset Arm` | Pauses walking and resets the selected arm's manual IK override. In Front Pose, it returns both preview hand targets to their rest positions. |
| `Reset View` | Reframes the camera around the complete stickman and visible prop. |
### Pose and camera gestures
- Drag the cyan diamond target in the preview viewport to pose the selected
arm. Dragging automatically pauses walking; use Resume Walk to return to the
walk animation.
- The target is clamped to the arm's reachable range, so the IK solver does
not request an impossible wrist position.
- The prop follows the solved wrist and selected grip anchor, including the
anchor's rotation and Z offset.
- Mouse wheel zooms the preview camera around the cursor.
- Middle-mouse drag pans the preview camera.
The preview is for visual authoring only. It does not create a gameplay
`PropBlock`, collision bodies, physics, Sandbox actions, or runtime attachment
state. It currently previews one selected grip and one hand; secondary grips
are saved, but they do not drive a second-hand IK solver.
## Saving, dirty state, and recovery
### Saving an off-origin prop
The editor does not silently recenter a prop. On an explicit Save or Save As,
it warns when the combined shape-bounds center is farther from the origin than
`max(1024 px, 2 × bounds diagonal)`. Choose one of:
| Choice | Result |
| --- | --- |
| `🎯 Center & Save` | Translates all shapes and non-origin anchors together, keeps the fixed origin at `(0, 0)`, focuses the canvas on it, then saves. |
| `💾 Save Anyway` | Saves the current positions unchanged. |
| `❌ Cancel` | Leaves the document and save flow unchanged. |
Autosaves never show this warning and never recenter the prop.
The editor marks the document dirty after changes to the name, shapes, or
anchors. Save after changing the material preset as well; a preset-only change
updates the current model but may not show `• Modified` until another
dirty-tracked edit is made. The status bar shows:
`Prop Name — Material • Modified`
The window title also adds `*` while dirty and includes the current filename
when one exists.
After about one second without further edits, a dirty document is written to
`user://prop_editor_autosave.prop`. This is an internal recovery file, not the
normal saved prop path.
- A successful Save, Load, New, or discard clears the autosave.
- If the editor starts with a usable unsaved autosave, it asks whether to
`🔄 Restore` or `🗑️ Discard` it.
- Restoring keeps the document dirty so it still requires an intentional Save.
- An autosave is silently discarded when its saved source file is already
newer or equal.
- The recent list is stored in
`user://prop_editor_settings.json` and contains at most five paths,
newest first.
Closing the window or choosing New while dirty opens a prompt with `💾 Save`,
`🗑️ Discard`, and `❌ Cancel`. Cancel leaves the document open. Save As and
Load dialogs report malformed or unsupported files through the Error dialog.
## Current limitations and future scope
- Attach mode and Holding Preview are Prop Editor authoring tools only.
Sandbox Stage custom-prop loading, equipping, releasing, physics, collision,
and gameplay effects are future work.
- Preview uses one selected grip and one-hand IK. Two-hand IK, emitter effects,
impact behavior, mount behavior, throwing, and runtime lifecycle are not
implemented here.
- The preview can show the full multi-shape authored prop. Existing runtime
prop spawning still uses a deterministic single-shape conversion: the
frontmost closed polygon, otherwise a circle, otherwise the first shape.
Lines are editor-only and do not become collision geometry in that path.
- There is no document-wide Undo/Redo history yet. Autosave and recovery are
safety features, not an edit history.
- The shape clipboard is an in-memory editor clipboard; it is cleared when a
new document is started and is not saved into `.prop` files.
## Complete manual test checklist
### Launch and layout
- [ ] Open `res://scenes/editors/prop/prop_editor.tscn` and press F6.
- [ ] Confirm the File/View menus, prop-name field, mode buttons, canvas,
Inspector, and status bar are visible.
- [ ] Drag the main canvas/Inspector splitter left and right.
- [ ] Make the window narrow; confirm Inspector content can be scrolled and
controls do not run off the window.
- [ ] Confirm the Inspector starts at its minimum usable width and has a
darker contrasting background than the canvas.
- [ ] Confirm the top `Grid` field and `Snap` button have no clipped top or
bottom edges.
- [ ] Enter Attach mode and drag the canvas/preview splitter both directions.
- [ ] Open `eye.prop` in Arrange mode, switch to Attach, and confirm the
preview does not push into the Prop Name row or bottom status bar.
- [ ] Toggle `Show Stickman Preview` and confirm the preview pane appears and
disappears without affecting the prop document.
### New, naming, material, and status
- [ ] Choose File → New and confirm the canvas is empty except for the subtle
fixed-origin marker visible in all three modes.
- [ ] Type a prop name; confirm the status shows `• Modified` and the window
title gets `*`.
- [ ] Select each material preset and confirm the Mass, Friction, and Bounce
readouts update.
- [ ] Enter Attach mode and confirm the Material section is hidden.
- [ ] Confirm the physics readouts are informational and do not launch a
simulation.
### Add and style shapes
- [ ] In Arrange mode, add a Box, Plank, Triangle, Line, and Circle from the
Add Shape section.
- [ ] Change the primitive parameters before adding each shape.
- [ ] Right-click empty canvas and add each of the same five primitives.
- [ ] Select a shape and change Fill, Outline, and Width; confirm the canvas
updates immediately.
- [ ] Confirm Vertices reports the selected shape's point count.
- [ ] Click Erase Shape and confirm only the selected shape is removed.
### Arrange transforms and context menu
- [ ] Confirm the Arrange top bar order is Transform Whole Prop, Normalize Size,
Center Prop, then Show Stickman Guide.
- [ ] Drag a selected shape body to translate it.
- [ ] Drag the rotation dot and confirm the shape rotates continuously.
- [ ] Hold Ctrl while rotating and confirm 15-degree stepping.
- [ ] Drag each scale cross; hold Ctrl and confirm uniform scaling.
- [ ] Enable Transform Whole Prop; drag visible geometry, the rotation dot, and
each corner cross, confirming that all shapes and editable anchors follow.
- [ ] With Snap enabled, move the whole prop and confirm the move delta follows
the current grid; hold Ctrl while rotating and confirm 15-degree snapping.
- [ ] Press Esc during a whole-prop gesture and confirm the preview cancels
without changing the document.
- [ ] Switch to Reshape or Attach and confirm Transform Whole Prop and
Normalize Size are hidden while Center Prop and Show Stickman Guide remain.
- [ ] Use Copy, right-click empty canvas, and Paste; confirm the pasted shape
is offset and selected.
- [ ] Test Flip Left-Right and Flip Up-Down.
- [ ] Stack two or more shapes and test Bring Forward and Send Back.
- [ ] Confirm the two-tone selection outline, bounding box, dot, and crosses
remain visible over light and dark shapes.
### Reshape and navigation
- [ ] Switch to Reshape and confirm Add Shape and Erase Shape are hidden.
- [ ] Drag an original vertex and confirm the outline changes.
- [ ] Right-click a segment and choose Create Point; confirm a hollow vertex
appears at the cursor's projected segment position and flashes yellow.
- [ ] Right-click a vertex and choose Remove Point.
- [ ] Right-click the interior of a closed shape and confirm it selects without
opening a menu.
- [ ] Right-click empty canvas and confirm no menu appears.
- [ ] Reshape a circle and confirm Radius is replaced by the reshaped note.
- [ ] Change Radius after reshaping and confirm a clean circle and Radius row
return.
- [ ] Test wheel zoom, pinch/pan if available, and middle-mouse pan. Use View
→ Reset View and confirm only canvas pan/zoom resets.
### Anchors
- [ ] Switch to Attach and confirm the Attach toolbar and Anchors inspector
appear.
- [ ] Confirm the fixed origin marker is visible in Arrange, Reshape, and
Attach, has no text label, and cannot intercept clicks.
- [ ] Confirm `origin` is not in the editable Anchors list.
- [ ] Add an anchor and confirm it appears in the list at the shape-bounds
center with role `custom`.
- [ ] Rename it with a normalized name; confirm both clicking Rename and
pressing **Enter** commit the change, while Cancel leaves the old name.
- [ ] Add another anchor and confirm IDs remain normalized and unique.
- [ ] Delete a non-origin anchor and confirm it is removed.
- [ ] Test every Role and Allowed Hands option.
- [ ] Confirm Role contains grip, emitter, impact, mount, and custom, but not
pivot.
- [ ] Edit X, Y, Rotation, and Z Offset in the Inspector and confirm the marker
and preview update.
- [ ] Drag a selected anchor marker. Toggle Snap and confirm movement follows
the Grid increment.
- [ ] For a grip or emitter, drag the rotation dot and test Ctrl 15-degree
snapping. Confirm the origin marker has no rotation dot.
- [ ] Toggle Show Other Markers and confirm the origin marker and unselected
markers hide/show while the selected anchor remains visible.
- [ ] Apply each preset and confirm its documented anchors appear.
- [ ] Draw or move a prop far from the origin, click Center Prop, and confirm
shapes and anchors translate together while the prop's relative layout stays
unchanged and the canvas focuses on the center marker.
### Holding Preview and IK
- [ ] Select a grip anchor and confirm the complete authored prop appears in
Holding Preview, not only the cyan target marker.
- [ ] Select a non-grip role and confirm the prop hides with the compatibility
hint.
- [ ] Confirm the hand choices are labeled Left Hand and Right Hand. Set a grip
to left-only and right-only and
confirm the incompatible hand is disabled/forced away.
- [ ] Test Front Pose, Walk Left, and Walk Right. Confirm walking previews move
in place and do not change saved anchor X/rotation values.
- [ ] Drag the cyan target and confirm the arm bends with IK and the prop
follows the solved wrist.
- [ ] Confirm dragging the target pauses walking, then click Resume Walk and
confirm the selected walk resumes.
- [ ] Click Pause Walk and Resume Walk and confirm the button text and motion
state change. Confirm Pause Walk is disabled for Front Pose.
- [ ] Click Reset Arm and confirm walking pauses and the selected arm's manual
IK override resets. In Front Pose, confirm both hand targets return to rest.
- [ ] Wheel-zoom and middle-drag the preview camera.
- [ ] Click Reset View and confirm the complete stickman and visible prop are
framed.
- [ ] Confirm the preview has no gameplay physics or collision response.
### Save, load, recent, autosave, and compatibility
- [ ] Save As with and without typing `.prop`; confirm the final file has one
`.prop` extension.
- [ ] Reload the saved file and confirm name, material, all shapes, colors,
transforms, and anchors survive.
- [ ] Confirm a successful save clears the dirty indicator and updates Recent.
- [ ] Load through File → Recent and confirm the same document returns.
- [ ] Save/load enough files to verify Recent keeps only five, newest first.
- [ ] Move a prop far from the origin and use Save; verify the off-origin
warning offers Center & Save, Save Anyway, and Cancel, and that each choice
has the documented result.
- [ ] Remove or rename a recent file outside the editor; open Recent and
confirm the missing entry is pruned.
- [ ] Make an edit, wait at least one second, close/restart the editor, and
test Restore and Discard in the recovery dialog.
- [ ] Confirm Restore keeps the prop dirty and Discard clears the recovery
file.
- [ ] Test File → New and window close while dirty; verify Save, Discard, and
Cancel each do the expected thing.
- [ ] Load a `.prop` file that has no `anchors` key. Confirm Attach mode
synthesizes the fixed origin, then save and verify anchors are written.
- [ ] Load malformed JSON or an unsupported `version` and confirm the Error
dialog appears without replacing the current valid document.
+51
View File
@@ -0,0 +1,51 @@
# Feature Backlog
This is the live list of future Stickman Studio feature ideas. Designer maintains it as ideas become clear in discussion with the user.
## Suggested order
Priorities are recommendations for discussion, not a committed release schedule. Keep higher-priority items first and explain the reason for each ranking.
1. **Move To with selectable movement styles** — **Status:** Verified · **Suggested priority:** High. **Intent:** Give a stickman one destination action with a per-action style choice, starting with Walk (default) and Run; leave room for future styles like Fly. Use a fixed pace for each style initially, matched to its animation. **Rationale:** Children can direct the same route at different paces without choosing duplicate action types. The first run cycle should read as a believable human gait on the existing stickman rig, with clear foot contact and push-off, a flight phase, alternating arm swing, and cadence matched to travel speed. **Connections:** [Phase 16 design brief](../plans/phase_16_running_stickman.md), `scripts/stage/action_registry.gd`, `scripts/editors/stage/action_editor.gd`, `scripts/stage/stage_scene_io.gd`, `scripts/characters/stickman/stickman_locomotion.gd`, `scripts/tools/create_animations.gd`; existing scenes and rules currently store `walk_to` and should continue as Move To · Walk.
2. **Prop depth and behavior settings** — **Status:** Verified · **Suggested priority:** Medium. **Intent:** Let placed props draw behind or in front of characters, and keep every prop selectable in EDIT even when covered. Dynamic sorting with characters is deferred until the Stage has a clear front/back movement convention. **Rationale:** Children can build layered sets with independently configurable visual depth, physics/support behavior, and interaction. Some props can provide surfaces that support other props while remaining pass-through for characters; others can block characters, move under physics, be picked up, or react to events. These roles should work across prop types rather than depend on one table-and-pot example. New placements start still and use a selected-prop settings card. **Set Down** targets a clicked stage-world release point, alongside free Drop and Throw; physics determines the subsequent landing on existing collision geometry, with no support-surface authoring. Door open/close behavior and scene travel stay in the later doorway feature. **Phase 17a is implemented and user-verified.** See the [Phase 17 — Props for Storytelling plan](../plans/phase_17_props_for_storytelling.md) and approved [17a UI specification](../specs/phase_17a_prop_roles_ui.md). **Connections:** `scripts/props/prop_block.gd`, `scripts/stage/stage_scene_io.gd`, `scripts/stage/stage_spawner.gd`, `scripts/stage/stage_selection.gd`, `scripts/editors/stage/sandbox_stage.gd`, `scripts/characters/stickman/stickman_prop_interactor.gd`, [Phase 11 prop interactions](../specs/phase_11_prop_interactions.md).
3. **Functional doorways and doors** — **Status:** Idea · **Suggested priority:** Medium. **Intent:** Let placed door artwork serve as scenery, an open passage, or a closed wall door that blocks characters until opened; an open passage may optionally link to another scene and arrival point. **Rationale:** Children can reuse door art for different stage roles. Door open/close actions and scene travel add state and depend on the basic prop blocking model and dynamic scene loading; keep this as later work. Whether a doorway activates automatically or through a directed action remains open. **Connections:** `scripts/stage/stage_scene_io.gd`, `scripts/stage/trigger_area.gd`, `scripts/stage/trigger_registry.gd`, `scripts/editors/stage/sandbox_stage.gd`.
4. **Scenery, backgrounds, and vector surface finishes** — **Status:** Planned · **Suggested priority:** Medium. **Plan:** [Phase 19 — Scenery, Backgrounds, and Surface Finishes](../plans/phase_19_scenery_backgrounds_surface_finishes.md). **Intent:** Give the Stage a Scenery category with three distinct uses: a scene-wide Sky background, finite wall panels, and individually placed artwork. One static, detailed Sky preset fills the camera view and remains fixed throughout PLAY; the initial designed presets are Clear Day, Cloudy Day, Sunset, and Starry Night. Skies are adaptive scene themes rather than `.prop` files: the `.scene` saves a stable preset ID, while each preset composes crisp vector gradients, varied clouds or stars, and sun or moon details without visible panel edges or obvious repetition. Wall panels are resizable, colorable rectangles with finishes such as paint, brick, and wood; `.prop` artwork can be placed as inert scenery. Scenery artwork supports simple Far and Near layers so a muted skyline strip can sit behind colorful storefronts, houses, and trees while all scenery remains behind the action. New Scene offers Blank and one polished Town Street starter built from a Cloudy Day Sky, Far skyline, Near storefront/tree scenery, and sidewalk/road Ground; the starter creates an unsaved scene whose ordinary parts remain editable. User-created scenery artwork reuses the existing vector artwork editor and `.prop` format; no separate Scenery Editor or `.scenery` format is needed initially. The Stage placement category, saved in the `.scene`, determines whether the same artwork is inert Scenery or an interactive Prop. Visual floor finishes are chosen through the existing Ground tool and remain walkable terrain; initial outdoor looks include concrete/sidewalk and asphalt/pavement alongside indoor finishes. Wall panels are inert Scenery. Shared finish names, previews, and color controls make the relationship clear without merging their behavior. Choosing a Ground finish affects new placement, while an existing piece changes only when directly edited. Ground alone gains finishes in this version; Ramp and Step retain their current appearance and remain unconstrained for a possible future terrain redesign. The artwork catalog uses optional multi-select tags from a built-in list: Nature, Buildings, Walls, Roads & Paths, and Objects. The same tags may filter both Props and Scenery because they describe the drawing, while the Stage entry point determines behavior. **Rationale:** Children can build outdoor and indoor sets without changing movement or interaction. A scene-wide Sky avoids accidental edges during camera movement, wall panels can build finite rooms over it, and cutout artwork dresses either setting. Curated long vector assets and the editable Town Street starter make a layered outdoor scene immediately achievable without adding a general template system. Reusing the established multi-shape vector authoring workflow avoids duplicate editors, split libraries, and file conversion. Its child-facing presentation may evolve from Prop Editor toward Artwork Editor so prop-only physics and attachment controls do not dominate scenery creation. Scenery objects and wall panels are explicitly inert: they cannot block, support, move, be picked up or used, or participate in events. Sky sits farthest back, wall panels and Far scenery render over it, Near scenery renders over those, and terrain, props, and actors remain in front. Ground patterns align across adjacent painted pieces so a floor or road reads as one continuous surface. A dedicated scenery-editing flow should keep broad or overlapping background art from blocking normal selection. Artwork may appear in several browse categories, while older untagged artwork remains available under All Artwork and Unsorted. Solid walls remain terrain or future static structures, while loose bricks and planks remain props. Individual clouds, suns, or moons may still be `.prop` scenery cutouts, but whole adaptive skies are not. Animated weather, parallax, directed sky changes, user-authored sky themes, custom repeatable finish authoring, a larger starter gallery, inserting starters into existing scenes, and user-authored set templates are future workflows and do not require a scenery asset type now. **Connections:** `scripts/editors/prop/prop_editor.gd`, `scripts/editors/prop/prop_file.gd`, `scripts/props/prop_library.gd`, `scripts/editors/stage/stage_asset_browser.gd`, `scripts/terrain/terrain_block.gd`, `scripts/editors/stage/stage_terrain_painter.gd`, `scripts/stage/stage_scene_io.gd`, `scripts/editors/stage/stage_scene_adapter.gd`, `scripts/stage/stage_selection.gd`, `scripts/editors/stage/sandbox_stage.gd`, [Stage panel UI system](../specs/stage_panel_ui_system.md).
**Sky scope:** Choosing a Sky preset changes only the sky; it does not tint or recolor terrain, props, characters, or other scenery.
**Indoor layering:** In addition to Far and Near backdrop scenery, an **On the floor** scenery placement lets inert rugs and floor markings render over finished Ground but beneath characters and interactive furniture. Ground remains in front of Far/Near scenery; floor decorations do not add collision or intercept ordinary object selection.
**Starter scope:** New Scene offers **Blank Scene**, **Town Street**, and **Living Room**. Living Room combines a painted wall panel, wood Ground, Near wall decorations, an On the floor rug, and furniture Props; all ordinary parts remain editable in the new unsaved scene.
**Built-in content:** The starters require a curated vector asset pack: four Sky themes; repeatable wall/Ground finishes; Town skyline, storefront, and tree scenery; Living Room window, fireplace, clock, shelf/books, pictures, plant, and rug scenery; and couch, armchair, and coffee-table Props. Raster concepts may guide the art, but shipped cutouts use repo-native vector/`.prop` assets and the Sky/finish visuals must remain crisp through Stage zoom.
**Art tracking:** The accepted phase brief must include an asset production manifest with stable IDs, asset class/final format, tags/default layer, starter usage, behavior/collision expectation, source/rights, final location, visual acceptance, validation evidence, and status. Use **Needed → Concept ready → Art approved → Final vector ready → Integrated → Verified** so raster concepts cannot be mistaken for finished vector assets.
**Structural scope:** Multi-level traversal and structural stairways are a separate future feature. House artwork, wall panels, and decorative stairs do not create reliable walkable floors or routes between elevations.
5. **Multi-level walkable scenes** — **Status:** Exploring · **Suggested priority:** Medium. **Intent:** Let children build a two-floor cutaway scene and direct a stickman with Move To from either floor to the other through one walkable stair connection. **Rationale:** This is separate follow-up work after the scenery feature, so navigation and locomotion changes do not delay visual set building. Upstairs/downstairs stories need reliable travel, correct-floor arrival, visible route feedback, and an understandable **No path upstairs** cue when disconnected; visual scenery alone cannot provide this. A narrow first slice uses editable lower and upper solid floors plus one child-facing Stairs connector, supports travel in both directions, and offers a way to frame both floors. The Stage has Ground, Ramp, and Step terrain with short-gap navigation links, but current movement and arrival are largely horizontal, so this needs a separate navigation and locomotion design. Keep Stairs independent from today's Ramp/Step taxonomy so their future redesign remains open. Defer additional floors, ladders, elevators, jumping routes, moving platforms, automatic camera direction, functional doors, and general building generation. **Connections:** `scripts/editors/stage/sandbox_stage.gd`, `scripts/characters/stickman/stickman_locomotion.gd`, `scripts/stage/stage_spawner.gd`, Move To backlog item 1, Scenery backlog item 4, Functional doorways backlog item 3.
6. **Whole-scene visual filters** — **Status:** Idea · **Suggested priority:** Low. **Intent:** Let children give the complete scene a unified look such as sepia or black-and-white. **Rationale:** A filter can signal a memory, old film, or mood across the set and characters together. It is distinct from Sky, which changes only the background. Explore it after the scenery system is established, with special attention to keeping characters, selection cues, and Stage controls readable. **Connections:** `scripts/editors/stage/sandbox_stage.gd`, `scripts/stage/stage_scene_io.gd`, `scripts/editors/stage/stage_scene_adapter.gd`, Scenery backlog item 4.
7. **Use spots on props** — **Status:** Verified · **Suggested priority:** Medium. **Intent:** Let children mark a handle, button, or lever on a placed prop and direct a stickman to use it. **Rationale:** A Use spot is distinct from a grip anchor: the prop stays in place while the stickman reaches to it. Initial direction is an instance-level spot that produces a **Used prop** event after the stickman reaches and completes the interaction. Rules can respond to use of the whole prop or filter for one specific spot; the event identifies the stickman, prop, and spot. Built-in effects such as toggling a light are deferred. The Stage needs a clear standing/reach target, visible EDIT feedback, and an understandable unreachable cue. **Phase 17b is implemented and user-verified.** See the [Phase 17 — Props for Storytelling plan](../plans/phase_17_props_for_storytelling.md). **Connections:** `scripts/props/prop_anchor_data.gd`, `scripts/props/prop_block.gd`, `scripts/characters/stickman/stickman_prop_interactor.gd`, `scripts/stage/action_registry.gd`, `scripts/stage/trigger_registry.gd`.
8. **Additional prop impact reactions** — **Status:** Idea · **Suggested priority:** Medium. **Intent:** Explore toppling or breaking props when they are struck or thrown. **Rationale:** The wobble response in **Phase 17c** is implemented and user-verified; this idea covers further reactions such as toppling or breaking, which remain future options. The wobble implementation is documented in the [Phase 17 plan](../plans/phase_17_props_for_storytelling.md). **Connections:** `scripts/props/prop_block.gd`, `scripts/editors/stage/sandbox_stage.gd`, `scripts/stage/trigger_registry.gd`.
9. **Group props into set pieces** — **Status:** Parked · **Suggested priority:** Low. **Intent:** Let children group objects for organization and moving together within one scene. **Rationale:** Deferred until later at the user's direction. Scene-only groups make arranging furnished sets faster. In PLAY, each item keeps its own physics and interaction; picking up one prop does not move the rest, and stopping PLAY returns the grouped items to their authored arrangement. Reusable set-piece assets are deferred. This is **Phase 17d** in the [Phase 17 — Props for Storytelling plan](../plans/phase_17_props_for_storytelling.md). **Connections:** `scripts/stage/stage_selection.gd`, `scripts/editors/stage/sandbox_stage.gd`, `scripts/stage/stage_scene_io.gd`.
## Entry format
For each feature, record:
- **Status:** Idea, Exploring, Planned, Verified, or Parked
- **Suggested priority:** High, Medium, or Low
- **Intent:** The user goal or experience the feature should support
- **Rationale:** Why it may be valuable and how it fits the project
- **Connections:** Relevant project docs, code, and dependencies, when known
## Historical context
The [archived roadmap](../archive/ROADMAP.md) is historical material, not an active priority list. Revisit individual ideas from it only when they are relevant to a current discussion.
+10 -10
View File
@@ -69,11 +69,11 @@ The guide and joints should slightly 'ghost' in front of the objects so that the
## Stickman editor (Phase 9 Round 1)
> FIXED — 2026 Round 1: implemented per docs/phase9_round1_bugfix_spec.md; anisotropic scaling, joint-based anchors, and node/driver transform neutralization; verified with a 37-assertion headless smoke test.
> FIXED — 2026 Round 1: implemented per docs/specs/phase9_round1_bugfix_spec.md; anisotropic scaling, joint-based anchors, and node/driver transform neutralization; verified with a 37-assertion headless smoke test.
## Stickman editor (Phase 9 Round 2)
> FIXED — 2026 Round 2: implemented per docs/phase9_round2_bugfix_spec.md; restored the `RemoteTransform2D` driver rotations (`update_rotation = true` again) so mounted shapes follow their bones under IK flexing, switched the mount to the rig's hanging convention (joint anchor at the local origin, far end along +Y, per-family alignment rotation), auto-detected the drawn long axis so only that axis scales to the bone length (cross-axis thickness 1:1), zeroed the Head driver's local position so the chin lands on the neck joint, dropped `DEFAULT_LINE_WIDTH` to 2.0 (editor's outline width), and rewrote the harness bone overlay (true parent→child segments + leaf bones drawn to their IK targets). Verified with a 46-assertion headless smoke test.
> FIXED — 2026 Round 2: implemented per docs/specs/phase9_round2_bugfix_spec.md; restored the `RemoteTransform2D` driver rotations (`update_rotation = true` again) so mounted shapes follow their bones under IK flexing, switched the mount to the rig's hanging convention (joint anchor at the local origin, far end along +Y, per-family alignment rotation), auto-detected the drawn long axis so only that axis scales to the bone length (cross-axis thickness 1:1), zeroed the Head driver's local position so the chin lands on the neck joint, dropped `DEFAULT_LINE_WIDTH` to 2.0 (editor's outline width), and rewrote the harness bone overlay (true parent→child segments + leaf bones drawn to their IK targets). Verified with a 46-assertion headless smoke test.
1. **Lower-limb bones/joints invisible in "Show Bones".** The harness `_draw_bones()` drew parent-origin → bone-origin lines, so for lower bones the segment duplicated the upper segment and wrist/ankle joints (bone ends, not origins) were never drawn. Fixed: the overlay now draws a joint dot per bone, parent→child bone segments, and leaf bones out to their IK targets.
2. **IK only moves lower-arm shapes; upper-arm shapes static.** Round 1's `_neutralize_driver_rotations()` set `update_rotation = false` on the 10 `RemoteTransform2D` drivers, so `Body/*` visuals sat unrotated at fixed joints. Fixed: driver rotation neutralization removed — the drivers keep their defaults and the hanging-convention mount rotates every part to follow its bone in any pose.
@@ -82,7 +82,7 @@ The guide and joints should slightly 'ghost' in front of the objects so that the
## Stickman editor (Phase 9 Round 3)
> FIXED — 2026 Round 3: implemented per docs/phase9_round3_bugfix_spec.md; one node per shape (closed → single Polygon2D, open → single Line2D), the part's preview rotation/scale composed into the mount before the hanging-convention fit (anchor/θ/bone-fit scale computed on the transformed geometry; ±180° flips swap the attachment to the drawn far end so flips render), and the harness head-leaf overlay drawn along the bone's own direction instead of to the LookAt aim point. Verified with a 26-assertion headless smoke test.
> FIXED — 2026 Round 3: implemented per docs/specs/phase9_round3_bugfix_spec.md; one node per shape (closed → single Polygon2D, open → single Line2D), the part's preview rotation/scale composed into the mount before the hanging-convention fit (anchor/θ/bone-fit scale computed on the transformed geometry; ±180° flips swap the attachment to the drawn far end so flips render), and the harness head-leaf overlay drawn along the bone's own direction instead of to the LookAt aim point. Verified with a 26-assertion headless smoke test.
1. **"The head bone has another bone extending out of it."** The harness bone overlay drew the Head leaf bone as a segment to `IK_Targets/Head` — the `SkeletonModification2DLookAt` aim point 232 px above the neck — a long line sticking out of the head. Fixed: the Head leaf now draws a ~90 px segment along the bone's own direction (`Vector2(length, 0)` rotated by `bone_angle` then `global_rotation`), so the head bone runs neck→head-top. Limb leaf bones (LeftLowerArm, RightLowerArm, LeftLowerLeg, RightLowerLeg) still draw to their IK targets.
2. **Duplicate Polygon2D + Line2D pairs per closed shape.** Closed shapes mounted as a `Polygon2D` fill **plus** a `Line2D` outline, producing two nodes per shape (e.g. `@Polygon2D@165` + `@Line2D@166`). Fixed: one node per shape — closed shapes mount as a single `Polygon2D`, open shapes as a single `Line2D` (width 2).
@@ -96,24 +96,24 @@ When applying .stk v1.4 data to master*rig.tscn, shapes are severely distorted:G
## Stickman editor (Phase 9 Round 4)
> FIXED — 2026 Round 4: implemented per docs/phase9_round4_bugfix_spec.md; the mounted head geometry is dropped +28 px (`HEAD_CHIN_DROP`) in rig space so the chin lands 28 px below the neck, aligned with the editor silhouette guide's head circle bottom and overlapping the torso. Verified with a 15-assertion headless smoke test.
> FIXED — 2026 Round 4: implemented per docs/specs/phase9_round4_bugfix_spec.md; the mounted head geometry is dropped +28 px (`HEAD_CHIN_DROP`) in rig space so the chin lands 28 px below the neck, aligned with the editor silhouette guide's head circle bottom and overlapping the torso. Verified with a 15-assertion headless smoke test.
1. **Head too high in the harness.** The head bone origin (neck) is at `y = −391.5`, and the mounted head's chin (its local origin) previously landed on that neck joint, so the head floated above the torso with no overlap. The editor's silhouette guide draws the head as a radius-100 circle centered at the Head joint `(0, −463.5)`, so the circle's bottom is `−363.5` — 28 px below the neck, overlapping the torso's top region. Fixed: the mounted head points now receive a `Vector2(0.0, 28.0)` rig-space translation after the part transform and the (θ = 0, s = 1) transform, so the chin lands at world ≈ `(0, −363.5)` — the guide circle's bottom — overlapping the torso by 28 px, matching the editor guide. The drop is a rig-space fixture position (applied after part scale/rotation), so a flipped/rotated head drops identically. node hierarchy does not multiply the geometry scaling a second time.Deliverable:Please update StkRigAdapter.gd to implement these corrected coordinate transform calculations and return the full updated GDScript.
## Stickman editor (Phase 9 Round 5)
> ADDED — 2026 Round 5: implemented per docs/phase9_round5_bugfix_spec.md; the harness now reproduces the editor's guide-relative part placement **1:1**. The editor exports per-part `guide_offset` (bbox center − guide joint, preview space; a pure master-space delta) as **write-only** metadata on save (`FILE_VERSION` bumped to `"1.5"`; the load path ignores the key). The head maps to the guide **Neck** joint — the head bone's rig attachment origin `(0, −391.5)` — **not** the circle center, which sits 72 px above the neck. `StkRigAdapter` applies the offset as a node-frame translation `t = (guide_offset + (A − C)).rotated(−c_node)` only when the key is present (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); old files without the key keep the previous offset-0 behavior and the head falls back to the Round 4 `HEAD_CHIN_DROP`. **Note:** `.stk` files must be re-saved from the editor to populate the offsets — only files carrying `guide_offset` pick up the guide-relative placement in the harness.
> ADDED — 2026 Round 5: implemented per docs/specs/phase9_round5_bugfix_spec.md; the harness now reproduces the editor's guide-relative part placement **1:1**. The editor exports per-part `guide_offset` (bbox center − guide joint, preview space; a pure master-space delta) as **write-only** metadata on save (`FILE_VERSION` bumped to `"1.5"`; the load path ignores the key). The head maps to the guide **Neck** joint — the head bone's rig attachment origin `(0, −391.5)` — **not** the circle center, which sits 72 px above the neck. `StkRigAdapter` applies the offset as a node-frame translation `t = (guide_offset + (A − C)).rotated(−c_node)` only when the key is present (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); old files without the key keep the previous offset-0 behavior and the head falls back to the Round 4 `HEAD_CHIN_DROP`. **Note:** `.stk` files must be re-saved from the editor to populate the offsets — only files carrying `guide_offset` pick up the guide-relative placement in the harness.
## Stickman editor (Phase 9 Round 6)
> FIXED — 2026 Round 6: implemented per docs/phase9_round6_bugfix_spec.md; the mount anchor is now the transformed end nearest the part's stored guide joint when `guide_offset` is present. Verified with a 32-assertion headless smoke test.
> FIXED — 2026 Round 6: implemented per docs/specs/phase9_round6_bugfix_spec.md; the mount anchor is now the transformed end nearest the part's stored guide joint when `guide_offset` is present. Verified with a 32-assertion headless smoke test.
1. **Lower-left-leg mounted 180° off its bone.** The leg's drawn knee was at the `min_x` end, but the per-side family rule anchored it at the `max_x` end, so the ankle attached at the joint and the shin hung backward. Root cause: the adapter picked the joint end with fixed per-side family rules (left limbs → `max_x`, right limbs → `min_x`) plus a 180° flip heuristic, but the user's drawn-side conventions are **inconsistent across parts** — no fixed rule can know which drawn end is the knee. Fix: when a part carries `guide_offset`, the anchor is whichever transformed end (`E(J_raw)` or `E(F_pt_raw)`) is **nearest `center − guide_offset`** (the part's guide joint) — the knee is now at the joint. Old files without the key keep the previous family rules.
2. **Lower-right-arm mounted 180° off its bone.** Same root cause mirrored on the right: the drawn elbow was at the `max_x` end, but the family rule anchored the arm at the `min_x` end, so the wrist attached at the joint and the forearm hung backward. Fix: the same nearest-end-to-guide-joint anchor selection puts the elbow at the joint. Old files without the key keep the previous rules.
## Stickman editor (Phase 9 Round 7)
> ADDED — 2026 Round 7: implemented per docs/phase9_round7_feature_spec.md; the test harness now exposes **six** 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** — the marker's `RemoteTransform2D` translates the hip bone and the whole skeleton + `Body/*` visuals follow rigidly, while the limb/head targets stay put (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). Verified with a 17-assertion headless test (Torso moved by (60, −40) → `Skeleton2D/Torso` and `Body/*` translate by exactly (60, −40); Head marker moved → Head bone + `Body/Head` rotate).
> ADDED — 2026 Round 7: implemented per docs/specs/phase9_round7_feature_spec.md; the test harness now exposes **six** 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** — the marker's `RemoteTransform2D` translates the hip bone and the whole skeleton + `Body/*` visuals follow rigidly, while the limb/head targets stay put (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). Verified with a 17-assertion headless test (Torso moved by (60, −40) → `Skeleton2D/Torso` and `Body/*` translate by exactly (60, −40); Head marker moved → Head bone + `Body/Head` rotate).
## Sandbox Stage — Director Tool (Phase 3a)
@@ -126,7 +126,7 @@ When applying .stk v1.4 data to master*rig.tscn, shapes are severely distorted:G
## Sandbox Stage (Phase 3c)
> FIXED — 2026-09-04: all five bugs fixed (developer stage). Bugs 1 and 4 (implementation defects — panel rows never parented into the list container) fixed by adding the row to the list container; bugs 2 and 3 (spec/design defects — unconditional entry-point items) fixed by gating popup items behind an `about_to_popup` refresh; bug 5 (spec/design defect — theme schema) fixed by extending `sandbox_theme.json`'s `fonts` block + adding `apply_font(...)` to the Phase 3c panels/editors. See per-bug notes below. Spec amendments: `docs/phase_3c_editor_spec.md` §2 decision 8, §5.3, §11, and §14.
> FIXED — 2026-09-04: all five bugs fixed (developer stage). Bugs 1 and 4 (implementation defects — panel rows never parented into the list container) fixed by adding the row to the list container; bugs 2 and 3 (spec/design defects — unconditional entry-point items) fixed by gating popup items behind an `about_to_popup` refresh; bug 5 (spec/design defect — theme schema) fixed by extending `sandbox_theme.json`'s `fonts` block + adding `apply_font(...)` to the Phase 3c panels/editors. See per-bug notes below. Spec amendments: `docs/specs/phase_3c_editor_spec.md` §2 decision 8, §5.3, §11, and §14.
1. **"Edit Queue" shows no actions (implementation defect).** `queue_panel.gd` `refresh()` builds each row via `_make_row()` into `_rows` but never `_list.add_child(...)`s it, so rows are tracked for reorder but never rendered. Fix: add `_list.add_child(panel)` in `_make_row()` (or in `refresh()`).
- **FIXED (2026-09-04):** rows are now parented into the list container (`_list.add_child(panel)` in `_make_row()`), so the queue rows render.
@@ -141,7 +141,7 @@ When applying .stk v1.4 data to master*rig.tscn, shapes are severely distorted:G
## Stickman rig (head mirror + recovery re-anchor)
> FIXED — 2026-09-04: two runtime bugs in `scripts/stickman_rig.gd` fixed (developer stage), covered by a new 76-assertion headless regression suite `tests/test_phase3c_walk_recovery.gd`. See per-bug notes below. No spec changes; no `.stk` / scene / `.tscn` file touched.
> FIXED — 2026-09-04: two runtime bugs in `scripts/characters/stickman/stickman_rig.gd` fixed (developer stage), covered by a new 76-assertion headless regression suite `tests/test_phase3c_walk_recovery.gd`. See per-bug notes below. No spec changes; no `.stk` / scene / `.tscn` file touched.
1. **Head does not mirror when the rig faces LEFT (implementation defect).** `_apply_head_flip()` reflected the mirror through the Head **Pivot** node's negative-determinant scale, which Godot re-decomposed into a per-frame Y-scale flip as the Head bone rotated under `LookAt` (the driver only forwards `update_rotation = true`, so the negative scale leaked into the rotation channel). Root cause: folding the reflection into a driver whose transform is applied through the rotation channel. Fix: keep the Pivot's transform identity for **all** profiles (scale `(1,1)`, rotation `0`) and mirror the mounted geometry directly on `Body/Head.scale.x` (`-1` for LEFT, `(1,1)` for RIGHT/FORWARD); the driver's `update_scale = false` never clobbers this manual scale. Note: the driver may canonicalize the mirror to `(1,-1)` — the X-mirror semantics are unchanged.
- **FIXED (2026-09-04):** `_apply_head_flip()` now resets the Pivot transform to identity for every profile and applies the LEFT mirror as `Body/Head.scale.x = -1` (RIGHT/FORWARD → `(1,1)`).
@@ -152,5 +152,5 @@ When applying .stk v1.4 data to master*rig.tscn, shapes are severely distorted:G
## Stickman rig (whole-rig Y-axis mirror — design change)
> **DESIGN CHANGE (proposed by user, 2026-09-05):** replace the per-part/head mirroring for `FacingProfile` LEFT/RIGHT with mirroring the **entire stickman** over the Y-axis (`Master.scale.x = -1` for LEFT, `(1,1)` otherwise), so the head AND body mirror together and face the correct direction. `_apply_head_flip()` and the `Body/Head.scale.x` mirror are **removed**; per-joint `flip_bend_direction` flags (`PROFILE_FLAGS`) and `Z_ORDER_BY_PROFILE` are **kept provisionally** (unchanged). **Walk-clip mapping — Option A (single canonical clip):** `walk_right` is the canonical walk; for `FacingProfile.LEFT` the rig root is X-mirrored and the **same `walk_right`** clip plays mirrored (`walk_left` becomes unused at runtime; the animation `.:facing_profile` tracks are neutralized/removed and facing is set explicitly by `set_facing_profile()`/`walk_to()`). See `docs/phase9_task4_refactor_spec.md` §9a and the `AGENTS.md` stickman_rig section.
> **DESIGN CHANGE (proposed by user, 2026-09-05):** replace the per-part/head mirroring for `FacingProfile` LEFT/RIGHT with mirroring the **entire stickman** over the Y-axis (`Master.scale.x = -1` for LEFT, `(1,1)` otherwise), so the head AND body mirror together and face the correct direction. `_apply_head_flip()` and the `Body/Head.scale.x` mirror are **removed**; per-joint `flip_bend_direction` flags (`PROFILE_FLAGS`) and `Z_ORDER_BY_PROFILE` are **kept provisionally** (unchanged). **Walk-clip mapping — Option A (single canonical clip):** `walk_right` is the canonical walk; for `FacingProfile.LEFT` the rig root is X-mirrored and the **same `walk_right`** clip plays mirrored (`walk_left` becomes unused at runtime; the animation `.:facing_profile` tracks are neutralized/removed and facing is set explicitly by `set_facing_profile()`/`walk_to()`). See `docs/specs/phase9_task4_refactor_spec.md` §9a and the `AGENTS.md` stickman_rig section.
> **Follow-up (2026-09-05):** under the LEFT root mirror the head was displaced/flipped — (a) the head `RemoteTransform2D` (`Skeleton2D/Torso/Head/Pivot`) set `update_scale = false`, a partial-channel push that re-canonicalized `Body/Head.scale` under the mirrored root (per-frame Y-flips/wrap-jumps), and (b) the head `SkeletonModification2DLookAt` is **not mirror-invariant**, writing a bone rotation 180° off the FORWARD aim that flips the head to hang below the neck. Fixed: the head driver now pushes the **full transform** like every other `Body` driver (`update_scale` no longer `false`), and `_apply_head_lookat_mirror_mode()` **disables** the LookAt when facing LEFT, pinning the head bone to the FORWARD canonical aim (π) with `_pin_mirrored_head_rotation()` re-asserting the pin each `_physics_process` frame while ANIMATED/RECOVERING (RIGHT/FORWARD re-enable the LookAt). Interactive head-aiming is intentionally static while facing LEFT.
+7
View File
@@ -0,0 +1,7 @@
# Active plans and design briefs
Keep accepted Designer handoff briefs and future implementation plans in this directory. Name phase briefs `phase_<number>_<feature_slug>.md` and title them `Phase <number> — <Feature>`.
Designer briefs capture the product goal, user experience, scope, success criteria, and questions for the spec-pipeline. They are planning inputs, not final architecture specifications; the architect writes those in `../specs/`.
Move completed or superseded plan drafts to `../archive/plans/`.
+104
View File
@@ -0,0 +1,104 @@
# Phase 16 — Running Stickman
> **Status:** Accepted Designer handoff for the spec-pipeline.
> **Audience:** Architect and implementation team.
> **Purpose:** Product/design brief; the final technical specification is a separate architect deliverable.
## Implementation update — DIRECT mode
The DIRECT-mode quick action uses one **Move To** command. The user places the destination first; a compact follow-up panel then offers a **Movement** dropdown and **Add Move To** / **Cancel**. The dropdown currently offers Walk and Run and is populated from the movement-style choices so more styles can be added without expanding the action menu. The first draft defaults to Walk. A stickman remembers its last successfully committed style while its scene is open; canceling a draft does not change that preference.
Cancel, Escape, right-click, dismissing the popup, or leaving DIRECT mode discards the uncommitted destination and style. The approved readability pass uses a 23 pt Movement label, a 25 pt selector (including style emoji), 22 pt buttons, 54 px selector height, 52 px action-button height, and a 380×172 px panel.
## Goal and audience
Give children a clear way to direct a stickman to a destination at different paces, starting with Walk and Run. Make Run read as a believable human gait in the existing stickman style, rather than a shortened or faster Walk cycle.
The action should describe the destination independently from the way the character travels. This leaves a clean product concept for future movement styles such as Fly, without including them in this phase.
## Desired user outcome
A child can make a stickman walk to one place, then run to another, using the same Move To action and waypoint workflow. The selected style affects both travel pace and animation, so a faster movement has a matching gait. Children do not need to tune a numeric speed to get the intended result.
## Core flow and behavior
1. In the Sandbox Stage action editor, the child adds or edits **Move To** and places a destination using the existing stage-click target flow.
2. The action offers **Walk** and **Run** as per-action movement styles. **Walk** is selected by default.
3. Each style uses a predictable default pace paired with its animation. Numeric speed adjustment is out of scope for the first version.
4. The queue and rule displays identify the style, for example **Move To · Run**, so the child can see which movement was chosen.
5. The existing waypoint drawing, re-placement, and path preview continue to represent the destination independently of the style.
6. In DIRECT mode, the child chooses **Move To**, places the destination, then chooses a style from the follow-up **Movement** dropdown and commits with **Add Move To**. **Cancel** discards the draft.
7. The first DIRECT-mode draft for a stickman defaults to Walk; later drafts use that stickman's last successfully committed style for the current open scene. Canceling never updates the remembered style.
## Run animation target
The target is a natural human run expressed through the project's simple stickman figure. The run should have recognizable gait phases: a foot contact and weight-bearing phase, push-off, a brief airborne phase, leg recovery, and the next contact. Arm swing should counter the legs, with a modest forward lean and controlled body bounce that support the motion without distracting from it.
The motion should be visibly different from walking. Its cycle cadence and foot travel should agree with the Run pace so the feet do not skate over level ground or pass through the floor. Entering and leaving the loop should feel smooth, including when the destination is close enough for only part of a cycle. The animation should read correctly while facing either direction.
The project already has a tool script that bakes walk animations from authored IK-target keyframes. This is a suitable starting point for a generated run clip, with visual tuning expected. Here, “generated” means authored pose/keyframe data is baked into an animation by the project tool; the brief does not assume automatic motion capture or an AI-generated gait.
## Story and directing value
Walk supports calm travel, exploration, and approaching something. Run supports urgency, chasing, escaping, and energetic entrances. Keeping both styles on Move To lets children reuse destinations and path editing while changing the performance of the character.
## Compatibility and project fit
The current action is `walk_to`. Existing `.scene` files and rules should load transparently as **Move To · Walk**, preserving targets, queue order, trigger behavior, and waypoint editing. Saving after load may write the new canonical form. Children should not need to repair or re-author existing stories.
Relevant current behavior and references:
- `scripts/stage/action_registry.gd` registers action names, summaries, and rule/queue conversions.
- `scripts/editors/stage/action_editor.gd` edits actions and captures destination clicks.
- `scripts/stage/stage_scene_io.gd` validates and serializes saved stage actions.
- `scripts/editors/stage/sandbox_stage.gd` builds direct/rule actions and converts rule actions for playback.
- `scripts/stage/stage_director_visuals.gd` draws waypoint paths and action markers.
- `scripts/characters/stickman/stickman_action_runner.gd` runs queued actions; `scripts/characters/stickman/stickman_locomotion.gd` currently owns navigation, walking animation, and terrain following.
- `scripts/tools/create_animations.gd` bakes the current walk clips and stand-up reference animation.
- `docs/specs/phase_3a_spec.md`, `docs/specs/phase_3c_editor_spec.md`, `docs/specs/phase_8_rig_modularization.md`, and `docs/specs/phase_12_live_director.md` describe the existing movement, director, and action workflows.
## Success criteria
- A child can add or edit Move To, choose Walk or Run for that action, and set its destination through the familiar click-on-stage flow.
- In DIRECT mode, destination placement comes before movement-style selection; the child can commit or cancel the staged Move To draft without reopening an action menu.
- DIRECT mode defaults to Walk per stickman and remembers the last committed style only for the lifetime of the open scene.
- Walk is the default, and unedited legacy `walk_to` actions keep walking.
- Walk and Run have distinct, understandable movement paces; choosing Run makes the character travel faster and show a recognizable run cycle.
- The run includes visible support, push-off, flight, recovery, and counter-swinging arms; on level ground, foot contact looks stable and the feet do not visibly skate or penetrate the surface.
- Run starts and finishes cleanly at both short and long destinations, and faces correctly in both directions.
- Queue actions, rule actions, summaries, action editing, waypoint re-placement, and saved/loaded scenes agree on the chosen style and destination.
- Existing arrival, action completion, interruption, and queue behavior remains understandable and intact.
## Scope and exclusions
Included:
- Rename the child-facing concept from Walk To to Move To across relevant authoring, queue/rule summaries, and waypoint interactions.
- Add per-action Walk/Run choice with Walk as the default and style-specific fixed paces.
- Add a destination-first DIRECT-mode Move To draft with an extensible Movement dropdown, Add Move To/Cancel controls, and per-stickman last-committed-style memory for the open scene.
- Add a generated, tuned run cycle and connect it to Run movement.
- Preserve compatibility for existing `walk_to` scene and rule data.
- Validate the animation against existing facing and grounded terrain behavior.
Not included in this phase:
- Fly or additional movement styles.
- Numeric speed controls or user-authored speed presets.
- New pathfinding, obstacle avoidance, slope navigation, or physically simulated locomotion.
- Replacing the stickman art/rig with motion capture.
## Dependencies and risks
- The style must survive all current conversions between queue actions, rule actions, runtime execution, and stage-file serialization.
- Old `walk_to` data must be recognized before new action validation rejects unknown types; migration must preserve waypoint identity and action/event semantics.
- The run cycle must be tuned together with its travel pace and current foot/terrain handling. A convincing in-place loop alone may still look slippery when the root travels.
- Arrival snapping and interruption/cancellation can stop a cycle between gait phases; the architect and developer should decide how to settle into the standing pose without visible popping.
- Current waypoints and event rules refer to movement actions. Renaming the action needs a consistent user-facing summary and migration story throughout both systems.
## Open questions for the spec-pipeline
- How should `ActionEditor` present Walk/Run: two large choice buttons, a segmented control, or another clear kid-facing selector? (The separate DIRECT-mode quick action uses the approved destination-first dropdown.)
- How should `walk_to` be canonicalized so old queue actions, rule actions, triggers that refer to action completion, and saved files retain their intended behavior?
- What run-cycle timing and foot-contact strategy best match the existing IK rig, terrain following, and fixed style pace?
- What transition behavior should apply when Run starts, reaches a short destination, or is interrupted by another action?
- Which level surfaces and terrain cases are required for first-pass visual verification?
@@ -0,0 +1,87 @@
# Phase 17 — Props for Storytelling
> **Status:** Accepted product brief. Phases 17a, 17b, and 17c are implemented and user-verified. Phase 17d remains deferred.
> **Audience:** UI/UX, architect, and implementation team.
> **Purpose:** Product and design brief; technical contracts belong in later specifications.
## Goal and audience
Help children build layered sets and direct clear cause-and-effect stories with props. A table can sit behind a character and hold a flower pot without blocking the character. The character can pick up the pot, set it down deliberately, drop or throw it, press a button on another prop, and make a struck prop wobble. Children can also keep related set pieces together while arranging a scene.
The audience is a child working in the Sandbox Stage's EDIT, DIRECT, and PLAY modes. The controls should describe visible outcomes, show the relevant target on the stage, and let the child correct a misplaced object or action without rebuilding the scene.
## Desired user outcome and delivery order
Phase 17 is one product milestone with four ordered parts. Each part should be independently reviewable; later parts build on the placed-prop behavior established in 17a.
| Part | Child-facing outcome | Priority | Status |
| --- | --- | --- |
| **17a — Prop roles and placement** | Arrange props behind or in front of characters; choose independent physical and interaction capabilities; build pass-through support props; select covered props in EDIT; use **Set Down** to release a held prop at a chosen stage spot. | First and highest | Implemented and user-verified |
| **17b — Use spots** | Mark a handle, button, or lever on a placed prop, direct a stickman to use it, and respond through a rule. | Next | Implemented and user-verified |
| **17c — Impact wobble** | Make a prop briefly wobble after a meaningful hit. | Next | Implemented and user-verified; automated behavior verification is not documented |
| **17d — Scene groups** | Organize and move several placed props together while editing a scene. | Last; lower priority | Deferred |
17a's role model, saved-scene migration, fixed depth tiers, covered-object chooser, collision handling, Set Down action, and UI are implemented and user-verified. Automated test run: no execution result is documented for `tests/test_phase17a_prop_roles.gd`. The success criteria below remain regression guidance. Depth is currently a fixed behind/in-front choice; dynamic Y sorting remains deferred.
## Core child flow and feedback
### 17a — Prop roles and placement
The child places a prop, selects it in EDIT, and chooses its visual depth separately from what it does in PLAY. The initial depth choices are **Behind characters** and **In front of characters**. Props keep a clear selection outline and a way to select or edit them even when another object covers them. The stage should make the chosen depth and behavior legible without exposing physics terminology as the primary labels.
Behavior is expressed through independent capabilities, because a prop may support other props while characters pass through it, and a pickable prop may be nonblocking at the start of PLAY but collide after release. Newly placed props start still by default. A table can remain fixed, sit behind a stickman, and support a flower pot without blocking that stickman. The pot stays at its authored starting position until picked up. While held, it follows the hand without collisions; after Set Down, Drop, or Throw, it interacts with configured surfaces, stickmen, and other props as appropriate. It remains available for another pickup. Stopping PLAY restores the authored arrangement and behavior for a repeatable replay.
**Set Down** complements the existing free **Drop** and **Throw** actions. The child directs a stickman holding a prop, chooses Set Down, then clicks any stage-world spot, including open air. The stickman approaches from a reachable grounded position and moves the held prop to the requested release point. If that point embeds the prop in a support surface, the stage raises the release point just enough to clear its collision shape. The arm reaches smoothly; low placements bend the torso and knees. At release, the object becomes physical and falls onto the crate, table, terrain, or other surface below. The stage previews the requested point, corrected release origin when needed, actor approach, and reach, and gives a clear, recoverable cue for an unreachable or blocked release point. A support prop uses its collision geometry; the child does not mark a special support surface.
### 17b — Use spots
In EDIT, the child adds visible Use spots to a **placed prop instance** and moves each marker onto a handle, button, or lever. A Use spot differs from a pickup grip: the stickman reaches to the spot while the target prop stays in place. In DIRECT, the child chooses **Use**, selects the prop and intended spot, and sees whether the spot is reachable. If it cannot be reached, the stage explains the problem at the target rather than silently skipping the action.
A successfully completed interaction emits a child-facing **Used prop** event that identifies the stickman, prop, and spot. New rules target one specific named spot; saved whole-prop (`spot_scope = "any"`) rules remain supported for compatibility. The spot gets a brief, visible completion cue. Built-in results such as opening a door or toggling a light are later work; the rule system supplies story consequences in this phase.
### 17c — Impact wobble
The child can turn **Wobble when bumped** on or off for one placed prop. Enabling it in EDIT plays a brief sample; the choice remains available for pass-through props. In PLAY, a qualifying approach-speed hit produces a short tilt and sideways give on the artwork while the physics body and collisions continue normally. Contact and blocking-movement sweep candidates share one resolver with pair rearming and cooldown gates; animated stickmen, ragdoll bodies, other props, and other physics bodies can supply hits when collision policy allows. Two enabled props can both wobble from one qualifying prop-to-prop hit. Scene v1.4 stores the choice per placement, and older scene versions default it to Off. Stopping PLAY cancels the effect and restores the authored state. The current score is based on approach speed, not mass; the resolver keeps scoring centralized for future mass or impulse work.
### 17d — Scene groups
In EDIT, the child selects related placed props such as a table, cloth, and pot, chooses **Group**, and gets a visible shared selection that can be moved together. The child can open the group to adjust one item and can ungroup without losing the items. A group organizes this scene; it does not turn its members into one physical object. In PLAY, each member keeps its own depth, collision, pickup, and event behavior. Picking up the pot moves only the pot. Stopping PLAY returns the authored arrangement, with the group still available in EDIT.
## Story and directing value
Depth lets children stage a character behind a table or in front of scenery. Independent support and collision roles make an apparently simple set behave as expected. Set Down gives deliberate blocking and placement moments, while Drop and Throw keep playful physical outcomes. Use spots let a character visibly press or handle a story object; rules let that moment trigger dialogue or another action. Wobble adds an immediate, readable response to a bump. Groups reduce repeated arranging as a set becomes more detailed.
## Success criteria
- A child can save, reload, and replay a scene in which a fixed table is visually behind a stickman, supports a pot, and lets the stickman pass through. The pot can be picked up, set down onto the table with a smooth reach, released collision-free until that moment, and then fall onto it through physics. It can also be dropped or thrown and picked up again. PLAY reset restores the authored setup.
- Visual depth and physical/interaction capabilities remain independent on placed copies of the same prop asset. A covered prop remains discoverable and editable in EDIT.
- A child can place more than one Use spot on a prop instance, direct a stickman to a chosen spot, and make a newly authored rule fire for that specific spot only after the interaction completes. Failed reach attempts give useful feedback. Legacy whole-prop rules continue to load and match.
- A meaningful collision produces one readable wobble when enabled; resting contact does not produce continuous wobbling, and stopping PLAY clears the effect.
- A saved scene group can be moved together in EDIT, opened to edit a member, and ungrouped. Members behave independently in PLAY and return to their authored arrangement on reset.
- Older scenes and reusable `.prop` assets continue to open with sensible behavior. A child does not have to re-author a scene to use the new controls.
## Scope and exclusions
Included: per-placement depth and independent capabilities; support for props that need not block characters; predictable start, held, and released prop behavior; Set Down by clicking a stage-world release point, followed by a physical fall; EDIT selection of obscured props; instance Use spots and Used prop rules; controlled impact wobble; and persistent scene-only groups. No support-surface authoring control is part of this phase.
Deferred: dynamic Y sorting by character and prop ground points; built-in Use effects; topple and break reactions; reusable set-piece assets; group physics or jointed assemblies; door opening/closing, functional doorways, and scene travel. Large vector background walls belong to the stage backgrounds work, while fixed actor-blocking walls are structure/terrain concerns. Loose bricks or planks remain movable props.
## As-built touchpoints and remaining risks
- `scripts/props/prop_block.gd` and `scripts/props/prop_stage_roles.gd` now hold per-placement role settings and EDIT, start-still, start-physics, held, and released profiles. `scripts/stage/stage_scene_io.gd` writes version 1.2 and migrates older scenes; the `.prop` asset schema is unchanged. Keep save/load compatibility and PLAY reset in the regression checklist.
- `scripts/characters/stickman/stickman_prop_interactor.gd` handles pickup, Drop, Throw, and Set Down. It clears collisions while a prop is held and applies its authored release profile afterward. Set Down previews and runtime checks share the target solver; complex queued/rule sequences and changed runtime conditions remain useful regression cases.
- `scripts/characters/stickman/stickman_locomotion.gd` and `scripts/stage/stage_physics_policy.gd` include actor-blocking prop collision bits and movement sweeps. Props set to block stickmen are not walkable platforms; terrain remains the movement surface. Keep obstruction, pause/reset, and ragdoll interactions in the regression checklist.
- `scripts/stage/stage_selection.gd` provides `hit_test_all()` and the stage shows an **Objects here** chooser for overlapping candidates. The current hit test uses world-space AABBs rather than exact vector-shape containment, so the chooser may include an object when the pointer is inside its bounds but outside its artwork.
- Props use fixed behind/in-front draw tiers. Dynamic ground-point Y sorting remains deferred. `scripts/props/prop_anchor_data.gd` continues to describe reusable grip anchors; 17b Use spots are placed-instance data and remain distinct from those grips.
- Phase 17b implementation is present. The architecture and UI contracts are documented in [Use spots: architecture](../specs/phase_17b_use_spots.md) and [Use spots: UI/UX](../specs/phase_17b_use_spots_ui.md). New Used prop rules choose one named spot; legacy whole-prop rules remain compatible. In-editor user verification is pending.
- Phase 17c implementation is present in `scripts/props/prop_block.gd`, `scripts/stage/stage_impact_resolver.gd`, `scripts/editors/stage/sandbox_stage.gd`, `scripts/editors/stage/stage_prop_settings_view.gd`, `scripts/editors/stage/stage_run_session.gd`, `scripts/editors/stage/stage_scene_adapter.gd`, and `scripts/stage/stage_scene_io.gd`. The prop card provides the per-placement switch and EDIT sample, including for pass-through props. PLAY applies a visual-only wobble to artwork; root physics motion and collision geometry remain under normal simulation. Body contacts and identified blocking-stickman sweeps use relative approach speed, with pair tracking and cooldown to suppress repeated resting/push contact. Ragdoll and prop-prop contacts are routed through the resolver. Scene v1.4 persists the boolean and migrates older scenes to Off. Material preset mass is not part of the score; the shared resolver is the extension seam for a future editable-mass or impulse model. The user has verified Phase 17c; no automated behavior verification result is documented.
- Stage multiselection can move several objects together in one edit, but it does not save persistent group membership. Phase 17d must preserve independent member identities and prop-targeted actions when adding saved groups.
- Relevant prior context: [Phase 11 prop interactions](../specs/phase_11_prop_interactions.md) and the live [feature backlog](../ideas/feature_backlog.md).
## Regression checks and future design questions
17a's settings controls, overlap chooser, Set Down release-point behavior, legacy scene defaults, implementation, and UI are user-approved. Keep the table/pot support and pass-through case, blocking movement, open-air and corrected Set Down targets, invalid-target recovery, covered-object selection, repeat PLAY reset, and old-scene replay as regression guidance. Phase 17c has been user-verified. Its regression guidance covers EDIT preview and cancellation; pass-through availability; fixed and moving props; a walking/running stickman contact and blocked movement sweep; a falling ragdoll; prop-to-prop hits with both enabled and one disabled; slow/resting contacts and sustained pushes; pause/resume and STOP/replay; per-instance save/reload; and v1.0–v1.3 migration to Off. No automated behavior verification result is documented.
The following design question remains for the deferred part of Phase 17:
- **17d — Scene groups:** What EDIT gesture makes it clear whether the child is selecting a whole group or one member? How should group membership be shown without covering the set artwork?
@@ -0,0 +1,192 @@
# Phase 19 — Scenery, Backgrounds, and Vector Surface Finishes
> **Status:** Accepted product/design brief. Technical specifications and implementation remain to be developed through the spec-pipeline.
> **Audience:** UI/UX, architecture, implementation, and art contributors.
> **Purpose:** Define the child experience and the content required for indoor and outdoor sets. Rendering, file contracts, and GDScript APIs belong in later specifications.
## Goal and audience
Give children a quick way to make a Stage feel like a place: a town street, a living room, or their own setting. They should be able to choose an appealing sky, dress a scene with inert vector artwork, paint visual wall and Ground finishes, and then direct characters among ordinary props. The art remains crisp when the Stage camera pans or zooms.
The audience is a child using the Sandbox Stage's EDIT, DIRECT, and PLAY modes. The central distinction should be easy to learn: **Scenery looks and stays put; Props can be part of the action; Ground is where stickmen walk.** An artwork asset can be used in more than one role; the Stage placement choice gives that copy its behavior.
## Desired child outcome and story value
- Start with **Blank Scene**, **Town Street**, or **Living Room**, then change any ordinary part of the starter instead of being locked into one background picture.
- Build a layered outdoor set: cloudy sky, distant skyline, nearer shops and trees, sidewalk, and road. Characters remain readable and can move on the Ground.
- Build an indoor set: painted wall, window, wall decorations, wood floor, rug, and furniture. A child can change the Sky seen through the starter's window without recoloring the room.
- Use Far, Near, and On the floor scenery to express distance and staging without giving scenery collision or interactions. A child may draw a tree once and place it as either Scenery or a Prop in different scenes.
- Save, reopen, revise, and replay the scene without losing the selected finishes, sky, artwork, or depth choices.
The feature helps children establish story mood and place before directing dialogue and action. Sky and set dressing are visual choices. Functional doors, upper floors, and whole-scene color filters remain separate story mechanics.
## Child flow and feedback
### Start a scene
**File → New** shows three large illustrated choices: **Blank Scene**, **Town Street**, and **Living Room**. The existing unsaved-work flow protects the current scene before any choice replaces it. Cancel returns to the existing work. A starter opens as a new **unsaved** scene with an editable suggested name; it does not insert itself into another scene or create a locked template instance.
| New Scene choice | Initial composition | Child can change |
| --- | --- | --- |
| **Blank Scene** | Empty Stage with the existing neutral background and no placed Ground. | Sky, Scenery, Ground, characters, and Props from scratch. |
| **Town Street** | Cloudy Day Sky; muted skyline in Far; colorful storefront, house, and tree artwork in Near; walkable Sidewalk and Asphalt Ground. | Sky, skyline strip, individual near artwork, Ground pieces and finishes, plus any added cast or Props. A skyline strip is one artwork placement, not independently editable buildings inside it. |
| **Living Room** | Clear Day Sky; green painted wall panels around a window opening; Near window frame, fireplace/mantel, clock, shelf/books, picture, and plant; Wood Ground; an On the floor rug; couch, armchair, and coffee-table Props. | Every panel, artwork placement, rug, Ground piece, and furniture Prop. The wall panels leave an intentional gap beneath the window frame, so the selected Sky is actually visible. Moving or resizing the window and wall pieces may require adjusting more than one piece; this phase has no automatic window-cutting tool. |
The starters demonstrate what children can make with the normal tools. Furniture in Living Room starts still with sensible blocking/support behavior where appropriate; the starter does not imply that characters can sit. A short cue may point children to Scenery after a starter opens, without covering the Stage.
### Open Scenery
The Stage palette gains **Scenery**. Its chooser has **Backgrounds** and **Artwork**:
```text
Scenery
[ Backgrounds ] [ Artwork ]
Backgrounds: [ Sky ] [ Wall panel ]
Artwork: [ All artwork ] [ Nature ] [ Buildings ] [ Walls ]
[ Roads & paths ] [ Objects ]
```
The Artwork choices use large previews. The tag filters describe the drawing, not what it can do. The built-in, optional, multi-select tag IDs are `nature`, `buildings`, `walls`, `roads`, and `objects`; the child-facing labels above can be refined without changing the stored meaning. One drawing may appear in several categories. **All artwork** includes old and untagged work; **Unsorted** can appear when there is untagged artwork to show. The same tags can filter the Props browser. The Props and Scenery entry points remain visibly distinct so children do not have to turn off interaction settings for every background item.
The existing vector Prop Editor and `.prop` assets supply user-created artwork. Phase 19 should make that art discoverable from Scenery, including artwork that has no usable collision hull. There is no separate Scenery Editor or `.scenery` format in this phase. A later child-facing name such as Artwork Editor may better describe its broadened use; the UI/UX specification should decide how much prop-only material and attachment controls are shown during scenery authoring.
### Choose a Sky
**Backgrounds → Sky** shows four designed thumbnail cards: **Clear Day**, **Cloudy Day**, **Sunset**, and **Starry Night**. One tap changes the scene's Sky, and choosing another card changes it again. A Sky fills every camera view without an exposed edge; its clouds, sun, moon, and stars remain visually stable and crisp during pan and zoom. Each preset should be a considered composition rather than a flat color with an obvious repeated icon. The Sky remains fixed throughout PLAY.
Changing Sky changes **only the sky**. It never tints or recolors Ground, wall panels, scenery artwork, characters, or Props. Sky is selected through its chooser, not by clicking a giant rectangle on the Stage.
### Draw a wall panel
**Backgrounds → Wall panel** offers **Paint**, **Brick**, and **Wood** finishes with a color choice. The child chooses a finish, drags from one corner to the other, sees a translucent preview of the intended size and pattern, and releases to place a finite rectangular panel. Snap may help edges meet. A selected panel can later be moved, resized, recolored, restyled, or erased; changing one panel does not silently alter others.
The child-facing explanation should make its role clear: **“Looks like a wall. Stickmen can pass through.”** A wall panel is world-anchored backdrop art. It does not block a stickman, support a prop, move under physics, respond to Use, or trigger rules. A solid wall belongs to terrain or a later structural feature. When a panel grows, bricks or boards retain a sensible world-scale motif rather than stretching a small image.
### Place and edit artwork
The child opens **Artwork**, chooses a tag such as Nature, taps a drawing such as **Tree**, sees a ghost over the Stage, and taps to place it. Each Scenery placement offers a simple illustrated depth choice: **Far**, **Near** (default), or **On the floor**. Far supports distant silhouettes such as a skyline. Near supports storefronts, trees, window frames, and wall decor. On the floor supports rugs and markings that sit over finished Ground but under action. A child can change the depth later. Artwork placed through Scenery stays fixed and inert even if the same `.prop` can also be placed through Props as an interactive object.
The authoritative visual order is:
```text
Sky
→ wall panels
→ Far scenery artwork
→ Near scenery artwork
→ walkable Ground
→ On the floor scenery artwork
→ characters and interactive Props (retaining their existing relative depth choices)
```
Every Scenery tier is visual only. Its depth never grants collision, support, pickup, Use spots, or event participation. An On the floor rug is visible above a wood Ground finish without covering furniture or the character's feet.
**Edit Scenery** exposes the scenery pieces for selection. A clear outline and large handles let the child move and resize art, change its depth, or erase it; a wall panel also exposes Finish and Color. Broad panels must not steal clicks or box selections from characters, Ground, and Props in ordinary EDIT. Within Edit Scenery, a cutout in front of a panel should be easy to choose, and the panel must still be reachable through its exposed area, edge, or an overlapping-items choice. Sky remains in its own chooser. A visible Cancel closes a placement or sizing gesture before commit, and leaving Edit Scenery returns to ordinary selection. Selection and focus cannot rely on color alone.
### Finish Ground and play
The existing **Ground** palette entry gains a visual finish chooser. Picture swatches include **Paint**, **Brick**, **Wood**, **Grass**, **Dirt**, **Sidewalk**, and **Asphalt**. Paint, Brick, and Wood share recognizable names/previews with Wall panel where applicable; Ground-only outdoor finishes need not appear in the wall picker. Tap for one Ground piece or use the existing drag-paint gesture for a run. Ghosts show the selected look. A new choice affects **new placements only**. Selecting an existing Ground piece lets the child deliberately change that piece's finish. Adjacent painted pieces should read as one continuous surface, especially wood floors, sidewalks, and roads.
Ground remains solid and walkable. This phase gives finishes only to **Ground**; it does not redesign or restyle Ramp and Step. Children can use Sidewalk and Asphalt Ground in Town Street and Wood Ground in Living Room. A Scenery wall panel or road marking never becomes a movement surface.
In **PLAY**, edit handles and placement ghosts disappear. Sky and Scenery stay still; characters move on Ground; Props follow their own saved behavior settings. Stopping PLAY restores the authored arrangement for another take. Story playback and camera movement should preserve the intended visual layer order and the child's view of the action.
## Included scope and exclusions
Included in Phase 19: one fixed scene-wide Sky preset; four initial Sky designs; finite editable wall panels; inert `.prop` Scenery placements with Far, Near, and On the floor depth; built-in multi-select browse tags; Ground-only visual finishes; editable Blank/Town Street/Living Room New Scene choices; Scenery-safe selection; scene save/reopen and legacy defaults; and the built-in vector content listed below.
Excluded from Phase 19: a new Scenery Editor or `.scenery` file; child-authored repeatable finish patterns or reusable whole-Sky themes; animated weather, parallax, or directed Sky transitions; whole-scene filters such as sepia or black-and-white; functional doors or scene travel; automatic window cutouts or glass; Ramp/Step finishes or terrain redesign; inserting a starter into an existing scene; user-authored reusable set templates; and functional stairs or multi-level travel. [Multi-level walkable scenes](../ideas/feature_backlog.md) and [whole-scene visual filters](../ideas/feature_backlog.md) are separate future ideas, not Phase 19 work.
## Asset production manifest
This is the authoritative content checklist for the two starters and the shared pickers. The initial status of **every** row is **Needed**. Progress through **Needed → Concept ready → Art approved → Final vector ready → Integrated → Verified**. A raster sketch may reach **Concept ready**; it is not a final `.prop`, Sky theme, or repeatable vector finish. Final cutouts must be repo-native vector/`.prop` artwork, and Sky/finish visuals must remain crisp through Stage zoom. Art contributors may use raster concept images as reference, then produce final assets in coordination with implementation.
In the tables, **TBD** in Source/rights means the creator, reference provenance, and license or permission still need recording. **TBD** in Final location and Evidence means those fields must be filled as content is produced. For each row, validation evidence should include a thumbnail or Sky/finish preview, the asset in the Stage at normal and zoomed views, and save/reopen of its starter use where applicable. Stable IDs identify deliverables; they are not prescribed file names or runtime contracts.
### Adaptive Sky themes
| Stable ID | Class / final format | Tags / default layer | Starter use | Behavior / collision | Status | Source / rights | Visual acceptance | Final location / definition | Validation evidence |
| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
| `sky_clear_day` | Adaptive vector Sky theme; not `.prop` | N/A; Sky | Living Room; picker | Scene-wide, static; none | Needed | TBD | Appealing blue depth and varied light cloud/sun detail; window gap reads clearly; no edge on pan. | TBD | TBD |
| `sky_cloudy_day` | Adaptive vector Sky theme; not `.prop` | N/A; Sky | Town Street; picker | Scene-wide, static; none | Needed | TBD | Layered, varied clouds leave town silhouettes readable; no obvious tile or edge. | TBD | TBD |
| `sky_sunset` | Adaptive vector Sky theme; not `.prop` | N/A; Sky | Picker | Scene-wide, static; none | Needed | TBD | Warm atmospheric color and sun/cloud composition remain distinct from Clear Day without tinting scene objects. | TBD | TBD |
| `sky_starry_night` | Adaptive vector Sky theme; not `.prop` | N/A; Sky | Picker | Scene-wide, static; none | Needed | TBD | Dark graduated sky, varied stars, and moon remain legible while foreground contrast survives. | TBD | TBD |
### Repeatable vector surface finishes
These are finish definitions and previews, not ordinary Props. Ground finishes change appearance without changing Ground's walkability; wall finishes stay inert.
| Stable ID | Class / final format | Tags / default layer | Starter use | Behavior / collision | Status | Source / rights | Visual acceptance | Final location / definition | Validation evidence |
| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
| `finish_wall_paint` | Vector wall finish | N/A; wall panel | Living Room; picker | Inert panel; none | Needed | TBD | Smooth green room wall and other chosen colors without seams at panel edges. | TBD | TBD |
| `finish_wall_brick` | Repeatable vector wall finish | N/A; wall panel | Picker | Inert panel; none | Needed | TBD | Bricks keep coherent size and joins as panel is resized. | TBD | TBD |
| `finish_wall_wood` | Repeatable vector wall finish | N/A; wall panel | Picker | Inert panel; none | Needed | TBD | Boards read as wood at common Stage zoom and do not stretch with the panel. | TBD | TBD |
| `finish_ground_paint` | Vector Ground finish | N/A; Ground | Picker | Solid, walkable Ground | Needed | TBD | Plain color is clean and visually distinct from wall-only backdrop. | TBD | TBD |
| `finish_ground_brick` | Repeatable vector Ground finish | N/A; Ground | Picker | Solid, walkable Ground | Needed | TBD | Neighboring pieces form a continuous brick surface. | TBD | TBD |
| `finish_ground_wood` | Repeatable vector Ground finish | N/A; Ground | Living Room; picker | Solid, walkable Ground | Needed | TBD | Wood plank floor reads continuously beneath rug and furniture. | TBD | TBD |
| `finish_ground_grass` | Repeatable vector Ground finish | N/A; Ground | Picker | Solid, walkable Ground | Needed | TBD | Grassy surface is readable and not noisy behind feet. | TBD | TBD |
| `finish_ground_dirt` | Repeatable vector Ground finish | N/A; Ground | Picker | Solid, walkable Ground | Needed | TBD | Earthy surface remains distinct from Grass at thumbnail and Stage size. | TBD | TBD |
| `finish_ground_sidewalk` | Repeatable vector Ground finish | N/A; Ground | Town Street; picker | Solid, walkable Ground | Needed | TBD | Light concrete sidewalk forms a broad, aligned run beneath shops. | TBD | TBD |
| `finish_ground_asphalt` | Repeatable vector Ground finish | N/A; Ground | Town Street; picker | Solid, walkable Ground | Needed | TBD | Darker road contrasts with sidewalk while preserving visible character edges. | TBD | TBD |
### Reusable Scenery artwork
Each item below is a `.prop` vector artwork asset offered through Scenery. Its Scenery placement has no collision or interaction even if the artwork is also available through Props. Final visual-only art must not require an invented solid collision shape merely to enter the Scenery catalog.
| Stable ID | Class / final format | Tags / default layer | Starter use | Behavior / collision | Status | Source / rights | Visual acceptance | Final location / definition | Validation evidence |
| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
| `art_city_skyline` | Vector `.prop` cutout | Buildings; Far | Town Street ×1 | Inert; none | Needed | TBD | Long, muted overlapping building silhouette reads as distant and leaves cloud sky visible. | TBD | TBD |
| `art_storefront` | Vector `.prop` cutout | Buildings; Near | Town Street ×1 or more | Inert; none | Needed | TBD | Colorful, readable shop front contrasts with skyline; repeat use does not look like one flat strip. | TBD | TBD |
| `art_town_house` | Vector `.prop` cutout | Buildings; Near | Town Street ×1 or more | Inert; none | Needed | TBD | House silhouette and palette differ clearly from storefront at normal zoom. | TBD | TBD |
| `art_tree` | Vector `.prop` cutout | Nature; Near | Town Street ×2 or more | Inert; none | Needed | TBD | Friendly, readable crown/trunk; repeated copies tolerate size and position variation. | TBD | TBD |
| `art_window_frame` | Vector `.prop` cutout | Walls, Buildings; Near | Living Room ×1 | Inert; none | Needed | TBD | Frame is transparent at its center and aligns over the wall-panel gap to reveal actual Sky. | TBD | TBD |
| `art_fireplace_mantel` | Vector `.prop` cutout | Walls, Objects; Near | Living Room ×1 | Inert; none | Needed | TBD | Fireplace and mantel read clearly as decoration without suggesting this asset itself blocks movement. | TBD | TBD |
| `art_wall_clock` | Vector `.prop` cutout | Walls, Objects; Near | Living Room ×1 | Inert; none | Needed | TBD | Clock reads at room scale without tiny illegible markings. | TBD | TBD |
| `art_bookshelf` | Vector `.prop` cutout | Objects, Walls; Near | Living Room ×1 | Inert; none | Needed | TBD | Shelf, books, and plant details remain legible without dense tracing. | TBD | TBD |
| `art_framed_picture` | Vector `.prop` cutout | Objects, Walls; Near | Living Room ×1 or more | Inert; none | Needed | TBD | Clear frame and image contrast on green paint; reuse can vary size/position. | TBD | TBD |
| `art_potted_plant` | Vector `.prop` cutout | Nature, Objects; Near | Living Room ×1 | Inert; none | Needed | TBD | Leaf and pot shapes remain distinct from wall and furniture. | TBD | TBD |
| `art_rug` | Vector `.prop` cutout | Objects; On the floor | Living Room ×1 | Inert; none | Needed | TBD | Visible over Wood Ground and below furniture/feet; border survives zoom. | TBD | TBD |
### Interactive furniture Props
These are `.prop` assets placed through Props in Living Room. Their authored shape and placement roles must support the stated behavior. The same artwork may still be offered as inert Scenery when deliberately placed through Scenery.
| Stable ID | Class / final format | Tags / default layer | Starter use | Behavior / collision | Status | Source / rights | Visual acceptance | Final location / definition | Validation evidence |
| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
| `prop_couch` | Vector `.prop` furniture | Objects; Prop depth per placement | Living Room ×1 | Still, appropriately character-blocking; valid collision | Needed | TBD | Comfortable couch silhouette sized for stickman and room; no implied Sit action. | TBD | TBD |
| `prop_armchair` | Vector `.prop` furniture | Objects; Prop depth per placement | Living Room ×1 | Still, appropriately character-blocking; valid collision | Needed | TBD | Distinct chair silhouette, matching couch style and character scale. | TBD | TBD |
| `prop_coffee_table` | Vector `.prop` furniture | Objects; Prop depth per placement | Living Room ×1 | Still, can support small props; valid collision | Needed | TBD | Tabletop/support reads clearly and leaves rug visible. | TBD | TBD |
Existing built-in crate and ball artwork may be used in Town Street if an interactive object benefits that starter; no new interactive town asset is required to achieve its visual reference. The artist should establish a coherent cartoon style and scale against the live stickman. Far art is quieter than Near art; outlines and silhouettes must remain clear at selector-thumbnail size and normal Stage zoom. Avoid excessive vector point counts, hard-to-read microdetail, and busy contrast behind character faces or speech bubbles. The source/rights field must distinguish original art, licensed sources, and generated raster concepts. Reference art is not automatically licensed final content.
## Success criteria
1. A child can create Town Street from New Scene, recognize the intended Sky/skyline/shops/trees/sidewalk/road composition at the default view, move or remove any placed piece, save it, reopen it, and keep the same look and behavior.
2. A child can create Living Room from New Scene, see the actual selected Sky through its window gap, change the Sky without tinting the room, restyle the wall or Wood Ground, move the rug and furniture independently, and save/reopen the revised set.
3. A child can place one `.prop` drawing through Scenery and through Props and understand that the two copies behave differently. The Scenery copy never blocks, supports, moves, is picked up or used, or triggers an event in PLAY.
4. Far skyline, Near buildings/trees, Ground, On the floor rug, and Props preserve their intended order through pan, zoom, PLAY, stop, and reload. Character and speech readability survives the four Sky styles and common finishes.
5. Ordinary EDIT selection still reaches foreground characters, Props, and Ground over broad panels. Edit Scenery makes every cutout and panel discoverable and correctable; placement and resize can be canceled before commit. Labels, icons, outlines, and focus reveal state without depending on color alone.
6. Wall panel and Ground finish choices preview correctly; wall patterns do not stretch as panels resize, and adjacent Ground pieces do not show distracting pattern restarts. Changing a palette finish does not repaint existing pieces; a selected piece can be changed deliberately.
7. Older `.scene` documents retain their prior neutral background and Ground appearance when opened. Older untagged `.prop` assets remain visible in All artwork, and a missing referenced Scenery asset yields a visible recovery cue rather than silently removing the child's placement.
8. Every required manifest item reaches **Verified**, with source/rights and final location recorded. A raster concept alone cannot satisfy a final vector content requirement.
## Current project context, risks, and dependencies
- The Stage currently has a World, camera, optional world-space grid, registry-driven palette, Ground/Ramp/Step terrain, Props, and stickmen. Ground is a solid `TerrainBlock`; its drag painter creates independent blocks. Phase 19 must preserve Ground walkability and avoid treating Scenery panels as terrain.
- The current `.scene` save boundary is `StageSceneIO` with mapping in `StageSceneAdapter`. It stores terrain, Props, stickmen, and areas but no Sky, wall panels, or Scenery placements. The accepted design requires additive saved appearance and placement data with legacy defaults; the architect will decide schema and migration details. Persist artwork references through the project's `ContentPaths` conventions.
- The `.prop` format and Prop Editor already author multi-shape vector art. `PropLibrary` currently rejects custom artwork without valid closed collision geometry. Visual-only Scenery must accept legitimate artwork without fake physics. The architect and UI/UX lead should resolve how the same art catalog serves both placement routes and how old untagged art stays discoverable.
- Stage selection currently uses World-object bounds and an overlapping-object chooser. Large wall panels and floor art can otherwise catch almost every click or box selection. A dedicated, forgiving Scenery edit flow and predictable hit order are product requirements. The approved [Stage panel UI system](../specs/stage_panel_ui_system.md) supplies the in-stage inspector visual contract; adapt it for selected Scenery/Ground controls where it fits.
- Finite panels, tiled vector finishes, and detailed Sky motifs must stay crisp and performant across Stage zoom and camera pan. Ground pieces need aligned world-scale patterns. The Town Street and Living Room references should guide composition and mood, not be treated as final licensed assets.
- The two starters require the manifest content as real deliverables. Missing art, inconsistent scale, or raster-only placeholders would leave a child with a visually incomplete feature even if the controls work.
## Remaining questions for the spec-pipeline
These are design and architecture tasks for the specification stage, not unresolved user choices about Phase 19's product goal:
1. What exact UI presents Backgrounds, Artwork tags, placement layer, Ground finish, and Edit Scenery without crowding the existing Stage palette? Define keyboard, touch, narrow-viewport, dismissal, and undo/cancel behavior using the established Stage panel patterns.
2. How will an adaptive Sky fill unbounded camera views while keeping motifs stable and detailed, and how will wall and Ground finishes keep repeatable pattern scale and alignment through resizing, drag painting, and zoom?
3. What saved scene and optional `.prop` tag fields, validation, migration defaults, missing-art recovery, and starter creation flow preserve existing documents and the same artwork's independent Scenery/Prop placements?
4. How will selected panels, cutouts, rugs, Ground, and ordinary objects remain reachable where bounds overlap, including the window gap and a room-sized wall?
5. Which asset production workflow turns concepts into final vector content, records provenance, checks collision only for interactive Props, and advances each manifest row through integration and verification?
The [live feature backlog](../ideas/feature_backlog.md) tracks the separate follow-up work, including multi-level walkable scenes and whole-scene visual filters.
@@ -1,11 +1,11 @@
# Phase 4.5a — Collision Bodies, Physics Events, and Hit Reactions
Source plan: `plans/PHASE_4.5_COLLISION_BODIES.md`
Source plan: `docs/archive/plans/PHASE_4.5_COLLISION_BODIES.md`
## Scope and verified corrections
Implement the feature in `scripts/stickman_rig.gd`, `scripts/prop_block.gd`, and
`scripts/sandbox_stage.gd`. Add a focused headless regression script, but no scene, `.stk`,
Implement the feature in `scripts/characters/stickman/stickman_rig.gd`, `scripts/props/prop_block.gd`, and
`scripts/editors/stage/sandbox_stage.gd`. Add a focused headless regression script, but no scene, `.stk`,
registry, rule-payload, layout, or theme changes.
The implementation must correct these plan/code mismatches:
@@ -354,7 +354,7 @@ capsule/circle proxies rather than per-shape polygons and keeps reaction timing
post-head-pin physics slot.
After editor verification, append these deferred trade-offs to
`docs/tech_debt_and_optimizations.md`: fixed proxy radii do not follow arbitrary mounted widths;
`docs/architecture/tech_debt_and_optimizations.md`: fixed proxy radii do not follow arbitrary mounted widths;
the head radius is fixed although its center follows geometry; signal-time direct state may use
the documented fallback on rare timing edges; contact speed is total prop speed rather than
normal relative speed/impulse; and the post-IK reaction slot remains engine-order sensitive.
@@ -120,7 +120,7 @@ Menu id: 0=Line, 1=Rectangle, 2=Circle, 3=Create Point (only shown in vertex con
4. Append `1` to `vertex_flags` at the corresponding position.
5. `queue_redraw()` and `emit shape_changed`.
*Note: PROJECT.md says "a small hollow rectangle will appear in the outline where the user clicked — this is not a point(vertex)". I interpret this to mean: the vertex is placed on the outline at the nearest edge midpoint. The "not a point" language means it's a vertex, just drawn differently (hollow rectangle).*
*Note: docs/archive/PROJECT.md says "a small hollow rectangle will appear in the outline where the user clicked — this is not a point(vertex)". I interpret this to mean: the vertex is placed on the outline at the nearest edge midpoint. The "not a point" language means it's a vertex, just drawn differently (hollow rectangle).*
### 2g. Vertex Dragging
@@ -207,12 +207,12 @@ Add a `deselect()` method to `BodyPartPanel` that sets `_selected=false` and `qu
| File | Changes |
|---|---|
| `scripts/body_part_panel.gd` | Major: vertex editing state, selection, zoom, context menu refactor, rendering with closed flag & vertex_flags, circle→12 vertices, mouse handling expanded |
| `scripts/stickman_editor.gd` | Version bump to 1.1, save format includes closed/vertex_flags, load handles 1.0+1.1, selection coordination signal |
| `scripts/whole_stickman_preview.gd` | Zoom support, closed-based rendering instead of shape_type-based, input coordinate transform |
| `scenes/body_part_panel.tscn` | Minimal — ContextMenu items may be reduced or left as-is (dynamic repopulation in script). *Consider adding a 4th static item "Create Point" hidden by default.* |
| `scripts/editors/stickman/body_part_panel.gd` | Major: vertex editing state, selection, zoom, context menu refactor, rendering with closed flag & vertex_flags, circle→12 vertices, mouse handling expanded |
| `scripts/editors/stickman/stickman_editor.gd` | Version bump to 1.1, save format includes closed/vertex_flags, load handles 1.0+1.1, selection coordination signal |
| `scripts/editors/stickman/whole_stickman_preview.gd` | Zoom support, closed-based rendering instead of shape_type-based, input coordinate transform |
| `scenes/editors/stickman/body_part_panel.tscn` | Minimal — ContextMenu items may be reduced or left as-is (dynamic repopulation in script). *Consider adding a 4th static item "Create Point" hidden by default.* |
| `README.md` | Document Phase 2 features, updated .stk format, vertex editing, zoom |
| `docs/phase2_spec.md` | This file |
| `docs/specs/phase2_spec.md` | This file |
---
@@ -475,7 +475,7 @@ func _on_view_menu_id_pressed(id: int) -> void:
## 5. Scene File Changes
### 5a. `scenes/stickman_editor.tscn`
### 5a. `scenes/editors/stickman/stickman_editor.tscn`
Add new nodes under the root `StickmanEditor`:
@@ -502,7 +502,7 @@ step = 1.0
rounded = true
```
### 5b. `scenes/body_part_panel.tscn`
### 5b. `scenes/editors/stickman/body_part_panel.tscn`
Add a ColorPicker popup:
@@ -538,12 +538,12 @@ text = "OK"
| File | Changes |
|---|---|
| `scripts/body_part_panel.gd` | Major: panning (middle mouse), grid drawing, snap-to-grid in vertex drag, ColorPicker integration, "Delete" shape, "Remove Point" vertex delete, context menu refactor, reset_view(), set_grid_size(), set_snap_enabled() |
| `scripts/stickman_editor.gd` | Modular: Edit/View menus, GridConfigDialog, settings.json load/save, broadcast grid/snap settings, Reset Views handler |
| `scripts/whole_stickman_preview.gd` | Major: panning, grid drawing, snap-to-grid in part drag, reset_view(), set_grid_size(), set_snap_enabled() |
| `scenes/stickman_editor.tscn` | Add GridConfigDialog + children |
| `scenes/body_part_panel.tscn` | Add ColorPickerPopup + children |
| `docs/phase3_spec.md` | This file |
| `scripts/editors/stickman/body_part_panel.gd` | Major: panning (middle mouse), grid drawing, snap-to-grid in vertex drag, ColorPicker integration, "Delete" shape, "Remove Point" vertex delete, context menu refactor, reset_view(), set_grid_size(), set_snap_enabled() |
| `scripts/editors/stickman/stickman_editor.gd` | Modular: Edit/View menus, GridConfigDialog, settings.json load/save, broadcast grid/snap settings, Reset Views handler |
| `scripts/editors/stickman/whole_stickman_preview.gd` | Major: panning, grid drawing, snap-to-grid in part drag, reset_view(), set_grid_size(), set_snap_enabled() |
| `scenes/editors/stickman/stickman_editor.tscn` | Add GridConfigDialog + children |
| `scenes/editors/stickman/body_part_panel.tscn` | Add ColorPickerPopup + children |
| `docs/specs/phase3_spec.md` | This file |
---
@@ -2,13 +2,13 @@
## Overview
This round fixes three user-reported bugs in the **pose silhouette guide** introduced in Phase 7 (see `BUGS.md`, "Stickman editor (Phase 7 Round 1)"):
This round fixes three user-reported bugs in the **pose silhouette guide** introduced in Phase 7 (see `docs/known_issues.md`, "Stickman editor (Phase 7 Round 1)"):
1. **Silhouette starting position** — on startup the guide is anchored at a fixed world position (`GUIDE_OFFSET = (170, 580)`) that puts the figure up-left of the preview, forcing the user to pan over to it. The guide should start **centered** in the Whole Stickman view.
2. **Menu label never changes** — View → "Show Pose Guide" toggles visibility but its text stays "Show Pose Guide" regardless of state. It must read **"Hide Pose Guide"** when visible and **"Show Pose Guide"** when hidden.
3. **Guide renders behind parts** — the guide is drawn *below* user parts, so it cannot be used to judge how well parts align to the joints/limbs. It should "ghost" *in front of* the parts.
All three are confined to two scripts (`scripts/whole_stickman_preview.gd`, `scripts/stickman_editor.gd`) and touch no `.stk` format and no `settings.json` schema (the `show_pose_guide` key already exists). **No `.tscn` changes are required.**
All three are confined to two scripts (`scripts/editors/stickman/whole_stickman_preview.gd`, `scripts/editors/stickman/stickman_editor.gd`) and touch no `.stk` format and no `settings.json` schema (the `show_pose_guide` key already exists). **No `.tscn` changes are required.**
Key behavior that is **preserved** (must not regress):
@@ -35,7 +35,7 @@ Why this approach:
- **Minimal** — a one-line change to `_guide_to_preview()` plus a renamed constant; no new state, no camera/timing machinery.
- **Preserves world-fixture semantics** — the guide is *not* screen-locked; it still moves with pan/zoom. "Centered" is true at the default view and after `Reset Views`, and the user can still pan/zoom the guide anywhere afterward.
### 1c. Code changes (`scripts/whole_stickman_preview.gd`)
### 1c. Code changes (`scripts/editors/stickman/whole_stickman_preview.gd`)
Replace the constant (line 48):
@@ -104,7 +104,7 @@ Make the label reflect the **action** the click will perform, per the user's exa
**User decision: drop the checkmark.** The item is text-only; the action wording itself communicates the current state. Remove all `set_item_checked` usage for this item (setup + refresh).
### 2c. Code changes (`scripts/stickman_editor.gd`)
### 2c. Code changes (`scripts/editors/stickman/stickman_editor.gd`)
Replace `_guide_menu_label()` (lines 271–272):
@@ -184,7 +184,7 @@ Move `_draw_silhouette_guide()` to draw **after** the part loop but **before** t
- **Drag highlight above the guide** — the yellow drag rect (drawn during `Interaction.TRANSLATE`) is also an affordance; keep it above the guide for the same reason. (Draw guide at step 5, before the highlight at step 6, so highlight + gizmos remain on top.)
- **Labels** — part labels (`H`, `T`, `LUA`, …) are drawn inside the part loop (step 4), so the guide may overlap them slightly. This is acceptable: labels are small, and the guide is semi-transparent. (Splitting labels into a later pass is not worth the complexity.)
### 3c. Code changes (`scripts/whole_stickman_preview.gd`)
### 3c. Code changes (`scripts/editors/stickman/whole_stickman_preview.gd`)
Delete line 322 (`_draw_silhouette_guide()` from its current position) and insert it after the part loop, immediately before the "Highlight dragged part" block (i.e., between line 384 and line 386):
@@ -211,9 +211,9 @@ The guide is pure `draw_*` calls and participates in **no** input logic. Hit-tes
| File | Changes |
|---|---|
| `scripts/whole_stickman_preview.gd` | Replace `GUIDE_OFFSET` const with `GUIDE_FIGURE_CENTER`; re-center in `_guide_to_preview()`; move `_draw_silhouette_guide()` call after the part loop (before the drag highlight). |
| `scripts/stickman_editor.gd` | Make `_guide_menu_label()` dynamic; refresh text in `_update_guide_menu_item()` and drop the checkmark (`set_item_checked` calls removed); add `_update_guide_menu_item()` call in `_load_settings()`. |
| `docs/phase7_round1_spec.md` | This file. |
| `scripts/editors/stickman/whole_stickman_preview.gd` | Replace `GUIDE_OFFSET` const with `GUIDE_FIGURE_CENTER`; re-center in `_guide_to_preview()`; move `_draw_silhouette_guide()` call after the part loop (before the drag highlight). |
| `scripts/editors/stickman/stickman_editor.gd` | Make `_guide_menu_label()` dynamic; refresh text in `_update_guide_menu_item()` and drop the checkmark (`set_item_checked` calls removed); add `_update_guide_menu_item()` call in `_load_settings()`. |
| `docs/specs/phase7_round1_spec.md` | This file. |
| `README.md` | Update §5 "Pose Silhouette Guide" (centering behavior, "ghosts in front of parts", dynamic menu label) and the menu-bar structure diagram (§ "Menu bar structure"). |
| `AGENTS.md` | Update the Phase 7 notes: `GUIDE_OFFSET (170, 580)` → dynamic centering via `GUIDE_FIGURE_CENTER`; "renders above the grid but below user parts" → "above the grid and above user parts, below selection gizmos"; "Show Pose Guide" → dynamic "Hide/Show Pose Guide" label. |
@@ -264,8 +264,8 @@ There is no automated test suite; verification is manual in the editor plus the
## 8. Recommended Implementation Order
1. `scripts/whole_stickman_preview.gd` — Bug 1 centering (`GUIDE_FIGURE_CENTER` + `_guide_to_preview`).
2. `scripts/whole_stickman_preview.gd` — Bug 3 reorder (`_draw_silhouette_guide()` move).
3. `scripts/stickman_editor.gd` — Bug 2 dynamic label + `_update_guide_menu_item()` + `_load_settings()` sync.
1. `scripts/editors/stickman/whole_stickman_preview.gd` — Bug 1 centering (`GUIDE_FIGURE_CENTER` + `_guide_to_preview`).
2. `scripts/editors/stickman/whole_stickman_preview.gd` — Bug 3 reorder (`_draw_silhouette_guide()` move).
3. `scripts/editors/stickman/stickman_editor.gd` — Bug 2 dynamic label + `_update_guide_menu_item()` + `_load_settings()` sync.
4. Manual verification (§6) + `--headless --check-only --quit`.
5. Doc updates (`README.md`, `AGENTS.md`).
@@ -60,7 +60,7 @@ The rest transforms are static authored data; reading them at runtime buys nothi
## 2. WholeStickmanPreview — Rendering
All changes live in `scripts/whole_stickman_preview.gd`. No new scene/node is required (the guide is a `_draw()` overlay, mirroring how `_draw_grid()` is already inlined).
All changes live in `scripts/editors/stickman/whole_stickman_preview.gd`. No new scene/node is required (the guide is a `_draw()` overlay, mirroring how `_draw_grid()` is already inlined).
### 2a. New constants
@@ -116,7 +116,7 @@ const GUIDE_BONES: Array = [
]
```
> The 13 joint names in `GUIDE_JOINTS` are exactly the key pivot locations required by PROJECT.md. `GUIDE_SCALE` and `GUIDE_OFFSET` are the only tunables mapping master-rig coordinates into preview world space (see §3 for the chosen values).
> The 13 joint names in `GUIDE_JOINTS` are exactly the key pivot locations required by docs/archive/PROJECT.md. `GUIDE_SCALE` and `GUIDE_OFFSET` are the only tunables mapping master-rig coordinates into preview world space (see §3 for the chosen values).
### 2b. New state + public API
@@ -351,9 +351,9 @@ Because `_broadcast_settings()` is already called in `_ready()` (after `_load_se
| File | Changes |
|---|---|
| `scripts/whole_stickman_preview.gd` | Add guide constants, `_show_guide` state, `set_show_guide()`, `_draw_silhouette_guide()` + `_guide_to_preview()`/`_guide_color()`/`_guide_joint_color()` helpers; call guide draw between grid and parts in `_on_preview_draw()`. |
| `scripts/stickman_editor.gd` | Add `_show_guide` state, `_view_menu` member, "Show Pose Guide" checkable View menu item (id 1), `_on_view_menu_about_to_popup`/`_update_guide_menu_item`, settings key `show_pose_guide`, broadcast to preview. |
| `docs/phase7_spec.md` | This file. |
| `scripts/editors/stickman/whole_stickman_preview.gd` | Add guide constants, `_show_guide` state, `set_show_guide()`, `_draw_silhouette_guide()` + `_guide_to_preview()`/`_guide_color()`/`_guide_joint_color()` helpers; call guide draw between grid and parts in `_on_preview_draw()`. |
| `scripts/editors/stickman/stickman_editor.gd` | Add `_show_guide` state, `_view_menu` member, "Show Pose Guide" checkable View menu item (id 1), `_on_view_menu_about_to_popup`/`_update_guide_menu_item`, settings key `show_pose_guide`, broadcast to preview. |
| `docs/specs/phase7_spec.md` | This file. |
| `README.md` | Document the guide, the View menu item, and the settings key (see §7). |
No `.tscn` changes are required.
@@ -376,7 +376,7 @@ No `.tscn` changes are required.
## 7. Testing / Verification
There is no automated test suite in the repo (only a sample figure `stickmen/test.stk`). Verification is manual in the Godot editor, plus the project parse check:
There is no automated test suite in the repo (only a sample figure `content/stickmen/test.stk`). Verification is manual in the Godot editor, plus the project parse check:
1. **Load check** — run the project; no parse errors; main scene opens.
2. **Toggle** — View → Show Pose Guide is checked by default and the guide is visible; clicking it unchecks it and hides the guide; clicking again re-shows it.
@@ -14,9 +14,9 @@ Scope is intentionally tight:
Key facts established from exploration (cited throughout):
- The Phase 7 silhouette guide already hardcodes the 13 rest-pose joint coordinates of `master_rig.tscn` as `GUIDE_JOINTS` in `scripts/whole_stickman_preview.gd:59-73`. These are the **same pivots** the adapter's bone-fitting targets.
- The Phase 7 silhouette guide already hardcodes the 13 rest-pose joint coordinates of `master_rig.tscn` as `GUIDE_JOINTS` in `scripts/editors/stickman/whole_stickman_preview.gd:59-73`. These are the **same pivots** the adapter's bone-fitting targets.
- `master_rig.tscn`'s bone names/paths match the Phase 8 requirement text exactly (`Skeleton2D/Torso/LeftUpperArm`, `LeftLowerArm`, `RightUpperArm`, `RightLowerArm`, `LeftUpperLeg`, `LeftLowerLeg`, `RightUpperLeg`, `RightLowerLeg`, `IK_Targets/Left_Leg`, `IK_Targets/Right_Leg`, `IK_Targets/Left_Hand`, `IK_Targets/Right_Hand`).
- The editor already serializes per-part `shapes`/`position`/`rotation`/`scale` in `scripts/stickman_editor.gd:363-394`.
- The editor already serializes per-part `shapes`/`position`/`rotation`/`scale` in `scripts/editors/stickman/stickman_editor.gd:363-394`.
---
@@ -111,7 +111,7 @@ Notes (reproducible):
- **Bone length vs. joint distance.** `upper_arm_length` is the *bone length* `168.0`, not the literal `|shoulder - elbow| = sqrt(168² + 8²) ≈ 168.19`. The 8 px vertical offset comes from the arm bone's `rotation = 0.0477` (`master_rig.tscn:170`), which the adapter does **not** touch. Writing `168.0` reproduces the rest pose exactly when the adapter sets `LeftUpperArm.length = 168.0` and leaves `rotation` alone. (Writing `168.19` would over-extend the bone.) The requirement's "distance between shoulder and elbow" is treated as the bone length.
- **`torso_length`** has no corresponding single bone (the spine is the `Head` bone authored at `y = -391.5`); it is informational in `proportions` (no adapter bone op consumes it).
- **`RightUpperLeg.length = 90.0` inconsistency.** `master_rig.tscn:245` authors the right upper leg bone at `90.0` while the left is `200.0` (`:222`) and `master_rig2.tscn:262` fixes the right to `200.0`. Use **200.0** (the symmetric/corrected value). The adapter overwrites both legs anyway, so the discrepancy is moot after fitting — but it is flagged in §5f.
- **`RightUpperLeg.length = 90.0` inconsistency.** `master_rig.tscn:245` authors the right upper leg bone at `90.0` while the left is `200.0` (`:222`) and `scenes/legacy/master_rig2.tscn:262` fixes the right to `200.0`. Use **200.0** (the symmetric/corrected value). The adapter overwrites both legs anyway, so the discrepancy is moot after fitting — but it is flagged in §5f.
---
@@ -150,7 +150,7 @@ Empty part (no shapes, or all shapes with <2 points) → `pivot {0,0}`, `length
---
## 4. Editor changes (`scripts/stickman_editor.gd`)
## 4. Editor changes (`scripts/editors/stickman/stickman_editor.gd`)
### 4a. New constants
@@ -240,7 +240,7 @@ No change to `_on_save_file_selected` (`:287-300`) beyond what the above touches
### 5a. Class shape & API
New file `res://scripts/stk_rig_adapter.gd`:
New file `res://scripts/characters/stickman/stk_rig_adapter.gd`:
```gdscript
class_name StkRigAdapter
@@ -346,15 +346,15 @@ For each of the 10 part keys, mount the part's `.stk` shapes into the correspond
> The coordinate mapping here is the most under-specified piece of the requirement. §5e defines a concrete, deterministic v1 (translate `pivot`→origin, scale along the segment axis to the bone length). The fine-grained fidelity (matching the user's in-preview `rotation`/`scale`/`position` exactly) is intentionally left to the runtime consumer — see §10 Q3.
### 5f. `master_rig.tscn` vs `master_rig2.tscn`
### 5f. `master_rig.tscn` vs `scenes/legacy/master_rig2.tscn`
- The adapter targets **`master_rig.tscn`** (the clean scene Phase 7 derived `GUIDE_JOINTS` from). It has **no** `metadata/_local_pose_override_enabled_` on its bones.
- `master_rig2.tscn` is a variant that (a) adds `metadata/_local_pose_override_enabled_ = true` to every bone, (b) fixes `RightUpperLeg.length` from `90.0` → `200.0` (`master_rig2.tscn:262`), and (c) explicitly sets `enabled = true` on the modification stack. `clear_pose.gd` (an `EditorScript`) exists to strip the pose-override metadata and reset bone scales to `Vector2.ONE`/`rest`.
- `scenes/legacy/master_rig2.tscn` is a variant that (a) adds `metadata/_local_pose_override_enabled_ = true` to every bone, (b) fixes `RightUpperLeg.length` from `90.0` → `200.0` (`scenes/legacy/master_rig2.tscn:262`), and (c) explicitly sets `enabled = true` on the modification stack. `clear_pose.gd` (an `EditorScript`) exists to strip the pose-override metadata and reset bone scales to `Vector2.ONE`/`rest`.
- **Implication:** the adapter is written against `master_rig.tscn` node names (identical in both) and overwrites lengths anyway, so it works with either — but the `90.0` right-leg value in `master_rig.tscn` is a latent bug the adapter must not rely on (it writes `RightUpperLeg.length` from `proportions`, §5c).
### 5g. Editor does **not** verify/import the adapter
The editor only *produces* data the adapter *consumes*. There is no editor→adapter reference, no `preload("res://scripts/stk_rig_adapter.gd")`, no scene that imports it. "Verification" is a standalone concern: the adapter is syntax-checked with the project parse check and (optionally) a future headless smoke test (§8). This keeps the editor decoupled from the runtime pipeline.
The editor only *produces* data the adapter *consumes*. There is no editor→adapter reference, no `preload("res://scripts/characters/stickman/stk_rig_adapter.gd")`, no scene that imports it. "Verification" is a standalone concern: the adapter is syntax-checked with the project parse check and (optionally) a future headless smoke test (§8). This keeps the editor decoupled from the runtime pipeline.
---
@@ -362,9 +362,9 @@ The editor only *produces* data the adapter *consumes*. There is no editor→ada
| File | Changes |
|---|---|
| `scripts/stickman_editor.gd` | `FILE_VERSION` → `"1.4"`; add `"1.4"` to `SUPPORTED_VERSIONS`; add `PROPORTIONS` const + `X_AXIS_PARTS` const; add `_compute_part_pivot_length()`; extend `_collect_all_shape_data()` with `pivot`/`length`; add `proportions` to `_build_json_data()`. |
| `scripts/stk_rig_adapter.gd` | **New.** `StkRigAdapter` (`RefCounted`) with `static func apply(stk_data, rig)` + `_fit_bones` / `_recalibrate_ik` / `_mount_shapes` + node-path consts (§5). |
| `docs/phase8_spec.md` | This file. |
| `scripts/editors/stickman/stickman_editor.gd` | `FILE_VERSION` → `"1.4"`; add `"1.4"` to `SUPPORTED_VERSIONS`; add `PROPORTIONS` const + `X_AXIS_PARTS` const; add `_compute_part_pivot_length()`; extend `_collect_all_shape_data()` with `pivot`/`length`; add `proportions` to `_build_json_data()`. |
| `scripts/characters/stickman/stk_rig_adapter.gd` | **New.** `StkRigAdapter` (`RefCounted`) with `static func apply(stk_data, rig)` + `_fit_bones` / `_recalibrate_ik` / `_mount_shapes` + node-path consts (§5). |
| `docs/specs/phase8_spec.md` | This file. |
| `README.md` | Document the `.stk` v1.4 `proportions`/`pivot`/`length` keys, the `StkRigAdapter.gd` script, and the runtime-pipeline note (§11). |
| `AGENTS.md` | Add a Phase 8 note (v1.4 export + `StkRigAdapter.gd`). |
@@ -392,10 +392,10 @@ There is no automated test suite in the repo (no `test/` directory, no GUT addon
```
..\Godot_v4.7.1-stable_win64_console.exe . --headless --check-only --quit
```
> The plain `--check-only` form hangs on renderer init in 4.7.x; use `--headless --check-only --quit` (as established in `docs/phase7_round1_spec.md` §6). The user's stated command `..\Godot_v4.7.1-stable_win64_console.exe . --check-only` is equivalent in intent but should include `--headless --quit`.
> The plain `--check-only` form hangs on renderer init in 4.7.x; use `--headless --check-only --quit` (as established in `docs/specs/phase7_round1_spec.md` §6). The user's stated command `..\Godot_v4.7.1-stable_win64_console.exe . --check-only` is equivalent in intent but should include `--headless --quit`.
2. **Save emits v1.4** — File → Save; inspect the `.stk`: `version == "1.4"`, a top-level `proportions` with the 5 values (`168.0/200.0/200.0/200.0/391.5`), and every `body_parts.*` entry has `pivot {x,y}` + `length` (arm parts' `length` = bbox width; leg/torso/head = bbox height).
3. **Empty part** — a part with no shapes writes `pivot {0,0}` / `length 0.0`; no crash.
4. **Backward-compat load** — load `stickmen/basic.stk` (v1.0) and `stickmen/test.stk` (v1.1); no error; save; result is v1.4 with computed pivot/length/proportions.
4. **Backward-compat load** — load `content/stickmen/basic.stk` (v1.0) and `content/stickmen/test.stk` (v1.1); no error; save; result is v1.4 with computed pivot/length/proportions.
5. **Adapter smoke test (standalone, optional/headless)** — a small `--headless` SceneTree script that instantiates `master_rig.tscn`, calls `StkRigAdapter.apply(sample_stk, rig)`, and asserts: `LeftUpperArm.length == 168.0`, `LeftLowerArm.position.x == -168.0`, `LeftUpperLeg.length == 200.0`, `IK_Targets/Left_Leg.position.y == 400.0`, `IK_Targets/Left_Hand.position == (-168.0, -456.0)`. (Add under `test/` later if GUT is introduced; out of scope for this phase.)
---
@@ -408,7 +408,7 @@ There is no automated test suite in the repo (no `test/` directory, no GUT addon
| D2 | `pivot` = local bbox center; `length` = axis-specific extent (arms→Δx, legs/torso/head→Δy) (§3) | Matches the editor's actual rotation pivot and the adapter's `position.x` vs `position.y` bone convention. |
| D3 | Version `"1.3" → "1.4"`, write-only metadata, no load migration (§1) | `pivot`/`length`/`proportions` are recomputed on save; old files load unchanged and gain keys on next save. |
| D4 | `StkRigAdapter` = `RefCounted` static `apply()`, standalone script (§5a) | No scene/autoload dependency; consumed by a future runtime pipeline, never by the editor. |
| D5 | Adapter targets `master_rig.tscn` (not `master_rig2.tscn`) (§5f) | It's the clean scene Phase 7 derived `GUIDE_JOINTS` from; `master_rig2` is a pose-override variant with a fixed right-leg length. |
| D5 | Adapter targets `master_rig.tscn` (not `scenes/legacy/master_rig2.tscn`) (§5f) | It's the clean scene Phase 7 derived `GUIDE_JOINTS` from; `master_rig2` is a pose-override variant with a fixed right-leg length. |
| D6 | Visual mount = update existing `Body/*` nodes in place (§5e) | Preserves `RemoteTransform2D` driving and node names; `.stk` shape → `Line2D`/`Polygon2D` with pivot→origin + length-normalization. |
| D7 | Editor never imports/verifies the adapter (§5g) | Keeps the editor decoupled; the adapter is verified standalone (parse check + optional headless smoke test). |
| D8 | Bone fitting adds lower-bone `.length` writes beyond the requirement's literal text (§5c) | Without it the lower limbs don't scale; the addition is a safe no-op at default proportions (flagged Q2). |
@@ -428,16 +428,16 @@ There is no automated test suite in the repo (no `test/` directory, no GUT addon
## 11. README / AGENTS Updates
- **README §"File format (`.stk`)"** — bump the example to `"1.4"`; add the top-level `proportions` table and the per-part `pivot`/`length` rows to the Part object table; update the migration note (`v1.0–v1.3` auto-migrate; pivot/length/proportions recomputed on save).
- **README new subsection (or §"Project structure")** — document `scripts/stk_rig_adapter.gd`: its `static apply()` API, that it fits an instantiated `master_rig.tscn` to a loaded `.stk`, and that it is a runtime-pipeline utility (not used by the editor).
- **AGENTS.md** — add a Phase 8 note: `FILE_VERSION "1.4"`; top-level `proportions` + per-part `pivot`/`length` computed on save in `stickman_editor.gd`; `scripts/stk_rig_adapter.gd` (`class_name StkRigAdapter`) as a standalone runtime adapter targeting `master_rig.tscn`.
- **README new subsection (or §"Project structure")** — document `scripts/characters/stickman/stk_rig_adapter.gd`: its `static apply()` API, that it fits an instantiated `master_rig.tscn` to a loaded `.stk`, and that it is a runtime-pipeline utility (not used by the editor).
- **AGENTS.md** — add a Phase 8 note: `FILE_VERSION "1.4"`; top-level `proportions` + per-part `pivot`/`length` computed on save in `stickman_editor.gd`; `scripts/characters/stickman/stk_rig_adapter.gd` (`class_name StkRigAdapter`) as a standalone runtime adapter targeting `master_rig.tscn`.
---
## 12. Recommended Implementation Order
1. `scripts/stickman_editor.gd` — constants (`FILE_VERSION`, `SUPPORTED_VERSIONS`, `PROPORTIONS`, `X_AXIS_PARTS`) + `_compute_part_pivot_length()`.
2. `scripts/stickman_editor.gd` — wire `pivot`/`length` into `_collect_all_shape_data()` and `proportions` into `_build_json_data()`.
3. `scripts/stk_rig_adapter.gd` — `apply()` + `_fit_bones()` + `_recalibrate_ik()` + `_mount_shapes()` with §5b paths.
1. `scripts/editors/stickman/stickman_editor.gd` — constants (`FILE_VERSION`, `SUPPORTED_VERSIONS`, `PROPORTIONS`, `X_AXIS_PARTS`) + `_compute_part_pivot_length()`.
2. `scripts/editors/stickman/stickman_editor.gd` — wire `pivot`/`length` into `_collect_all_shape_data()` and `proportions` into `_build_json_data()`.
3. `scripts/characters/stickman/stk_rig_adapter.gd` — `apply()` + `_fit_bones()` + `_recalibrate_ik()` + `_mount_shapes()` with §5b paths.
4. Manual verification (§8) + `--headless --check-only --quit`.
5. Optional headless adapter smoke test.
6. Doc updates (`README.md`, `AGENTS.md`).

Some files were not shown because too many files have changed in this diff Show More