fable-engine 1.3.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (104) hide show
  1. fable_compressor.py +356 -0
  2. fable_engine/__init__.py +1 -0
  3. fable_engine/actions/__init__.py +291 -0
  4. fable_engine/actions/cas.py +182 -0
  5. fable_engine/actions/deliberation.py +523 -0
  6. fable_engine/actions/fleet.py +807 -0
  7. fable_engine/actions/lifecycle.py +298 -0
  8. fable_engine/actions/scrapers.py +116 -0
  9. fable_engine/actions/system3.py +815 -0
  10. fable_engine/browser.py +824 -0
  11. fable_engine/cas.py +974 -0
  12. fable_engine/fable_session.json +510 -0
  13. fable_engine/guards.py +283 -0
  14. fable_engine/schema.py +714 -0
  15. fable_engine/scrapers/__init__.py +32 -0
  16. fable_engine/scrapers/arxiv.py +115 -0
  17. fable_engine/scrapers/base.py +386 -0
  18. fable_engine/scrapers/github.py +129 -0
  19. fable_engine/scrapers/reddit.py +154 -0
  20. fable_engine/scrapers/web.py +120 -0
  21. fable_engine/scrapers/x.py +125 -0
  22. fable_engine/scrapers/youtube.py +132 -0
  23. fable_engine/server.py +414 -0
  24. fable_engine/session.py +1819 -0
  25. fable_engine/test_server.py +1362 -0
  26. fable_engine/updater.py +541 -0
  27. fable_engine-1.3.1.dist-info/LICENSE +22 -0
  28. fable_engine-1.3.1.dist-info/METADATA +173 -0
  29. fable_engine-1.3.1.dist-info/RECORD +104 -0
  30. fable_engine-1.3.1.dist-info/WHEEL +5 -0
  31. fable_engine-1.3.1.dist-info/entry_points.txt +5 -0
  32. fable_engine-1.3.1.dist-info/top_level.txt +6 -0
  33. fable_mode/__init__.py +3 -0
  34. fable_mode/__main__.py +4 -0
  35. fable_mode/adapters.py +1014 -0
  36. fable_mode/installer.py +553 -0
  37. fable_mode/launcher.py +437 -0
  38. fable_mode/manifest.py +142 -0
  39. fable_mode/resources.json +114 -0
  40. fable_mode/safety.py +103 -0
  41. fable_mode_entry.py +10 -0
  42. fable_v2/__init__.py +146 -0
  43. fable_v2/adapters.py +151 -0
  44. fable_v2/coder_fleet/__init__.py +100 -0
  45. fable_v2/coder_fleet/ast_tools.py +158 -0
  46. fable_v2/coder_fleet/compute.py +199 -0
  47. fable_v2/coder_fleet/design_engine.py +1316 -0
  48. fable_v2/coder_fleet/diagnostics.py +293 -0
  49. fable_v2/coder_fleet/fleet_dispatcher.py +214 -0
  50. fable_v2/coder_fleet/mock_auditor.py +306 -0
  51. fable_v2/coder_fleet/mutation.py +216 -0
  52. fable_v2/coder_fleet/property_oracle.py +260 -0
  53. fable_v2/coder_fleet/receipt_attestor.py +122 -0
  54. fable_v2/coder_fleet/red_team_swarm.py +908 -0
  55. fable_v2/coder_fleet/test_harness.py +198 -0
  56. fable_v2/coder_fleet/vector_engine.py +1287 -0
  57. fable_v2/coder_fleet/visual.py +357 -0
  58. fable_v2/coder_fleet/workspace.py +153 -0
  59. fable_v2/cortical/__init__.py +20 -0
  60. fable_v2/cortical/plasticity_engine.py +992 -0
  61. fable_v2/execution_broker.py +811 -0
  62. fable_v2/proof_engine.py +1141 -0
  63. fable_v2/protocol.py +485 -0
  64. fable_v2/runtime.py +1010 -0
  65. fable_v2/system3/__init__.py +204 -0
  66. fable_v2/system3/causal.py +558 -0
  67. fable_v2/system3/dialectical.py +577 -0
  68. fable_v2/system3/evolution.py +503 -0
  69. fable_v2/system3/executive.py +338 -0
  70. fable_v2/system3/free_energy.py +479 -0
  71. fable_v2/system3/hyperbolic.py +555 -0
  72. fable_v2/system3/induction.py +336 -0
  73. fable_v2/system3/kripke.py +548 -0
  74. fable_v2/system3/oracle.py +745 -0
  75. fable_v2/verifiers.py +72 -0
  76. tests/__init__.py +1 -0
  77. tests/test_anti_loop_circuit_breaker.py +64 -0
  78. tests/test_auto_updater.py +407 -0
  79. tests/test_coder_fleet.py +535 -0
  80. tests/test_delegation_compiler.py +54 -0
  81. tests/test_descriptor_boundaries.py +126 -0
  82. tests/test_design_engine.py +603 -0
  83. tests/test_epistemic_evidence_validator.py +66 -0
  84. tests/test_execution_broker.py +233 -0
  85. tests/test_fable_v2.py +406 -0
  86. tests/test_fleet_transitions.py +116 -0
  87. tests/test_fsm_redteam_evolution.py +406 -0
  88. tests/test_goal_rubric_and_pipeline.py +367 -0
  89. tests/test_hebbian_plasticity.py +585 -0
  90. tests/test_packaging_runtime.py +194 -0
  91. tests/test_proof_engine.py +259 -0
  92. tests/test_red_team_swarm.py +645 -0
  93. tests/test_redteam_remediation.py +169 -0
  94. tests/test_registration_transaction.py +375 -0
  95. tests/test_requested_regressions.py +467 -0
  96. tests/test_scrapers.py +370 -0
  97. tests/test_server_actions.py +93 -0
  98. tests/test_server_frontier_actions.py +269 -0
  99. tests/test_server_protocol.py +88 -0
  100. tests/test_stealth_browser.py +970 -0
  101. tests/test_system3.py +381 -0
  102. tests/test_system3_deep_integration.py +385 -0
  103. tests/test_system3_frontier.py +436 -0
  104. tests/test_vector_engine.py +608 -0
