# Work Plan: Sprint 3c -- Spatial Audit, Asset Registry, and Suggest Scale Tools

Created Date: 2026-03-03
Type: feature
Estimated Duration: 3 days
Estimated Impact: 5 files (2 modified, 3 new effective + test skeletons already exist)
Related Issue/PR: Sprint 3c

## Related Documents
- Design Spec: Inline (user-provided Sprint 3c specification)
- Sprint 3b (prior): `docs/plans-s3b/work-plan.md`

## Objective

Add three new MCP tools to the GodotIQ server:
1. **godotiq_spatial_audit** (B03) -- spatial linter for 3D scenes, detecting floating objects, scale mismatches, z-fighting, empty markers, overlapping instances, and extreme positions.
2. **godotiq_asset_registry** (F01) -- asset inventory with usage tracking, categorization by extension, and unused asset detection.
3. **godotiq_suggest_scale** (F02) -- scale/position recommendation based on similar models in a scene, with fuzzy near_node search.

Upon completion, the server will have 11 registered MCP tools and 367+ passing tests.

## Background

Sprint 3b delivered 3 new code intelligence tools (signal_map, impact_check, validate) plus a scene_map focus search bug fix, bringing the test count to 342 and registered tools to 8. The spatial module already contains `_find_focus_node` for fuzzy node search, and the assets module exists as a stub with only a docstring. The test skeletons for all three new tools have been generated (RED phase) and are ready for implementation to turn them GREEN.

## Risks and Countermeasures

### Technical Risks

- **Risk**: `spatial_audit` relies on `session.resolve_scene_spatial()` returning `WorldNode` data with world transforms. Each of the 6 checks requires different spatial analysis (distances, scale comparisons, position extremes).
  - **Impact**: Incorrect threshold values could produce false positives or miss real issues.
  - **Countermeasure**: Use fixture data (fdm_printer.tscn with known nodes: FDMPrinter, PrinterModel at scale 0.35, WorkPoint, StatusLight) to calibrate thresholds. PrinterModel's scale mismatch vs siblings is a known positive.

- **Risk**: `asset_registry` depends on `session._index.resource_usage` and `os.path.getsize()` for file sizes. Missing files on disk would cause `FileNotFoundError`.
  - **Impact**: Crash when asset files are indexed but deleted from disk.
  - **Countermeasure**: Wrap `os.path.getsize()` in try/except, report `size=0` for missing files. Test skeleton already covers this case (`test_registry_missing_file`).

- **Risk**: `suggest_scale` imports `_find_focus_node` from the spatial module for near_node fuzzy search. Cross-module import coupling.
  - **Impact**: If `_find_focus_node` signature changes, suggest_scale breaks.
  - **Countermeasure**: `_find_focus_node` is already stable (implemented in Sprint 3b). Import is a one-way dependency with a frozen interface.

- **Risk**: `assets/__init__.py` needs `from godotiq.server import mcp` and `server.py` needs `from godotiq.tools import assets`. Circular import risk if not wired correctly.
  - **Impact**: Import error at server startup.
  - **Countermeasure**: Follow the exact same pattern used by `memory`, `code`, and `spatial` modules. Wire the import in Phase 1 and verify with smoke test before proceeding.

### Schedule Risks

- **Risk**: Two tools (asset_registry and suggest_scale) go into the same file (`assets/__init__.py`), which starts as a docstring-only stub. Significant new code volume in one file.
  - **Impact**: Merge conflicts if tasks are parallelized within the same file.
  - **Countermeasure**: Implement asset_registry first (simpler, no cross-module dependencies), then suggest_scale. Sequential implementation within the file.

## Phase Structure

```mermaid
graph TD
    P1["Phase 1: Server Wiring + spatial_audit<br/>(server.py + spatial/__init__.py + 11 tests)"]
    P2["Phase 2: asset_registry<br/>(assets/__init__.py + 7 tests)"]
    P3["Phase 3: suggest_scale<br/>(assets/__init__.py + 7 tests)"]
    P4["Phase 4: Quality Assurance<br/>(all tests, registration, final checks)"]

    P1 --> P2
    P1 --> P3
    P2 --> P4
    P3 --> P4
```

Note: Phase 2 and Phase 3 can run in parallel since they are independent functions in the same file. However, sequential execution is recommended to avoid merge conflicts.

## Task Dependencies

