# Sprint 1 — TSCN Parser: TDD Plan

Testing framework: **pytest** with fixtures. Async mode: `auto`. Test files in `tests/test_parsers/`. Fixtures in `tests/fixtures/`.

## Section 1: Value Parser Foundation

### Before implementing `value_parser.py`:

```python
# tests/test_parsers/test_value_parser.py

# --- Scalar parsing ---
# Test: parse_value("true") returns True
# Test: parse_value("false") returns False
# Test: parse_value("null") returns None
# Test: parse_value("42") returns int 42
# Test: parse_value("3.14") returns float 3.14
# Test: parse_value("-3.059797e-08") returns float (scientific notation)
# Test: parse_value('"hello world"') returns "hello world" (string)

# --- Vector/math types ---
# Test: parse_value("Vector2(1.5, 2.0)") returns (1.5, 2.0)
# Test: parse_value("Vector3(1, 2, 3)") returns (1.0, 2.0, 3.0)
# Test: parse_value("Vector3i(1, 2, 3)") returns (1, 2, 3)
# Test: parse_value("Color(0.5, 0.3, 0.1, 1.0)") returns (0.5, 0.3, 0.1, 1.0)
# Test: parse_value("Rect2(0, 0, 100, 50)") returns tuple of 4 floats
# Test: parse_value("AABB(-1, -1, -1, 2, 2, 2)") returns tuple of 6 floats
# Test: parse_value("Quaternion(0, 0, 0, 1)") returns tuple of 4 floats

# --- Reference types ---
# Test: parse_value('ExtResource("1_abc")') returns ExtResource ref with id "1_abc"
# Test: parse_value('SubResource("BoxShape3D_1ufgv")') returns SubResource ref with id
# Test: parse_value('NodePath("Path/To/Node")') returns string "Path/To/Node"
# Test: parse_value('StringName(&"signal_name")') returns string "signal_name"

# --- Packed arrays (raw string) ---
# Test: parse_value("PackedByteArray(...)") stores as raw string
# Test: parse_value("PackedFloat32Array(...)") stores as raw string

# --- Arrays and dictionaries ---
# Test: parse_value('["enemy", "damageable"]') returns list of strings
# Test: parse_value("[1, 2, 3]") returns list of ints
# Test: parse_value with nested dict {"key": value} returns parsed dict

# --- Transform3D decomposition ---
# Test: identity transform → pos=(0,0,0), rot=(0,0,0), scale=(1,1,1)
# Test: translation only → pos=(-36.117, 0, -1.947), rot=(0,0,0), scale=(1,1,1)
# Test: uniform scale 0.35 → scale=(0.35, 0.35, 0.35)
# Test: non-uniform scale → scale=(1, 1.25, 1)
# Test: 180° Y rotation with scale → rot≈(0, π, 0), scale≈(1.2, 1.2, 1.2)
# Test: scientific notation noise → cleaned to proper values
# Test: 90° rotation → rot≈(0, -π/2, 0)
# Test: negative determinant (mirrored) → correct negative scale on one axis

# --- Edge cases ---
# Test: parse_value("truefalse") does NOT match bool (not exact match)
# Test: parse_value with empty string returns empty string
```

## Section 2: Dataclass Definitions

### Before extending dataclasses:

```python
# Test within test_tscn_parser.py or test_dataclasses.py

# Test: TscnNode can be instantiated with all new fields having defaults
# Test: TscnNode.groups defaults to empty list
# Test: TscnNode.metadata defaults to empty dict
# Test: SceneHeader can be instantiated with format, load_steps, uid
# Test: ExtResource can be instantiated with id, type, path, uid
# Test: SubResource can be instantiated with id, type, properties
# Test: Connection can be instantiated with signal, from_node, to_node, method
# Test: TscnScene has ext_resources, sub_resources, root, nodes, connections fields
# Test: parse_tscn raises FileNotFoundError for nonexistent path (new API)
```

## Section 3: State Machine Parser

### Before implementing the parser:

