# Sprint 1B+1C Research Findings

## 1. Codebase Analysis

### 1.1 FileCache Implementation
**File:** `src/godotiq/cache/file_cache.py`

- `CacheEntry` dataclass: stores `path`, `mtime`, `content_hash`, `data`
- `FileCache` class with methods:
  - `is_stale(path) -> bool` — checks if mtime differs or entry missing
  - `get(path) -> Any | None` — returns cached data if not stale
  - `put(path, data) -> None` — stores data with current mtime + SHA-256 hash
  - `invalidate(path) -> None` — removes specific entry
  - `clear() -> None` — wipes all entries
- No external dependencies, mtime-based staleness, silent on file errors

### 1.2 Test Fixtures Instance Structure

**main.tscn** (876 nodes, 68 ext_resources):
- Instances `res://scenes/equipment/fdm_printer.tscn` via ExtResource("1_lquwl")
- Instances `res://scenes/equipment/door_sliding.tscn` via ExtResource("5_jq2sk")
- Many GLB instances (floor tiles, models) — NOT .tscn, should not be recursed
- Instance nodes have `instance=ExtResource("id")` without explicit `type` attribute

**fdm_printer.tscn** (4 nodes, 3 ext_resources):
- Script: `res://scripts/entities/printer.gd`
- Instances GLB models: `printer_standard.glb`, `filament_spool.glb`
- Groups: `["interactable"]` on root
- Structure: FDMPrinter → StaticBody3D/CollisionShape3D, PrinterModel (GLB), FilamentSpool (GLB), WorkPoint

**worker.tscn** — No scene instances (simple CharacterBody3D with script)

### 1.3 res:// Path Usage
- Stored as opaque strings in ExtResource.path
- No filesystem resolution exists in current codebase
- Scene Resolver needs to implement: `res://X → project_root/X`

### 1.4 Existing Query Methods on TscnScene
- `find_node(path)` — slash-separated path from root
- `get_nodes_by_group(group)` — filter by group membership
- `get_nodes_by_type(type_name)` — filter by exact type match
- `get_all_scripts()` — unique script res:// paths
- `get_all_instances()` — unique instanced scene res:// paths
- Instance/script fields on TscnNode are ExtResource IDs, not paths

### 1.5 Conftest Fixtures
- `project_root()` fixture returns project root Path
- `tmp_config()` creates temp .godotiq.json
- FIXTURES path: `Path(__file__).resolve().parent.parent / "fixtures"`

---

## 2. Web Research

### 2.1 Transform3D Multiplication

**World-space accumulation order:** `world_transform = parent_transform * local_transform`

From Godot source (`transform_3d.cpp`):
```cpp
void Transform3D::operator*=(const Transform3D &p_transform) {
    origin = xform(p_transform.origin);  // rotate/scale OTHER's origin by THIS
    basis *= p_transform.basis;          // compose bases
}
```

**Composition formula:**
- `C.basis = A.basis * B.basis` (standard 3x3 matrix multiply)
- `C.origin = A.basis.xform(B.origin) + A.origin`
  - Rotate/scale B's position by A's rotation/scale, then add A's translation

**For position-only calculation (simplified):**
- Without rotation: `world_pos = parent_world_pos + local_pos`
- With rotation: `world_pos = parent_world_pos + parent_basis * local_pos`

### 2.2 Basis Internal Layout

- Stored as `rows[0]`, `rows[1]`, `rows[2]` but **transposed relative to conceptual meaning**
- `basis.x` (column 0) = `Vector3(rows[0][0], rows[1][0], rows[2][0])`
- User-facing API is column-major; internal storage is row-major for cache efficiency

**Transform3D serialization in .tscn:**
```
Transform3D(col0.x, col0.y, col0.z, col1.x, col1.y, col1.z, col2.x, col2.y, col2.z, ox, oy, oz)
```

Our existing `decompose_transform3d()` already handles this correctly:
- `(values[0], values[1], values[2])` = column 0 (X axis)
- `(values[3], values[4], values[5])` = column 1 (Y axis)
- `(values[6], values[7], values[8])` = column 2 (Z axis)
- `(values[9], values[10], values[11])` = origin

### 2.3 Basis Multiplication Formula (for world-space calc)

Given two 3x3 bases A and B stored as columns:
```
result_col0 = A * B_col0 = A_col0*B[0][0] + A_col1*B[1][0] + A_col2*B[2][0]
result_col1 = A * B_col1 = A_col0*B[0][1] + A_col1*B[1][1] + A_col2*B[2][1]
result_col2 = A * B_col2 = A_col0*B[0][2] + A_col1*B[1][2] + A_col2*B[2][2]
```

**Point transformation (xform):**
```
result = basis.xform(point) + origin
       = col0*point.x + col1*point.y + col2*point.z + origin
```

### 2.4 project.godot File Format

INI-like format with sections and Godot variant values:

```ini
[application]
config/name="My Game"
run/main_scene="res://scenes/main.tscn"

[autoload]
GameManager="*res://scripts/managers/game_manager.gd"
Events="*res://scripts/autoloads/events.gd"
```

**Autoload format:**
- Key = singleton name
- Value = quoted string with res:// path
- `*` prefix = instantiate and register as global singleton
- Without `*` = loaded but not exposed as global name
- `.gd` path: creates Node with script attached
- `.tscn` path: instances the scene

### 2.5 res:// Path Resolution

**Simple 1:1 mapping:**
```
res://scenes/equipment/fdm_printer.tscn
  → project_root/scenes/equipment/fdm_printer.tscn
```

`project_root` = directory containing `project.godot`

No indirection for source files (.tscn, .gd, .tres, .gdshader). Imported binary assets go through `.godot/imported/` but that's irrelevant for source-tree resolution.

### 2.6 Scene Instancing Rules

**Instance node in parent .tscn:**
```
[node name="Printer1" parent="." instance=ExtResource("1_abc")]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 2, 0, 0)
```

**Key rules:**
1. Child nodes of instanced scene do NOT appear in parent .tscn by default
2. Only overridden properties (differing from source defaults) are stored
3. Instance root can have overridden transform, scripts, exported vars, groups
4. `[editable path="NodeName"]` enables child override listing in parent

**Resolution algorithm:**
1. Find ExtResource by ID → get res:// path
2. Parse referenced .tscn recursively
3. Start with all nodes/properties from referenced scene
4. Apply property overrides from parent file's [node] block
5. Check for `[editable path="..."]` for child overrides
6. Transform on instance root = local transform, accumulate top-down

---

## 3. Key Implementation Insights

### 3.1 Scene Resolver Design
- Only recurse into `.tscn` instances (not `.glb`, `.gltf`, `.tres`)
- Instance nodes lack `type` attribute — get type from instanced scene's root
- Property overrides: parent file's node properties replace instanced scene's defaults
- GLB/GLTF instances should be noted as "external model" but not expanded

### 3.2 World Transform Calculation
- Walk tree top-down accumulating transforms
- For each child: `child_world = parent_world_transform * child_local_transform`
- Already have `decompose_transform3d()` for extracting position/rotation/scale from 12 values
- Need `compose_transform3d()` or direct matrix multiplication for accumulation
- Simpler approach: work with raw 3x3+origin instead of decomposed euler angles

### 3.3 Project Index Design
- FileCache already suitable for caching parsed TscnScene objects
- res:// → filesystem is trivial: strip "res://" prefix, join with project_root
- project.godot is simple INI parsing — can use Python's configparser or simple regex
- Dependency graph is bidirectional: scene_instances + scene_instance_of maps