```mermaid
graph LR
    subgraph "Phase 1 - Wiring + spatial_audit"
        T1A["Add assets import<br/>to server.py"]
        T1B["Implement _build_spatial_audit<br/>in spatial/__init__.py"]
        T1C["Register godotiq_spatial_audit<br/>MCP tool"]
        T1D["Turn GREEN: 11 spatial_audit<br/>tests pass"]
    end

    subgraph "Phase 2 - asset_registry"
        T2A["Scaffold assets/__init__.py<br/>(imports + mcp)"]
        T2B["Implement _build_asset_registry"]
        T2C["Register godotiq_asset_registry<br/>MCP tool"]
        T2D["Turn GREEN: 7 asset_registry<br/>tests pass"]
    end

    subgraph "Phase 3 - suggest_scale"
        T3A["Implement _build_suggest_scale"]
        T3B["Register godotiq_suggest_scale<br/>MCP tool"]
        T3C["Turn GREEN: 7 suggest_scale<br/>tests pass"]
    end

    subgraph "Phase 4 - Quality Assurance"
        T4A["Full test suite:<br/>367+ tests pass"]
        T4B["Tool count: 11 registered"]
        T4C["Constraint verification"]
    end

    T1A --> T1B
    T1B --> T1C
    T1C --> T1D
    T1D --> T2A
    T2A --> T2B
    T2B --> T2C
    T2C --> T2D
    T1D --> T3A
    T3A --> T3B
    T3B --> T3C
    T2D --> T4A
    T3C --> T4A
    T4A --> T4B
    T4B --> T4C
```

## Implementation Phases

### Phase 1: Server Wiring + spatial_audit Implementation (Estimated commits: 2)
**Purpose**: Wire the assets module into the server import chain and implement the spatial audit tool with all 6 checks.

**Test case resolution**: 0/25 -> 11/25

#### Tasks
- [ ] **Task 1.1**: Add assets import to `server.py`
  - Add `from godotiq.tools import assets  # noqa: E402, F401` after the spatial import line
  - File: `src/godotiq/server.py` (line 51, after spatial import)
  - **Completion**: `python -c "from godotiq.server import mcp; print('OK')"` succeeds without import errors

- [ ] **Task 1.2**: Implement `_build_spatial_audit` function in `spatial/__init__.py`
  - Parameters: `session: GodotIQSession`, `scene: str`, `checks: list[str] | None = None`
  - Scene resolution via `session.resolve_scene_spatial(scene)`: return `{"error": ...}` if None
  - 6 check functions (all operate on `list[WorldNode]`):
    1. **floating_objects**: Nodes with Y position > threshold (e.g., 100) that are not Light3D or Camera3D types. Severity: warning.
    2. **scale_mismatch**: Nodes whose scale differs significantly from sibling median (e.g., >3x or <0.3x). Severity: warning. (PrinterModel at 0.35 vs siblings at 1.0 is a known positive.)
    3. **z_fighting**: Pairs of nodes at nearly identical positions (distance < 0.01). Severity: warning.
    4. **empty_markers**: Marker3D nodes with groups but no children. Severity: info.
    5. **overlapping**: Instance root nodes at identical positions. Severity: info.
    6. **extreme_positions**: Nodes positioned far from scene centroid (e.g., >1000 units). Severity: info.
  - If `checks` parameter provided, run only those checks; otherwise run all 6
  - Each issue dict: `{"check": str, "node": str, "severity": str, "detail": str, "suggestion": str, ...}` plus check-specific fields (`position`, `scale`, etc.)
  - Sort issues: warnings before info
  - Cap at 50 issues
  - Return dict: `{"scene": str, "total_issues": int, "issues": list, "by_check": dict, "checks_run": list}`
  - `by_check` maps each check name to its issue count (include all requested checks, even those with 0 issues)
  - **Completion**: Function handles all 6 checks, filtering, sorting, and capping correctly

- [ ] **Task 1.3**: Register `godotiq_spatial_audit` MCP tool with `@mcp.tool()` decorator
  - Params: `scene: str`, `checks: list[str] | None = None`, `ctx: Context = None`
  - Annotations: `readOnlyHint=True`, `destructiveHint=False`, `idempotentHint=True`, `openWorldHint=False`
  - Wrapper calls `_build_spatial_audit(session, scene, checks)`
  - **Completion**: Tool registered and callable via MCP