```python
# tests/test_parsers/test_tscn_parser.py

# --- Header parsing ---
# Test: worker.tscn → header.format == 3
# Test: main.tscn → header.format (verify actual value)
# Test: worker.tscn → header.uid starts with "uid://"

# --- External resources ---
# Test: fdm_printer.tscn → 3 ext_resources
# Test: fdm_printer.tscn → ext_resource has correct type, path, id

# --- Sub resources ---
# Test: furniture_slot.tscn → 3 sub_resources with correct types
# Test: sub_resource properties parsed (e.g., size = Vector3)

# --- Basic node parsing ---
# Test: worker.tscn → 4 nodes total
# Test: worker.tscn → root node name = "Worker", type = "CharacterBody3D"
# Test: worker.tscn → root node has script

# --- Groups ---
# Test: fdm_printer.tscn → FDMPrinter node has group "interactable"
# Test: main.tscn → node with multiple groups

# --- Transforms ---
# Test: worker.tscn → CollisionShape3D position = (0, 0.5, 0)
# Test: fdm_printer.tscn → PrinterModel scale ≈ (0.35, 0.35, 0.35)
# Test: main.tscn → non-uniform scale Y=1.25
# Test: main.tscn → rotation decomposition
# Test: main.tscn → scientific notation values

# --- Instance and script nodes ---
# Test: fdm_printer.tscn → PrinterModel has instance (ExtResource ref)
# Test: worker.tscn → root has script ExtResource
# Test: fdm_printer.tscn → PrinterModel has no explicit type

# --- Properties and metadata ---
# Test: main.tscn → LockedZone has zone_name, unlock_cost, tier_required
# Test: main.tscn → ResearchDesk has metadata/station_type
# Test: furniture_slot.tscn → Color, Vector3, int, float all parsed

# --- Edge cases ---
# Test: main.tscn → @CSGBox3D@106885 node name parsed correctly
# Test: main.tscn → exactly 876 nodes
# Test: main.tscn → exactly 68 ext_resources
```

## Section 4: Tree Reconstruction & Query Methods

### Before implementing tree build and queries:

```python
# --- Hierarchy ---
# Test: fdm_printer.tscn → root has correct number of children
# Test: main.tscn → deep nesting (6+ levels) node reachable via children chain
# Test: all nodes in flat list are also reachable via tree traversal

# --- Query methods ---
# Test: find_node("CollisionShape3D") on worker.tscn returns correct node
# Test: find_node("Building/LockedZones/PostProcessingZone") on main.tscn
# Test: find_node("nonexistent") returns None
# Test: get_nodes_by_group("interactable") on fdm_printer.tscn
# Test: get_nodes_by_group("printer_slots") on main.tscn → count matches
# Test: get_nodes_by_type("Marker3D") on main.tscn → returns list
# Test: get_all_scripts() on main.tscn → returns list of res:// paths
# Test: get_all_instances() on main.tscn → returns list of res:// paths
# Test: get_all_scripts() handles missing ext_resource gracefully
```

## Section 5: Test Fixtures & Unit Tests

This section is the implementation of the tests defined above. Write fixture file `furniture_slot_placeholder.tscn` first, then implement all test functions.

## Section 6: Integration Tests & Sprint 0 Fixes

### Integration tests:

```python
# tests/test_parsers/test_tscn_integration.py

# Test: parse each fixture (worker, fdm_printer, furniture_slot, main) → no exceptions
# Test: all parsed nodes have non-empty name
# Test: all nodes with transform have position/rotation/scale set
# Test: find_node by multi-level path works on main.tscn
# Test: get_all_scripts returns valid res:// paths
# Test: get_all_instances returns valid res:// paths
# Test: get_nodes_by_group returns nodes that actually have that group
# Test: get_nodes_by_type returns nodes that actually have that type
```

### Sprint 0 fixes:

```python
# Update tests/test_server.py test_parser_stubs_raise:
# - parse_tscn no longer raises NotImplementedError
# - Change to test that parse_tscn is callable and raises FileNotFoundError for bad path
# - Keep parse_gd and parse_tres NotImplementedError tests unchanged
```
