1818
1919def _layout_test_kit () -> TileKit :
2020 kit = TileKit (name = "Layout Kit" , kit_id = "layout" )
21- kit .floors .append (TileTemplate (TileTemplateKind .FLOOR , "floor_a" , "floor_a" , name = "Floor A" , model = "floor_a" ))
21+ kit .floors .append (
22+ TileTemplate (TileTemplateKind .FLOOR , "floor_a" , "floor_a" , name = "Floor A" , model = "floor_a" )
23+ )
2224 kit .ceilings .append (
23- TileTemplate (TileTemplateKind .CEILING , "ceiling_a" , "ceiling_a" , name = "Ceiling A" , model = "ceiling_a" )
25+ TileTemplate (
26+ TileTemplateKind .CEILING , "ceiling_a" , "ceiling_a" , name = "Ceiling A" , model = "ceiling_a"
27+ )
28+ )
29+ kit .walls .append (
30+ TileTemplate (TileTemplateKind .WALL , "wall_a" , "wall_a" , name = "Wall A" , model = "wall_a" )
2431 )
25- kit .walls .append (TileTemplate (TileTemplateKind .WALL , "wall_a" , "wall_a" , name = "Wall A" , model = "wall_a" ))
2632 kit .inner_corners .append (
27- TileTemplate (TileTemplateKind .INNER_CORNER , "inner_a" , "inner_a" , name = "Inner A" , model = "inner_a" )
33+ TileTemplate (
34+ TileTemplateKind .INNER_CORNER , "inner_a" , "inner_a" , name = "Inner A" , model = "inner_a"
35+ )
2836 )
2937 kit .outer_corners .append (
30- TileTemplate (TileTemplateKind .OUTER_CORNER , "outer_a" , "outer_a" , name = "Outer A" , model = "outer_a" )
38+ TileTemplate (
39+ TileTemplateKind .OUTER_CORNER , "outer_a" , "outer_a" , name = "Outer A" , model = "outer_a"
40+ )
3141 )
3242 kit .tiles .append (
3343 TileCellTemplate (
@@ -40,13 +50,22 @@ def _layout_test_kit() -> TileKit:
4050 WallHookTemplate ("wall_a" , Vector3 (- 5.0 , 0.0 , 0.0 ), QuaternionWXYZ ()),
4151 ],
4252 inner_corner_hooks = [
43- CornerHookTemplate ("inner_a" , Vector3 (5.0 , 5.0 , 0.0 ), QuaternionWXYZ (), adjacent = [0 , 1 ])
53+ CornerHookTemplate (
54+ "inner_a" , Vector3 (5.0 , 5.0 , 0.0 ), QuaternionWXYZ (), adjacent = [0 , 1 ]
55+ )
4456 ],
4557 outer_corner_hooks = [
46- CornerHookTemplate ("outer_a" , Vector3 (- 5.0 , - 5.0 , 0.0 ), QuaternionWXYZ (), adjacent = [0 , 1 ])
58+ CornerHookTemplate (
59+ "outer_a" , Vector3 (- 5.0 , - 5.0 , 0.0 ), QuaternionWXYZ (), adjacent = [0 , 1 ]
60+ )
4761 ],
4862 )
4963 )
64+ kit .objects .append (
65+ TileTemplate (
66+ TileTemplateKind .OBJECT , "crate_a" , "crate_a" , name = "Crate A" , model = "crate_a"
67+ ),
68+ )
5069 return kit
5170
5271
@@ -111,3 +130,72 @@ def test_area_layout_roundtrip_dict_preserves_tiles_and_wall_overrides():
111130 assert len (restored .rooms [0 ].tiles ) == 2
112131 assert restored .rooms [0 ].tiles [1 ].local_position .x == pytest .approx (10.0 )
113132 assert restored .rooms [0 ].tiles [0 ].walls [0 ].linked_tile is restored .rooms [0 ].tiles [1 ]
133+
134+
135+ def _kit_with_tile_b_wall_mismatch () -> TileKit :
136+ """Second tile only exposes ``wall_b`` hooks; extending from a ``wall_a`` edge must fail loudly."""
137+ kit = _layout_test_kit ()
138+ kit .walls .append (
139+ TileTemplate (TileTemplateKind .WALL , "wall_b" , "wall_b" , name = "Wall B" , model = "wall_b" )
140+ )
141+ kit .tiles .append (
142+ TileCellTemplate (
143+ template_id = "tile_b" ,
144+ name = "Tile B" ,
145+ default_floor_id = "floor_a" ,
146+ default_ceiling_id = "ceiling_a" ,
147+ wall_hooks = [WallHookTemplate ("wall_b" , Vector3 (5.0 , 0.0 , 0.0 ), QuaternionWXYZ ())],
148+ )
149+ )
150+ return kit
151+
152+
153+ def test_area_layout_extend_tile_from_wall_raises_when_no_hook_matches_wall_type ():
154+ kit = _kit_with_tile_b_wall_mismatch ()
155+ layout = AreaLayout ()
156+ room = layout .add_room_from_tile (kit , "tile_a" )
157+ wall_a = room .tiles [0 ].walls [0 ]
158+
159+ with pytest .raises (ValueError , match = "has no wall hook for wall_a" ):
160+ room .extend_tile_from_wall (kit , room .tiles [0 ], wall_a , "tile_b" )
161+
162+
163+ def test_area_layout_from_dict_missing_kit_raises ():
164+ kit = _layout_test_kit ()
165+ layout = AreaLayout ()
166+ layout .add_room_from_tile (kit , "tile_a" )
167+ data = area_layout_to_dict (layout )
168+ data ["rooms" ][0 ]["tiles" ][0 ]["kitID" ] = "no_such_kit"
169+
170+ with pytest .raises (ValueError , match = "Missing kit no_such_kit" ):
171+ area_layout_from_dict (data , {"layout" : kit })
172+
173+
174+ def test_area_layout_from_dict_invalid_floor_override_raises ():
175+ kit = _layout_test_kit ()
176+ layout = AreaLayout ()
177+ layout .add_room_from_tile (kit , "tile_a" )
178+ data = area_layout_to_dict (layout )
179+ data ["rooms" ][0 ]["tiles" ][0 ]["floor" ]["templateID" ] = "not_a_floor_template"
180+
181+ with pytest .raises (ValueError , match = "Missing floor template not_a_floor_template" ):
182+ area_layout_from_dict (data , {"layout" : kit })
183+
184+
185+ def test_area_layout_objects_roundtrip_dict ():
186+ kit = _layout_test_kit ()
187+ layout = AreaLayout ()
188+ room = layout .add_room_from_tile (kit , "tile_a" )
189+ room .add_object (kit , "crate_a" , position = Vector3 (1.0 , 2.0 , 3.0 ))
190+
191+ data = area_layout_to_dict (layout )
192+ restored = area_layout_from_dict (data , {"layout" : kit })
193+
194+ assert len (restored .rooms ) == 1
195+ assert len (restored .rooms [0 ].objects ) == 1
196+ obj = restored .rooms [0 ].objects [0 ]
197+ assert obj .template_id == "crate_a"
198+ assert obj .kit_id == "layout"
199+ assert obj .local_position .x == pytest .approx (1.0 )
200+ assert obj .local_position .y == pytest .approx (2.0 )
201+ assert obj .local_position .z == pytest .approx (3.0 )
0 commit comments