Author SHA1 Message Date
ryan 043f114083 Implement interactive prop handling for StickmanRig
- Added PropTrajectory class for deterministic ballistic calculations.
- Introduced StickmanPropInteraction component to manage prop interactions (pickup, drop, throw).
- Implemented grip resolution and attachment logic for props.
- Added signals for interaction events (prop_picked_up, prop_dropped, prop_thrown).
- Defined specifications for prop interaction lifecycle and event rules.
- Ensured compatibility with existing prop systems and added new capabilities for prop pickup.
2026-09-23 21:26:13 -04:00
603 changed files with 9934 additions and 117075 deletions
+6 -19
View File
@@ -1,6 +1,6 @@
name = "architect"
description = "Evaluates architectural choices, proposes Godot 4 best practices, asks interactive clarification questions, and writes implementation specs."
model = "gpt-6-sol"
model = "gpt-5.6-sol"
model_reasoning_effort = "high"
sandbox_mode = "read-only"
@@ -11,27 +11,14 @@ You are the Lead Systems Architect specializing in Godot 4 and idiomatic GDScrip
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`.
## Feature worktree
Before writing a feature document, confirm that the orchestrator has asked the user whether this task should use a Git worktree and supplied the selected project path and branch. If the user has not chosen yet, ask the orchestrator to get that preference before writing. Use the supplied path whether it is a worktree or the current checkout; do not create a worktree or switch branches yourself. Follow `AGENTS.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.
4. **Log:** Append trade-offs to `docs/tech_debt_and_optimizations.md`.
---
## 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.
Unless explicitly instructed by the prompt to "Skip clarification and build spec immediately," DO NOT write to `specs/[feature-name].md` during your first response.
Instead, analyze the requirement and output a **Clarification & Architecture Brief**:
@@ -51,7 +38,7 @@ Instead, analyze the requirement and output a **Clarification & Architecture Bri
### 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:
1. **Generate Spec:** Output the complete specification into `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.
@@ -59,7 +46,7 @@ Once the user responds to Phase 1 (or approves the pitch), perform the following
- **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`.
2. **Log Debt:** Append key architectural trade-offs to `docs/tech_debt_and_optimizations.md`.
3. **Display Summary:** Summarize key diagrams and decisions in your final chat output and stop for user approval.
"""
"""
-62
View File
@@ -1,62 +0,0 @@
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.
- For Stage-usable `.prop` artwork, infer a sensible everyday size from the named subject and intended use, using the current standard stickman guide as the ruler. For example, an ordinary refrigerator is roughly 0.85–1 standard stickman height; a toy, giant, or distant version is an intentional exception. Author the geometry at its intended Stage size so a new instance reads correctly at scale 1; do not treat the Prop Editor's fixed 96-unit Normalize Size target as a general Stage size. Ask about scale only when the intent is ambiguous or deliberately unusual.
- 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
Before creating an asset review bundle, confirm that the orchestrator has asked the user whether this task should use a Git worktree and supplied the selected project path and branch. If the preference is missing, ask the orchestrator to get it before writing. Use the selected path whether it is a worktree or the current checkout; do not create a worktree or switch branches yourself. Follow `AGENTS.md`.
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.
For Stage-usable `.prop` artwork, the representative preview must include a scale check against the current standard stickman guide, composited visibly in front of the art, plus a ground/contact reference where relevant. Record the inferred intended size in `review.md` as an approximate fraction of standard stickman height, along with any assumptions or intentional perspective/oversize choices. Check outline and detail readability at normal Stage zoom after sizing; geometry scaling alone may leave line weights too thin or too heavy.
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.
"""
-47
View File
@@ -1,47 +0,0 @@
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
- **Worktree choice:** Before preparing an accepted phase brief or updating the backlog, confirm that the orchestrator has asked the user whether this task should use a Git worktree and supplied the selected project path and branch. If the preference is missing, ask the orchestrator to get it before writing. Use the supplied path whether it is a worktree or the current checkout. Do not create a worktree, switch branches, or write into another checkout yourself. Follow `AGENTS.md`.
- 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 -7
View File
@@ -1,7 +1,7 @@
name = "developer"
description = "Implements GDScript logic and scene adjustments."
model = "gpt-6-luna"
model_reasoning_effort = "max"
model = "gpt-5.6-terra"
model_reasoning_effort = "medium"
sandbox_mode = "workspace-write"
developer_instructions = """
@@ -9,15 +9,14 @@ You are a Godot 4 / GDScript 2.0 implementation developer. Adhere to coding stan
## Rules
- **Worktree choice:** Before persistent edits, confirm that the orchestrator has asked the user whether this task should use a Git worktree and supplied the selected project path and branch. If the preference is missing, ask the orchestrator to get it before editing. Use the supplied path whether it is a worktree or the current checkout. Do not create a worktree, switch branches, merge, or edit another checkout yourself. Follow `AGENTS.md` for the selected path.
- **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:** 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.
- **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"`
## Output Format
- **Summary:** 1-2 lines on changes made.
- **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.
"""
- **Code Edits:** Show only modified snippet/function blocks with surrounding context lines.
"""
-95
View File
@@ -1,95 +0,0 @@
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 = """
## Worktree choice
You are read-only and must not create worktrees or write project files. For any handoff that will create project files, confirm that the orchestrator has asked the user whether to use a worktree and supplied the selected project path and branch. Do not require a worktree when the user chooses the current checkout.
## 0. Editor Visual Style Guide
Before making, mocking, reviewing, or suggesting any Prop Editor or Stickman Editor UI/UX
change, read `docs/guides/editor_visual_style_guide.md`. Use its documented implemented
patterns and shared theme ownership as the starting point, then check relevant feature specs
and current controls for workflow-specific behavior. Clearly distinguish current behavior
from recommendations. If a proposal intentionally departs from the guide, explain why and
identify the departure in the proposal or spec. Follow explicit user direction when it differs.
## 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.
* **Dialogs & Popups:** Before designing a child-facing dialog, read
`docs/specs/dialog_design_principles.md`. Carry its rounded controls, thicker borders, blue
primary actions with bold white labels, comfortable button spacing, and large readable type
into the proposed UI. Prefer visible, labeled dismissal actions such as Cancel, Done, or Back;
do not default to a small corner × as the only way to close a dialog. Use the Choose Scenery
dialog and its theme tokens as the current implementation reference.
* **Stable Dialog Anchoring:** When tabs, filters, or other in-place choices change a dialog's
size, keep its current anchor so the clicked control stays under the pointer. Grow from a
stable edge while space allows; when the safe viewport is reached, cap the dialog and scroll
its content. Nudge it only as much as needed to keep essential controls visible. Do not
recenter after every content change.
* **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.
* Provide a prominent, clickable, visibly labeled action such as **Cancel** on active workflows so users do not need to know the `Esc` key. Do not rely on a corner × as the sole dismissal control.
## 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. Present about three distinct UI concepts by default (usually exactly three). Apply the same default when the user directly asks you for UI ideas or mockups outside a formal spec-pipeline run.
3. Give each concept a visual mockup, not only a prose description. Use one comparison board when all three remain legible; otherwise create a separate mockup for each. Use supplied screenshots and the established product style as visual references when available. Make the concepts differ in interaction or information hierarchy, not only colors or decoration. If visual mockup generation is unavailable, provide annotated wireframes and say so.
4. Pair each mockup with a short title and concise tradeoff. Mark the recommended option with [Recommended] and explain why. Ask up to 3 focused clarification questions after presenting the concepts.
5. Do not require alternatives or mockups for implementation-only requests, narrow bug fixes, or critique-only questions unless the user asks. Follow any user-specified number or format.
6. STOP and wait for user feedback before writing the UI spec.
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)
"""
+3 -4
View File
@@ -1,6 +1,6 @@
name = "writer"
description = "Drafts and updates technical documentation, GDScript APIs, and architecture specs."
model = "gpt-6-luna"
model = "gpt-5.6-luna"
sandbox_mode = "read-only"
developer_instructions = """
@@ -10,11 +10,10 @@ 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/architecture/tech_debt_and_optimizations.md`.
- **Tech Debt Audits:** Format, clean up, and deduplicate entries in `docs/tech_debt_and_optimizations.md`.
### Execution Rules
1. **Targeted Edits Only:** Touch only the specific markdown sections affected by recent changes. Never output full unchanged files.
2. **Scannable & Technical:** Lead directly with code blocks, tables, and structured lists. Eliminate marketing fluff or generic introductions.
3. **Worktree Choice:** Before any persistent documentation work, confirm that the orchestrator has asked the user whether this task should use a Git worktree and supplied the selected project path and branch. If the preference is missing, ask the orchestrator to get it first. Use the selected path whether it is a worktree or the current checkout; do not create a worktree yourself.
"""
"""
-36
View File
@@ -1,36 +0,0 @@
---
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.
## Choose a worktree before creating files
Before creating an asset review bundle or other project files, ask whether the user wants this task to use a Git worktree unless they have already stated a preference. For small, self-contained asset changes, offer the current checkout as the default. Wait for the answer before creating a worktree. Follow `AGENTS.md`, and pass Artist the absolute path and branch the user selected, whether that is a worktree or the current checkout. Do not require a worktree when the user declines one.
## 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. For Stage-usable `.prop` art, include a requested scale when one is specified; otherwise have Artist infer a conventional size from the named subject and intended use against the standard stickman guide, and record that estimate in the review bundle. Do not require the user to repeat ordinary size information or clarify unless the requested scale is ambiguous or intentionally unusual.
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.
@@ -1,4 +0,0 @@
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."
@@ -1,35 +0,0 @@
# 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.
@@ -1,36 +0,0 @@
# 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.
-44
View File
@@ -1,44 +0,0 @@
---
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, ask whether the brief and later feature work should use a Git worktree unless the user has already stated a preference for this task. Follow the choice and the `Feature branches and worktrees` rules in `AGENTS.md`; do not create a worktree before the user answers. Use the selected checkout consistently for the brief, later specs, implementation, and verification. Pass its absolute path and current branch to Designer. Do not switch branches or write outside the selected checkout.
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.
@@ -1,3 +0,0 @@
interface:
display_name: "Feature Ideation"
short_description: "Help with Feature Ideation tasks"
+15 -50
View File
@@ -1,63 +1,28 @@
---
name: spec-pipeline
description: "Stickman Studio feature workflow: UI/UX and architecture specs, implementation, agent-run behavior verification, then user verification."
description: "Lean implementation loop: Architect (Clarify -> Spec) -> Developer -> User Verification."
---
## Execution Logic
### Feature branch and worktree setup
Before creating a worktree for a task, ask the user whether they want one and wait for the answer. For small, self-contained fixes or documentation changes, offer the current checkout as the default; for larger feature phases, explain the benefit of a dedicated worktree. Do not create a worktree before the user chooses. Keep the user's choice for the task from the first persistent artifact through implementation and verification.
Follow the `Feature branches and worktrees` rules in `AGENTS.md`: inspect `git status --short`, the current branch, and worktree list before the first persistent change. If the user chooses the current checkout, stay on its branch. If they choose a worktree, reuse a matching one or create `.worktrees/<feature-slug>` on `feature/<feature-slug>` from the integration branch, including the phase number when known. Do not switch branches, transfer uncommitted changes without direction, or merge automatically.
Pass every delegated agent the absolute selected project path and its branch, whether that path is a worktree or the current checkout. Tell each agent to use it for all repository reads, writes, Godot runs, and visual checks. Do not stop file-writing work solely because the user chose not to use a worktree.
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.
- Before proposing, mocking, reviewing, or suggesting UI/UX changes, `@uiux` reads `docs/guides/editor_visual_style_guide.md` and checks the relevant implemented controls/specs. Treat the guide as the current visual baseline; distinguish implemented behavior from recommendations and explain intentional departures. This also applies to direct `@uiux` requests outside the pipeline.
- 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.
- Follow the concept-count and visual-mockup protocol in `.codex/agents/uiux.toml`; it also applies when the user asks `@uiux` for ideas outside a formal pipeline run.
- `@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 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).
- Carry forward the style guide's applicable patterns and token ownership. If the approved direction departs from them, record the reason and the specific difference in the spec.
- 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 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.
1. **Phase 1a: Architecture Proposal & Clarification**
- `@architect` analyzes existing patterns, pitches 1–3 Godot best-practice ideas, and asks up to 3 clarification questions with `[Recommended]` options.
- `@architect` stops without writing the spec file yet.
4. **Phase 1b: User Feedback & Spec Generation**
2. **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.
- `@architect` writes the finalized specification to `specs/[feature-name].md` and logs decisions in `docs/tech_debt_and_optimizations.md`.
- **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.
3. **Phase 2: Implementation & Engine Validation**
- `@developer` updates code and scenes using targeted diffs/snippets based on `specs/[feature-name].md`.
- `@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.
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.
4. **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.
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."
5. **Phase 4: Documentation (Triggered manually by user)**
- Only call `@writer` after the user confirms: _"Feature verified and working."_
-12
View File
@@ -1,20 +1,8 @@
# Godot 4+ specific ignores
.godot/
.worktrees/
.images/
/android/
addons/
*.tmp
.opencode/skills/playwright-cli
*.import
# Local scratch captures, logs, and temporary browser profiles
/tmp/
/work/
content/review/artist/
content/examples/
/tests/godot_test_user_data/
/tests/*_runtime.log
/tests/phase19_*.log
/tests/phase19_visual_*.png
/tests/phase19_selected_artwork_depth_inspector.png
/.codex-remote-attachments/
+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/architecture/tech_debt_and_optimizations.md`.
3. **Log:** Append trade-offs to `docs/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 `docs/specs/[feature-name].md` and stop for user approval.
3. **Handoff:** Save to `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/architecture/tech_debt_and_optimizations.md`.
- **Tech Debt Audits:** Format, clean up, and deduplicate entries in `docs/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 `docs/specs/[feature-name].md`.
- `@architect` defines signatures, node structures, or spec updates in `specs/[feature-name].md`.
- **STOP:** Wait for user approval on design choices before invoking developer.
2. **Phase 2: Implementation & Engine Validation**
+75 -137
View File
@@ -4,28 +4,7 @@
- **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/main.tscn` (set as `run/main_scene` in `project.godot`; hosts the Stickman Editor)
## Feature branches and worktrees
- Before creating a Git worktree for a task, ask the user whether they want this task to use one. Do not create a worktree until they answer. For small, self-contained fixes and documentation changes, offer the current checkout as the default; for larger feature phases, explain why a worktree may help and let the user choose.
- Read-only discussion and proposals do not need a worktree. Before the first persistent change, inspect `git status --short`, `git branch --show-current`, and `git worktree list`.
- If the user chooses the current checkout, keep working on its current branch. Do not switch branches, stash, reset, clean, move, or overwrite unrelated work.
- If the user chooses a worktree, reuse a matching one when present. Otherwise, create `.worktrees/<feature-slug>` with branch `feature/<feature-slug>`, based on the integration branch (usually `main`). Include the phase number in the slug when known, such as `phase-20-stage-search`. Example: `git worktree add .worktrees/phase-20-stage-search -b feature/phase-20-stage-search main`. Confirm the path and branch with `git worktree list` and `git branch --show-current`.
- If relevant uncommitted changes need to be carried into a chosen worktree, ask how to handle them before proceeding.
- Pass every subagent the absolute path and branch the user chose, whether that is a worktree or the current checkout. All project reads, writes, Godot commands, and visual checks must target that path. Do not require a worktree when the user chose the current checkout.
- Do not merge feature branches automatically. Keep each feature branch available for review and merge only when the user asks or the active workflow explicitly authorizes it.
- `.worktrees/` is a local worktree container and must remain gitignored.
## 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.
- **Main scene:** `res://scenes/stickman_editor.tscn` (set as `run/main_scene` in `project.godot`)
## Command-line testing & debug playback
@@ -35,12 +14,11 @@
- `<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/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()`.
`<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()`.
- `--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.
@@ -63,58 +41,18 @@ 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/editors/prop/prop_editor.tscn` and is **not**
vector prop assets. It is launched via **F6** on `res://scenes/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.
## Child-facing dialogs and popups
- When a user prefixes a UI/UX question or request with `uiux:` or `uiux,`, route it to the
custom UI/UX agent defined in `.codex/agents/uiux.toml`. Treat that prefix as an explicit
delegation request. The UI/UX agent is read-only; carry its design guidance into implementation
separately when implementation is requested.
- Before making, mocking, reviewing, or suggesting Prop or Stickman UI/UX changes, have `@uiux`
read `docs/guides/editor_visual_style_guide.md` and consult the relevant implemented patterns
and feature specs. Treat the guide as the current visual baseline; call out intentional
departures and distinguish current behavior from recommendations.
- For new or revised child-facing dialogs, popups, and short setup flows, follow the
[Dialog Design Principles](docs/specs/dialog_design_principles.md). Use rounded containers
and controls, clear borders, blue primary actions with bold white labels, comfortable button
spacing, and large readable type. Prefer a visible, labeled action such as Cancel, Done, or
Back; do not make a small corner × the default or only way to close a dialog.
- When a dialog resizes after a tab, filter, or other in-place choice, preserve its current
anchor so the clicked control does not jump away from the pointer. Keep the dialog within the
safe viewport by limiting its height and scrolling content; nudge it only as much as needed
to keep essential controls visible. Avoid recentering after every content change.
- Keep reusable values in `SandboxTheme` or `PropTheme`. The Choose Scenery dialog in
`scripts/shared/ui/asset_selector.gd` and the `popups.asset_selector` tokens are the current
implementation reference. Adapt its layout to each workflow.
## 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 `scenes/legacy/stick.tscn` rig.
but keep it present — it is required for the legacy `stick.tscn` rig.
## Architecture
- `scripts/editors/stickman/stickman_editor.gd` — `extends Control`; the main controller. Owns the menu bar,
- `scripts/stickman_editor.gd` — `extends Control`; the main controller. Owns the menu bar,
save/load/clear flow, JSON (de)serialization, and populates the 10 body-part panels.
Writes `FILE_VERSION "1.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).
@@ -160,7 +98,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/editors/stickman/body_part_panel.gd` — `class_name BodyPartPanel`, `extends PanelContainer`.
- `scripts/body_part_panel.gd` — `class_name BodyPartPanel`, `extends PanelContainer`.
Reusable per-part editor. Public API:
- `set_shape_data(data: Variant)` — import shape data (Array or single Dictionary; used on Load/Clear)
- `get_shape_data() -> Array[Dictionary]` — export array of `{shape_type, points, color, closed, vertex_flags}`
@@ -192,7 +130,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/editors/stickman/whole_stickman_preview.gd` — `class_name WholeStickmanPreview`, `extends Control`;
- `scripts/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
@@ -208,7 +146,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 `stickman_rig.tscn` rest pose (`GUIDE_JOINTS`: 13 anchors
constants** derived from the `master_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
@@ -216,9 +154,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/editors/prop/prop_editor.gd` — `class_name PropEditor`, `extends Control`; the **Prop Editor**
- `scripts/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/editors/prop/prop_editor.tscn`). Owns the menu bar, save/load/new flow, `.prop`
`res://scenes/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`,
@@ -311,7 +249,7 @@ preview_area.size * 0.5`, so it also re-centers on window resize; it remains a
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
- `scripts/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
@@ -391,9 +329,9 @@ preview_area.size * 0.5`, so it also re-centers on window resize; it remains a
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`
- `scripts/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://content/props"`, `SUPPORTED_VERSIONS: ["1.0"]`,
`FILE_EXT := ".prop"`, `DEFAULT_DIR := "res://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
@@ -424,7 +362,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/editors/prop/prop_theme.gd` — `class_name PropTheme`, `extends RefCounted`; the **validated theme
- `scripts/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
@@ -445,14 +383,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/shared/thumbnails/thumbnail_cache.gd` — see its existing section; the Phase 9 extension
- `scripts/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/characters/stickman/stk_rig_adapter.gd` — `class_name StkRigAdapter`, `extends RefCounted`; a **standalone
- `scripts/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 `stickman_rig.tscn` to a loaded `.stk`
`static func apply(stk_data, rig)` fits an instantiated `master_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
@@ -463,7 +401,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.
`content/stickmen/test.stk`) mount as visible geometry instead of being cleared to nothing.
`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
@@ -546,11 +484,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 `stickman_rig.tscn` node paths; every node lookup is null-guarded (missing node →
Targets `master_rig.tscn` node paths; every node lookup is null-guarded (missing node →
`push_warning` + skip, never crash). Consumed by a future runtime pipeline.
- `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
- `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
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 }`.
@@ -595,7 +533,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 stickman_rig.tscn head `RemoteTransform2D` (`Skeleton2D/Torso/Head/Pivot`)
under the mirror: the master_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
@@ -638,7 +576,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 — `stickman_rig.tscn` is
harness "Knock Up" button. All ragdoll nodes are spawned procedurally — `master_rig.tscn` is
**not** modified.
- **Phase 11 instant handoff + recovery:** `_update_rest_detection()` (only when `state ==
RAGDOLL`)
@@ -790,30 +728,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/characters/stickman/stickman_factory.gd` — `class_name StickmanFactory`, `extends RefCounted`; a **static
- `scripts/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 `stickman_rig.tscn` instance:
`.stk` file into a live, rigged `master_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://scenes/characters/stickman/stickman_rig.tscn`, calls `StkRigAdapter.apply(stk_data, rig)`, returns the rig root
`res://master_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/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).
- `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).
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 `stickman_rig.tscn` defaults), with `POSE_PATHS`/`POSE_MARKERS` consts.
`POSE_STANDING` (matching `master_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/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
- `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
offsets, and IK limits in isolation. Top UI bar: "Open .stk…" button → `FileDialog` (`*.stk`);
quick-select buttons for `content/stickmen/break.stk`, `content/stickmen/basic.stk`, `content/stickmen/test.stk`; "Show
quick-select buttons for `stickmen/break.stk`, `stickmen/basic.stk`, `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
@@ -931,7 +869,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/terrain_block.gd` — `class_name TerrainBlock`, `extends StaticBody2D`; a **reusable
- `scripts/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,
@@ -939,7 +877,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/terrain_utils.gd` — `class_name TerrainUtils`, `extends RefCounted`; static utility
- `scripts/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
@@ -949,7 +887,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/props/prop_block.gd` — `class_name PropBlock`, `extends RigidBody2D`; a **reusable
- `scripts/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
@@ -976,12 +914,12 @@ PackedVector2Array`, `radius: float`, `fill_color`, `outline_color`, `outline_wi
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**
- `scripts/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/props/prop_utils.gd` — `class_name PropUtils`, `extends RefCounted`; static factory
- `scripts/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
@@ -992,10 +930,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/tools/physics_test_harness.gd` — `class_name PhysicsTestHarness`, `extends Node2D`; standalone
- `scripts/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/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
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
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
@@ -1028,9 +966,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/editors/stage/sandbox_stage.gd` — `class_name SandboxStage`, `extends Node2D`; the **Sandbox Stage
- `scripts/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/stage/sandbox_stage.tscn`). Owns the EDIT/DIRECT/PLAY mode state machine, placement mode,
`res://scenes/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` +
@@ -1147,7 +1085,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/stage/asset_selector.tscn` `PopupPanel`, added to the UI `CanvasLayer` in
an instantiated `scenes/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
@@ -1177,7 +1115,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/speech/stickman_speech_bubble.gd` — `class_name SpeechBubble`, `extends Node2D`; a **world-space
- `scripts/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`.
@@ -1185,7 +1123,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/stage_director_visuals.gd` — `class_name StageDirectorVisuals`, `extends Node2D`; the
- `scripts/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
@@ -1203,7 +1141,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/stage_spawner.gd` — `class_name StageSpawner`, `extends RefCounted`; registry-driven
- `scripts/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
@@ -1219,7 +1157,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://content/stickmen/test.stk`) and
`selected_stickman_path` (default `DEFAULT_STICKMAN_PATH` = `res://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.
@@ -1227,9 +1165,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/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:
- `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:
`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
@@ -1239,7 +1177,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/props/prop_library.gd` — `class_name PropLibrary`, `extends RefCounted` (Phase 3b, **not used
- `scripts/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,
@@ -1248,7 +1186,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/shared/thumbnails/thumbnail_cache.gd` — `class_name ThumbnailCache`, `extends RefCounted`
- `scripts/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)>"`
@@ -1257,7 +1195,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/shared/thumbnails/stickman_thumbnail.gd` — `class_name StickmanThumbnail`, `extends Node`
- `scripts/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`)
@@ -1269,7 +1207,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/shared/thumbnails/prop_thumbnail.gd` — `class_name PropThumbnail`, `extends Node` (Phase 3b,
- `scripts/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
@@ -1278,8 +1216,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/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**.
- `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**.
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
@@ -1301,7 +1239,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/stage_selection.gd` — `class_name StageSelection`, `extends RefCounted`; hover/click/
- `scripts/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
@@ -1312,7 +1250,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/stage_gizmos.gd` — `class_name StageGizmos`, `extends Node2D`; hover highlight +
- `scripts/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
@@ -1333,46 +1271,46 @@ page_count}` helper. `_build_cell(entry)` returns a `Button` with a `TextureRect
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/stage_grid.gd` — `class_name StageGrid`, `extends Node2D`; the optional world-space
- `scripts/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/editors/stickman/stickman_editor.tscn` — main editor layout; unique-name nodes (`%Prefix`) used
- `scenes/stickman_editor.tscn` — main editor layout; unique-name nodes (`%Prefix`) used
for typed `@onready` access: `%MenuBar`, `%StickmanNameEdit`, `%LeftColumn`,
`%CenterColumn`, `%WholeStickmanPreview`, `%SaveDialog`, `%LoadDialog`,
`%ClearConfirmDialog`, `%ErrorDialog`, `%StatusBar`, `%CursorCoords`, `%SnapStatus`.
- **Phase 6 status bar:** `StatusBar` is an `HBoxContainer` bottom-anchored at 28 px
height containing `CursorCoords` (left) and `SnapStatus` (right); `MainLayout`
`offset_bottom` is `-28.0` to leave room for it.
- `scenes/editors/stickman/body_part_panel.tscn` — instantiated 10× at runtime (5 per column). Each panel
- `scenes/body_part_panel.tscn` — instantiated 10× at runtime (5 per column). Each panel
sets `size_flags_vertical = SIZE_EXPAND_FILL` so the panels expand to fill the column
height in their parent VBoxContainer.
- `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 /
- `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 /
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/tools/physics_test_harness.tscn` — **standalone staging scene** (Vector Terrain System /
- `scenes/physics_test_harness.tscn` — **standalone staging scene** (Vector Terrain System /
Dynamic Vector Props, not wired into the editor; run via **F6**). Backed by
`scripts/tools/physics_test_harness.gd`. Root `Node2D` + script, `Camera2D` at position `(0, -400)`
`scripts/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/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
- `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
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/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
- `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
**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`,
@@ -1380,8 +1318,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/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
- `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
`%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 +
@@ -1417,7 +1355,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://content/props/*.prop`, and converted to a runtime
defined in `README.md`, saved under `res://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",
@@ -1450,7 +1388,7 @@ rotation, scale}`.
### Legacy scene (do not delete)
- `scenes/legacy/stick.tscn` — the original rigged/animated figure using `Skeleton2D` + IK targets +
- `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.
+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/specs/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/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/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.
> 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.
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/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.
> 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.
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/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.
> 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.
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/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.
> 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.
## Stickman editor (Phase 9 Round 6)
> 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.
> 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.
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/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).
> 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).
## 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/specs/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/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/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.
> 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.
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/specs/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/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.
+136 -174
View File
@@ -1,6 +1,6 @@
# Stickman Studio
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.
A Godot 4.4 editor tool for creating and assembling stick figures. Stickman Studio lets you draw individual body-part shapes, edit vertices, assemble them into a whole stickman, and save/load your figure to a JSON file (`.stk`) for later use or animation scripting.
## Overview
@@ -20,44 +20,19 @@ Drawing surfaces **clip** their content to the panel bounds: shapes and parts no
| Item | Version |
|---|---|
| Engine | Godot 4.7.1 |
| Engine | Godot 4.4 or newer |
| Addon | Scalable Vector Shapes 2D (`addons/curved_lines_2d/`) |
> The legacy `scenes/legacy/stick.tscn` scene (a rigged walk animation using `Skeleton2D` + IK) remains for reference but is **no longer the main scene**.
> The legacy `stick.tscn` scene (a rigged walk animation using `Skeleton2D` + IK) remains in the project root for reference but is **no longer the main scene**.
## Running the project
Run the project from the Godot editor:
No CLI build, test, or lint commands are used. Run the project from the Godot editor:
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).
1. Open the project folder in **Godot 4.4**.
2. Press **F5**, or open and run `res://scenes/stickman_editor.tscn`.
The main scene is configured as `run/main_scene="res://scenes/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.
The main scene is configured as `run/main_scene="res://scenes/stickman_editor.tscn"` in `project.godot`.
## Quick Start
@@ -123,7 +98,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 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 Whole Stickman preview can render a **pose silhouette guide**: a semi-transparent, color-coded stick figure that marks the rest-pose pivot joints of the future rig defined by `master_rig.tscn`. Its 13 joint anchors (Head, Neck, Left/Right Shoulder, Left/Right Elbow, Left/Right Wrist, Hips, Left/Right Knee, Left/Right Ankle) correspond to the exact pivot locations a future `Skeleton2D` rig will use, so you can align your body-part shapes to the same pivots.
The guide is a **view aid only**: it is drawn above the grid **and in front of user parts** (ghosting over them), moves with pan/zoom, is **not** hit-testable (it never intercepts part dragging or selection), and is **not** saved to the `.stk` file.
@@ -135,13 +110,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 `stickman_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 `master_rig.tscn` scale) centered on the Head joint.
- **Joint dots** are constant **6 px** on screen at every zoom level (`GUIDE_JOINT_RADIUS / _zoom`).
- The guide is **ON by default**. Toggle it via **View → Hide Pose Guide** / **View → Show Pose Guide** (the menu item is a dynamic, text-only label — no checkmark — reading "Hide Pose Guide" while the guide is visible and "Show Pose Guide" while it is hidden); the setting persists to `user://settings.json` (`show_pose_guide`).
**Centered at the default view:** the guide's figure bounding box is centered at the preview-area center, computed at draw time from the live preview size (`GUIDE_FIGURE_CENTER = (0, -93.75)`; `_guide_to_preview()` maps `master_pos` to `(master_pos - GUIDE_FIGURE_CENTER) * GUIDE_SCALE + preview_area.size * 0.5`). It is centered at startup and after **Reset Views** (zoom 1 / pan 0), and re-centers automatically when the window is resized. Because it remains a **world-space fixture**, panning/zooming moves it with the grid and parts.
**1:1 master-rig scale (deliberate):** the guide matches `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.
**1:1 master-rig scale (deliberate):** the guide matches `master_rig.tscn` proportions exactly (a figure of ≈ 336 × 940 px, roughly x ∈ [-168, 168], y ∈ [-563.5, 376]). Because the default starter parts are small, the guide intentionally **dwarfs** them. At 1:1 zoom the ~940 px figure is taller than a typical panel, so the head and feet clip equally above/below the fold when centered — accepted. `GUIDE_SCALE`/`GUIDE_FIGURE_CENTER` are the tunables if a different size or placement is wanted later.
### 6. Grid & Panning
@@ -266,21 +241,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 `stickman_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 `master_rig.tscn` instance — plus a standalone staging scene for debugging it in isolation. **Neither is wired into the editor.**
**Stickman Factory (`res://scripts/characters/stickman/stickman_factory.gd`)** — the runtime entry point:
**Stickman Factory (`res://scripts/stickman_factory.gd`)** — the runtime entry point:
| Method | Signature | Behavior |
|---|---|---|
| `load_stk` | `static func load_stk(path: String) -> Dictionary` | Reads a `.stk` file via `FileAccess` + `JSON.parse_string`. Returns `{}` and `push_warning` on failure. |
| `spawn_from_data` | `static func spawn_from_data(stk_data: Dictionary) -> Node2D` | Instantiates `res://scenes/characters/stickman/stickman_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://master_rig.tscn`, calls `StkRigAdapter.apply(stk_data, rig)`, returns the rig root. |
| `spawn` | `static func spawn(path: String) -> Node2D` | Chains `load_stk()` → `spawn_from_data()`. Returns `null` on empty data. |
The factory is the intended runtime API: `StickmanFactory.spawn("res://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).
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).
**Test Harness (`res://scenes/tools/test_harness.tscn`)** — run via **F6** on the scene (NOT via the main editor scene). Controls:
**Test Harness (`res://scenes/test_harness.tscn`)** — run via **F6** on the scene (NOT via the main editor scene). Controls:
- **Open .stk…** — `FileDialog` filtered to `*.stk`; quick-select buttons for `res://content/stickmen/break.stk`, `res://content/stickmen/basic.stk`, `res://content/stickmen/test.stk`.
- **Open .stk…** — `FileDialog` filtered to `*.stk`; quick-select buttons for `res://stickmen/break.stk`, `res://stickmen/basic.stk`, `res://stickmen/test.stk`.
- **Show Bones / Show IK Handles** — checkboxes toggling the debug overlay.
- **Loaded filename** — status label showing the currently loaded file.
- **Pan / zoom** — middle-mouse drag to pan, mouse-wheel to zoom the `Camera2D`; the camera recenters on each spawn.
@@ -296,7 +271,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/terrain_block.gd`)** — `@tool` `class_name TerrainBlock`, `extends StaticBody2D`. A single terrain segment that builds its three children in code:
**`TerrainBlock` (`res://scripts/terrain_block.gd`)** — `@tool` `class_name TerrainBlock`, `extends StaticBody2D`. A single terrain segment that builds its three children in code:
| Child | Node type | Purpose |
|---|---|---|
@@ -306,7 +281,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/terrain_utils.gd`)** — `class_name TerrainUtils`, `extends RefCounted`, static utility.
**`TerrainUtils` (`res://scripts/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**).
@@ -314,13 +289,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/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`.
**`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`.
### 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/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):
**`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):
| Child | Node type | Purpose |
|---|---|---|
@@ -353,7 +328,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/props/prop_utils.gd`)** — `class_name PropUtils`, `extends RefCounted`, static factory.
**`PropUtils` (`res://scripts/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).
@@ -374,7 +349,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 `stickman_rig.tscn`) can switch between physics modes via a reversible state machine with three states:
`StickmanRig` (the runtime root of `master_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.
@@ -408,7 +383,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/characters/stickman/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/stickman_rig.gd`, keyed by facing profile, so joints can be tuned without touching pipeline code:
| Const | Shape | Purpose |
|---|---|---|
@@ -424,30 +399,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 — `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).
**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).
### 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/stage/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/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/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://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://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`:
@@ -463,7 +438,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://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.
- **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.
- **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.
@@ -495,14 +470,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/stage/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/sandbox_stage.tscn`.
| File | Purpose |
|---|---|
| `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. |
| `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. |
**Direct tool workflow (Edit):**
@@ -542,7 +517,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.
- `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.
- `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.
- 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:**
@@ -551,7 +526,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/architecture/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/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:**
@@ -578,19 +553,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/stage/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/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/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. |
| `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. |
**Trigger types:**
@@ -643,7 +618,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 `stickman_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 `master_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.
@@ -656,7 +631,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/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.
**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.
#### 21.1 Terrain drag-painting (Edit mode)
@@ -680,7 +655,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 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.
**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.
#### 21.2 Director targeting UX (Direct / rule-building)
@@ -696,7 +671,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/architecture/SANDBOX_THEME.md](docs/architecture/SANDBOX_THEME.md).
[docs/SANDBOX_THEME.md](docs/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
@@ -718,13 +693,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 (`stickman_rig.tscn`)
#### 21.5 Head LookAt solver fix (`master_rig.tscn`)
`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).
`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).
### 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/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.
**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.
**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.
@@ -732,12 +707,12 @@ The result: `mode` stays constant for the whole walk, exactly one `arrived` fire
| Grid | Backing | Cells | Details |
|---|---|---|---|
| **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 |
| **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 |
| **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/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()`.
**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()`.
**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`:
@@ -756,30 +731,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/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")`).
**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")`).
**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`.
**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`.
### 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/stage/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/sandbox_stage.tscn`. **No `.stk` format change.**
**New scripts:**
| File | `class_name` / extends | Responsibility |
|---|---|---|
| `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). |
| `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). |
**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/specs/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/phase_3c_editor_spec.md` §14.
**Entry points:**
@@ -806,19 +781,7 @@ 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/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.
Spec: `docs/phase_3c_editor_spec.md`.
## File format (`.stk`)
@@ -883,7 +846,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 `stickman_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 `master_rig.tscn`), not measured from the user's shapes. Write-only metadata — never read back on load. |
| `body_parts` | `object` | Map of `body_part_name → shape` objects. Keyed by the 10 internal part names below. |
| `metadata.created_at` | `string` | Timestamp written on save. |
| `metadata.modified_at` | `string` | Timestamp written on save. |
@@ -922,7 +885,7 @@ Each entry in the `shapes` array:
## File format (`.prop`)
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).
The **Prop Editor** (Phase 9, extended by Phase 10a) 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).
In the Prop Editor, Arrange exposes **Transform Whole Prop**, **Normalize Size**,
**Center Prop**, and **Show Stickman Guide** in that order. Whole-prop move,
@@ -1035,54 +998,53 @@ Behavior:
| Path | Purpose |
|---|---|
| `res://project.godot` | Engine config; sets main scene to the editor and enabled features. |
| `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://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://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/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://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://addons/curved_lines_2d/` | Scalable Vector Shapes 2D addon (v2.27.7) — required dependency. |
| `res://scenes/legacy/scenes/legacy/stick.tscn` | **Legacy** rigged/animated stick figure scene (Skeleton2D + IK). Not used by the editor. |
| `res://stick.tscn` | **Legacy** rigged/animated stick figure scene (Skeleton2D + IK). Not used by the editor. |
| `res://AGENTS.md` | Guidance for AI agents working in this codebase. |
### Menu bar structure
@@ -1160,27 +1122,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 `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 7:** Adds the pose silhouette guide to the Whole Stickman preview — a semi-transparent, color-coded stick figure (left cyan-blue, right orange-red, central white) whose 13 joint anchors match the rest-pose pivots of the future `master_rig.tscn` rig, drawn above the grid and in front of user parts (ghosting over them, below the selection gizmos and drag highlight), centered in the preview at the default view. Toggleable via the dynamic **View → Hide/Show Pose Guide** label (on by default, persisted to `settings.json`). The guide is a view aid only: **no `.stk` format change** — `FILE_VERSION` stays `"1.3"`.
> **Phase 8:** The `.stk` export evolved to **v1.4** with write-only metadata the editor never reads back. A top-level `proportions` object stores the 5 master-rig rest-pose bone lengths (`upper_arm_length` 168.0, `lower_arm_length` 200.0, `upper_leg_length` 200.0, `lower_leg_length` 200.0, `torso_length` 391.5) — hardcoded constants from `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 8:** The `.stk` export evolved to **v1.4** with write-only metadata the editor never reads back. A top-level `proportions` object stores the 5 master-rig rest-pose bone lengths (`upper_arm_length` 168.0, `lower_arm_length` 200.0, `upper_leg_length` 200.0, `lower_leg_length` 200.0, `torso_length` 391.5) — hardcoded constants from `master_rig.tscn`, not measured from the user's shapes. Each `body_parts` entry gains `pivot` (local bounding-box center = rotation origin) and `length` (bbox extent along the segment axis: width for arms, height for torso/legs/head). v1.0–v1.3 files load unchanged and gain these keys on their next save. A new standalone `res://scripts/stk_rig_adapter.gd` (`class_name StkRigAdapter`) fits an instantiated `master_rig.tscn` to a loaded `.stk` (bone fitting + IK recalibration + visual shape mount); it is **not** used by the editor and is reserved for a future runtime pipeline.
> **Phase 9:** Adds the **runtime pipeline** for turning a `.stk` file into a live, rigged `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:** Adds the **runtime pipeline** for turning a `.stk` file into a live, rigged `master_rig.tscn` instance, plus a standalone staging scene to debug it. A new `res://scripts/stickman_factory.gd` (`class_name StickmanFactory`) provides the runtime entry point: `load_stk(path)` reads/parses a `.stk` (`FileAccess` + `JSON.parse_string`, `{}` + `push_warning` on failure), `spawn_from_data(stk_data)` instantiates `master_rig.tscn` and applies `StkRigAdapter.apply(stk_data, rig)`, and `spawn(path)` chains them (`null` on empty data). `StkRigAdapter` is extended to also fit the head bone (`Head.position.y = -proportions.torso_length`) and to mount the head as **full geometry** like every other part — clearing its inline `@tool` circle script and mounting `.stk` head shapes as `Line2D`/`Polygon2D` (removed the dead `_mount_head_circle`, `_compute_shapes_bbox`, and `_first_shape_color` helpers). A new standalone scene/resource pair, `res://scenes/test_harness.tscn` + `res://scripts/test_harness.gd` (run via **F6**), loads `.stk` files (open dialog + quick-select for `stickmen/break.stk`, `stickmen/basic.stk`, `stickmen/test.stk`), toggles a world-space debug overlay (true bone segments — parent→child lines with joint dots, limb leaf bones drawn to their IK targets and the Head leaf along its own direction — plus IK-target markers), pans/zooms a `Camera2D`, and lets you click-drag the 4 limb IK targets with live TwoBoneIK flexing (`SkeletonModificationStack2D` enabled on spawn). Neither the factory nor the harness is wired into the editor. **No `.stk` format change** — `FILE_VERSION` stays `"1.4"`.
> **Phase 9 Round 2 bugfix:** `StkRigAdapter._mount_shapes()` no longer trusts the file's `pivot`/`length`; it recomputes each part's bounding box at mount time and mounts geometry in the rig's **hanging convention** (joint anchor at the local origin, far end along local `+Y`) with a per-family alignment rotation (head `0`, torso `π`, left limbs `−π/2`, right limbs `+π/2`, vertical-drawn limbs `0`). Anchors: head/torso bottom-center, left horizontal limbs `max_x`, right horizontal limbs `min_x`, vertical limbs top-center. Scaling is **anisotropic** — only the **auto-detected drawn long axis** (`width >= height`) scales to the bone length (`bone_length/extent`, guard `extent <= 0.0001` → `1.0`), cross-axis thickness stays 1:1. Driver rotation neutralization was **removed** — the 10 `RemoteTransform2D` drivers keep `update_rotation = true`, so mounted shapes follow their bones under IK flexing. `Body/*` container transforms are reset (scale `(1,1)`, rotation `0`, position untouched), the Head driver's local position is zeroed so the chin lands on the neck joint, and `DEFAULT_LINE_WIDTH` is 2.0 (editor's 2 px outline). The harness `_draw_bones()` was rewritten to draw true parent→child bone segments with leaf bones out to their IK targets (wrist/ankle joints now visible). Per docs/specs/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/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/specs/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/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/specs/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/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/specs/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/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/specs/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/phase9_round6_bugfix_spec.md; verified with a 32-assertion headless smoke 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 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 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 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 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/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`.
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`.
> **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).
> **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).
+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/specs/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/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/specs/phase9_task3_coords_spec.md`; the harness adds a
**Implemented (Phase 9 Task 3)** — see `docs/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/specs/phase9_task4_refactor_spec.md`; a new
`scripts/characters/stickman/stickman_rig.gd` (`class_name StickmanRig`, attached to the `master_rig.tscn` root)
**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)
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/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.
**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.
Both AnimationPlayer and AnimationTree has been added to the master_rig.tscn. Animations should be selectable and playable in the harness.
-715
View File
@@ -1,715 +0,0 @@
{
"anchors": {
"origin": {
"allowed_hands": "both",
"role": "pivot",
"rotation_deg": 0.0,
"x": 0.0,
"y": 0.0,
"z_index_offset": 0
}
},
"material_preset": "wood",
"metadata": {
"created_at": "2026-10-21T23:11:16",
"modified_at": "2026-10-21T23:11:16"
},
"prop_name": "Bookshelf",
"shapes": [
{
"closed": true,
"fill_color": "#AB795E",
"outline_color": "#332B45",
"outline_width": 12.553713506884,
"points": [
{
"x": -320.119689941406,
"y": -488.025634765625
},
{
"x": 320.119689941406,
"y": -488.025634765625
},
{
"x": 320.119689941406,
"y": 488.025604248047
},
{
"x": -320.119689941406,
"y": 488.025604248047
}
],
"reshaped": false,
"shape_type": "polygon",
"vertex_flags": [
0,
0,
0,
0
]
},
{
"closed": true,
"fill_color": "#F5D9AA",
"outline_color": "#332B45",
"outline_width": 12.553713506884,
"points": [
{
"x": -273.043273925781,
"y": -431.533935546875
},
{
"x": 273.043273925781,
"y": -431.533935546875
},
{
"x": 273.043273925781,
"y": 412.703308105469
},
{
"x": -273.043273925781,
"y": 412.703308105469
}
],
"reshaped": false,
"shape_type": "polygon",
"vertex_flags": [
0,
0,
0,
0
]
},
{
"closed": true,
"fill_color": "#AB795E",
"outline_color": "#332B45",
"outline_width": 12.553713506884,
"points": [
{
"x": -273.043273925781,
"y": -171.044372558594
},
{
"x": 273.043273925781,
"y": -171.044372558594
},
{
"x": 273.043273925781,
"y": -133.383239746094
},
{
"x": -273.043273925781,
"y": -133.383239746094
}
],
"reshaped": false,
"shape_type": "polygon",
"vertex_flags": [
0,
0,
0,
0
]
},
{
"closed": true,
"fill_color": "#AB795E",
"outline_color": "#332B45",
"outline_width": 12.553713506884,
"points": [
{
"x": -273.043273925781,
"y": 108.275756835938
},
{
"x": 273.043273925781,
"y": 108.275756835938
},
{
"x": 273.043273925781,
"y": 145.936904907227
},
{
"x": -273.043273925781,
"y": 145.936904907227
}
],
"reshaped": false,
"shape_type": "polygon",
"vertex_flags": [
0,
0,
0,
0
]
},
{
"closed": true,
"fill_color": "#C87375",
"outline_color": "#332B45",
"outline_width": 12.553713506884,
"points": [
{
"x": -238.520568847656,
"y": -409.56494140625
},
{
"x": -182.028854370117,
"y": -409.56494140625
},
{
"x": -182.028854370117,
"y": -183.598083496094
},
{
"x": -238.520568847656,
"y": -183.598083496094
}
],
"reshaped": false,
"shape_type": "polygon",
"vertex_flags": [
0,
0,
0,
0
]
},
{
"closed": true,
"fill_color": "#77A7B2",
"outline_color": "#332B45",
"outline_width": 12.553713506884,
"points": [
{
"x": -169.475143432617,
"y": -409.56494140625
},
{
"x": -112.983421325684,
"y": -409.56494140625
},
{
"x": -112.983421325684,
"y": -183.598083496094
},
{
"x": -169.475143432617,
"y": -183.598083496094
}
],
"reshaped": false,
"shape_type": "polygon",
"vertex_flags": [
0,
0,
0,
0
]
},
{
"closed": true,
"fill_color": "#E3B765",
"outline_color": "#332B45",
"outline_width": 12.553713506884,
"points": [
{
"x": -100.429710388184,
"y": -409.56494140625
},
{
"x": -43.9379997253418,
"y": -409.56494140625
},
{
"x": -43.9379997253418,
"y": -183.598083496094
},
{
"x": -100.429710388184,
"y": -183.598083496094
}
],
"reshaped": false,
"shape_type": "polygon",
"vertex_flags": [
0,
0,
0,
0
]
},
{
"closed": true,
"fill_color": "#82B38D",
"outline_color": "#332B45",
"outline_width": 12.553713506884,
"points": [
{
"x": -28.2458553314209,
"y": -409.56494140625
},
{
"x": 28.2458553314209,
"y": -409.56494140625
},
{
"x": 28.2458553314209,
"y": -183.598083496094
},
{
"x": -28.2458553314209,
"y": -183.598083496094
}
],
"reshaped": false,
"shape_type": "polygon",
"vertex_flags": [
0,
0,
0,
0
]
},
{
"closed": true,
"fill_color": "#C87375",
"outline_color": "#332B45",
"outline_width": 12.553713506884,
"points": [
{
"x": 53.3532829284668,
"y": -409.56494140625
},
{
"x": 109.844993591309,
"y": -409.56494140625
},
{
"x": 109.844993591309,
"y": -183.598083496094
},
{
"x": 53.3532829284668,
"y": -183.598083496094
}
],
"reshaped": false,
"shape_type": "polygon",
"vertex_flags": [
0,
0,
0,
0
]
},
{
"closed": true,
"fill_color": "#77A7B2",
"outline_color": "#332B45",
"outline_width": 12.553713506884,
"points": [
{
"x": 134.952423095703,
"y": -409.56494140625
},
{
"x": 191.444137573242,
"y": -409.56494140625
},
{
"x": 191.444137573242,
"y": -183.598083496094
},
{
"x": 134.952423095703,
"y": -183.598083496094
}
],
"reshaped": false,
"shape_type": "polygon",
"vertex_flags": [
0,
0,
0,
0
]
},
{
"closed": true,
"fill_color": "#78A7B2",
"outline_color": "#332B45",
"outline_width": 12.553713506884,
"points": [
{
"x": -232.243698120117,
"y": -114.552658081055
},
{
"x": -178.890426635742,
"y": -114.552658081055
},
{
"x": -178.890426635742,
"y": 86.3067626953125
},
{
"x": -232.243698120117,
"y": 86.3067626953125
}
],
"reshaped": false,
"shape_type": "polygon",
"vertex_flags": [
0,
0,
0,
0
]
},
{
"closed": true,
"fill_color": "#E5B869",
"outline_color": "#332B45",
"outline_width": 12.553713506884,
"points": [
{
"x": -166.336700439453,
"y": -114.552658081055
},
{
"x": -112.983421325684,
"y": -114.552658081055
},
{
"x": -112.983421325684,
"y": 86.3067626953125
},
{
"x": -166.336700439453,
"y": 86.3067626953125
}
],
"reshaped": false,
"shape_type": "polygon",
"vertex_flags": [
0,
0,
0,
0
]
},
{
"closed": true,
"fill_color": "#C97375",
"outline_color": "#332B45",
"outline_width": 12.553713506884,
"points": [
{
"x": -100.429710388184,
"y": -114.552658081055
},
{
"x": -47.0764274597168,
"y": -114.552658081055
},
{
"x": -47.0764274597168,
"y": 86.3067626953125
},
{
"x": -100.429710388184,
"y": 86.3067626953125
}
],
"reshaped": false,
"shape_type": "polygon",
"vertex_flags": [
0,
0,
0,
0
]
},
{
"closed": true,
"fill_color": "#9BB586",
"outline_color": "#332B45",
"outline_width": 12.553713506884,
"points": [
{
"x": 12.5537137985229,
"y": -114.552658081055
},
{
"x": 65.9069976806641,
"y": -114.552658081055
},
{
"x": 65.9069976806641,
"y": 86.3067626953125
},
{
"x": 12.5537137985229,
"y": 86.3067626953125
}
],
"reshaped": false,
"shape_type": "polygon",
"vertex_flags": [
0,
0,
0,
0
]
},
{
"closed": true,
"fill_color": "#83AABB",
"outline_color": "#332B45",
"outline_width": 12.553713506884,
"points": [
{
"x": 84.7375717163086,
"y": -114.552658081055
},
{
"x": 138.090850830078,
"y": -114.552658081055
},
{
"x": 138.090850830078,
"y": 86.3067626953125
},
{
"x": 84.7375717163086,
"y": 86.3067626953125
}
],
"reshaped": false,
"shape_type": "polygon",
"vertex_flags": [
0,
0,
0,
0
]
},
{
"closed": true,
"fill_color": "#DEAA72",
"outline_color": "#332B45",
"outline_width": 12.553713506884,
"points": [
{
"x": 150.644561767578,
"y": -114.552658081055
},
{
"x": 203.997848510742,
"y": -114.552658081055
},
{
"x": 203.997848510742,
"y": 86.3067626953125
},
{
"x": 150.644561767578,
"y": 86.3067626953125
}
],
"reshaped": false,
"shape_type": "polygon",
"vertex_flags": [
0,
0,
0,
0
]
},
{
"closed": true,
"fill_color": "#8CB98E",
"outline_color": "#332B45",
"outline_width": 12.553713506884,
"points": [
{
"x": -213.413131713867,
"y": 199.290191650391
},
{
"x": -37.6611404418945,
"y": 199.290191650391
},
{
"x": -37.6611404418945,
"y": 371.903747558594
},
{
"x": -213.413131713867,
"y": 371.903747558594
}
],
"reshaped": false,
"shape_type": "polygon",
"vertex_flags": [
0,
0,
0,
0
]
},
{
"closed": true,
"fill_color": "#79AF81",
"outline_color": "#332B45",
"outline_width": 12.553713506884,
"points": [
{
"x": -28.2458553314209,
"y": 211.843902587891
},
{
"x": -35.6525459289551,
"y": 240.654678344727
},
{
"x": -56.7427864074707,
"y": 265.10302734375
},
{
"x": -88.3153762817383,
"y": 281.4228515625
},
{
"x": -125.537139892578,
"y": 287.166198730469
},
{
"x": -162.758895874023,
"y": 281.4228515625
},
{
"x": -194.331481933594,
"y": 265.10302734375
},
{
"x": -215.421722412109,
"y": 240.654678344727
},
{
"x": -222.828414916992,
"y": 211.843902587891
},
{
"x": -215.421722412109,
"y": 183.033111572266
},
{
"x": -194.331481933594,
"y": 158.584777832031
},
{
"x": -162.758895874023,
"y": 142.264938354492
},
{
"x": -125.537139892578,
"y": 136.521606445313
},
{
"x": -88.3153762817383,
"y": 142.264938354492
},
{
"x": -56.7427864074707,
"y": 158.584777832031
},
{
"x": -35.6525459289551,
"y": 183.033111572266
}
],
"reshaped": false,
"shape_type": "polygon",
"vertex_flags": [
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0
]
},
{
"closed": true,
"fill_color": "#C17476",
"outline_color": "#332B45",
"outline_width": 12.553713506884,
"points": [
{
"x": 31.3842849731445,
"y": 252.643463134766
},
{
"x": 207.136276245117,
"y": 252.643463134766
},
{
"x": 207.136276245117,
"y": 393.872741699219
},
{
"x": 31.3842849731445,
"y": 393.872741699219
}
],
"reshaped": false,
"shape_type": "polygon",
"vertex_flags": [
0,
0,
0,
0
]
},
{
"closed": false,
"fill_color": "#00000000",
"outline_color": "#F4CE9E",
"outline_width": 9.41528513016301,
"points": [
{
"x": 31.3842849731445,
"y": 312.273620605469
},
{
"x": 207.136276245117,
"y": 312.273620605469
}
],
"reshaped": false,
"shape_type": "line",
"vertex_flags": [
0,
0
]
}
],
"tags": [
"objects",
"walls"
],
"version": "1.1"
}
-368
View File
@@ -1,368 +0,0 @@
{
"anchors": {
"origin": {
"allowed_hands": "both",
"role": "pivot",
"rotation_deg": 0.0,
"x": 0.0,
"y": 0.0,
"z_index_offset": 0
}
},
"material_preset": "none",
"metadata": {
"created_at": "2026-10-21T21:43:32",
"modified_at": "2026-10-21T21:43:32"
},
"prop_name": "City Skyline",
"shapes": [
{
"closed": true,
"fill_color": "#879DB0",
"outline_color": "#687F97",
"outline_width": 8.76671850699844,
"points": [
{
"x": -935.116638183594,
"y": 284.918365478516
},
{
"x": -935.116638183594,
"y": 7.30561065673828
},
{
"x": -812.382568359375,
"y": 7.30561065673828
},
{
"x": -812.382568359375,
"y": -138.806365966797
},
{
"x": -642.892700195313,
"y": -138.806365966797
},
{
"x": -642.892700195313,
"y": -7.30558013916016
},
{
"x": -537.692077636719,
"y": -7.30558013916016
},
{
"x": -537.692077636719,
"y": -246.929229736328
},
{
"x": -397.424591064453,
"y": -246.929229736328
},
{
"x": -397.424591064453,
"y": -80.361572265625
},
{
"x": -251.312606811523,
"y": -80.361572265625
},
{
"x": -251.312606811523,
"y": -176.795471191406
},
{
"x": -111.045104980469,
"y": -176.795471191406
},
{
"x": -111.045104980469,
"y": -21.9167785644531
},
{
"x": 43.8335914611816,
"y": -21.9167785644531
},
{
"x": 43.8335914611816,
"y": -284.918334960938
},
{
"x": 181.178848266602,
"y": -284.918334960938
},
{
"x": 181.178848266602,
"y": -71.5948486328125
},
{
"x": 321.446350097656,
"y": -71.5948486328125
},
{
"x": 321.446350097656,
"y": -188.484436035156
},
{
"x": 467.558319091797,
"y": -188.484436035156
},
{
"x": 467.558319091797,
"y": -18.9945449829102
},
{
"x": 604.903564453125,
"y": -18.9945449829102
},
{
"x": 604.903564453125,
"y": -124.195159912109
},
{
"x": 756.860046386719,
"y": -124.195159912109
},
{
"x": 756.860046386719,
"y": 21.9168128967285
},
{
"x": 935.116638183594,
"y": 21.9168128967285
},
{
"x": 935.116638183594,
"y": 284.918365478516
}
],
"reshaped": false,
"shape_type": "polygon",
"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,
0,
0,
0,
0
]
},
{
"closed": true,
"fill_color": "#DDE7E8",
"outline_color": "#DDE7E8",
"outline_width": 0.0,
"points": [
{
"x": -195.827392578125,
"y": -154.283813476563
},
{
"x": -160.760498046875,
"y": -154.283813476563
},
{
"x": -160.760498046875,
"y": -98.7612609863281
},
{
"x": -195.827392578125,
"y": -98.7612609863281
}
],
"reshaped": false,
"shape_type": "polygon",
"vertex_flags": [
0,
0,
0,
0
]
},
{
"closed": true,
"fill_color": "#DDE7E8",
"outline_color": "#DDE7E8",
"outline_width": 0.0,
"points": [
{
"x": -753.937805175781,
"y": -71.5948486328125
},
{
"x": -718.870910644531,
"y": -71.5948486328125
},
{
"x": -718.870910644531,
"y": -18.9945449829102
},
{
"x": -753.937805175781,
"y": -18.9945449829102
}
],
"reshaped": false,
"shape_type": "polygon",
"vertex_flags": [
0,
0,
0,
0
]
},
{
"closed": true,
"fill_color": "#DDE7E8",
"outline_color": "#DDE7E8",
"outline_width": 0.0,
"points": [
{
"x": -470.480560302734,
"y": -191.406677246094
},
{
"x": -435.413696289063,
"y": -191.406677246094
},
{
"x": -435.413696289063,
"y": -138.806365966797
},
{
"x": -470.480560302734,
"y": -138.806365966797
}
],
"reshaped": false,
"shape_type": "polygon",
"vertex_flags": [
0,
0,
0,
0
]
},
{
"closed": true,
"fill_color": "#DDE7E8",
"outline_color": "#DDE7E8",
"outline_width": 0.0,
"points": [
{
"x": 87.6671829223633,
"y": -232.318023681641
},
{
"x": 122.734062194824,
"y": -232.318023681641
},
{
"x": 122.734062194824,
"y": -173.873229980469
},
{
"x": 87.6671829223633,
"y": -173.873229980469
}
],
"reshaped": false,
"shape_type": "polygon",
"vertex_flags": [
0,
0,
0,
0
]
},
{
"closed": true,
"fill_color": "#DDE7E8",
"outline_color": "#DDE7E8",
"outline_width": 0.0,
"points": [
{
"x": 371.124420166016,
"y": -135.884124755859
},
{
"x": 406.191284179688,
"y": -135.884124755859
},
{
"x": 406.191284179688,
"y": -83.2838134765625
},
{
"x": 371.124420166016,
"y": -83.2838134765625
}
],
"reshaped": false,
"shape_type": "polygon",
"vertex_flags": [
0,
0,
0,
0
]
},
{
"closed": true,
"fill_color": "#DDE7E8",
"outline_color": "#DDE7E8",
"outline_width": 0.0,
"points": [
{
"x": 660.426147460938,
"y": -77.4393310546875
},
{
"x": 695.492980957031,
"y": -77.4393310546875
},
{
"x": 695.492980957031,
"y": -27.7612609863281
},
{
"x": 660.426147460938,
"y": -27.7612609863281
}
],
"reshaped": false,
"shape_type": "polygon",
"vertex_flags": [
0,
0,
0,
0
]
}
],
"tags": [
"buildings"
],
"version": "1.1"
}
File diff suppressed because it is too large Load Diff
-277
View File
@@ -1,277 +0,0 @@
{
"anchors": {
"origin": {
"allowed_hands": "both",
"role": "pivot",
"rotation_deg": 0.0,
"x": 0.0,
"y": 0.0,
"z_index_offset": 0
}
},
"material_preset": "none",
"metadata": {
"created_at": "2026-10-21T23:11:48",
"modified_at": "2026-10-21T23:11:48"
},
"prop_name": "Framed Picture",
"shapes": [
{
"closed": true,
"fill_color": "#B68165",
"outline_color": "#332B45",
"outline_width": 5.324,
"points": [
{
"x": -146.410003662109,
"y": -109.141998291016
},
{
"x": 146.410003662109,
"y": -109.141998291016
},
{
"x": 146.410003662109,
"y": 109.141998291016
},
{
"x": -146.410003662109,
"y": 109.141998291016
}
],
"reshaped": false,
"shape_type": "polygon",
"vertex_flags": [
0,
0,
0,
0
]
},
{
"closed": true,
"fill_color": "#F4DEAF",
"outline_color": "#332B45",
"outline_width": 5.324,
"points": [
{
"x": -121.120994567871,
"y": -85.1839981079102
},
{
"x": 121.120994567871,
"y": -85.1839981079102
},
{
"x": 121.120994567871,
"y": 85.1839981079102
},
{
"x": -121.120994567871,
"y": 85.1839981079102
}
],
"reshaped": false,
"shape_type": "polygon",
"vertex_flags": [
0,
0,
0,
0
]
},
{
"closed": true,
"fill_color": "#A4CFCD",
"outline_color": "#332B45",
"outline_width": 5.324,
"points": [
{
"x": -106.479995727539,
"y": -71.8740005493164
},
{
"x": 106.479995727539,
"y": -71.8740005493164
},
{
"x": 106.479995727539,
"y": 71.8740005493164
},
{
"x": -106.479995727539,
"y": 71.8740005493164
}
],
"reshaped": false,
"shape_type": "polygon",
"vertex_flags": [
0,
0,
0,
0
]
},
{
"closed": true,
"fill_color": "#F3D68C",
"outline_color": "#F3D68C",
"outline_width": 0.0,
"points": [
{
"x": 77.197998046875,
"y": -39.9300003051758
},
{
"x": 75.6806564331055,
"y": -32.2900581359863
},
{
"x": 71.3549118041992,
"y": -25.8080883026123
},
{
"x": 64.8729400634766,
"y": -21.4823379516602
},
{
"x": 57.2329978942871,
"y": -19.9650001525879
},
{
"x": 49.5930557250977,
"y": -21.4823379516602
},
{
"x": 43.1110877990723,
"y": -25.8080883026123
},
{
"x": 38.7853393554688,
"y": -32.2900581359863
},
{
"x": 37.2679977416992,
"y": -39.9300003051758
},
{
"x": 38.7853393554688,
"y": -47.5699424743652
},
{
"x": 43.1110877990723,
"y": -54.0519104003906
},
{
"x": 49.5930557250977,
"y": -58.3776588439941
},
{
"x": 57.2329978942871,
"y": -59.8950004577637
},
{
"x": 64.8729400634766,
"y": -58.3776588439941
},
{
"x": 71.3549118041992,
"y": -54.0519104003906
},
{
"x": 75.6806564331055,
"y": -47.5699424743652
}
],
"reshaped": false,
"shape_type": "polygon",
"vertex_flags": [
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0
]
},
{
"closed": true,
"fill_color": "#88B798",
"outline_color": "#88B798",
"outline_width": 0.0,
"points": [
{
"x": -106.479995727539,
"y": 71.8740005493164
},
{
"x": -26.6199989318848,
"y": -14.6409997940063
},
{
"x": 13.3099994659424,
"y": 30.6129989624023
},
{
"x": 53.2399978637695,
"y": -23.9580001831055
},
{
"x": 106.479995727539,
"y": 51.9090003967285
},
{
"x": 106.479995727539,
"y": 71.8740005493164
}
],
"reshaped": false,
"shape_type": "polygon",
"vertex_flags": [
0,
0,
0,
0,
0,
0
]
},
{
"closed": false,
"fill_color": "#00000000",
"outline_color": "#F0E1BC",
"outline_width": 5.324,
"points": [
{
"x": -85.1839981079102,
"y": 55.9020004272461
},
{
"x": -10.6479997634888,
"y": 55.9020004272461
}
],
"reshaped": false,
"shape_type": "line",
"vertex_flags": [
0,
0
]
}
],
"tags": [
"objects",
"walls"
],
"version": "1.1"
}
-236
View File
@@ -1,236 +0,0 @@
{
"version": "1.1",
"prop_name": "Potted Plant",
"material_preset": "none",
"tags": ["nature", "objects"],
"shapes": [
{
"shape_type": "polygon",
"points": [
{
"x": -45,
"y": 16
},
{
"x": -55,
"y": -6
},
{
"x": -39,
"y": -74
},
{
"x": -4,
"y": -28
},
{
"x": 8,
"y": 11
}
],
"fill_color": "#70B481",
"outline_color": "#332B45",
"outline_width": 4,
"closed": true,
"vertex_flags": [
0,
0,
0,
0,
0
],
"reshaped": false
},
{
"shape_type": "polygon",
"points": [
{
"x": 3,
"y": 8
},
{
"x": 1,
"y": -31
},
{
"x": 38,
"y": -91
},
{
"x": 48,
"y": -48
},
{
"x": 36,
"y": 4
}
],
"fill_color": "#83C290",
"outline_color": "#332B45",
"outline_width": 4,
"closed": true,
"vertex_flags": [
0,
0,
0,
0,
0
],
"reshaped": false
},
{
"shape_type": "polygon",
"points": [
{
"x": -18,
"y": 14
},
{
"x": -4,
"y": -112
},
{
"x": 18,
"y": -56
},
{
"x": 16,
"y": 15
}
],
"fill_color": "#9CCF90",
"outline_color": "#332B45",
"outline_width": 4,
"closed": true,
"vertex_flags": [
0,
0,
0,
0
],
"reshaped": false
},
{
"shape_type": "line",
"points": [
{
"x": 0,
"y": 30
},
{
"x": 0,
"y": -69
}
],
"fill_color": "#00000000",
"outline_color": "#5C946F",
"outline_width": 5,
"closed": false,
"vertex_flags": [
0,
0
],
"reshaped": false
},
{
"shape_type": "polygon",
"points": [
{
"x": -55,
"y": 17
},
{
"x": 55,
"y": 17
},
{
"x": 43,
"y": 102
},
{
"x": -43,
"y": 102
}
],
"fill_color": "#C47761",
"outline_color": "#332B45",
"outline_width": 4,
"closed": true,
"vertex_flags": [
0,
0,
0,
0
],
"reshaped": false
},
{
"shape_type": "polygon",
"points": [
{
"x": -60,
"y": 14
},
{
"x": 60,
"y": 14
},
{
"x": 60,
"y": 31
},
{
"x": -60,
"y": 31
}
],
"fill_color": "#DC9670",
"outline_color": "#332B45",
"outline_width": 4,
"closed": true,
"vertex_flags": [
0,
0,
0,
0
],
"reshaped": false
},
{
"shape_type": "line",
"points": [
{
"x": -21,
"y": 59
},
{
"x": 21,
"y": 59
}
],
"fill_color": "#00000000",
"outline_color": "#EFC09A",
"outline_width": 4,
"closed": false,
"vertex_flags": [
0,
0
],
"reshaped": false
}
],
"anchors": {
"origin": {
"role": "pivot",
"x": 0,
"y": 0,
"rotation_deg": 0,
"z_index_offset": 0,
"allowed_hands": "both"
}
},
"metadata": {
"created_at": "2026-10-15T00:00:00",
"modified_at": "2026-10-15T00:00:00"
}
}
-306
View File
@@ -1,306 +0,0 @@
{
"anchors": {
"origin": {
"allowed_hands": "both",
"role": "pivot",
"rotation_deg": 0.0,
"x": 0.0,
"y": 0.0,
"z_index_offset": 0
}
},
"material_preset": "none",
"metadata": {
"created_at": "2026-10-21T23:12:17",
"modified_at": "2026-10-21T23:12:17"
},
"prop_name": "Patterned Rug",
"shapes": [
{
"closed": true,
"fill_color": "#C77377",
"outline_color": "#332B45",
"outline_width": 5.324,
"points": [
{
"x": -210.297988891602,
"y": -89.8424987792969
},
{
"x": 210.297988891602,
"y": -89.8424987792969
},
{
"x": 235.586990356445,
"y": 71.20849609375
},
{
"x": -235.586990356445,
"y": 71.20849609375
}
],
"reshaped": false,
"shape_type": "polygon",
"vertex_flags": [
0,
0,
0,
0
]
},
{
"closed": true,
"fill_color": "#F0D29A",
"outline_color": "#F0D29A",
"outline_width": 2.662,
"points": [
{
"x": -175.692001342773,
"y": -63.2224998474121
},
{
"x": 175.692001342773,
"y": -63.2224998474121
},
{
"x": 195.656997680664,
"y": 44.5884971618652
},
{
"x": -195.656997680664,
"y": 44.5884971618652
}
],
"reshaped": false,
"shape_type": "polygon",
"vertex_flags": [
0,
0,
0,
0
]
},
{
"closed": true,
"fill_color": "#7E9B9C",
"outline_color": "#7E9B9C",
"outline_width": 2.662,
"points": [
{
"x": -129.106994628906,
"y": -44.5885009765625
},
{
"x": 129.106994628906,
"y": -44.5885009765625
},
{
"x": 141.085998535156,
"y": 25.9544982910156
},
{
"x": -141.085998535156,
"y": 25.9544982910156
}
],
"reshaped": false,
"shape_type": "polygon",
"vertex_flags": [
0,
0,
0,
0
]
},
{
"closed": true,
"fill_color": "#F6D69A",
"outline_color": "#F6D69A",
"outline_width": 1.331,
"points": [
{
"x": 0.0,
"y": -37.9335021972656
},
{
"x": 46.5849990844727,
"y": -8.6515007019043
},
{
"x": 0.0,
"y": 19.2994995117188
},
{
"x": -46.5849990844727,
"y": -8.6515007019043
}
],
"reshaped": false,
"shape_type": "polygon",
"vertex_flags": [
0,
0,
0,
0
]
},
{
"closed": false,
"fill_color": "#00000000",
"outline_color": "#DBA389",
"outline_width": 3.993,
"points": [
{
"x": -183.677993774414,
"y": 72.5394973754883
},
{
"x": -183.677993774414,
"y": 89.8424987792969
}
],
"reshaped": false,
"shape_type": "line",
"vertex_flags": [
0,
0
]
},
{
"closed": false,
"fill_color": "#00000000",
"outline_color": "#DBA389",
"outline_width": 3.993,
"points": [
{
"x": -122.451995849609,
"y": 72.5394973754883
},
{
"x": -122.451995849609,
"y": 89.8424987792969
}
],
"reshaped": false,
"shape_type": "line",
"vertex_flags": [
0,
0
]
},
{
"closed": false,
"fill_color": "#00000000",
"outline_color": "#DBA389",
"outline_width": 3.993,
"points": [
{
"x": -61.2259979248047,
"y": 72.5394973754883
},
{
"x": -61.2259979248047,
"y": 89.8424987792969
}
],
"reshaped": false,
"shape_type": "line",
"vertex_flags": [
0,
0
]
},
{
"closed": false,
"fill_color": "#00000000",
"outline_color": "#DBA389",
"outline_width": 3.993,
"points": [
{
"x": 0.0,
"y": 72.5394973754883
},
{
"x": 0.0,
"y": 89.8424987792969
}
],
"reshaped": false,
"shape_type": "line",
"vertex_flags": [
0,
0
]
},
{
"closed": false,
"fill_color": "#00000000",
"outline_color": "#DBA389",
"outline_width": 3.993,
"points": [
{
"x": 61.2259979248047,
"y": 72.5394973754883
},
{
"x": 61.2259979248047,
"y": 89.8424987792969
}
],
"reshaped": false,
"shape_type": "line",
"vertex_flags": [
0,
0
]
},
{
"closed": false,
"fill_color": "#00000000",
"outline_color": "#DBA389",
"outline_width": 3.993,
"points": [
{
"x": 122.451995849609,
"y": 72.5394973754883
},
{
"x": 122.451995849609,
"y": 89.8424987792969
}
],
"reshaped": false,
"shape_type": "line",
"vertex_flags": [
0,
0
]
},
{
"closed": false,
"fill_color": "#00000000",
"outline_color": "#DBA389",
"outline_width": 3.993,
"points": [
{
"x": 183.677993774414,
"y": 72.5394973754883
},
{
"x": 183.677993774414,
"y": 89.8424987792969
}
],
"reshaped": false,
"shape_type": "line",
"vertex_flags": [
0,
0
]
}
],
"tags": [
"objects"
],
"version": "1.1"
}
-548
View File
@@ -1,548 +0,0 @@
{
"anchors": {
"origin": {
"allowed_hands": "both",
"role": "pivot",
"rotation_deg": 0.0,
"x": 0.0,
"y": 0.0,
"z_index_offset": 0
}
},
"material_preset": "none",
"metadata": {
"created_at": "2026-10-21T21:40:26",
"modified_at": "2026-10-21T21:40:26"
},
"prop_name": "Sunny Storefront",
"shapes": [
{
"closed": true,
"fill_color": "#E8B571",
"outline_color": "#332B45",
"outline_width": 20.2178811399718,
"points": [
{
"x": -783.44287109375,
"y": -596.427490234375
},
{
"x": 783.44287109375,
"y": -596.427490234375
},
{
"x": 783.44287109375,
"y": 798.606262207031
},
{
"x": -783.44287109375,
"y": 798.606262207031
}
],
"reshaped": false,
"shape_type": "polygon",
"vertex_flags": [
0,
0,
0,
0
]
},
{
"closed": true,
"fill_color": "#6C4E69",
"outline_color": "#332B45",
"outline_width": 20.2178811399718,
"points": [
{
"x": -859.259887695313,
"y": -677.299011230469
},
{
"x": 859.259887695313,
"y": -677.299011230469
},
{
"x": 859.259887695313,
"y": -535.773864746094
},
{
"x": -859.259887695313,
"y": -535.773864746094
}
],
"reshaped": false,
"shape_type": "polygon",
"vertex_flags": [
0,
0,
0,
0
]
},
{
"closed": true,
"fill_color": "#FAE6AF",
"outline_color": "#332B45",
"outline_width": 20.2178811399718,
"points": [
{
"x": -581.264038085938,
"y": -813.769653320313
},
{
"x": 581.264038085938,
"y": -813.769653320313
},
{
"x": 581.264038085938,
"y": -682.353454589844
},
{
"x": -581.264038085938,
"y": -682.353454589844
}
],
"reshaped": false,
"shape_type": "polygon",
"vertex_flags": [
0,
0,
0,
0
]
},
{
"closed": false,
"fill_color": "#00000000",
"outline_color": "#B97768",
"outline_width": 25.2723514249647,
"points": [
{
"x": -252.723510742188,
"y": -743.007141113281
},
{
"x": 252.723510742188,
"y": -743.007141113281
}
],
"reshaped": false,
"shape_type": "line",
"vertex_flags": [
0,
0
]
},
{
"closed": true,
"fill_color": "#F3D77A",
"outline_color": "#332B45",
"outline_width": 20.2178811399718,
"points": [
{
"x": -717.734741210938,
"y": -520.610412597656
},
{
"x": 717.734741210938,
"y": -520.610412597656
},
{
"x": 717.734741210938,
"y": -313.377197265625
},
{
"x": -717.734741210938,
"y": -313.377197265625
}
],
"reshaped": false,
"shape_type": "polygon",
"vertex_flags": [
0,
0,
0,
0
]
},
{
"closed": true,
"fill_color": "#E37C70",
"outline_color": "#332B45",
"outline_width": 20.2178811399718,
"points": [
{
"x": -717.734741210938,
"y": -520.610412597656
},
{
"x": -480.174652099609,
"y": -520.610412597656
},
{
"x": -520.610412597656,
"y": -318.431640625
},
{
"x": -677.299011230469,
"y": -318.431640625
}
],
"reshaped": false,
"shape_type": "polygon",
"vertex_flags": [
0,
0,
0,
0
]
},
{
"closed": true,
"fill_color": "#FFF2D4",
"outline_color": "#332B45",
"outline_width": 20.2178811399718,
"points": [
{
"x": -480.174652099609,
"y": -520.610412597656
},
{
"x": -242.614562988281,
"y": -520.610412597656
},
{
"x": -283.050323486328,
"y": -318.431640625
},
{
"x": -439.738891601563,
"y": -318.431640625
}
],
"reshaped": false,
"shape_type": "polygon",
"vertex_flags": [
0,
0,
0,
0
]
},
{
"closed": true,
"fill_color": "#E37C70",
"outline_color": "#332B45",
"outline_width": 20.2178811399718,
"points": [
{
"x": -242.614562988281,
"y": -520.610412597656
},
{
"x": -5.05447006225586,
"y": -520.610412597656
},
{
"x": -45.4902305603027,
"y": -318.431640625
},
{
"x": -202.178802490234,
"y": -318.431640625
}
],
"reshaped": false,
"shape_type": "polygon",
"vertex_flags": [
0,
0,
0,
0
]
},
{
"closed": true,
"fill_color": "#FFF2D4",
"outline_color": "#332B45",
"outline_width": 20.2178811399718,
"points": [
{
"x": -5.05447006225586,
"y": -520.610412597656
},
{
"x": 232.505615234375,
"y": -520.610412597656
},
{
"x": 192.069854736328,
"y": -318.431640625
},
{
"x": 35.381290435791,
"y": -318.431640625
}
],
"reshaped": false,
"shape_type": "polygon",
"vertex_flags": [
0,
0,
0,
0
]
},
{
"closed": true,
"fill_color": "#E37C70",
"outline_color": "#332B45",
"outline_width": 20.2178811399718,
"points": [
{
"x": 232.505615234375,
"y": -520.610412597656
},
{
"x": 470.065704345703,
"y": -520.610412597656
},
{
"x": 429.629943847656,
"y": -318.431640625
},
{
"x": 272.941375732422,
"y": -318.431640625
}
],
"reshaped": false,
"shape_type": "polygon",
"vertex_flags": [
0,
0,
0,
0
]
},
{
"closed": true,
"fill_color": "#FFF2D4",
"outline_color": "#332B45",
"outline_width": 20.2178811399718,
"points": [
{
"x": 470.065704345703,
"y": -520.610412597656
},
{
"x": 707.625793457031,
"y": -520.610412597656
},
{
"x": 667.190063476563,
"y": -318.431640625
},
{
"x": 510.50146484375,
"y": -318.431640625
}
],
"reshaped": false,
"shape_type": "polygon",
"vertex_flags": [
0,
0,
0,
0
]
},
{
"closed": true,
"fill_color": "#81B5BD",
"outline_color": "#332B45",
"outline_width": 20.2178811399718,
"points": [
{
"x": -636.863220214844,
"y": -247.669067382813
},
{
"x": -101.089401245117,
"y": -247.669067382813
},
{
"x": -101.089401245117,
"y": 722.789184570313
},
{
"x": -636.863220214844,
"y": 722.789184570313
}
],
"reshaped": false,
"shape_type": "polygon",
"vertex_flags": [
0,
0,
0,
0
]
},
{
"closed": true,
"fill_color": "#D4F1E8",
"outline_color": "#332B45",
"outline_width": 20.2178811399718,
"points": [
{
"x": -566.100646972656,
"y": -171.851989746094
},
{
"x": -171.851989746094,
"y": -171.851989746094
},
{
"x": -171.851989746094,
"y": 591.372985839844
},
{
"x": -566.100646972656,
"y": 591.372985839844
}
],
"reshaped": false,
"shape_type": "polygon",
"vertex_flags": [
0,
0,
0,
0
]
},
{
"closed": true,
"fill_color": "#835B73",
"outline_color": "#332B45",
"outline_width": 20.2178811399718,
"points": [
{
"x": 111.198341369629,
"y": -247.669067382813
},
{
"x": 611.590881347656,
"y": -247.669067382813
},
{
"x": 611.590881347656,
"y": 722.789184570313
},
{
"x": 111.198341369629,
"y": 722.789184570313
}
],
"reshaped": false,
"shape_type": "polygon",
"vertex_flags": [
0,
0,
0,
0
]
},
{
"closed": true,
"fill_color": "#C4DCDD",
"outline_color": "#332B45",
"outline_width": 20.2178811399718,
"points": [
{
"x": 171.851989746094,
"y": -181.9609375
},
{
"x": 545.882751464844,
"y": -181.9609375
},
{
"x": 545.882751464844,
"y": 596.427429199219
},
{
"x": 171.851989746094,
"y": 596.427429199219
}
],
"reshaped": false,
"shape_type": "polygon",
"vertex_flags": [
0,
0,
0,
0
]
},
{
"closed": false,
"fill_color": "#00000000",
"outline_color": "#835B73",
"outline_width": 25.2723514249647,
"points": [
{
"x": 358.867370605469,
"y": -181.9609375
},
{
"x": 358.867370605469,
"y": 596.427429199219
}
],
"reshaped": false,
"shape_type": "line",
"vertex_flags": [
0,
0
]
},
{
"closed": true,
"fill_color": "#C99672",
"outline_color": "#332B45",
"outline_width": 20.2178811399718,
"points": [
{
"x": -808.715209960938,
"y": 722.789184570313
},
{
"x": 808.715209960938,
"y": 722.789184570313
},
{
"x": 808.715209960938,
"y": 813.769653320313
},
{
"x": -808.715209960938,
"y": 813.769653320313
}
],
"reshaped": false,
"shape_type": "polygon",
"vertex_flags": [
0,
0,
0,
0
]
}
],
"tags": [
"buildings"
],
"version": "1.1"
}
-475
View File
@@ -1,475 +0,0 @@
{
"anchors": {
"origin": {
"allowed_hands": "both",
"role": "pivot",
"rotation_deg": 0.0,
"x": 0.0,
"y": 0.0,
"z_index_offset": 0
}
},
"material_preset": "none",
"metadata": {
"created_at": "2026-10-21T21:40:03",
"modified_at": "2026-10-21T21:40:03"
},
"prop_name": "Town House",
"shapes": [
{
"closed": true,
"fill_color": "#B3C99C",
"outline_color": "#332B45",
"outline_width": 27.3735711442786,
"points": [
{
"x": -807.520385742188,
"y": -732.243041992188
},
{
"x": 807.520385742188,
"y": -732.243041992188
},
{
"x": 807.520385742188,
"y": 1307.08801269531
},
{
"x": -807.520385742188,
"y": 1307.08801269531
}
],
"reshaped": false,
"shape_type": "polygon",
"vertex_flags": [
0,
0,
0,
0
]
},
{
"closed": true,
"fill_color": "#CF735F",
"outline_color": "#332B45",
"outline_width": 27.3735711442786,
"points": [
{
"x": -944.38818359375,
"y": -663.80908203125
},
{
"x": 0.0,
"y": -1361.83520507813
},
{
"x": 944.38818359375,
"y": -663.80908203125
}
],
"reshaped": false,
"shape_type": "polygon",
"vertex_flags": [
0,
0,
0
]
},
{
"closed": true,
"fill_color": "#F3E3B2",
"outline_color": "#332B45",
"outline_width": 27.3735711442786,
"points": [
{
"x": -164.241424560547,
"y": -1197.59375
},
{
"x": 164.241424560547,
"y": -1197.59375
},
{
"x": 164.241424560547,
"y": -725.399658203125
},
{
"x": -164.241424560547,
"y": -725.399658203125
}
],
"reshaped": false,
"shape_type": "polygon",
"vertex_flags": [
0,
0,
0,
0
]
},
{
"closed": true,
"fill_color": "#85ADBC",
"outline_color": "#332B45",
"outline_width": 27.3735711442786,
"points": [
{
"x": -136.867858886719,
"y": -1170.22021484375
},
{
"x": 136.867858886719,
"y": -1170.22021484375
},
{
"x": 136.867858886719,
"y": -752.773193359375
},
{
"x": -136.867858886719,
"y": -752.773193359375
}
],
"reshaped": false,
"shape_type": "polygon",
"vertex_flags": [
0,
0,
0,
0
]
},
{
"closed": true,
"fill_color": "#E9D6A9",
"outline_color": "#332B45",
"outline_width": 27.3735711442786,
"points": [
{
"x": -602.218566894531,
"y": -362.699829101563
},
{
"x": -191.615005493164,
"y": -362.699829101563
},
{
"x": -191.615005493164,
"y": 225.831939697266
},
{
"x": -602.218566894531,
"y": 225.831939697266
}
],
"reshaped": false,
"shape_type": "polygon",
"vertex_flags": [
0,
0,
0,
0
]
},
{
"closed": true,
"fill_color": "#E9D6A9",
"outline_color": "#332B45",
"outline_width": 27.3735711442786,
"points": [
{
"x": 191.615005493164,
"y": -362.699829101563
},
{
"x": 602.218566894531,
"y": -362.699829101563
},
{
"x": 602.218566894531,
"y": 225.831939697266
},
{
"x": 191.615005493164,
"y": 225.831939697266
}
],
"reshaped": false,
"shape_type": "polygon",
"vertex_flags": [
0,
0,
0,
0
]
},
{
"closed": true,
"fill_color": "#B9DFE3",
"outline_color": "#332B45",
"outline_width": 27.3735711442786,
"points": [
{
"x": -533.78466796875,
"y": -287.422546386719
},
{
"x": -260.048919677734,
"y": -287.422546386719
},
{
"x": -260.048919677734,
"y": 150.554595947266
},
{
"x": -533.78466796875,
"y": 150.554595947266
}
],
"reshaped": false,
"shape_type": "polygon",
"vertex_flags": [
0,
0,
0,
0
]
},
{
"closed": true,
"fill_color": "#B9DFE3",
"outline_color": "#332B45",
"outline_width": 27.3735711442786,
"points": [
{
"x": 260.048919677734,
"y": -287.422546386719
},
{
"x": 533.78466796875,
"y": -287.422546386719
},
{
"x": 533.78466796875,
"y": 150.554595947266
},
{
"x": 260.048919677734,
"y": 150.554595947266
}
],
"reshaped": false,
"shape_type": "polygon",
"vertex_flags": [
0,
0,
0,
0
]
},
{
"closed": false,
"fill_color": "#00000000",
"outline_color": "#E9D6A9",
"outline_width": 27.3735711442786,
"points": [
{
"x": -396.916778564453,
"y": -287.422546386719
},
{
"x": -396.916778564453,
"y": 150.554595947266
}
],
"reshaped": false,
"shape_type": "line",
"vertex_flags": [
0,
0
]
},
{
"closed": false,
"fill_color": "#00000000",
"outline_color": "#E9D6A9",
"outline_width": 27.3735711442786,
"points": [
{
"x": 396.916778564453,
"y": -287.422546386719
},
{
"x": 396.916778564453,
"y": 150.554595947266
}
],
"reshaped": false,
"shape_type": "line",
"vertex_flags": [
0,
0
]
},
{
"closed": true,
"fill_color": "#8A6273",
"outline_color": "#332B45",
"outline_width": 27.3735711442786,
"points": [
{
"x": -246.36213684082,
"y": 417.446929931641
},
{
"x": 246.36213684082,
"y": 417.446929931641
},
{
"x": 246.36213684082,
"y": 1307.08801269531
},
{
"x": -246.36213684082,
"y": 1307.08801269531
}
],
"reshaped": false,
"shape_type": "polygon",
"vertex_flags": [
0,
0,
0,
0
]
},
{
"closed": true,
"fill_color": "#F5D98B",
"outline_color": "#F5D98B",
"outline_width": 6.84339278606966,
"points": [
{
"x": 184.771606445313,
"y": 903.327819824219
},
{
"x": 182.171127319336,
"y": 916.398681640625
},
{
"x": 174.780258178711,
"y": 927.553466796875
},
{
"x": 163.625518798828,
"y": 934.9443359375
},
{
"x": 150.554641723633,
"y": 937.544799804688
},
{
"x": 137.483764648438,
"y": 934.9443359375
},
{
"x": 126.329025268555,
"y": 927.553466796875
},
{
"x": 118.938163757324,
"y": 916.398681640625
},
{
"x": 116.337677001953,
"y": 903.327819824219
},
{
"x": 118.938163757324,
"y": 890.256958007813
},
{
"x": 126.329025268555,
"y": 879.102172851563
},
{
"x": 137.483764648438,
"y": 871.711364746094
},
{
"x": 150.554641723633,
"y": 869.11083984375
},
{
"x": 163.625518798828,
"y": 871.711364746094
},
{
"x": 174.780258178711,
"y": 879.102172851563
},
{
"x": 182.171127319336,
"y": 890.256958007813
}
],
"reshaped": false,
"shape_type": "polygon",
"vertex_flags": [
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0
]
},
{
"closed": true,
"fill_color": "#E8B38A",
"outline_color": "#332B45",
"outline_width": 27.3735711442786,
"points": [
{
"x": -875.954284667969,
"y": 1231.81066894531
},
{
"x": 875.954284667969,
"y": 1231.81066894531
},
{
"x": 875.954284667969,
"y": 1361.83520507813
},
{
"x": -875.954284667969,
"y": 1361.83520507813
}
],
"reshaped": false,
"shape_type": "polygon",
"vertex_flags": [
0,
0,
0,
0
]
}
],
"tags": [
"buildings"
],
"version": "1.1"
}
File diff suppressed because it is too large Load Diff
-433
View File
@@ -1,433 +0,0 @@
{
"anchors": {
"origin": {
"allowed_hands": "both",
"role": "pivot",
"rotation_deg": 0.0,
"x": 0.0,
"y": 0.0,
"z_index_offset": 0
}
},
"material_preset": "none",
"metadata": {
"created_at": "2026-10-21T23:12:39",
"modified_at": "2026-10-21T23:12:39"
},
"prop_name": "Wall Clock",
"shapes": [
{
"closed": true,
"fill_color": "#DCA678",
"outline_color": "#332B45",
"outline_width": 7.3205,
"points": [
{
"x": 102.486999511719,
"y": 0.0
},
{
"x": 94.6833419799805,
"y": 39.2232398986816
},
{
"x": 72.4729537963867,
"y": 72.4729537963867
},
{
"x": 39.2232398986816,
"y": 94.6833419799805
},
{
"x": 0.0,
"y": 102.486999511719
},
{
"x": -39.2232398986816,
"y": 94.6833419799805
},
{
"x": -72.4729537963867,
"y": 72.4729537963867
},
{
"x": -94.6833419799805,
"y": 39.2232398986816
},
{
"x": -102.486999511719,
"y": 0.0
},
{
"x": -94.6833419799805,
"y": -39.2232398986816
},
{
"x": -72.4729537963867,
"y": -72.4729537963867
},
{
"x": -39.2232398986816,
"y": -94.6833419799805
},
{
"x": 0.0,
"y": -102.486999511719
},
{
"x": 39.2232398986816,
"y": -94.6833419799805
},
{
"x": 72.4729537963867,
"y": -72.4729537963867
},
{
"x": 94.6833419799805,
"y": -39.2232398986816
}
],
"reshaped": false,
"shape_type": "polygon",
"vertex_flags": [
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0
]
},
{
"closed": true,
"fill_color": "#FCF2D8",
"outline_color": "#332B45",
"outline_width": 4.3923,
"points": [
{
"x": 79.0614013671875,
"y": 0.0
},
{
"x": 73.0439453125,
"y": 30.2483062744141
},
{
"x": 55.8993377685547,
"y": 55.8993377685547
},
{
"x": 30.2483062744141,
"y": 73.0439453125
},
{
"x": 0.0,
"y": 79.0614013671875
},
{
"x": -30.2483062744141,
"y": 73.0439453125
},
{
"x": -55.8993377685547,
"y": 55.8993377685547
},
{
"x": -73.0439453125,
"y": 30.2483062744141
},
{
"x": -79.0614013671875,
"y": 0.0
},
{
"x": -73.0439453125,
"y": -30.2483062744141
},
{
"x": -55.8993377685547,
"y": -55.8993377685547
},
{
"x": -30.2483062744141,
"y": -73.0439453125
},
{
"x": 0.0,
"y": -79.0614013671875
},
{
"x": 30.2483062744141,
"y": -73.0439453125
},
{
"x": 55.8993377685547,
"y": -55.8993377685547
},
{
"x": 73.0439453125,
"y": -30.2483062744141
}
],
"reshaped": false,
"shape_type": "polygon",
"vertex_flags": [
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0
]
},
{
"closed": false,
"fill_color": "#00000000",
"outline_color": "#75586C",
"outline_width": 5.8564,
"points": [
{
"x": 0.0,
"y": -65.8844985961914
},
{
"x": 0.0,
"y": -45.3871002197266
}
],
"reshaped": false,
"shape_type": "line",
"vertex_flags": [
0,
0
]
},
{
"closed": false,
"fill_color": "#00000000",
"outline_color": "#75586C",
"outline_width": 5.8564,
"points": [
{
"x": 64.4204025268555,
"y": 0.0
},
{
"x": 43.9230003356934,
"y": 0.0
}
],
"reshaped": false,
"shape_type": "line",
"vertex_flags": [
0,
0
]
},
{
"closed": false,
"fill_color": "#00000000",
"outline_color": "#75586C",
"outline_width": 5.8564,
"points": [
{
"x": 0.0,
"y": 65.8844985961914
},
{
"x": 0.0,
"y": 45.3871002197266
}
],
"reshaped": false,
"shape_type": "line",
"vertex_flags": [
0,
0
]
},
{
"closed": false,
"fill_color": "#00000000",
"outline_color": "#75586C",
"outline_width": 5.8564,
"points": [
{
"x": -64.4204025268555,
"y": 0.0
},
{
"x": -43.9230003356934,
"y": 0.0
}
],
"reshaped": false,
"shape_type": "line",
"vertex_flags": [
0,
0
]
},
{
"closed": false,
"fill_color": "#00000000",
"outline_color": "#75586C",
"outline_width": 7.3205,
"points": [
{
"x": 0.0,
"y": 0.0
},
{
"x": 0.0,
"y": -35.1384010314941
}
],
"reshaped": false,
"shape_type": "line",
"vertex_flags": [
0,
0
]
},
{
"closed": false,
"fill_color": "#00000000",
"outline_color": "#75586C",
"outline_width": 7.3205,
"points": [
{
"x": 0.0,
"y": 0.0
},
{
"x": 35.1384010314941,
"y": 21.9615001678467
}
],
"reshaped": false,
"shape_type": "line",
"vertex_flags": [
0,
0
]
},
{
"closed": true,
"fill_color": "#75586C",
"outline_color": "#332B45",
"outline_width": 1.4641,
"points": [
{
"x": 7.32049989700317,
"y": 0.0
},
{
"x": 6.76414203643799,
"y": 2.79643106460571
},
{
"x": 5.1829137802124,
"y": 5.1829137802124
},
{
"x": 2.79643106460571,
"y": 6.76414203643799
},
{
"x": 0.0,
"y": 7.32049989700317
},
{
"x": -2.79643106460571,
"y": 6.76414203643799
},
{
"x": -5.1829137802124,
"y": 5.1829137802124
},
{
"x": -6.76414203643799,
"y": 2.79643106460571
},
{
"x": -7.32049989700317,
"y": 0.0
},
{
"x": -6.76414203643799,
"y": -2.79643106460571
},
{
"x": -5.1829137802124,
"y": -5.1829137802124
},
{
"x": -2.79643106460571,
"y": -6.76414203643799
},
{
"x": 0.0,
"y": -7.32049989700317
},
{
"x": 2.79643106460571,
"y": -6.76414203643799
},
{
"x": 5.1829137802124,
"y": -5.1829137802124
},
{
"x": 6.76414203643799,
"y": -2.79643106460571
}
],
"reshaped": false,
"shape_type": "polygon",
"vertex_flags": [
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0
]
}
],
"tags": [
"walls",
"objects"
],
"version": "1.1"
}
-687
View File
@@ -1,687 +0,0 @@
{
"version": "1.1",
"prop_name": "Wall TV",
"material_preset": "none",
"tags": [
"objects",
"walls"
],
"shapes": [
{
"shape_type": "polygon",
"points": [
{
"x": -335,
"y": -195
},
{
"x": 335,
"y": -195
},
{
"x": 340,
"y": -190
},
{
"x": 340,
"y": 190
},
{
"x": 335,
"y": 195
},
{
"x": -335,
"y": 195
},
{
"x": -340,
"y": 190
},
{
"x": -340,
"y": -190
}
],
"fill_color": "#1A202A",
"outline_color": "#090D13",
"outline_width": 3.8,
"closed": true,
"vertex_flags": [
0,
0,
0,
0,
0,
0,
0,
0
],
"reshaped": false
},
{
"shape_type": "polygon",
"points": [
{
"x": -333,
"y": -188
},
{
"x": 333,
"y": -188
},
{
"x": 324,
"y": -178
},
{
"x": -324,
"y": -178
}
],
"fill_color": "#3C4652",
"outline_color": "#3C4652",
"outline_width": 0,
"closed": true,
"vertex_flags": [
0,
0,
0,
0
],
"reshaped": false
},
{
"shape_type": "polygon",
"points": [
{
"x": -333,
"y": -187
},
{
"x": -324,
"y": -178
},
{
"x": -324,
"y": 168
},
{
"x": -333,
"y": 185
}
],
"fill_color": "#323C47",
"outline_color": "#323C47",
"outline_width": 0,
"closed": true,
"vertex_flags": [
0,
0,
0,
0
],
"reshaped": false
},
{
"shape_type": "polygon",
"points": [
{
"x": 333,
"y": -187
},
{
"x": 333,
"y": 185
},
{
"x": 324,
"y": 168
},
{
"x": 324,
"y": -178
}
],
"fill_color": "#101720",
"outline_color": "#101720",
"outline_width": 0,
"closed": true,
"vertex_flags": [
0,
0,
0,
0
],
"reshaped": false
},
{
"shape_type": "polygon",
"points": [
{
"x": -332,
"y": 168
},
{
"x": 332,
"y": 168
},
{
"x": 332,
"y": 188
},
{
"x": -332,
"y": 188
}
],
"fill_color": "#232B36",
"outline_color": "#232B36",
"outline_width": 0,
"closed": true,
"vertex_flags": [
0,
0,
0,
0
],
"reshaped": false
},
{
"shape_type": "polygon",
"points": [
{
"x": -327,
"y": -177
},
{
"x": 327,
"y": -177
},
{
"x": 327,
"y": 169
},
{
"x": -327,
"y": 169
}
],
"fill_color": "#10151D",
"outline_color": "#080C12",
"outline_width": 2.3,
"closed": true,
"vertex_flags": [
0,
0,
0,
0
],
"reshaped": false
},
{
"shape_type": "polygon",
"points": [
{
"x": -318,
"y": -167
},
{
"x": 318,
"y": -167
},
{
"x": 318,
"y": 158
},
{
"x": -318,
"y": 158
}
],
"fill_color": "#242932",
"outline_color": "#10141B",
"outline_width": 2.4,
"closed": true,
"vertex_flags": [
0,
0,
0,
0
],
"reshaped": false
},
{
"shape_type": "polygon",
"points": [
{
"x": -309,
"y": -158
},
{
"x": -67,
"y": -158
},
{
"x": -151,
"y": 149
},
{
"x": -309,
"y": 149
}
],
"fill_color": "#36404A",
"outline_color": "#36404A",
"outline_width": 0,
"closed": true,
"vertex_flags": [
0,
0,
0,
0
],
"reshaped": false
},
{
"shape_type": "polygon",
"points": [
{
"x": -309,
"y": -158
},
{
"x": -230,
"y": -158
},
{
"x": -299,
"y": 149
},
{
"x": -309,
"y": 149
}
],
"fill_color": "#3C4651",
"outline_color": "#3C4651",
"outline_width": 0,
"closed": true,
"vertex_flags": [
0,
0,
0,
0
],
"reshaped": false
},
{
"shape_type": "line",
"points": [
{
"x": -296,
"y": -154
},
{
"x": -90,
"y": -154
}
],
"fill_color": "#00000000",
"outline_color": "#66717B",
"outline_width": 1.4,
"closed": false,
"vertex_flags": [
0,
0
],
"reshaped": false
},
{
"shape_type": "line",
"points": [
{
"x": -309,
"y": 151
},
{
"x": 309,
"y": 151
}
],
"fill_color": "#00000000",
"outline_color": "#1A2029",
"outline_width": 4.0,
"closed": false,
"vertex_flags": [
0,
0
],
"reshaped": false
},
{
"shape_type": "line",
"points": [
{
"x": -320,
"y": 171
},
{
"x": 320,
"y": 171
}
],
"fill_color": "#00000000",
"outline_color": "#4A535F",
"outline_width": 1.2,
"closed": false,
"vertex_flags": [
0,
0
],
"reshaped": false
},
{
"shape_type": "polygon",
"points": [
{
"x": -7.0,
"y": 183.0
},
{
"x": -7.94,
"y": 186.5
},
{
"x": -10.5,
"y": 189.06
},
{
"x": -14.0,
"y": 190.0
},
{
"x": -17.5,
"y": 189.06
},
{
"x": -20.06,
"y": 186.5
},
{
"x": -21.0,
"y": 183.0
},
{
"x": -20.06,
"y": 179.5
},
{
"x": -17.5,
"y": 176.94
},
{
"x": -14.0,
"y": 176.0
},
{
"x": -10.5,
"y": 176.94
},
{
"x": -7.94,
"y": 179.5
}
],
"fill_color": "#39232A",
"outline_color": "#111720",
"outline_width": 1.0,
"closed": true,
"vertex_flags": [
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0
],
"reshaped": false
},
{
"shape_type": "polygon",
"points": [
{
"x": -10.0,
"y": 183.0
},
{
"x": -10.54,
"y": 185.0
},
{
"x": -12.0,
"y": 186.46
},
{
"x": -14.0,
"y": 187.0
},
{
"x": -16.0,
"y": 186.46
},
{
"x": -17.46,
"y": 185.0
},
{
"x": -18.0,
"y": 183.0
},
{
"x": -17.46,
"y": 181.0
},
{
"x": -16.0,
"y": 179.54
},
{
"x": -14.0,
"y": 179.0
},
{
"x": -12.0,
"y": 179.54
},
{
"x": -10.54,
"y": 181.0
}
],
"fill_color": "#E05A60",
"outline_color": "#E05A60",
"outline_width": 0,
"closed": true,
"vertex_flags": [
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0
],
"reshaped": false
},
{
"shape_type": "polygon",
"points": [
{
"x": 21.0,
"y": 183.0
},
{
"x": 20.06,
"y": 186.5
},
{
"x": 17.5,
"y": 189.06
},
{
"x": 14.0,
"y": 190.0
},
{
"x": 10.5,
"y": 189.06
},
{
"x": 7.94,
"y": 186.5
},
{
"x": 7.0,
"y": 183.0
},
{
"x": 7.94,
"y": 179.5
},
{
"x": 10.5,
"y": 176.94
},
{
"x": 14.0,
"y": 176.0
},
{
"x": 17.5,
"y": 176.94
},
{
"x": 20.06,
"y": 179.5
}
],
"fill_color": "#243A30",
"outline_color": "#111720",
"outline_width": 1.0,
"closed": true,
"vertex_flags": [
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0
],
"reshaped": false
},
{
"shape_type": "polygon",
"points": [
{
"x": 18.0,
"y": 183.0
},
{
"x": 17.46,
"y": 185.0
},
{
"x": 16.0,
"y": 186.46
},
{
"x": 14.0,
"y": 187.0
},
{
"x": 12.0,
"y": 186.46
},
{
"x": 10.54,
"y": 185.0
},
{
"x": 10.0,
"y": 183.0
},
{
"x": 10.54,
"y": 181.0
},
{
"x": 12.0,
"y": 179.54
},
{
"x": 14.0,
"y": 179.0
},
{
"x": 16.0,
"y": 179.54
},
{
"x": 17.46,
"y": 181.0
}
],
"fill_color": "#68CB8D",
"outline_color": "#68CB8D",
"outline_width": 0,
"closed": true,
"vertex_flags": [
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0
],
"reshaped": false
}
],
"anchors": {
"origin": {
"role": "pivot",
"x": 0,
"y": 0,
"rotation_deg": 0,
"z_index_offset": 0,
"allowed_hands": "both"
}
},
"metadata": {
"created_at": "2026-10-21T20:07:34-04:00",
"modified_at": "2026-10-21T20:07:34-04:00"
}
}
File diff suppressed because it is too large Load Diff
-249
View File
@@ -1,249 +0,0 @@
{
"anchors": {
"origin": {
"allowed_hands": "both",
"role": "pivot",
"rotation_deg": 0.0,
"x": 0.0,
"y": 0.0,
"z_index_offset": 0
}
},
"material_preset": "wood",
"metadata": {
"created_at": "2026-10-21T23:13:10",
"modified_at": "2026-10-21T23:13:10"
},
"prop_name": "Open Window Frame",
"shapes": [
{
"closed": true,
"fill_color": "#EBCF9E",
"outline_color": "#332B45",
"outline_width": 5.324,
"points": [
{
"x": -171.698989868164,
"y": -186.339996337891
},
{
"x": -145.078994750977,
"y": -186.339996337891
},
{
"x": -145.078994750977,
"y": 165.043991088867
},
{
"x": -171.698989868164,
"y": 165.043991088867
}
],
"reshaped": false,
"shape_type": "polygon",
"vertex_flags": [
0,
0,
0,
0
]
},
{
"closed": true,
"fill_color": "#EBCF9E",
"outline_color": "#332B45",
"outline_width": 5.324,
"points": [
{
"x": 145.078994750977,
"y": -186.339996337891
},
{
"x": 171.698989868164,
"y": -186.339996337891
},
{
"x": 171.698989868164,
"y": 165.043991088867
},
{
"x": 145.078994750977,
"y": 165.043991088867
}
],
"reshaped": false,
"shape_type": "polygon",
"vertex_flags": [
0,
0,
0,
0
]
},
{
"closed": true,
"fill_color": "#EBCF9E",
"outline_color": "#332B45",
"outline_width": 5.324,
"points": [
{
"x": -171.698989868164,
"y": -186.339996337891
},
{
"x": 171.698989868164,
"y": -186.339996337891
},
{
"x": 171.698989868164,
"y": -159.720001220703
},
{
"x": -171.698989868164,
"y": -159.720001220703
}
],
"reshaped": false,
"shape_type": "polygon",
"vertex_flags": [
0,
0,
0,
0
]
},
{
"closed": true,
"fill_color": "#EBCF9E",
"outline_color": "#332B45",
"outline_width": 5.324,
"points": [
{
"x": -171.698989868164,
"y": 138.42399597168
},
{
"x": 171.698989868164,
"y": 138.42399597168
},
{
"x": 171.698989868164,
"y": 165.043991088867
},
{
"x": -171.698989868164,
"y": 165.043991088867
}
],
"reshaped": false,
"shape_type": "polygon",
"vertex_flags": [
0,
0,
0,
0
]
},
{
"closed": true,
"fill_color": "#EBCF9E",
"outline_color": "#332B45",
"outline_width": 5.324,
"points": [
{
"x": -13.3099994659424,
"y": -159.720001220703
},
{
"x": 13.3099994659424,
"y": -159.720001220703
},
{
"x": 13.3099994659424,
"y": 138.42399597168
},
{
"x": -13.3099994659424,
"y": 138.42399597168
}
],
"reshaped": false,
"shape_type": "polygon",
"vertex_flags": [
0,
0,
0,
0
]
},
{
"closed": true,
"fill_color": "#EBCF9E",
"outline_color": "#332B45",
"outline_width": 5.324,
"points": [
{
"x": -145.078994750977,
"y": -23.9580001831055
},
{
"x": 145.078994750977,
"y": -23.9580001831055
},
{
"x": 145.078994750977,
"y": 2.66199994087219
},
{
"x": -145.078994750977,
"y": 2.66199994087219
}
],
"reshaped": false,
"shape_type": "polygon",
"vertex_flags": [
0,
0,
0,
0
]
},
{
"closed": true,
"fill_color": "#C89670",
"outline_color": "#332B45",
"outline_width": 5.324,
"points": [
{
"x": -192.994995117188,
"y": 165.043991088867
},
{
"x": 192.994995117188,
"y": 165.043991088867
},
{
"x": 192.994995117188,
"y": 186.339996337891
},
{
"x": -192.994995117188,
"y": 186.339996337891
}
],
"reshaped": false,
"shape_type": "polygon",
"vertex_flags": [
0,
0,
0,
0
]
}
],
"tags": [
"walls",
"buildings"
],
"version": "1.1"
}
File diff suppressed because it is too large Load Diff
-726
View File
@@ -1,726 +0,0 @@
{
"anchors": {
"origin": {
"allowed_hands": "both",
"role": "pivot",
"rotation_deg": 0.0,
"x": 0.0,
"y": 0.0,
"z_index_offset": 0
}
},
"material_preset": "wood",
"metadata": {
"created_at": "2026-10-21T23:13:31",
"modified_at": "2026-10-21T23:13:31"
},
"prop_name": "End Table",
"shapes": [
{
"closed": true,
"fill_color": "#B49A82FF",
"outline_color": "#30213DFF",
"outline_width": 3.0,
"points": [
{
"x": -98.0,
"y": -118.0
},
{
"x": 98.0,
"y": -118.0
},
{
"x": 98.0,
"y": -43.0
},
{
"x": -98.0,
"y": -43.0
}
],
"reshaped": true,
"shape_type": "polygon",
"vertex_flags": [
0,
0,
0,
0
]
},
{
"closed": true,
"fill_color": "#347E91FF",
"outline_color": "#30213DFF",
"outline_width": 3.0,
"points": [
{
"x": -83.0,
"y": -102.0
},
{
"x": 83.0,
"y": -102.0
},
{
"x": 83.0,
"y": -51.0
},
{
"x": -83.0,
"y": -51.0
}
],
"reshaped": true,
"shape_type": "polygon",
"vertex_flags": [
0,
0,
0,
0
]
},
{
"closed": true,
"fill_color": "#B3D1E4FF",
"outline_color": "#30213DFF",
"outline_width": 2.0,
"points": [
{
"x": -76.0,
"y": -95.0
},
{
"x": 76.0,
"y": -95.0
},
{
"x": 76.0,
"y": -58.0
},
{
"x": -76.0,
"y": -58.0
}
],
"reshaped": true,
"shape_type": "polygon",
"vertex_flags": [
0,
0,
0,
0
]
},
{
"closed": true,
"fill_color": "#E9F6F9FF",
"outline_color": "#E9F6F9FF",
"outline_width": 0.0,
"points": [
{
"x": -73.0,
"y": -92.0
},
{
"x": 73.0,
"y": -92.0
},
{
"x": 73.0,
"y": -87.0
},
{
"x": -73.0,
"y": -87.0
}
],
"reshaped": true,
"shape_type": "polygon",
"vertex_flags": [
0,
0,
0,
0
]
},
{
"closed": true,
"fill_color": "#65ADBDFF",
"outline_color": "#65ADBDFF",
"outline_width": 0.0,
"points": [
{
"x": -73.0,
"y": -65.0
},
{
"x": 73.0,
"y": -65.0
},
{
"x": 73.0,
"y": -60.0
},
{
"x": -73.0,
"y": -60.0
}
],
"reshaped": true,
"shape_type": "polygon",
"vertex_flags": [
0,
0,
0,
0
]
},
{
"closed": true,
"fill_color": "#EBD8B5FF",
"outline_color": "#30213DFF",
"outline_width": 3.0,
"points": [
{
"x": -96.0,
"y": -30.0
},
{
"x": 96.0,
"y": -30.0
},
{
"x": 96.0,
"y": -14.0
},
{
"x": -96.0,
"y": -14.0
}
],
"reshaped": true,
"shape_type": "polygon",
"vertex_flags": [
0,
0,
0,
0
]
},
{
"closed": true,
"fill_color": "#FFF0CEFF",
"outline_color": "#FFF0CEFF",
"outline_width": 0.0,
"points": [
{
"x": -92.0,
"y": -28.0
},
{
"x": 92.0,
"y": -28.0
},
{
"x": 92.0,
"y": -23.0
},
{
"x": -92.0,
"y": -23.0
}
],
"reshaped": true,
"shape_type": "polygon",
"vertex_flags": [
0,
0,
0,
0
]
},
{
"closed": true,
"fill_color": "#EBD8B5FF",
"outline_color": "#30213DFF",
"outline_width": 3.0,
"points": [
{
"x": -96.0,
"y": 76.0
},
{
"x": 96.0,
"y": 76.0
},
{
"x": 96.0,
"y": 92.0
},
{
"x": -96.0,
"y": 92.0
}
],
"reshaped": true,
"shape_type": "polygon",
"vertex_flags": [
0,
0,
0,
0
]
},
{
"closed": true,
"fill_color": "#FFF0CEFF",
"outline_color": "#FFF0CEFF",
"outline_width": 0.0,
"points": [
{
"x": -92.0,
"y": 78.0
},
{
"x": 92.0,
"y": 78.0
},
{
"x": 92.0,
"y": 83.0
},
{
"x": -92.0,
"y": 83.0
}
],
"reshaped": true,
"shape_type": "polygon",
"vertex_flags": [
0,
0,
0,
0
]
},
{
"closed": true,
"fill_color": "#E5CDA9FF",
"outline_color": "#30213DFF",
"outline_width": 3.0,
"points": [
{
"x": -102.0,
"y": -126.0
},
{
"x": -82.0,
"y": -126.0
},
{
"x": -82.0,
"y": 142.0
},
{
"x": -102.0,
"y": 142.0
}
],
"reshaped": true,
"shape_type": "polygon",
"vertex_flags": [
0,
0,
0,
0
]
},
{
"closed": true,
"fill_color": "#FFF0CEFF",
"outline_color": "#FFF0CEFF",
"outline_width": 0.0,
"points": [
{
"x": -98.0,
"y": -118.0
},
{
"x": -93.0,
"y": -118.0
},
{
"x": -93.0,
"y": 136.0
},
{
"x": -98.0,
"y": 136.0
}
],
"reshaped": true,
"shape_type": "polygon",
"vertex_flags": [
0,
0,
0,
0
]
},
{
"closed": true,
"fill_color": "#E5CDA9FF",
"outline_color": "#30213DFF",
"outline_width": 3.0,
"points": [
{
"x": 82.0,
"y": -126.0
},
{
"x": 102.0,
"y": -126.0
},
{
"x": 102.0,
"y": 142.0
},
{
"x": 82.0,
"y": 142.0
}
],
"reshaped": true,
"shape_type": "polygon",
"vertex_flags": [
0,
0,
0,
0
]
},
{
"closed": true,
"fill_color": "#FFF0CEFF",
"outline_color": "#FFF0CEFF",
"outline_width": 0.0,
"points": [
{
"x": 86.0,
"y": -118.0
},
{
"x": 91.0,
"y": -118.0
},
{
"x": 91.0,
"y": 136.0
},
{
"x": 86.0,
"y": 136.0
}
],
"reshaped": true,
"shape_type": "polygon",
"vertex_flags": [
0,
0,
0,
0
]
},
{
"closed": true,
"fill_color": "#E72F4AFF",
"outline_color": "#30213DFF",
"outline_width": 4.0,
"points": [
{
"x": -116.0,
"y": -142.0
},
{
"x": 116.0,
"y": -142.0
},
{
"x": 116.0,
"y": -122.0
},
{
"x": -116.0,
"y": -122.0
}
],
"reshaped": true,
"shape_type": "polygon",
"vertex_flags": [
0,
0,
0,
0
]
},
{
"closed": true,
"fill_color": "#DFD0B9FF",
"outline_color": "#30213DFF",
"outline_width": 2.0,
"points": [
{
"x": -112.0,
"y": -142.0
},
{
"x": 112.0,
"y": -142.0
},
{
"x": 112.0,
"y": -135.0
},
{
"x": -112.0,
"y": -135.0
}
],
"reshaped": true,
"shape_type": "polygon",
"vertex_flags": [
0,
0,
0,
0
]
},
{
"closed": true,
"fill_color": "#D72E4CFF",
"outline_color": "#30213DFF",
"outline_width": 2.5,
"points": [
{
"x": 9.0,
"y": -76.0
},
{
"x": 8.69299983978271,
"y": -73.6710052490234
},
{
"x": 7.79400014877319,
"y": -71.5
},
{
"x": 6.36399984359741,
"y": -69.6360015869141
},
{
"x": 4.5,
"y": -68.2060012817383
},
{
"x": 2.3289999961853,
"y": -67.306999206543
},
{
"x": 0.0,
"y": -67.0
},
{
"x": -2.3289999961853,
"y": -67.306999206543
},
{
"x": -4.5,
"y": -68.2060012817383
},
{
"x": -6.36399984359741,
"y": -69.6360015869141
},
{
"x": -7.79400014877319,
"y": -71.5
},
{
"x": -8.69299983978271,
"y": -73.6710052490234
},
{
"x": -9.0,
"y": -76.0
},
{
"x": -8.69299983978271,
"y": -78.3289947509766
},
{
"x": -7.79400014877319,
"y": -80.5
},
{
"x": -6.36399984359741,
"y": -82.3639984130859
},
{
"x": -4.5,
"y": -83.7939987182617
},
{
"x": -2.3289999961853,
"y": -84.693000793457
},
{
"x": 0.0,
"y": -85.0
},
{
"x": 2.3289999961853,
"y": -84.693000793457
},
{
"x": 4.5,
"y": -83.7939987182617
},
{
"x": 6.36399984359741,
"y": -82.3639984130859
},
{
"x": 7.79400014877319,
"y": -80.5
},
{
"x": 8.69299983978271,
"y": -78.3289947509766
}
],
"reshaped": true,
"shape_type": "polygon",
"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
]
},
{
"closed": true,
"fill_color": "#FFE3DBFF",
"outline_color": "#FFE3DBFF",
"outline_width": 0.0,
"points": [
{
"x": 0.0,
"y": -79.0
},
{
"x": -0.228000000119209,
"y": -77.8520050048828
},
{
"x": -0.879000008106232,
"y": -76.8789978027344
},
{
"x": -1.85199999809265,
"y": -76.2279968261719
},
{
"x": -3.0,
"y": -76.0
},
{
"x": -4.14799976348877,
"y": -76.2279968261719
},
{
"x": -5.12099981307983,
"y": -76.8789978027344
},
{
"x": -5.77199983596802,
"y": -77.8520050048828
},
{
"x": -6.0,
"y": -79.0
},
{
"x": -5.77199983596802,
"y": -80.1479949951172
},
{
"x": -5.12099981307983,
"y": -81.1210021972656
},
{
"x": -4.14799976348877,
"y": -81.7720031738281
},
{
"x": -3.0,
"y": -82.0
},
{
"x": -1.85199999809265,
"y": -81.7720031738281
},
{
"x": -0.879000008106232,
"y": -81.1210021972656
},
{
"x": -0.228000000119209,
"y": -80.1479949951172
}
],
"reshaped": true,
"shape_type": "polygon",
"vertex_flags": [
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0
]
}
],
"tags": [],
"version": "1.1"
}
File diff suppressed because it is too large Load Diff
-278
View File
@@ -1,278 +0,0 @@
{
"anchors": {
"origin": {
"allowed_hands": "both",
"role": "pivot",
"rotation_deg": 0.0,
"x": 0.0,
"y": 0.0,
"z_index_offset": 0
}
},
"material_preset": "wood",
"metadata": {
"created_at": "2026-10-21T23:15:32",
"modified_at": "2026-10-21T23:15:32"
},
"prop_name": "Cozy Armchair",
"shapes": [
{
"closed": true,
"fill_color": "#80A9AF",
"outline_color": "#332B45",
"outline_width": 10.3749698404,
"points": [
{
"x": -243.811782836914,
"y": 185.452575683594
},
{
"x": -243.811782836914,
"y": -208.796264648438
},
{
"x": -184.155700683594,
"y": -281.421051025391
},
{
"x": 184.155700683594,
"y": -281.421051025391
},
{
"x": 243.811782836914,
"y": -208.796264648438
},
{
"x": 243.811782836914,
"y": 185.452575683594
},
{
"x": 197.124420166016,
"y": 239.921173095703
},
{
"x": -197.124420166016,
"y": 239.921173095703
}
],
"reshaped": false,
"shape_type": "polygon",
"vertex_flags": [
0,
0,
0,
0,
0,
0,
0,
0
]
},
{
"closed": true,
"fill_color": "#A8CED0",
"outline_color": "#332B45",
"outline_width": 10.3749698404,
"points": [
{
"x": -168.59326171875,
"y": -213.983749389648
},
{
"x": 168.59326171875,
"y": -213.983749389648
},
{
"x": 168.59326171875,
"y": 60.9529457092285
},
{
"x": -168.59326171875,
"y": 60.9529457092285
}
],
"reshaped": false,
"shape_type": "polygon",
"vertex_flags": [
0,
0,
0,
0
]
},
{
"closed": true,
"fill_color": "#C3DDCF",
"outline_color": "#332B45",
"outline_width": 10.3749698404,
"points": [
{
"x": -189.343200683594,
"y": 50.5779762268066
},
{
"x": 189.343200683594,
"y": 50.5779762268066
},
{
"x": 212.686874389648,
"y": 182.858840942383
},
{
"x": -212.686874389648,
"y": 182.858840942383
}
],
"reshaped": false,
"shape_type": "polygon",
"vertex_flags": [
0,
0,
0,
0
]
},
{
"closed": true,
"fill_color": "#80A9AF",
"outline_color": "#332B45",
"outline_width": 10.3749698404,
"points": [
{
"x": -246.405517578125,
"y": -3.8906135559082
},
{
"x": -186.749450683594,
"y": -40.2030067443848
},
{
"x": -153.030792236328,
"y": 9.07809829711914
},
{
"x": -153.030792236328,
"y": 208.796264648438
},
{
"x": -246.405517578125,
"y": 208.796264648438
}
],
"reshaped": false,
"shape_type": "polygon",
"vertex_flags": [
0,
0,
0,
0,
0
]
},
{
"closed": true,
"fill_color": "#80A9AF",
"outline_color": "#332B45",
"outline_width": 10.3749698404,
"points": [
{
"x": 246.405517578125,
"y": -3.8906135559082
},
{
"x": 186.749450683594,
"y": -40.2030067443848
},
{
"x": 153.030792236328,
"y": 9.07809829711914
},
{
"x": 153.030792236328,
"y": 208.796264648438
},
{
"x": 246.405517578125,
"y": 208.796264648438
}
],
"reshaped": false,
"shape_type": "polygon",
"vertex_flags": [
0,
0,
0,
0,
0
]
},
{
"closed": true,
"fill_color": "#755369",
"outline_color": "#332B45",
"outline_width": 10.3749698404,
"points": [
{
"x": -184.155700683594,
"y": 224.35871887207
},
{
"x": -124.499633789063,
"y": 224.35871887207
},
{
"x": -124.499633789063,
"y": 281.421051025391
},
{
"x": -184.155700683594,
"y": 281.421051025391
}
],
"reshaped": false,
"shape_type": "polygon",
"vertex_flags": [
0,
0,
0,
0
]
},
{
"closed": true,
"fill_color": "#755369",
"outline_color": "#332B45",
"outline_width": 10.3749698404,
"points": [
{
"x": 124.499633789063,
"y": 224.35871887207
},
{
"x": 184.155700683594,
"y": 224.35871887207
},
{
"x": 184.155700683594,
"y": 281.421051025391
},
{
"x": 124.499633789063,
"y": 281.421051025391
}
],
"reshaped": false,
"shape_type": "polygon",
"vertex_flags": [
0,
0,
0,
0
]
}
],
"tags": [
"objects"
],
"version": "1.1"
}
-206
View File
@@ -1,206 +0,0 @@
{
"anchors": {
"origin": {
"allowed_hands": "both",
"role": "pivot",
"rotation_deg": 0.0,
"x": 0.0,
"y": 0.0,
"z_index_offset": 0
}
},
"material_preset": "wood",
"metadata": {
"created_at": "2026-10-21T23:15:55",
"modified_at": "2026-10-21T23:15:55"
},
"prop_name": "Coffee Table",
"shapes": [
{
"closed": true,
"fill_color": "#B9805D",
"outline_color": "#332B45",
"outline_width": 6.44204,
"points": [
{
"x": -241.57649230957,
"y": -112.735702514648
},
{
"x": 241.57649230957,
"y": -112.735702514648
},
{
"x": 241.57649230957,
"y": -67.6414184570313
},
{
"x": -241.57649230957,
"y": -67.6414184570313
}
],
"reshaped": false,
"shape_type": "polygon",
"vertex_flags": [
0,
0,
0,
0
]
},
{
"closed": true,
"fill_color": "#D69E70",
"outline_color": "#332B45",
"outline_width": 6.44204,
"points": [
{
"x": -206.145278930664,
"y": -67.6414184570313
},
{
"x": 206.145278930664,
"y": -67.6414184570313
},
{
"x": 206.145278930664,
"y": -48.315299987793
},
{
"x": -206.145278930664,
"y": -48.315299987793
}
],
"reshaped": false,
"shape_type": "polygon",
"vertex_flags": [
0,
0,
0,
0
]
},
{
"closed": true,
"fill_color": "#A66F58",
"outline_color": "#332B45",
"outline_width": 6.44204,
"points": [
{
"x": -199.703231811523,
"y": -49.9258079528809
},
{
"x": -157.829986572266,
"y": -49.9258079528809
},
{
"x": -144.945892333984,
"y": 112.735702514648
},
{
"x": -186.819152832031,
"y": 112.735702514648
}
],
"reshaped": false,
"shape_type": "polygon",
"vertex_flags": [
0,
0,
0,
0
]
},
{
"closed": true,
"fill_color": "#A66F58",
"outline_color": "#332B45",
"outline_width": 6.44204,
"points": [
{
"x": 157.829986572266,
"y": -49.9258079528809
},
{
"x": 199.703231811523,
"y": -49.9258079528809
},
{
"x": 186.819152832031,
"y": 112.735702514648
},
{
"x": 144.945892333984,
"y": 112.735702514648
}
],
"reshaped": false,
"shape_type": "polygon",
"vertex_flags": [
0,
0,
0,
0
]
},
{
"closed": true,
"fill_color": "#C38C68",
"outline_color": "#332B45",
"outline_width": 6.44204,
"points": [
{
"x": -162.661514282227,
"y": 35.4312210083008
},
{
"x": 162.661514282227,
"y": 35.4312210083008
},
{
"x": 162.661514282227,
"y": 54.7573394775391
},
{
"x": -162.661514282227,
"y": 54.7573394775391
}
],
"reshaped": false,
"shape_type": "polygon",
"vertex_flags": [
0,
0,
0,
0
]
},
{
"closed": false,
"fill_color": "#00000000",
"outline_color": "#E8B98A",
"outline_width": 4.83153,
"points": [
{
"x": -185.208648681641,
"y": -99.8516159057617
},
{
"x": 185.208648681641,
"y": -99.8516159057617
}
],
"reshaped": false,
"shape_type": "line",
"vertex_flags": [
0,
0
]
}
],
"tags": [
"objects"
],
"version": "1.1"
}
-300
View File
@@ -1,300 +0,0 @@
{
"anchors": {
"origin": {
"allowed_hands": "both",
"role": "pivot",
"rotation_deg": 0.0,
"x": 0.0,
"y": 0.0,
"z_index_offset": 0
}
},
"material_preset": "wood",
"metadata": {
"created_at": "2026-10-21T23:16:12",
"modified_at": "2026-10-21T23:16:12"
},
"prop_name": "Cozy Couch",
"shapes": [
{
"closed": true,
"fill_color": "#B7767B",
"outline_color": "#332B45",
"outline_width": 10.3749698404,
"points": [
{
"x": -435.748718261719,
"y": 165.99951171875
},
{
"x": -435.748718261719,
"y": -171.186996459961
},
{
"x": -376.092651367188,
"y": -261.967987060547
},
{
"x": 376.092651367188,
"y": -261.967987060547
},
{
"x": 435.748718261719,
"y": -171.186996459961
},
{
"x": 435.748718261719,
"y": 165.99951171875
},
{
"x": 378.686401367188,
"y": 217.874359130859
},
{
"x": -378.686401367188,
"y": 217.874359130859
}
],
"reshaped": false,
"shape_type": "polygon",
"vertex_flags": [
0,
0,
0,
0,
0,
0,
0,
0
]
},
{
"closed": true,
"fill_color": "#D9908A",
"outline_color": "#332B45",
"outline_width": 10.3749698404,
"points": [
{
"x": -355.342712402344,
"y": -186.749450683594
},
{
"x": 355.342712402344,
"y": -186.749450683594
},
{
"x": 355.342712402344,
"y": 41.4998779296875
},
{
"x": -355.342712402344,
"y": 41.4998779296875
}
],
"reshaped": false,
"shape_type": "polygon",
"vertex_flags": [
0,
0,
0,
0
]
},
{
"closed": false,
"fill_color": "#00000000",
"outline_color": "#985C71",
"outline_width": 10.3749698404,
"points": [
{
"x": 0.0,
"y": -184.155700683594
},
{
"x": 0.0,
"y": 38.906135559082
}
],
"reshaped": false,
"shape_type": "line",
"vertex_flags": [
0,
0
]
},
{
"closed": true,
"fill_color": "#EDB09A",
"outline_color": "#332B45",
"outline_width": 10.3749698404,
"points": [
{
"x": -368.311401367188,
"y": 23.3436813354492
},
{
"x": 368.311401367188,
"y": 23.3436813354492
},
{
"x": 391.655090332031,
"y": 160.812026977539
},
{
"x": -391.655090332031,
"y": 160.812026977539
}
],
"reshaped": false,
"shape_type": "polygon",
"vertex_flags": [
0,
0,
0,
0
]
},
{
"closed": true,
"fill_color": "#B7767B",
"outline_color": "#332B45",
"outline_width": 10.3749698404,
"points": [
{
"x": -433.154968261719,
"y": -59.6560745239258
},
{
"x": -373.498901367188,
"y": -88.1872406005859
},
{
"x": -339.780242919922,
"y": -51.8748474121094
},
{
"x": -339.780242919922,
"y": 186.749450683594
},
{
"x": -435.748718261719,
"y": 186.749450683594
}
],
"reshaped": false,
"shape_type": "polygon",
"vertex_flags": [
0,
0,
0,
0,
0
]
},
{
"closed": true,
"fill_color": "#B7767B",
"outline_color": "#332B45",
"outline_width": 10.3749698404,
"points": [
{
"x": 433.154968261719,
"y": -59.6560745239258
},
{
"x": 373.498901367188,
"y": -88.1872406005859
},
{
"x": 339.780242919922,
"y": -51.8748474121094
},
{
"x": 339.780242919922,
"y": 186.749450683594
},
{
"x": 435.748718261719,
"y": 186.749450683594
}
],
"reshaped": false,
"shape_type": "polygon",
"vertex_flags": [
0,
0,
0,
0,
0
]
},
{
"closed": true,
"fill_color": "#755369",
"outline_color": "#332B45",
"outline_width": 10.3749698404,
"points": [
{
"x": -360.530181884766,
"y": 202.311904907227
},
{
"x": -293.092895507813,
"y": 202.311904907227
},
{
"x": -293.092895507813,
"y": 261.967987060547
},
{
"x": -360.530181884766,
"y": 261.967987060547
}
],
"reshaped": false,
"shape_type": "polygon",
"vertex_flags": [
0,
0,
0,
0
]
},
{
"closed": true,
"fill_color": "#755369",
"outline_color": "#332B45",
"outline_width": 10.3749698404,
"points": [
{
"x": 293.092895507813,
"y": 202.311904907227
},
{
"x": 360.530181884766,
"y": 202.311904907227
},
{
"x": 360.530181884766,
"y": 261.967987060547
},
{
"x": 293.092895507813,
"y": 261.967987060547
}
],
"reshaped": false,
"shape_type": "polygon",
"vertex_flags": [
0,
0,
0,
0
]
}
],
"tags": [
"objects"
],
"version": "1.1"
}
-372
View File
@@ -1,372 +0,0 @@
{
"version": "1.6",
"scene_name": "Blank Scene",
"sky_id": "sky_neutral",
"camera": {
"position": {
"x": 1863.90759277344,
"y": -391.476318359375
},
"zoom": 0.665500044822693
},
"objects": [
{
"file_id": "wall_panel_1",
"type": "wall_panel",
"position": {
"x": 448.0,
"y": -320.0
},
"rotation": 0.0,
"size": {
"x": 5120.0,
"y": 2816.0
},
"finish_id": "finish_wall_brick",
"color": "#b95f3dff"
},
{
"file_id": "stickman_1",
"type": "stickman",
"position": {
"x": -128.0,
"y": -129.0
},
"rotation": 0.0,
"stk_path": "res://content/stickmen/break.stk",
"facing_profile": "FORWARD",
"action_queue": [
{
"type": "move_to",
"target": {
"x": 2404.91528320313,
"y": 141.710083007813
},
"style": "walk"
}
]
},
{
"file_id": "terrain_1",
"type": "terrain",
"position": {
"x": -384.0,
"y": 256.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
},
"finish_id": "finish_ground_asphalt"
},
{
"file_id": "terrain_2",
"type": "terrain",
"position": {
"x": -384.0,
"y": 288.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
},
"finish_id": "finish_ground_asphalt"
},
{
"file_id": "terrain_3",
"type": "terrain",
"position": {
"x": 192.0,
"y": 256.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
},
"finish_id": "finish_ground_asphalt"
},
{
"file_id": "terrain_4",
"type": "terrain",
"position": {
"x": 384.0,
"y": 256.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
},
"finish_id": "finish_ground_asphalt"
},
{
"file_id": "terrain_5",
"type": "terrain",
"position": {
"x": 576.0,
"y": 256.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
},
"finish_id": "finish_ground_asphalt"
},
{
"file_id": "terrain_6",
"type": "terrain",
"position": {
"x": 768.0,
"y": 256.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
},
"finish_id": "finish_ground_asphalt"
},
{
"file_id": "terrain_7",
"type": "terrain",
"position": {
"x": 960.0,
"y": 256.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
},
"finish_id": "finish_ground_asphalt"
},
{
"file_id": "terrain_8",
"type": "terrain",
"position": {
"x": 1152.0,
"y": 224.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
},
"finish_id": "finish_ground_asphalt"
},
{
"file_id": "terrain_9",
"type": "terrain",
"position": {
"x": 1344.0,
"y": 224.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
},
"finish_id": "finish_ground_asphalt"
},
{
"file_id": "terrain_10",
"type": "terrain",
"position": {
"x": 1536.0,
"y": 224.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
},
"finish_id": "finish_ground_asphalt"
},
{
"file_id": "terrain_11",
"type": "terrain",
"position": {
"x": 1728.0,
"y": 224.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
},
"finish_id": "finish_ground_asphalt"
},
{
"file_id": "terrain_12",
"type": "terrain",
"position": {
"x": 1920.0,
"y": 224.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
},
"finish_id": "finish_ground_asphalt"
},
{
"file_id": "terrain_13",
"type": "terrain",
"position": {
"x": 2112.0,
"y": 224.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
},
"finish_id": "finish_ground_asphalt"
},
{
"file_id": "terrain_14",
"type": "terrain",
"position": {
"x": 2304.0,
"y": 224.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
},
"finish_id": "finish_ground_asphalt"
},
{
"file_id": "terrain_15",
"type": "terrain",
"position": {
"x": 2496.0,
"y": 224.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
},
"finish_id": "finish_ground_asphalt"
},
{
"file_id": "terrain_16",
"type": "terrain",
"position": {
"x": -192.0,
"y": 320.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
},
"finish_id": "finish_ground_asphalt"
},
{
"file_id": "terrain_17",
"type": "terrain",
"position": {
"x": 0.0,
"y": 320.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
},
"finish_id": "finish_ground_asphalt"
},
{
"file_id": "terrain_18",
"type": "terrain",
"position": {
"x": 192.0,
"y": 288.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
},
"finish_id": "finish_ground_asphalt"
},
{
"file_id": "terrain_19",
"type": "terrain",
"position": {
"x": 384.0,
"y": 288.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
},
"finish_id": "finish_ground_asphalt"
},
{
"file_id": "terrain_20",
"type": "terrain",
"position": {
"x": 576.0,
"y": 288.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
},
"finish_id": "finish_ground_asphalt"
},
{
"file_id": "wall_panel_2",
"type": "wall_panel",
"position": {
"x": 1152.0,
"y": -64.0
},
"rotation": 0.0,
"size": {
"x": 9984.0,
"y": 5504.0
},
"finish_id": "finish_wall_brick",
"color": "#b95f3dff"
}
],
"event_rules": [],
"metadata": {
"created_unix": 1792022400,
"modified_unix": 1792470929.846,
"autosave": false,
"source_path": ""
}
}
-641
View File
@@ -1,641 +0,0 @@
{
"version": "1.5",
"scene_name": "Living Room",
"sky_id": "sky_clear_day",
"camera": {
"position": {
"x": 194.703018188477,
"y": -486.092163085938
},
"zoom": 0.665500044822693
},
"objects": [
{
"file_id": "wall_panel_1",
"type": "wall_panel",
"position": {
"x": 0.0,
"y": -820.0
},
"rotation": 0.0,
"size": {
"x": 1120.0,
"y": 170.0
},
"finish_id": "finish_wall_paint",
"color": "#5a9d70ff"
},
{
"file_id": "wall_panel_2",
"type": "wall_panel",
"position": {
"x": -360.0,
"y": -535.0
},
"rotation": 0.0,
"size": {
"x": 440.0,
"y": 400.0
},
"finish_id": "finish_wall_paint",
"color": "#5a9d70ff"
},
{
"file_id": "wall_panel_3",
"type": "wall_panel",
"position": {
"x": 360.0,
"y": -535.0
},
"rotation": 0.0,
"size": {
"x": 440.0,
"y": 400.0
},
"finish_id": "finish_wall_paint",
"color": "#5a9d70ff"
},
{
"file_id": "scenery_artwork_1",
"type": "scenery_artwork",
"position": {
"x": 0.0,
"y": -530.0
},
"rotation": 0.0,
"asset_id": "res://content/props/art_window_frame-v1.prop",
"display_name": "Window frame",
"depth": "near",
"scale": {
"x": 1.0,
"y": 1.0
},
"last_known_local_bounds": {
"position": {
"x": -147.0,
"y": -154.0
},
"size": {
"x": 294.0,
"y": 284.0
}
}
},
{
"file_id": "scenery_artwork_2",
"type": "scenery_artwork",
"position": {
"x": -430.0,
"y": -570.0
},
"rotation": 0.0,
"asset_id": "res://content/props/art_wall_clock-v1.prop",
"display_name": "Wall clock",
"depth": "near",
"scale": {
"x": 0.800000011920929,
"y": 0.800000011920929
},
"last_known_local_bounds": {
"position": {
"x": -72.5,
"y": -72.5
},
"size": {
"x": 145.0,
"y": 145.0
}
}
},
{
"file_id": "scenery_artwork_3",
"type": "scenery_artwork",
"position": {
"x": 365.0,
"y": -460.0
},
"rotation": 0.0,
"asset_id": "res://content/props/art_fireplace_mantel-v2.prop",
"display_name": "Fireplace",
"depth": "near",
"scale": {
"x": 0.819999992847443,
"y": 0.819999992847443
},
"last_known_local_bounds": {
"position": {
"x": -153.0,
"y": -116.0
},
"size": {
"x": 306.0,
"y": 244.0
}
}
},
{
"file_id": "scenery_artwork_4",
"type": "scenery_artwork",
"position": {
"x": -475.0,
"y": -310.0
},
"rotation": 0.0,
"asset_id": "res://content/props/art_bookshelf-v1.prop",
"display_name": "Bookshelf",
"depth": "near",
"scale": {
"x": 0.819999992847443,
"y": 0.819999992847443
},
"last_known_local_bounds": {
"position": {
"x": -104.0,
"y": -190.0
},
"size": {
"x": 208.0,
"y": 315.0
}
}
},
{
"file_id": "scenery_artwork_5",
"type": "scenery_artwork",
"position": {
"x": 185.0,
"y": -575.0
},
"rotation": 0.0,
"asset_id": "res://content/props/art_framed_picture-v1.prop",
"display_name": "Framed picture",
"depth": "near",
"scale": {
"x": 0.75,
"y": 0.75
},
"last_known_local_bounds": {
"position": {
"x": -112.0,
"y": -84.0
},
"size": {
"x": 224.0,
"y": 168.0
}
}
},
{
"file_id": "terrain_1",
"type": "terrain",
"position": {
"x": -774.0,
"y": 832.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
},
"finish_id": "finish_ground_wood"
},
{
"file_id": "terrain_2",
"type": "terrain",
"position": {
"x": -588.0,
"y": 840.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
},
"finish_id": "finish_ground_wood"
},
{
"file_id": "terrain_3",
"type": "terrain",
"position": {
"x": -374.0,
"y": 832.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
},
"finish_id": "finish_ground_wood"
},
{
"file_id": "terrain_4",
"type": "terrain",
"position": {
"x": -174.0,
"y": 832.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
},
"finish_id": "finish_ground_wood"
},
{
"file_id": "terrain_5",
"type": "terrain",
"position": {
"x": 26.0,
"y": 832.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
},
"finish_id": "finish_ground_wood"
},
{
"file_id": "terrain_6",
"type": "terrain",
"position": {
"x": 226.0,
"y": 832.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
},
"finish_id": "finish_ground_wood"
},
{
"file_id": "terrain_7",
"type": "terrain",
"position": {
"x": 426.0,
"y": 832.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
},
"finish_id": "finish_ground_wood"
},
{
"file_id": "scenery_artwork_6",
"type": "scenery_artwork",
"position": {
"x": 0.0,
"y": 112.0
},
"rotation": 0.0,
"asset_id": "res://content/props/art_rug-v1.prop",
"display_name": "Floor rug",
"depth": "floor",
"scale": {
"x": 1.0,
"y": 1.0
},
"last_known_local_bounds": {
"position": {
"x": -179.0,
"y": -60.0
},
"size": {
"x": 358.0,
"y": 139.0
}
}
},
{
"file_id": "scenery_artwork_7",
"type": "scenery_artwork",
"position": {
"x": 500.0,
"y": 84.0
},
"rotation": 0.0,
"asset_id": "res://content/props/art_potted_plant-v1.prop",
"display_name": "Potted plant",
"depth": "floor",
"scale": {
"x": 0.800000011920929,
"y": 0.800000011920929
},
"last_known_local_bounds": {
"position": {
"x": -62.5,
"y": -114.5
},
"size": {
"x": 125.0,
"y": 219.0
}
}
},
{
"file_id": "prop_1",
"type": "prop",
"position": {
"x": -364.0,
"y": 680.0
},
"rotation": 0.0,
"asset_id": "res://content/props/prop_couch-v1.prop",
"stage_object_id": "starter_couch",
"roles": {
"depth": "behind",
"start_motion": "still",
"holds_other_props": false,
"blocks_stickmen": false,
"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": false,
"scale": {
"x": 0.819999992847443,
"y": 0.819999992847443
},
"material_preset": 1
},
{
"file_id": "prop_2",
"type": "prop",
"position": {
"x": 36.0,
"y": 684.0
},
"rotation": 0.0,
"asset_id": "res://content/props/prop_armchair-v1.prop",
"stage_object_id": "starter_armchair",
"roles": {
"depth": "behind",
"start_motion": "still",
"holds_other_props": false,
"blocks_stickmen": false,
"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": false,
"scale": {
"x": 0.779999971389771,
"y": 0.779999971389771
},
"material_preset": 1
},
{
"file_id": "prop_3",
"type": "prop",
"position": {
"x": -159.0,
"y": 748.0
},
"rotation": 0.0,
"asset_id": "res://content/props/prop_coffee_table-v1.prop",
"stage_object_id": "starter_coffee_table",
"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
},
"use_spots": [],
"next_use_spot_number": 1,
"wobble_on_impact": false,
"scale": {
"x": 0.75,
"y": 0.75
},
"material_preset": 1
},
{
"file_id": "stickman_1",
"type": "stickman",
"position": {
"x": -690.0,
"y": 152.0
},
"rotation": 0.0,
"stk_path": "res://content/stickmen/break.stk",
"facing_profile": "FORWARD",
"action_queue": [
{
"type": "move_to",
"target": {
"x": 588.195678710938,
"y": 144.184204101563
},
"style": "walk"
}
]
},
{
"file_id": "wall_panel_4",
"type": "wall_panel",
"position": {
"x": 206.112670898438,
"y": -758.285827636719
},
"rotation": 0.0,
"size": {
"x": 634.58154296875,
"y": 125.426147460938
},
"finish_id": "finish_wall_paint",
"color": "#5a9d70ff"
},
{
"file_id": "wall_panel_5",
"type": "wall_panel",
"position": {
"x": 16.1107177734375,
"y": -777.534423828125
},
"rotation": 0.0,
"size": {
"x": 281.898193359375,
"y": 181.309020996094
},
"finish_id": "finish_wall_paint",
"color": "#5a9d70ff"
},
{
"file_id": "wall_panel_6",
"type": "wall_panel",
"position": {
"x": -37.2885131835938,
"y": -739.658203125
},
"rotation": 0.0,
"size": {
"x": 276.930786132813,
"y": 100.589233398438
},
"finish_id": "finish_wall_paint",
"color": "#5a9d70ff"
},
{
"file_id": "wall_panel_7",
"type": "wall_panel",
"position": {
"x": 218.531005859375,
"y": -634.1015625
},
"rotation": 0.0,
"size": {
"x": 180.067138671875,
"y": 257.061401367188
},
"finish_id": "finish_wall_paint",
"color": "#5a9d70ff"
},
{
"file_id": "wall_panel_8",
"type": "wall_panel",
"position": {
"x": -193.760681152344,
"y": -507.433654785156
},
"rotation": 0.0,
"size": {
"x": 142.811889648438,
"y": 428.435668945313
},
"finish_id": "finish_wall_paint",
"color": "#5a9d70ff"
},
{
"file_id": "wall_panel_9",
"type": "wall_panel",
"position": {
"x": -5.0006103515625,
"y": -762.63232421875
},
"rotation": 0.0,
"size": {
"x": 321.63720703125,
"y": 211.113220214844
},
"finish_id": "finish_wall_paint",
"color": "#5a9d70ff"
},
{
"file_id": "wall_panel_10",
"type": "wall_panel",
"position": {
"x": 29.7709350585938,
"y": -198.21484375
},
"rotation": 0.0,
"size": {
"x": 736.412475585938,
"y": 403.598876953125
},
"finish_id": "finish_wall_paint",
"color": "#5a9d70ff"
},
{
"file_id": "wall_panel_11",
"type": "wall_panel",
"position": {
"x": 458.206604003906,
"y": -332.954742431641
},
"rotation": 0.0,
"size": {
"x": 661.901977539063,
"y": 986.023010253906
},
"finish_id": "finish_wall_paint",
"color": "#5a9d70ff"
},
{
"file_id": "wall_panel_12",
"type": "wall_panel",
"position": {
"x": -522.22802734375,
"y": -360.275299072266
},
"rotation": 0.0,
"size": {
"x": 789.811767578125,
"y": 1020.79449462891
},
"finish_id": "finish_wall_paint",
"color": "#5a9d70ff"
},
{
"file_id": "wall_panel_13",
"type": "wall_panel",
"position": {
"x": 44.6731567382813,
"y": -16.9058227539063
},
"rotation": 0.0,
"size": {
"x": 1493.93664550781,
"y": 430.919311523438
},
"finish_id": "finish_wall_paint",
"color": "#5a9d70ff"
},
{
"file_id": "wall_panel_14",
"type": "wall_panel",
"position": {
"x": -199.348937988281,
"y": 81.19970703125
},
"rotation": 0.0,
"size": {
"x": 1577.14001464844,
"y": 259.545166015625
},
"finish_id": "finish_wall_paint",
"color": "#5a9d70ff"
},
{
"file_id": "wall_panel_15",
"type": "wall_panel",
"position": {
"x": 61.60986328125,
"y": 297.579467773438
},
"rotation": 0.0,
"size": {
"x": 1281.58154296875,
"y": 192.485595703125
},
"finish_id": "finish_wall_wood",
"color": "#5a9d70ff"
}
],
"event_rules": [],
"metadata": {
"created_unix": 1792022400,
"modified_unix": 1792163812.927,
"autosave": false,
"source_path": ""
}
}
-659
View File
@@ -1,659 +0,0 @@
{
"version": "1.5",
"scene_name": "Living Room",
"sky_id": "sky_clear_day",
"camera": {
"position": {
"x": -43.7599487304688,
"y": 247.024810791016
},
"zoom": 0.665500164031982
},
"objects": [
{
"file_id": "wall_panel_1",
"type": "wall_panel",
"position": {
"x": 0.0,
"y": -820.0
},
"rotation": 0.0,
"size": {
"x": 1120.0,
"y": 170.0
},
"finish_id": "finish_wall_paint",
"color": "#5a9d70ff"
},
{
"file_id": "wall_panel_2",
"type": "wall_panel",
"position": {
"x": -360.0,
"y": -535.0
},
"rotation": 0.0,
"size": {
"x": 440.0,
"y": 400.0
},
"finish_id": "finish_wall_paint",
"color": "#5a9d70ff"
},
{
"file_id": "wall_panel_3",
"type": "wall_panel",
"position": {
"x": 360.0,
"y": -535.0
},
"rotation": 0.0,
"size": {
"x": 440.0,
"y": 400.0
},
"finish_id": "finish_wall_paint",
"color": "#5a9d70ff"
},
{
"file_id": "scenery_artwork_1",
"type": "scenery_artwork",
"position": {
"x": 0.0,
"y": -530.0
},
"rotation": 0.0,
"asset_id": "res://content/props/art_window_frame-v1.prop",
"display_name": "Window frame",
"depth": "near",
"scale": {
"x": 1.0,
"y": 1.0
},
"last_known_local_bounds": {
"position": {
"x": -147.0,
"y": -154.0
},
"size": {
"x": 294.0,
"y": 284.0
}
}
},
{
"file_id": "scenery_artwork_2",
"type": "scenery_artwork",
"position": {
"x": -430.0,
"y": -570.0
},
"rotation": 0.0,
"asset_id": "res://content/props/art_wall_clock-v1.prop",
"display_name": "Wall clock",
"depth": "near",
"scale": {
"x": 0.800000011920929,
"y": 0.800000011920929
},
"last_known_local_bounds": {
"position": {
"x": -72.5,
"y": -72.5
},
"size": {
"x": 145.0,
"y": 145.0
}
}
},
{
"file_id": "scenery_artwork_3",
"type": "scenery_artwork",
"position": {
"x": 365.0,
"y": -460.0
},
"rotation": 0.0,
"asset_id": "res://content/props/art_fireplace_mantel-v2.prop",
"display_name": "Fireplace",
"depth": "near",
"scale": {
"x": 0.819999992847443,
"y": 0.819999992847443
},
"last_known_local_bounds": {
"position": {
"x": -153.0,
"y": -116.0
},
"size": {
"x": 306.0,
"y": 244.0
}
}
},
{
"file_id": "scenery_artwork_4",
"type": "scenery_artwork",
"position": {
"x": -475.0,
"y": -310.0
},
"rotation": 0.0,
"asset_id": "res://content/props/art_bookshelf-v1.prop",
"display_name": "Bookshelf",
"depth": "near",
"scale": {
"x": 0.819999992847443,
"y": 0.819999992847443
},
"last_known_local_bounds": {
"position": {
"x": -104.0,
"y": -190.0
},
"size": {
"x": 208.0,
"y": 315.0
}
}
},
{
"file_id": "scenery_artwork_5",
"type": "scenery_artwork",
"position": {
"x": 185.0,
"y": -575.0
},
"rotation": 0.0,
"asset_id": "res://content/props/art_framed_picture-v1.prop",
"display_name": "Framed picture",
"depth": "near",
"scale": {
"x": 0.75,
"y": 0.75
},
"last_known_local_bounds": {
"position": {
"x": -112.0,
"y": -84.0
},
"size": {
"x": 224.0,
"y": 168.0
}
}
},
{
"file_id": "scenery_artwork_6",
"type": "scenery_artwork",
"position": {
"x": 0.0,
"y": 112.0
},
"rotation": 0.0,
"asset_id": "res://content/props/art_rug-v1.prop",
"display_name": "Floor rug",
"depth": "floor",
"scale": {
"x": 1.0,
"y": 1.0
},
"last_known_local_bounds": {
"position": {
"x": -179.0,
"y": -60.0
},
"size": {
"x": 358.0,
"y": 139.0
}
}
},
{
"file_id": "scenery_artwork_7",
"type": "scenery_artwork",
"position": {
"x": 500.0,
"y": 84.0
},
"rotation": 0.0,
"asset_id": "res://content/props/art_potted_plant-v1.prop",
"display_name": "Potted plant",
"depth": "floor",
"scale": {
"x": 0.800000011920929,
"y": 0.800000011920929
},
"last_known_local_bounds": {
"position": {
"x": -62.5,
"y": -114.5
},
"size": {
"x": 125.0,
"y": 219.0
}
}
},
{
"file_id": "stickman_1",
"type": "stickman",
"position": {
"x": -690.0,
"y": 152.0
},
"rotation": 0.0,
"stk_path": "res://content/stickmen/break.stk",
"facing_profile": "FORWARD",
"action_queue": [
{
"type": "move_to",
"target": {
"x": 588.195678710938,
"y": 144.184204101563
},
"style": "walk"
}
]
},
{
"file_id": "wall_panel_4",
"type": "wall_panel",
"position": {
"x": 206.112670898438,
"y": -758.285827636719
},
"rotation": 0.0,
"size": {
"x": 634.58154296875,
"y": 125.426147460938
},
"finish_id": "finish_wall_paint",
"color": "#5a9d70ff"
},
{
"file_id": "wall_panel_5",
"type": "wall_panel",
"position": {
"x": 16.1107177734375,
"y": -777.534423828125
},
"rotation": 0.0,
"size": {
"x": 281.898193359375,
"y": 181.309020996094
},
"finish_id": "finish_wall_paint",
"color": "#5a9d70ff"
},
{
"file_id": "wall_panel_6",
"type": "wall_panel",
"position": {
"x": -37.2885131835938,
"y": -739.658203125
},
"rotation": 0.0,
"size": {
"x": 276.930786132813,
"y": 100.589233398438
},
"finish_id": "finish_wall_paint",
"color": "#5a9d70ff"
},
{
"file_id": "wall_panel_7",
"type": "wall_panel",
"position": {
"x": 218.531005859375,
"y": -634.1015625
},
"rotation": 0.0,
"size": {
"x": 180.067138671875,
"y": 257.061401367188
},
"finish_id": "finish_wall_paint",
"color": "#5a9d70ff"
},
{
"file_id": "wall_panel_8",
"type": "wall_panel",
"position": {
"x": -193.760681152344,
"y": -507.433654785156
},
"rotation": 0.0,
"size": {
"x": 142.811889648438,
"y": 428.435668945313
},
"finish_id": "finish_wall_paint",
"color": "#5a9d70ff"
},
{
"file_id": "wall_panel_9",
"type": "wall_panel",
"position": {
"x": -5.0006103515625,
"y": -762.63232421875
},
"rotation": 0.0,
"size": {
"x": 321.63720703125,
"y": 211.113220214844
},
"finish_id": "finish_wall_paint",
"color": "#5a9d70ff"
},
{
"file_id": "wall_panel_10",
"type": "wall_panel",
"position": {
"x": 29.7709350585938,
"y": -198.21484375
},
"rotation": 0.0,
"size": {
"x": 736.412475585938,
"y": 403.598876953125
},
"finish_id": "finish_wall_paint",
"color": "#5a9d70ff"
},
{
"file_id": "wall_panel_11",
"type": "wall_panel",
"position": {
"x": 458.206604003906,
"y": -332.954742431641
},
"rotation": 0.0,
"size": {
"x": 661.901977539063,
"y": 986.023010253906
},
"finish_id": "finish_wall_paint",
"color": "#5a9d70ff"
},
{
"file_id": "wall_panel_12",
"type": "wall_panel",
"position": {
"x": -522.22802734375,
"y": -360.275299072266
},
"rotation": 0.0,
"size": {
"x": 789.811767578125,
"y": 1020.79449462891
},
"finish_id": "finish_wall_paint",
"color": "#5a9d70ff"
},
{
"file_id": "wall_panel_13",
"type": "wall_panel",
"position": {
"x": 44.6731567382813,
"y": -16.9058227539063
},
"rotation": 0.0,
"size": {
"x": 1493.93664550781,
"y": 430.919311523438
},
"finish_id": "finish_wall_paint",
"color": "#5a9d70ff"
},
{
"file_id": "wall_panel_14",
"type": "wall_panel",
"position": {
"x": -199.348937988281,
"y": 81.19970703125
},
"rotation": 0.0,
"size": {
"x": 1577.14001464844,
"y": 259.545166015625
},
"finish_id": "finish_wall_paint",
"color": "#5a9d70ff"
},
{
"file_id": "wall_panel_15",
"type": "wall_panel",
"position": {
"x": 61.60986328125,
"y": 297.579467773438
},
"rotation": 0.0,
"size": {
"x": 1281.58154296875,
"y": 192.485595703125
},
"finish_id": "finish_wall_wood",
"color": "#5a9d70ff"
},
{
"file_id": "wall_panel_16",
"type": "wall_panel",
"position": {
"x": -1040.0,
"y": -848.0
},
"rotation": 0.0,
"size": {
"x": 32.0,
"y": 32.0
},
"finish_id": "finish_wall_paint",
"color": "#5a9d70ff"
},
{
"file_id": "wall_panel_17",
"type": "wall_panel",
"position": {
"x": -64.0,
"y": -176.0
},
"rotation": 0.0,
"size": {
"x": 2048.0,
"y": 1504.0
},
"finish_id": "finish_wall_paint",
"color": "#5a9d70ff"
},
{
"file_id": "wall_panel_18",
"type": "wall_panel",
"position": {
"x": -64.0,
"y": 592.0
},
"rotation": 0.0,
"size": {
"x": 2048.0,
"y": 32.0
},
"finish_id": "finish_wall_wood",
"color": "#5a9d70ff"
},
{
"file_id": "terrain_1",
"type": "terrain",
"position": {
"x": -960.0,
"y": 640.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
},
"finish_id": "finish_ground_wood"
},
{
"file_id": "terrain_2",
"type": "terrain",
"position": {
"x": -768.0,
"y": 640.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
},
"finish_id": "finish_ground_wood"
},
{
"file_id": "terrain_3",
"type": "terrain",
"position": {
"x": -576.0,
"y": 640.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
},
"finish_id": "finish_ground_wood"
},
{
"file_id": "terrain_4",
"type": "terrain",
"position": {
"x": -384.0,
"y": 640.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
},
"finish_id": "finish_ground_wood"
},
{
"file_id": "terrain_5",
"type": "terrain",
"position": {
"x": -192.0,
"y": 640.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
},
"finish_id": "finish_ground_wood"
},
{
"file_id": "terrain_6",
"type": "terrain",
"position": {
"x": 0.0,
"y": 640.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
},
"finish_id": "finish_ground_wood"
},
{
"file_id": "terrain_7",
"type": "terrain",
"position": {
"x": 192.0,
"y": 640.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
},
"finish_id": "finish_ground_wood"
},
{
"file_id": "terrain_8",
"type": "terrain",
"position": {
"x": 384.0,
"y": 640.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
},
"finish_id": "finish_ground_wood"
},
{
"file_id": "terrain_9",
"type": "terrain",
"position": {
"x": 576.0,
"y": 640.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
},
"finish_id": "finish_ground_wood"
},
{
"file_id": "terrain_10",
"type": "terrain",
"position": {
"x": 768.0,
"y": 640.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
},
"finish_id": "finish_ground_wood"
},
{
"file_id": "terrain_11",
"type": "terrain",
"position": {
"x": 960.0,
"y": 640.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
},
"finish_id": "finish_ground_wood"
}
],
"event_rules": [],
"metadata": {
"created_unix": 1792022400,
"modified_unix": 1792165751.76,
"autosave": false,
"source_path": ""
}
}
-319
View File
@@ -1,319 +0,0 @@
{
"version": "1.5",
"scene_name": "Untitled",
"sky_id": "sky_neutral",
"camera": {
"position": {
"x": 143.607360839844,
"y": -79.9739379882813
},
"zoom": 0.732050061225891
},
"objects": [
{
"file_id": "terrain_1",
"type": "terrain",
"position": {
"x": -768.0,
"y": -32.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
},
"finish_id": "finish_ground_wood"
},
{
"file_id": "terrain_2",
"type": "terrain",
"position": {
"x": -576.0,
"y": -32.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
},
"finish_id": "finish_ground_wood"
},
{
"file_id": "terrain_3",
"type": "terrain",
"position": {
"x": -384.0,
"y": -32.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
},
"finish_id": "finish_ground_wood"
},
{
"file_id": "terrain_4",
"type": "terrain",
"position": {
"x": -192.0,
"y": -32.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
},
"finish_id": "finish_ground_wood"
},
{
"file_id": "terrain_5",
"type": "terrain",
"position": {
"x": 0.0,
"y": -32.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
},
"finish_id": "finish_ground_wood"
},
{
"file_id": "terrain_6",
"type": "terrain",
"position": {
"x": 192.0,
"y": -32.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
},
"finish_id": "finish_ground_wood"
},
{
"file_id": "terrain_7",
"type": "terrain",
"position": {
"x": 384.0,
"y": -32.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
},
"finish_id": "finish_ground_wood"
},
{
"file_id": "terrain_8",
"type": "terrain",
"position": {
"x": 576.0,
"y": -32.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
},
"finish_id": "finish_ground_wood"
},
{
"file_id": "terrain_9",
"type": "terrain",
"position": {
"x": 768.0,
"y": -32.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
},
"finish_id": "finish_ground_wood"
},
{
"file_id": "terrain_10",
"type": "terrain",
"position": {
"x": 960.0,
"y": -32.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
},
"finish_id": "finish_ground_wood"
},
{
"file_id": "scenery_artwork_1",
"type": "scenery_artwork",
"position": {
"x": -93.5323791503906,
"y": -461.938385009766
},
"rotation": 0.0,
"asset_id": "res://content/props/art_fireplace_mantel-v2.prop",
"display_name": "Cozy Brick Fireplace Mantel",
"depth": "near",
"scale": {
"x": 1.0,
"y": 1.0
},
"last_known_local_bounds": {
"position": {
"x": -386.467620849609,
"y": -306.061584472656
},
"size": {
"x": 772.935241699219,
"y": 612.123168945313
}
}
},
{
"file_id": "stickman_1",
"type": "stickman",
"position": {
"x": 600.0,
"y": -457.0
},
"rotation": 0.0,
"stk_path": "res://content/stickmen/break.stk",
"facing_profile": "FORWARD",
"action_queue": [
{
"type": "move_to",
"target": {
"x": -711.525268554688,
"y": -142.811157226563
},
"style": "run"
}
]
},
{
"file_id": "wall_panel_1",
"type": "wall_panel",
"position": {
"x": 36.0,
"y": -612.0
},
"rotation": 0.0,
"size": {
"x": 1560.0,
"y": 840.0
},
"finish_id": "finish_wall_paint",
"color": "#5a9d70ff"
},
{
"file_id": "wall_panel_2",
"type": "wall_panel",
"position": {
"x": -36.0,
"y": -528.0
},
"rotation": 0.0,
"size": {
"x": 1656.0,
"y": 960.0
},
"finish_id": "finish_wall_paint",
"color": "#5a9d70ff"
},
{
"file_id": "wall_panel_3",
"type": "wall_panel",
"position": {
"x": 120.0,
"y": -84.0
},
"rotation": 0.0,
"size": {
"x": 1728.0,
"y": 168.0
},
"finish_id": "finish_wall_wood",
"color": "#5a9d70ff"
},
{
"file_id": "wall_panel_4",
"type": "wall_panel",
"position": {
"x": -684.0,
"y": -156.0
},
"rotation": 0.0,
"size": {
"x": 360.0,
"y": 24.0
},
"finish_id": "finish_wall_wood",
"color": "#5a9d70ff"
},
{
"file_id": "wall_panel_5",
"type": "wall_panel",
"position": {
"x": -792.0,
"y": -84.0
},
"rotation": 0.0,
"size": {
"x": 144.0,
"y": 168.0
},
"finish_id": "finish_wall_wood",
"color": "#5a9d70ff"
},
{
"file_id": "scenery_artwork_2",
"type": "scenery_artwork",
"position": {
"x": -109.0,
"y": -108.0
},
"rotation": 0.0,
"asset_id": "res://content/props/art_rug-v1.prop",
"display_name": "Patterned Rug",
"depth": "near",
"scale": {
"x": 1.0,
"y": 1.0
},
"last_known_local_bounds": {
"position": {
"x": -179.0,
"y": -60.0
},
"size": {
"x": 358.0,
"y": 139.0
}
}
}
],
"event_rules": [],
"metadata": {
"created_unix": 1792178012.798,
"modified_unix": 1792178012.798,
"autosave": false,
"source_path": ""
}
}
-140
View File
@@ -1,140 +0,0 @@
{
"version": "1.6",
"scene_name": "Untitled",
"sky_id": "sky_neutral",
"camera": {
"position": {
"x": 0.0,
"y": -400.0
},
"zoom": 0.5
},
"objects": [
{
"file_id": "wall_panel_1",
"type": "wall_panel",
"position": {
"x": -768.0,
"y": -608.0
},
"rotation": 0.0,
"size": {
"x": 1152.0,
"y": 832.0
},
"finish_id": "finish_wall_brick",
"color": "#b95f3dff"
},
{
"file_id": "scenery_artwork_1",
"type": "scenery_artwork",
"position": {
"x": -64.0,
"y": -868.5
},
"rotation": 0.0,
"asset_id": "res://content/props/art_city_skyline-v1.prop",
"display_name": "City Skyline",
"depth": "far",
"scale": {
"x": 1.0,
"y": 1.0
},
"last_known_local_bounds": {
"position": {
"x": -321.5,
"y": -126.5
},
"size": {
"x": 643.0,
"y": 198.0
}
}
},
{
"file_id": "scenery_artwork_2",
"type": "scenery_artwork",
"position": {
"x": 320.0,
"y": -804.5
},
"rotation": 0.0,
"asset_id": "res://content/props/art_city_skyline-v1.prop",
"display_name": "City Skyline",
"depth": "floor",
"scale": {
"x": 1.0,
"y": 1.0
},
"last_known_local_bounds": {
"position": {
"x": -321.5,
"y": -126.5
},
"size": {
"x": 643.0,
"y": 198.0
}
}
},
{
"file_id": "scenery_artwork_3",
"type": "scenery_artwork",
"position": {
"x": 768.0,
"y": -804.5
},
"rotation": 0.0,
"asset_id": "res://content/props/art_city_skyline-v1.prop",
"display_name": "City Skyline",
"depth": "near",
"scale": {
"x": 1.0,
"y": 1.0
},
"last_known_local_bounds": {
"position": {
"x": -321.5,
"y": -126.5
},
"size": {
"x": 643.0,
"y": 198.0
}
}
},
{
"file_id": "scenery_artwork_4",
"type": "scenery_artwork",
"position": {
"x": -448.0,
"y": -1600.0
},
"rotation": 0.0,
"asset_id": "res://content/props/baseball.prop",
"display_name": "Baseball",
"depth": "near",
"scale": {
"x": 1.0,
"y": 1.0
},
"last_known_local_bounds": {
"position": {
"x": -26.1999988555908,
"y": -26.1999988555908
},
"size": {
"x": 52.4000015258789,
"y": 52.4000015258789
}
}
}
],
"event_rules": [],
"metadata": {
"created_unix": 1792555445.055,
"modified_unix": 1792555445.055,
"autosave": false,
"source_path": ""
}
}
-419
View File
@@ -1,419 +0,0 @@
{
"version": "1.7",
"scene_name": "Untitled",
"sky_id": "sky_sunset",
"background_kind": "plain",
"plain_color": "#FFFFFF",
"camera": {
"position": {
"x": 1676.29174804688,
"y": -39.946044921875
},
"zoom": 0.310460686683655
},
"objects": [
{
"file_id": "wall_panel_1",
"type": "wall_panel",
"position": {
"x": 288.0,
"y": -224.0
},
"rotation": 0.0,
"size": {
"x": 4160.0,
"y": 2368.0
},
"finish_id": "finish_wall_brick",
"color": "#b95f3dff"
},
{
"file_id": "wall_panel_2",
"type": "wall_panel",
"position": {
"x": 288.0,
"y": -352.0
},
"rotation": 0.0,
"size": {
"x": 5568.0,
"y": 3136.0
},
"finish_id": "finish_wall_wood",
"color": "#c87137ff"
},
{
"file_id": "background_ground_1",
"type": "background_ground",
"position": {
"x": 256.0,
"y": -352.0
},
"rotation": 0.0,
"size": {
"x": 6144.0,
"y": 3392.0
},
"finish_id": "finish_background_concrete",
"color": "#a6aaa3ff"
},
{
"file_id": "wall_panel_3",
"type": "wall_panel",
"position": {
"x": 128.0,
"y": -320.0
},
"rotation": 0.0,
"size": {
"x": 6016.0,
"y": 3456.0
},
"finish_id": "finish_wall_brick",
"color": "#22c6ffff"
},
{
"file_id": "stickman_1",
"type": "stickman",
"position": {
"x": -1088.0,
"y": -641.0
},
"rotation": 0.0,
"stk_path": "res://content/stickmen/break.stk",
"facing_profile": "FORWARD",
"action_queue": [
{
"type": "move_to",
"target": {
"x": 1562.8154296875,
"y": -145.01513671875
},
"style": "walk"
}
]
},
{
"file_id": "terrain_1",
"type": "terrain",
"position": {
"x": -2112.0,
"y": 0.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
},
"finish_id": "finish_ground_grass"
},
{
"file_id": "terrain_2",
"type": "terrain",
"position": {
"x": -1920.0,
"y": 0.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
},
"finish_id": "finish_ground_grass"
},
{
"file_id": "terrain_3",
"type": "terrain",
"position": {
"x": -1728.0,
"y": 0.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
},
"finish_id": "finish_ground_grass"
},
{
"file_id": "terrain_4",
"type": "terrain",
"position": {
"x": -1536.0,
"y": 0.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
},
"finish_id": "finish_ground_grass"
},
{
"file_id": "terrain_5",
"type": "terrain",
"position": {
"x": -1344.0,
"y": 0.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
},
"finish_id": "finish_ground_grass"
},
{
"file_id": "terrain_6",
"type": "terrain",
"position": {
"x": -1152.0,
"y": 0.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
},
"finish_id": "finish_ground_grass"
},
{
"file_id": "terrain_7",
"type": "terrain",
"position": {
"x": -960.0,
"y": 0.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
},
"finish_id": "finish_ground_grass"
},
{
"file_id": "terrain_8",
"type": "terrain",
"position": {
"x": -768.0,
"y": 0.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
},
"finish_id": "finish_ground_grass"
},
{
"file_id": "terrain_9",
"type": "terrain",
"position": {
"x": -576.0,
"y": 0.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
},
"finish_id": "finish_ground_grass"
},
{
"file_id": "terrain_10",
"type": "terrain",
"position": {
"x": -384.0,
"y": 0.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
},
"finish_id": "finish_ground_grass"
},
{
"file_id": "terrain_11",
"type": "terrain",
"position": {
"x": -192.0,
"y": -32.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
},
"finish_id": "finish_ground_grass"
},
{
"file_id": "terrain_12",
"type": "terrain",
"position": {
"x": 0.0,
"y": -32.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
},
"finish_id": "finish_ground_grass"
},
{
"file_id": "terrain_13",
"type": "terrain",
"position": {
"x": 192.0,
"y": -32.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
},
"finish_id": "finish_ground_grass"
},
{
"file_id": "terrain_14",
"type": "terrain",
"position": {
"x": 384.0,
"y": -32.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
},
"finish_id": "finish_ground_grass"
},
{
"file_id": "terrain_15",
"type": "terrain",
"position": {
"x": 576.0,
"y": -32.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
},
"finish_id": "finish_ground_grass"
},
{
"file_id": "terrain_16",
"type": "terrain",
"position": {
"x": 768.0,
"y": -32.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
},
"finish_id": "finish_ground_grass"
},
{
"file_id": "terrain_17",
"type": "terrain",
"position": {
"x": 960.0,
"y": -32.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
},
"finish_id": "finish_ground_grass"
},
{
"file_id": "terrain_18",
"type": "terrain",
"position": {
"x": 1152.0,
"y": -32.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
},
"finish_id": "finish_ground_grass"
},
{
"file_id": "terrain_19",
"type": "terrain",
"position": {
"x": 1344.0,
"y": -32.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
},
"finish_id": "finish_ground_grass"
},
{
"file_id": "terrain_20",
"type": "terrain",
"position": {
"x": 1536.0,
"y": -32.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
},
"finish_id": "finish_ground_grass"
},
{
"file_id": "terrain_21",
"type": "terrain",
"position": {
"x": 1728.0,
"y": -32.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
},
"finish_id": "finish_ground_grass"
}
],
"event_rules": [],
"metadata": {
"created_unix": 1792467978,
"modified_unix": 1792700708.001,
"autosave": false,
"source_path": ""
}
}
-501
View File
@@ -1,501 +0,0 @@
{
"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
@@ -1,546 +0,0 @@
{
"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": ""
}
}
-857
View File
@@ -1,857 +0,0 @@
{
"version": "1.7",
"scene_name": "Plain Background",
"sky_id": "sky_starry_night",
"background_kind": "sky",
"plain_color": "#F5EFE0",
"camera": {
"position": {
"x": -43.8016738891602,
"y": -691.851379394531
},
"zoom": 0.605000019073486
},
"objects": [
{
"file_id": "background_ground_1",
"type": "background_ground",
"position": {
"x": 96.0,
"y": 672.0
},
"rotation": 0.0,
"size": {
"x": 6592.0,
"y": 192.0
},
"finish_id": "finish_background_grass",
"color": "#72a84cff"
},
{
"file_id": "scenery_artwork_1",
"type": "scenery_artwork",
"position": {
"x": -1472.0,
"y": -320.0
},
"rotation": 0.0,
"asset_id": "res://content/props/art_storefront-v1.prop",
"display_name": "Sunny Storefront",
"depth": "near",
"scale": {
"x": 1.0,
"y": 1.0
},
"last_known_local_bounds": {
"position": {
"x": -871.896057128906,
"y": -826.405822753906
},
"size": {
"x": 1743.79211425781,
"y": 1652.81164550781
}
}
},
{
"file_id": "scenery_artwork_2",
"type": "scenery_artwork",
"position": {
"x": -321.925048828125,
"y": -864.47802734375
},
"rotation": 0.0,
"asset_id": "res://content/props/art_town_house-v1.prop",
"display_name": "Town House",
"depth": "near",
"scale": {
"x": 1.0,
"y": 1.0
},
"last_known_local_bounds": {
"position": {
"x": -958.074951171875,
"y": -1375.52197265625
},
"size": {
"x": 1916.14990234375,
"y": 2751.0439453125
}
}
},
{
"file_id": "scenery_artwork_3",
"type": "scenery_artwork",
"position": {
"x": 1408.0,
"y": -320.0
},
"rotation": 0.0,
"asset_id": "res://content/props/art_storefront-v1.prop",
"display_name": "Sunny Storefront",
"depth": "near",
"scale": {
"x": 1.0,
"y": 1.0
},
"last_known_local_bounds": {
"position": {
"x": -871.896057128906,
"y": -826.405822753906
},
"size": {
"x": 1743.79211425781,
"y": 1652.81164550781
}
}
},
{
"file_id": "scenery_artwork_4",
"type": "scenery_artwork",
"position": {
"x": -2368.0,
"y": -256.0
},
"rotation": 0.0,
"asset_id": "res://content/props/art_city_skyline-v1.prop",
"display_name": "City Skyline",
"depth": "far",
"scale": {
"x": 1.0,
"y": 1.0
},
"last_known_local_bounds": {
"position": {
"x": -939.5,
"y": -289.301696777344
},
"size": {
"x": 1879.0,
"y": 578.603393554688
}
}
},
{
"file_id": "scenery_artwork_5",
"type": "scenery_artwork",
"position": {
"x": 704.0,
"y": -256.0
},
"rotation": 0.0,
"asset_id": "res://content/props/art_city_skyline-v1.prop",
"display_name": "City Skyline",
"depth": "far",
"scale": {
"x": 1.0,
"y": 1.0
},
"last_known_local_bounds": {
"position": {
"x": -939.5,
"y": -289.301696777344
},
"size": {
"x": 1879.0,
"y": 578.603393554688
}
}
},
{
"file_id": "scenery_artwork_6",
"type": "scenery_artwork",
"position": {
"x": 2304.0,
"y": -256.0
},
"rotation": 0.0,
"asset_id": "res://content/props/art_city_skyline-v1.prop",
"display_name": "City Skyline",
"depth": "far",
"scale": {
"x": 1.0,
"y": 1.0
},
"last_known_local_bounds": {
"position": {
"x": -939.5,
"y": -289.301696777344
},
"size": {
"x": 1879.0,
"y": 578.603393554688
}
}
},
{
"file_id": "background_ground_2",
"type": "background_ground",
"position": {
"x": 96.0,
"y": 352.0
},
"rotation": 0.0,
"size": {
"x": 6848.0,
"y": 832.0
},
"finish_id": "finish_background_grass",
"color": "#72a84cff"
},
{
"file_id": "terrain_1",
"type": "terrain",
"position": {
"x": -3264.0,
"y": 768.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
},
"finish_id": "finish_ground_sidewalk"
},
{
"file_id": "terrain_2",
"type": "terrain",
"position": {
"x": -3072.0,
"y": 768.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
},
"finish_id": "finish_ground_sidewalk"
},
{
"file_id": "terrain_3",
"type": "terrain",
"position": {
"x": -2880.0,
"y": 768.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
},
"finish_id": "finish_ground_sidewalk"
},
{
"file_id": "terrain_4",
"type": "terrain",
"position": {
"x": -2688.0,
"y": 768.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
},
"finish_id": "finish_ground_sidewalk"
},
{
"file_id": "terrain_5",
"type": "terrain",
"position": {
"x": -2496.0,
"y": 768.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
},
"finish_id": "finish_ground_sidewalk"
},
{
"file_id": "terrain_6",
"type": "terrain",
"position": {
"x": -2304.0,
"y": 768.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
},
"finish_id": "finish_ground_sidewalk"
},
{
"file_id": "terrain_7",
"type": "terrain",
"position": {
"x": -2112.0,
"y": 768.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
},
"finish_id": "finish_ground_sidewalk"
},
{
"file_id": "terrain_8",
"type": "terrain",
"position": {
"x": -1920.0,
"y": 768.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
},
"finish_id": "finish_ground_sidewalk"
},
{
"file_id": "terrain_9",
"type": "terrain",
"position": {
"x": -1728.0,
"y": 768.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
},
"finish_id": "finish_ground_sidewalk"
},
{
"file_id": "terrain_10",
"type": "terrain",
"position": {
"x": -1536.0,
"y": 768.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
},
"finish_id": "finish_ground_sidewalk"
},
{
"file_id": "terrain_11",
"type": "terrain",
"position": {
"x": -1344.0,
"y": 768.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
},
"finish_id": "finish_ground_sidewalk"
},
{
"file_id": "terrain_12",
"type": "terrain",
"position": {
"x": -1152.0,
"y": 768.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
},
"finish_id": "finish_ground_sidewalk"
},
{
"file_id": "terrain_13",
"type": "terrain",
"position": {
"x": -960.0,
"y": 768.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
},
"finish_id": "finish_ground_sidewalk"
},
{
"file_id": "terrain_14",
"type": "terrain",
"position": {
"x": -768.0,
"y": 768.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
},
"finish_id": "finish_ground_sidewalk"
},
{
"file_id": "terrain_15",
"type": "terrain",
"position": {
"x": -576.0,
"y": 768.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
},
"finish_id": "finish_ground_sidewalk"
},
{
"file_id": "terrain_16",
"type": "terrain",
"position": {
"x": -384.0,
"y": 768.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
},
"finish_id": "finish_ground_sidewalk"
},
{
"file_id": "terrain_17",
"type": "terrain",
"position": {
"x": -192.0,
"y": 768.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
},
"finish_id": "finish_ground_sidewalk"
},
{
"file_id": "terrain_18",
"type": "terrain",
"position": {
"x": 0.0,
"y": 768.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
},
"finish_id": "finish_ground_sidewalk"
},
{
"file_id": "terrain_19",
"type": "terrain",
"position": {
"x": 192.0,
"y": 768.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
},
"finish_id": "finish_ground_sidewalk"
},
{
"file_id": "terrain_20",
"type": "terrain",
"position": {
"x": 384.0,
"y": 768.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
},
"finish_id": "finish_ground_sidewalk"
},
{
"file_id": "terrain_21",
"type": "terrain",
"position": {
"x": 576.0,
"y": 768.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
},
"finish_id": "finish_ground_sidewalk"
},
{
"file_id": "terrain_22",
"type": "terrain",
"position": {
"x": 768.0,
"y": 768.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
},
"finish_id": "finish_ground_sidewalk"
},
{
"file_id": "terrain_23",
"type": "terrain",
"position": {
"x": 960.0,
"y": 768.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
},
"finish_id": "finish_ground_sidewalk"
},
{
"file_id": "terrain_24",
"type": "terrain",
"position": {
"x": 1152.0,
"y": 768.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
},
"finish_id": "finish_ground_sidewalk"
},
{
"file_id": "terrain_25",
"type": "terrain",
"position": {
"x": 1344.0,
"y": 768.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
},
"finish_id": "finish_ground_sidewalk"
},
{
"file_id": "terrain_26",
"type": "terrain",
"position": {
"x": 1536.0,
"y": 768.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
},
"finish_id": "finish_ground_sidewalk"
},
{
"file_id": "terrain_27",
"type": "terrain",
"position": {
"x": 1728.0,
"y": 768.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
},
"finish_id": "finish_ground_sidewalk"
},
{
"file_id": "terrain_28",
"type": "terrain",
"position": {
"x": 1920.0,
"y": 768.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
},
"finish_id": "finish_ground_sidewalk"
},
{
"file_id": "terrain_29",
"type": "terrain",
"position": {
"x": 2112.0,
"y": 768.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
},
"finish_id": "finish_ground_sidewalk"
},
{
"file_id": "terrain_30",
"type": "terrain",
"position": {
"x": 2304.0,
"y": 768.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
},
"finish_id": "finish_ground_sidewalk"
},
{
"file_id": "terrain_31",
"type": "terrain",
"position": {
"x": 2496.0,
"y": 768.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
},
"finish_id": "finish_ground_sidewalk"
},
{
"file_id": "terrain_32",
"type": "terrain",
"position": {
"x": 2688.0,
"y": 768.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
},
"finish_id": "finish_ground_sidewalk"
},
{
"file_id": "terrain_33",
"type": "terrain",
"position": {
"x": 2880.0,
"y": 768.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
},
"finish_id": "finish_ground_sidewalk"
},
{
"file_id": "terrain_34",
"type": "terrain",
"position": {
"x": 3072.0,
"y": 768.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
},
"finish_id": "finish_ground_sidewalk"
},
{
"file_id": "terrain_35",
"type": "terrain",
"position": {
"x": 3264.0,
"y": 768.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
},
"finish_id": "finish_ground_sidewalk"
},
{
"file_id": "terrain_36",
"type": "terrain",
"position": {
"x": 3456.0,
"y": 768.0
},
"rotation": 0.0,
"asset_id": "ground",
"scale": {
"x": 1.0,
"y": 1.0
},
"finish_id": "finish_ground_sidewalk"
},
{
"file_id": "background_ground_3",
"type": "background_ground",
"position": {
"x": 160.0,
"y": 864.0
},
"rotation": 0.0,
"size": {
"x": 6912.0,
"y": 192.0
},
"finish_id": "finish_background_concrete",
"color": "#a6aaa3ff"
},
{
"file_id": "background_ground_4",
"type": "background_ground",
"position": {
"x": -2896.0,
"y": 848.0
},
"rotation": 0.0,
"size": {
"x": 800.0,
"y": 160.0
},
"finish_id": "finish_background_concrete",
"color": "#a6aaa3ff"
},
{
"file_id": "background_ground_5",
"type": "background_ground",
"position": {
"x": -3264.0,
"y": 864.0
},
"rotation": 0.0,
"size": {
"x": 320.0,
"y": 192.0
},
"finish_id": "finish_background_concrete",
"color": "#a6aaa3ff"
},
{
"file_id": "stickman_1",
"type": "stickman",
"position": {
"x": -2784.0,
"y": 351.0
},
"rotation": 0.0,
"stk_path": "res://content/stickmen/break.stk",
"facing_profile": "FORWARD",
"action_queue": [
{
"type": "move_to",
"target": {
"x": 2027.27270507813,
"y": 683.355224609375
},
"style": "walk"
}
]
},
{
"file_id": "stickman_2",
"type": "stickman",
"position": {
"x": -672.0,
"y": 352.0
},
"rotation": 0.0,
"stk_path": "res://content/stickmen/break.stk",
"facing_profile": "FORWARD",
"action_queue": [
{
"type": "move_to",
"target": {
"x": -3161.15698242188,
"y": 701.537109375
},
"style": "walk"
}
]
},
{
"file_id": "stickman_3",
"type": "stickman",
"position": {
"x": 3392.0,
"y": 351.0
},
"rotation": 0.0,
"stk_path": "res://content/stickmen/break.stk",
"facing_profile": "FORWARD",
"action_queue": [
{
"type": "move_to",
"target": {
"x": -3333.0576171875,
"y": 726.330444335938
},
"style": "run"
}
]
}
],
"event_rules": [],
"metadata": {
"created_unix": 1792022400,
"modified_unix": 1792732500.154,
"autosave": false,
"source_path": ""
}
}
-321
View File
@@ -1,321 +0,0 @@
{
"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
@@ -1,567 +0,0 @@
{
"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
@@ -1,311 +0,0 @@
{
"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
@@ -1,140 +0,0 @@
{
"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
@@ -1,342 +0,0 @@
{
"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": ""
}
}
-11
View File
@@ -1,11 +0,0 @@
{
"version": "1.7",
"scene_name": "Open Sky",
"sky_id": "sky_neutral",
"background_kind": "sky",
"plain_color": "#F5EFE0",
"camera": {"position": {"x": 0.0, "y": -400.0}, "zoom": 0.5},
"objects": [],
"event_rules": [],
"metadata": {"created_unix": 1792022400, "modified_unix": 1792022400, "autosave": false, "source_path": ""}
}
-50
View File
@@ -1,50 +0,0 @@
{
"version": "1.6",
"scene_name": "Living Room",
"sky_id": "sky_clear_day",
"camera": {"position": {"x": 118.0, "y": -432.0}, "zoom": 0.38},
"objects": [
{"file_id":"wall_top","type":"wall_panel","position":{"x":0.0,"y":-941.0},"rotation":0.0,"size":{"x":4760.0,"y":518.0},"finish_id":"finish_wall_paint","color":"#5A9D70"},
{"file_id":"wall_left","type":"wall_panel","position":{"x":-1262.5,"y":-256.0},"rotation":0.0,"size":{"x":2235.0,"y":852.0},"finish_id":"finish_wall_paint","color":"#5A9D70"},
{"file_id":"wall_right","type":"wall_panel","position":{"x":1262.5,"y":-256.0},"rotation":0.0,"size":{"x":2235.0,"y":852.0},"finish_id":"finish_wall_paint","color":"#5A9D70"},
{"file_id":"wall_under_window","type":"wall_panel","position":{"x":0.0,"y":-116.0},"rotation":0.0,"size":{"x":290.0,"y":572.0},"finish_id":"finish_wall_paint","color":"#5A9D70"},
{"file_id":"wall_baseboard","type":"wall_panel","position":{"x":0.0,"y":177.0},"rotation":0.0,"size":{"x":4760.0,"y":14.0},"finish_id":"finish_wall_paint","color":"#E4C496"},
{"file_id":"window_frame","type":"scenery_artwork","asset_id":"res://content/props/art_window_frame-v1.prop","display_name":"Window frame","depth":"near","position":{"x":0.0,"y":-530.0},"rotation":0.0,"scale":{"x":1.0,"y":1.0}},
{"file_id":"wall_clock","type":"scenery_artwork","asset_id":"res://content/props/art_wall_clock-v1.prop","display_name":"Wall clock","depth":"near","position":{"x":-450.0,"y":-540.0},"rotation":0.0,"scale":{"x":1.0,"y":1.0}},
{"file_id":"fireplace","type":"scenery_artwork","asset_id":"res://content/props/art_fireplace_mantel-v2.prop","display_name":"Fireplace","depth":"near","position":{"x":1280.0,"y":-126.5},"rotation":0.0,"scale":{"x":1.0,"y":1.0}},
{"file_id":"bookshelf","type":"scenery_artwork","asset_id":"res://content/props/art_bookshelf-v1.prop","display_name":"Bookshelf","depth":"near","position":{"x":-930.0,"y":-311.0},"rotation":0.0,"scale":{"x":1.0,"y":1.0}},
{"file_id":"picture","type":"scenery_artwork","asset_id":"res://content/props/art_framed_picture-v1.prop","display_name":"Framed picture","depth":"near","position":{"x":1000.0,"y":-530.0},"rotation":0.0,"scale":{"x":1.0,"y":1.0}},
{"file_id":"ground_wood_left_12","type":"terrain","asset_id":"ground","position":{"x":-2304.0,"y":200.0},"rotation":0.0,"scale":{"x":1.0,"y":1.0},"finish_id":"finish_ground_wood"},
{"file_id":"ground_wood_left_11","type":"terrain","asset_id":"ground","position":{"x":-2112.0,"y":200.0},"rotation":0.0,"scale":{"x":1.0,"y":1.0},"finish_id":"finish_ground_wood"},
{"file_id":"ground_wood_left_10","type":"terrain","asset_id":"ground","position":{"x":-1920.0,"y":200.0},"rotation":0.0,"scale":{"x":1.0,"y":1.0},"finish_id":"finish_ground_wood"},
{"file_id":"ground_wood_left_09","type":"terrain","asset_id":"ground","position":{"x":-1728.0,"y":200.0},"rotation":0.0,"scale":{"x":1.0,"y":1.0},"finish_id":"finish_ground_wood"},
{"file_id":"ground_wood_left_08","type":"terrain","asset_id":"ground","position":{"x":-1536.0,"y":200.0},"rotation":0.0,"scale":{"x":1.0,"y":1.0},"finish_id":"finish_ground_wood"},
{"file_id":"ground_wood_left_07","type":"terrain","asset_id":"ground","position":{"x":-1344.0,"y":200.0},"rotation":0.0,"scale":{"x":1.0,"y":1.0},"finish_id":"finish_ground_wood"},
{"file_id":"ground_wood_left_06","type":"terrain","asset_id":"ground","position":{"x":-1152.0,"y":200.0},"rotation":0.0,"scale":{"x":1.0,"y":1.0},"finish_id":"finish_ground_wood"},
{"file_id":"ground_wood_left_05","type":"terrain","asset_id":"ground","position":{"x":-960.0,"y":200.0},"rotation":0.0,"scale":{"x":1.0,"y":1.0},"finish_id":"finish_ground_wood"},
{"file_id":"ground_wood_left_04","type":"terrain","asset_id":"ground","position":{"x":-768.0,"y":200.0},"rotation":0.0,"scale":{"x":1.0,"y":1.0},"finish_id":"finish_ground_wood"},
{"file_id":"ground_wood_left_03","type":"terrain","asset_id":"ground","position":{"x":-576.0,"y":200.0},"rotation":0.0,"scale":{"x":1.0,"y":1.0},"finish_id":"finish_ground_wood"},
{"file_id":"ground_wood_left_02","type":"terrain","asset_id":"ground","position":{"x":-384.0,"y":200.0},"rotation":0.0,"scale":{"x":1.0,"y":1.0},"finish_id":"finish_ground_wood"},
{"file_id":"ground_wood_left_01","type":"terrain","asset_id":"ground","position":{"x":-192.0,"y":200.0},"rotation":0.0,"scale":{"x":1.0,"y":1.0},"finish_id":"finish_ground_wood"},
{"file_id":"ground_wood","type":"terrain","asset_id":"ground","position":{"x":0.0,"y":200.0},"rotation":0.0,"scale":{"x":1.0,"y":1.0},"finish_id":"finish_ground_wood"},
{"file_id":"ground_wood_right_01","type":"terrain","asset_id":"ground","position":{"x":192.0,"y":200.0},"rotation":0.0,"scale":{"x":1.0,"y":1.0},"finish_id":"finish_ground_wood"},
{"file_id":"ground_wood_right_02","type":"terrain","asset_id":"ground","position":{"x":384.0,"y":200.0},"rotation":0.0,"scale":{"x":1.0,"y":1.0},"finish_id":"finish_ground_wood"},
{"file_id":"ground_wood_right_03","type":"terrain","asset_id":"ground","position":{"x":576.0,"y":200.0},"rotation":0.0,"scale":{"x":1.0,"y":1.0},"finish_id":"finish_ground_wood"},
{"file_id":"ground_wood_right_04","type":"terrain","asset_id":"ground","position":{"x":768.0,"y":200.0},"rotation":0.0,"scale":{"x":1.0,"y":1.0},"finish_id":"finish_ground_wood"},
{"file_id":"ground_wood_right_05","type":"terrain","asset_id":"ground","position":{"x":960.0,"y":200.0},"rotation":0.0,"scale":{"x":1.0,"y":1.0},"finish_id":"finish_ground_wood"},
{"file_id":"ground_wood_right_06","type":"terrain","asset_id":"ground","position":{"x":1152.0,"y":200.0},"rotation":0.0,"scale":{"x":1.0,"y":1.0},"finish_id":"finish_ground_wood"},
{"file_id":"ground_wood_right_07","type":"terrain","asset_id":"ground","position":{"x":1344.0,"y":200.0},"rotation":0.0,"scale":{"x":1.0,"y":1.0},"finish_id":"finish_ground_wood"},
{"file_id":"ground_wood_right_08","type":"terrain","asset_id":"ground","position":{"x":1536.0,"y":200.0},"rotation":0.0,"scale":{"x":1.0,"y":1.0},"finish_id":"finish_ground_wood"},
{"file_id":"ground_wood_right_09","type":"terrain","asset_id":"ground","position":{"x":1728.0,"y":200.0},"rotation":0.0,"scale":{"x":1.0,"y":1.0},"finish_id":"finish_ground_wood"},
{"file_id":"ground_wood_right_10","type":"terrain","asset_id":"ground","position":{"x":1920.0,"y":200.0},"rotation":0.0,"scale":{"x":1.0,"y":1.0},"finish_id":"finish_ground_wood"},
{"file_id":"ground_wood_right_11","type":"terrain","asset_id":"ground","position":{"x":2112.0,"y":200.0},"rotation":0.0,"scale":{"x":1.0,"y":1.0},"finish_id":"finish_ground_wood"},
{"file_id":"ground_wood_right_12","type":"terrain","asset_id":"ground","position":{"x":2304.0,"y":200.0},"rotation":0.0,"scale":{"x":1.0,"y":1.0},"finish_id":"finish_ground_wood"},
{"file_id":"rug","type":"scenery_artwork","asset_id":"res://content/props/art_rug-v1.prop","display_name":"Floor rug","depth":"floor","position":{"x":0.0,"y":87.2},"rotation":0.0,"scale":{"x":1.0,"y":1.0}},
{"file_id":"plant","type":"scenery_artwork","asset_id":"res://content/props/art_potted_plant-v1.prop","display_name":"Potted plant","depth":"floor","position":{"x":1715.0,"y":75.0},"rotation":0.0,"scale":{"x":1.0,"y":1.0}},
{"file_id":"couch","type":"prop","asset_id":"res://content/props/prop_couch-v1.prop","position":{"x":-690.0,"y":-85.0},"rotation":0.0,"scale":{"x":1.0,"y":1.0},"stage_object_id":"starter_couch","roles":{"depth":"behind","start_motion":"still","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":false,"material_preset":"WOOD"},
{"file_id":"armchair","type":"prop","asset_id":"res://content/props/prop_armchair-v1.prop","position":{"x":620.0,"y":-104.4},"rotation":0.0,"scale":{"x":1.0,"y":1.0},"stage_object_id":"starter_armchair","roles":{"depth":"behind","start_motion":"still","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":false,"material_preset":"WOOD"},
{"file_id":"coffee_table","type":"prop","asset_id":"res://content/props/prop_coffee_table-v1.prop","position":{"x":-515.0,"y":64.3},"rotation":0.0,"scale":{"x":1.0,"y":1.0},"stage_object_id":"starter_coffee_table","roles":{"depth":"front","start_motion":"still","holds_other_props":true,"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":false,"material_preset":"WOOD"}
],
"event_rules": [],
"metadata": {"created_unix": 1792022400, "modified_unix": 1792022400, "autosave": false, "source_path": ""}
}
@@ -1,11 +0,0 @@
{
"version": "1.7",
"scene_name": "Plain Background",
"sky_id": "sky_neutral",
"background_kind": "plain",
"plain_color": "#F5EFE0",
"camera": {"position": {"x": 0.0, "y": -400.0}, "zoom": 0.5},
"objects": [],
"event_rules": [],
"metadata": {"created_unix": 1792022400, "modified_unix": 1792022400, "autosave": false, "source_path": ""}
}
-102
View File
@@ -1,102 +0,0 @@
{
"version": "1.6",
"scene_name": "Town Street",
"sky_id": "sky_cloudy_day",
"camera": {"position": {"x": 0.0, "y": -1200.0}, "zoom": 0.16},
"objects": [
{"file_id":"town_landscape_ground","type":"background_ground","position":{"x":0.0,"y":660.0},"size":{"x":8400.0,"y":2000.0},"finish_id":"finish_background_grass","color":"#72A84C"},
{"file_id":"scenery_skyline_left_outer","type":"scenery_artwork","asset_id":"res://content/props/art_city_skyline-v1.prop","display_name":"City skyline","depth":"far","position":{"x":-3700.0,"y":-604.918},"rotation":0.0,"scale":{"x":1.0,"y":1.0}},
{"file_id":"scenery_skyline_left","type":"scenery_artwork","asset_id":"res://content/props/art_city_skyline-v1.prop","display_name":"City skyline","depth":"far","position":{"x":-1850.0,"y":-604.918},"rotation":0.0,"scale":{"x":1.0,"y":1.0}},
{"file_id":"scenery_skyline","type":"scenery_artwork","asset_id":"res://content/props/art_city_skyline-v1.prop","display_name":"City skyline","depth":"far","position":{"x":0.0,"y":-604.918},"rotation":0.0,"scale":{"x":1.0,"y":1.0}},
{"file_id":"scenery_skyline_right","type":"scenery_artwork","asset_id":"res://content/props/art_city_skyline-v1.prop","display_name":"City skyline","depth":"far","position":{"x":1850.0,"y":-604.918},"rotation":0.0,"scale":{"x":1.0,"y":1.0}},
{"file_id":"scenery_skyline_right_outer","type":"scenery_artwork","asset_id":"res://content/props/art_city_skyline-v1.prop","display_name":"City skyline","depth":"far","position":{"x":3700.0,"y":-604.918},"rotation":0.0,"scale":{"x":1.0,"y":1.0}},
{"file_id":"scenery_tree_left","type":"scenery_artwork","asset_id":"res://content/props/art_tree-v2.prop","display_name":"Tree","depth":"near","position":{"x":-2600.0,"y":-1271.498},"rotation":0.0,"scale":{"x":1.0,"y":1.0}},
{"file_id":"scenery_town_house","type":"scenery_artwork","asset_id":"res://content/props/art_town_house-v1.prop","display_name":"Town house","depth":"near","position":{"x":-1400.0,"y":-1361.835},"rotation":0.0,"scale":{"x":1.0,"y":1.0}},
{"file_id":"scenery_storefront","type":"scenery_artwork","asset_id":"res://content/props/art_storefront-v1.prop","display_name":"Storefront","depth":"near","position":{"x":1400.0,"y":-813.77},"rotation":0.0,"scale":{"x":1.0,"y":1.0}},
{"file_id":"scenery_tree_right","type":"scenery_artwork","asset_id":"res://content/props/art_tree-v2.prop","display_name":"Tree","depth":"near","position":{"x":2600.0,"y":-1271.498},"rotation":0.0,"scale":{"x":1.0,"y":1.0}},
{"file_id":"ground_sidewalk_left_12","type":"terrain","asset_id":"ground","position":{"x":-2304.0,"y":0.0},"rotation":0.0,"scale":{"x":1.0,"y":1.0},"finish_id":"finish_ground_sidewalk"},
{"file_id":"ground_sidewalk_left_11","type":"terrain","asset_id":"ground","position":{"x":-2112.0,"y":0.0},"rotation":0.0,"scale":{"x":1.0,"y":1.0},"finish_id":"finish_ground_sidewalk"},
{"file_id":"ground_sidewalk_left_10","type":"terrain","asset_id":"ground","position":{"x":-1920.0,"y":0.0},"rotation":0.0,"scale":{"x":1.0,"y":1.0},"finish_id":"finish_ground_sidewalk"},
{"file_id":"ground_sidewalk_left_09","type":"terrain","asset_id":"ground","position":{"x":-1728.0,"y":0.0},"rotation":0.0,"scale":{"x":1.0,"y":1.0},"finish_id":"finish_ground_sidewalk"},
{"file_id":"ground_sidewalk_left_08","type":"terrain","asset_id":"ground","position":{"x":-1536.0,"y":0.0},"rotation":0.0,"scale":{"x":1.0,"y":1.0},"finish_id":"finish_ground_sidewalk"},
{"file_id":"ground_sidewalk_left_07","type":"terrain","asset_id":"ground","position":{"x":-1344.0,"y":0.0},"rotation":0.0,"scale":{"x":1.0,"y":1.0},"finish_id":"finish_ground_sidewalk"},
{"file_id":"ground_sidewalk_left_06","type":"terrain","asset_id":"ground","position":{"x":-1152.0,"y":0.0},"rotation":0.0,"scale":{"x":1.0,"y":1.0},"finish_id":"finish_ground_sidewalk"},
{"file_id":"ground_sidewalk_left_05","type":"terrain","asset_id":"ground","position":{"x":-960.0,"y":0.0},"rotation":0.0,"scale":{"x":1.0,"y":1.0},"finish_id":"finish_ground_sidewalk"},
{"file_id":"ground_sidewalk_left_04","type":"terrain","asset_id":"ground","position":{"x":-768.0,"y":0.0},"rotation":0.0,"scale":{"x":1.0,"y":1.0},"finish_id":"finish_ground_sidewalk"},
{"file_id":"ground_sidewalk_left_03","type":"terrain","asset_id":"ground","position":{"x":-576.0,"y":0.0},"rotation":0.0,"scale":{"x":1.0,"y":1.0},"finish_id":"finish_ground_sidewalk"},
{"file_id":"ground_sidewalk_left_02","type":"terrain","asset_id":"ground","position":{"x":-384.0,"y":0.0},"rotation":0.0,"scale":{"x":1.0,"y":1.0},"finish_id":"finish_ground_sidewalk"},
{"file_id":"ground_sidewalk_left_01","type":"terrain","asset_id":"ground","position":{"x":-192.0,"y":0.0},"rotation":0.0,"scale":{"x":1.0,"y":1.0},"finish_id":"finish_ground_sidewalk"},
{"file_id":"ground_sidewalk","type":"terrain","asset_id":"ground","position":{"x":0.0,"y":0.0},"rotation":0.0,"scale":{"x":1.0,"y":1.0},"finish_id":"finish_ground_sidewalk"},
{"file_id":"ground_sidewalk_right_01","type":"terrain","asset_id":"ground","position":{"x":192.0,"y":0.0},"rotation":0.0,"scale":{"x":1.0,"y":1.0},"finish_id":"finish_ground_sidewalk"},
{"file_id":"ground_sidewalk_right_02","type":"terrain","asset_id":"ground","position":{"x":384.0,"y":0.0},"rotation":0.0,"scale":{"x":1.0,"y":1.0},"finish_id":"finish_ground_sidewalk"},
{"file_id":"ground_sidewalk_right_03","type":"terrain","asset_id":"ground","position":{"x":576.0,"y":0.0},"rotation":0.0,"scale":{"x":1.0,"y":1.0},"finish_id":"finish_ground_sidewalk"},
{"file_id":"ground_sidewalk_right_04","type":"terrain","asset_id":"ground","position":{"x":768.0,"y":0.0},"rotation":0.0,"scale":{"x":1.0,"y":1.0},"finish_id":"finish_ground_sidewalk"},
{"file_id":"ground_sidewalk_right_05","type":"terrain","asset_id":"ground","position":{"x":960.0,"y":0.0},"rotation":0.0,"scale":{"x":1.0,"y":1.0},"finish_id":"finish_ground_sidewalk"},
{"file_id":"ground_sidewalk_right_06","type":"terrain","asset_id":"ground","position":{"x":1152.0,"y":0.0},"rotation":0.0,"scale":{"x":1.0,"y":1.0},"finish_id":"finish_ground_sidewalk"},
{"file_id":"ground_sidewalk_right_07","type":"terrain","asset_id":"ground","position":{"x":1344.0,"y":0.0},"rotation":0.0,"scale":{"x":1.0,"y":1.0},"finish_id":"finish_ground_sidewalk"},
{"file_id":"ground_sidewalk_right_08","type":"terrain","asset_id":"ground","position":{"x":1536.0,"y":0.0},"rotation":0.0,"scale":{"x":1.0,"y":1.0},"finish_id":"finish_ground_sidewalk"},
{"file_id":"ground_sidewalk_right_09","type":"terrain","asset_id":"ground","position":{"x":1728.0,"y":0.0},"rotation":0.0,"scale":{"x":1.0,"y":1.0},"finish_id":"finish_ground_sidewalk"},
{"file_id":"ground_sidewalk_right_10","type":"terrain","asset_id":"ground","position":{"x":1920.0,"y":0.0},"rotation":0.0,"scale":{"x":1.0,"y":1.0},"finish_id":"finish_ground_sidewalk"},
{"file_id":"ground_sidewalk_right_11","type":"terrain","asset_id":"ground","position":{"x":2112.0,"y":0.0},"rotation":0.0,"scale":{"x":1.0,"y":1.0},"finish_id":"finish_ground_sidewalk"},
{"file_id":"ground_sidewalk_right_12","type":"terrain","asset_id":"ground","position":{"x":2304.0,"y":0.0},"rotation":0.0,"scale":{"x":1.0,"y":1.0},"finish_id":"finish_ground_sidewalk"},
{"file_id":"ground_sidewalk_left_13","type":"terrain","asset_id":"ground","position":{"x":-2496.0,"y":0.0},"rotation":0.0,"scale":{"x":1.0,"y":1.0},"finish_id":"finish_ground_sidewalk"},
{"file_id":"ground_sidewalk_left_14","type":"terrain","asset_id":"ground","position":{"x":-2688.0,"y":0.0},"rotation":0.0,"scale":{"x":1.0,"y":1.0},"finish_id":"finish_ground_sidewalk"},
{"file_id":"ground_sidewalk_left_15","type":"terrain","asset_id":"ground","position":{"x":-2880.0,"y":0.0},"rotation":0.0,"scale":{"x":1.0,"y":1.0},"finish_id":"finish_ground_sidewalk"},
{"file_id":"ground_sidewalk_left_16","type":"terrain","asset_id":"ground","position":{"x":-3072.0,"y":0.0},"rotation":0.0,"scale":{"x":1.0,"y":1.0},"finish_id":"finish_ground_sidewalk"},
{"file_id":"ground_sidewalk_left_17","type":"terrain","asset_id":"ground","position":{"x":-3264.0,"y":0.0},"rotation":0.0,"scale":{"x":1.0,"y":1.0},"finish_id":"finish_ground_sidewalk"},
{"file_id":"ground_sidewalk_left_18","type":"terrain","asset_id":"ground","position":{"x":-3456.0,"y":0.0},"rotation":0.0,"scale":{"x":1.0,"y":1.0},"finish_id":"finish_ground_sidewalk"},
{"file_id":"ground_sidewalk_left_19","type":"terrain","asset_id":"ground","position":{"x":-3648.0,"y":0.0},"rotation":0.0,"scale":{"x":1.0,"y":1.0},"finish_id":"finish_ground_sidewalk"},
{"file_id":"ground_sidewalk_left_20","type":"terrain","asset_id":"ground","position":{"x":-3840.0,"y":0.0},"rotation":0.0,"scale":{"x":1.0,"y":1.0},"finish_id":"finish_ground_sidewalk"},
{"file_id":"ground_sidewalk_right_13","type":"terrain","asset_id":"ground","position":{"x":2496.0,"y":0.0},"rotation":0.0,"scale":{"x":1.0,"y":1.0},"finish_id":"finish_ground_sidewalk"},
{"file_id":"ground_sidewalk_right_14","type":"terrain","asset_id":"ground","position":{"x":2688.0,"y":0.0},"rotation":0.0,"scale":{"x":1.0,"y":1.0},"finish_id":"finish_ground_sidewalk"},
{"file_id":"ground_sidewalk_right_15","type":"terrain","asset_id":"ground","position":{"x":2880.0,"y":0.0},"rotation":0.0,"scale":{"x":1.0,"y":1.0},"finish_id":"finish_ground_sidewalk"},
{"file_id":"ground_sidewalk_right_16","type":"terrain","asset_id":"ground","position":{"x":3072.0,"y":0.0},"rotation":0.0,"scale":{"x":1.0,"y":1.0},"finish_id":"finish_ground_sidewalk"},
{"file_id":"ground_sidewalk_right_17","type":"terrain","asset_id":"ground","position":{"x":3264.0,"y":0.0},"rotation":0.0,"scale":{"x":1.0,"y":1.0},"finish_id":"finish_ground_sidewalk"},
{"file_id":"ground_sidewalk_right_18","type":"terrain","asset_id":"ground","position":{"x":3456.0,"y":0.0},"rotation":0.0,"scale":{"x":1.0,"y":1.0},"finish_id":"finish_ground_sidewalk"},
{"file_id":"ground_sidewalk_right_19","type":"terrain","asset_id":"ground","position":{"x":3648.0,"y":0.0},"rotation":0.0,"scale":{"x":1.0,"y":1.0},"finish_id":"finish_ground_sidewalk"},
{"file_id":"ground_sidewalk_right_20","type":"terrain","asset_id":"ground","position":{"x":3840.0,"y":0.0},"rotation":0.0,"scale":{"x":1.0,"y":1.0},"finish_id":"finish_ground_sidewalk"},
{"file_id":"ground_asphalt_left_12","type":"terrain","asset_id":"ground","position":{"x":-2304.0,"y":32.0},"rotation":0.0,"scale":{"x":1.0,"y":1.0},"finish_id":"finish_ground_asphalt"},
{"file_id":"ground_asphalt_left_11","type":"terrain","asset_id":"ground","position":{"x":-2112.0,"y":32.0},"rotation":0.0,"scale":{"x":1.0,"y":1.0},"finish_id":"finish_ground_asphalt"},
{"file_id":"ground_asphalt_left_10","type":"terrain","asset_id":"ground","position":{"x":-1920.0,"y":32.0},"rotation":0.0,"scale":{"x":1.0,"y":1.0},"finish_id":"finish_ground_asphalt"},
{"file_id":"ground_asphalt_left_09","type":"terrain","asset_id":"ground","position":{"x":-1728.0,"y":32.0},"rotation":0.0,"scale":{"x":1.0,"y":1.0},"finish_id":"finish_ground_asphalt"},
{"file_id":"ground_asphalt_left_08","type":"terrain","asset_id":"ground","position":{"x":-1536.0,"y":32.0},"rotation":0.0,"scale":{"x":1.0,"y":1.0},"finish_id":"finish_ground_asphalt"},
{"file_id":"ground_asphalt_left_07","type":"terrain","asset_id":"ground","position":{"x":-1344.0,"y":32.0},"rotation":0.0,"scale":{"x":1.0,"y":1.0},"finish_id":"finish_ground_asphalt"},
{"file_id":"ground_asphalt_left_06","type":"terrain","asset_id":"ground","position":{"x":-1152.0,"y":32.0},"rotation":0.0,"scale":{"x":1.0,"y":1.0},"finish_id":"finish_ground_asphalt"},
{"file_id":"ground_asphalt_left_05","type":"terrain","asset_id":"ground","position":{"x":-960.0,"y":32.0},"rotation":0.0,"scale":{"x":1.0,"y":1.0},"finish_id":"finish_ground_asphalt"},
{"file_id":"ground_asphalt_left_04","type":"terrain","asset_id":"ground","position":{"x":-768.0,"y":32.0},"rotation":0.0,"scale":{"x":1.0,"y":1.0},"finish_id":"finish_ground_asphalt"},
{"file_id":"ground_asphalt_left_03","type":"terrain","asset_id":"ground","position":{"x":-576.0,"y":32.0},"rotation":0.0,"scale":{"x":1.0,"y":1.0},"finish_id":"finish_ground_asphalt"},
{"file_id":"ground_asphalt_left_02","type":"terrain","asset_id":"ground","position":{"x":-384.0,"y":32.0},"rotation":0.0,"scale":{"x":1.0,"y":1.0},"finish_id":"finish_ground_asphalt"},
{"file_id":"ground_asphalt_left_01","type":"terrain","asset_id":"ground","position":{"x":-192.0,"y":32.0},"rotation":0.0,"scale":{"x":1.0,"y":1.0},"finish_id":"finish_ground_asphalt"},
{"file_id":"ground_asphalt","type":"terrain","asset_id":"ground","position":{"x":0.0,"y":32.0},"rotation":0.0,"scale":{"x":1.0,"y":1.0},"finish_id":"finish_ground_asphalt"},
{"file_id":"ground_asphalt_right_01","type":"terrain","asset_id":"ground","position":{"x":192.0,"y":32.0},"rotation":0.0,"scale":{"x":1.0,"y":1.0},"finish_id":"finish_ground_asphalt"},
{"file_id":"ground_asphalt_right_02","type":"terrain","asset_id":"ground","position":{"x":384.0,"y":32.0},"rotation":0.0,"scale":{"x":1.0,"y":1.0},"finish_id":"finish_ground_asphalt"},
{"file_id":"ground_asphalt_right_03","type":"terrain","asset_id":"ground","position":{"x":576.0,"y":32.0},"rotation":0.0,"scale":{"x":1.0,"y":1.0},"finish_id":"finish_ground_asphalt"},
{"file_id":"ground_asphalt_right_04","type":"terrain","asset_id":"ground","position":{"x":768.0,"y":32.0},"rotation":0.0,"scale":{"x":1.0,"y":1.0},"finish_id":"finish_ground_asphalt"},
{"file_id":"ground_asphalt_right_05","type":"terrain","asset_id":"ground","position":{"x":960.0,"y":32.0},"rotation":0.0,"scale":{"x":1.0,"y":1.0},"finish_id":"finish_ground_asphalt"},
{"file_id":"ground_asphalt_right_06","type":"terrain","asset_id":"ground","position":{"x":1152.0,"y":32.0},"rotation":0.0,"scale":{"x":1.0,"y":1.0},"finish_id":"finish_ground_asphalt"},
{"file_id":"ground_asphalt_right_07","type":"terrain","asset_id":"ground","position":{"x":1344.0,"y":32.0},"rotation":0.0,"scale":{"x":1.0,"y":1.0},"finish_id":"finish_ground_asphalt"},
{"file_id":"ground_asphalt_right_08","type":"terrain","asset_id":"ground","position":{"x":1536.0,"y":32.0},"rotation":0.0,"scale":{"x":1.0,"y":1.0},"finish_id":"finish_ground_asphalt"},
{"file_id":"ground_asphalt_right_09","type":"terrain","asset_id":"ground","position":{"x":1728.0,"y":32.0},"rotation":0.0,"scale":{"x":1.0,"y":1.0},"finish_id":"finish_ground_asphalt"},
{"file_id":"ground_asphalt_right_10","type":"terrain","asset_id":"ground","position":{"x":1920.0,"y":32.0},"rotation":0.0,"scale":{"x":1.0,"y":1.0},"finish_id":"finish_ground_asphalt"},
{"file_id":"ground_asphalt_right_11","type":"terrain","asset_id":"ground","position":{"x":2112.0,"y":32.0},"rotation":0.0,"scale":{"x":1.0,"y":1.0},"finish_id":"finish_ground_asphalt"},
{"file_id":"ground_asphalt_right_12","type":"terrain","asset_id":"ground","position":{"x":2304.0,"y":32.0},"rotation":0.0,"scale":{"x":1.0,"y":1.0},"finish_id":"finish_ground_asphalt"},
{"file_id":"ground_asphalt_left_extension","type":"terrain","asset_id":"ground","position":{"x":-2496.0,"y":32.0},"rotation":0.0,"scale":{"x":1.0,"y":1.0},"finish_id":"finish_ground_asphalt"},
{"file_id":"ground_asphalt_right_extension","type":"terrain","asset_id":"ground","position":{"x":2496.0,"y":32.0},"rotation":0.0,"scale":{"x":1.0,"y":1.0},"finish_id":"finish_ground_asphalt"},
{"file_id":"ground_asphalt_left_14","type":"terrain","asset_id":"ground","position":{"x":-2688.0,"y":32.0},"rotation":0.0,"scale":{"x":1.0,"y":1.0},"finish_id":"finish_ground_asphalt"},
{"file_id":"ground_asphalt_left_15","type":"terrain","asset_id":"ground","position":{"x":-2880.0,"y":32.0},"rotation":0.0,"scale":{"x":1.0,"y":1.0},"finish_id":"finish_ground_asphalt"},
{"file_id":"ground_asphalt_left_16","type":"terrain","asset_id":"ground","position":{"x":-3072.0,"y":32.0},"rotation":0.0,"scale":{"x":1.0,"y":1.0},"finish_id":"finish_ground_asphalt"},
{"file_id":"ground_asphalt_left_17","type":"terrain","asset_id":"ground","position":{"x":-3264.0,"y":32.0},"rotation":0.0,"scale":{"x":1.0,"y":1.0},"finish_id":"finish_ground_asphalt"},
{"file_id":"ground_asphalt_left_18","type":"terrain","asset_id":"ground","position":{"x":-3456.0,"y":32.0},"rotation":0.0,"scale":{"x":1.0,"y":1.0},"finish_id":"finish_ground_asphalt"},
{"file_id":"ground_asphalt_left_19","type":"terrain","asset_id":"ground","position":{"x":-3648.0,"y":32.0},"rotation":0.0,"scale":{"x":1.0,"y":1.0},"finish_id":"finish_ground_asphalt"},
{"file_id":"ground_asphalt_left_20","type":"terrain","asset_id":"ground","position":{"x":-3840.0,"y":32.0},"rotation":0.0,"scale":{"x":1.0,"y":1.0},"finish_id":"finish_ground_asphalt"},
{"file_id":"ground_asphalt_right_14","type":"terrain","asset_id":"ground","position":{"x":2688.0,"y":32.0},"rotation":0.0,"scale":{"x":1.0,"y":1.0},"finish_id":"finish_ground_asphalt"},
{"file_id":"ground_asphalt_right_15","type":"terrain","asset_id":"ground","position":{"x":2880.0,"y":32.0},"rotation":0.0,"scale":{"x":1.0,"y":1.0},"finish_id":"finish_ground_asphalt"},
{"file_id":"ground_asphalt_right_16","type":"terrain","asset_id":"ground","position":{"x":3072.0,"y":32.0},"rotation":0.0,"scale":{"x":1.0,"y":1.0},"finish_id":"finish_ground_asphalt"},
{"file_id":"ground_asphalt_right_17","type":"terrain","asset_id":"ground","position":{"x":3264.0,"y":32.0},"rotation":0.0,"scale":{"x":1.0,"y":1.0},"finish_id":"finish_ground_asphalt"},
{"file_id":"ground_asphalt_right_18","type":"terrain","asset_id":"ground","position":{"x":3456.0,"y":32.0},"rotation":0.0,"scale":{"x":1.0,"y":1.0},"finish_id":"finish_ground_asphalt"},
{"file_id":"ground_asphalt_right_19","type":"terrain","asset_id":"ground","position":{"x":3648.0,"y":32.0},"rotation":0.0,"scale":{"x":1.0,"y":1.0},"finish_id":"finish_ground_asphalt"},
{"file_id":"ground_asphalt_right_20","type":"terrain","asset_id":"ground","position":{"x":3840.0,"y":32.0},"rotation":0.0,"scale":{"x":1.0,"y":1.0},"finish_id":"finish_ground_asphalt"}
],
"event_rules": [],
"metadata": {"created_unix": 1792022400, "modified_unix": 1792022400, "autosave": false, "source_path": ""}
}
-297
View File
@@ -1,297 +0,0 @@
{
"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": ""
}
}
-546
View File
@@ -1,546 +0,0 @@
{
"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
@@ -1,322 +0,0 @@
{
"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
@@ -1,303 +0,0 @@
{
"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": ""
}
}
-15
View File
@@ -1,15 +0,0 @@
# 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/stage/sandbox_stage.tscn`). It is loaded by
the Sandbox Stage (`res://scenes/sandbox_stage.tscn`). It is loaded by
`SandboxStage` during `_ready()`, before the code-built UI is created. The
loader is `res://scripts/stage/sandbox_theme.gd` (`class_name SandboxTheme`).
loader is `res://scripts/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.
@@ -1,87 +0,0 @@
# Patterned scenery rendering
A patterned wall or background uses one small repeating image for each finish and tint. The camera samples that same image wherever the surface appears. A pan or zoom changes the visible rectangle and sampling scale; it does not create a new image for each camera position.
![Patterned scenery render and cache pipeline](diagrams/background_pipeline.svg)
## Render pipeline
1. A visible, committed patterned surface requests its style from the scene's `StageSurfaceBakePool`.
2. The cache checks the style key. A ready entry is reused; a missing entry is queued for the shared baker.
3. At most one queued style is baked per process frame. The baker draws a four-world-unit overscan around the material period so edge details can be cropped without gaps.
4. After the submitted drawing reaches `frame_post_draw`, the pool captures the image, crops the period, generates mipmaps, and publishes an `ImageTexture`.
5. The temporary vector commands are cleared. The surface draws one camera-clipped quad using the shared image, repeat sampling, and mipmapped filtering.
The GPU still renders the textured rectangle every frame. When the view changes, the CPU also updates that rectangle and its texture coordinates. This is a small drawing command; it does not rebuild brick faces, grain, pores, or other vector details. The cache counters distinguish image builds and readbacks from ordinary redraws.
## Shared style images
The cache is owned by a scene-scoped pool. Its key includes finish, tint, wall/background kind, material version, repeat period, pixel density, and mipmap configuration. It excludes surface position, camera position, zoom, rectangle size, and erase fragment. Thus same-style surfaces and fragments share one image, while a tint change correctly requests a different image.
![Many surfaces sharing one finished material image](diagrams/background_reuse.svg)
The current periods are finite, seamless repeat units baked at two bitmap pixels per world unit:
| Finish | Repeat period in world units | Bitmap size |
| --- | ---: | ---: |
| Brick | 800 × 304 | 1600 × 608 |
| Wood | 960 × 384 | 1920 × 768 |
| Grass | 704 × 576 | 1408 × 1152 |
| Carpet | 544 × 416 | 1088 × 832 |
| Concrete | 1120 × 760 | 2240 × 1520 |
For example, a 100-world-unit brick remains 100 world units wide. At 0.4× zoom it covers about 40 screen pixels; zoom changes how the GPU samples the image, not the brick geometry. The finite repeat unit makes detail bounded, so landmarks can become noticeable on very large surfaces.
## Zoom and mipmaps
The renderer captures the material once at fixed density, then builds smaller mip levels from that image. During zoom-out the GPU samples a smaller level to reduce shimmer; during zoom-in it samples a larger level. This replaces separately drawing the material at several CPU-selected zoom levels. At the Stage's maximum 6× zoom, the source image is magnified three times and can look softer. The current quality/performance tradeoff is fixed in the cache configuration.
![Fixed material image with mipmap levels](diagrams/background_mipmaps.svg)
## Erasing and pattern alignment
The eraser still cuts a rectangle into up to four rectangular fragments. Each fragment draws only its own bounds, but requests the same style image; it does not bake a fragment-specific copy. Cancel restores the original geometry, and Commit retains the new fragments. Both use the existing material sampling rules.
The sampling origin is significant: walls use `global_position`; background ground uses `global_position + motif_origin_offset`. Each surface computes its source UV from its local visible position plus that draw origin, wrapped by the material period. This preserves the current alignment convention across cuts. Avoid changing the origin rule as a generic transform correction: unusual transformed fragments need separate verification before making a broader alignment claim.
![Erase fragments clipping the same repeating image](diagrams/background_erase.svg)
## Memory and rebuild boundaries
The scene cache has a 64 MiB resident texture budget. Accounting includes mip levels, textures waiting for safe retirement, and reserved space for a capture. Unused entries are evicted by least-recent use when needed. A style held by active surfaces can prevent admission of another style; the pool reports this as budget pressure rather than exceeding the cap. Under ordinary use, returning to a recently used style should reuse its warm image. Many tint variants can force eviction and a later rebuild.
A style's first capture still costs CPU/GPU work and can briefly affect a frame. Once ready, moving the camera across a large wall does not enqueue position tiles or read back new images. The shared baker also clears its vector command list after capture, preventing the native drawing resources that previously accumulated in many per-tile viewports.
These images are temporary runtime data. A saved `.scene` keeps surface settings and geometry; the cache recreates its images when needed in a new Stage session. Closing the scene releases its pool.
## What causes another bake?
| Action | New image work? |
| --- | --- |
| Pan left, then return right | No, while the style remains cached. |
| Zoom in or out | No. The GPU samples existing mipmaps. |
| Resize a wall | No. Change the rectangle and texture coordinates. |
| Erase, Cancel, or Commit | No, if the style is ready. Change geometry and clipping. |
| Use the same finish and tint on another wall | No, if the style is ready. Share its image. |
| Choose a new finish or tint | Bake once if that style is missing. |
| Return to an evicted style, or open a fresh session | Recreate the missing image. |
## Terms used here
- **Bake:** render the vector artwork once to make an image.
- **Texture:** the image the GPU samples when drawing.
- **Quad:** one rectangle, represented by two triangles.
- **UVs:** coordinates that select which part of a texture to sample.
- **Mipmaps:** smaller copies of an image, prepared in advance for zooming out.
- **Readback:** copy the rendered image from the GPU so the cache can keep an independent texture.
## Scope and code map
The bitmap cache is for committed patterned walls and backgrounds in render-capable runs. Plain paint surfaces, interactive placement previews, and headless runs retain the direct renderer path. Fully opaque scenery can suppress a fully hidden surface's texture request; partial coverage remains conservative.
- [StageSurfaceCache](../../scripts/stage/scenery/stage_surface_cache.gd) owns style handles, the scene pool, bake queue, image capture, mipmaps, cache keys, and budget statistics.
- [StageRectSurface](../../scripts/stage/scenery/stage_rect_surface.gd) requests the style, clips the surface to the visible camera area, and draws the repeated texture.
- [StageSurfaceRenderer](../../scripts/stage/scenery/stage_surface_renderer.gd) defines patterned artwork, material periods, and the direct-drawing path.
- [Repeating scenery material cache spec](../specs/scenery_bitmap_tile_cache.md) records implementation constraints and acceptance checks.
In one observed warm run of `fps.scene`, four style entries used 36,399,144 bytes (about 34.71 MiB); 720 warm camera frames recorded zero builds, readbacks, or evictions. This is a run snapshot, not a guarantee for other scenes, tint combinations, or hardware.
@@ -1,43 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="1200" height="640" viewBox="0 0 1200 640" role="img" aria-labelledby="title desc">
<title id="title">Erasing changes rectangles while sharing the same background texture</title>
<desc id="desc">A rectangular cut turns one wall into top, bottom, left and right fragments. All fragments sample the existing image using their pattern coordinates. The image itself is not painted over or copied.</desc>
<defs>
<pattern id="brick" width="80" height="48" patternUnits="userSpaceOnUse"><rect width="80" height="48" fill="#d2cbaf"/><rect x="2" y="2" width="36" height="19" fill="#25b8df"/><rect x="42" y="2" width="36" height="19" fill="#39c7ee"/><rect x="-18" y="26" width="36" height="19" fill="#32c2e8"/><rect x="22" y="26" width="36" height="19" fill="#21b4da"/><rect x="62" y="26" width="36" height="19" fill="#32c2e8"/></pattern>
<marker id="arrow" markerWidth="10" markerHeight="10" refX="8" refY="5" orient="auto"><path d="M0 0L10 5L0 10Z" fill="#475569"/></marker>
<style>text{font-family:Segoe UI,Arial,sans-serif;fill:#172033}.title{font-size:30px;font-weight:700}.heading{font-size:22px;font-weight:700}.body{font-size:18px}.small{font-size:16px}.arrow{fill:none;stroke:#475569;stroke-width:3;marker-end:url(#arrow)}</style>
</defs>
<rect width="1200" height="640" rx="16" fill="#f8fafc"/>
<text x="40" y="53" class="title">Erase the wall geometry. Keep the shared image.</text>
<text x="40" y="86" class="body">Fragments crop their own rectangles and use the existing pattern coordinates.</text>
<text x="50" y="136" class="heading">Before: one wall rectangle</text>
<g transform="translate(50 160)">
<rect width="460" height="300" fill="url(#brick)" stroke="#94a3b8" stroke-width="2"/>
<rect x="155" y="90" width="150" height="120" fill="#fff" fill-opacity=".3" stroke="#b45309" stroke-width="4" stroke-dasharray="10 6"/>
<rect x="174" y="131" width="112" height="36" rx="6" fill="#fff7ed"/>
<text x="230" y="155" class="body" text-anchor="middle">Cut here</text>
</g>
<path d="M532 310H648" class="arrow"/>
<text x="590" y="283" class="small" text-anchor="middle">Erase area</text>
<text x="680" y="136" class="heading">After: up to four fragments</text>
<g transform="translate(680 160)">
<rect width="460" height="300" fill="#dbeafe"/>
<g fill="url(#brick)" stroke="#0f766e" stroke-width="3">
<rect width="460" height="90"/>
<rect x="0" y="210" width="460" height="90"/>
<rect x="0" y="90" width="155" height="120"/>
<rect x="305" y="90" width="155" height="120"/>
</g>
<g fill="#fff"><rect x="195" y="26" width="70" height="36" rx="6"/><rect x="182" y="242" width="96" height="36" rx="6"/><rect x="43" y="131" width="70" height="36" rx="6"/><rect x="342" y="131" width="78" height="36" rx="6"/></g>
<text x="230" y="51" class="body" text-anchor="middle">Top</text>
<text x="230" y="267" class="body" text-anchor="middle">Bottom</text>
<text x="78" y="155" class="body" text-anchor="middle">Left</text>
<text x="381" y="155" class="body" text-anchor="middle">Right</text>
<text x="230" y="143" class="body" text-anchor="middle">Empty area</text>
<text x="230" y="173" class="small" text-anchor="middle">Scene behind</text>
</g>
<rect x="40" y="510" width="1120" height="95" rx="12" fill="#eff6ff" stroke="#2563eb" stroke-width="2"/>
<text x="62" y="546" class="heading">All remaining pieces still use the same cached texture.</text>
<text x="62" y="580" class="body">Cancel restores the original geometry. Commit keeps the fragments. Neither paints into the image.</text>
</svg>

Before

Width:  |  Height:  |  Size: 3.6 KiB

@@ -1,39 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="1200" height="630" viewBox="0 0 1200 630" role="img" aria-labelledby="title desc">
<title id="title">Zoom uses smaller copies already stored in the material texture</title>
<desc id="desc">The brick material starts at 1600 by 608 bitmap pixels. Its mip chain includes 800 by 304, 400 by 152 and 200 by 76, continuing down to one pixel. These copies are prepared once. Brick world dimensions stay fixed.</desc>
<defs>
<pattern id="brick" width="100" height="38" patternUnits="userSpaceOnUse"><rect width="100" height="38" fill="#d2cbaf"/><rect x="2" y="2" width="46" height="15" fill="#26b8df"/><rect x="52" y="2" width="46" height="15" fill="#39c7ee"/><rect x="-23" y="21" width="46" height="15" fill="#32c2e8"/><rect x="27" y="21" width="46" height="15" fill="#21b4da"/><rect x="77" y="21" width="46" height="15" fill="#32c2e8"/></pattern>
<pattern id="half" xlink:href="#brick" patternTransform="scale(.5)"/>
<pattern id="quarter" xlink:href="#brick" patternTransform="scale(.25)"/>
<pattern id="eighth" xlink:href="#brick" patternTransform="scale(.125)"/>
<marker id="arrow" markerWidth="10" markerHeight="10" refX="8" refY="5" orient="auto"><path d="M0 0L10 5L0 10Z" fill="#7c3aed"/></marker>
<style>text{font-family:Segoe UI,Arial,sans-serif;fill:#172033}.title{font-size:30px;font-weight:700}.heading{font-size:22px;font-weight:700}.body{font-size:18px}.small{font-size:16px}.arrow{fill:none;stroke:#7c3aed;stroke-width:3;marker-end:url(#arrow)}</style>
</defs>
<rect width="1200" height="630" rx="16" fill="#f8fafc"/>
<text x="40" y="53" class="title">Zoom samples ready-made sizes of the same image.</text>
<text x="40" y="86" class="body">Mipmaps are generated once with the material. The GPU selects and blends them during rendering.</text>
<rect x="60" y="150" width="400" height="152" fill="url(#brick)" stroke="#94a3b8"/>
<path d="M471 228H508" class="arrow"/>
<rect x="530" y="188" width="200" height="76" fill="url(#half)" stroke="#94a3b8"/>
<path d="M741 228H778" class="arrow"/>
<rect x="800" y="207" width="100" height="38" fill="url(#quarter)" stroke="#94a3b8"/>
<path d="M911 228H948" class="arrow"/>
<rect x="970" y="217" width="50" height="19" fill="url(#eighth)" stroke="#94a3b8"/>
<text x="1043" y="234" class="heading">…</text>
<text x="260" y="342" class="heading" text-anchor="middle">1600 × 608</text>
<text x="260" y="373" class="small" text-anchor="middle">Base image: 2 pixels / world unit</text>
<text x="630" y="342" class="heading" text-anchor="middle">800 × 304</text>
<text x="630" y="373" class="small" text-anchor="middle">Half size</text>
<text x="850" y="342" class="heading" text-anchor="middle">400 × 152</text>
<text x="850" y="373" class="small" text-anchor="middle">Quarter size</text>
<text x="1000" y="342" class="heading" text-anchor="middle">200 × 76</text>
<text x="1000" y="373" class="small" text-anchor="middle">Eighth size</text>
<text x="60" y="414" class="small">The actual chain continues down to 1 × 1. Diagram sizes are scaled for illustration.</text>
<rect x="40" y="450" width="1120" height="143" rx="12" fill="#ecfdf5" stroke="#0f766e" stroke-width="2"/>
<text x="62" y="486" class="heading">World size, image size and screen size are different things.</text>
<text x="62" y="521" class="body">A brick stays 100 world units wide. At 40% zoom, it covers about 40 screen pixels.</text>
<text x="62" y="556" class="body">Camera zoom changes sampling detail; it does not bake another image or resize the brick geometry.</text>
</svg>

Before

Width:  |  Height:  |  Size: 3.6 KiB

@@ -1,51 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="1200" height="640" viewBox="0 0 1200 640" role="img" aria-labelledby="title desc">
<title id="title">Background rendering: prepare a style once, reuse it while moving the camera</title>
<desc id="desc">A visible surface requests its finish and tint from the scene cache. A cache hit goes directly to the GPU repeat sampler. A cache miss uses one temporary baker, captures an image and mipmaps, and releases vector commands.</desc>
<defs>
<marker id="arrow" markerWidth="10" markerHeight="10" refX="8" refY="5" orient="auto"><path d="M0 0L10 5L0 10Z" fill="#475569"/></marker>
<style>text{font-family:Segoe UI,Arial,sans-serif;fill:#172033}.title{font-size:30px;font-weight:700}.heading{font-size:22px;font-weight:700}.body{font-size:18px}.small{font-size:16px}.arrow{fill:none;stroke:#475569;stroke-width:3;marker-end:url(#arrow)}</style>
</defs>
<rect width="1200" height="640" rx="16" fill="#f8fafc"/>
<text x="40" y="53" class="title">Prepare a material once. Reuse it as the camera moves.</text>
<text x="40" y="86" class="body">Only visible, committed patterned scenery requests a cached material.</text>
<rect x="40" y="140" width="250" height="126" rx="12" fill="#eff6ff" stroke="#2563eb" stroke-width="2"/>
<text x="60" y="177" class="heading">Visible surface</text>
<text x="60" y="210" class="body">Finish + tint</text>
<text x="60" y="239" class="small">Example: blue brick</text>
<path d="M292 203H370" class="arrow"/>
<rect x="390" y="140" width="260" height="126" rx="12" fill="#eff6ff" stroke="#2563eb" stroke-width="2"/>
<text x="410" y="177" class="heading">Scene style cache</text>
<text x="410" y="210" class="body">Already have this image?</text>
<text x="410" y="239" class="small">Shared by all matching surfaces</text>
<path d="M652 203H740" class="arrow"/>
<text x="674" y="182" class="small">Hit</text>
<rect x="760" y="140" width="400" height="126" rx="12" fill="#ecfdf5" stroke="#0f766e" stroke-width="2"/>
<text x="782" y="177" class="heading">GPU draws the visible rectangle</text>
<text x="782" y="210" class="body">Repeat texture + sample its mipmaps</text>
<text x="782" y="239" class="small">Pan / zoom updates sampling coordinates</text>
<path d="M520 268V324" class="arrow"/>
<text x="540" y="304" class="small">Miss: prepare this style</text>
<rect x="390" y="344" width="260" height="158" rx="12" fill="#f5f3ff" stroke="#7c3aed" stroke-width="2"/>
<text x="410" y="379" class="heading">One temporary baker</text>
<text x="410" y="412" class="body">Draw one pattern period</text>
<text x="410" y="441" class="body">Wait for GPU; capture image</text>
<text x="410" y="476" class="small">Then clear vector commands</text>
<path d="M652 423H740" class="arrow"/>
<rect x="760" y="344" width="400" height="158" rx="12" fill="#f5f3ff" stroke="#7c3aed" stroke-width="2"/>
<text x="782" y="379" class="heading">Store the reusable texture</text>
<text x="782" y="412" class="body">Crop overscan; generate mipmaps</text>
<text x="782" y="441" class="body">Keep the image, not the vector artwork</text>
<text x="782" y="476" class="small">Ready styles stay warm within the cache budget</text>
<path d="M960 342V282" class="arrow"/>
<text x="980" y="309" class="small">Ready</text>
<rect x="40" y="548" width="540" height="56" rx="10" fill="#dbeafe"/>
<text x="59" y="582" class="body">Blue route: reuse during ordinary pan and zoom</text>
<rect x="604" y="548" width="556" height="56" rx="10" fill="#ede9fe"/>
<text x="623" y="582" class="body">Purple route: bake only when a style is missing</text>
</svg>

Before

Width:  |  Height:  |  Size: 3.6 KiB

@@ -1,44 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="1200" height="650" viewBox="0 0 1200 650" role="img" aria-labelledby="title desc">
<title id="title">Many wall rectangles share one material image</title>
<desc id="desc">One blue-brick image supplies Wall A, Wall B, and an erased fragment. Different sizes, positions, and camera views do not create more images. A different tint or finish is a different cached style.</desc>
<defs>
<pattern id="brick" width="64" height="40" patternUnits="userSpaceOnUse">
<rect width="64" height="40" fill="#d2cbaf"/>
<g><rect x="2" y="2" width="28" height="15" fill="#25b8df"/><rect x="34" y="2" width="28" height="15" fill="#39c7ee"/><rect x="-14" y="22" width="28" height="15" fill="#32c2e8"/><rect x="18" y="22" width="28" height="15" fill="#21b4da"/><rect x="50" y="22" width="28" height="15" fill="#32c2e8"/></g>
</pattern>
<marker id="arrow" markerWidth="10" markerHeight="10" refX="8" refY="5" orient="auto"><path d="M0 0L10 5L0 10Z" fill="#2563eb"/></marker>
<style>text{font-family:Segoe UI,Arial,sans-serif;fill:#172033}.title{font-size:30px;font-weight:700}.heading{font-size:22px;font-weight:700}.body{font-size:18px}.small{font-size:16px}.arrow{fill:none;stroke:#2563eb;stroke-width:3;marker-end:url(#arrow)}</style>
</defs>
<rect width="1200" height="650" rx="16" fill="#f8fafc"/>
<text x="40" y="53" class="title">Wall area can grow. The number of style images stays the same.</text>
<text x="40" y="86" class="body">Position, wall size, camera position and zoom are not part of the style key.</text>
<rect x="400" y="117" width="400" height="163" rx="12" fill="#eff6ff" stroke="#2563eb" stroke-width="2"/>
<rect x="420" y="140" width="160" height="112" fill="url(#brick)" stroke="#94a3b8"/>
<text x="602" y="162" class="heading">One shared image</text>
<text x="602" y="194" class="body">Blue brick</text>
<text x="602" y="224" class="small">Varied brick faces</text>
<text x="602" y="250" class="small">Repeats both ways</text>
<path d="M600 282V311H210V341" class="arrow"/>
<path d="M600 311V341" class="arrow"/>
<path d="M600 311H990V341" class="arrow"/>
<rect x="40" y="360" width="340" height="192" rx="12" fill="#fff" stroke="#cbd5e1" stroke-width="2"/>
<text x="62" y="396" class="heading">Wall A</text>
<rect x="62" y="416" width="296" height="103" fill="url(#brick)"/>
<rect x="430" y="360" width="340" height="192" rx="12" fill="#fff" stroke="#cbd5e1" stroke-width="2"/>
<text x="452" y="396" class="heading">Wall B</text>
<rect x="452" y="416" width="190" height="103" fill="url(#brick)"/>
<text x="659" y="455" class="small">Same</text>
<text x="659" y="480" class="small">style</text>
<rect x="820" y="360" width="340" height="192" rx="12" fill="#fff" stroke="#cbd5e1" stroke-width="2"/>
<text x="842" y="396" class="heading">Erased fragment</text>
<rect x="842" y="416" width="90" height="103" fill="url(#brick)"/>
<text x="952" y="455" class="small">Only the visible</text>
<text x="952" y="480" class="small">rectangle changes</text>
<text x="40" y="591" class="body">A different finish or tint is a separate style. A matching warm style reuses the existing image.</text>
<text x="40" y="621" class="small" fill="#64748b">Patterns shown here are schematic; the actual brick image contains 8 columns × 8 rows of varied artwork.</text>
</svg>

Before

Width:  |  Height:  |  Size: 3.3 KiB

@@ -1,46 +0,0 @@
# 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.
@@ -1,42 +0,0 @@
# Scenery surface caching
`StageRectSurface` renders committed patterned scenery through reusable texture tiles.
Drawing previews use the adaptive lightweight renderer. Paint remains a single rectangle.
## Camera movement
Tiles use a fixed grid in the surface's local coordinates. Texture resolution changes in
powers of two, so adjacent wheel steps reuse existing tiles. Panning requests only newly
exposed tiles. Existing textures stay visible while missing tiles are prepared, with a
lightweight surface motif beneath them to prevent blank areas.
`StageSurfaceCache` keeps queued tiles as metadata. A shared queue creates and renders at
most one tile per process frame across all surfaces. It also retires at most two obsolete
render targets per frame. This avoids large allocations, vector rebuilds, and batches of
GPU resource releases during camera input.
Each tile has a 256 × 128 pixel core and a four pixel gutter. The gutter is cropped during
compositing to prevent seams. Each surface requests at most 192 visible tiles and retains
at most 224 tiles, evicting older unused entries. Half-resolution tiles retain full brick
texture and wood grain. Only quarter-resolution tiles and below omit fine texture, for
distant views. Brick and plank positions remain fixed. Detail can fill in progressively
after movement, including when returning from a distant view to a normal zoom level.
## Invalidation and coverage
Geometry, finish, tint, and motif-origin changes invalidate that surface's tiles. Camera
movement selects tiles without invalidating their artwork. Render targets belong to the
surface node; queued references are weak so deleted surfaces cannot keep jobs alive.
Fully opaque rectangular sibling surfaces can suppress artwork entirely hidden behind
them. Coverage checks account for transforms, draw order, and visibility. Transparent
surfaces, custom materials, differing relative-Z conventions, and Y-sorted parents disable
this optimization conservatively. Revealing a surface resumes its pending tiles.
## Verification
`tests/test_scenery_surface_cache.gd` checks rendered appearance against the direct vector
renderer, tile reuse and invalidation, the shared build limit, allocation and retention
bounds, and covered-surface behavior. Run it with a graphical Godot renderer for pixel
comparisons. The adaptive preview regressions remain in
`tests/test_scenery_surface_preview.gd` and `tests/test_scenery_brick_preview.gd`.
@@ -1,63 +0,0 @@
# 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.
@@ -1,197 +0,0 @@
# 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) |
| 72 | **Stage-wide Undo/Redo needs its own phase** — Phase 19 placement, erase, style, repair, selected-artwork depth, direct single-piece moves/resizes, and group Keep actions change the document without an integrated command history. | Medium | Deferred to separate phase | Design one Stage-level history with coherent commands across Scenery, Ground, Props, terrain, characters, scene naming, and directed content, with safe boundaries around PLAY/paused edits and save/recovery. The Phase 19 sizing amendment keeps single-piece gestures commit-on-release with cancellable active snapshots and retains group Keep/Cancel; neither is an Undo implementation or shortcut. Selected-artwork depth choices also commit immediately. User explicitly deferred Stage Undo/Redo to its own phase. See `docs/specs/phase_19_scenery_backgrounds_surface_finishes.md` and `docs/specs/phase_19_selected_artwork_depth_inspector.md`. (2026-10-17) |
| 73 | **Whole-set transform requires cross-system invariants** — Phase 19's temporary group transform covers only inert Scenery pieces: wall panels, artwork, and the planned Background ground. | Medium | Future feature | Moving/scaling walkable Ground, characters, interactive Props, Sky, camera composition, waypoints, and rules together needs separate physics, navigation, event-target, and save contracts. Do not expand the Scenery group tool into a whole-set transform without that design. See `docs/specs/phase_19_scenery_backgrounds_surface_finishes.md` and `docs/specs/phase_19_background_ground_and_wall_color.md`. (2026-10-17) |
| 74 | **Background surface motifs need a shared zoom-consistent draw budget** — a pattern detail scale derived from each panel's size can shift seams or texture density where adjacent pieces meet, while uncapped large rectangles can issue excessive draw calls. | Medium | Planned | Phase 19 follow-up uses world-cell coordinates, visible-region culling, and any level-of-detail choice derived from camera zoom for all pieces in the same view. Keep Grass/Concrete/Carpet detail deterministic and bounded; consider a texture cache only if measured scenes remain expensive. See `docs/specs/phase_19_background_ground_and_wall_color.md`. (2026-10-17) |
| 75 | **Old Brick/Wood wall colors were saved but not rendered** — v1.5 walls can carry the old default green while their brick and wood palettes were hard-coded. | Low | Planned migration | Scene v1.6 migration replaces only the old green placeholder on pre-v1.6 Brick/Wood entries with natural finish defaults; Paint and non-placeholder colors remain. The exact placeholder could have been intentionally authored in an older file, which cannot be distinguished retrospectively. New v1.6 colors are authoritative, including green Brick/Wood. See `docs/specs/phase_19_background_ground_and_wall_color.md`. (2026-10-17) |
| 76 | **Camera-aware Sky redraws vector motifs while framing changes** — the former static Sky drew motifs only when the preset or viewport changed. | Low | Planned | The Phase 19 Sky addition keeps one screen-space `StageSkyView`, redraws only when a bound camera's effective framing changes, and caches reusable cloud/moon curve samples where practical. Measure sustained pan/zoom cost in a rendered Stage before adding a raster or shader path. The fixed reference frame and bounded response can saturate at distant camera positions; that is an accepted composition trade-off, not scene state to persist. See `docs/specs/phase_19_scenery_backgrounds_surface_finishes.md#phase-19-addition-camera-aware-sky-details`. (2026-10-21) |
| 77 | **Starter surfaces guarantee a finite pan envelope, not arbitrary camera framing** — the two bundled sets use a fixed authored width to keep their scene data simple. | Low | Implemented; visual review pending | The authored envelope is a 1,280-logical-pixel viewport at zoom 0.5, ±1,000 world units of travel, and 100 units of margin per side. User review prompted an opaque Town Grass Background ground and separate scale-1 tile rows. Twenty-five 192-unit tiles cover the 4,760-unit minimum; Asphalt has 27 tiles (5,184 units) so the road extends one tile beyond the sidewalk at both ends. Navigation merges contiguous tile tops and routes the 32-unit curb step at the exposed ends without merging Ground objects or colliders. The focused suite passes 515 assertions, including the curb step and PLAY landing. The camera remains unrestricted, so wider viewports, farther pans, and zoomed-out shots can reveal finite ends. Rendered seam review remains pending. See docs/specs/phase_19_starter_scene_composition.md. (2026-10-21)
| 78 | **Plain Background adds a second backdrop kind while Sky remains a separate renderer** — live Stage, starter previews, and chooser cards must agree on kind and remembered color. | Medium | Spec prepared; implementation pending | Phase 19 composes one shared `StageBackgroundView` from the existing Sky view and an opaque fill. Scene v1.7 stores active kind, last Sky ID, and confirmed plain color; all older scenes migrate as Sky. `SandboxStage` owns the transient custom-picker draft, which must never enter Save or autosave. The existing chooser's four-Sky layout and 42 px compact swatches need targeted extension, and grid/gizmo contrast needs theme-backed treatment for light and dark fills. Rendered pan/zoom/resize, legacy migration, and every draft-cancel exit remain verification gates. See `docs/specs/phase_19_plain_background.md`. (2026-10-22) |
| 79 | **Editor chrome has two existing token sources and scene-level literals** — PropTheme owns current Prop UI values while Stickman scenes hardcode bars, labels, and borders. | Medium | Spec prepared; implementation pending | Add one validated `EditorVisualTheme` source for shared semantic chrome and editor-local `Theme` instances. Retain PropTheme for prop material/primitive/canvas values and keep Stickman canvas drawing tokens in a separate namespace. Migrate legacy Prop `ui` accessors deliberately so a chrome role has one final authority; use SandboxTheme values as a visual reference without a runtime dependency or Stage inspector component reuse. See `docs/specs/editor_visual_harmonization.md`. (2026-10-23) |
| 80 | **Stickman panel nodes currently own both document shapes and local view state** — rebuilding a panel for a focused layout would reset selection, zoom, and pan and risk save/preview drift. | Medium | Spec prepared; implementation pending | Move the same ten BodyPartPanel instances between Overview and Focus hosts, retain one permanent WholeStickmanPreview sibling, and defer switching while a drag or popup is active. The controller owns session-only view choice and active part; manually chosen views survive resize, and narrow Overview remains scrollable. A future document-model extraction is warranted only if new editor features make live-node ownership limiting. See `docs/specs/editor_visual_harmonization.md`. (2026-10-23) |
| 81 | **Stickman authored transforms lack a complete dirty-state boundary** — preview translation emits `part_moved`, while rotation and scale currently have no commit signal; Save/Load/Clear do not track unsaved work. | Medium | Spec prepared; implementation pending | Add a controller-observable transform commit path for position, rotation, and scale, plus `_dirty`/`_current_path` and Save/Discard Changes/Cancel transactions for replacement and close. Keep Overview/Focus, guide, grid, selection, and zoom/pan as session UI state that never marks dirty. Preserve the `.stk` format. See `docs/specs/editor_visual_harmonization.md`. (2026-10-23) |
---
## 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. |
| 2026-10-15 | Phase 19 architecture specification drafted for final user sign-off (`docs/specs/phase_19_scenery_backgrounds_surface_finishes.md`) after approval of live `.prop` references with a repairable missing-art placement, adaptive viewport Sky, and a Stage-hosted Prop Editor route. The design adds inert wall/artwork nodes, vector finishes, scene v1.5 migration, `.prop` v1.1 tags, Scenery-only selection and snapshot group transforms, shared Stage UI generalizations, and ordinary editable starter scenes. Logged #72 for a separate Stage Undo/Redo phase and #73 for a separately designed whole-set transform. No runtime code or assets changed. |
| 2026-10-16 | Added the Phase 19 Stage-relative sizing architecture amendment to `docs/specs/phase_19_scenery_backgrounds_surface_finishes.md` for final sign-off. Prop Editor Size for Stage uses a reversible snapshot, longest-edge presets of 0.35/1/2 standing-stickman heights, origin-based uniform geometry/stroke/editable-anchor scaling, and an editor-only foreground figure/floor comparison. A selected Scenery piece uses its own snapshot transaction and commits on release; a group still uses Keep/Cancel. Existing `.scene` v1.5 placement scale and `.prop` v1.1 geometry need no schema change or load-time normalization. Explicit Save at an existing `.prop` path retains live-reference updates for all users of that source; no asset-version enforcement or new save prompt was requested. Updated #72 for the deferred Stage Undo boundary. The amendment needs no new art and Phase 19 user/editor verification remains pending. No runtime code changed by this specification entry. |
| 2026-10-17 | Wrote the Phase 19 selected-artwork depth inspector specification (`docs/specs/phase_19_selected_artwork_depth_inspector.md`) for final sign-off. It reuses the existing `StageInspectorPanel` and `StagePanelChoiceGroup`, applies Far/Near/On the floor immediately to one live or missing Scenery placement through `StageSceneryArtwork.set_depth()`, and follows the Stage's existing dirty/save path. The current `.scene` depth field, `StageDrawOrder`, and `stage_panels` tokens need no changes. Updated #72 to include immediate depth edits in a future Stage-wide Undo phase. No runtime code changed by this specification entry. |
| 2026-10-17 | Wrote the Phase 19 Background ground and wall color architecture specification (`docs/specs/phase_19_background_ground_and_wall_color.md`) for final sign-off. It defines distinct inert wall/backdrop scene types over a small shared rectangle contract, world-aligned bounded vector materials, selected-piece color preview without mutating saved tint, one eight-color Stage settings list, scene v1.6 migration of old Brick/Wood placeholder green, shared Stage inspector controls, and confirmed single-piece backdrop erasure. Logged #74 for zoom-consistent motif budgets and #75 for the old unused wall-color ambiguity. No runtime code changed. |
| 2026-10-20 | Approved the scenery bitmap tile cache architecture (`docs/specs/scenery_bitmap_tile_cache.md`). Lightweight `StageSurfaceCache` handles share immutable bitmap tiles through one `StageSurfaceBakePool` under the current scene, with a 64 MiB resident bitmap budget and one reusable offscreen baker. Camera-visible tiles use four fixed zoom levels (0.125, 0.5, 1, 2); the coarsest tile bakes first and refined detail overlays it. At maximum 6× zoom, the 2× bitmap may look softer because it is magnified threefold. Erased fragments clip shared tiles only when their complete pattern sampling phase matches. The previous per-surface viewport cache retained vector commands and had no scene-wide resource limit, a likely contributor to `Element limit reached`; the exact RID pool remains unconfirmed. GPU readback during bitmap capture and regeneration after budget eviction are accepted costs to measure during implementation. |
| 2026-10-20 | Replaced the positional scenery tile design after the user observed continuous buffering when panning after zoom. The accepted revision in `docs/specs/scenery_bitmap_tile_cache.md` bakes one seamless, 2× bitmap per finish/tint period and uses GPU repeat plus generated mipmaps for all camera positions and zooms. Erased fragments share the style texture and crop it using their existing sample coordinate. The scene-wide 64 MiB limit includes mipmaps; inactive style variants may be evicted, while recently used styles stay warm when space allows. This trades unique random detail across an unlimited wall for finite repeated variation and keeps the existing possible 3× bitmap enlargement at maximum zoom. A style change can bake once; repeated camera movement must not increment build or readback counts after styles are ready. |
| 2026-10-21 | Drafted the camera-aware Sky architecture addendum in `docs/specs/phase_19_scenery_backgrounds_surface_finishes.md` for user sign-off. Accepted a fixed built-in camera reference, one bounded response profile per preset, and normalized motif anchors with uniform scaling. The gradient remains a full-screen `CanvasLayer` draw; motifs derive deterministic bounded pan/zoom response from absolute `Camera2D` framing. Chooser and starter previews stay unbound and still; existing Sky/camera scene fields suffice. The trade-offs are response saturation far from the reference and more frequent vector redraws during camera movement; logged #76 for measured performance follow-up. No runtime code or tests changed. |
| 2026-10-21 | Wrote the Phase 19 starter-scene composition UI/UX and architecture specifications (`docs/specs/phase_19_starter_scene_composition_ui_ux.md`, `docs/specs/phase_19_starter_scene_composition.md`) after approval of the Everyday Storybook brief and the 1,280-pixel / ±1,000-world-unit pan defaults. The edited starter snapshots use current scene v1.6 and existing Ground, wall, Background ground, Scenery, and Prop types, and keep the camera unrestricted; the starter card derives a central opening preview without new scene metadata. No code or art changed. Added #77 for the finite authored-envelope and broad-selection trade-off; the architecture spec is approved and implementation/agent verification are underway.
| 2026-10-21 | Implemented the approved starter-scene composition spec. Town Street and Living Room now use continuous 4,760-unit support surfaces, aligned bases, and camera-derived starter-card framing; both edited scene files use current v1.6. The focused production-load/save-reopen/PLAY composition test passed 177 assertions, with measured visible-contact geometry within 0.225 logical pixels. The broader scenery suite exits 1 on nine other background-card and wall-erasure/capture-bar assertions, while the new surface assertions pass. GUI capture exited 134 and headless capture produced no image, so rendered-pixel checks, pan-endpoint seam inspection, narrow viewport/zoom-in framing, and user/editor verification remain pending. Updated #77 from planned to implemented with the finite-camera limitation accepted. See `docs/specs/phase_19_starter_scene_composition.md`.
| 2026-10-21 | Revised the starters after the user's Town Street screenshot exposed Sky clouds below the skyline and the user rejected oversized scaled Ground blocks. Added an opaque Grass Background ground, replaced the sidewalk and both starter floors with scale-1 tile rows, and extended Asphalt by one tile at each end so the road is accessible. The focused production-path test passed 515 assertions, including navigation across tile seams, sidewalk-to-road routing, and a PLAY actor landing grounded on Asphalt. The broader scenery suite still exits 1 on the same nine Sky-preview/gallery and Scenery-edit UI assertions observed before this revision; its new surface assertions pass. A bounded GUI capture exited 134 without an image, so rendered pixels, pan endpoints, card previews, narrow viewport/zoom-in states, and user/editor review remain pending. See `docs/specs/phase_19_starter_scene_composition.md`.
| 2026-10-22 | Prepared and revised the Phase 19 Plain Background architecture specification (`docs/specs/phase_19_plain_background.md`) for final user sign-off. The accepted composed Stage backdrop view serves live Stage and previews; scene v1.7 persists active kind, last Sky ID, and confirmed plain color while migrating all older scenes as Sky. Color drafts stay out of Save/autosave, and theme-backed grid and two-tone gizmo overlays address light fills without changing art. Per user feedback, the current-color chip is the sole custom-picker entry, the separate More colors button is removed, and the custom picker uses a simple Preview label without a temporary-state helper message. Logged #78 for the chooser extension and rendered verification gates. No runtime code or tests changed. |
_End of Log_
@@ -1,73 +0,0 @@
# 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.
@@ -1,157 +0,0 @@
## 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.
-386
View File
@@ -1,386 +0,0 @@
# Editor Visual Style Guide
**Scope:** Establish a practical visual and interaction direction for the Prop Editor and Stickman Editor, using the Sandbox Stage UI as a product reference.
**Status:** Guidance for current and future editor UI. Prop and Stickman share `EditorVisualTheme` for editor chrome and selected reusable controls; Stage-only layouts and components remain Stage-specific unless explicitly generalized.
## 1. Product direction
Both editors should feel like approachable creative workspaces in the same Stickman Studio product:
- Keep the canvas or preview as the main work surface. Put tools and settings around it, not on top of the artwork unless a temporary task requires an overlay.
- Make the current task and mode easy to recognize. Use direct labels such as **Arrange**, **Reshape**, **Attach**, **Save**, and **Cancel**.
- Prefer visible feedback on the object over abstract state: selection outlines, vertex handles, previews, mode coloring, and status text.
- Preserve a stable layout while the child works. Opening an inspector section, showing help, or changing a mode should not make the active control jump away from the pointer.
- Make recovery and destructive actions predictable. Show what is about to happen and keep a visible way to cancel.
- Share a visual language, not necessarily identical panel layouts. A vector canvas, a modal dialog, an in-stage inspector card, and a stickman-part panel have different jobs.
The Sandbox Stage establishes a useful baseline for hierarchy, contrast, child-facing language, control states, safe viewport behavior, and theme tokens. Its in-stage inspector and capture bar are Stage patterns; they should be adapted only when an editor workflow has the same interaction needs.
## 2. Current implementation map
| Surface | Current implementation | Reuse implication |
| --- | --- | --- |
| Sandbox Stage | SandboxTheme loads validated tokens from [sandbox_theme.json](../../sandbox_theme.json). Stage inspector controls live under [scripts/shared/stage_ui](../../scripts/shared/stage_ui/). | Use as the product reference for semantic colors, readable type, focus, action roles, and reusable theme access. |
| Stage inspector | The approved [Stage panel UI system](../specs/stage_panel_ui_system.md) targets in-stage selected-object cards. | Do not insert StageInspectorPanel into editor inspectors by default. Borrow fitting rules and state behavior; use its components only if the layout and ownership match. |
| Stage and editor dialogs | [StageDialogStyler](../../scripts/shared/stage_ui/stage_dialog_styler.gd) draws the shared confirmation card over the active scene while retaining native dialog signals. The style reads `popups.confirmation` from SandboxTheme. | Use this shared card for Prop and Stickman confirmation, recovery, and short action dialogs. Native file picker content remains native. |
| Shared editor chrome | [EditorVisualTheme](../../scripts/shared/editor_ui/editor_visual_theme.gd) loads validated tokens from [editor_visual_theme.json](../../editor_visual_theme.json) and builds each editor's local control Theme. `EditorSelectionDropdown` and `EditorContextMenu` supply shared selection and canvas-menu styling. | Use shared semantic tokens and controls for matching editor workflows. Keep editor-specific document behavior in its owner. |
| Prop Editor | [PropTheme](../../scripts/editors/prop/prop_theme.gd) reads [prop_theme.json](../../prop_theme.json) for material, geometry, and canvas defaults; editor chrome comes from `EditorVisualTheme`. The main scene uses split containers, a scrollable inspector, modes, and a nested Attach preview. | Keep Prop artwork/material values and canvas contrast in `PropTheme`; use shared editor tokens for chrome, inspector states, and selection dropdown styling. |
| Stickman Editor | [stickman_editor.gd](../../scripts/editors/stickman/stickman_editor.gd) uses `EditorVisualTheme` for chrome and coordinates ten BodyPartPanels in a two-column Overview beside the Whole Stickman preview, plus a Focus view for one large panel. | Overview columns and panels expand to the workspace size, retaining a 230 × 160 px minimum and scrolling when space is short. Keep the splitter draggable, preserve panel state, and show the part title inside its panel in both views. |
| Child-facing dialogs | [Dialog Design Principles](../specs/dialog_design_principles.md) documents the rounded, labeled-action pattern and stable resizing behavior. | Apply these rules to editor dialogs, adapting ownership and host behavior to each editor. |
The Prop Editor user guide at [prop_editor_user_guide.md](prop_editor_user_guide.md) describes its current controls and layout. Use it to avoid documenting proposed changes as existing behavior.
## 3. Shared visual system
### 3.1 Semantic color roles
Use color to reinforce meaning consistently. Keep labels, shapes, icons, or state text alongside color so meaning survives grayscale, low contrast, and color-vision differences.
| Role | Sandbox Stage reference | Editor guidance |
| --- | --- | --- |
| Main background | global.bg_color #171B24F2 | Use a dark neutral around the work area; avoid competing with authored art. |
| Raised surface | global.surface_color #252B38FA | Use for toolbars, inspector shells, and cards. Distinguish adjacent surfaces by value, border, or spacing. |
| Primary text | global.text_color #F5F7FA | Use for titles, control names, and important values. |
| Secondary text | global.muted_text_color #AAB2C0 | Use for helper text and secondary status; keep readable at the chosen size. |
| EDIT accent | mode_colors.edit #22C6FF | Shared editor accent for active modes, selection accents, and editor guidance through `EditorVisualTheme`. |
| DIRECT accent | mode_colors.direct #FFB300 | Reserved for the Stage's directing mode. Do not use as a generic warning color in editors. |
| PLAY accent | mode_colors.play #33DD77 | Reserved for Stage playback state. |
| Primary action | Stage dialog/action blue #087DBA | Use for the single most important forward action in a dialog. |
| Destructive action | Stage danger #E66B75 with dark red surfaces | Use only for destructive actions; pair with explicit wording and a cancel path. |
| Focus | Stage dialog focus #8BD9FF | Give keyboard focus an outline that remains visible over selected, hovered, and dark states. |
| Canvas grid | Stage tokens vary with canvas treatment; Prop canvas currently uses #FFFFFF26. | Keep grid lines subtle. The work, selection, and handles must dominate the grid. |
These values are current Stage reference values, not a mandate to paste literal hex values into editor controls. Resolve colors through validated theme accessors. If an editor needs a warmer or quieter local accent, define a semantic editor token and document its mapping to Stage meaning.
### 3.2 Shared chrome and intentional editor differences
Both standalone editors now use `EditorVisualTheme` for shared chrome roles, sizing, and local control Themes. Keep these domain-specific values in their existing owner:
- `PropTheme` retains material tints, primitive defaults, outline widths, and the Prop canvas surface/grid. Keep its neutral charcoal canvas distinct from the blue-tinted editor panels. Stickman canvas grid and selection colors remain in `EditorVisualTheme.stickman_canvas` tokens.
- `EditorVisualTheme` owns common editor surfaces, text, EDIT accent, focus, action roles, menu sizes, and minimum targets. `SandboxTheme` continues to own Stage-specific panels and dialog tokens.
Preserve these boundaries when extending the UI: share semantic chrome roles where workflows match, and keep canvas, material, and runtime-domain colors local to their owner.
### 3.3 Type, spacing, and shape
Use these Stage examples as starting points, then tune them for the denser editor workspace:
- Stage inspector hierarchy: title **24 px**, section **20 px**, body **20 px**, helper **16 px**.
- Stage inspector shell: **12 px** padding, **8 px** header gap, **12 px** section gap, **10 px** corner radius.
- Stage choice row: **56 px** minimum height; Stage toggle row: **64 px** minimum, increasing when helper text is visible.
- Large child-facing dialogs: title about **28–30 px**, body about **20 px**, button about **20 px**, **2 px** borders, **12–18 px** corner radii, and generous footer spacing.
- Stage generally uses dark surfaces with a restrained border. The dialog guide's stronger borders and rounder controls are for modal emphasis, not a requirement to put every editor widget inside a thick outline.
For compact inspectors, shorten the hierarchy only after checking it at the intended window size. Prefer a clear label and value over shrinking type until it becomes difficult to read. Use container spacing and alignment rather than per-control position nudges.
### 3.4 State styling contract
Every interactive control should have distinct, inspectable states:
| State | Expected feedback |
| --- | --- |
| Neutral | Stable surface and legible label; no implied selection. |
| Hover | Small surface or border change. Must not resemble selected or focused. |
| Selected / active | Persistent fill, border, pressed state, or explicit status. Keep the label unchanged when possible. |
| Keyboard focus | High-contrast focus ring or border that is visible even over selected fill. |
| Disabled | Control cannot activate, remains readable, and explains why when the reason is not obvious. |
| Dragging / live preview | Immediate object feedback; indicate the active handle or preview and make cancellation discoverable. |
| Error / invalid | Explain the problem near the affected task and offer a concrete next step. Do not rely on red alone. |
| Saved / dirty | Show a concise, persistent status such as **Saved** / **Unsaved changes** where it helps prevent lost work. |
For drawing handles, keep a contrasting backing/halo where the canvas can contain arbitrary colors. The Prop canvas's two-tone selection halo and the Stage gizmo backing are useful precedents. Selection should be readable at all zoom levels; keep handle sizes in screen space when scaling the canvas.
## 4. Layout and reuse rules
### 4.1 Use the right pattern for the job
- **In-stage selected-object inspector:** follow StageInspectorPanel, choice groups, and toggle rows in the [Stage UI spec](../specs/stage_panel_ui_system.md). Its title/Close header, fixed header with scrolling body, and safe-rect fitting are specific to a floating selection card.
- **Editor inspector:** favor a persistent column beside the canvas, with labeled groups and vertical scrolling. The Prop Editor already uses a split container and scrollable inspector. Separate sections with rounded cards, readable headings, colorful section icons, and concise helper copy; keep related values grouped in compact tiles. Reuse Stage's hierarchy and state clarity, but do not force the floating Stage card structure into this column.
- **Temporary targeting step:** the Stage capture bar is for a short-lived click-on-stage task. An editor can borrow the prompt-plus-visible-Cancel contract when it has a similar temporary canvas capture step, but should not copy the bar just because it is visible in Stage.
- **Modal dialog:** follow [Dialog Design Principles](../specs/dialog_design_principles.md). Keep a labeled **Cancel**, **Done**, or **Back** action; use one visually clear primary action; preserve the clicked control's anchor when content grows; cap height and scroll content on short viewports.
- **Canvas tools:** keep mode and document controls outside the draw area where possible. Put contextual handles and temporary prompts near the object only if they do not obscure the task.
### 4.2 Reuse through tokens and small components
1. Put repeated colors, font sizes, minimum sizes, spacing, radii, border widths, and state colors in a validated theme source.
2. Use typed accessors with sensible fallbacks. Avoid repeating raw values across script-built controls and .tscn files.
3. Apply per-control StyleBox overrides. Do not mutate a cached/shared Theme to style one editor or one accent variant.
4. Extract a reusable control only after the behavior or presentation appears in at least two appropriate contexts. Keep domain rules and document mutations in the editor controller.
5. Prefer native Button, ButtonGroup, CheckBox, OptionButton, and LineEdit semantics. Custom-drawn controls should separate visual drawing from input and expose a clear typed API.
6. Keep canvas-specific rendering tokens separate from chrome tokens. Grid, artwork, shape fill, selection halo, vertex handles, and preview ghosts have different contrast jobs.
7. Do not serialize UI state unless it is part of the authored document. Tool preference, document content, preview-only state, and persistence should be deliberately separated.
Both standalone editors build their chrome Theme from `EditorVisualTheme`; Prop also uses `PropTheme` for prop-specific art and material defaults. Stage remains the reference for fitting, dialog hierarchy, and child-facing language, but its in-stage inspector layout is not a default editor-inspector component.
### 4.3 Responsive workspace behavior
- Preserve canvas priority. When space is constrained, inspector content scrolls before drawing tools or essential canvas controls are clipped.
- Keep splitters draggable and give both sides a useful minimum. Prop Editor has the outer canvas/inspector split and an Attach preview split; these are good workspace patterns to retain.
- Use container-driven rows (FlowContainer, HBoxContainer, VBoxContainer) instead of fixed offsets for control groups that can wrap or grow with theme fonts.
- Keep top actions and mode controls reachable at smaller window sizes. Wrap or stack controls only when necessary, and preserve their order and mode meaning.
- Keep toolbars, status bars, and dialogs inside safe margins. A dynamic dialog should grow from a stable anchor and scroll rather than jump or recenter after each choice.
- Stickman Overview uses two equal, flexible columns of five body-part drawing panels beside the Whole Stickman preview. Let both columns fill the Overview pane and share extra width; let the five rows share extra height while preserving a 230 × 160 px minimum panel size. Scroll the Overview when the viewport is shorter than its minimum content size.
- Keep the Overview/preview splitter draggable. On narrow windows, use the existing Focus view to give one body-part panel the available space; keep Whole Stickman access available through its dedicated control. Switching views must preserve each panel instance, authored geometry, selection, and view transform. In Focus, use the title inside the active panel as the only canvas heading; do not repeat it above the panel.
- Validate the layout at wide and narrow windows, with raised fonts and resized panels, before claiming responsive behavior.
### 4.4 Shared confirmation dialog style
Use the Sandbox Stage confirmation card for child-facing confirmation, recovery, and short action dialogs in both standalone editors. The shared implementation is `StageDialogStyler`, with color, sizing, and spacing values from `SandboxTheme.popups.confirmation`.
- Draw a dim backdrop and a centered, rounded dark card. Use the themed card width (560 px target), 22 px inset, 18 px corners, and 2 px border; fit the card inside the safe viewport.
- Put a contextual icon badge and a left-aligned 28 px title in the header. Put the message in a left-aligned 20 px body. Wrap longer copy and scroll the body if the viewport is short.
- Order actions as primary, destructive, then **Cancel**. Use Stage blue for Save, Restore, and other forward actions; use the danger treatment for Discard, Start Over, and other destructive choices; use the neutral surface for Cancel. Make action labels at least 52 px high and 112 px wide, with 12 px gaps.
- Keep the visible **Cancel** action even when Escape is supported. Do not show a native title-bar **×**. Focus the primary action when present; for a destructive-only prompt, focus Cancel.
- Keep the native `AcceptDialog` or `ConfirmationDialog` as the signal source and open its shared overlay through `StageDialogStyler`. This keeps confirmation, custom-action, cancellation, keyboard, and safe-viewport behavior consistent.
- Apply this pattern to Prop recovery, unsaved changes before New/Close/Stage return, off-origin save choices, anchor rename, and error acknowledgement; apply it to Stickman Start Over, unsaved changes, grid setup, and error acknowledgement. File pickers keep their native file-selection interface.
Treat the card as one shared product pattern. Do not create editor-local confirmation palettes or button-role variants.
## 5. Editor flows and state maps
The following maps describe the current interaction architecture at a level useful for future visual work. They are not proposed additions to file formats.
### 5.1 Prop Editor flow
Current primary flow, based on [prop_editor.gd](../../scripts/editors/prop/prop_editor.gd) and [prop_canvas.gd](../../scripts/editors/prop/prop_canvas.gd):
~~~mermaid
flowchart TD
Open[Open Prop Editor] --> Recovery{Recover autosave?}
Recovery -->|Restore| Dirty[Restore document as dirty]
Recovery -->|Discard / none| New[New or loaded document]
Dirty --> Arrange[Arrange shapes]
New --> Arrange
Arrange --> Reshape[Reshape vertices]
Arrange --> Attach[Attach anchors and preview]
Reshape --> Arrange
Attach --> Arrange
Attach --> Reshape
Arrange --> Save[Save / Save As]
Reshape --> Save
Attach --> Save
Save --> Clean[Saved status; autosave cleared]
Arrange --> Exit{New / close with changes?}
Reshape --> Exit
Attach --> Exit
Exit -->|Save| Save
Exit -->|Discard| Close[Close or start new]
Exit -->|Cancel| CurrentMode[Return to current mode]
~~~
Presentation rules for this flow:
- Arrange, Reshape, and Attach are persistent tool modes. Keep the active mode visually selected and keep mode labels stable.
- Mode changes may change available controls and inspector sections. Explain missing controls through a short label or helper only when the reason is not obvious from the mode.
- Shape/anchor edits change the document and should visibly change dirty status. Preview motion, visibility toggles, and other session-only controls should not imply the document was edited.
- Saving should confirm completion in the status area or title state. Errors should identify the failed file action and retain the child's work.
- New and close with unsaved changes offer explicit **Save**, **Discard**, and **Cancel** outcomes. For actions that could replace work, name the consequence in the prompt.
- In Attach mode, show the selected anchor and its editable handles clearly on the canvas, and keep the private stickman/prop preview visually separate from the authoring surface.
### 5.2 Stickman Editor flow
Current primary flow, based on [stickman_editor.gd](../../scripts/editors/stickman/stickman_editor.gd), [body_part_panel.gd](../../scripts/editors/stickman/body_part_panel.gd), and [whole_stickman_preview.gd](../../scripts/editors/stickman/whole_stickman_preview.gd):
~~~mermaid
flowchart LR
LoadOrStart[Load or start stickman] --> Parts[Choose a body-part drawing panel]
Parts --> Draw[Draw and color one or more shapes]
Draw --> Select[Select shape or vertex]
Select --> Preview[Inspect whole-stickman assembly]
Preview --> Guide[Toggle pose guide / reset views]
Guide --> Parts
Parts --> Save[Save stickman]
Save --> Continue[Continue editing]
LoadOrStart --> Clear[Clear with confirmation]
Clear --> Parts
~~~
Presentation rules for this flow:
- Make the active body-part panel and active shape unambiguous. Selecting a shape in one panel clears selection in the others.
- Keep local drawing context and the assembled Whole Stickman preview distinct but visually related. A change in one body part should have a prompt, visible effect in the assembly preview.
- Use the pose guide as quiet construction help beneath selection gizmos. Guide colors distinguish left/right limbs; the guide must not capture pointer input or obscure active edits.
- Keep color editing and recent colors close to the relevant part, and confirm a color only through the explicit dialog action.
- Keep grid/snap and guide visibility available as tool preferences, visually separated from authored shape color and geometry.
### 5.3 Reusable dialog transaction
For either editor, the expected child-facing sequence is:
1. State the action in the title and one short sentence.
2. Show the effect or consequence when it may be surprising.
3. Present one primary action, any needed destructive action, and a visible **Cancel**. If the only forward-looking choice is destructive (such as Start Over), keep it in the danger role and focus Cancel initially.
4. Keep focus in the dialog, preserve the current anchor during resize, and return focus to the invoking control after close where practical.
5. On success, close the dialog and update the affected editor state once. On cancel, leave the document unchanged.
This is a target interaction contract for editor dialogs. Existing controls should be checked individually before being treated as compliant.
## 6. Interaction, feedback, and child-facing language
### 6.1 Input and feedback
- Support mouse and touch-size targets; target at least **44 × 44 logical pixels** for child-facing actions. Prefer Stage's **48 px** action target where the layout allows.
- Keep keyboard navigation visible and complete: Tab reaches meaningful controls, Enter/Space activates them, Escape cancels temporary tasks, and focus does not disappear behind a selected style.
- Keep right-click and keyboard shortcuts as optional accelerators. A visible menu item or button should remain available for important operations.
- Keep the top-level File/Edit/View menu titles plain and use familiar emoji beside readable action labels inside their dropdowns. Match the Sandbox Stage menu scale: 22 px menu-bar labels and 26 px popup labels. Group file actions as New/Open/Recent, then Save/Save As, with separators before a distinct Rename or Start Over action where present. Use the restrained charcoal popup surface for menu-bar dropdowns; reserve the raised Studio List surface for canvas right-click menus.
- Style single-choice editor pickers as a dark field with the chosen label and chevron. Popup rows keep readable text and may include a meaningful domain icon, as in Prop material and shape choices; do not show radio/check glyphs or an empty indicator lane. The current choice gets a full teal surface and cyan outline. Keep hover and keyboard focus visible, and give the field and rows at least 44 logical pixels of height. Use the shared `EditorSelectionDropdown` control for Prop and Stickman editor pickers.
- Standardize Prop and Stickman canvas context menus on the **Studio List** pattern: a compact vertical list on a raised editor surface, with a subtle border, rounded corners, readable 20 px labels, and 48 px rows. Resolve surface, border, text, hover, EDIT accent, focus, and destructive colors through `EditorVisualTheme` tokens rather than local color literals.
- Group related actions with spacing and separators, and keep each menu's actions specific to the current mode and hit target. Hover uses a lighter surface with a slim EDIT-cyan cue; keyboard focus has a distinct, high-contrast focus outline. Open with no focused row so mouse users do not mistake an action for a selection; show the focus outline after Up/Down navigation, and clear it when the pointer takes over. Give destructive actions explicit labels, separate them from regular actions, and place them last when practical; do not use color as their only cue.
- Keep labels as the source of meaning and use icons sparingly and consistently. Open the menu near the pointer, keep the clicked point or shape edge visible, and clamp the menu within the viewport.
- For dragging, preview changes continuously, show which handle is active, commit on release, and provide a clear cancel path when the operation is consequential or multi-step.
- Avoid hit-test surprises: decorative labels/icons ignore mouse input; a large row toggle has one activation target; a canvas guide is visual only.
- Preserve the world point under the cursor during zoom. Keep selected gizmos and handles visible above artwork and grid.
- Use both visual and textual feedback for snap, mode, selected state, disabled reason, save state, and errors.
The Stage panel's full-row toggle and stable help layout are useful behaviors when a row switch is appropriate. In an editor inspector, apply the same principles without copying the Stage card's floating shell.
### 6.2 Copy and labels
Prefer concise, concrete verbs and familiar terms:
| Prefer | Avoid |
| --- | --- |
| **Arrange**, **Reshape**, **Attach** | Technical labels such as “Transform mode” or “Edit points mode” as the main child-facing label |
| **Add Shape**, **Erase Shape**, **Create Point**, **Remove Point** | Ambiguous labels such as “Apply” when the result can be named |
| **Save**, **Save As…**, **Load**, **Cancel** | Icon-only actions with no tooltip or accessible label |
| “This shape was reshaped.” | A disabled Radius field with no explanation |
| “Choose where this prop attaches.” | Internal terms such as “anchor payload” |
| “Unsaved changes. Save before starting a new prop?” | Generic “Are you sure?” without naming the consequence |
Emoji can help distinguish common actions, as used by both editor menus, but should be an optional cue rather than the only meaning. Keep the text label understandable if an emoji font is unavailable. Use the same wording for the same action in menu, dialog, toolbar, tooltip, and user guide.
### 6.3 Icons and emoji
- Use one icon or emoji to help scan a familiar action; do not put emoji on every label by default.
- Keep the text label as the source of meaning. Provide a tooltip or accessible name for icon-only canvas tools.
- Use icons consistently for the same action across menus, toolbars, empty states, and dialogs. Avoid changing icon meaning between editors.
- Avoid using an emoji's color as the only state cue; fonts and platforms may render it differently.
- Both editors use icons beside readable menu action labels, with separators between distinct groups. Keep top-level File/Edit/View titles plain; place icons on actions and use the same symbol for the same operation across editors.
- In the Prop inspector, use colorful, flat icons for section headings, material choices, 2D shape choices, and artwork tags. Keep text labels authoritative. Show tag selection with the selected surface and border; do not add a separate check badge. Shape icons must depict planar 2D geometry.
### 6.4 Empty, loading, and error states
The current editors have different levels of state guidance. The following table separates known behavior from recommended presentation:
| State | Current reference | Recommended presentation |
| --- | --- | --- |
| New/empty Prop canvas | Arrange has an add-shape context menu and an Arrange-only empty-canvas hint; Reshape does not offer adding a shape. | Keep the canvas mostly clear. Show a short Arrange hint only when it helps discover the next action; hide it as soon as artwork exists. Do not show a dead-end hint in Reshape or Attach. |
| Empty Prop inspector selection | Inspector sections depend on mode and selected shape/anchor. | State what to select or create when the empty inspector would otherwise look broken. Keep the message short and do not add a second competing empty-canvas prompt. |
| Prop load/recovery | Load failures use an error dialog; autosave recovery offers Restore or Discard. | Name the file/action in the message, preserve the current artwork on failure, and keep Restore/Discard outcomes distinct. Loading feedback should not imply that the document is already saved. |
| Stickman panel before drawing | The ten body-part panels are the main workspace; the panel script owns drawing and selection behavior. | Give each blank panel a concise, low-contrast drawing cue if discovery testing shows it is needed. Remove it on first shape so it does not compete with artwork. |
| Stickman load/clear | Load and save use file dialogs; Clear has a confirmation dialog; error dialogs report failures. | Keep the current document intact on a failed load. State the consequence in Clear confirmation and offer a visible cancel path. |
| Save / dirty state | Prop Editor has status/title and autosave behavior; Stickman Editor's status bar reports cursor coordinates, snap state, and save state. | Keep save status easy to find in both editors. Do not treat autosave as a user-visible successful save unless the file was actually saved to the chosen destination. |
Do not add spinners or blocking overlays for synchronous work without evidence that the operation takes long enough to need them. If an operation becomes asynchronous, keep progress near the task and preserve access to cancellation where safe.
### 6.5 Motion and reduced motion
- Use motion to show cause and effect: a brief selection flash, a panel entering, or a successful action cue. Keep it short and do not delay the next input.
- Do not animate every hover or value change. Motion should help the child understand an edit, not decorate idle controls.
- Keep persistent mode and selection state visible after any transition ends.
- Respect a reduced-motion preference for nonessential transitions and flashes. Provide an equally clear static state.
- The current code has short visual effects such as the Prop canvas's new-point flash and Stage completion effects. A unified reduced-motion preference is not established by the referenced theme/specs; treat it as a future product-level decision.
## 7. Theme and implementation checklist
Before adding or revising an editor control:
### Design
- [ ] Is the control's job clear and is it in the correct workspace area?
- [ ] Does it follow the correct pattern: persistent inspector, modal dialog, canvas tool, or temporary capture?
- [ ] Are neutral, hover, selected, focused, disabled, dragging, and error states defined?
- [ ] Does selected state remain distinct from hover and focus?
- [ ] Are colors backed by labels, icons, position, or shape cues where appropriate?
- [ ] Are labels child-readable and consistent with the existing action vocabulary?
- [ ] Can the layout handle smaller windows, longer names, and larger font presets?
### Theme
- [ ] Are colors, type sizes, spacing, radii, borders, and target sizes tokens rather than repeated literals?
- [ ] Does the token use a semantic name rather than a control-specific unexplained hex?
- [ ] Does it have a validated accessor, fallback, and range clamp where applicable?
- [ ] Are per-control style changes local and isolated from a shared cached Theme?
- [ ] Is the difference from Stage tokens intentional and documented?
### Interaction
- [ ] Can pointer, touch, and keyboard users reach and activate the control?
- [ ] Does a disabled control explain why, if not self-evident?
- [ ] Does temporary work have an obvious Cancel/undo path?
- [ ] Does a control's position remain stable while help, validation, or mode content changes?
- [ ] Does changing a tool preference avoid marking the document dirty unless that is intentional?
- [ ] Does success/failure produce visible feedback without hiding the canvas or losing authored work?
### Architecture
- [ ] Does the editor controller own document mutation, persistence, and dirty state?
- [ ] Does a reusable control emit a typed intent and display a supplied value?
- [ ] Is new reuse actually shared by more than one appropriate UI context?
- [ ] Are UI preferences, preview-only state, and document data kept separate?
- [ ] Are new theme values represented in both default config and committed JSON?
- [ ] Are existing user guides/specs updated only after the behavior is verified?
### Implementation recipe
For a new editor UI element, use this sequence:
1. Name its job and state owner. Decide whether the editor controller, canvas, or reusable view owns the value and mutation.
2. Identify the closest reference pattern: Stage inspector, Stage dialog, Prop Editor mode control, Prop canvas selection, or Stickman body-part panel. Record what should carry over and what should not.
3. Define the semantic tokens and states before styling. Reuse existing token names where their meaning matches; add new names to the theme defaults and JSON with validation/fallback behavior.
4. Build with Godot containers and native controls first. Add a custom control only when it provides repeated behavior or custom canvas drawing.
5. Wire typed intent signals to the owning controller. Refresh controls from document state without emitting a second mutation.
6. Check the smallest supported window, larger type, long labels, all modes, selected/disabled/focused states, and save/error/cancel paths.
7. Capture before/after screenshots or a short manual checklist. Update the user guide after the behavior is accepted.
This is recommended implementation order, not a claim that every current editor control follows it.
### Recommended harmonization order
1. **Maintain token ownership.** `EditorVisualTheme` owns shared standalone-editor chrome; `PropTheme` owns prop-domain and canvas defaults; `SandboxTheme` owns Stage-specific UI. Document any intentional override at the token or component that owns it.
2. **Audit remaining overrides.** Check for editor chrome values that still bypass `EditorVisualTheme`; preserve the Prop canvas surface/material palette and Stickman canvas drawing tokens where they serve artwork contrast.
3. **Document responsive limits.** Record minimum usable widths, toolbar wrapping, how the two Stickman Overview columns share space beside the preview, and when Focus or scrolling takes over.
4. **Audit remaining state gaps.** Verify focus, disabled reasons, save/error status, destructive confirmation, and cancel behavior. Both editors use the shared `StageDialogStyler` card for confirmation, recovery, and short action dialogs.
5. **Validate visually at agreed sizes.** Compare the editor screenshots with the Stage reference and adjust intentional differences explicitly.
This order is a maintenance checklist for the current shared chrome and editor-specific drawing patterns; it does not propose replacing the established theme architecture.
### Visual QA checklist
- [ ] Compare Prop and Stickman editor startup screens side by side with the Stage's dark surface and EDIT accent references.
- [ ] Check every mode with no selection, with a selected object, during drag, and after release/cancel.
- [ ] Check primary action, destructive action, disabled control, hover, keyboard focus, and persistent selected states.
- [ ] Check prop autosave recovery, load failure, unsaved New/Close, and successful save feedback.
- [ ] Check Stickman Clear confirmation, load failure, snap status, pose guide, and cross-panel selection.
- [ ] Resize to the smallest supported viewport and increase font size. Confirm canvas access, scroll access, and nonoverlapping controls.
- [ ] Verify visible keyboard order and Return/Space/Escape behavior; confirm focus returns to the invoking control where appropriate.
- [ ] Check grid, artwork, selection halo, vertex handles, pose guide, anchor markers, and preview ghosts against light and dark artwork.
- [ ] Check text contrast and state distinction with a contrast measurement and color-vision simulation; the current specs do not provide measured results.
- [ ] Repeat the checks with emoji rendering unavailable or replaced; action meaning must remain clear from text.
## 8. Recommended additional coverage
To make this guide a stronger long-term design reference, add these pieces as the editor UI evolves:
1. **Annotated screenshots at canonical window sizes.** Include each editor's initial screen, every mode, selection/reshape states, Attach preview, and one modal. Mark which visuals are current and which are target.
2. **A theme ownership map.** Document how `SandboxTheme`, `EditorVisualTheme`, and `PropTheme` divide Stage UI, shared editor chrome, and Prop domain/canvas values. Note intentional overrides and identify any remaining editor chrome that bypasses its shared token.
3. **Responsive breakpoints and minimum workspace sizes.** Record minimum usable canvas/inspector widths, when controls wrap, how the Attach preview stacks, and how the two Stickman body-panel columns share space with the Whole Stickman preview.
4. **Accessibility and contrast acceptance checks.** Specify contrast targets, keyboard tab order, focus visibility, touch target checks, color-vision-safe cues, and high-font-size behavior. The current references provide useful states and target sizes but do not establish a measured accessibility audit.
5. **Motion and sound rules.** Define when transitions, selection flashes, save feedback, and error feedback animate or use sound; keep them brief, informative, and respectful of reduced-motion preferences.
6. **Content and localization rules.** Include label length budgets, wrapping/truncation behavior, tooltip standards, pluralization, and translation expansion checks.
7. **Editor-specific control patterns.** Document mode bars, drawing canvases, color pickers, anchor lists, selection gizmos, empty states, and destructive file flows as reusable examples with their owning state and signals.
8. **Verification matrix.** Link each visual contract to a scene/test/manual check so future changes can distinguish an implemented pattern from a recommendation.
## 9. Reference files
- [AGENTS.md](../../AGENTS.md) — repository layout, editor architecture, shared Stage UI, and child-facing dialog guardrails.
- [Stage panel UI system](../specs/stage_panel_ui_system.md) — Stage inspector card, transient capture bar, component contracts, fitting, and acceptance criteria.
- [Dialog Design Principles](../specs/dialog_design_principles.md) — child-facing modal shell, action hierarchy, and stable resize behavior.
- [SandboxTheme implementation](../../scripts/stage/sandbox_theme.gd) and [SandboxTheme config](../../sandbox_theme.json).
- [PropTheme implementation](../../scripts/editors/prop/prop_theme.gd) and [PropTheme config](../../prop_theme.json).
- [Prop Editor controller](../../scripts/editors/prop/prop_editor.gd), [Prop Editor scene](../../scenes/editors/prop/prop_editor.tscn), and [Prop Editor guide](prop_editor_user_guide.md).
- [Prop canvas](../../scripts/editors/prop/prop_canvas.gd) — mode behavior, canvas gestures, selection handles, and context menus.
- [Stickman Editor controller](../../scripts/editors/stickman/stickman_editor.gd), [Stickman Editor scene](../../scenes/editors/stickman/stickman_editor.tscn), [BodyPartPanel](../../scripts/editors/stickman/body_part_panel.gd), and [Whole Stickman preview](../../scripts/editors/stickman/whole_stickman_preview.gd).
- [Stage dialog style helper](../../scripts/shared/stage_ui/stage_dialog_styler.gd) and [Stage capture bar](../../scripts/editors/stage/stage_use_target_capture_bar.gd).
- [Prop Editor UX specification](../specs/phase_9c_prop_editor_ux.md) — current approved Prop Editor mode, Attach preview, marker, and layout behavior.
File diff suppressed because one or more lines are too long
@@ -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: 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).*
*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).*
### 2g. Vertex Dragging
@@ -207,12 +207,12 @@ Add a `deselect()` method to `BodyPartPanel` that sets `_selected=false` and `qu
| File | Changes |
|---|---|
| `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.* |
| `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.* |
| `README.md` | Document Phase 2 features, updated .stk format, vertex editing, zoom |
| `docs/specs/phase2_spec.md` | This file |
| `docs/phase2_spec.md` | This file |
---
@@ -475,7 +475,7 @@ func _on_view_menu_id_pressed(id: int) -> void:
## 5. Scene File Changes
### 5a. `scenes/editors/stickman/stickman_editor.tscn`
### 5a. `scenes/stickman_editor.tscn`
Add new nodes under the root `StickmanEditor`:
@@ -502,7 +502,7 @@ step = 1.0
rounded = true
```
### 5b. `scenes/editors/stickman/body_part_panel.tscn`
### 5b. `scenes/body_part_panel.tscn`
Add a ColorPicker popup:
@@ -538,12 +538,12 @@ text = "OK"
| File | Changes |
|---|---|
| `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 |
| `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 |
---
@@ -2,13 +2,13 @@
## Overview
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)"):
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)"):
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/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.**
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.**
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/editors/stickman/whole_stickman_preview.gd`)
### 1c. Code changes (`scripts/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/editors/stickman/stickman_editor.gd`)
### 2c. Code changes (`scripts/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/editors/stickman/whole_stickman_preview.gd`)
### 3c. Code changes (`scripts/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/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. |
| `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. |
| `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/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.
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.
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/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).
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).
### 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 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).
> 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).
### 2b. New state + public API
@@ -351,9 +351,9 @@ Because `_broadcast_settings()` is already called in `_ready()` (after `_load_se
| File | Changes |
|---|---|
| `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. |
| `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. |
| `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 `content/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 `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/editors/stickman/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/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/editors/stickman/stickman_editor.gd:363-394`.
- The editor already serializes per-part `shapes`/`position`/`rotation`/`scale` in `scripts/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 `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.
- **`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.
---
@@ -150,7 +150,7 @@ Empty part (no shapes, or all shapes with <2 points) → `pivot {0,0}`, `length
---
## 4. Editor changes (`scripts/editors/stickman/stickman_editor.gd`)
## 4. Editor changes (`scripts/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/characters/stickman/stk_rig_adapter.gd`:
New file `res://scripts/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 `scenes/legacy/master_rig2.tscn`
### 5f. `master_rig.tscn` vs `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.
- `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`.
- `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`.
- **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/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.
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.
---
@@ -362,9 +362,9 @@ The editor only *produces* data the adapter *consumes*. There is no editor→ada
| File | Changes |
|---|---|
| `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. |
| `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. |
| `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/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`.
> 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`.
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 `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.
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.
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 `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. |
| 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. |
| 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/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`.
- **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`.
---
## 12. Recommended Implementation Order
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.
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.
4. Manual verification (§8) + `--headless --check-only --quit`.
5. Optional headless adapter smoke test.
6. Doc updates (`README.md`, `AGENTS.md`).
@@ -2,7 +2,7 @@
## Overview
The runtime adapter `scripts/characters/stickman/stk_rig_adapter.gd` mounts `.stk` v1.4 vector shapes onto
The runtime adapter `scripts/stk_rig_adapter.gd` mounts `.stk` v1.4 vector shapes onto
`master_rig.tscn`'s `Body/*` visual nodes. Two defects make the mounted result unusable:
1. **Giant Polygon Explosions (legs)** — `_mount_shapes()` computes a single **uniform**
@@ -13,7 +13,7 @@ The runtime adapter `scripts/characters/stickman/stk_rig_adapter.gd` mounts `.st
is the bounding-box **center** `(min+max)/2`, not the joint connection (neck base / hip).
Shapes therefore rotate around their geometric center instead of the bone joint.
The fix is confined to `scripts/characters/stickman/stk_rig_adapter.gd` (the file the bug's "ActorFactory.gd"
The fix is confined to `scripts/stk_rig_adapter.gd` (the file the bug's "ActorFactory.gd"
reference actually means; the factory `stickman_factory.gd` only calls it). It introduces
**anisotropic scaling** (scale only the bone's primary axis), **joint-based anchors** (computed
from the part bbox at mount time, not the file `pivot`), and a **node-transform reset** on the
@@ -21,7 +21,7 @@ from the part bbox at mount time, not the file `pivot`), and a **node-transform
### Scope
- **Changed:** `scripts/characters/stickman/stk_rig_adapter.gd` (the mount pipeline only — `_mount_shapes`,
- **Changed:** `scripts/stk_rig_adapter.gd` (the mount pipeline only — `_mount_shapes`,
`_mount_shape`, `_transform_points`, plus new bbox/anchor/scale helpers).
- **Unchanged:** `_fit_bones()`, `_recalibrate_ik()`, `_bone_length_for()`, all node-path
constants, `stickman_factory.gd`, `test_harness.gd`, `master_rig.tscn`.
@@ -93,7 +93,7 @@ torso-facing end — see §8 Q3.)
---
## 3. Function-level change list (`scripts/characters/stickman/stk_rig_adapter.gd`)
## 3. Function-level change list (`scripts/stk_rig_adapter.gd`)
### 3a. `_mount_shapes()` (line 166) — rewrite the per-part loop
@@ -222,8 +222,8 @@ aspirational and was never added as a file). Verification is manual + parse chec
1. **Parse check** (per project convention, from `C:\Godot4\stickman`):
`..\Godot_v4.7.1-stable_win64_console.exe . --headless --check-only --quit`
2. **Harness visual check** (F6 on `res://scenes/tools/test_harness.tscn`), load each of
`content/stickmen/basic.stk`, `content/stickmen/test.stk`, `content/stickmen/break.stk`:
2. **Harness visual check** (F6 on `res://scenes/test_harness.tscn`), load each of
`stickmen/basic.stk`, `stickmen/test.stk`, `stickmen/break.stk`:
- **Legs** are thin (no block explosion) — cross-axis thickness is preserved at drawn size;
only the long axis stretches to the bone length.
- **Head** is unscaled (identical to drawn size), anchored at the neck base.
@@ -294,29 +294,29 @@ harness and resolve Q1 if limbs render rotated.
| File | Change |
|---|---|
| `scripts/characters/stickman/stk_rig_adapter.gd` | Rewrite `_mount_shapes()` per §3a; add `_compute_part_bbox()`, `_compute_anchor()`, `_compute_part_length()`, `_compute_scale()`, `_reset_node_transform()`, `_neutralize_driver_rotations()`; change `_mount_shape()` and `_transform_points()` signatures to `anchor: Vector2, scale: Vector2`; add `X_AXIS_PARTS` const; call `_neutralize_driver_rotations()` from `apply()`. |
| `docs/specs/phase9_round1_bugfix_spec.md` | This file. |
| `scripts/stk_rig_adapter.gd` | Rewrite `_mount_shapes()` per §3a; add `_compute_part_bbox()`, `_compute_anchor()`, `_compute_part_length()`, `_compute_scale()`, `_reset_node_transform()`, `_neutralize_driver_rotations()`; change `_mount_shape()` and `_transform_points()` signatures to `anchor: Vector2, scale: Vector2`; add `X_AXIS_PARTS` const; call `_neutralize_driver_rotations()` from `apply()`. |
| `docs/phase9_round1_bugfix_spec.md` | This file. |
No changes to `stickman_factory.gd`, `test_harness.gd`, `stickman_editor.gd`, `master_rig.tscn`,
`scenes/legacy/master_rig2.tscn`, `clear_pose.gd`, `master_rig_builder.gd`, or the `.stk` files.
`master_rig2.tscn`, `clear_pose.gd`, `master_rig_builder.gd`, or the `.stk` files.
**Adjacent files checked, no impact:**
- `scenes/legacy/master_rig2.tscn` — node-for-node mirror of `master_rig.tscn` (same `Body/*` + `RemoteTransform2D`
layout, `scenes/legacy/master_rig2.tscn:72-286`); adapter targets `master_rig.tscn`, and the fix applies to
- `master_rig2.tscn` — node-for-node mirror of `master_rig.tscn` (same `Body/*` + `RemoteTransform2D`
layout, `master_rig2.tscn:72-286`); adapter targets `master_rig.tscn`, and the fix applies to
either since node names/paths are identical.
- `clear_pose.gd` — an `EditorScript` that resets bone `scale`/`rest` on the *edited* scene; not
consumed by the runtime adapter.
- `scripts/tools/master_rig_builder.gd` — builds a **different** rig (`Sticky/Stickman/.../Hip` naming),
- `scripts/master_rig_builder.gd` — builds a **different** rig (`Sticky/Stickman/.../Hip` naming),
unrelated to `master_rig.tscn`'s `Master/Body/Skeleton2D/Torso` naming; not an adapter target.
---
## 10. Recommended implementation order
1. `scripts/characters/stickman/stk_rig_adapter.gd` — add `X_AXIS_PARTS` const + `_compute_part_bbox()`.
2. `scripts/characters/stickman/stk_rig_adapter.gd` — add `_compute_anchor()`, `_compute_part_length()`,
1. `scripts/stk_rig_adapter.gd` — add `X_AXIS_PARTS` const + `_compute_part_bbox()`.
2. `scripts/stk_rig_adapter.gd` — add `_compute_anchor()`, `_compute_part_length()`,
`_compute_scale()`, `_reset_node_transform()`, `_neutralize_driver_rotations()`.
3. `scripts/characters/stickman/stk_rig_adapter.gd` — rewrite `_mount_shapes()`; change `_mount_shape()` /
3. `scripts/stk_rig_adapter.gd` — rewrite `_mount_shapes()`; change `_mount_shape()` /
`_transform_points()` signatures and point math; wire `_neutralize_driver_rotations()` into
`apply()`.
4. Parse check + harness visual verification (§5); flip the torso anchor if it renders upside-down.
@@ -3,7 +3,7 @@
## Overview
Testing `break.stk` in the test harness revealed four defects. This round fixes all four with
changes confined to `scripts/characters/stickman/stk_rig_adapter.gd` (mount pipeline) and `scripts/tools/test_harness.gd`
changes confined to `scripts/stk_rig_adapter.gd` (mount pipeline) and `scripts/test_harness.gd`
(debug bone overlay). Root causes were established with a headless probe of the live rig
(spawned via `StickmanFactory` with the modification stack enabled, 2 frames).
@@ -127,9 +127,9 @@ dots + target joints — lower limbs clearly visible.
| File | Changes |
|---|---|
| `scripts/characters/stickman/stk_rig_adapter.gd` | Remove `_neutralize_driver_rotations()` + call; new anchor/scale/rotation table in `_mount_shapes()` + `_compute_anchor`/`_compute_scale`/rotation helpers; `DEFAULT_LINE_WIDTH = 2.0`; zero the Head driver's local position in `_fit_bones()`; update doc comments. |
| `scripts/tools/test_harness.gd` | Rewrite `_draw_bones()` (child-direction segments + leaf→IK-target segments + dots). |
| `docs/specs/phase9_round2_bugfix_spec.md` | This file. |
| `scripts/stk_rig_adapter.gd` | Remove `_neutralize_driver_rotations()` + call; new anchor/scale/rotation table in `_mount_shapes()` + `_compute_anchor`/`_compute_scale`/rotation helpers; `DEFAULT_LINE_WIDTH = 2.0`; zero the Head driver's local position in `_fit_bones()`; update doc comments. |
| `scripts/test_harness.gd` | Rewrite `_draw_bones()` (child-direction segments + leaf→IK-target segments + dots). |
| `docs/phase9_round2_bugfix_spec.md` | This file. |
## 5. Edge cases
@@ -188,4 +188,4 @@ dots + target joints — lower limbs clearly visible.
`DEFAULT_LINE_WIDTH = 2.0`; zero Head driver position.
3. `test_harness.gd` — rewrite `_draw_bones()`.
4. Headless smoke test + parse check.
5. Docs (`docs/known_issues.md` Round 2 note, `AGENTS.md`, `README.md`).
5. Docs (`BUGS.md` Round 2 note, `AGENTS.md`, `README.md`).
@@ -21,7 +21,7 @@ Three defects remain after Round 2 (user report, testing `break.stk` in the harn
## 1. Root-cause analysis
### 1a. Overlay head segment (`scripts/tools/test_harness.gd`)
### 1a. Overlay head segment (`scripts/test_harness.gd`)
`_draw_bones()` treats every leaf bone identically: draw to its IK target. For the four limbs
the targets ARE the wrist/ankle joints (correct). For the Head the target is a LookAt aim point
@@ -32,13 +32,13 @@ the targets ARE the wrist/ankle joints (correct). For the Head the target is a L
**Fix:** special-case the Head leaf — draw along the bone's own direction; keep the limb leaf
bones drawing to their IK targets.
### 1b. Duplicate nodes (`scripts/characters/stickman/stk_rig_adapter.gd` `_mount_shape`)
### 1b. Duplicate nodes (`scripts/stk_rig_adapter.gd` `_mount_shape`)
Closed shapes mount `Polygon2D` + closed `Line2D` outline (Round 1 mirrored the editor's
fill+outline rendering). The user wants one node per shape: closed → `Polygon2D` only; open →
`Line2D` only. `DEFAULT_LINE_WIDTH = 2.0` still applies to open lines.
### 1c. Part rotation/scale (`scripts/characters/stickman/stk_rig_adapter.gd` mount pipeline)
### 1c. Part rotation/scale (`scripts/stk_rig_adapter.gd` mount pipeline)
The `.stk` part dict carries `rotation` (degrees, about the part bbox center) and `scale`
(about the bbox center) from the Whole Stickman preview
@@ -84,7 +84,7 @@ double-rotates. The correct composition (per user direction) is:
## 2. Fix specification
### 2a. `scripts/tools/test_harness.gd` — head leaf segment
### 2a. `scripts/test_harness.gd` — head leaf segment
In `_draw_bones()`, for the leaf bone `Head` (no Bone2D child): draw
`origin → origin + Vector2(0, -length).rotated(global_rotation)` instead of the IK-target
@@ -96,12 +96,12 @@ already draws `origin + R(global_rotation)·(length, 0)` — change it to use th
for the Head (`bone_angle = −90`) that yields `(0, −90)` rotated by the pose — the desired
segment; for other bones `bone_angle` is 0 → `(length, 0)` — same as today).
### 2b. `scripts/characters/stickman/stk_rig_adapter.gd` — one node per shape
### 2b. `scripts/stk_rig_adapter.gd` — one node per shape
`_mount_shape()`: closed → `Polygon2D` only (polygon + color; **no** outline Line2D); open →
`Line2D` (width `DEFAULT_LINE_WIDTH`). Delete the outline branch.
### 2c. `scripts/characters/stickman/stk_rig_adapter.gd` — part transform composition
### 2c. `scripts/stk_rig_adapter.gd` — part transform composition
Read per-part `rotation` (float, **degrees**, default 0.0) and `scale` (`{x, y}`, default
`(1, 1)`) from the part dict. Apply in the mount pipeline:
@@ -146,9 +146,9 @@ constants, null guards.
| File | Changes |
|---|---|
| `scripts/characters/stickman/stk_rig_adapter.gd` | `_mount_shapes()` reads part `rotation`/`scale`; mount pipeline composes `E_full` + fitted anchor/θ/fit-scale + flip heuristic; `_mount_shape()` mounts one node per shape (closed → Polygon2D only). |
| `scripts/tools/test_harness.gd` | `_draw_bones()` leaf fallback uses the bone's `bone_angle` direction (Head draws a 90 px segment inside the head; limbs unchanged). |
| `docs/specs/phase9_round3_bugfix_spec.md` | This file. |
| `scripts/stk_rig_adapter.gd` | `_mount_shapes()` reads part `rotation`/`scale`; mount pipeline composes `E_full` + fitted anchor/θ/fit-scale + flip heuristic; `_mount_shape()` mounts one node per shape (closed → Polygon2D only). |
| `scripts/test_harness.gd` | `_draw_bones()` leaf fallback uses the bone's `bone_angle` direction (Head draws a 90 px segment inside the head; limbs unchanged). |
| `docs/phase9_round3_bugfix_spec.md` | This file. |
## 4. Open question — RESOLVED (user directive)
@@ -210,4 +210,4 @@ constants, null guards.
2. `stk_rig_adapter.gd` — `_mount_shape()` single-node-per-shape.
3. `test_harness.gd` — head-leaf overlay segment.
4. Headless smoke test + parse check.
5. Docs (`docs/known_issues.md` Round 3 note, `AGENTS.md`, `README.md`).
5. Docs (`BUGS.md` Round 3 note, `AGENTS.md`, `README.md`).
@@ -6,7 +6,7 @@ User report: the head is **too high** in the harness. Reference: the editor's si
head is a circle; the bottom of the mounted head shape must line up with the bottom of that
circle relative to the torso bone, so the head **overlaps the torso** some.
### Guide geometry (source of truth, `scripts/editors/stickman/whole_stickman_preview.gd`)
### Guide geometry (source of truth, `scripts/whole_stickman_preview.gd`)
| Constant | Value |
|---|---|
@@ -29,7 +29,7 @@ The mounted head's bottom (chin) must land at `y = −363.5` — i.e. the mounte
gets a `+28 px` local Y offset so the chin sits 28 px below the neck joint, matching the
guide circle's bottom and overlapping the torso.
## Fix specification — `scripts/characters/stickman/stk_rig_adapter.gd`
## Fix specification — `scripts/stk_rig_adapter.gd`
1. Add `const HEAD_CHIN_DROP := 28.0` with a doc comment deriving the value
(guide head circle bottom `−363.5` − neck `−391.5` = 28; the guide circle has radius 100
@@ -48,8 +48,8 @@ fixture position, not part geometry), so a 180°-rotated head drops by the same
| File | Changes |
|---|---|
| `scripts/characters/stickman/stk_rig_adapter.gd` | `HEAD_CHIN_DROP` const + head-points translation. |
| `docs/specs/phase9_round4_bugfix_spec.md` | This file. |
| `scripts/stk_rig_adapter.gd` | `HEAD_CHIN_DROP` const + head-points translation. |
| `docs/phase9_round4_bugfix_spec.md` | This file. |
## Test plan
@@ -18,7 +18,7 @@ size-independent delta in preview pixels = master pixels, since `GUIDE_SCALE = 1
writes it as per-part write-only metadata. The adapter converts it to the joint-end offset and
translates the mounted geometry accordingly, in the bone's frame.
## 1. Guide reference geometry (`scripts/editors/stickman/whole_stickman_preview.gd`)
## 1. Guide reference geometry (`scripts/whole_stickman_preview.gd`)
`GUIDE_JOINTS` (`:59-73`) gives every joint in master space. The part→joint mapping:
@@ -37,7 +37,7 @@ translates the mounted geometry accordingly, in the bone's frame.
## 2. Editor changes
### 2a. `scripts/editors/stickman/whole_stickman_preview.gd`
### 2a. `scripts/whole_stickman_preview.gd`
Add a public method:
@@ -49,7 +49,7 @@ func get_guide_joint_preview(joint_name: String) -> Vector2:
return _guide_to_preview(GUIDE_JOINTS[joint_name])
```
### 2b. `scripts/editors/stickman/stickman_editor.gd`
### 2b. `scripts/stickman_editor.gd`
1. Add `const GUIDE_JOINT_FOR_PART: Dictionary` (the 10-row mapping above).
2. Bump `FILE_VERSION` `"1.4"` → `"1.5"`; add `"1.5"` to `SUPPORTED_VERSIONS`; update the
@@ -68,7 +68,7 @@ func get_guide_joint_preview(joint_name: String) -> Vector2:
(`_apply_json_data` unchanged); v1.0–v1.4 files load unchanged and gain the key on their
next save.
## 3. Adapter changes (`scripts/characters/stickman/stk_rig_adapter.gd`)
## 3. Adapter changes (`scripts/stk_rig_adapter.gd`)
1. Read `guide_offset` (`{x, y}`) from the part dict (default `null`/absent).
2. **Only when present** (old files keep the current offset-0 behavior), compute the anchor
@@ -98,10 +98,10 @@ the Hips joint, reproducing the editor placement.
| File | Changes |
|---|---|
| `scripts/editors/stickman/whole_stickman_preview.gd` | `get_guide_joint_preview()` public method. |
| `scripts/editors/stickman/stickman_editor.gd` | `GUIDE_JOINT_FOR_PART` const; `FILE_VERSION "1.5"` (+ `SUPPORTED_VERSIONS` + error text); `guide_offset` computed per non-head part in `_collect_all_shape_data()`. |
| `scripts/characters/stickman/stk_rig_adapter.gd` | Read + apply `guide_offset` (node-frame translation, only when present). |
| `docs/specs/phase9_round5_bugfix_spec.md` | This file. |
| `scripts/whole_stickman_preview.gd` | `get_guide_joint_preview()` public method. |
| `scripts/stickman_editor.gd` | `GUIDE_JOINT_FOR_PART` const; `FILE_VERSION "1.5"` (+ `SUPPORTED_VERSIONS` + error text); `guide_offset` computed per non-head part in `_collect_all_shape_data()`. |
| `scripts/stk_rig_adapter.gd` | Read + apply `guide_offset` (node-frame translation, only when present). |
| `docs/phase9_round5_bugfix_spec.md` | This file. |
## 5. Edge cases
@@ -156,4 +156,4 @@ the Hips joint, reproducing the editor placement.
2. `stickman_editor.gd` — const map + version bump + `guide_offset` in `_collect_all_shape_data()`.
3. `stk_rig_adapter.gd` — read/apply `guide_offset`.
4. Headless smoke test + parse check.
5. Docs (docs/known_issues.md Round 5 note, AGENTS.md, README.md).
5. Docs (BUGS.md Round 5 note, AGENTS.md, README.md).
@@ -29,7 +29,7 @@ the joint is the **user's placement**, which Round 5 now stores as `guide_offset
part space). This fixes both buggy parts, preserves every correct case, and naturally
reproduces the 180° flip behavior (a flipped part's far end lands near the joint).
## 2. Fix specification — `scripts/characters/stickman/stk_rig_adapter.gd`
## 2. Fix specification — `scripts/stk_rig_adapter.gd`
In `_compute_mount_transform()` (the anchor/flip section, ~lines 397–407):
@@ -56,8 +56,8 @@ record which end is the joint), the stored guide placement decides the pivot sid
| File | Changes |
|---|---|
| `scripts/characters/stickman/stk_rig_adapter.gd` | Nearest-end anchor selection when `guide_offset` is present (replaces the family-side + flip choice for that case). |
| `docs/specs/phase9_round6_bugfix_spec.md` | This file. |
| `scripts/stk_rig_adapter.gd` | Nearest-end anchor selection when `guide_offset` is present (replaces the family-side + flip choice for that case). |
| `docs/phase9_round6_bugfix_spec.md` | This file. |
## 4. Edge cases
@@ -100,4 +100,4 @@ record which end is the joint), the stored guide placement decides the pivot sid
1. `stk_rig_adapter.gd` — nearest-end anchor selection (guarded by `has_guide_offset`).
2. Headless smoke test + parse check.
3. Docs (docs/known_issues.md Round 6 note, AGENTS.md, README.md).
3. Docs (BUGS.md Round 6 note, AGENTS.md, README.md).
@@ -13,12 +13,12 @@ The rig already provides both targets:
- `IK_Targets/Head` (Marker2D at `(0, −624)`) — the `SkeletonModification2DLookAt` aim point
for the Head bone (`bone_index = 1`, constrained, `constraint_in_localspace = true`).
The harness (`scripts/tools/test_harness.gd`) already has generic handle dragging
The harness (`scripts/test_harness.gd`) already has generic handle dragging
(`IK_HANDLE_PATHS` → `_hit_test_handle` → `_dragging_handle` → `_handle_mouse_motion` sets
`global_position`; `_draw_ik_handles` draws markers). Only the two new handles need wiring,
plus torso-follow semantics.
## 1. Fix specification — `scripts/tools/test_harness.gd`
## 1. Fix specification — `scripts/test_harness.gd`
### 1a. Register the new handles
@@ -56,8 +56,8 @@ existing overlay draw style, e.g. width 1.5/zoom, alpha ~0.5.)
| File | Changes |
|---|---|
| `scripts/tools/test_harness.gd` | `IK_HANDLE_PATHS` + 2 entries (Head, Torso); head/torso marker colors; head aim line. |
| `docs/specs/phase9_round7_feature_spec.md` | This file. |
| `scripts/test_harness.gd` | `IK_HANDLE_PATHS` + 2 entries (Head, Torso); head/torso marker colors; head aim line. |
| `docs/phase9_round7_feature_spec.md` | This file. |
## 3. Edge cases
@@ -100,4 +100,4 @@ existing overlay draw style, e.g. width 1.5/zoom, alpha ~0.5.)
1. `test_harness.gd` — new handles + colors + aim line + torso-follow.
2. Headless verification + parse check.
3. Docs (docs/known_issues.md Round 7 note, AGENTS.md, README.md).
3. Docs (BUGS.md Round 7 note, AGENTS.md, README.md).
@@ -2,7 +2,7 @@
## Overview
docs/guides/RIGGING.md Task 2: when the stickman faces different directions, body parts must be drawn in
RIGGING.md Task 2: when the stickman faces different directions, body parts must be drawn in
the right order relative to the torso:
1. **Facing left** → left upper/lower arm **and** left upper/lower leg draw **behind** the
@@ -57,7 +57,7 @@ with node names from `master_rig.tscn` (`"Head"`, `"Body"`, `"LeftUpperArm"`,
it (requirement 2).
- The head is frontmost in all three (requirement 4).
## 3. Implementation — `scripts/tools/test_harness.gd`
## 3. Implementation — `scripts/test_harness.gd`
### 3a. Constants
@@ -93,11 +93,11 @@ the back.
| File | Changes |
|---|---|
| `scripts/tools/test_harness.gd` | `BODY_CONTAINER_PATH`, `Z_ORDER_BY_PROFILE`, `_body_container`, resolve + clear + `_apply_body_z_order()`, hook in `_apply_facing_profile()`. |
| `docs/specs/phase9_task2_zorder_spec.md` | This file. |
| `scripts/test_harness.gd` | `BODY_CONTAINER_PATH`, `Z_ORDER_BY_PROFILE`, `_body_container`, resolve + clear + `_apply_body_z_order()`, hook in `_apply_facing_profile()`. |
| `docs/phase9_task2_zorder_spec.md` | This file. |
| `AGENTS.md` | Test-harness section: "Phase 9 Task 2 body-part z-order" bullet. |
| `README.md` | Test-harness bullet: facing profiles now also reorder `Body/*` draw order. |
| `docs/guides/RIGGING.md` | Mark Task 2 implemented (Task 1 was documented in the same way). |
| `RIGGING.md` | Mark Task 2 implemented (Task 1 was documented in the same way). |
## 5. Edge cases
@@ -121,7 +121,7 @@ the back.
1. Parse check: `..\Godot_v4.7.1-stable_win64_console.exe . --headless --check-only --quit`
(user-requested; no errors).
2. Temporary headless SceneTree script (pattern from the Round 7 spec; deleted afterwards):
- Instantiate the harness scene, `add_child` it, call `_load_and_spawn("res://content/stickmen/basic.stk")`.
- Instantiate the harness scene, `add_child` it, call `_load_and_spawn("res://stickmen/basic.stk")`.
- Assert `Body` children order equals `Z_ORDER_BY_PROFILE[FacingProfile.FORWARD]`.
- Call `_apply_facing_profile(FacingProfile.LEFT)` → assert left limb pairs precede the
torso node (left arm pair before left leg pair) and right pairs follow it (right leg
@@ -132,6 +132,6 @@ the back.
## 8. Implementation order
1. `scripts/tools/test_harness.gd` — constants, state, resolution, reorder + hook.
1. `scripts/test_harness.gd` — constants, state, resolution, reorder + hook.
2. Parse check + headless verification script (temp, then removed).
3. Docs: `AGENTS.md`, `README.md`, `docs/guides/RIGGING.md`.
3. Docs: `AGENTS.md`, `README.md`, `RIGGING.md`.
@@ -2,7 +2,7 @@
## Overview
docs/guides/RIGGING.md Task 3: the test harness needs a show/hide readout on the right side of the screen
RIGGING.md Task 3: the test harness needs a show/hide readout on the right side of the screen
displaying the current skeleton pose so bone/IK work can be inspected numerically:
1. **Skeleton2D** position and rotation.
@@ -28,7 +28,7 @@ Values are read live each frame in `_process(delta)`, so the panel always mirror
pose. All values are **world-space** (`global_position` / `global_rotation`). Rotation is
reported in degrees (`rad_to_deg`); positions are `Vector2` formatted to 1 decimal place.
## 2. Implementation — `scripts/tools/test_harness.gd`
## 2. Implementation — `scripts/test_harness.gd`
### 2a. Constants
@@ -105,10 +105,10 @@ calls `_update_coords_display()` each frame. `_update_coords_display()`:
| File | Changes |
|---|---|
| `scripts/tools/test_harness.gd` | `COORDS_PANEL_WIDTH`, `COORD_BONE_PATHS`, `_coords_panel`, `_coords_label`, `_coord_bones`, `_show_coords`, `_build_coords_panel()`, `_on_show_coords_toggled()`, `_process()`, `_update_coords_display()`, `_resolve_coord_bones()`, `_fmt_vec2()`, `_fmt_deg()`; "Show Coords" checkbox. |
| `docs/specs/phase9_task3_coords_spec.md` | This file. |
| `scripts/test_harness.gd` | `COORDS_PANEL_WIDTH`, `COORD_BONE_PATHS`, `_coords_panel`, `_coords_label`, `_coord_bones`, `_show_coords`, `_build_coords_panel()`, `_on_show_coords_toggled()`, `_process()`, `_update_coords_display()`, `_resolve_coord_bones()`, `_fmt_vec2()`, `_fmt_deg()`; "Show Coords" checkbox. |
| `docs/phase9_task3_coords_spec.md` | This file. |
| `AGENTS.md` | Test-harness section: "Phase 9 Task 3 coordinates display" bullet; `test_harness.tscn` scene description now lists the "Show Coords" toggle. |
| `docs/guides/RIGGING.md` | Mark Task 3 implemented. |
| `RIGGING.md` | Mark Task 3 implemented. |
## 4. Edge cases
@@ -139,7 +139,7 @@ calls `_update_coords_display()` each frame. `_update_coords_display()`:
(user-requested; no errors).
2. Manual F6 check:
- Launch harness; readout visible on the top-right with `"No rig loaded"`.
- Load `content/stickmen/basic.stk`; Skeleton2D, 10 Bones, and 6 IK Target rows populate with
- Load `stickmen/basic.stk`; Skeleton2D, 10 Bones, and 6 IK Target rows populate with
numeric values.
- Drag an IK handle → position/rotation values update live each frame.
- Uncheck "Show Coords" → panel hides; recheck → reappears with current values.
@@ -2,7 +2,7 @@
## Overview
docs/guides/RIGGING.md Task 4: facing direction and per-joint bone bend must become **part of
RIGGING.md Task 4: facing direction and per-joint bone bend must become **part of
`master_rig.tscn`**, not the test harness. A rigged stickman instance should carry its own facing
profile and bend flags so different instances can face different directions / bend differently at
the same time. These must be **exported controls** on the rig so they are easy to access from the
@@ -21,7 +21,7 @@ Today the harness owns all of this:
Task 4 moves that state + logic into a new script attached to the `master_rig.tscn` root, and the
harness becomes a thin driver that reads/writes the rig's exported properties.
## 1. New rig script — `scripts/characters/stickman/stickman_rig.gd`
## 1. New rig script — `scripts/stickman_rig.gd`
A new `class_name StickmanRig` script, `extends Node2D`, attached to the `Master` root node of
`master_rig.tscn` (the root currently has **no** script). It is the single owner of facing profile
@@ -238,7 +238,7 @@ Matches repo style throughout (`get_node_or_null`, `push_warning`, skip):
## 2. `master_rig.tscn` changes
1. **Attach the script** to the root `Master` node: `script = ExtResource("…")` for
`res://scripts/characters/stickman/stickman_rig.gd`. The exported defaults get serialized (`facing_profile = 2`,
`res://scripts/stickman_rig.gd`. The exported defaults get serialized (`facing_profile = 2`,
`left_arm_bend = 0`, `right_arm_bend = 1`, `left_leg_bend = 1`, `right_leg_bend = 0`).
2. **Align authored state to the FORWARD default** (Q1 **resolved: yes**). So the
@@ -259,7 +259,7 @@ Matches repo style throughout (`get_node_or_null`, `push_warning`, skip):
FORWARD on `_ready`), so this only affects the as-authored appearance and makes the "default
leaves the scene as-authored" verification true.
## 3. Harness changes — `scripts/tools/test_harness.gd`
## 3. Harness changes — `scripts/test_harness.gd`
The harness stops owning bend/facing state and drives the rig script. It keeps a thin UI mirror for
menu labels + re-application across respawns (the rig remains the authority over the actual flags).
@@ -355,10 +355,10 @@ menu still refreshes on `about_to_popup` as the primary mechanism.)
the tree), so mounted shape children are already present when the rig reorders `Body/*` — moving a
part node moves its whole shape group, exactly as today.
## 5. `master_rig_builder.gd`, `scenes/legacy/master_rig2.tscn`
## 5. `master_rig_builder.gd`, `master_rig2.tscn`
Out of scope and untouched (per `docs/specs/phase9_round1_bugfix_spec.md`): `master_rig_builder.gd` builds
a **different** rig (`Sticky/Stickman/.../Hip` naming), and `scenes/legacy/master_rig2.tscn` is a pose-override
Out of scope and untouched (per `docs/phase9_round1_bugfix_spec.md`): `master_rig_builder.gd` builds
a **different** rig (`Sticky/Stickman/.../Hip` naming), and `master_rig2.tscn` is a pose-override
variant not referenced by the factory. The new `StickmanRig` script targets `master_rig.tscn` only.
## 6. Defaults & backward compatibility
@@ -381,14 +381,14 @@ variant not referenced by the factory. The new `StickmanRig` script targets `mas
| File | Changes |
|---|---|
| `scripts/characters/stickman/stickman_rig.gd` | **New.** `class_name StickmanRig extends Node2D` — enums, constants, exported properties, signals, methods, runtime resolution/apply. |
| `scripts/stickman_rig.gd` | **New.** `class_name StickmanRig extends Node2D` — enums, constants, exported properties, signals, methods, runtime resolution/apply. |
| `master_rig.tscn` | Attach `StickmanRig` to root `Master`; (recommended) align authored TwoBoneIK flags + `Body/*` order to FORWARD (§2). |
| `scripts/tools/test_harness.gd` | Delete bend/facing ownership (§3a); add `_rig_script` + repurposed `_facing_profile`; rewire spawn, facing menu, right-click toggle (§3c–3f). |
| `scripts/characters/stickman/stickman_factory.gd` | Narrow `spawn_from_data`/`spawn` return type to `StickmanRig` (Q6). |
| `docs/specs/phase9_task4_refactor_spec.md` | This file. |
| `AGENTS.md` | Add `scripts/characters/stickman/stickman_rig.gd` bullet; update `test_harness.gd` bullet (facing/bend now drive the rig); note `master_rig.tscn` root script. |
| `scripts/test_harness.gd` | Delete bend/facing ownership (§3a); add `_rig_script` + repurposed `_facing_profile`; rewire spawn, facing menu, right-click toggle (§3c–3f). |
| `scripts/stickman_factory.gd` | Narrow `spawn_from_data`/`spawn` return type to `StickmanRig` (Q6). |
| `docs/phase9_task4_refactor_spec.md` | This file. |
| `AGENTS.md` | Add `scripts/stickman_rig.gd` bullet; update `test_harness.gd` bullet (facing/bend now drive the rig); note `master_rig.tscn` root script. |
| `README.md` | Project-structure table row for `stickman_rig.gd`; update test-harness + factory bullets. |
| `docs/guides/RIGGING.md` | Mark Task 4 implemented. |
| `RIGGING.md` | Mark Task 4 implemented. |
## 8. Edge cases
@@ -411,7 +411,7 @@ variant not referenced by the factory. The new `StickmanRig` script targets `mas
|---|---|---|
| D1 | New `class_name StickmanRig extends Node2D` on the `master_rig.tscn` root | The rig is the natural owner of per-instance facing/bend; a `class_name` script makes it a typed, reusable API for the harness and future consumers. |
| D2 | Enums live in the rig script (`StickmanRig.FacingProfile` / `StickmanRig.BendDirection`) | No autoload/singleton; idiomatic `class_name` enum access; single source of truth both sides can reference. |
| D3 | `facing_profile` is a preset; the four `*_bend` enum exports are the per-joint source of truth | Matches the harness's existing preset-then-override behavior (docs/guides/RIGGING.md: "user is free to change bend manually"); the enum dropdowns give readable "Normal"/"Inverted" inspector labels (the task's "per-joint bend enums" hint). |
| D3 | `facing_profile` is a preset; the four `*_bend` enum exports are the per-joint source of truth | Matches the harness's existing preset-then-override behavior (RIGGING.md: "user is free to change bend manually"); the enum dropdowns give readable "Normal"/"Inverted" inspector labels (the task's "per-joint bend enums" hint). |
| D4 | Public bool-form methods (`set_joint_bend_flipped`/`get_joint_bend_flipped`) alongside the enum exports | `flip_bend_direction` is a bool; the bool form is the engine-accurate contract and keeps the harness context-menu label logic unchanged. |
| D5 | TwoBoneIK resolved by `joint_two_bone2d_node` NodePath matching, not stack index | Preserves the existing (Task 1) resolution approach; robust to stack reordering. |
| D6 | Non-`@tool` script; apply only at runtime | IK doesn't solve in-editor; mutating `Body/*` order / mod flags in-editor would dirty the scene; runtime-only is the task's permitted minimum. |
@@ -455,7 +455,7 @@ head-aiming while facing LEFT is intentionally static.
1. **Parse check** (same as prior tasks):
`..\Godot_v4.7.1-stable_win64_console.exe . --headless --check-only --quit` — no errors.
2. **Headless SceneTree verification** (temporary script, deleted after; pattern from Round 7):
- `StickmanFactory.spawn("res://content/stickmen/basic.stk")` → cast root to `StickmanRig` (non-null);
- `StickmanFactory.spawn("res://stickmen/basic.stk")` → cast root to `StickmanRig` (non-null);
`add_child` it; assert `get_facing_profile() == FacingProfile.FORWARD` and the 4 mods' flags
equal the FORWARD set; assert `Body` child order == `Z_ORDER_BY_PROFILE[FORWARD]` and `Body/Head`
is last.
@@ -468,7 +468,7 @@ head-aiming while facing LEFT is intentionally static.
3. **Harness code review**: facing menu ids use `StickmanRig.FacingProfile`; spawn connects signals
before `add_child` and re-applies `_facing_profile`; right-click toggle reads/writes the rig
script, not a mod directly; `_resolve_rig_nodes()` no longer touches bend/z-order state.
4. **Manual F6** (`res://scenes/tools/test_harness.tscn`):
4. **Manual F6** (`res://scenes/test_harness.tscn`):
- Load `basic.stk`; figure starts FORWARD (unchanged from before).
- Facing menu → Left/Right: limbs tuck behind the torso correctly, head stays frontmost; `[√] `
moves; context menu labels reflect the new per-joint flags.
@@ -479,12 +479,12 @@ head-aiming while facing LEFT is intentionally static.
## 11. Implementation order
1. `scripts/characters/stickman/stickman_rig.gd` — enums, constants, exports, signals, methods, runtime apply.
1. `scripts/stickman_rig.gd` — enums, constants, exports, signals, methods, runtime apply.
2. `master_rig.tscn` — attach script; align authored flags + `Body/*` order to FORWARD (Q1).
3. `scripts/tools/test_harness.gd` — remove ownership, add `_rig_script`, rewire spawn/menus/toggle.
4. `scripts/characters/stickman/stickman_factory.gd` — (optional) narrow return type.
3. `scripts/test_harness.gd` — remove ownership, add `_rig_script`, rewire spawn/menus/toggle.
4. `scripts/stickman_factory.gd` — (optional) narrow return type.
5. Parse check + headless verification (temp, then removed).
6. Docs: `AGENTS.md`, `README.md`, `docs/guides/RIGGING.md`, this spec.
6. Docs: `AGENTS.md`, `README.md`, `RIGGING.md`, this spec.
## 12. Open questions — RESOLVED (user approval)
@@ -2,20 +2,20 @@
## Overview
docs/guides/RIGGING.md Task 5: `master_rig.tscn` already ships an `AnimationPlayer` and an `AnimationTree`.
RIGGING.md Task 5: `master_rig.tscn` already ships an `AnimationPlayer` and an `AnimationTree`.
The test harness must let the user **select** an animation from a dropdown, **play / pause /
resume / stop** it, and **toggle loop vs. play-once**. Concretely:
1. A **dropdown** listing the rig's animations (from `AnimationPlayer.get_animation_list()`).
2. A **play/pause/resume** button (single button whose label reflects playback state) plus a
**Stop** button. Per docs/guides/RIGGING.md, **Play always restarts from the beginning**; **Stop resets to
**Stop** button. Per RIGGING.md, **Play always restarts from the beginning**; **Stop resets to
the start** so the next Play restarts.
3. A **Loop** checkbox that makes the selected animation loop or play just once.
This is a **harness-only** change. `master_rig.tscn` is **not** modified (the two animation nodes
already exist and are sufficient). `scripts/characters/stickman/stickman_rig.gd` is **not** modified (the harness
already exist and are sufficient). `scripts/stickman_rig.gd` is **not** modified (the harness
resolves the `AnimationPlayer` directly by node path, exactly as it already resolves `Skeleton2D`
and the IK handles). Only `scripts/tools/test_harness.gd` changes.
and the IK handles). Only `scripts/test_harness.gd` changes.
## 1. Findings — `master_rig.tscn` animation nodes
@@ -32,7 +32,7 @@ rig root is the `Master` node (`_rig`, a `StickmanRig`), and `AnimationPlayer` i
### 1b. Animation library — two animations (not one)
The `AnimationLibrary_t75yq._data` dictionary holds **two** animations (docs/guides/RIGGING.md says "currently
The `AnimationLibrary_t75yq._data` dictionary holds **two** animations (RIGGING.md says "currently
just 'walk_right'", but there is also a pose-reset helper):
| Name | Length | `loop_mode` | Tracks |
@@ -114,7 +114,7 @@ before the "Open .stk…" button** (keeps the two rig-behavior control clusters
Controls are **always enabled** (matching the harness's existing "Facing" menu / checkbox style);
each handler no-op-guards on a missing `AnimationPlayer` instead of disabling the control.
## 4. Implementation — `scripts/tools/test_harness.gd`
## 4. Implementation — `scripts/test_harness.gd`
### 4a. Constants
@@ -122,7 +122,7 @@ each handler no-op-guards on a missing `AnimationPlayer` instead of disabling th
## AnimationPlayer node path (relative to rig root).
const ANIMATION_PLAYER_PATH := "AnimationPlayer"
## Initially-selected animation in the dropdown (docs/guides/RIGGING.md default).
## Initially-selected animation in the dropdown (RIGGING.md default).
const DEFAULT_ANIMATION := "walk_right"
```
@@ -314,14 +314,14 @@ func _update_play_button() -> void:
| File | Changes |
|---|---|
| `scripts/tools/test_harness.gd` | `ANIMATION_PLAYER_PATH`, `DEFAULT_ANIMATION`, `PlaybackState`, `_anim_dropdown`/`_play_button`/`_stop_button`/`_loop_check`, `_anim_player`/`_selected_animation`/`_playback_state`/`_loop`, UI block in `_build_ui()`, `_resolve_anim_player()`, `_populate_animation_dropdown()`, `_on_anim_dropdown_selected()`, `_on_play_pressed()`, `_on_stop_pressed()`, `_on_loop_toggled()`, `_on_animation_finished()`, `_apply_loop_mode()`, `_update_play_button()`; lifecycle hooks in `_resolve_rig_nodes()` + `_free_current_rig()`. |
| `docs/specs/phase9_task5_animation_spec.md` | This file. |
| `scripts/test_harness.gd` | `ANIMATION_PLAYER_PATH`, `DEFAULT_ANIMATION`, `PlaybackState`, `_anim_dropdown`/`_play_button`/`_stop_button`/`_loop_check`, `_anim_player`/`_selected_animation`/`_playback_state`/`_loop`, UI block in `_build_ui()`, `_resolve_anim_player()`, `_populate_animation_dropdown()`, `_on_anim_dropdown_selected()`, `_on_play_pressed()`, `_on_stop_pressed()`, `_on_loop_toggled()`, `_on_animation_finished()`, `_apply_loop_mode()`, `_update_play_button()`; lifecycle hooks in `_resolve_rig_nodes()` + `_free_current_rig()`. |
| `docs/phase9_task5_animation_spec.md` | This file. |
| `AGENTS.md` | Test-harness section: "Phase 9 Task 5 rig animation" bullet (dropdown + play/pause/stop + loop, drives `AnimationPlayer` directly). |
| `README.md` | Test-harness bullet: animation select/play/pause/stop/loop controls. |
| `docs/guides/RIGGING.md` | Mark Task 5 implemented. |
| `RIGGING.md` | Mark Task 5 implemented. |
**Not modified:** `master_rig.tscn`, `scripts/characters/stickman/stickman_rig.gd`, `scripts/characters/stickman/stickman_factory.gd`,
`scripts/characters/stickman/stk_rig_adapter.gd`.
**Not modified:** `master_rig.tscn`, `scripts/stickman_rig.gd`, `scripts/stickman_factory.gd`,
`scripts/stk_rig_adapter.gd`.
## 6. Edge cases
@@ -350,18 +350,18 @@ func _update_play_button() -> void:
| # | Decision | Justification |
|---|---|---|
| D1 | Drive `AnimationPlayer` directly; `AnimationTree` out of scope | `AnimationTree` is an unconfigured placeholder (`active = false`, empty state machine). Select/play/pause/stop/loop are all first-class `AnimationPlayer` APIs; wiring a blend tree would require editing `master_rig.tscn` for no gain here. |
| D2 | Single play/pause/resume button + separate Stop button | Matches docs/guides/RIGGING.md "pause/resume" (toggling) vs "stop (restart on play)" as distinct states; the label swap is the harness's existing dynamic-text pattern (cf. the editor's snap/guide menus). |
| D2 | Single play/pause/resume button + separate Stop button | Matches RIGGING.md "pause/resume" (toggling) vs "stop (restart on play)" as distinct states; the label swap is the harness's existing dynamic-text pattern (cf. the editor's snap/guide menus). |
| D3 | Track playback state via `_playback_state` + `animation_finished`, not `is_playing()` polling | The harness is the sole driver, so state is deterministic. `animation_finished` reliably fires for non-looping end-of-play and never fires on `pause()`/`stop()`; the `_loop` guard makes the looping-wrap ambiguity moot. Avoids adding per-frame polling to `_process`. |
| D4 | Loop = write `Animation.loop_mode` on the selected `Animation` before playing | Loop is an `Animation`-resource property, not a `play()` argument; this is the only way to override the authored value. Re-applied on every play so the harness `_loop` toggle is authoritative regardless of authored `loop_mode`. |
| D5 | Loop default **ON** | Matches the authored `walk_right` (`loop_mode = 1`), and a walk cycle is the natural looping case. |
| D6 | Dropdown populated dynamically per spawn from `get_animation_list()` | The list comes from the rig's own library, so future animations appear automatically; no hardcoded list. |
| D7 | Prefer `walk_right` as initial selection (`DEFAULT_ANIMATION`) | Matches docs/guides/RIGGING.md's "currently just 'walk_right'" default even though the library also contains `RESET`. |
| D7 | Prefer `walk_right` as initial selection (`DEFAULT_ANIMATION`) | Matches RIGGING.md's "currently just 'walk_right'" default even though the library also contains `RESET`. |
| D8 | No `stickman_rig.gd` change | The harness already resolves rig children by node path (`SKELETON_PATH`, `IK_HANDLE_PATHS`); `ANIMATION_PLAYER_PATH` follows that established pattern. A rig-level accessor is unnecessary. |
| D9 | Controls never disabled; handlers no-op-guard | Matches the existing harness style (the "Facing" menu and checkboxes are always enabled). |
## 8. Verification
Only `scripts/tools/test_harness.gd` changes, so the syntax checks target that script.
Only `scripts/test_harness.gd` changes, so the syntax checks target that script.
1. **Whole-project parse check** (established form used by every prior phase — the plain
`--check-only` form hangs on renderer init in 4.7.x, so use the `--headless --check-only --quit`
@@ -374,11 +374,11 @@ Only `scripts/tools/test_harness.gd` changes, so the syntax checks target that s
2. **Single-script check** (user-specified form; run from anywhere):
```
& "C:\Godot4\Godot_v4.7.1-stable_win64_console.exe" --headless --path "C:\Godot4\stickman" --check-only --script "res://scripts/tools/test_harness.gd"
& "C:\Godot4\Godot_v4.7.1-stable_win64_console.exe" --headless --path "C:\Godot4\stickman" --check-only --script "res://scripts/test_harness.gd"
```
3. **Manual F6 check** (`res://scenes/tools/test_harness.tscn`):
- Load `content/stickmen/basic.stk` → dropdown lists `RESET` and `walk_right`, `walk_right` selected,
3. **Manual F6 check** (`res://scenes/test_harness.tscn`):
- Load `stickmen/basic.stk` → dropdown lists `RESET` and `walk_right`, `walk_right` selected,
play button shows `"Play"`, Loop checked.
- Press **Play** → button `"Pause"`; the figure walks (IK targets animate, legs/arms swing,
facing menu flips to `[√] Right`). Coordinates readout updates live.
@@ -392,7 +392,7 @@ Only `scripts/tools/test_harness.gd` changes, so the syntax checks target that s
## 9. Implementation order
1. `scripts/tools/test_harness.gd` — constants, enum, node refs, state, `_build_ui()` block, handlers,
1. `scripts/test_harness.gd` — constants, enum, node refs, state, `_build_ui()` block, handlers,
helpers, lifecycle hooks.
2. Parse checks (§8 items 1–2) + manual F6 (§8 item 3).
3. Docs: `AGENTS.md`, `README.md`, `docs/guides/RIGGING.md`, this spec.
3. Docs: `AGENTS.md`, `README.md`, `RIGGING.md`, this spec.
@@ -1,14 +1,14 @@
# Phase 3a — Core Director Functionality (Implementation Specification)
> **Status:** Draft for review — Architect deliverable (research + spec only; no code written).
> **Source plan:** `docs/archive/plans/PHASE_3a_CORE_DIRECTOR.md`
> **Source plan:** `plans/PHASE_3a_CORE_DIRECTOR.md`
> **Scope:** Director Tool, waypoint visuals, per-stickman action queues, and an action runner executed on Play.
---
## 1. Overview & Goals
Phase 3a turns the Sandbox Stage Builder (`scripts/editors/stage/sandbox_stage.gd`) into a mini
Phase 3a turns the Sandbox Stage Builder (`scripts/sandbox_stage.gd`) into a mini
"director" tool: 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 queue.
@@ -143,7 +143,7 @@ in memory and are lost on scene reload. Save/Load is a later phase (ROADMAP).
## 4. File-by-file changes
### 4.1 `scripts/characters/stickman/stickman_rig.gd` (primary changes)
### 4.1 `scripts/stickman_rig.gd` (primary changes)
Add navigation/walking, speech, action queue, and runner. All public, strictly typed,
null-guarded, `push_warning` prefixed `"StickmanRig: "` (matching existing style).
@@ -353,7 +353,7 @@ _update_speech(delta)
_update_runner(delta)
```
### 4.2 `scripts/speech/stickman_speech_bubble.gd` (NEW)
### 4.2 `scripts/stickman_speech_bubble.gd` (NEW)
`class_name SpeechBubble extends Node2D` — a world-space bubble drawn in `_draw()`.
@@ -379,7 +379,7 @@ HORIZONTAL_ALIGNMENT_LEFT, MAX_WIDTH, FONT_SIZE)`, draws a rounded-rect backgrou
downward tail triangle centered on the rig-local origin (the origin is the anchor above the
head), then `draw_string(...)` the text. `visible = false` by default. No hit-testing.
### 4.3 `scripts/stage/stage_director_visuals.gd` (NEW)
### 4.3 `scripts/stage_director_visuals.gd` (NEW)
`class_name StageDirectorVisuals extends Node2D` — Edit-mode director overlay.
@@ -425,11 +425,11 @@ Drawing rules (per rig, in queue order `i = 0..n-1`):
- All sizes divided by `_zoom()` so markers stay screen-constant while panning/zooming
(matches `StageGizmos`).
### 4.4 `scripts/editors/stage/sandbox_stage.gd` (changes)
### 4.4 `scripts/sandbox_stage.gd` (changes)
**New preload:**
```gdscript
const STAGE_DIRECTOR_VISUALS := preload("res://scripts/stage/stage_director_visuals.gd")
const STAGE_DIRECTOR_VISUALS := preload("res://scripts/stage_director_visuals.gd")
```
**New constants (popup item ids):**
@@ -579,11 +579,11 @@ if _direct_mode:
`_director_visuals.set_enabled(...)`.
- Add `_direct_button.visible` to `_set_build_controls_visible(...)`.
### 4.5 `scripts/stage/stage_gizmos.gd`
### 4.5 `scripts/stage_gizmos.gd`
**No changes.** Director visuals live in `StageDirectorVisuals` (D7). Gizmos remain the
selection/hover/rotate layer.
### 4.6 `scenes/stage/sandbox_stage.tscn`
### 4.6 `scenes/sandbox_stage.tscn`
**No change required.** All stage UI is built in code (`_build_ui`), so the "Direct" button
is added programmatically (consistent with the existing palette/grid/snap controls). The
scene already has only `SandboxStage`, `Camera2D`, and `World` — the visuals layer and popup
@@ -740,7 +740,7 @@ do follow Edit-mode drags live.
legacy `curved_lines_2d` addon — it is not present on disk). No `res://test/` directory.
- **No CLI test command.** `project.godot` has no test autoload; `run/main_scene` is the editor.
- **Established pattern: headless scripted smoke tests** — previous phases (per `docs/phase9_*`
specs and `docs/known_issues.md`) verified work with **temporary headless `SceneTree` scripts** run
specs and `BUGS.md`) verified work with **temporary headless `SceneTree` scripts** run
against the console build, e.g.:
```
..\Godot_v4.7.1-stable_win64_console.exe . --headless --check-only --quit
@@ -754,7 +754,7 @@ do follow Edit-mode drags live.
1. **Parse check** (all changed scripts):
`..\Godot_v4.7.1-stable_win64_console.exe . --headless --check-only --quit`
2. **Headless queue/runner smoke test** (temporary `SceneTree` script, deleted after):
- `StickmanFactory.spawn_from_data(load_stk("res://content/stickmen/test.stk"))`, `add_child`,
- `StickmanFactory.spawn_from_data(load_stk("res://stickmen/test.stk"))`, `add_child`,
await a few frames.
- Queue API: `queue_action` ×3 → `queue_size()==3`, `get_queue()` order, `insert_action`,
`remove_action`, `clear_queue`.
@@ -775,7 +775,7 @@ do follow Edit-mode drags live.
`StageDirectorVisuals` with a rig that has a queue; assert `_collect_rigs()` and that
`_draw()` runs without errors (drawing can't be pixel-asserted headlessly).
### 10.3 Manual checklist (F6 on `res://scenes/stage/sandbox_stage.tscn`)
### 10.3 Manual checklist (F6 on `res://scenes/sandbox_stage.tscn`)
1. Place a Ground + a Stickman (feet on ground).
2. Select **Direct** → click the stickman → popup appears.
@@ -803,8 +803,8 @@ Two debug instrumentation gates are shipped, **off by default** (flip the const
| Gate | Location | Traces |
| ---- | -------- | ------ |
| `const DEBUG_WALK := false` | `scripts/characters/stickman/stickman_rig.gd` | Per-frame walk trace (`[walk] frame=… mode=nav\|direct finished=… reachable=… map_iter=…`) plus one-shot `walk_to` / `_finish_walk` / `_cancel_walking` / runner prints. |
| `const DEBUG_STAGE := false` | `scripts/editors/stage/sandbox_stage.gd` | `[stage] walk_to captured target=…`, `[nav] baked verts=… polys=…`, `[stage] PLAY rigs=…`. |
| `const DEBUG_WALK := false` | `scripts/stickman_rig.gd` | Per-frame walk trace (`[walk] frame=… mode=nav\|direct finished=… reachable=… map_iter=…`) plus one-shot `walk_to` / `_finish_walk` / `_cancel_walking` / runner prints. |
| `const DEBUG_STAGE := false` | `scripts/sandbox_stage.gd` | `[stage] walk_to captured target=…`, `[nav] baked verts=… polys=…`, `[stage] PLAY rigs=…`. |
These are diagnostic-only and are never enabled in production.
@@ -1,7 +1,7 @@
# Phase 3b — Asset Library: Stickman + Prop Selector Grids (Spec)
Status: IMPLEMENTED (revised by selector bugfix round, 2026-09-03)
Related plan: `docs/archive/plans/PHASE_3b_ASSET_GRID.md`
Related plan: `plans/PHASE_3b_ASSET_GRID.md`
Target: Godot **4.7** (`project.godot:19` declares `config/features=PackedStringArray("4.7", ...)`; the test runner comment in `tests/test_phase4b_stage.gd:12` names `Godot_v4.7.1`).
---
@@ -11,7 +11,7 @@ Target: Godot **4.7** (`project.godot:19` declares `config/features=PackedString
Phase 3b replaces the Sandbox Stage Builder's hard-coded single-stickman and single-prop
palette buttons with **visual selector grids**:
- Clicking **"Stickman"** opens a grid of every `.stk` file in `res://content/stickmen/`, each with a
- Clicking **"Stickman"** opens a grid of every `.stk` file in `res://stickmen/`, each with a
rendered thumbnail.
- Clicking **"Prop"** opens a grid of 4 built-in prop templates (Crate/Ball/Plank/Triangle),
each with a thumbnail + material badge.
@@ -32,12 +32,12 @@ palette buttons with **visual selector grids**:
### Out of scope / untouched (must not regress)
- **Terrain palette buttons** `Ground`/`Ramp`/`Step` and the **`Area`** button remain direct
placement buttons, unchanged (`scripts/stage/stage_spawner.gd:178-220` registry, `scripts/editors/stage/sandbox_stage.gd:1151-1157`).
placement buttons, unchanged (`scripts/stage_spawner.gd:178-220` registry, `scripts/sandbox_stage.gd:1151-1157`).
- **Terrain drag-painting** (`_begin_terrain_drag` / `_update_terrain_drag` /
`_commit_terrain_drag`, `scripts/editors/stage/sandbox_stage.gd:712-869`) — untouched; `prop`/`stickman` are
`_commit_terrain_drag`, `scripts/sandbox_stage.gd:712-869`) — untouched; `prop`/`stickman` are
non-terrain ids so they keep single-placement via `_place_at`.
- **Ghost system** (`_spawn_ghost`, `_configure_ghost_collision`, `_update_ghost_position`,
`scripts/editors/stage/sandbox_stage.gd:614-668`) — untouched; the ghost automatically reflects the selected
`scripts/sandbox_stage.gd:614-668`) — untouched; the ghost automatically reflects the selected
asset once `selected_stickman_path`/`selected_prop_id` are honored by `spawn()`.
- **Director / rule-builder** flows (Phase 3a/4) — untouched.
- No `.stk` format changes; no editor changes.
@@ -53,8 +53,8 @@ palette buttons with **visual selector grids**:
`StickmanFactory.spawn_from_data(stk_data)` in an offscreen `SubViewport` — identical to what
gets placed on stage (not a re-implementation of `WholeStickmanPreview`).
3. **Prop set: ALL 4 TEMPLATES** — Crate/Wood, Ball/Rubber, Plank/Metal, Triangle/Cardboard.
4. **Selector build: SEPARATE `.tscn` SCENE** — `res://scenes/stage/asset_selector.tscn` with root
script `scripts/shared/ui/asset_selector.gd`. The `.tscn` is a minimal **shell/layout skeleton only**
4. **Selector build: SEPARATE `.tscn` SCENE** — `res://scenes/asset_selector.tscn` with root
script `scripts/asset_selector.gd`. The `.tscn` is a minimal **shell/layout skeleton only**
(title bar, empty grid placeholder, footer controls as authored nodes); all dynamic cell
content is still built in code at runtime. `sandbox_stage.gd` instantiates the scene and
applies its `_ui_font`/`_emoji_font`/theme overrides after instantiation.
@@ -65,15 +65,15 @@ palette buttons with **visual selector grids**:
| File | `class_name` / extends | Responsibility |
|---|---|---|
| `res://scripts/characters/stickman/stickman_library.gd` | `StickmanLibrary` / `RefCounted` | Scan `res://content/stickmen/*.stk` → entry models; display-name fallback; corrupt-file skip; ad-hoc Browse entries |
| `res://scripts/props/prop_library.gd` | `PropLibrary` / `RefCounted` | Static registry of the 4 prop templates (id → payload + material preset + label) |
| `res://scripts/shared/thumbnails/thumbnail_cache.gd` | `ThumbnailCache` / `RefCounted` | Disk PNG cache: key computation, load/save, invalidation, stale cleanup |
| `res://scripts/shared/thumbnails/stickman_thumbnail.gd` | `StickmanThumbnail` / `Node` | Render a parsed `.stk` → `Texture2D` via real rig in offscreen `SubViewport` |
| `res://scripts/shared/thumbnails/prop_thumbnail.gd` | `PropThumbnail` / `Node` | Render a prop template → `Texture2D` (lightweight visual, no physics) |
| `res://scripts/shared/ui/asset_selector.gd` | `AssetSelector` / `PopupPanel` | Grid UI controller (root of the `.tscn` shell) |
| `res://scenes/stage/asset_selector.tscn` | `PopupPanel` root + `asset_selector.gd` | Shell: title bar, empty `GridContainer`, footer buttons |
| `res://scripts/stickman_library.gd` | `StickmanLibrary` / `RefCounted` | Scan `res://stickmen/*.stk` → entry models; display-name fallback; corrupt-file skip; ad-hoc Browse entries |
| `res://scripts/prop_library.gd` | `PropLibrary` / `RefCounted` | Static registry of the 4 prop templates (id → payload + material preset + label) |
| `res://scripts/thumbnails/thumbnail_cache.gd` | `ThumbnailCache` / `RefCounted` | Disk PNG cache: key computation, load/save, invalidation, stale cleanup |
| `res://scripts/thumbnails/stickman_thumbnail.gd` | `StickmanThumbnail` / `Node` | Render a parsed `.stk` → `Texture2D` via real rig in offscreen `SubViewport` |
| `res://scripts/thumbnails/prop_thumbnail.gd` | `PropThumbnail` / `Node` | Render a prop template → `Texture2D` (lightweight visual, no physics) |
| `res://scripts/asset_selector.gd` | `AssetSelector` / `PopupPanel` | Grid UI controller (root of the `.tscn` shell) |
| `res://scenes/asset_selector.tscn` | `PopupPanel` root + `asset_selector.gd` | Shell: title bar, empty `GridContainer`, footer buttons |
The shared thumbnail scripts now live under `res://scripts/shared/thumbnails/`.
Directory `res://scripts/thumbnails/` does not exist yet — create it.
---
@@ -83,7 +83,7 @@ The shared thumbnail scripts now live under `res://scripts/shared/thumbnails/`.
```gdscript
{
"path": String, # "res://content/stickmen/bob.stk" (or a Browse-chosen arbitrary path)
"path": String, # "res://stickmen/bob.stk" (or a Browse-chosen arbitrary path)
"name": String, # display name: stickman_name if non-empty, else filename basename
"data": Dictionary, # parsed JSON root (parse failures skip the entry entirely — never {})
}
@@ -113,19 +113,19 @@ Default materials (per decision 3):
| `plank` | Plank | `PropUtils.create_plank()` | `PropBlock.MaterialPreset.METAL` | Metal |
| `triangle` | Triangle | `PropUtils.create_triangle()` | `PropBlock.MaterialPreset.CARDBOARD` | Cardboard |
The generator color themes already match these presets (`scripts/props/prop_utils.gd:23-30`).
The generator color themes already match these presets (`scripts/prop_utils.gd:23-30`).
---
## 5. Public API Signatures (GDScript)
### 5.1 `StickmanLibrary` (`res://scripts/characters/stickman/stickman_library.gd`)
### 5.1 `StickmanLibrary` (`res://scripts/stickman_library.gd`)
```gdscript
class_name StickmanLibrary
extends RefCounted
const STICKMEN_DIR := "res://content/stickmen"
const STICKMEN_DIR := "res://stickmen"
var entries: Array[Dictionary] = [] # last scan result (empty until scan())
@@ -142,7 +142,7 @@ func find_by_path(path: String) -> Dictionary # {} if absent
func make_entry(path: String) -> Dictionary # ad-hoc (Browse): load_stk + name fallback; {} on failure
```
### 5.2 `PropLibrary` (`res://scripts/props/prop_library.gd`)
### 5.2 `PropLibrary` (`res://scripts/prop_library.gd`)
```gdscript
class_name PropLibrary
@@ -159,7 +159,7 @@ static func get_default_id() -> String # "crate"
Note: `PropUtils.create_*()` are `static func` — they **cannot** run in a `const` initializer, so
`_templates` is a `static var` built on first access.
### 5.3 `ThumbnailCache` (`res://scripts/shared/thumbnails/thumbnail_cache.gd`)
### 5.3 `ThumbnailCache` (`res://scripts/thumbnails/thumbnail_cache.gd`)
```gdscript
class_name ThumbnailCache
@@ -180,7 +180,7 @@ func ensure_dir(dir: String) -> void # DirAccess.make_dir_recursive_abso
func clean_stale_stickmen(valid_keys: Dictionary) -> void # optional: delete basename_* not in valid set
```
### 5.4 `StickmanThumbnail` (`res://scripts/shared/thumbnails/stickman_thumbnail.gd`)
### 5.4 `StickmanThumbnail` (`res://scripts/thumbnails/stickman_thumbnail.gd`)
```gdscript
class_name StickmanThumbnail
@@ -193,7 +193,7 @@ func render(stk_data: Dictionary) -> Texture2D
# Returns a placeholder ImageTexture on blank/failed capture (headless degrade).
```
### 5.5 `PropThumbnail` (`res://scripts/shared/thumbnails/prop_thumbnail.gd`)
### 5.5 `PropThumbnail` (`res://scripts/thumbnails/prop_thumbnail.gd`)
```gdscript
class_name PropThumbnail
@@ -206,7 +206,7 @@ func render(payload: Dictionary, material_preset: int) -> Texture2D
# tinted via PropBlock.tint_for(material_preset) when preset != NONE; frames + captures.
```
### 5.6 `AssetSelector` (`res://scripts/shared/ui/asset_selector.gd`, root of `.tscn`)
### 5.6 `AssetSelector` (`res://scripts/asset_selector.gd`, root of `.tscn`)
```gdscript
class_name AssetSelector
@@ -283,7 +283,7 @@ func _frame_camera(bbox: Rect2) -> void:
### 6.2 Prop (lightweight visual — no physics)
Mirror `PropBlock._apply_polygon_geometry()` / `_apply_circle_geometry()`
(`scripts/props/prop_block.gd:169-194`) into a plain `Node2D` with a `Polygon2D` (fill) and a `Line2D`
(`scripts/prop_block.gd:169-194`) into a plain `Node2D` with a `Polygon2D` (fill) and a `Line2D`
(outline, closed loop, round joints/caps). **Do not instantiate `PropBlock`** — it is a
`RigidBody2D` and would fall under gravity inside the SubViewport. Apply
`PropBlock.tint_for(material_preset)` for non-`NONE` presets, then the same `_frame_camera` +
@@ -313,10 +313,10 @@ double-`frame_post_draw` capture as above.
## 7. `AssetSelector` — `.tscn`-Based Design
### 7.1 Scene structure (`res://scenes/stage/asset_selector.tscn`)
### 7.1 Scene structure (`res://scenes/asset_selector.tscn`)
```
AssetSelector (PopupPanel, root, script = res://scripts/shared/ui/asset_selector.gd)
AssetSelector (PopupPanel, root, script = res://scripts/asset_selector.gd)
├── MarginContainer
│ └── VBoxContainer
│ ├── TitleBar (HBoxContainer)
@@ -364,7 +364,7 @@ relied on).
```gdscript
# preload at top:
const ASSET_SELECTOR := preload("res://scenes/stage/asset_selector.tscn")
const ASSET_SELECTOR := preload("res://scenes/asset_selector.tscn")
# in _ready(), after _build_ui():
_selector = ASSET_SELECTOR.instantiate() as AssetSelector
@@ -380,11 +380,11 @@ _selector.refresh_requested.connect(_on_refresh_requested)
If a dedicated `AssetSelector.apply_font(ui_font: Font, emoji_font: Font) -> void` helper is
preferred (walking the authored controls and adding font overrides), the stage calls it right
after `add_child` using `_ui_font`/`_emoji_font` (loaded in `_load_theme`, `scripts/editors/stage/sandbox_stage.gd:2164-2212`).
after `add_child` using `_ui_font`/`_emoji_font` (loaded in `_load_theme`, `scripts/sandbox_stage.gd:2164-2212`).
---
## 8. `StageSpawner` Precise Edits (`scripts/stage/stage_spawner.gd`)
## 8. `StageSpawner` Precise Edits (`scripts/stage_spawner.gd`)
### 8.1 New state (replace the single `_stickman_data`)
@@ -461,7 +461,7 @@ func get_selected_prop_id() -> String
---
## 9. `SandboxStage` Precise Edits (`scripts/editors/stage/sandbox_stage.gd`)
## 9. `SandboxStage` Precise Edits (`scripts/sandbox_stage.gd`)
### 9.1 New state
@@ -493,7 +493,7 @@ add_child(_prop_thumb)
```
Build the selector in `_build_ui()` (see §7.3) or immediately after it; build `_browse_dialog`
mirroring `scripts/tools/test_harness.gd:286-292`:
mirroring `scripts/test_harness.gd:286-292`:
```gdscript
_browse_dialog = FileDialog.new()
@@ -558,7 +558,7 @@ func _on_selector_cancelled() -> void:
set_placement_mode("") # un-press the palette button
```
`set_placement_mode` (`scripts/editors/stage/sandbox_stage.gd:566-576`) already loops `_palette_buttons` to sync
`set_placement_mode` (`scripts/sandbox_stage.gd:566-576`) already loops `_palette_buttons` to sync
pressed state, so it correctly presses the `stickman`/`prop` button on selection and un-presses on
cancel.
@@ -609,7 +609,7 @@ selection.
### 9.8 Guards
While `_selector_open`, `_handle_world_click` (`scripts/editors/stage/sandbox_stage.gd:360-409`) and
While `_selector_open`, `_handle_world_click` (`scripts/sandbox_stage.gd:360-409`) and
`_handle_mouse_motion` (lines 412-426) early-return at the top (the modal `PopupPanel` already
blocks most input, but the stage uses `_input` for mouse — the explicit flag is belt-and-suspenders).
@@ -627,7 +627,7 @@ blocks most input, but the stage uses `_input` for mouse — the explicit flag i
| Modified `.stk` | New cache key (mtime-embedded) → regenerate thumbnail |
| Thumbnail render fails (headless / blank) | Placeholder texture; retried next open |
| Selected file deleted | On open, if `selected_stickman_path` not in scan → fall back to first entry (or empty state) |
| Browse-selected file (outside `content/stickmen/`) | Spawnable by path via `make_entry`; not rescanned; re-validated next open |
| Browse-selected file (outside `stickmen/`) | Spawnable by path via `make_entry`; not rescanned; re-validated next open |
| Duplicate display names | Allowed; selection is by path/id, not name |
| Grid resize | `GridContainer` reflows; page bounds recomputed on `resized` |
| Window resize | Selector re-centers (`popup_centered()` re-run on the root's `size_changed` while visible); stage behind the grid is dimmed by `_selector_dim` while open |
@@ -641,7 +641,7 @@ blocks most input, but the stage uses `_input` for mouse — the explicit flag i
### 11.1 Stickman selector
- [ ] "Stickman" opens the grid (does not immediately spawn `test.stk`).
- [ ] Grid lists all `.stk` in `res://content/stickmen/` (test/basic/break).
- [ ] Grid lists all `.stk` in `res://stickmen/` (test/basic/break).
- [ ] Each cell shows a rig-rendered thumbnail.
- [ ] Name = `stickman_name`, else filename (`test`/`break` fall back; `Basic` shows "Basic").
- [ ] Click → select + close + enter placement (ghost appears).
@@ -699,7 +699,7 @@ Cover (no pixel assertions — headless renders blank):
(no `crate`/`ball`).
6. `_spawn_prop` honors `selected_prop_id`: default spawns a `PropBlock` (crate); set
`selected_prop_id = "ball"` → spawns a circle prop (`shape_type == ShapeType.CIRCLE`).
7. `_spawn_stickman` honors `selected_stickman_path`: set it to `res://content/stickmen/basic.stk` → spawns
7. `_spawn_stickman` honors `selected_stickman_path`: set it to `res://stickmen/basic.stk` → spawns
a `StickmanRig` whose mounted geometry differs from `test.stk` (or at minimum spawns non-null).
8. `ThumbnailCache`: `stickman_key(path)` changes when `FileAccess.get_modified_time` changes
(mock by passing a path + expected mtime); `stickman_png`/`prop_png` path formatting;
@@ -722,7 +722,7 @@ Cover (no pixel assertions — headless renders blank):
### 12.4 Manual (F6)
`res://scenes/stage/sandbox_stage.tscn`: visual grid, thumbnails, pagination, Browse, Refresh,
`res://scenes/sandbox_stage.tscn`: visual grid, thumbnails, pagination, Browse, Refresh,
ghost/placement, mode-toggle persistence.
---
@@ -1,7 +1,7 @@
# Phase 3c — Editor Tools: Action & Rule Editing (Implementation Spec)
Status: IMPLEMENTED + TESTED (12 headless suites, 667 assertions; `tests/test_phase3c_editor.gd` alone: 253 assertions)
Related plan: `docs/archive/plans/PHASE_3c_EDITOR.md`
Related plan: `plans/PHASE_3c_EDITOR.md`
Target: Godot **4.7** (`project.godot:19` declares `config/features=PackedStringArray("4.7", ...)`; the Phase 3c test header names `Godot_v4.7.1-stable_win64_console.exe`).
---
@@ -26,16 +26,16 @@ one-entry registry append (plan §4/§10) — no other code changes.
### In scope
- `scripts/stage/action_registry.gd` / `trigger_registry.gd` — extensible template registries.
- `scripts/editors/stage/queue_panel.gd` + `scenes/editors/stage/queue_panel.tscn` — Action Queue panel.
- `scripts/editors/stage/rule_panel.gd` + `scenes/editors/stage/rule_panel.tscn` — Rule list panel.
- `scripts/editors/stage/action_editor.gd` + `scenes/editors/stage/action_editor.tscn` — single-action property editor.
- `scripts/editors/stage/rule_editor.gd` + `scenes/editors/stage/rule_editor.tscn` — full / consequence-only rule editor.
- `scripts/stage/waypoint_context.gd` — waypoint right-click menu.
- `scripts/editors/stage/sandbox_stage.gd` — Phase 3c wiring: "Edit Queue…"/"Edit Rules…" entry points,
- `scripts/action_registry.gd` / `trigger_registry.gd` — extensible template registries.
- `scripts/queue_panel.gd` + `scenes/queue_panel.tscn` — Action Queue panel.
- `scripts/rule_panel.gd` + `scenes/rule_panel.tscn` — Rule list panel.
- `scripts/action_editor.gd` + `scenes/action_editor.tscn` — single-action property editor.
- `scripts/rule_editor.gd` + `scenes/rule_editor.tscn` — full / consequence-only rule editor.
- `scripts/waypoint_context.gd` — waypoint right-click menu.
- `scripts/sandbox_stage.gd` — Phase 3c wiring: "Edit Queue…"/"Edit Rules…" entry points,
right-click context menus, the **unified target-capture system** (`CaptureKind`), the shared
confirmation dialog, and consequence-only rule editing on rule-label click.
- `scripts/stage/stage_director_visuals.gd` — `hit_test_waypoint_action()`, `set/clear_edit_waypoint`
- `scripts/stage_director_visuals.gd` — `hit_test_waypoint_action()`, `set/clear_edit_waypoint`
with the pulsing edit highlight, and rule-label → editor routing.
- `tests/test_phase3c_editor.gd` — 253-assertion headless suite.
@@ -95,20 +95,20 @@ one-entry registry append (plan §4/§10) — no other code changes.
| File | `class_name` / extends | Responsibility |
|---|---|---|
| `res://scripts/stage/action_registry.gd` | `ActionRegistry` / `RefCounted` | `ACTION_TEMPLATES` (5 actions), static accessors + queue⇄rule conversions + summaries |
| `res://scripts/stage/trigger_registry.gd` | `TriggerRegistry` / `RefCounted` | `TRIGGER_TEMPLATES` (5 triggers), static accessors + summaries |
| `res://scripts/editors/stage/queue_panel.gd` | `QueuePanel` / `PopupPanel` | Action Queue editor popup (root of `queue_panel.tscn`) |
| `res://scripts/editors/stage/rule_panel.gd` | `RulePanel` / `PopupPanel` | Rule list editor popup (root of `rule_panel.tscn`) |
| `res://scripts/editors/stage/action_editor.gd` | `ActionEditor` / `PopupPanel` | Single-action add/edit editor (root of `action_editor.tscn`) |
| `res://scripts/editors/stage/rule_editor.gd` | `RuleEditor` / `PopupPanel` | Full + consequence-only rule editor (root of `rule_editor.tscn`) |
| `res://scripts/stage/waypoint_context.gd` | `WaypointContext` / `PopupMenu` | Waypoint right-click menu |
| `res://scenes/editors/stage/queue_panel.tscn` / `rule_panel.tscn` / `action_editor.tscn` / `rule_editor.tscn` | `PopupPanel` roots | Minimal shells; UI built in code |
| `res://scripts/action_registry.gd` | `ActionRegistry` / `RefCounted` | `ACTION_TEMPLATES` (5 actions), static accessors + queue⇄rule conversions + summaries |
| `res://scripts/trigger_registry.gd` | `TriggerRegistry` / `RefCounted` | `TRIGGER_TEMPLATES` (5 triggers), static accessors + summaries |
| `res://scripts/queue_panel.gd` | `QueuePanel` / `PopupPanel` | Action Queue editor popup (root of `queue_panel.tscn`) |
| `res://scripts/rule_panel.gd` | `RulePanel` / `PopupPanel` | Rule list editor popup (root of `rule_panel.tscn`) |
| `res://scripts/action_editor.gd` | `ActionEditor` / `PopupPanel` | Single-action add/edit editor (root of `action_editor.tscn`) |
| `res://scripts/rule_editor.gd` | `RuleEditor` / `PopupPanel` | Full + consequence-only rule editor (root of `rule_editor.tscn`) |
| `res://scripts/waypoint_context.gd` | `WaypointContext` / `PopupMenu` | Waypoint right-click menu |
| `res://scenes/queue_panel.tscn` / `rule_panel.tscn` / `action_editor.tscn` / `rule_editor.tscn` | `PopupPanel` roots | Minimal shells; UI built in code |
---
## 4. Public API Signatures (GDScript)
### 4.1 `ActionRegistry` (`res://scripts/stage/action_registry.gd`)
### 4.1 `ActionRegistry` (`res://scripts/action_registry.gd`)
```gdscript
class_name ActionRegistry
@@ -140,7 +140,7 @@ static func summarize(action: Dictionary) -> String
# both the flat and rule-action key layouts via get() fallbacks.
```
### 4.2 `TriggerRegistry` (`res://scripts/stage/trigger_registry.gd`)
### 4.2 `TriggerRegistry` (`res://scripts/trigger_registry.gd`)
```gdscript
class_name TriggerRegistry
@@ -162,7 +162,7 @@ static func target_type(type: String) -> String # "waypoint" | "action_type" |
static func summarize(trigger: Dictionary) -> String # "<icon> <label>", e.g. "📍 Arrives at waypoint"
```
### 4.3 `QueuePanel` (`res://scripts/editors/stage/queue_panel.gd`, root of `queue_panel.tscn`)
### 4.3 `QueuePanel` (`res://scripts/queue_panel.gd`, root of `queue_panel.tscn`)
```gdscript
class_name QueuePanel
@@ -186,7 +186,7 @@ target, release = commit). On commit it moves the action via the rig's existing
fires and the director overlay redraws. The panel stays decoupled — it never mutates directly
beyond the reorder helper, and add/edit/delete/clear are delegated to the stage via signals.
### 4.4 `RulePanel` (`res://scripts/editors/stage/rule_panel.gd`, root of `rule_panel.tscn`)
### 4.4 `RulePanel` (`res://scripts/rule_panel.gd`, root of `rule_panel.tscn`)
```gdscript
class_name RulePanel
@@ -211,7 +211,7 @@ indented `→ ...` line per action (`<icon> <actor?> <summary>`), an order numbe
drag-reorder ≡. Reorder emits the new order of the *displayed* rule ids (see
`SandboxStage._reorder_filtered_rules`, §6) so un-filtered rules keep their slots.
### 4.5 `ActionEditor` (`res://scripts/editors/stage/action_editor.gd`, root of `action_editor.tscn`)
### 4.5 `ActionEditor` (`res://scripts/action_editor.gd`, root of `action_editor.tscn`)
```gdscript
class_name ActionEditor
@@ -233,7 +233,7 @@ no-param action) → no fields. Pressing **OK** on a `walk_to` with no target se
`target_requested()` instead of committing; once a target is captured (via
`set_walk_target`) OK emits `committed(action)`. Esc emits `cancelled()`.
### 4.6 `RuleEditor` (`res://scripts/editors/stage/rule_editor.gd`, root of `rule_editor.tscn`)
### 4.6 `RuleEditor` (`res://scripts/rule_editor.gd`, root of `rule_editor.tscn`)
```gdscript
class_name RuleEditor
@@ -263,7 +263,7 @@ target…" button (target label reads `waypoint (x, y)` / `(not set)`); `action_
an "➕ Add Action" button; ✕ removes locally (`_remove_action`). **OK** emits `committed({id,
trigger, actions})` preserving `_rule_id`. Esc emits `cancelled()`.
### 4.7 `WaypointContext` (`res://scripts/stage/waypoint_context.gd`)
### 4.7 `WaypointContext` (`res://scripts/waypoint_context.gd`)
```gdscript
class_name WaypointContext
@@ -285,7 +285,7 @@ Item order is fixed in `_init()`: Edit this Walk / Delete this Walk / ⬆ Insert
---
## 5. `SandboxStage` Phase 3c State & Entry Points (`scripts/editors/stage/sandbox_stage.gd`)
## 5. `SandboxStage` Phase 3c State & Entry Points (`scripts/sandbox_stage.gd`)
### 5.1 New state
@@ -442,7 +442,7 @@ cancel callback re-pops the editor that requested the capture (`_cb_rule_trigger
---
## 8. `StageDirectorVisuals` Extensions (`scripts/stage/stage_director_visuals.gd`)
## 8. `StageDirectorVisuals` Extensions (`scripts/stage_director_visuals.gd`)
```gdscript
# Phase 3c: the walk_to waypoint currently being visually edited (blinking highlight).
@@ -608,7 +608,7 @@ Headless `--editor --quit` rescan (to register the new `class_name`s), then per-
### 12.4 Manual (F6)
`res://scenes/stage/sandbox_stage.tscn`: queue/rule panel edit-delete-reorder, waypoint context menu,
`res://scenes/sandbox_stage.tscn`: queue/rule panel edit-delete-reorder, waypoint context menu,
visual walk edit, full vs consequence-only rule editor, rule-label click → consequence editor.
---

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