- [ ] **Task 1.4**: Turn GREEN -- all 11 spatial_audit tests pass
  - Verify: `pytest tests/test_tools/test_spatial_audit.py -v` -- 11 tests pass
  - Tests cover: all checks default (AC1), floating_objects structure (AC2), scale_mismatch with PrinterModel (AC3), z_fighting structure (AC4), empty_markers structure (AC5), overlapping structure (AC6), extreme_positions structure (AC7), severity ordering (AC8), 50-issue cap (AC9), specific checks filter (AC10), missing scene error (AC11)
  - **Completion**: All 11 tests GREEN, zero failures

#### Phase Completion Criteria
- [ ] `server.py` imports assets module without errors
- [ ] `_build_spatial_audit` handles all 6 checks with correct issue structure
- [ ] `checks` parameter filters to only specified checks
- [ ] Issues sorted by severity (warnings before info) and capped at 50
- [ ] `by_check` dict includes counts for all requested checks
- [ ] Missing scene returns `{"error": ...}` (not exception)
- [ ] All 11 spatial_audit tests pass
- [ ] All 342 existing tests still pass (342 + 11 = 353)

#### Operational Verification Procedures
1. Run `pytest tests/test_tools/test_spatial_audit.py -v` -- all 11 tests pass
2. Run `pytest tests/ -v` -- all 353 tests pass (342 existing + 11 new)
3. Run `python -c "from godotiq.server import mcp; print('OK')"` -- no import errors
4. Verify scale_mismatch: `_build_spatial_audit(session, scene)` flags PrinterModel (scale 0.35 vs siblings at 1.0)
5. Verify check filter: `_build_spatial_audit(session, scene, checks=["floating_objects"])` returns only floating_objects issues
6. Verify missing scene: `_build_spatial_audit(session, "res://nonexistent.tscn")` returns error dict

---

### Phase 2: asset_registry Implementation (Estimated commits: 1)
**Purpose**: Implement the asset inventory tool with usage tracking and categorization.

**Dependencies**: Phase 1 complete (server wiring for assets module must be in place).

**Test case resolution**: 11/25 -> 18/25

#### Tasks
- [ ] **Task 2.1**: Scaffold `assets/__init__.py` with imports and mcp registration
  - Add `from godotiq.server import mcp` and necessary imports
  - Add `from godotiq.session import GodotIQSession`
  - Add `from mcp.server.fastmcp import Context`
  - Add `import os` (for `os.path.getsize`)
  - File: `src/godotiq/tools/assets/__init__.py`
  - **Completion**: Module imports succeed, no circular import issues

- [ ] **Task 2.2**: Implement `_build_asset_registry` function in `assets/__init__.py`
  - Parameters: `session: GodotIQSession`, `category: str = "all"`, `check_usage: bool = True`
  - Asset categorization by extension:
    - `models`: `.glb`, `.gltf`, `.obj`, `.fbx`
    - `textures`: `.png`, `.jpg`, `.jpeg`, `.svg`, `.webp`
    - `audio`: `.wav`, `.ogg`, `.mp3`
    - `fonts`: `.ttf`, `.otf`, `.woff`
    - `scenes`: `.tscn`, `.scn`
    - `resources`: `.tres`, `.res`
    - `shaders`: `.gdshader`, `.shader`
    - `other`: anything else
  - Scan `session._index.assets` (or equivalent) for all asset paths
  - If `category != "all"`, filter to that category only
  - Usage tracking via `session._index.resource_usage`: classify each asset as used/unused
  - File sizes via `os.path.getsize()` with try/except for missing files (size=0)
  - Build per-asset entries: `{"path": str, "type": str, "size": int, "size_bytes": int, "used": bool}`
  - Build `by_category` dict: category name -> count
  - Build `by_directory` dict: directory path -> count
  - Build `unused_list`: unused assets sorted by size descending, capped at 30
  - Return dict: `{"total_assets": int, "by_category": dict, "by_directory": dict, "assets": list, "used_count": int, "unused_count": int, "unused_list": list}`
  - **Completion**: Function categorizes, tracks usage, and reports sizes correctly

- [ ] **Task 2.3**: Register `godotiq_asset_registry` MCP tool with `@mcp.tool()` decorator
  - Params: `category: str = "all"`, `check_usage: bool = True`, `ctx: Context = None`
  - Annotations: `readOnlyHint=True`, `destructiveHint=False`, `idempotentHint=True`, `openWorldHint=False`
  - **Completion**: Tool registered and callable

