WIP Lane E S17 gate 3.1: corroded tier (PREDECESSOR STATE — negative control M1 still applied)
Preserved verbatim from the interrupted session. NOTE: build_sail_post_corroded still carries the M1 mutation (clean galvanised steel, DROOP=0), so the two GLBs in this commit are the UNTELLED post, not the shipped asset. Restored in the next commit after the red-check is witnessed. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
75d5f9a4bf
commit
a180fce8c3
@ -2,906 +2,28 @@
|
|||||||
"blender": "5.1.2",
|
"blender": "5.1.2",
|
||||||
"assets": [
|
"assets": [
|
||||||
{
|
{
|
||||||
"name": "ref_capsule",
|
"name": "sail_post_corroded",
|
||||||
"dims": [
|
|
||||||
0.4,
|
|
||||||
0.4,
|
|
||||||
1.7
|
|
||||||
],
|
|
||||||
"tris": 220,
|
|
||||||
"nodes": [
|
|
||||||
"head_height",
|
|
||||||
"ref_capsule",
|
|
||||||
"ref_capsule_mesh"
|
|
||||||
],
|
|
||||||
"anchors": [],
|
|
||||||
"status": "PASS",
|
|
||||||
"problems": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "tree_gum_01",
|
|
||||||
"dims": [
|
|
||||||
4.5522,
|
|
||||||
4.956,
|
|
||||||
7.9702
|
|
||||||
],
|
|
||||||
"tris": 492,
|
|
||||||
"nodes": [
|
|
||||||
"branch_anchor_01",
|
|
||||||
"branch_anchor_02",
|
|
||||||
"branch_anchor_03",
|
|
||||||
"canopy",
|
|
||||||
"canopy_01",
|
|
||||||
"canopy_02",
|
|
||||||
"canopy_03",
|
|
||||||
"tree_gum_01",
|
|
||||||
"trunk"
|
|
||||||
],
|
|
||||||
"anchors": [
|
|
||||||
{
|
|
||||||
"node": "branch_anchor_01",
|
|
||||||
"anchor_type": "tree",
|
|
||||||
"rating_hint": 1.0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"node": "branch_anchor_02",
|
|
||||||
"anchor_type": "tree",
|
|
||||||
"rating_hint": 0.88
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"node": "branch_anchor_03",
|
|
||||||
"anchor_type": "tree",
|
|
||||||
"rating_hint": 0.76
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"status": "PASS",
|
|
||||||
"problems": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "tree_gum_02",
|
|
||||||
"dims": [
|
|
||||||
3.8871,
|
|
||||||
2.7787,
|
|
||||||
5.4972
|
|
||||||
],
|
|
||||||
"tris": 352,
|
|
||||||
"nodes": [
|
|
||||||
"branch_anchor_01",
|
|
||||||
"branch_anchor_02",
|
|
||||||
"canopy",
|
|
||||||
"canopy_01",
|
|
||||||
"canopy_02",
|
|
||||||
"tree_gum_02",
|
|
||||||
"trunk"
|
|
||||||
],
|
|
||||||
"anchors": [
|
|
||||||
{
|
|
||||||
"node": "branch_anchor_01",
|
|
||||||
"anchor_type": "tree",
|
|
||||||
"rating_hint": 1.0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"node": "branch_anchor_02",
|
|
||||||
"anchor_type": "tree",
|
|
||||||
"rating_hint": 0.88
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"status": "PASS",
|
|
||||||
"problems": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "tree_jacaranda_01",
|
|
||||||
"dims": [
|
|
||||||
7.4789,
|
|
||||||
6.5035,
|
|
||||||
6.0535
|
|
||||||
],
|
|
||||||
"tris": 632,
|
|
||||||
"nodes": [
|
|
||||||
"branch_anchor_01",
|
|
||||||
"branch_anchor_02",
|
|
||||||
"branch_anchor_03",
|
|
||||||
"canopy",
|
|
||||||
"canopy_01",
|
|
||||||
"canopy_02",
|
|
||||||
"canopy_03",
|
|
||||||
"canopy_04",
|
|
||||||
"tree_jacaranda_01",
|
|
||||||
"trunk"
|
|
||||||
],
|
|
||||||
"anchors": [
|
|
||||||
{
|
|
||||||
"node": "branch_anchor_01",
|
|
||||||
"anchor_type": "tree",
|
|
||||||
"rating_hint": 0.95
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"node": "branch_anchor_02",
|
|
||||||
"anchor_type": "tree",
|
|
||||||
"rating_hint": 0.52
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"node": "branch_anchor_03",
|
|
||||||
"anchor_type": "tree",
|
|
||||||
"rating_hint": 0.4
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"status": "PASS",
|
|
||||||
"problems": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "fence_post",
|
|
||||||
"dims": [
|
|
||||||
0.13,
|
|
||||||
0.13,
|
|
||||||
2.03
|
|
||||||
],
|
|
||||||
"tris": 24,
|
|
||||||
"nodes": [
|
|
||||||
"fence_post",
|
|
||||||
"post"
|
|
||||||
],
|
|
||||||
"anchors": [],
|
|
||||||
"status": "PASS",
|
|
||||||
"problems": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "fence_panel",
|
|
||||||
"dims": [
|
|
||||||
2.4,
|
|
||||||
0.054,
|
|
||||||
1.8194
|
|
||||||
],
|
|
||||||
"tris": 324,
|
|
||||||
"nodes": [
|
|
||||||
"fence_panel",
|
|
||||||
"palings",
|
|
||||||
"rails"
|
|
||||||
],
|
|
||||||
"anchors": [],
|
|
||||||
"status": "PASS",
|
|
||||||
"problems": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "gate",
|
|
||||||
"dims": [
|
|
||||||
1.045,
|
|
||||||
0.0615,
|
|
||||||
1.75
|
|
||||||
],
|
|
||||||
"tris": 220,
|
|
||||||
"nodes": [
|
|
||||||
"gate",
|
|
||||||
"gate_frame",
|
|
||||||
"gate_palings",
|
|
||||||
"hinge_axis",
|
|
||||||
"hinges"
|
|
||||||
],
|
|
||||||
"anchors": [],
|
|
||||||
"status": "PASS",
|
|
||||||
"problems": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "house_yardside",
|
|
||||||
"dims": [
|
|
||||||
9.2,
|
|
||||||
1.0547,
|
|
||||||
2.9
|
|
||||||
],
|
|
||||||
"tris": 212,
|
|
||||||
"nodes": [
|
|
||||||
"door",
|
|
||||||
"fascia",
|
|
||||||
"fascia_anchor_01",
|
|
||||||
"fascia_anchor_02",
|
|
||||||
"fascia_anchor_03",
|
|
||||||
"gutter",
|
|
||||||
"house_yardside",
|
|
||||||
"roof",
|
|
||||||
"wall",
|
|
||||||
"window",
|
|
||||||
"window_glow",
|
|
||||||
"window_light_anchor"
|
|
||||||
],
|
|
||||||
"anchors": [
|
|
||||||
{
|
|
||||||
"node": "fascia_anchor_01",
|
|
||||||
"anchor_type": "house",
|
|
||||||
"rating_hint": 0.35,
|
|
||||||
"collateral": "gutter"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"node": "fascia_anchor_02",
|
|
||||||
"anchor_type": "house",
|
|
||||||
"rating_hint": 0.35,
|
|
||||||
"collateral": "gutter"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"node": "fascia_anchor_03",
|
|
||||||
"anchor_type": "house",
|
|
||||||
"rating_hint": 0.35,
|
|
||||||
"collateral": "gutter"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"node": "window_light_anchor",
|
|
||||||
"tie_off": false
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"status": "PASS",
|
|
||||||
"problems": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "house_yardside_wrecked",
|
|
||||||
"dims": [
|
|
||||||
9.4552,
|
|
||||||
2.1021,
|
|
||||||
2.9172
|
|
||||||
],
|
|
||||||
"tris": 340,
|
|
||||||
"nodes": [
|
|
||||||
"debris_fascia",
|
|
||||||
"door",
|
|
||||||
"downpipe_loose",
|
|
||||||
"fascia_torn",
|
|
||||||
"gutter_down",
|
|
||||||
"gutter_torn",
|
|
||||||
"house_yardside_wrecked",
|
|
||||||
"roof",
|
|
||||||
"wall",
|
|
||||||
"window",
|
|
||||||
"window_glow",
|
|
||||||
"window_light_anchor"
|
|
||||||
],
|
|
||||||
"anchors": [
|
|
||||||
{
|
|
||||||
"node": "window_light_anchor",
|
|
||||||
"tie_off": false
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"status": "PASS",
|
|
||||||
"problems": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "carport_01",
|
|
||||||
"dims": [
|
|
||||||
3.24,
|
|
||||||
5.5959,
|
|
||||||
2.6945
|
|
||||||
],
|
|
||||||
"tris": 172,
|
|
||||||
"nodes": [
|
|
||||||
"beam_anchor_01",
|
|
||||||
"beam_anchor_02",
|
|
||||||
"beams",
|
|
||||||
"carport_01",
|
|
||||||
"footings",
|
|
||||||
"post_anchor_01",
|
|
||||||
"post_anchor_02",
|
|
||||||
"posts",
|
|
||||||
"roof"
|
|
||||||
],
|
|
||||||
"anchors": [
|
|
||||||
{
|
|
||||||
"node": "beam_anchor_01",
|
|
||||||
"anchor_type": "carport",
|
|
||||||
"rating_hint": 0.22,
|
|
||||||
"collateral": "carport"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"node": "beam_anchor_02",
|
|
||||||
"anchor_type": "carport",
|
|
||||||
"rating_hint": 0.22,
|
|
||||||
"collateral": "carport"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"node": "post_anchor_01",
|
|
||||||
"anchor_type": "carport_post",
|
|
||||||
"rating_hint": 0.3,
|
|
||||||
"collateral": "carport"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"node": "post_anchor_02",
|
|
||||||
"anchor_type": "carport_post",
|
|
||||||
"rating_hint": 0.3,
|
|
||||||
"collateral": "carport"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"status": "PASS",
|
|
||||||
"problems": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "carport_01_wrecked",
|
|
||||||
"dims": [
|
|
||||||
6.2819,
|
|
||||||
5.5704,
|
|
||||||
2.1983
|
|
||||||
],
|
|
||||||
"tris": 144,
|
|
||||||
"nodes": [
|
|
||||||
"beams",
|
|
||||||
"carport_01_wrecked",
|
|
||||||
"footings",
|
|
||||||
"posts",
|
|
||||||
"roof_down"
|
|
||||||
],
|
|
||||||
"anchors": [],
|
|
||||||
"status": "PASS",
|
|
||||||
"problems": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "swing_set_01",
|
|
||||||
"dims": [
|
|
||||||
2.3416,
|
|
||||||
0.9469,
|
|
||||||
2.0741
|
|
||||||
],
|
|
||||||
"tris": 220,
|
|
||||||
"nodes": [
|
|
||||||
"crossbar",
|
|
||||||
"frame",
|
|
||||||
"frame_anchor_01",
|
|
||||||
"frame_anchor_02",
|
|
||||||
"swing_set_01",
|
|
||||||
"swings"
|
|
||||||
],
|
|
||||||
"anchors": [
|
|
||||||
{
|
|
||||||
"node": "crossbar",
|
|
||||||
"tie_off": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"node": "frame_anchor_01",
|
|
||||||
"anchor_type": "swing_frame",
|
|
||||||
"rating_hint": 0.45,
|
|
||||||
"collateral": "swing_set"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"node": "frame_anchor_02",
|
|
||||||
"anchor_type": "swing_frame",
|
|
||||||
"rating_hint": 0.45,
|
|
||||||
"collateral": "swing_set"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"status": "PASS",
|
|
||||||
"problems": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "swing_set_01_wrecked",
|
|
||||||
"dims": [
|
|
||||||
2.3416,
|
|
||||||
2.4842,
|
|
||||||
0.9325
|
|
||||||
],
|
|
||||||
"tris": 220,
|
|
||||||
"nodes": [
|
|
||||||
"crossbar",
|
|
||||||
"frame",
|
|
||||||
"swing_set_01_wrecked",
|
|
||||||
"swings"
|
|
||||||
],
|
|
||||||
"anchors": [
|
|
||||||
{
|
|
||||||
"node": "crossbar",
|
|
||||||
"tie_off": false
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"status": "PASS",
|
|
||||||
"problems": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "shed_01",
|
|
||||||
"dims": [
|
|
||||||
2.58,
|
|
||||||
1.9708,
|
|
||||||
2.2224
|
|
||||||
],
|
|
||||||
"tris": 96,
|
|
||||||
"nodes": [
|
|
||||||
"door_anchor",
|
|
||||||
"doors",
|
|
||||||
"roof",
|
|
||||||
"shed_01",
|
|
||||||
"shell"
|
|
||||||
],
|
|
||||||
"anchors": [
|
|
||||||
{
|
|
||||||
"node": "door_anchor",
|
|
||||||
"tie_off": false
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"status": "PASS",
|
|
||||||
"problems": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "shed_table",
|
|
||||||
"dims": [
|
|
||||||
1.6,
|
|
||||||
0.6,
|
|
||||||
0.9
|
|
||||||
],
|
|
||||||
"tris": 72,
|
|
||||||
"nodes": [
|
|
||||||
"pickup_anchor",
|
|
||||||
"shed_table",
|
|
||||||
"table_frame",
|
|
||||||
"table_top"
|
|
||||||
],
|
|
||||||
"anchors": [
|
|
||||||
{
|
|
||||||
"node": "pickup_anchor",
|
|
||||||
"tie_off": false
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"status": "PASS",
|
|
||||||
"problems": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "garden_bed",
|
|
||||||
"dims": [
|
|
||||||
3.0,
|
|
||||||
1.2,
|
|
||||||
0.8609
|
|
||||||
],
|
|
||||||
"tris": 2580,
|
|
||||||
"nodes": [
|
|
||||||
"bed",
|
|
||||||
"garden_bed",
|
|
||||||
"plants_dead",
|
|
||||||
"plants_full",
|
|
||||||
"plants_tattered",
|
|
||||||
"soil"
|
|
||||||
],
|
|
||||||
"anchors": [],
|
|
||||||
"status": "PASS",
|
|
||||||
"problems": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "sail_post",
|
|
||||||
"dims": [
|
"dims": [
|
||||||
0.507,
|
0.507,
|
||||||
0.52,
|
0.52,
|
||||||
4.0327
|
4.0516
|
||||||
],
|
],
|
||||||
"tris": 528,
|
"tris": 816,
|
||||||
"nodes": [
|
"nodes": [
|
||||||
"footing",
|
"footing",
|
||||||
"pad_eye",
|
"pad_eye",
|
||||||
"post",
|
"post",
|
||||||
"rake_pivot",
|
"rake_pivot",
|
||||||
"sail_post",
|
"rust",
|
||||||
|
"sail_post_corroded",
|
||||||
"top_anchor"
|
"top_anchor"
|
||||||
],
|
],
|
||||||
"anchors": [
|
"anchors": [
|
||||||
{
|
{
|
||||||
"node": "top_anchor",
|
"node": "top_anchor",
|
||||||
"anchor_type": "post",
|
"anchor_type": "corroded_post",
|
||||||
"rating_hint": 1.0
|
"rating_hint": 0.55,
|
||||||
}
|
"collateral": "corroded_post"
|
||||||
],
|
|
||||||
"status": "PASS",
|
|
||||||
"problems": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "ladder_01",
|
|
||||||
"dims": [
|
|
||||||
0.455,
|
|
||||||
0.075,
|
|
||||||
3.0
|
|
||||||
],
|
|
||||||
"tris": 276,
|
|
||||||
"nodes": [
|
|
||||||
"ladder",
|
|
||||||
"ladder_01",
|
|
||||||
"ladder_base",
|
|
||||||
"ladder_top"
|
|
||||||
],
|
|
||||||
"anchors": [],
|
|
||||||
"status": "PASS",
|
|
||||||
"problems": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "shackle",
|
|
||||||
"dims": [
|
|
||||||
0.0569,
|
|
||||||
0.019,
|
|
||||||
0.0744
|
|
||||||
],
|
|
||||||
"tris": 560,
|
|
||||||
"nodes": [
|
|
||||||
"bow",
|
|
||||||
"pin",
|
|
||||||
"shackle"
|
|
||||||
],
|
|
||||||
"anchors": [],
|
|
||||||
"status": "PASS",
|
|
||||||
"problems": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "carabiner",
|
|
||||||
"dims": [
|
|
||||||
0.049,
|
|
||||||
0.009,
|
|
||||||
0.1027
|
|
||||||
],
|
|
||||||
"tris": 476,
|
|
||||||
"nodes": [
|
|
||||||
"body",
|
|
||||||
"carabiner",
|
|
||||||
"gate"
|
|
||||||
],
|
|
||||||
"anchors": [],
|
|
||||||
"status": "PASS",
|
|
||||||
"problems": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "turnbuckle",
|
|
||||||
"dims": [
|
|
||||||
0.0292,
|
|
||||||
0.0341,
|
|
||||||
0.1955
|
|
||||||
],
|
|
||||||
"tris": 728,
|
|
||||||
"nodes": [
|
|
||||||
"body",
|
|
||||||
"eye_a",
|
|
||||||
"eye_b",
|
|
||||||
"turnbuckle"
|
|
||||||
],
|
|
||||||
"anchors": [],
|
|
||||||
"status": "PASS",
|
|
||||||
"problems": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "tramp_01",
|
|
||||||
"dims": [
|
|
||||||
2.9555,
|
|
||||||
2.9555,
|
|
||||||
0.78
|
|
||||||
],
|
|
||||||
"tris": 976,
|
|
||||||
"nodes": [
|
|
||||||
"legs",
|
|
||||||
"mat",
|
|
||||||
"pad",
|
|
||||||
"rim",
|
|
||||||
"tramp_01"
|
|
||||||
],
|
|
||||||
"anchors": [],
|
|
||||||
"status": "PASS",
|
|
||||||
"problems": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "wheelie_bin_01",
|
|
||||||
"dims": [
|
|
||||||
0.58,
|
|
||||||
0.6808,
|
|
||||||
1.1188
|
|
||||||
],
|
|
||||||
"tris": 120,
|
|
||||||
"nodes": [
|
|
||||||
"bin_body",
|
|
||||||
"lid",
|
|
||||||
"lid_plate",
|
|
||||||
"wheelie_bin_01",
|
|
||||||
"wheels"
|
|
||||||
],
|
|
||||||
"anchors": [],
|
|
||||||
"status": "PASS",
|
|
||||||
"problems": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "washing_line_01",
|
|
||||||
"dims": [
|
|
||||||
2.8441,
|
|
||||||
2.8441,
|
|
||||||
2.2777
|
|
||||||
],
|
|
||||||
"tris": 336,
|
|
||||||
"nodes": [
|
|
||||||
"arms",
|
|
||||||
"head",
|
|
||||||
"mast",
|
|
||||||
"washing_line_01"
|
|
||||||
],
|
|
||||||
"anchors": [],
|
|
||||||
"status": "PASS",
|
|
||||||
"problems": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "garden_gnome_01",
|
|
||||||
"dims": [
|
|
||||||
0.1427,
|
|
||||||
0.15,
|
|
||||||
0.365
|
|
||||||
],
|
|
||||||
"tris": 236,
|
|
||||||
"nodes": [
|
|
||||||
"garden_gnome_01",
|
|
||||||
"gnome"
|
|
||||||
],
|
|
||||||
"anchors": [],
|
|
||||||
"status": "PASS",
|
|
||||||
"problems": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "bike_kid_01",
|
|
||||||
"dims": [
|
|
||||||
1.148,
|
|
||||||
0.328,
|
|
||||||
0.7269
|
|
||||||
],
|
|
||||||
"tris": 700,
|
|
||||||
"nodes": [
|
|
||||||
"bars",
|
|
||||||
"bike_kid_01",
|
|
||||||
"frame",
|
|
||||||
"wheel_front",
|
|
||||||
"wheel_rear"
|
|
||||||
],
|
|
||||||
"anchors": [],
|
|
||||||
"status": "PASS",
|
|
||||||
"problems": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "garden_gnome_01_broken",
|
|
||||||
"dims": [
|
|
||||||
0.3947,
|
|
||||||
0.3519,
|
|
||||||
0.106
|
|
||||||
],
|
|
||||||
"tris": 344,
|
|
||||||
"nodes": [
|
|
||||||
"garden_gnome_01_broken",
|
|
||||||
"hat",
|
|
||||||
"head",
|
|
||||||
"shards",
|
|
||||||
"stump"
|
|
||||||
],
|
|
||||||
"anchors": [],
|
|
||||||
"status": "PASS",
|
|
||||||
"problems": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "hail_stone_01",
|
|
||||||
"dims": [
|
|
||||||
0.0198,
|
|
||||||
0.0176,
|
|
||||||
0.0197
|
|
||||||
],
|
|
||||||
"tris": 20,
|
|
||||||
"nodes": [
|
|
||||||
"hail_stone_01",
|
|
||||||
"stone"
|
|
||||||
],
|
|
||||||
"anchors": [],
|
|
||||||
"status": "PASS",
|
|
||||||
"problems": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "broom_01",
|
|
||||||
"dims": [
|
|
||||||
0.3891,
|
|
||||||
0.055,
|
|
||||||
1.4213
|
|
||||||
],
|
|
||||||
"tris": 200,
|
|
||||||
"nodes": [
|
|
||||||
"bristles",
|
|
||||||
"broom_01",
|
|
||||||
"grip_anchor",
|
|
||||||
"handle",
|
|
||||||
"head",
|
|
||||||
"poke_tip"
|
|
||||||
],
|
|
||||||
"anchors": [
|
|
||||||
{
|
|
||||||
"node": "grip_anchor",
|
|
||||||
"tie_off": false
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"status": "PASS",
|
|
||||||
"problems": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "fence_panel_snapped",
|
|
||||||
"dims": [
|
|
||||||
2.4125,
|
|
||||||
1.0144,
|
|
||||||
1.8181
|
|
||||||
],
|
|
||||||
"tris": 312,
|
|
||||||
"nodes": [
|
|
||||||
"debris_palings",
|
|
||||||
"fence_panel_snapped",
|
|
||||||
"palings",
|
|
||||||
"rails"
|
|
||||||
],
|
|
||||||
"anchors": [],
|
|
||||||
"status": "PASS",
|
|
||||||
"problems": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "pergola_01",
|
|
||||||
"dims": [
|
|
||||||
3.2,
|
|
||||||
2.6,
|
|
||||||
2.71
|
|
||||||
],
|
|
||||||
"tris": 276,
|
|
||||||
"nodes": [
|
|
||||||
"beams",
|
|
||||||
"deck",
|
|
||||||
"pergola_01",
|
|
||||||
"pergola_anchor_01",
|
|
||||||
"pergola_anchor_02",
|
|
||||||
"posts",
|
|
||||||
"rafters"
|
|
||||||
],
|
|
||||||
"anchors": [
|
|
||||||
{
|
|
||||||
"node": "pergola_anchor_01",
|
|
||||||
"anchor_type": "pergola",
|
|
||||||
"rating_hint": 0.65,
|
|
||||||
"collateral": "pergola"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"node": "pergola_anchor_02",
|
|
||||||
"anchor_type": "pergola",
|
|
||||||
"rating_hint": 0.65,
|
|
||||||
"collateral": "pergola"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"status": "PASS",
|
|
||||||
"problems": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "pergola_01_wrecked",
|
|
||||||
"dims": [
|
|
||||||
3.3288,
|
|
||||||
2.6,
|
|
||||||
2.6648
|
|
||||||
],
|
|
||||||
"tris": 300,
|
|
||||||
"nodes": [
|
|
||||||
"beam_down",
|
|
||||||
"beams",
|
|
||||||
"deck",
|
|
||||||
"pergola_01_wrecked",
|
|
||||||
"posts",
|
|
||||||
"rafters_down"
|
|
||||||
],
|
|
||||||
"anchors": [],
|
|
||||||
"status": "PASS",
|
|
||||||
"problems": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "glasshouse_01",
|
|
||||||
"dims": [
|
|
||||||
2.645,
|
|
||||||
1.945,
|
|
||||||
2.3025
|
|
||||||
],
|
|
||||||
"tris": 356,
|
|
||||||
"nodes": [
|
|
||||||
"door",
|
|
||||||
"frame",
|
|
||||||
"glasshouse_01",
|
|
||||||
"panes",
|
|
||||||
"ridge_anchor"
|
|
||||||
],
|
|
||||||
"anchors": [
|
|
||||||
{
|
|
||||||
"node": "ridge_anchor",
|
|
||||||
"tie_off": false
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"status": "PASS",
|
|
||||||
"problems": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "glasshouse_01_wrecked",
|
|
||||||
"dims": [
|
|
||||||
4.3356,
|
|
||||||
2.9387,
|
|
||||||
2.3025
|
|
||||||
],
|
|
||||||
"tris": 680,
|
|
||||||
"nodes": [
|
|
||||||
"door",
|
|
||||||
"frame",
|
|
||||||
"glasshouse_01_wrecked",
|
|
||||||
"panes_left",
|
|
||||||
"shards"
|
|
||||||
],
|
|
||||||
"anchors": [],
|
|
||||||
"status": "PASS",
|
|
||||||
"problems": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "pool_kit_01",
|
|
||||||
"dims": [
|
|
||||||
5.645,
|
|
||||||
4.5207,
|
|
||||||
1.22
|
|
||||||
],
|
|
||||||
"tris": 2780,
|
|
||||||
"nodes": [
|
|
||||||
"fence_post_01",
|
|
||||||
"fence_post_02",
|
|
||||||
"fence_post_03",
|
|
||||||
"fence_post_04",
|
|
||||||
"fence_post_05",
|
|
||||||
"fence_post_06",
|
|
||||||
"fence_post_07",
|
|
||||||
"fence_post_08",
|
|
||||||
"fence_post_09",
|
|
||||||
"fence_post_10",
|
|
||||||
"fence_post_11",
|
|
||||||
"fence_post_12",
|
|
||||||
"fence_post_13",
|
|
||||||
"fence_post_14",
|
|
||||||
"fence_rails",
|
|
||||||
"pool_gate",
|
|
||||||
"pool_kit_01",
|
|
||||||
"pool_shell",
|
|
||||||
"pool_water"
|
|
||||||
],
|
|
||||||
"anchors": [
|
|
||||||
{
|
|
||||||
"node": "fence_post_01",
|
|
||||||
"tie_off": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"node": "fence_post_02",
|
|
||||||
"tie_off": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"node": "fence_post_03",
|
|
||||||
"tie_off": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"node": "fence_post_04",
|
|
||||||
"tie_off": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"node": "fence_post_05",
|
|
||||||
"tie_off": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"node": "fence_post_06",
|
|
||||||
"tie_off": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"node": "fence_post_07",
|
|
||||||
"tie_off": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"node": "fence_post_08",
|
|
||||||
"tie_off": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"node": "fence_post_09",
|
|
||||||
"tie_off": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"node": "fence_post_10",
|
|
||||||
"tie_off": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"node": "fence_post_11",
|
|
||||||
"tie_off": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"node": "fence_post_12",
|
|
||||||
"tie_off": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"node": "fence_post_13",
|
|
||||||
"tie_off": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"node": "fence_post_14",
|
|
||||||
"tie_off": false
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"status": "PASS",
|
"status": "PASS",
|
||||||
|
|||||||
@ -137,6 +137,14 @@ PAL = {
|
|||||||
"bike_kid": "#D8483C", # the Henderson kid's bike — bought bright on purpose
|
"bike_kid": "#D8483C", # the Henderson kid's bike — bought bright on purpose
|
||||||
"bike_grip": "#3B4048", # grips and saddle
|
"bike_grip": "#3B4048", # grips and saddle
|
||||||
"ref_pink": "#E85C8A", # the reference capsule — deliberately loud
|
"ref_pink": "#E85C8A", # the reference capsule — deliberately loud
|
||||||
|
"steel_weathered": "#8D8A7E", # gal gone matte: the corroded post's shaft.
|
||||||
|
# Next to steel_gal (#B6BCC2) it reads DIRTY at
|
||||||
|
# 20 m before you can see a single bloom — the
|
||||||
|
# colour is the long-range half of the tell.
|
||||||
|
"rust_bloom": "#9C5B33", # active surface rust — orange enough to read
|
||||||
|
# against every steel/timber in the palette
|
||||||
|
"rust_deep": "#67351F", # the worst of it: fold lines, weld collars,
|
||||||
|
# streak stains — near-brown, high saturation
|
||||||
"alu_frame": "#C9CED2", # powder-coated aluminium — glasshouse + pool fence
|
"alu_frame": "#C9CED2", # powder-coated aluminium — glasshouse + pool fence
|
||||||
"pool_water": "#4FA8BE", # chlorinated blue, NOT the pond's grey-green —
|
"pool_water": "#4FA8BE", # chlorinated blue, NOT the pond's grey-green —
|
||||||
# a pool is the one water in the game that is
|
# a pool is the one water in the game that is
|
||||||
@ -2256,6 +2264,258 @@ def build_sail_post(name):
|
|||||||
return root
|
return root
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# THE CORRODED TIER (SPRINT17 gate 3.1 — the pool yard's other half)
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# The pool kit is fourteen tie-offs that DON'T exist (every fence post an
|
||||||
|
# honest no). This is the tie-off that DOES exist and shouldn't be trusted:
|
||||||
|
# a real sail post, real concrete footing, real pad eye — gone rusty.
|
||||||
|
#
|
||||||
|
# WHY 0.55, argued on the ladder it joins (0.22 carport < 0.30 carport_post <
|
||||||
|
# 0.35 fascia < 0.45 swing_frame < [0.55] < 0.65 pergola < 1.00 post):
|
||||||
|
# · ABOVE the swing frame (0.45): the footing is still 300 mm of concrete
|
||||||
|
# and the load path still reaches the ground. A sound welded frame
|
||||||
|
# standing loose on grass drags; a rusty post in a real footing holds
|
||||||
|
# until its steel gives — corroded anchored beats sound unanchored.
|
||||||
|
# · BELOW the pergola (0.65): the pergola's members are SOUND and its
|
||||||
|
# weakness (deck flex) is visible and bounded. Corrosion's weakness is
|
||||||
|
# section loss you cannot see the bottom of — the bloom on the outside is
|
||||||
|
# pitting on the inside, so the visible rust is a FLOOR on the damage,
|
||||||
|
# not a ceiling. You de-rate below sound timber because you're rating
|
||||||
|
# what you can't inspect.
|
||||||
|
# · And it FILLS the 0.45→0.65 gap: rungs create decisions (D's S15
|
||||||
|
# endorsement logic, the reason the pergola exists). With this rung the
|
||||||
|
# ladder steps 0.45 / 0.55 / 0.65 — three different kinds of "not quite":
|
||||||
|
# unanchored steel, eaten steel, flexing timber.
|
||||||
|
CORRODED_POST_RATING = 0.55
|
||||||
|
|
||||||
|
# The bill when it snaps. Proposal — Lane A confirms (my S17 prompt says rule
|
||||||
|
# it and let A confirm): a post IS the client's property, so a snapped one
|
||||||
|
# bills — "it was already scrap" is a warranty argument, not a free pass. But
|
||||||
|
# the steel was condemned before you touched it, so what you're billing is
|
||||||
|
# the MAKE-SAFE, not the post: cutting the fold, core-drilling the stub out
|
||||||
|
# of the footing. That's under the gutter's $90 (a full trade's morning run)
|
||||||
|
# and above the gnome's $25. Cheap ON PURPOSE: the real price of trusting
|
||||||
|
# corroded steel is the corner you lose mid-storm — the trap costs you in
|
||||||
|
# sail HP and garden exposure, and the invoice line is honest small change
|
||||||
|
# beside it. If the bill were big, the lesson would read "don't touch rust";
|
||||||
|
# at $45 it reads "rust is a bet, and here's the stake".
|
||||||
|
CORRODED_POST_COLLATERAL = 45
|
||||||
|
|
||||||
|
|
||||||
|
def build_sail_post_corroded(name):
|
||||||
|
"""The corroded sail-post variant — same bones as build_sail_post (H, R,
|
||||||
|
footing, rake_pivot mechanism all identical, so it rakes and places like
|
||||||
|
any post), different truth.
|
||||||
|
|
||||||
|
THE TELL, designed to read at two ranges (the bike lesson: LOOK at it,
|
||||||
|
and look from where the player actually stands):
|
||||||
|
· 20 m / colour: the whole shaft is weathered matte (steel_weathered)
|
||||||
|
against the honest post's bright galvanised, the footing collar wears
|
||||||
|
a rust stain skirt, and the cap sits askew. You don't see rust from
|
||||||
|
the back fence — you see a DIRTY post next to clean ones.
|
||||||
|
· 3 m / detail: rust blooms where posts actually rust (the base band
|
||||||
|
where water pools on the collar, the head collar around the pad-eye
|
||||||
|
weld), deep-rust fold line just above the footing, streaks bleeding
|
||||||
|
down the shaft, and the pad eye SAGS — the weld ate through and the
|
||||||
|
eye drooped toward the yard. The anchor empty sits at the DROOPED
|
||||||
|
eye, so the sag is a measured fact (e.test pins corroded top_anchor
|
||||||
|
below the honest one), not set dressing.
|
||||||
|
|
||||||
|
Corrosion a player can't see is a trap with no tell, and this repo
|
||||||
|
doesn't ship those — the tell is pinned by AREA in e.test.js: rebuild
|
||||||
|
this in clean steel with the same 0.55 hint and the suite goes red.
|
||||||
|
"""
|
||||||
|
root = add_empty(name)
|
||||||
|
steel = get_material("Mat_SteelWeathered", PAL["steel_weathered"], 0.6,
|
||||||
|
metallic=0.55)
|
||||||
|
dark = get_material("Mat_SteelDark", PAL["steel_dark"], 0.45, metallic=0.8)
|
||||||
|
rust = get_material("Mat_Rust", PAL["rust_bloom"], 0.9, metallic=0.1)
|
||||||
|
rust_d = get_material("Mat_RustDeep", PAL["rust_deep"], 0.95, metallic=0.05)
|
||||||
|
conc = get_material("Mat_Concrete", PAL["concrete"], 0.95)
|
||||||
|
H, R = 4.0, 0.048 # same post the honest one is — that's the trap
|
||||||
|
# NEGATIVE CONTROL M1 (S17): the untelled corroded post — same 0.55 hint,
|
||||||
|
# no visual. Clean galvanised everywhere, no droop.
|
||||||
|
steel = get_material("Mat_Steel", PAL["steel_gal"], 0.35, metallic=0.9)
|
||||||
|
rust = steel
|
||||||
|
rust_d = steel
|
||||||
|
DROOP = math.radians(0.0) # pad-eye sag off vertical
|
||||||
|
WELD_Z = H - 0.16 # the slumped weld line the eye hinges at
|
||||||
|
|
||||||
|
# Footing identical to the honest post's, plus the stain skirt: rust
|
||||||
|
# bleeds onto the collar top, so even the GROUND LINE reads brown.
|
||||||
|
join_group([
|
||||||
|
add_cyl(f"{name}_collar", 0.26, 0.14, (0, 0, 0.05), conc, verts=14),
|
||||||
|
add_cyl(f"{name}_collar_top", 0.22, 0.04, (0, 0, 0.13), conc, verts=14),
|
||||||
|
add_cyl(f"{name}_stain", 0.23, 0.012, (0, 0, 0.148), rust_d, verts=14),
|
||||||
|
], "footing", root)
|
||||||
|
|
||||||
|
# Same rake mechanism as build_sail_post — rake is a runtime decision and
|
||||||
|
# a corroded post rakes like any other; the lean you might be tempted to
|
||||||
|
# bake here would double-apply the moment a player rakes it.
|
||||||
|
rake = add_empty("rake_pivot", (0, 0, 0.12), root, size=0.25)
|
||||||
|
rake["rake_axis"] = "x/z — rake AWAY from the load (DESIGN.md)"
|
||||||
|
rake["rake_default_deg"] = 8
|
||||||
|
|
||||||
|
above = []
|
||||||
|
above.append(join_group([
|
||||||
|
add_cyl(f"{name}_shaft", R, H, (0, 0, H / 2), steel, verts=12),
|
||||||
|
add_cyl(f"{name}_base_plate", 0.11, 0.02, (0, 0, 0.13), dark, verts=12),
|
||||||
|
# The cap sits askew — rust let the friction fit go. Silhouette tell.
|
||||||
|
add_cyl(f"{name}_cap", R * 1.2, 0.02, (0, 0, H + 0.008), dark, verts=12,
|
||||||
|
rot=(math.radians(14), 0, 0)),
|
||||||
|
], "post"))
|
||||||
|
|
||||||
|
# The corrosion itself, its own node so the mutation control can kill it
|
||||||
|
# cleanly: blooms slightly proud of the shaft (rust swells), streaks
|
||||||
|
# bleeding DOWN from each bloom. Positions are hand-placed, not rng — the
|
||||||
|
# blooms sit where posts actually rust, and determinism costs nothing.
|
||||||
|
corrosion = [
|
||||||
|
add_cyl(f"{name}_rust_base", R + 0.004, 0.34, (0, 0, 0.34), rust, verts=12),
|
||||||
|
add_cyl(f"{name}_rust_line", R + 0.007, 0.11, (0, 0, 0.185), rust_d, verts=12),
|
||||||
|
add_cyl(f"{name}_rust_head", R + 0.004, 0.26, (0, 0, H - 0.16), rust, verts=12),
|
||||||
|
add_cyl(f"{name}_rust_weld", R + 0.006, 0.06, (0, 0, WELD_Z), rust_d, verts=12),
|
||||||
|
]
|
||||||
|
for i, (ang_deg, length, z_top) in enumerate([
|
||||||
|
(200, 1.45, H - 0.24), # off the weld collar, yard side (-y)
|
||||||
|
(335, 0.95, H - 0.30),
|
||||||
|
(25, 0.70, H - 0.35),
|
||||||
|
(160, 0.80, 0.95), # wicking up off the base band
|
||||||
|
(300, 0.60, 0.85)]):
|
||||||
|
a = math.radians(ang_deg)
|
||||||
|
corrosion.append(add_box(
|
||||||
|
f"{name}_streak_{i}", (0.016, 0.016, length),
|
||||||
|
(math.cos(a) * (R + 0.003), math.sin(a) * (R + 0.003),
|
||||||
|
z_top - length / 2), rust_d, rot=(0, 0, a)))
|
||||||
|
above.append(join_group(corrosion, "rust"))
|
||||||
|
|
||||||
|
# The sagging pad eye: plate and ring both pitched DROOP about the weld
|
||||||
|
# line, hanging toward -y (three.js +Z, the yard side — same face the
|
||||||
|
# pergola presents). Built in the tilted plane directly, no post-rotation.
|
||||||
|
ring_c = (0, -math.sin(DROOP) * 0.08, WELD_Z + math.cos(DROOP) * 0.08)
|
||||||
|
eye_parts = [add_box(f"{name}_padeye", (0.012, 0.07, 0.09),
|
||||||
|
(0, -math.sin(DROOP) * 0.04,
|
||||||
|
WELD_Z + math.cos(DROOP) * 0.04), rust_d,
|
||||||
|
rot=(-DROOP, 0, 0))]
|
||||||
|
r_eye, segs = 0.026, 10
|
||||||
|
pts = []
|
||||||
|
for i in range(segs + 1):
|
||||||
|
t = math.tau * i / segs
|
||||||
|
pts.append((math.cos(t) * r_eye,
|
||||||
|
ring_c[1] - math.sin(t) * r_eye * math.sin(DROOP),
|
||||||
|
ring_c[2] + math.sin(t) * r_eye * math.cos(DROOP)))
|
||||||
|
for i in range(segs):
|
||||||
|
eye_parts.append(add_tube_between(f"{name}_eye_s{i}", pts[i], pts[i + 1],
|
||||||
|
0.008, rust, verts=8))
|
||||||
|
above.append(join_group(eye_parts, "pad_eye"))
|
||||||
|
|
||||||
|
# The anchor sits AT the drooped eye — the sag is where you'd clip on,
|
||||||
|
# so the sag is data the sim sees, not paint.
|
||||||
|
e = add_empty("top_anchor", ring_c, size=0.2)
|
||||||
|
e["anchor_type"] = "corroded_post"
|
||||||
|
e["rating_hint"] = CORRODED_POST_RATING
|
||||||
|
e["collateral"] = "corroded_post"
|
||||||
|
e["why"] = ("a real post in a real footing, eaten: above the swing frame "
|
||||||
|
"(0.45) because concrete beats loose-on-grass, below the "
|
||||||
|
"pergola (0.65) because the bloom outside is pitting inside — "
|
||||||
|
"you de-rate what you can't inspect. The rung fills 0.45→0.65; "
|
||||||
|
"rungs create decisions")
|
||||||
|
above.append(e)
|
||||||
|
for o in above:
|
||||||
|
parent_keep_transform(o, rake)
|
||||||
|
|
||||||
|
stamp(root, name, "structure")
|
||||||
|
root["post_height"] = H
|
||||||
|
root["rake_note"] = "rotate about rake_pivot; rake away from the load"
|
||||||
|
root["collateral_key"] = "corroded_post"
|
||||||
|
root["collateral_value"] = CORRODED_POST_COLLATERAL
|
||||||
|
root["collateral_label"] = "the corroded post"
|
||||||
|
root["breakable"] = True
|
||||||
|
root["corrosion_note"] = ("the tell is load-bearing: e.test pins rust "
|
||||||
|
"area, shaft dullness and the eye sag — a "
|
||||||
|
"clean-steel rebuild at the same hint goes red")
|
||||||
|
return root
|
||||||
|
|
||||||
|
|
||||||
|
def build_sail_post_corroded_wrecked(name):
|
||||||
|
"""The corroded post after a loaded corner won: FOLDED at the deep-rust
|
||||||
|
line just above the footing (where the intact variant wears its worst
|
||||||
|
band — the wreck fails exactly where the tell said it would), stub
|
||||||
|
standing plumb with a torn rim, the rest of the post flat on the grass
|
||||||
|
reaching ~3.8 m into the yard (-y here, three.js +Z), pad eye still
|
||||||
|
drooped at the far end with nothing left to hold. Same footing, same
|
||||||
|
numbers, same price as its twin (the carport/gutter chain, application
|
||||||
|
six). No anchor empty survives — an anchor that outlives its structure
|
||||||
|
is the free-failure bug in a costume (fascia rule, fourth application)."""
|
||||||
|
root = add_empty(name)
|
||||||
|
steel = get_material("Mat_SteelWeathered", PAL["steel_weathered"], 0.6,
|
||||||
|
metallic=0.55)
|
||||||
|
dark = get_material("Mat_SteelDark", PAL["steel_dark"], 0.45, metallic=0.8)
|
||||||
|
rust = get_material("Mat_Rust", PAL["rust_bloom"], 0.9, metallic=0.1)
|
||||||
|
rust_d = get_material("Mat_RustDeep", PAL["rust_deep"], 0.95, metallic=0.05)
|
||||||
|
conc = get_material("Mat_Concrete", PAL["concrete"], 0.95)
|
||||||
|
H, R = 4.0, 0.048
|
||||||
|
FOLD = 0.46 # it let go at the rust line, not the weld:
|
||||||
|
# the fold is where the section was thinnest
|
||||||
|
|
||||||
|
join_group([
|
||||||
|
add_cyl(f"{name}_collar", 0.26, 0.14, (0, 0, 0.05), conc, verts=14),
|
||||||
|
add_cyl(f"{name}_collar_top", 0.22, 0.04, (0, 0, 0.13), conc, verts=14),
|
||||||
|
add_cyl(f"{name}_stain", 0.23, 0.012, (0, 0, 0.148), rust_d, verts=14),
|
||||||
|
], "footing", root)
|
||||||
|
|
||||||
|
# The stub: plumb (the footing held — that half of the 0.55 was honest),
|
||||||
|
# deep rust at the fold, torn rim leaning the way the post went.
|
||||||
|
join_group([
|
||||||
|
add_cyl(f"{name}_stub", R, FOLD, (0, 0, 0.14 + FOLD / 2), steel, verts=12),
|
||||||
|
add_cyl(f"{name}_base_plate", 0.11, 0.02, (0, 0, 0.13), dark, verts=12),
|
||||||
|
add_cyl(f"{name}_stub_rust", R + 0.007, 0.14, (0, 0, 0.14 + FOLD - 0.07),
|
||||||
|
rust_d, verts=12),
|
||||||
|
add_cone(f"{name}_tear", R + 0.004, 0.012, 0.09,
|
||||||
|
(0.005, -0.02, 0.14 + FOLD + 0.035), rust_d, verts=10,
|
||||||
|
rot=(math.radians(18), 0, 0)),
|
||||||
|
], "post_stub", root)
|
||||||
|
|
||||||
|
# The fallen length, resting on its radius, slightly yawed — nothing real
|
||||||
|
# falls on axis. Blooms ride along it where the intact bands were.
|
||||||
|
L = H - FOLD
|
||||||
|
yaw = math.radians(7)
|
||||||
|
rotv = (math.radians(90), 0, yaw)
|
||||||
|
|
||||||
|
def along(s): # a point s metres from the break end, down the fallen shaft
|
||||||
|
return (-math.sin(yaw) * (0.30 + s), -math.cos(yaw) * (0.30 + s), R)
|
||||||
|
|
||||||
|
down = [
|
||||||
|
add_cyl(f"{name}_shaft_down", R, L, along(L / 2), steel, verts=12, rot=rotv),
|
||||||
|
add_cyl(f"{name}_down_break", R + 0.007, 0.18, along(0.10), rust_d,
|
||||||
|
verts=12, rot=rotv),
|
||||||
|
add_cyl(f"{name}_down_mid", R + 0.004, 0.30, along(L * 0.55), rust,
|
||||||
|
verts=12, rot=rotv),
|
||||||
|
add_cyl(f"{name}_down_head", R + 0.004, 0.26, along(L - 0.35), rust,
|
||||||
|
verts=12, rot=rotv),
|
||||||
|
add_cyl(f"{name}_down_cap", R * 1.2, 0.02, along(L - 0.01), dark,
|
||||||
|
verts=12, rot=rotv),
|
||||||
|
]
|
||||||
|
join_group(down, "post_down", root)
|
||||||
|
|
||||||
|
# The pad eye at the far end, on the grass, still drooped, still rusty —
|
||||||
|
# the one part of this post that was never going to hold anything again.
|
||||||
|
ex, ey, _ = along(L - 0.10)
|
||||||
|
eye = [add_box(f"{name}_padeye_down", (0.012, 0.09, 0.07),
|
||||||
|
(ex + 0.09, ey, 0.035), rust_d,
|
||||||
|
rot=(0, math.radians(80), 0)),
|
||||||
|
add_arc_tube(f"{name}_eye_down", 0.026, 0.008, 0, math.tau, rust,
|
||||||
|
segs=10, center=(ex + 0.16, ey, 0.012), plane='XY')]
|
||||||
|
join_group(eye, "pad_eye_down", root)
|
||||||
|
|
||||||
|
stamp(root, name, "structure")
|
||||||
|
root["broken_variant_of"] = "sail_post_corroded"
|
||||||
|
root["collateral_key"] = "corroded_post"
|
||||||
|
root["collateral_value"] = CORRODED_POST_COLLATERAL
|
||||||
|
root["collateral_label"] = "the corroded post"
|
||||||
|
return root
|
||||||
|
|
||||||
|
|
||||||
def build_ladder_01(name):
|
def build_ladder_01(name):
|
||||||
root = add_empty(name)
|
root = add_empty(name)
|
||||||
alu = get_material("Mat_Steel", PAL["steel_gal"], 0.35, metallic=0.85)
|
alu = get_material("Mat_Steel", PAL["steel_gal"], 0.35, metallic=0.85)
|
||||||
@ -3544,6 +3804,19 @@ ASSETS = [
|
|||||||
dims=((5.4, 5.9), (4.1, 4.8), (1.12, 1.32)),
|
dims=((5.4, 5.9), (4.1, 4.8), (1.12, 1.32)),
|
||||||
nodes=["pool_shell", "pool_water", "fence_rails", "pool_gate",
|
nodes=["pool_shell", "pool_water", "fence_rails", "pool_gate",
|
||||||
"fence_post_01", "fence_post_14"]),
|
"fence_post_01", "fence_post_14"]),
|
||||||
|
# SPRINT17 gate 3.1 — the corroded tier. The intact variant must stand
|
||||||
|
# POST height (its lie is "I'm a sail post", so it stands like one; only
|
||||||
|
# the colour and the drooped eye say otherwise). The wreck's z ceiling is
|
||||||
|
# under a metre because the post FOLDED at the rust line — a corroded
|
||||||
|
# wreck still standing tall is a wreck that never told the truth — and
|
||||||
|
# its y floor is past 3.5 because the fallen shaft is out in the yard.
|
||||||
|
dict(name="sail_post_corroded", fn=build_sail_post_corroded,
|
||||||
|
dims=((0.40, 0.60), (0.40, 0.65), (3.90, 4.15)),
|
||||||
|
nodes=["footing", "post", "rust", "pad_eye", "top_anchor",
|
||||||
|
"rake_pivot"]),
|
||||||
|
dict(name="sail_post_corroded_wrecked", fn=build_sail_post_corroded_wrecked,
|
||||||
|
dims=((0.40, 0.80), (3.50, 4.40), (0.45, 0.95)),
|
||||||
|
nodes=["footing", "post_stub", "post_down", "pad_eye_down"]),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 4.5 MiB After Width: | Height: | Size: 192 KiB |
@ -73,6 +73,13 @@ export const FACTORY_ANCHOR_RATINGS = Object.freeze({
|
|||||||
"carport_01/post_anchor_02"
|
"carport_01/post_anchor_02"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"corroded_post": {
|
||||||
|
"collateral": "corroded_post",
|
||||||
|
"rating_hint": 0.55,
|
||||||
|
"sources": [
|
||||||
|
"sail_post_corroded/top_anchor"
|
||||||
|
]
|
||||||
|
},
|
||||||
"house": {
|
"house": {
|
||||||
"collateral": "gutter",
|
"collateral": "gutter",
|
||||||
"rating_hint": 0.35,
|
"rating_hint": 0.35,
|
||||||
|
|||||||
@ -89,9 +89,21 @@ export const PHASES = ['forecast', 'prep', 'storm', 'aftermath'];
|
|||||||
* loose-on-grass (0.45), but the deck flexes and the timber cracks before
|
* loose-on-grass (0.45), but the deck flexes and the timber cracks before
|
||||||
* concrete would notice (1.00). One rating for the whole type, per the
|
* concrete would notice (1.00). One rating for the whole type, per the
|
||||||
* manifest rule (THREADS [E] S15): unambiguous types stay node-less-safe.
|
* manifest rule (THREADS [E] S15): unambiguous types stay node-less-safe.
|
||||||
|
*
|
||||||
|
* `corroded_post` (SPRINT17 gate 3.1, E): a sail post gone rusty — the pool
|
||||||
|
* yard's "corroded cheap hardware" thesis as an anchor word. NOT a `post`,
|
||||||
|
* and this time the word isn't about the footing (the footing is fine —
|
||||||
|
* concrete, same as ever): it's about the STEEL. "post" promises sound
|
||||||
|
* galvanised tube; this is eaten tube whose visible bloom is a floor on the
|
||||||
|
* damage, not a ceiling. rating_hint 0.55 — above the swing frame (footed
|
||||||
|
* beats loose), below the pergola (sound flexing timber beats steel you
|
||||||
|
* can't inspect the inside of). One rating for the whole type, per the
|
||||||
|
* manifest rule. The GLB wears the tell (rust, a sagging pad eye); the type
|
||||||
|
* string is the player's pre-rig read of the same fact.
|
||||||
*/
|
*/
|
||||||
export const ANCHOR_TYPE = Object.freeze([
|
export const ANCHOR_TYPE = Object.freeze([
|
||||||
'house', 'tree', 'post', 'carport', 'carport_post', 'swing_frame', 'pergola',
|
'house', 'tree', 'post', 'carport', 'carport_post', 'swing_frame', 'pergola',
|
||||||
|
'corroded_post',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|||||||
@ -116,6 +116,16 @@ const ASSETS = [
|
|||||||
{ name: 'pool_kit_01', h: [1.12, 1.32],
|
{ name: 'pool_kit_01', h: [1.12, 1.32],
|
||||||
nodes: ['pool_shell', 'pool_water', 'fence_rails', 'pool_gate',
|
nodes: ['pool_shell', 'pool_water', 'fence_rails', 'pool_gate',
|
||||||
'fence_post_01', 'fence_post_14'] },
|
'fence_post_01', 'fence_post_14'] },
|
||||||
|
// SPRINT17 gate 3.1 — the corroded tier. The intact variant stands POST
|
||||||
|
// height on purpose: its lie is "I'm a sail post", so it must stand like
|
||||||
|
// one, and only the colour and the drooped eye say otherwise. The wreck's
|
||||||
|
// ceiling is under a metre because the post FOLDED at the rust line — a
|
||||||
|
// corroded wreck still standing 4 m tall is a wreck that never told the
|
||||||
|
// truth about where it was weakest.
|
||||||
|
{ name: 'sail_post_corroded', h: [3.90, 4.15],
|
||||||
|
nodes: ['footing', 'post', 'rust', 'pad_eye', 'top_anchor', 'rake_pivot'] },
|
||||||
|
{ name: 'sail_post_corroded_wrecked', h: [0.45, 0.95],
|
||||||
|
nodes: ['footing', 'post_stub', 'post_down', 'pad_eye_down'] },
|
||||||
];
|
];
|
||||||
|
|
||||||
function sizeOf(gltf) {
|
function sizeOf(gltf) {
|
||||||
@ -1050,6 +1060,180 @@ export default async function run(t) {
|
|||||||
+ 'lie the invoice exists to kill; when debris can take a panel out, price it THEN');
|
+ 'lie the invoice exists to kill; when debris can take a panel out, price it THEN');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
// SPRINT17 gate 3.1 — THE CORRODED TIER. The pool kit is fourteen tie-offs
|
||||||
|
// that don't exist; this is the one that exists and shouldn't be trusted.
|
||||||
|
// The design claim is "corrosion a player can't see is a trap with no tell,
|
||||||
|
// and this repo doesn't ship those" — so the TELL itself is pinned as
|
||||||
|
// numbers (area, lightness, sag), not adjectives. An untelled corroded post
|
||||||
|
// (same 0.55 hint, clean steel) passes the factory, passes RULE 4a/4b, and
|
||||||
|
// passes every dims/nodes check — this test is the ONLY thing that goes red
|
||||||
|
// on it, which is exactly why it exists. (Proven red in the S17 negative
|
||||||
|
// controls; receipts in THREADS.)
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/** Total triangle area of every mesh under a GLB scene, split by predicate
|
||||||
|
* on the mesh's material. World transforms applied; area is exact. */
|
||||||
|
const areaBy = (scene, pred) => {
|
||||||
|
scene.updateMatrixWorld(true);
|
||||||
|
let hit = 0, total = 0;
|
||||||
|
const a = new THREE.Vector3(), b = new THREE.Vector3(), c = new THREE.Vector3();
|
||||||
|
scene.traverse((o) => {
|
||||||
|
if (!o.isMesh) return;
|
||||||
|
const pos = o.geometry.attributes.position;
|
||||||
|
const idx = o.geometry.index;
|
||||||
|
const n = idx ? idx.count : pos.count;
|
||||||
|
let area = 0;
|
||||||
|
for (let i = 0; i < n; i += 3) {
|
||||||
|
const ia = idx ? idx.getX(i) : i, ib = idx ? idx.getX(i + 1) : i + 1,
|
||||||
|
ic = idx ? idx.getX(i + 2) : i + 2;
|
||||||
|
a.fromBufferAttribute(pos, ia).applyMatrix4(o.matrixWorld);
|
||||||
|
b.fromBufferAttribute(pos, ib).applyMatrix4(o.matrixWorld);
|
||||||
|
c.fromBufferAttribute(pos, ic).applyMatrix4(o.matrixWorld);
|
||||||
|
b.sub(a); c.sub(a);
|
||||||
|
area += b.cross(c).length() / 2;
|
||||||
|
}
|
||||||
|
total += area;
|
||||||
|
if (pred(o.material)) hit += area;
|
||||||
|
});
|
||||||
|
return { hit, total };
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Rust, as a colour band: strongly saturated orange-brown, darker than
|
||||||
|
* mid. Nothing else in this palette is saturated in that hue (weathered
|
||||||
|
* steel and concrete are near-grey, s < 0.15; timber is lighter). Colours
|
||||||
|
* are read in the loader's working space — thresholds chosen with margin
|
||||||
|
* on both sides (rust s ≈ 0.8, everything else ≤ 0.15). */
|
||||||
|
const isRust = (m) => {
|
||||||
|
const hsl = { h: 0, s: 0, l: 0 };
|
||||||
|
(m.color ?? new THREE.Color(0xffffff)).getHSL(hsl);
|
||||||
|
return hsl.h <= 0.12 && hsl.s >= 0.35 && hsl.l <= 0.45;
|
||||||
|
};
|
||||||
|
|
||||||
|
t.test('the corroded post TELLS: rust area, duller steel, and a pad eye that sags — all measured', () => {
|
||||||
|
const g = loaded.get('sail_post_corroded');
|
||||||
|
const honest = loaded.get('sail_post');
|
||||||
|
assert(g && honest, 'both post variants must load — the tell is a comparison');
|
||||||
|
|
||||||
|
// (a) The at-a-glance read, as a number: rust-coloured surface area. 5%
|
||||||
|
// of a 4 m post is roughly the base band + head bloom + streaks — under
|
||||||
|
// that and the corrosion is a caption, not a tell. (Shipped value ~19%.)
|
||||||
|
const { hit, total } = areaBy(g.scene, isRust);
|
||||||
|
assert(total > 0, 'no measurable surface at all');
|
||||||
|
const frac = hit / total;
|
||||||
|
assert(frac >= 0.05,
|
||||||
|
`rust covers ${(frac * 100).toFixed(1)}% of the corroded post's surface — under 5% ` +
|
||||||
|
'is corrosion nobody can read from the lawn, a trap with no tell');
|
||||||
|
// And the honest post must NOT read rusty — the tell only works if the
|
||||||
|
// clean one is clean. (Also guards the colour thresholds themselves.)
|
||||||
|
const h = areaBy(honest.scene, isRust);
|
||||||
|
assert(h.hit === 0,
|
||||||
|
`the HONEST sail post carries ${(h.hit / h.total * 100).toFixed(1)}% rust-coloured area — ` +
|
||||||
|
'either the post rusted or the rust predicate is reading galvanised steel as rust');
|
||||||
|
|
||||||
|
// (b) The 20 m read: the corroded SHAFT is measurably duller/darker than
|
||||||
|
// galvanised. Compare the lightest non-rust material on each 'post' node —
|
||||||
|
// that's the shaft steel on both (dark trim is darker by construction).
|
||||||
|
const lightestL = (gl) => {
|
||||||
|
let best = -1;
|
||||||
|
gl.scene.getObjectByName('post').traverse((o) => {
|
||||||
|
if (!o.isMesh || isRust(o.material)) return;
|
||||||
|
const hsl = { h: 0, s: 0, l: 0 };
|
||||||
|
o.material.color.getHSL(hsl);
|
||||||
|
best = Math.max(best, hsl.l);
|
||||||
|
});
|
||||||
|
return best;
|
||||||
|
};
|
||||||
|
const lHonest = lightestL(honest), lCorroded = lightestL(g);
|
||||||
|
assert(lHonest >= 0 && lCorroded >= 0, 'could not find shaft materials on the post nodes');
|
||||||
|
assert(lCorroded < lHonest - 0.08,
|
||||||
|
`corroded shaft lightness ${lCorroded.toFixed(3)} vs galvanised ${lHonest.toFixed(3)} — ` +
|
||||||
|
'the shafts read the same at yard distance, which erases the long-range half of the tell');
|
||||||
|
|
||||||
|
// (c) The sag is geometry the sim SEES: the anchor sits at the drooped
|
||||||
|
// eye, below where the honest post carries its. Not set dressing.
|
||||||
|
const anchorY = (gl) => {
|
||||||
|
gl.scene.updateMatrixWorld(true);
|
||||||
|
return gl.scene.getObjectByName('top_anchor').getWorldPosition(new THREE.Vector3()).y;
|
||||||
|
};
|
||||||
|
const sag = anchorY(honest) - anchorY(g);
|
||||||
|
assert(sag > 0.05,
|
||||||
|
`the corroded top_anchor sits only ${sag.toFixed(3)} m below the honest one — ` +
|
||||||
|
'the pad eye is meant to have SAGGED, visibly and measurably');
|
||||||
|
});
|
||||||
|
|
||||||
|
t.test('corroded steel is a RUNG: above the swing frame, below sound timber, resolvable node-less', () => {
|
||||||
|
const T = FACTORY_ANCHOR_RATINGS.types;
|
||||||
|
const entry = T.corroded_post;
|
||||||
|
assert(entry, 'corroded_post is not in the ratings manifest — the type cannot resolve node-less');
|
||||||
|
assert(!FACTORY_ANCHOR_RATINGS.ambiguous.corroded_post,
|
||||||
|
'corroded_post is manifest-AMBIGUOUS — the one-rating-per-type rule (THREADS [E] S15) broke');
|
||||||
|
// The ladder claims, pinned against the MANIFEST, never against literals
|
||||||
|
// (a literal pin would stay green while the whole ladder reshuffled):
|
||||||
|
assert(T.swing_frame.rating_hint < entry.rating_hint,
|
||||||
|
`corroded post (${entry.rating_hint}) must out-rate the swing frame (${T.swing_frame.rating_hint}) — ` +
|
||||||
|
'a real concrete footing beats sound steel standing loose on grass');
|
||||||
|
assert(entry.rating_hint < T.pergola.rating_hint,
|
||||||
|
`corroded post (${entry.rating_hint}) must rate under the pergola (${T.pergola.rating_hint}) — ` +
|
||||||
|
'sound flexing timber beats steel whose bloom outside means pitting inside');
|
||||||
|
assert(entry.rating_hint < T.post.rating_hint,
|
||||||
|
'a corroded post rating at or above the honest post erases the tier entirely');
|
||||||
|
// It fails through the client's property: collateral named, priced, and
|
||||||
|
// priced UNDER the gutter — the bill is the make-safe, not the steel; the
|
||||||
|
// real price of trusting rust is the corner you lose mid-storm.
|
||||||
|
assert(entry.collateral === 'corroded_post',
|
||||||
|
'the manifest must carry the collateral so node-less anchors bill on failure');
|
||||||
|
const root = g('sail_post_corroded');
|
||||||
|
const gutter = g('house_yardside');
|
||||||
|
assert(typeof root.userData.collateral_value === 'number' && root.userData.collateral_value > 0,
|
||||||
|
'the corroded post names collateral nothing prices — free failure, the gutter bug');
|
||||||
|
assert(root.userData.collateral_value < gutter.userData.collateral_value,
|
||||||
|
`a snapped corroded post ($${root.userData.collateral_value}) must bill under the gutter ` +
|
||||||
|
`($${gutter.userData.collateral_value}) — condemned steel is a make-safe charge, not a rebuild`);
|
||||||
|
|
||||||
|
function g(name) {
|
||||||
|
const glb = loaded.get(name);
|
||||||
|
assert(glb, `${name} did not load`);
|
||||||
|
return glb.scene.getObjectByName(name);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
t.test('the corroded wreck folded at the rust line: stub stands, shaft in the yard, nothing left to tie to', () => {
|
||||||
|
const w = loaded.get('sail_post_corroded_wrecked');
|
||||||
|
assert(w, 'sail_post_corroded_wrecked did not load');
|
||||||
|
const box = new THREE.Box3().setFromObject(w.scene);
|
||||||
|
// It FELL: nothing stands past the stub, and the fallen shaft reaches
|
||||||
|
// into the yard on +Z (the same face the pergola presents — placement
|
||||||
|
// facts in THREADS). Both directions measured, not asserted by name.
|
||||||
|
assert(box.max.y < 1.0,
|
||||||
|
`the wreck stands ${box.max.y.toFixed(2)} m — a corroded post that still stands never folded`);
|
||||||
|
assert(box.max.z > 3.2,
|
||||||
|
`the fallen shaft reaches z=${box.max.z.toFixed(2)} — it is meant to be ~3.5 m out in the yard (+Z)`);
|
||||||
|
// No anchor survives, nothing rates, nothing enrols — an anchor that
|
||||||
|
// outlives its structure is the free-failure bug in a costume (the
|
||||||
|
// fascia rule; fourth application).
|
||||||
|
assert(!w.scene.getObjectByName('top_anchor'),
|
||||||
|
'top_anchor survives the wreck — you cannot clip to a post that is lying on the grass');
|
||||||
|
const offers = [];
|
||||||
|
w.scene.traverse((o) => {
|
||||||
|
if (typeof o.userData?.rating_hint === 'number' || o.userData?.anchor_type !== undefined) {
|
||||||
|
offers.push(o.name);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
assert(offers.length === 0,
|
||||||
|
`the wreck offers [${offers.join(', ')}] — a wreck must offer nothing`);
|
||||||
|
// Twin-priced, and it names its twin — the carport/gutter chain, sixth
|
||||||
|
// application, compared between the GLBs and never against a literal.
|
||||||
|
const wr = w.scene.getObjectByName('sail_post_corroded_wrecked');
|
||||||
|
const ir = loaded.get('sail_post_corroded')?.scene.getObjectByName('sail_post_corroded');
|
||||||
|
assert(wr?.userData?.broken_variant_of === 'sail_post_corroded',
|
||||||
|
'the wreck must name its intact twin so A can pair the swap');
|
||||||
|
assert(wr?.userData?.collateral_value === ir?.userData?.collateral_value,
|
||||||
|
'the wrecked post must be priced the same as the one it used to be');
|
||||||
|
assert(wr?.userData?.collateral_key === 'corroded_post',
|
||||||
|
'the wreck must keep the collateral key or the swap changes what the bill is about');
|
||||||
|
});
|
||||||
|
|
||||||
// One GLB carries three wilt states as siblings; Lane A toggles .visible
|
// One GLB carries three wilt states as siblings; Lane A toggles .visible
|
||||||
// rather than reloading, so all three have to be present at once.
|
// rather than reloading, so all three have to be present at once.
|
||||||
t.test('garden_bed carries all 3 damage states in one GLB', () => {
|
t.test('garden_bed carries all 3 damage states in one GLB', () => {
|
||||||
|
|||||||
BIN
web/world/models/sail_post_corroded_v1.glb
Normal file
BIN
web/world/models/sail_post_corroded_v1.glb
Normal file
Binary file not shown.
BIN
web/world/models/sail_post_corroded_wrecked_v1.glb
Normal file
BIN
web/world/models/sail_post_corroded_wrecked_v1.glb
Normal file
Binary file not shown.
Loading…
Reference in New Issue
Block a user