@@ -0,0 +1,608 @@
1
+ """Comprehensive Unit Test Suite for Fable-Vector Neuro-Symbolic Vector Engine.
2
+
3
+ Verifies:
4
+ 1. OKLCHColor and APCA contrast calibration (|Lc| >= 60, WCAG AAA accessibility, relative luminance, edge cases).
5
+ 2. ParametricGeometry (Polar/Cartesian, circular arcs, Catmull-Rom splines with C1/C2 continuity, isometric matrix).
6
+ 3. BoundingBox (spatial queries, intersections, enclosure, viewBox formatting).
7
+ 4. VNode (AST tree structure, serialization, defensive sanitization).
8
+ 5. VLayoutSolver (relational constraints, viewport pinning, thread-safety, defensive guards against NaN/Inf/null-bytes/deep-nesting).
9
+ 6. FableVectorCompiler (XML AST well-formedness, viewBox enclosure, procedural noise filters, OKLCH gradients, a11y).
10
+ 7. CoderFleetDispatcher vector integration ("compile_vector" and "solve_layout" actions).
11
+ """
12
+ from __future__ import annotations
13
+
14
+ import concurrent.futures
15
+ import math
16
+ import sys
17
+ import unittest
18
+ import xml.etree.ElementTree as ET
19
+ from pathlib import Path
20
+
21
+ # Ensure workspace root is in sys.path
22
+ sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
23
+
24
+ from fable_v2.coder_fleet import (
25
+ BoundingBox,
26
+ CoderFleetDispatcher,
27
+ FableVectorCompiler,
28
+ OKLCHColor,
29
+ ParametricGeometry,
30
+ VLayoutSolver,
31
+ VNode,
32
+ )
33
+
34
+
35
+ class TestOKLCHColorAndAPCA(unittest.TestCase):
36
+ """Test suite for OKLCH color space math, luminance, and APCA contrast scoring."""
37
+
38
+ def test_oklch_initialization_and_clamping(self) -> None:
39
+ c1 = OKLCHColor(l=0.75, c=0.15, h=250.0, alpha=0.9)
40
+ self.assertAlmostEqual(c1.l, 0.75)
41
+ self.assertAlmostEqual(c1.c, 0.15)
42
+ self.assertAlmostEqual(c1.h, 250.0)
43
+ self.assertAlmostEqual(c1.alpha, 0.9)
44
+
45
+ # Defensive clamping for out-of-bound inputs
46
+ c2 = OKLCHColor(l=-0.5, c=-0.1, h=400.0, alpha=1.5)
47
+ self.assertEqual(c2.l, 0.0)
48
+ self.assertEqual(c2.c, 0.0)
49
+ self.assertAlmostEqual(c2.h, 40.0)
50
+ self.assertEqual(c2.alpha, 1.0)
51
+
52
+ # Defensive coercion for NaN and Inf
53
+ c3 = OKLCHColor(l=float("nan"), c=float("inf"), h=float("-inf"), alpha=float("nan"))
54
+ self.assertEqual(c3.l, 0.0)
55
+ self.assertEqual(c3.c, 0.0)
56
+ self.assertEqual(c3.h, 0.0)
57
+ self.assertEqual(c3.alpha, 1.0)
58
+
59
+ def test_to_svg_formatting(self) -> None:
60
+ c_opaque = OKLCHColor(l=0.96, c=0.01, h=270.0, alpha=1.0)
61
+ self.assertEqual(c_opaque.to_svg(), "oklch(0.9600 0.0100 270.00)")
62
+
63
+ c_translucent = OKLCHColor(l=0.12, c=0.02, h=270.0, alpha=0.85)
64
+ self.assertEqual(c_translucent.to_svg(), "oklch(0.1200 0.0200 270.00 / 0.85)")
65
+
66
+ def test_estimated_y_relative_luminance(self) -> None:
67
+ # Achromatic black
68
+ black = OKLCHColor(l=0.0, c=0.0, h=0.0)
69
+ self.assertAlmostEqual(black.estimated_y(), 0.0, places=4)
70
+
71
+ # Achromatic white
72
+ white = OKLCHColor(l=1.0, c=0.0, h=0.0)
73
+ self.assertAlmostEqual(white.estimated_y(), 1.0, places=4)
74
+
75
+ # Mid-gray achromatic: Y == l^3
76
+ mid_gray = OKLCHColor(l=0.5, c=0.0, h=0.0)
77
+ self.assertAlmostEqual(mid_gray.estimated_y(), 0.125, places=4)
78
+
79
+ # Chromatic color should produce valid Y in [0, 1]
80
+ cyan = OKLCHColor(l=0.75, c=0.18, h=190.0)
81
+ y_cyan = cyan.estimated_y()
82
+ self.assertGreaterEqual(y_cyan, 0.0)
83
+ self.assertLessEqual(y_cyan, 1.0)
84
+
85
+ def test_apca_contrast_high_contrast_pairs(self) -> None:
86
+ """Verify APCA contrast meets or exceeds |Lc| >= 60 for accessible typography."""
87
+ # Cyber-Obsidian Monolith design tokens:
88
+ # Light text on deep obsidian substrate (WoB - negative score)
89
+ text_primary = OKLCHColor(l=0.96, c=0.01, h=270.0) # High-contrast read
90
+ void_deep = OKLCHColor(l=0.08, c=0.02, h=270.0) # Deep obsidian
91
+ lc_wob = text_primary.apca_contrast(void_deep)
92
+
93
+ self.assertLess(lc_wob, 0.0, "Light text on dark bg should yield negative Lc in APCA")
94
+ self.assertGreaterEqual(abs(lc_wob), 60.0, f"Expected |Lc| >= 60, got {abs(lc_wob)}")
95
+ self.assertGreaterEqual(abs(lc_wob), 75.0, f"Expected WCAG AAA |Lc| >= 75, got {abs(lc_wob)}")
96
+
97
+ # Dark text on light background (BoW - positive score)
98
+ dark_text = OKLCHColor(l=0.08, c=0.02, h=270.0)
99
+ light_bg = OKLCHColor(l=0.98, c=0.01, h=270.0)
100
+ lc_bow = dark_text.apca_contrast(light_bg)
101
+
102
+ self.assertGreater(lc_bow, 0.0, "Dark text on light bg should yield positive Lc in APCA")
103
+ self.assertGreaterEqual(lc_bow, 60.0, f"Expected Lc >= 60, got {lc_bow}")
104
+ self.assertGreaterEqual(lc_bow, 75.0, f"Expected WCAG AAA Lc >= 75, got {lc_bow}")
105
+
106
+ def test_apca_contrast_edge_cases(self) -> None:
107
+ # Identical colors must have zero contrast
108
+ color = OKLCHColor(l=0.5, c=0.1, h=180.0)
109
+ self.assertAlmostEqual(color.apca_contrast(color), 0.0, places=3)
110
+
111
+ # Extremely small delta luminance below noise threshold
112
+ c_a = OKLCHColor(l=0.500, c=0.0, h=0.0)
113
+ c_b = OKLCHColor(l=0.5001, c=0.0, h=0.0)
114
+ self.assertEqual(c_a.apca_contrast(c_b), 0.0)
115
+
116
+ def test_from_hex_conversion(self) -> None:
117
+ c_black = OKLCHColor.from_hex("#000000")
118
+ self.assertAlmostEqual(c_black.l, 0.0, places=3)
119
+ self.assertAlmostEqual(c_black.c, 0.0, places=3)
120
+
121
+ c_white = OKLCHColor.from_hex("#ffffff")
122
+ self.assertAlmostEqual(c_white.l, 1.0, places=3)
123
+ self.assertAlmostEqual(c_white.c, 0.0, places=3)
124
+
125
+ # Contrast between white and black
126
+ lc = c_white.apca_contrast(c_black)
127
+ self.assertGreaterEqual(abs(lc), 100.0)
128
+
129
+
130
+ class TestParametricGeometry(unittest.TestCase):
131
+ """Test suite for 2D parametric geometry, arcs, splines, and isometric projection."""
132
+
133
+ def test_polar_to_cartesian(self) -> None:
134
+ # Center (100, 100), radius 50
135
+ x, y = ParametricGeometry.polar_to_cartesian(100.0, 100.0, 50.0, 0.0)
136
+ self.assertAlmostEqual(x, 150.0, places=3)
137
+ self.assertAlmostEqual(y, 100.0, places=3)
138
+
139
+ x, y = ParametricGeometry.polar_to_cartesian(100.0, 100.0, 50.0, 90.0)
140
+ self.assertAlmostEqual(x, 100.0, places=3)
141
+ self.assertAlmostEqual(y, 150.0, places=3)
142
+
143
+ x, y = ParametricGeometry.polar_to_cartesian(100.0, 100.0, 50.0, 180.0)
144
+ self.assertAlmostEqual(x, 50.0, places=3)
145
+ self.assertAlmostEqual(y, 100.0, places=3)
146
+
147
+ x, y = ParametricGeometry.polar_to_cartesian(100.0, 100.0, 50.0, 270.0)
148
+ self.assertAlmostEqual(x, 100.0, places=3)
149
+ self.assertAlmostEqual(y, 50.0, places=3)
150
+
151
+ def test_circular_arc(self) -> None:
152
+ # 90 degree arc
153
+ arc_d = ParametricGeometry.circular_arc(50.0, 50.0, 30.0, 0.0, 90.0, clock_wise=True)
154
+ self.assertTrue(arc_d.startswith("M 80.0000 50.0000 A 30.0000 30.0000 0 0 1"))
155
+
156
+ # Full 360 degree circle
157
+ full_circle_d = ParametricGeometry.circular_arc(50.0, 50.0, 30.0, 0.0, 360.0)
158
+ self.assertIn("A 30.0000 30.0000", full_circle_d)
159
+ # Semicircles combined
160
+ self.assertEqual(full_circle_d.count("A"), 2)
161
+
162
+ def test_catmull_rom_spline_c1_continuity(self) -> None:
163
+ """Verify Catmull-Rom spline generates exact C1 (tangent) continuous cubic Bezier segments."""
164
+ pts = [
165
+ (20.0, 30.0),
166
+ (60.0, 120.0),
167
+ (120.0, 80.0),
168
+ (180.0, 160.0),
169
+ (240.0, 50.0),
170
+ (300.0, 100.0),
171
+ ]
172
+
173
+ segments = ParametricGeometry.derive_catmull_rom_bezier_segments(pts, tension=0.5, closed=False)
174
+ self.assertEqual(len(segments), len(pts) - 1)
175
+
176
+ # For every adjacent pair of segments (S_k and S_{k+1}), verify:
177
+ # 1. C0 continuity: end point of S_k == start point of S_{k+1}
178
+ # 2. C1 continuity: tangent leaving S_k == tangent entering S_{k+1}
179
+ for k in range(len(segments) - 1):
180
+ p1_curr, c1_curr, c2_curr, p2_curr = segments[k]
181
+ p1_next, c1_next, c2_next, p2_next = segments[k + 1]
182
+
183
+ # C0 positional continuity
184
+ self.assertAlmostEqual(p2_curr[0], p1_next[0], places=4)
185
+ self.assertAlmostEqual(p2_curr[1], p1_next[1], places=4)
186
+
187
+ # Cubic Bezier first derivative:
188
+ # Incoming tangent at end of segment k: B'_curr(1) = 3 * (P2 - C2)
189
+ tan_in_x = 3.0 * (p2_curr[0] - c2_curr[0])
190
+ tan_in_y = 3.0 * (p2_curr[1] - c2_curr[1])
191
+
192
+ # Outgoing tangent at start of segment k+1: B'_next(0) = 3 * (C1 - P1)
193
+ tan_out_x = 3.0 * (c1_next[0] - p1_next[0])
194
+ tan_out_y = 3.0 * (c1_next[1] - p1_next[1])
195
+
196
+ # Tangents must be identical within floating point threshold
197
+ self.assertAlmostEqual(tan_in_x, tan_out_x, places=4, msg=f"C1 continuity violated in X at junction {k}")
198
+ self.assertAlmostEqual(tan_in_y, tan_out_y, places=4, msg=f"C1 continuity violated in Y at junction {k}")
199
+
200
+ def test_catmull_rom_spline_svg_output(self) -> None:
201
+ pts = [(10.0, 10.0), (50.0, 80.0), (100.0, 30.0)]
202
+ d_str = ParametricGeometry.catmull_rom_spline(pts)
203
+ self.assertTrue(d_str.startswith("M 10.0000 10.0000"))
204
+ self.assertIn("C ", d_str)
205
+ self.assertFalse(d_str.endswith("Z"))
206
+
207
+ # Closed spline
208
+ closed_d = ParametricGeometry.catmull_rom_spline(pts, closed=True)
209
+ self.assertTrue(closed_d.endswith("Z"))
210
+
211
+ def test_isometric_projection_30_degree(self) -> None:
212
+ # Origin
213
+ iso_x, iso_y = ParametricGeometry.isometric_project(0.0, 0.0, 0.0)
214
+ self.assertEqual(iso_x, 0.0)
215
+ self.assertEqual(iso_y, 0.0)
216
+
217
+ # X axis point: (100, 0, 0)
218
+ # x_iso = 100 * cos(30 deg) = 100 * 0.866025 = 86.6025
219
+ # y_iso = 100 * sin(30 deg) = 100 * 0.5 = 50.0
220
+ iso_x, iso_y = ParametricGeometry.isometric_project(100.0, 0.0, 0.0)
221
+ self.assertAlmostEqual(iso_x, 86.6025, places=3)
222
+ self.assertAlmostEqual(iso_y, 50.0, places=3)
223
+
224
+ # Z elevation: (0, 0, 50) -> y_screen shifted upward by 50
225
+ iso_x, iso_y = ParametricGeometry.isometric_project(0.0, 0.0, 50.0)
226
+ self.assertEqual(iso_x, 0.0)
227
+ self.assertAlmostEqual(iso_y, -50.0, places=3)
228
+
229
+
230
+ class TestBoundingBox(unittest.TestCase):
231
+ """Test suite for spatial queries and viewBox bounding box calculations."""
232
+
233
+ def test_bbox_properties(self) -> None:
234
+ b = BoundingBox(x=10.0, y=20.0, width=100.0, height=50.0)
235
+ self.assertEqual(b.right, 110.0)
236
+ self.assertEqual(b.bottom, 70.0)
237
+ self.assertEqual(b.cx, 60.0)
238
+ self.assertEqual(b.cy, 45.0)
239
+
240
+ def test_contains(self) -> None:
241
+ b = BoundingBox(x=0.0, y=0.0, width=100.0, height=100.0)
242
+ # Point containment
243
+ self.assertTrue(b.contains((50.0, 50.0)))
244
+ self.assertTrue(b.contains((0.0, 0.0)))
245
+ self.assertTrue(b.contains((100.0, 100.0)))
246
+ self.assertFalse(b.contains((101.0, 50.0)))
247
+ self.assertFalse(b.contains((-1.0, 50.0)))
248
+
249
+ # Sub-box containment
250
+ inside = BoundingBox(x=10.0, y=10.0, width=50.0, height=50.0)
251
+ self.assertTrue(b.contains(inside))
252
+
253
+ outside = BoundingBox(x=90.0, y=90.0, width=20.0, height=20.0)
254
+ self.assertFalse(b.contains(outside))
255
+
256
+ def test_intersects(self) -> None:
257
+ b1 = BoundingBox(x=0.0, y=0.0, width=50.0, height=50.0)
258
+ b2 = BoundingBox(x=40.0, y=40.0, width=50.0, height=50.0)
259
+ b3 = BoundingBox(x=60.0, y=60.0, width=50.0, height=50.0)
260
+
261
+ self.assertTrue(b1.intersects(b2))
262
+ self.assertTrue(b2.intersects(b3))
263
+ self.assertFalse(b1.intersects(b3))
264
+
265
+ def test_enclosing_box_and_viewbox(self) -> None:
266
+ boxes = [
267
+ BoundingBox(x=10.0, y=20.0, width=40.0, height=30.0),
268
+ BoundingBox(x=100.0, y=80.0, width=50.0, height=60.0),
269
+ ]
270
+ enc = BoundingBox.enclosing(boxes, padding=10.0)
271
+ # min_x = 10, min_y = 20, max_right = 150, max_bottom = 140
272
+ # with pad=10: x=0, y=10, width=140+20=160, height=120+20=140
273
+ self.assertEqual(enc.x, 0.0)
274
+ self.assertEqual(enc.y, 10.0)
275
+ self.assertEqual(enc.width, 160.0)
276
+ self.assertEqual(enc.height, 140.0)
277
+
278
+ viewbox = enc.to_viewbox()
279
+ self.assertEqual(viewbox, "0.00 10.00 160.00 140.00")
280
+
281
+
282
+ class TestVNode(unittest.TestCase):
283
+ """Test suite for VNode AST representation, serialization, and sanitization."""
284
+
285
+ def test_vnode_creation_and_defaults(self) -> None:
286
+ node = VNode(id="card_main", kind="card", width=300.0, height=200.0)
287
+ self.assertEqual(node.id, "card_main")
288
+ self.assertEqual(node.kind, "card")
289
+ self.assertEqual(node.width, 300.0)
290
+ self.assertEqual(node.height, 200.0)
291
+ self.assertEqual(node.bbox.width, 300.0)
292
+
293
+ def test_vnode_defensive_sanitization(self) -> None:
294
+ # Null bytes stripped
295
+ bad_id = "node\x00_test"
296
+ bad_kind = "CARD\x00"
297
+ node = VNode(id=bad_id, kind=bad_kind, width=-50.0, height=float("nan"))
298
+ self.assertEqual(node.id, "node_test")
299
+ self.assertEqual(node.kind, "card")
300
+ self.assertEqual(node.width, 0.0)
301
+ self.assertEqual(node.height, 0.0)
302
+
303
+ def test_vnode_hierarchy_and_serialization(self) -> None:
304
+ parent = VNode(id="root", kind="card", width=400.0, height=300.0)
305
+ child = VNode(id="badge1", kind="badge", width=80.0, height=24.0)
306
+ parent.add_child(child)
307
+
308
+ self.assertEqual(len(parent.children), 1)
309
+ found = parent.find_by_id("badge1")
310
+ self.assertIsNotNone(found)
311
+ self.assertEqual(found.id, "badge1")
312
+
313
+ # Roundtrip serialization
314
+ data = parent.to_dict()
315
+ restored = VNode.from_dict(data)
316
+ self.assertEqual(restored.id, "root")
317
+ self.assertEqual(len(restored.children), 1)
318
+ self.assertEqual(restored.children[0].id, "badge1")
319
+
320
+
321
+ class TestVLayoutSolver(unittest.TestCase):
322
+ """Test suite for relational 2D layout solver, constraints, and defensive guards."""
323
+
324
+ def setUp(self) -> None:
325
+ self.solver = VLayoutSolver()
326
+
327
+ def test_pin_to_viewport_anchors(self) -> None:
328
+ node = VNode(id="panel", kind="card", width=200.0, height=100.0)
329
+
330
+ # Top-Left
331
+ self.solver.pin_to_viewport(node, 1920.0, 1080.0, anchor="top-left", margin=20.0)
332
+ self.assertEqual(node.x, 20.0)
333
+ self.assertEqual(node.y, 20.0)
334
+
335
+ # Top-Right
336
+ self.solver.pin_to_viewport(node, 1920.0, 1080.0, anchor="top-right", margin=20.0)
337
+ self.assertEqual(node.x, 1920.0 - 200.0 - 20.0)
338
+ self.assertEqual(node.y, 20.0)
339
+
340
+ # Bottom-Right
341
+ self.solver.pin_to_viewport(node, 1920.0, 1080.0, anchor="bottom-right", margin=10.0)
342
+ self.assertEqual(node.x, 1920.0 - 200.0 - 10.0)
343
+ self.assertEqual(node.y, 1080.0 - 100.0 - 10.0)
344
+
345
+ # Center
346
+ self.solver.pin_to_viewport(node, 1000.0, 600.0, anchor="center")
347
+ self.assertEqual(node.x, 400.0)
348
+ self.assertEqual(node.y, 250.0)
349
+
350
+ def test_align_relative_relations(self) -> None:
351
+ ref = VNode(id="ref", kind="card", x=100.0, y=100.0, width=200.0, height=150.0)
352
+ target = VNode(id="target", kind="badge", width=50.0, height=30.0)
353
+
354
+ # Right of with middle alignment
355
+ self.solver.align_relative(target, ref, relation="right_of", gap=15.0, alignment="center")
356
+ self.assertEqual(target.x, 100.0 + 200.0 + 15.0)
357
+ self.assertEqual(target.y, ref.cy - 15.0)
358
+
359
+ # Below with start alignment
360
+ self.solver.align_relative(target, ref, relation="below", gap=10.0, alignment="start")
361
+ self.assertEqual(target.x, 100.0)
362
+ self.assertEqual(target.y, 100.0 + 150.0 + 10.0)
363
+
364
+ # Left of
365
+ self.solver.align_relative(target, ref, relation="left_of", gap=5.0, alignment="start")
366
+ self.assertEqual(target.x, 100.0 - 50.0 - 5.0)
367
+ self.assertEqual(target.y, 100.0)
368
+
369
+ # Inside / center
370
+ self.solver.align_relative(target, ref, relation="inside")
371
+ self.assertEqual(target.x, 100.0 + (200.0 - 50.0) / 2.0)
372
+ self.assertEqual(target.y, 100.0 + (150.0 - 30.0) / 2.0)
373
+
374
+ def test_solve_with_props_constraints(self) -> None:
375
+ root = VNode(id="container", kind="card", width=800.0, height=600.0)
376
+ header = VNode(id="header", kind="card", width=760.0, height=80.0, props={
377
+ "pin_to_viewport": {"anchor": "top-left", "margin": 20.0},
378
+ })
379
+ content = VNode(id="content", kind="card", width=760.0, height=450.0, props={
380
+ "align_relative": {"reference_id": "header", "relation": "below", "gap": 20.0},
381
+ })
382
+ root.add_child(header)
383
+ root.add_child(content)
384
+
385
+ bbox = self.solver.solve(root, viewport_width=800.0, viewport_height=600.0)
386
+ self.assertEqual(header.x, 20.0)
387
+ self.assertEqual(header.y, 20.0)
388
+ self.assertEqual(content.x, 20.0)
389
+ self.assertEqual(content.y, 120.0)
390
+ self.assertGreater(bbox.width, 0.0)
391
+ self.assertGreater(bbox.height, 0.0)
392
+
393
+ def test_defensive_input_guards(self) -> None:
394
+ # Guard against NaN/Inf in solve
395
+ node = VNode(id="bad_node", kind="card", width=float("nan"), height=float("inf"))
396
+ bbox = self.solver.solve(node, viewport_width=float("nan"), viewport_height=float("-inf"))
397
+ self.assertTrue(math.isfinite(bbox.x))
398
+ self.assertTrue(math.isfinite(bbox.y))
399
+ self.assertTrue(math.isfinite(bbox.width))
400
+ self.assertTrue(math.isfinite(bbox.height))
401
+
402
+ # Guard against deep nesting recursion overflow
403
+ deep_root = VNode(id="lvl_0", kind="card")
404
+ curr = deep_root
405
+ for idx in range(1, 80):
406
+ nxt = VNode(id=f"lvl_{idx}", kind="card")
407
+ curr.add_child(nxt)
408
+ curr = nxt
409
+
410
+ with self.assertRaises(ValueError):
411
+ self.solver.solve(deep_root)
412
+
413
+ def test_thread_safety(self) -> None:
414
+ solver = VLayoutSolver()
415
+
416
+ def worker(idx: int) -> float:
417
+ n1 = VNode(id=f"n1_{idx}", kind="card", width=100.0, height=50.0)
418
+ n2 = VNode(id=f"n2_{idx}", kind="badge", width=40.0, height=20.0)
419
+ solver.pin_to_viewport(n1, 1000.0, 1000.0, anchor="top-left", margin=idx * 2.0)
420
+ solver.align_relative(n2, n1, relation="right_of", gap=5.0)
421
+ return n2.x
422
+
423
+ with concurrent.futures.ThreadPoolExecutor(max_workers=8) as executor:
424
+ futures = [executor.submit(worker, i) for i in range(20)]
425
+ results = [f.result() for f in futures]
426
+
427
+ self.assertEqual(len(results), 20)
428
+ for i, res in enumerate(results):
429
+ expected = (i * 2.0) + 100.0 + 5.0
430
+ self.assertEqual(res, expected)
431
+
432
+
433
+ class TestFableVectorCompiler(unittest.TestCase):
434
+ """Test suite for SVG compilation, XML AST well-formedness, filters, and gradients."""
435
+
436
+ def setUp(self) -> None:
437
+ self.compiler = FableVectorCompiler()
438
+
439
+ def test_compile_xml_ast_well_formedness(self) -> None:
440
+ # Build comprehensive scene with all 7 VNode kinds
441
+ root = VNode(id="dashboard", kind="card", x=0.0, y=0.0, width=1200.0, height=800.0, props={
442
+ "title": "Quantum Telemetry Dashboard",
443
+ "desc": "Real-time Fable Neuro-Symbolic Vector Telemetry",
444
+ })
445
+
446
+ # Dial node
447
+ dial = VNode(id="cpu_gauge", kind="dial", x=50.0, y=50.0, width=160.0, height=160.0, props={
448
+ "value": 0.82,
449
+ "label": "82% CPU",
450
+ })
451
+ root.add_child(dial)
452
+
453
+ # Spline node
454
+ spline = VNode(id="waveform", kind="spline", x=250.0, y=50.0, width=400.0, height=160.0, props={
455
+ "points": [(0, 80), (100, 20), (200, 140), (300, 40), (400, 80)],
456
+ "tension": 0.5,
457
+ })
458
+ root.add_child(spline)
459
+
460
+ # Text node
461
+ text_node = VNode(id="header_text", kind="text", x=700.0, y=80.0, width=300.0, height=30.0, props={
462
+ "text": "NEURO-SYMBOLIC RUNTIME: NOMINAL",
463
+ "font_size": 16,
464
+ "font_weight": "700",
465
+ })
466
+ root.add_child(text_node)
467
+
468
+ # Badge node
469
+ badge = VNode(id="status_badge", kind="badge", x=700.0, y=120.0, width=100.0, height=28.0, props={
470
+ "label": "ONLINE",
471
+ })
472
+ root.add_child(badge)
473
+
474
+ # Grid node
475
+ grid = VNode(id="bg_grid", kind="grid", x=50.0, y=260.0, width=500.0, height=400.0, props={
476
+ "spacing": 32.0,
477
+ "isometric": False,
478
+ })
479
+ root.add_child(grid)
480
+
481
+ # Isometric grid
482
+ iso_grid = VNode(id="iso_grid", kind="grid", x=600.0, y=260.0, width=500.0, height=400.0, props={
483
+ "spacing": 40.0,
484
+ "isometric": True,
485
+ })
486
+ root.add_child(iso_grid)
487
+
488
+ # Conduit node
489
+ conduit = VNode(id="wire_1", kind="conduit", x=210.0, y=130.0, width=40.0, height=0.0, props={
490
+ "x1": 210.0, "y1": 130.0, "x2": 250.0, "y2": 130.0,
491
+ })
492
+ root.add_child(conduit)
493
+
494
+ # Compile to SVG
495
+ svg_code = self.compiler.compile(root)
496
+ self.assertIsInstance(svg_code, str)
497
+ self.assertGreater(len(svg_code), 500)
498
+
499
+ # Validate that output is strictly well-formed XML AST
500
+ xml_root = ET.fromstring(svg_code)
501
+ self.assertEqual(xml_root.tag.split("}")[-1], "svg")
502
+
503
+ # Accessibility checks
504
+ self.assertEqual(xml_root.attrib.get("role"), "img")
505
+ self.assertIn("Quantum Telemetry", xml_root.attrib.get("aria-label", ""))
506
+
507
+ # Verify <title> and <desc>
508
+ titles = [e.text for e in xml_root.findall("{http://www.w3.org/2000/svg}title") or xml_root.findall("title")]
509
+ self.assertTrue(any("Quantum Telemetry" in str(t) for t in titles))
510
+
511
+ # Procedural noise filter check
512
+ filters = xml_root.findall(".//{http://www.w3.org/2000/svg}filter") or xml_root.findall(".//filter")
513
+ filter_ids = [f.attrib.get("id") for f in filters]
514
+ self.assertIn("fable-micro-grain", filter_ids)
515
+
516
+ turbulences = xml_root.findall(".//{http://www.w3.org/2000/svg}feTurbulence") or xml_root.findall(".//feTurbulence")
517
+ self.assertGreaterEqual(len(turbulences), 1)
518
+
519
+ # OKLCH Gradients check
520
+ linear_grads = xml_root.findall(".//{http://www.w3.org/2000/svg}linearGradient") or xml_root.findall(".//linearGradient")
521
+ grad_ids = [g.attrib.get("id") for g in linear_grads]
522
+ self.assertIn("fable-grad-obsidian", grad_ids)
523
+ self.assertIn("fable-grad-specular", grad_ids)
524
+ self.assertIn("fable-grad-accent", grad_ids)
525
+
526
+ # ViewBox enclosure check
527
+ viewbox = xml_root.attrib.get("viewBox", "")
528
+ self.assertTrue(viewbox, "viewBox attribute must be present")
529
+ vb_parts = [float(p) for p in viewbox.split()]
530
+ self.assertEqual(len(vb_parts), 4)
531
+ self.assertGreaterEqual(vb_parts[2], 1200.0)
532
+ self.assertGreaterEqual(vb_parts[3], 800.0)
533
+
534
+
535
+ class TestFleetDispatcherVectorIntegration(unittest.TestCase):
536
+ """Test suite for CoderFleetDispatcher vector action dispatching."""
537
+
538
+ def setUp(self) -> None:
539
+ self.dispatcher = CoderFleetDispatcher()
540
+
541
+ def test_list_actions_contains_vector_actions(self) -> None:
542
+ actions = self.dispatcher.list_actions()
543
+ self.assertIn("compile_vector", actions)
544
+ self.assertIn("solve_layout", actions)
545
+
546
+ def test_dispatch_compile_vector(self) -> None:
547
+ node_def = {
548
+ "id": "telemetry_card",
549
+ "kind": "card",
550
+ "width": 320.0,
551
+ "height": 180.0,
552
+ "props": {
553
+ "title": "Core Status",
554
+ },
555
+ "children": [
556
+ {
557
+ "id": "badge_ok",
558
+ "kind": "badge",
559
+ "x": 20.0,
560
+ "y": 20.0,
561
+ "width": 60.0,
562
+ "height": 24.0,
563
+ "props": {"label": "OK"},
564
+ }
565
+ ],
566
+ }
567
+ res = self.dispatcher.dispatch("compile_vector", {"root": node_def})
568
+ self.assertTrue(res["success"])
569
+ self.assertIn("svg", res["result"])
570
+ self.assertTrue(res["result"]["valid"])
571
+
572
+ # Check SVG string validity
573
+ svg_code = res["result"]["svg"]
574
+ parsed = ET.fromstring(svg_code)
575
+ self.assertEqual(parsed.tag.split("}")[-1], "svg")
576
+
577
+ def test_dispatch_solve_layout(self) -> None:
578
+ node_def = {
579
+ "id": "root_card",
580
+ "kind": "card",
581
+ "width": 500.0,
582
+ "height": 300.0,
583
+ "children": [
584
+ {
585
+ "id": "sidebar",
586
+ "kind": "card",
587
+ "width": 120.0,
588
+ "height": 260.0,
589
+ "props": {"pin_to_viewport": {"anchor": "top-left", "margin": 20.0}},
590
+ },
591
+ {
592
+ "id": "main_view",
593
+ "kind": "card",
594
+ "width": 320.0,
595
+ "height": 260.0,
596
+ "props": {"align_relative": {"reference_id": "sidebar", "relation": "right_of", "gap": 20.0}},
597
+ },
598
+ ],
599
+ }
600
+ res = self.dispatcher.dispatch("solve_layout", {"root": node_def, "viewport_width": 500.0, "viewport_height": 300.0})
601
+ self.assertTrue(res["success"])
602
+ self.assertIn("bbox", res["result"])
603
+ self.assertIn("viewBox", res["result"])
604
+ self.assertEqual(res["result"]["root"]["children"][1]["x"], 160.0)
605
+
606
+
607
+ if __name__ == "__main__":
608
+ unittest.main()