- [ ] **Task 2.4**: Turn GREEN -- all 7 asset_registry tests pass
  - Verify: `pytest tests/test_tools/test_asset_registry.py -v` -- 7 tests pass
  - Tests cover: total_assets + by_category (AC1), category filter (AC2), usage tracking (AC3), unused list with paths/sizes (AC4), unused cap at 30 (AC5), by_directory grouping (AC6), missing file size=0 (AC7)
  - **Completion**: All 7 tests GREEN, zero failures

#### Phase Completion Criteria
- [ ] Asset categorization by extension works for all categories
- [ ] Category filter returns only matching assets
- [ ] Usage tracking correctly identifies used and unused assets
- [ ] Unused list sorted by size, capped at 30
- [ ] Missing files on disk reported with size=0 (no crash)
- [ ] `by_directory` groups assets by directory path
- [ ] All 7 asset_registry tests pass
- [ ] All prior tests still pass (353 + 7 = 360)

#### Operational Verification Procedures
1. Run `pytest tests/test_tools/test_asset_registry.py -v` -- all 7 tests pass
2. Run `pytest tests/ -v` -- all 360 tests pass (353 + 7)
3. Verify category filter: `_build_asset_registry(session, category="models")` returns only model assets
4. Verify usage: printer_standard.glb is marked as used, unused_model.glb as unused
5. Verify missing file handling: injected fake asset with non-existent path reports size=0

---

### Phase 3: suggest_scale Implementation (Estimated commits: 1)
**Purpose**: Implement the scale/position recommendation tool using similar model analysis and fuzzy node search.

**Dependencies**: Phase 1 complete (requires `_find_focus_node` from spatial module for near_node search). Can run in parallel with Phase 2.

**Test case resolution**: 18/25 -> 25/25

#### Tasks
- [ ] **Task 3.1**: Implement `_build_suggest_scale` function in `assets/__init__.py`
  - Parameters: `session: GodotIQSession`, `scene: str`, `model: str`, `near_node: str | None = None`, `intended_use: str = ""`
  - Scene resolution via `session.resolve_scene_spatial(scene)`: return `{"error": ...}` if None
  - Similar model search in scene WorldNodes:
    1. **Exact match**: nodes whose `instance_source` matches `model` path exactly
    2. **Directory match**: nodes whose `instance_source` is in the same directory as `model`
    3. **Group match**: nodes in same groups as directory-matched nodes
  - If references found: compute median scale from `wn.world_scale` across reference nodes
  - If no references found: return `recommended_scale: None` with a suggestion/note
  - `near_node` processing:
    - Import `_find_focus_node` from `godotiq.tools.spatial`
    - Use `_find_focus_node(world_nodes, near_node)` to find the reference node
    - If found: compute `position_suggestion` as the near_node's world position (offset slightly)
    - If not found: omit `position_suggestion` or set to None
  - Return dict: `{"scene": str, "model": str, "recommended_scale": list[float] | None, "reference_models": list, "reasoning": str, "position_suggestion": list[float] | None, "suggestion": str | None, "note": str | None}`
  - `reasoning` must mention "median" or "reference" when scale is computed
  - **Completion**: Function finds similar models, computes median scale, and handles near_node

- [ ] **Task 3.2**: Register `godotiq_suggest_scale` MCP tool with `@mcp.tool()` decorator
  - Params: `scene: str`, `model: str`, `near_node: str | None = None`, `intended_use: str = ""`, `ctx: Context = None`
  - Annotations: `readOnlyHint=True`, `destructiveHint=False`, `idempotentHint=True`, `openWorldHint=False`
  - **Completion**: Tool registered and callable

- [ ] **Task 3.3**: Turn GREEN -- all 7 suggest_scale tests pass
  - Verify: `pytest tests/test_tools/test_suggest_scale.py -v` -- 7 tests pass
  - Tests cover: references found with recommended_scale (AC1), no references returns null scale + suggestion (AC2), near_node position suggestion (AC3), near_node fuzzy search (AC4), reasoning explains median (AC5), missing scene error (AC6), missing model still works (AC7)
  - **Completion**: All 7 tests GREEN, zero failures

