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",
|
||||
"assets": [
|
||||
{
|
||||
"name": "ref_capsule",
|
||||
"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",
|
||||
"name": "sail_post_corroded",
|
||||
"dims": [
|
||||
0.507,
|
||||
0.52,
|
||||
4.0327
|
||||
4.0516
|
||||
],
|
||||
"tris": 528,
|
||||
"tris": 816,
|
||||
"nodes": [
|
||||
"footing",
|
||||
"pad_eye",
|
||||
"post",
|
||||
"rake_pivot",
|
||||
"sail_post",
|
||||
"rust",
|
||||
"sail_post_corroded",
|
||||
"top_anchor"
|
||||
],
|
||||
"anchors": [
|
||||
{
|
||||
"node": "top_anchor",
|
||||
"anchor_type": "post",
|
||||
"rating_hint": 1.0
|
||||
}
|
||||
],
|
||||
"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
|
||||
"anchor_type": "corroded_post",
|
||||
"rating_hint": 0.55,
|
||||
"collateral": "corroded_post"
|
||||
}
|
||||
],
|
||||
"status": "PASS",
|
||||
|
||||
@ -137,6 +137,14 @@ PAL = {
|
||||
"bike_kid": "#D8483C", # the Henderson kid's bike — bought bright on purpose
|
||||
"bike_grip": "#3B4048", # grips and saddle
|
||||
"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
|
||||
"pool_water": "#4FA8BE", # chlorinated blue, NOT the pond's grey-green —
|
||||
# a pool is the one water in the game that is
|
||||
@ -2256,6 +2264,258 @@ def build_sail_post(name):
|
||||
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):
|
||||
root = add_empty(name)
|
||||
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)),
|
||||
nodes=["pool_shell", "pool_water", "fence_rails", "pool_gate",
|
||||
"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"
|
||||
]
|
||||
},
|
||||
"corroded_post": {
|
||||
"collateral": "corroded_post",
|
||||
"rating_hint": 0.55,
|
||||
"sources": [
|
||||
"sail_post_corroded/top_anchor"
|
||||
]
|
||||
},
|
||||
"house": {
|
||||
"collateral": "gutter",
|
||||
"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
|
||||
* concrete would notice (1.00). One rating for the whole type, per the
|
||||
* 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([
|
||||
'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],
|
||||
nodes: ['pool_shell', 'pool_water', 'fence_rails', 'pool_gate',
|
||||
'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) {
|
||||
@ -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');
|
||||
});
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// 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
|
||||
// rather than reloading, so all three have to be present at once.
|
||||
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