#### Phase Completion Criteria
- [ ] Similar model search works (exact, directory, group matching)
- [ ] Median scale computed correctly from reference models
- [ ] No references: returns `recommended_scale: None` with explanation
- [ ] `near_node` provides `position_suggestion` using `_find_focus_node` fuzzy search
- [ ] Case-insensitive/substring near_node search works (e.g., "workpoint" finds "WorkPoint")
- [ ] `reasoning` field mentions "median" or "reference"
- [ ] Missing scene returns error dict (not exception)
- [ ] Model not in scene yet: no error, uses scene context
- [ ] All 7 suggest_scale tests pass
- [ ] All prior tests still pass (360 + 7 = 367)

#### Operational Verification Procedures
1. Run `pytest tests/test_tools/test_suggest_scale.py -v` -- all 7 tests pass
2. Run `pytest tests/ -v` -- all 367 tests pass (360 + 7)
3. Verify with fixture: `_build_suggest_scale(session, scene, "res://assets/models/printers/printer_standard.glb")` returns `recommended_scale` and `reference_models`
4. Verify no references: `_build_suggest_scale(session, scene, "res://assets/models/some_unique_model.glb")` returns `recommended_scale: None`
5. Verify near_node: `_build_suggest_scale(session, scene, model, near_node="WorkPoint")` returns `position_suggestion` list of 3 floats
6. Verify fuzzy near_node: `near_node="workpoint"` (lowercase) still finds WorkPoint

---

### Phase 4: Quality Assurance (Estimated commits: 1)
**Purpose**: Final verification -- all tests pass, 11 tools registered, no regressions, all constraints respected.

**Test case resolution**: 25/25 (all resolved)

#### Tasks
- [ ] **Task 4.1**: Run full test suite
  - `pytest tests/ -v` -- ALL tests pass
  - Expected count: 342 (existing) + 11 (spatial_audit) + 7 (asset_registry) + 7 (suggest_scale) = 367+ tests
  - **Completion**: Zero failures

- [ ] **Task 4.2**: Verify tool registration count
  - `python -c "from godotiq.server import mcp; print(len(mcp._tool_manager._tools))"`
  - Expected: 11 tools (ping + project_summary + file_context + dependency_graph + scene_map + signal_map + impact_check + validate + spatial_audit + asset_registry + suggest_scale)
  - **Completion**: 11 tools registered

- [ ] **Task 4.3**: Verify no parser or session modifications
  - Confirm `src/godotiq/parsers/` files are unmodified (`git diff`)
  - Confirm `src/godotiq/session.py` is unmodified
  - **Completion**: Constraints respected

- [ ] **Task 4.4**: Verify no external dependencies added
  - Check `pyproject.toml` -- only `mcp>=1.0.0` runtime dependency
  - **Completion**: No new dependencies

- [ ] **Task 4.5**: Smoke test all new tools via internal functions
  - `_build_spatial_audit(session, scene)` -- returns `total_issues` as int, `by_check` has 6 keys
  - `_build_asset_registry(session)` -- returns `total_assets` >= 2, `by_category` dict
  - `_build_suggest_scale(session, scene, model)` -- returns `recommended_scale` and `reasoning`
  - **Completion**: All smoke tests produce valid output

- [ ] **Task 4.6**: Verify existing tools unaffected
  - Run `pytest tests/test_tools/test_scene_map.py -v` -- all existing scene_map tests pass
  - Run `pytest tests/test_tools/test_signal_map.py -v` -- all signal_map tests pass
  - Run `pytest tests/test_tools/test_impact_check.py -v` -- all impact_check tests pass
  - Run `pytest tests/test_tools/test_validate.py -v` -- all validate tests pass
  - **Completion**: No regressions in any existing tool

#### Phase Completion Criteria
- [ ] 367+ tests pass with zero failures
- [ ] 11 MCP tools registered
- [ ] No parser/session file modifications
- [ ] No new external dependencies
- [ ] All smoke tests pass
- [ ] No regressions in existing tools

#### Operational Verification Procedures
1. `pytest tests/ -v` -- 367+ tests, 0 failures
2. `python -c "from godotiq.server import mcp; print('OK')"` -- no import errors
3. Verify tool count matches spec (11 total)
4. `git diff --name-only` shows only expected files modified/added
5. `git diff src/godotiq/parsers/` -- no changes
6. `git diff src/godotiq/session.py` -- no changes

---

## File Change Summary

| File | Action | Phase | Description |
|------|--------|-------|-------------|
| `src/godotiq/server.py` | MODIFY | 1 | Add `from godotiq.tools import assets` import |
| `src/godotiq/tools/spatial/__init__.py` | MODIFY | 1 | Add `_build_spatial_audit` + `godotiq_spatial_audit` tool |
| `src/godotiq/tools/assets/__init__.py` | MODIFY | 2, 3 | Replace docstring-only stub with full module: `_build_asset_registry`, `_build_suggest_scale`, both MCP tools |
| `tests/test_tools/test_spatial_audit.py` | EXISTS (RED) | 1 | 11 tests turn GREEN |
| `tests/test_tools/test_asset_registry.py` | EXISTS (RED) | 2 | 7 tests turn GREEN |
| `tests/test_tools/test_suggest_scale.py` | EXISTS (RED) | 3 | 7 tests turn GREEN |

## Completion Criteria
- [ ] All phases completed
- [ ] Each phase's operational verification procedures executed
- [ ] 367+ total tests pass (342 existing + 25 new)
- [ ] 11 MCP tools registered (8 from S3/S3b + 3 new)
- [ ] spatial_audit runs 6 checks with severity ordering and 50-issue cap
- [ ] asset_registry categorizes assets, tracks usage, caps unused at 30
- [ ] suggest_scale computes median scale from similar models, supports fuzzy near_node search
- [ ] No parser or session.py modifications
- [ ] No new external dependencies added

## Progress Tracking

### Phase 1: Server Wiring + spatial_audit
- Start:
- Complete:
- Notes:

### Phase 2: asset_registry
- Start:
- Complete:
- Notes:

### Phase 3: suggest_scale
- Start:
- Complete:
- Notes:

### Phase 4: Quality Assurance
- Start:
- Complete:
- Notes:

## Notes

### Implementation Order Rationale
1. **Phase 1 (spatial_audit + wiring)** comes first because it wires the assets module into server.py (prerequisite for Phase 2/3) and spatial_audit is self-contained within the spatial module with no new-module dependencies.
2. **Phase 2 (asset_registry)** scaffolds assets/__init__.py with the mcp import and implements the simpler of the two asset tools (no cross-module dependencies).
3. **Phase 3 (suggest_scale)** depends on `_find_focus_node` from spatial (already stable) and builds on the assets module scaffolded in Phase 2.
4. **Phase 4 (QA)** is the required final quality gate.

Phases 2 and 3 are structurally parallelizable (independent functions) but sequential execution is recommended since they share the same file.

### Key API Patterns to Follow
- All tools use `@mcp.tool()` decorator with readOnlyHint annotations
- All tool functions are `async def` returning `dict`
- Internal implementation is in synchronous `_build_*` functions (testable without MCP context)
- Session access: `ctx.request_context.lifespan_context["session"]`
- Scene resolution: `session.resolve_scene_spatial(scene)` returns `(resolved, world_nodes)` or `None`
- Fuzzy node search: `_find_focus_node(world_nodes, name)` returns `(node, alternatives)`

### Test Meta Information Summary (extracted from test skeletons)

| Test File | Tests | Categories | Max Complexity | Dependencies |
|-----------|-------|-----------|----------------|--------------|
| test_spatial_audit.py | 11 | core-functionality (7), edge-case (3), integration (1) | medium | GodotIQSession, scene resolver, world transforms |
| test_asset_registry.py | 7 | core-functionality (4), edge-case (2), integration (1) | medium | GodotIQSession, ProjectIndex, resource_usage |
| test_suggest_scale.py | 7 | core-functionality (4), edge-case (1), integration (1) | high | GodotIQSession, scene resolver, fuzzy search |

### Constraint Reminders
- DO NOT modify parser files (`src/godotiq/parsers/`)
- DO NOT modify `session.py`
- DO NOT modify existing tools (code, memory, spatial scene_map)
- DO NOT break existing 342 tests
- DO NOT add external dependencies

### Test Fixture Data Available
- 4 scripts: `printer.gd`, `worker.gd`, `economy_manager.gd`, `progression_manager.gd`
- 2 scenes: `fdm_printer.tscn` (4 nodes: FDMPrinter root, PrinterModel at scale 0.35, WorkPoint, StatusLight), `main.tscn`
- 3 autoloads: GameManager, EconomyManager, ProgressionManager
- Asset files: `printer_standard.glb` (used by fdm_printer.tscn), `unused_model.glb` (not referenced)
