-
Notifications
You must be signed in to change notification settings - Fork 4
Expand file tree
/
Copy pathantikythera_diff_engine.jl
More file actions
2342 lines (2030 loc) · 100 KB
/
Copy pathantikythera_diff_engine.jl
File metadata and controls
2342 lines (2030 loc) · 100 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
# ==========================================================================
# THE ANTIKYTHERA DIFF-ENGINE (GEOM-CALC v2.0)
# ==========================================================================
# Geometric calculus via preloaded SDF fields.
# Derivative is a spatial property, not a symbolic procedure.
# GPU is foundry. Slack is tolerance. Throttle is flow gate.
# Calculus is just gears turning.
# ==========================================================================
using LinearAlgebra
# ----------------------------------------------------------
# GRUG SAY: NO SILENT OOPSIE.
# IF ROCK BREAK, GRUG WANT TO HEAR LOUD CRUNCH.
# GRUG ALSO WANT TO KNOW WHICH ROCK AND WHY.
# ----------------------------------------------------------
struct MachineCrunch <: Exception
message::String
context::String # GRUG: Which gear broke? What was happening?
MachineCrunch(msg::String) = new(msg, "")
MachineCrunch(msg::String, ctx::String) = new(msg, ctx)
end
function Base.showerror(io::IO, e::MachineCrunch)
print(io, "⚙️ MACHINE CRUNCH: ", e.message)
!isempty(e.context) && print(io, "\n CONTEXT: ", e.context)
end
# ----------------------------------------------------------
# THE COG: THIS IS THE NONLINEAR GEAR.
# IT HAS "SHAPE" (SDF) AND "TEETH" (PARAMETERS).
# GRUG: Think of it like a bronze gear in the mechanism.
# The shape is the gear profile.
# The teeth are what other gears push on.
# ----------------------------------------------------------
mutable struct Cog
name::Symbol
# GRUG: "How far is rock from gear?" logic.
# This is the Signed Distance Field.
# Negative = inside gear. Zero = on surface. Positive = outside.
shape_logic::Function
# GRUG: These are teeth. They change when other gears push them.
# params[1] might be radius, params[2] might be twist, etc.
teeth_params::Vector{Float64}
# GRUG: How many directions does this gear live in?
# 2D gear = flat. 3D gear = chunky.
ndims::Int
# GRUG: Each gear knows its PURPOSE. What topic does it channel toward?
# This is the underlying target - the "why" for the gear's existence.
# Used for intelligent perturbation when normal-aligned targets block geodesic paths.
# Empty string = no specific purpose (default behavior).
channel_topic::String
function Cog(name::Symbol, logic::Function, params::AbstractVector; ndims::Int=3, channel_topic::String="")
# GRUG: Convert any vector to Float64 teeth.
float_params = convert(Vector{Float64}, params)
# GRUG: Ghost gear no turn. Need at least one tooth.
if isempty(float_params)
throw(MachineCrunch("GEAR $(name) HAS NO TEETH. CANNOT TURN.", "Cog constructor"))
end
# GRUG: Gear must live somewhere. No zero-dimension ghosts.
if ndims < 2 || ndims > 3
throw(MachineCrunch("GEAR $(name) MUST BE 2D OR 3D. GOT $(ndims)D.", "Cog constructor"))
end
new(name, logic, float_params, ndims, channel_topic)
end
end
# ----------------------------------------------------------
# THE MAP: THE ENTIRE ANTIKYTHERA CLOCKWORK.
# IT SITS IN MEMORY DOING NOTHING UNTIL FLOW STARTS.
# GRUG: This is the whole machine. All gears, one map.
# Valve shut = sleeping. Valve open = computing.
# ----------------------------------------------------------
mutable struct AntikytheraMap
gears::Dict{Symbol, Cog}
# GRUG: Water valve. 0.0 = shut, nobody home.
# 1.0 = wide open, full electrochemical flow.
throttle_clamp::Float64
# GRUG: AK-47 wiggle room. Gear not need to be perfect.
# If it rattle a little, it still work. That's compliance.
slack::Float64
# GRUG: How many times has machine been poked?
query_count::Int
function AntikytheraMap(wiggle::Float64=0.01)
if wiggle <= 0.0
throw(MachineCrunch("SLACK MUST BE POSITIVE. ZERO TOLERANCE IS BRITTLE.", "AntikytheraMap constructor"))
end
new(Dict{Symbol, Cog}(), 0.0, wiggle, 0)
end
end
# ==========================================================================
# GEAR LIBRARY: STOCK SDF SHAPES
# ==========================================================================
# GRUG: These are gear templates. Like cookie cutters for geometry.
# Traditional math chokes on most of these when you try to
# differentiate through them symbolically. But as SDF fields,
# the gradient is just "poke and measure."
# ==========================================================================
# ----------------------------------------------------------
# SPHERE: Simplest gear. Just a ball.
# GRUG: Even Grug can make round rock.
# params[1] = radius
# ----------------------------------------------------------
function sdf_sphere(p::Vector{Float64}, params::Vector{Float64})
return norm(p) - params[1]
end
# ----------------------------------------------------------
# TORUS: A donut gear. Ring with thickness.
# GRUG: Hard donut. Not for eating.
# params[1] = major radius (ring size)
# params[2] = minor radius (tube thickness)
# ----------------------------------------------------------
function sdf_torus(p::Vector{Float64}, params::Vector{Float64})
length(p) == 3 || throw(MachineCrunch("TORUS NEEDS 3D POINT.", "sdf_torus"))
R, r = params[1], params[2]
q = [sqrt(p[1]^2 + p[3]^2) - R, p[2]]
return norm(q) - r
end
# ----------------------------------------------------------
# BOX: A brick. Sharp edges.
# GRUG: Square rock. Very stable. Good for stacking.
# params[1:3] = half-extents (width, height, depth) / 2
# ----------------------------------------------------------
function sdf_box(p::Vector{Float64}, params::Vector{Float64})
length(p) == 3 || throw(MachineCrunch("BOX NEEDS 3D POINT.", "sdf_box"))
b = params[1:3]
q = abs.(p) .- b
return norm(max.(q, 0.0)) + min(maximum(q), 0.0)
end
# ----------------------------------------------------------
# CYLINDER: A tube standing up.
# GRUG: Like hollow log but math.
# params[1] = radius, params[2] = half-height
# ----------------------------------------------------------
function sdf_cylinder(p::Vector{Float64}, params::Vector{Float64})
length(p) == 3 || throw(MachineCrunch("CYLINDER NEEDS 3D POINT.", "sdf_cylinder"))
r, h = params[1], params[2]
d = [norm([p[1], p[3]]) - r, abs(p[2]) - h]
return min(max(d[1], d[2]), 0.0) + norm(max.(d, 0.0))
end
# ----------------------------------------------------------
# GYROID: Triply-periodic minimal surface.
# GRUG: This one is MAGIC ROCK. It tiles forever in all
# directions. Traditional math HATES this because the
# implicit surface has no closed-form gradient.
# But we just poke it and measure. Easy.
# params[1] = scale (period), params[2] = thickness
# ----------------------------------------------------------
function sdf_gyroid(p::Vector{Float64}, params::Vector{Float64})
length(p) == 3 || throw(MachineCrunch("GYROID NEEDS 3D POINT.", "sdf_gyroid"))
s = params[1] # scale
t = length(params) >= 2 ? params[2] : 0.0 # thickness offset
x, y, z = p[1] / s, p[2] / s, p[3] / s
return (sin(x) * cos(y) + sin(y) * cos(z) + sin(z) * cos(x)) - t
end
# ----------------------------------------------------------
# SCHWARZ P: Another triply-periodic minimal surface.
# GRUG: Gyroid's cousin. Also magic. Also hates symbolic diff.
# params[1] = scale, params[2] = thickness
# ----------------------------------------------------------
function sdf_schwarz_p(p::Vector{Float64}, params::Vector{Float64})
length(p) == 3 || throw(MachineCrunch("SCHWARZ NEEDS 3D POINT.", "sdf_schwarz_p"))
s = params[1]
t = length(params) >= 2 ? params[2] : 0.0
x, y, z = p[1] / s, p[2] / s, p[3] / s
return (cos(x) + cos(y) + cos(z)) - t
end
# ----------------------------------------------------------
# TWISTED TORUS: Torus with a helical twist.
# GRUG: Donut that somebody wrung like a towel.
# Symbolic differentiation of this is BRUTAL.
# Five nested trig functions. Chain rule explodes.
# But spatial probe? Still just poke-and-measure.
# params[1] = major R, params[2] = minor r, params[3] = twist rate
# ----------------------------------------------------------
function sdf_twisted_torus(p::Vector{Float64}, params::Vector{Float64})
length(p) == 3 || throw(MachineCrunch("TWISTED TORUS NEEDS 3D POINT.", "sdf_twisted_torus"))
R, r, twist = params[1], params[2], params[3]
# GRUG: First find angle around the ring
angle = atan(p[3], p[1])
# GRUG: Then twist the cross-section by that angle
q_x = sqrt(p[1]^2 + p[3]^2) - R
q_y = p[2]
twist_angle = angle * twist
rotated_x = q_x * cos(twist_angle) - q_y * sin(twist_angle)
rotated_y = q_x * sin(twist_angle) + q_y * cos(twist_angle)
return sqrt(rotated_x^2 + rotated_y^2) - r
end
# ----------------------------------------------------------
# GEAR LIBRARY REGISTRY
# GRUG: Menu of available cookie cutters.
# ----------------------------------------------------------
# ----------------------------------------------------------
# CONE: A pointy shape.
# GRUG: Like mountain but more stabby.
# params[1] = half-angle in radians, params[2] = height
# ----------------------------------------------------------
function sdf_cone(p::Vector{Float64}, params::Vector{Float64})
length(p) == 3 || throw(MachineCrunch("CONE NEEDS 3D POINT.", "sdf_cone"))
half_angle, h = params[1], params[2]
q = [sqrt(p[1]^2 + p[3]^2), p[2]]
sin_a, cos_a = sin(half_angle), cos(half_angle)
k = dot(q, [-sin_a, cos_a])
if k < 0.0
return norm(q)
end
if k > norm(q)
return norm(q .- [0.0, h])
end
return dot(q, [cos_a, sin_a])
end
# ----------------------------------------------------------
# CAPSULE: A pill shape. Cylinder with hemispherical caps.
# GRUG: Like fat ant. Or medicine pill.
# params[1] = radius, params[2] = half-height of cylinder
# ----------------------------------------------------------
function sdf_capsule(p::Vector{Float64}, params::Vector{Float64})
length(p) == 3 || throw(MachineCrunch("CAPSULE NEEDS 3D POINT.", "sdf_capsule"))
r, h = params[1], params[2]
q = [norm([p[1], p[3]]), p[2]]
q[2] -= clamp(q[2], -h, h)
return norm(q) - r
end
# ----------------------------------------------------------
# PLANE: An infinite flat surface.
# GRUG: Like world before Grug dug first hole.
# params[1:3] = normal direction, params[4] = offset
# ----------------------------------------------------------
function sdf_plane(p::Vector{Float64}, params::Vector{Float64})
length(p) == 3 || throw(MachineCrunch("PLANE NEEDS 3D POINT.", "sdf_plane"))
n = params[1:3]
n_norm = norm(n)
n_norm < 1e-12 && throw(MachineCrunch("PLANE NORMAL CANNOT BE ZERO.", "sdf_plane"))
return dot(p, n ./ n_norm) - params[4]
end
# ----------------------------------------------------------
# ELLIPSOID: A stretched ball.
# GRUG: Like sphere that ate too much in one direction.
# params[1:3] = semi-axes (a, b, c)
# ----------------------------------------------------------
function sdf_ellipsoid(p::Vector{Float64}, params::Vector{Float64})
length(p) == 3 || throw(MachineCrunch("ELLIPSOID NEEDS 3D POINT.", "sdf_ellipsoid"))
a, b, c = params[1], params[2], params[3]
k0 = norm([p[1]/a, p[2]/b, p[3]/c])
k1 = norm([p[1]/(a*a), p[2]/(b*b), p[3]/(c*c)])
k0 < 1e-12 && return -min(a, b, c)
return k0 * (k0 - 1.0) / k1
end
# ----------------------------------------------------------
# GEAR LIBRARY REGISTRY
# GRUG: Menu of available cookie cutters.
# ----------------------------------------------------------
const GEAR_LIBRARY = Dict{String, Tuple{Function, Vector{Float64}, Int, String}}(
"sphere" => (sdf_sphere, [5.0], 3, "Round rock. params: [radius]"),
"torus" => (sdf_torus, [8.0, 2.0], 3, "Donut. params: [major_R, minor_r]"),
"box" => (sdf_box, [3.0, 4.0, 5.0], 3, "Brick. params: [half_w, half_h, half_d]"),
"cylinder" => (sdf_cylinder, [3.0, 5.0], 3, "Tube. params: [radius, half_height]"),
"gyroid" => (sdf_gyroid, [6.28, 0.0], 3, "Magic tiling surface. params: [scale, thickness]"),
"schwarz" => (sdf_schwarz_p, [6.28, 0.0], 3, "Schwarz P surface. params: [scale, thickness]"),
"twisted_torus" => (sdf_twisted_torus, [8.0, 2.0, 1.5], 3, "Wrung donut. params: [major_R, minor_r, twist]"),
"cone" => (sdf_cone, [0.4, 5.0], 3, "Pointy rock. params: [half_angle_rad, height]"),
"capsule" => (sdf_capsule, [2.0, 4.0], 3, "Pill shape. params: [radius, half_height]"),
"plane" => (sdf_plane, [0.0, 1.0, 0.0, 0.0], 3, "Infinite flat. params: [nx, ny, nz, offset]"),
"ellipsoid" => (sdf_ellipsoid, [4.0, 2.0, 3.0], 3, "Stretched ball. params: [a, b, c semi-axes]"),
)
# ==========================================================================
# JIT CASTING: GPU FOUNDRY
# ==========================================================================
# GRUG: Use shiny light-box to make gears. Once made, light-box sleeps.
# You can cast from library or bring your own shape.
# ==========================================================================
function jit_cast_gears!(am::AntikytheraMap; preset::String="default")
# GRUG: "default" = load the standard gear set for demo
# GRUG: Each gear gets a channel_topic describing its underlying purpose.
if preset == "default"
am.gears[:Sphere] = Cog(:Sphere, sdf_sphere, [5.0]; channel_topic="shortest")
am.gears[:Torus] = Cog(:Torus, sdf_torus, [8.0, 2.0]; channel_topic="meridian")
am.gears[:Gyroid] = Cog(:Gyroid, sdf_gyroid, [6.28, 0.0]; channel_topic="spiral")
am.gears[:TwistedTorus] = Cog(:TwistedTorus, sdf_twisted_torus, [8.0, 2.0, 1.5]; channel_topic="spiral")
println("⚙️ GRUG: Foundry cast 4 default gears. Light-box is now off.")
elseif preset == "all"
for (name, (fn, params, nd, _)) in GEAR_LIBRARY
sym = Symbol(uppercasefirst(name))
# GRUG: Assign default channel topics based on shape
default_topic = _default_channel_topic(name)
am.gears[sym] = Cog(sym, fn, copy(params); ndims=nd, channel_topic=default_topic)
end
println("⚙️ GRUG: Foundry cast $(length(GEAR_LIBRARY)) gears (full library). Light-box is now off.")
else
throw(MachineCrunch("UNKNOWN PRESET: $(preset)", "jit_cast_gears!"))
end
end
# ----------------------------------------------------------
# DEFAULT CHANNEL TOPIC ASSIGNMENT
# GRUG: Give each shape a sensible default topic for pathfinding.
# ----------------------------------------------------------
function _default_channel_topic(shape_name::String)::String
# GRUG: Map shape names to sensible default channel topics
topic_map = Dict{String, String}(
"sphere" => "shortest",
"torus" => "meridian",
"box" => "shortest",
"cylinder" => "meridian",
"gyroid" => "spiral",
"schwarz" => "spiral",
"twisted_torus" => "spiral",
"cone" => "radial",
"capsule" => "shortest",
"plane" => "shortest",
"ellipsoid" => "shortest"
)
return get(topic_map, lowercase(shape_name), "shortest")
end
function cast_single!(am::AntikytheraMap, gear_name::Symbol, shape_key::String, params::Vector{Float64}; channel_topic::String="")
# GRUG: Cast one gear from library with custom params.
# GRUG: channel_topic tells the gear its underlying purpose for intelligent pathfinding.
if !haskey(GEAR_LIBRARY, shape_key)
throw(MachineCrunch("NO SUCH SHAPE IN LIBRARY: $(shape_key)", "cast_single!"))
end
fn, _, nd, _ = GEAR_LIBRARY[shape_key]
am.gears[gear_name] = Cog(gear_name, fn, params; ndims=nd, channel_topic=channel_topic)
topic_info = isempty(channel_topic) ? "" : " [topic: $(channel_topic)]"
println("⚙️ GRUG: Cast gear :$(gear_name) as $(shape_key) with params=$(params)$(topic_info)")
end
# ==========================================================================
# CORE GEOMETRIC OPERATIONS
# ==========================================================================
# GRUG: These are the things you can DO to gears.
# Traditional math dies on most of these for complex shapes.
# We just poke the field and measure what happens.
# ==========================================================================
# ----------------------------------------------------------
# VALIDATION HELPERS
# GRUG: Check everything before turning any gear.
# ----------------------------------------------------------
function _require_flow!(am::AntikytheraMap)
if am.throttle_clamp < 0.01
throw(MachineCrunch("THROTTLE SHUT. MACHINE IDLE. NO FLOW.", "throttle_check"))
end
end
function _require_gear(am::AntikytheraMap, name::Symbol)::Cog
!haskey(am.gears, name) && throw(MachineCrunch("GEAR :$(name) IS MISSING!", "gear_lookup"))
return am.gears[name]
end
function _require_point(point::Vector{Float64}, gear::Cog)
if length(point) != gear.ndims
throw(MachineCrunch(
"POINT IS $(length(point))D BUT GEAR :$(gear.name) IS $(gear.ndims)D.",
"dimension_check"
))
end
end
# GRUG: Make a poke vector. All zeros except position `dim` which gets value `val`.
function _basis(ndims::Int, dim::Int, val::Float64)::Vector{Float64}
v = zeros(ndims)
v[dim] = val
return v
end
# ----------------------------------------------------------
# /probe — RAW SDF EVALUATION
# GRUG: "How far is this spot from the gear surface?"
# Negative = inside. Zero = on surface. Positive = outside.
# ----------------------------------------------------------
function probe(am::AntikytheraMap, gear_name::Symbol, point::Vector{Float64})::Float64
_require_flow!(am)
gear = _require_gear(am, gear_name)
_require_point(point, gear)
am.query_count += 1
return gear.shape_logic(point, gear.teeth_params)
end
# ----------------------------------------------------------
# /gradient — SPATIAL DIFFERENTIATION (THE CORE OPERATION)
# GRUG: "Which way does the gear surface tilt at this spot?"
# We don't calculate. We poke and measure.
# This is what traditional d/dx does symbolically,
# but we do it spatially on the preloaded field.
# ----------------------------------------------------------
function gradient(am::AntikytheraMap, gear_name::Symbol, point::Vector{Float64})::Vector{Float64}
_require_flow!(am)
gear = _require_gear(am, gear_name)
_require_point(point, gear)
am.query_count += 1
f = gear.shape_logic
p = gear.teeth_params
h = am.slack # AK-47 rattle = finite difference step
nd = gear.ndims
grad = zeros(nd)
try
for dim in 1:nd
fwd = f(point .+ _basis(nd, dim, h), p)
bwd = f(point .- _basis(nd, dim, h), p)
grad[dim] = (fwd - bwd) / (2 * h)
end
catch err
throw(MachineCrunch(
"GEAR CRUNCHED IN :$(gear_name) DURING GRADIENT.",
sprint(showerror, err)
))
end
# GRUG: If math go crazy, stop machine. No silent fail!
if any(isnan, grad) || any(isinf, grad)
throw(MachineCrunch("GEAR JAMMED IN :$(gear_name). GRADIENT IS BROKEN.", "NaN/Inf detected"))
end
return grad
end
# ----------------------------------------------------------
# /normal — UNIT SURFACE NORMAL
# GRUG: "Which way does the surface FACE at this spot?"
# Just the gradient, but normalized to length 1.
# Needed for lighting, reflection, collision, everything.
#
# GRUG NOTE: If point has zero gradient (e.g. dead centre of torus tube,
# sphere origin), we auto-project to nearest surface and warn.
# No silent failures. GRUG SHOUT WHEN ROCK MOVES.
# ----------------------------------------------------------
function surface_normal(am::AntikytheraMap, gear_name::Symbol, point::Vector{Float64})::Vector{Float64}
g = gradient(am, gear_name, point)
n = norm(g)
if n < 1e-12
# GRUG: Zero gradient = degenerate point (symmetry axis, tube centre, etc.)
# Project to nearest surface and retry. Warn loudly - no silent failures.
projected = _project_to_surface(am, gear_name, point)
dist_moved = norm(projected .- point)
println(" WARNING: Zero gradient at $(point). Auto-projected to surface.")
println(" Projected: $(projected) (moved $(round(dist_moved, digits=4)))")
g = gradient(am, gear_name, projected)
n = norm(g)
if n < 1e-12
throw(MachineCrunch("GRADIENT IS ZERO AT THIS POINT. NO SURFACE HERE.", "surface_normal"))
end
return g ./ n
end
return g ./ n
end
# ----------------------------------------------------------
# /curvature — MEAN AND GAUSSIAN CURVATURE VIA HESSIAN
# GRUG: "How bendy is the gear surface at this spot?"
# This needs SECOND derivatives. The Hessian matrix.
# For composed SDFs, doing this symbolically is INSANE.
# You'd need the chain rule applied to the chain rule.
# But spatially? Just poke three times per pair of axes.
#
# GRUG NOTE: If point has zero gradient (degenerate/interior point),
# we auto-project to nearest surface point and warn.
# No silent failures. GRUG SHOUT WHEN ROCK MOVES.
#
# Returns: (mean_curvature, gaussian_curvature, principal_k1, principal_k2)
# ----------------------------------------------------------
function curvature(am::AntikytheraMap, gear_name::Symbol, point::Vector{Float64})
_require_flow!(am)
gear = _require_gear(am, gear_name)
_require_point(point, gear)
am.query_count += 1
f = gear.shape_logic
p = gear.teeth_params
h = am.slack
nd = gear.ndims
# GRUG: First get the gradient (first derivatives)
g = gradient(am, gear_name, point)
g_norm = norm(g)
# GRUG: Zero gradient = degenerate point (symmetry axis, tube centre, etc.)
# Common causes: centre of sphere, tube axis of torus at major radius,
# any point where the SDF has a local extremum or is exactly at an axis.
# Auto-project to nearest surface and retry. Warn loudly - no silent failures.
actual_point = point
if g_norm < 1e-12
projected = _project_to_surface(am, gear_name, point)
dist_moved = norm(projected .- point)
println(" WARNING: Zero gradient at $(point). Auto-projected to surface.")
println(" Projected: $(projected) (moved $(round(dist_moved, digits=4)))")
actual_point = projected
g = gradient(am, gear_name, actual_point)
g_norm = norm(g)
if g_norm < 1e-12
throw(MachineCrunch("ZERO GRADIENT EVEN AFTER SURFACE PROJECTION. GEOMETRY IS DEGENERATE.", "curvature"))
end
end
# GRUG: Now build the Hessian (second derivatives) at actual_point.
# H[i,j] = d²f / (dxi dxj)
# Each entry = poke twice, measure once.
H = zeros(nd, nd)
f0 = f(actual_point, p)
try
for i in 1:nd
for j in i:nd
if i == j
# GRUG: Diagonal = pure second derivative
fwd = f(actual_point .+ _basis(nd, i, h), p)
bwd = f(actual_point .- _basis(nd, i, h), p)
H[i, i] = (fwd - 2 * f0 + bwd) / (h * h)
else
# GRUG: Off-diagonal = mixed partial
fpp = f(actual_point .+ _basis(nd, i, h) .+ _basis(nd, j, h), p)
fpm = f(actual_point .+ _basis(nd, i, h) .- _basis(nd, j, h), p)
fmp = f(actual_point .- _basis(nd, i, h) .+ _basis(nd, j, h), p)
fmm = f(actual_point .- _basis(nd, i, h) .- _basis(nd, j, h), p)
H[i, j] = (fpp - fpm - fmp + fmm) / (4 * h * h)
H[j, i] = H[i, j] # Symmetric
end
end
end
catch err
throw(MachineCrunch("GEAR CRUNCHED DURING HESSIAN IN :$(gear_name).", sprint(showerror, err)))
end
# GRUG: Mean curvature from divergence of unit normal
# κ_mean = (1/|∇f|) * (Δf - (∇f' H ∇f)/|∇f|²)
# where Δf = trace(H) = laplacian
lapl = tr(H)
bilinear = dot(g, H * g)
mean_k = (g_norm^2 * lapl - bilinear) / (2 * g_norm^3)
# GRUG: Gaussian curvature (3D only, needs adjugate of bordered Hessian)
gauss_k = 0.0
if nd == 3
# Bordered Hessian method for implicit surfaces
# Build the 4x4 bordered matrix, take cofactor
# But Grug do it the direct way with the adjugate formula
gx, gy, gz = g[1], g[2], g[3]
# Adjugate of Hessian projected onto tangent plane
# Gaussian curvature = det(shape_operator)
# For implicit surface: use the formula involving bordered Hessian
B = zeros(4, 4)
B[1:3, 1:3] .= H
B[1:3, 4] .= g
B[4, 1:3] .= vec(g')
B[4, 4] = 0.0
gauss_k = -det(B) / (g_norm^4)
end
# GRUG: Principal curvatures from mean and gaussian
# κ_mean = (κ1 + κ2) / 2
# κ_gauss = κ1 * κ2
# So κ1, κ2 are roots of: κ² - 2*κ_mean*κ + κ_gauss = 0
discriminant = mean_k^2 - gauss_k
if discriminant < 0
discriminant = 0.0 # GRUG: Numerical noise. Clamp it.
end
k1 = mean_k + sqrt(discriminant)
k2 = mean_k - sqrt(discriminant)
return (mean=mean_k, gaussian=gauss_k, k1=k1, k2=k2)
end
# ----------------------------------------------------------
# /laplacian — TRACE OF HESSIAN (SECOND-ORDER OPERATOR)
# GRUG: "How much does the field want to spread out here?"
# Sum of all pure second derivatives.
# Symbolically brutal for composed SDFs.
# Spatially? Just three center-difference pokes.
# ----------------------------------------------------------
function laplacian(am::AntikytheraMap, gear_name::Symbol, point::Vector{Float64})::Float64
_require_flow!(am)
gear = _require_gear(am, gear_name)
_require_point(point, gear)
am.query_count += 1
f = gear.shape_logic
p = gear.teeth_params
h = am.slack
nd = gear.ndims
f0 = f(point, p)
result = 0.0
try
for dim in 1:nd
fwd = f(point .+ _basis(nd, dim, h), p)
bwd = f(point .- _basis(nd, dim, h), p)
result += (fwd - 2 * f0 + bwd) / (h * h)
end
catch err
throw(MachineCrunch("LAPLACIAN CRUNCH IN :$(gear_name).", sprint(showerror, err)))
end
return result
end
# ----------------------------------------------------------
# /divergence — DIVERGENCE OF GRADIENT FIELD
# GRUG: "Is the gradient field squeezing or expanding here?"
# div(∇f) = Δf = laplacian. Same thing for scalar fields.
# But Grug expose it separately because the CONCEPT is
# different even though the NUMBER is the same.
# ----------------------------------------------------------
function divergence(am::AntikytheraMap, gear_name::Symbol, point::Vector{Float64})::Float64
return laplacian(am, gear_name, point)
end
# ==========================================================================
# CSG BOOLEAN OPERATIONS: UNION / INTERSECT / SUBTRACT
# ==========================================================================
# GRUG: This is where SDF DESTROYS traditional methods.
# Boolean ops on implicit surfaces are just min() and max().
# But try to differentiate THROUGH a min/max junction
# symbolically. The derivative is DISCONTINUOUS at the seam.
# Traditional AD/symbolic diff DIES here.
# Spatial probe? Doesn't care. Poke both sides. Done.
# ==========================================================================
# ----------------------------------------------------------
# /boolean union — Combine two gears. Keep the outside of both.
# GRUG: Smash two rocks together. Keep the big shape.
# ----------------------------------------------------------
function boolean_union!(am::AntikytheraMap, result_name::Symbol,
gear_a::Symbol, gear_b::Symbol)
a = _require_gear(am, gear_a)
b = _require_gear(am, gear_b)
combined_logic = (p, params) -> begin
da = a.shape_logic(p, a.teeth_params)
db = b.shape_logic(p, b.teeth_params)
return min(da, db)
end
am.gears[result_name] = Cog(result_name, combined_logic, [0.0]; ndims=a.ndims)
println("⚙️ GRUG: Boolean UNION :$(gear_a) ∪ :$(gear_b) → :$(result_name)")
end
# ----------------------------------------------------------
# /boolean intersect — Keep only where both gears overlap.
# GRUG: Two rocks overlap. Keep the overlap part only.
# ----------------------------------------------------------
function boolean_intersect!(am::AntikytheraMap, result_name::Symbol,
gear_a::Symbol, gear_b::Symbol)
a = _require_gear(am, gear_a)
b = _require_gear(am, gear_b)
combined_logic = (p, params) -> begin
da = a.shape_logic(p, a.teeth_params)
db = b.shape_logic(p, b.teeth_params)
return max(da, db)
end
am.gears[result_name] = Cog(result_name, combined_logic, [0.0]; ndims=a.ndims)
println("⚙️ GRUG: Boolean INTERSECT :$(gear_a) ∩ :$(gear_b) → :$(result_name)")
end
# ----------------------------------------------------------
# /boolean subtract — Cut one gear out of another.
# GRUG: Use gear B as cookie cutter on gear A.
# ----------------------------------------------------------
function boolean_subtract!(am::AntikytheraMap, result_name::Symbol,
gear_a::Symbol, gear_b::Symbol)
a = _require_gear(am, gear_a)
b = _require_gear(am, gear_b)
combined_logic = (p, params) -> begin
da = a.shape_logic(p, a.teeth_params)
db = b.shape_logic(p, b.teeth_params)
return max(da, -db)
end
am.gears[result_name] = Cog(result_name, combined_logic, [0.0]; ndims=a.ndims)
println("⚙️ GRUG: Boolean SUBTRACT :$(gear_a) \\ :$(gear_b) → :$(result_name)")
end
# ==========================================================================
# SMOOTH BLEND: DIFFERENTIABLE BOOLEAN UNION
# ==========================================================================
# GRUG: Normal boolean union has a SHARP crease where gears meet.
# The derivative is discontinuous there. Traditional methods
# CANNOT handle this. Smooth blend uses a polynomial fillet
# to round the junction. Now the derivative exists everywhere.
# But the blend region has NO closed-form symbolic gradient.
# You MUST probe it spatially. This is our territory.
# ==========================================================================
# ----------------------------------------------------------
# /blend — Smooth union of two gears with fillet radius k.
# params: gear_a, gear_b, blend_radius k
# Bigger k = smoother blend. k=0 = hard boolean.
# ----------------------------------------------------------
function blend!(am::AntikytheraMap, result_name::Symbol,
gear_a::Symbol, gear_b::Symbol, k::Float64)
a = _require_gear(am, gear_a)
b = _require_gear(am, gear_b)
if k < 0
throw(MachineCrunch("BLEND RADIUS MUST BE >= 0.", "blend!"))
end
blended_logic = (p, params) -> begin
da = a.shape_logic(p, a.teeth_params)
db = b.shape_logic(p, b.teeth_params)
if k < 1e-12
return min(da, db) # Degenerate: hard boolean
end
# GRUG: Polynomial smooth-min. The magic fillet.
h_val = max(k - abs(da - db), 0.0) / k
return min(da, db) - h_val * h_val * h_val * k * (1.0 / 6.0)
end
am.gears[result_name] = Cog(result_name, blended_logic, [k]; ndims=a.ndims)
println("⚙️ GRUG: Smooth BLEND :$(gear_a) + :$(gear_b) → :$(result_name) (k=$(k))")
end
# ==========================================================================
# MORPH: PARAMETER INTERPOLATION BETWEEN GEAR STATES
# ==========================================================================
# GRUG: Take a gear and smoothly change its teeth.
# At t=0 you have state A. At t=1 you have state B.
# In between? The gear is in a shape that has NO NAME
# in traditional geometry. But the SDF still works.
# You can still probe it. Still differentiate it.
# Traditional parametric methods need explicit formulas
# for every intermediate state. We just interpolate teeth.
# ==========================================================================
function morph!(am::AntikytheraMap, gear_name::Symbol,
target_params::Vector{Float64}, t::Float64)
gear = _require_gear(am, gear_name)
if t < 0.0 || t > 1.0
throw(MachineCrunch("MORPH t MUST BE IN [0, 1]. GOT $(t).", "morph!"))
end
if length(target_params) != length(gear.teeth_params)
throw(MachineCrunch(
"TARGET HAS $(length(target_params)) PARAMS BUT GEAR HAS $(length(gear.teeth_params)).",
"morph!"
))
end
# GRUG: Linear interpolation of teeth. Simple but powerful.
gear.teeth_params .= (1.0 - t) .* gear.teeth_params .+ t .* target_params
println("⚙️ GRUG: Morphed :$(gear_name) teeth to $(gear.teeth_params) (t=$(t))")
end
# ==========================================================================
# FLOW: STREAMLINE TRACING THROUGH GRADIENT FIELD
# ==========================================================================
# GRUG: "If I drop a leaf in the river, where does it go?"
# Follow the gradient downhill from a starting point.
# For complex SDF compositions, the flow paths have
# NO analytic solution. The streamlines twist through
# topological features that can't be expressed in closed form.
# But stepping along the gradient? That always works.
# ==========================================================================
function flow(am::AntikytheraMap, gear_name::Symbol, start::Vector{Float64};
steps::Int=100, step_size::Float64=0.1, direction::Symbol=:descent)::Vector{Vector{Float64}}
_require_flow!(am)
gear = _require_gear(am, gear_name)
_require_point(start, gear)
am.query_count += 1
sign_mult = direction == :descent ? -1.0 : 1.0
path = Vector{Vector{Float64}}()
push!(path, copy(start))
current = copy(start)
# GRUG: If start has zero gradient (e.g. exact centre of sphere),
# perturb slightly so we can actually walk somewhere.
g_check = gradient(am, gear_name, current)
if norm(g_check) < 1e-10
# Nudge along each axis until we find a direction
nd = gear.ndims
for d in 1:nd
perturbed = copy(current)
perturbed[d] += am.slack * 100
gp = gradient(am, gear_name, perturbed)
if norm(gp) > 1e-10
current = perturbed
push!(path, copy(current))
break
end
end
end
prev_sdf = gear.shape_logic(current, gear.teeth_params)
for i in 1:steps
g = gradient(am, gear_name, current)
g_norm = norm(g)
if g_norm < 1e-10
break
end
next = current .+ sign_mult .* step_size .* (g ./ g_norm)
next_sdf = gear.shape_logic(next, gear.teeth_params)
if prev_sdf * next_sdf < 0.0
lo, hi = copy(current), copy(next)
lo_sdf = prev_sdf
for _ in 1:10
mid = (lo .+ hi) ./ 2
mid_sdf = gear.shape_logic(mid, gear.teeth_params)
if abs(mid_sdf) < am.slack * 0.1
next = mid
next_sdf = mid_sdf
break
end
if lo_sdf * mid_sdf < 0.0
hi = mid
else
lo = mid
lo_sdf = mid_sdf
end
end
current = next
push!(path, copy(current))
break
end
current = next
prev_sdf = next_sdf
push!(path, copy(current))
if abs(next_sdf) < max(am.slack * 10, step_size * 0.01)
break
end
end
return path
end
# ==========================================================================
# LEVELSET: FIND ZERO-CROSSING ALONG A RAY
# ==========================================================================
# GRUG: "Where does a ray hit the gear surface?"
# March along the ray using the SDF itself as step size.
# This is sphere tracing / ray marching.
# Traditional ray-surface intersection for implicit surfaces
# requires solving f(o + td) = 0, which for complex SDFs
# has NO closed-form solution. Newton's method can diverge.
# But SDF gives us a safe step distance at every point.
# So we just walk forward, guaranteed not to overshoot.
# ==========================================================================
function levelset(am::AntikytheraMap, gear_name::Symbol,
origin::Vector{Float64}, direction::Vector{Float64};
max_steps::Int=256, max_dist::Float64=100.0)
_require_flow!(am)
gear = _require_gear(am, gear_name)
_require_point(origin, gear)
_require_point(direction, gear)
am.query_count += 1
dir_norm = norm(direction)
if dir_norm < 1e-12
throw(MachineCrunch("RAY DIRECTION IS ZERO.", "levelset"))
end
dir = direction ./ dir_norm
t = 0.0
for i in 1:max_steps
p = origin .+ t .* dir
d = gear.shape_logic(p, gear.teeth_params)
# GRUG: Close enough to surface? Found it!
if abs(d) < am.slack
return (hit=true, point=p, distance=t, steps=i)
end
# GRUG: SDF tells us we can safely step |d| forward
t += abs(d)
if t > max_dist
break
end
end
return (hit=false, point=origin .+ t .* dir, distance=t, steps=max_steps)
end
# ==========================================================================
# GEODESIC: APPROXIMATE GEODESIC DISTANCE ON SURFACE
# ==========================================================================
# GRUG: "What's the shortest path ALONG the surface between two points?"
# This requires solving the Eikonal equation |∇T| = 1.
# For arbitrary implicit surfaces, this is COMPLETELY
# intractable analytically. Even numerical methods (fast
# marching) need a grid. We do it with gradient-constrained
# stepping: project each step onto the tangent plane.
# It's approximate. But it converges. And it works on
# surfaces that don't even have names.
# ==========================================================================
function geodesic(am::AntikytheraMap, gear_name::Symbol,
start::Vector{Float64}, target::Vector{Float64};
max_steps::Int=500, step_size::Float64=0.05)
_require_flow!(am)
gear = _require_gear(am, gear_name)
_require_point(start, gear)
_require_point(target, gear)
am.query_count += 1
# GRUG: First project both points onto the surface.
# If start/end are not on the surface (SDF != 0), project them.
# Warn loudly if significant movement needed — no silent failures.
current = _project_to_surface(am, gear_name, start)
start_moved = norm(current .- start)
if start_moved > am.slack * 10
println(" WARNING: Start point $(start) not on surface (SDF=$(round(gear.shape_logic(start, gear.teeth_params), digits=4))).")
println(" Projected to $(round.(current, digits=4)) (moved $(round(start_moved, digits=4)))")
end
target_proj = _project_to_surface(am, gear_name, target)
end_moved = norm(target_proj .- target)
if end_moved > am.slack * 10
println(" WARNING: End point $(target) not on surface (SDF=$(round(gear.shape_logic(target, gear.teeth_params), digits=4))).")
println(" Projected to $(round.(target_proj, digits=4)) (moved $(round(end_moved, digits=4)))")
end
total_dist = 0.0
path = [copy(current)]
stagnation_count = 0 # GRUG: Track if we're stuck in one place
prev_remaining = Inf
for i in 1:max_steps
# GRUG: Direction toward target
to_target = target_proj .- current
remaining = norm(to_target)
# GRUG: Close enough? Done.
if remaining < step_size
total_dist += remaining
push!(path, copy(target_proj))
break
end
# GRUG: Check for stagnation — if we're not making progress, something's wrong
if abs(remaining - prev_remaining) < am.slack * 0.1
stagnation_count += 1
else
stagnation_count = 0
end
prev_remaining = remaining
# GRUG: Too much stagnation = we're stuck. Throw loudly, no silent failure.
if stagnation_count > 20
throw(MachineCrunch(
"GEODESIC PATH STAGNATED. CANNOT REACH TARGET.",
"Gear :$(gear_name), stuck at $(round.(current, digits=4)), target $(round.(target_proj, digits=4)). Channel topic: '$(gear.channel_topic)'"
))
end
# GRUG: Project direction onto tangent plane (remove normal component)
n = surface_normal(am, gear_name, current)
tangent_dir = to_target .- dot(to_target, n) .* n
td_norm = norm(tangent_dir)
if td_norm < 1e-12
# GRUG: Target is directly above/below on the normal direction.
# This is the CHANNEL DIRECTORY problem — we need to go AROUND the surface
# to reach a point that's aligned with the normal. The tangent plane
# offers no direction because the target is perpendicular to it.
#
# GRUG: Each gear has a channel_topic — the underlying purpose/target it knows.
# Use this knowledge to pick an intelligent perturbation direction.
tangent_dir = _compute_channel_direction(am, gear_name, current, n, to_target)
td_norm = norm(tangent_dir)
# GRUG: If channel direction computation failed, no silent failure!
if td_norm < 1e-12
throw(MachineCrunch(
"GEODESIC: CANNOT FIND PATH AROUND NORMAL-ALIGNED TARGET.",
"Gear :$(gear_name) at $(round.(current, digits=4)). Target is directly along surface normal. Channel topic: '$(gear.channel_topic)'. Try a different start point or increase step_size."
))
end
println(" INFO: Target aligned with surface normal. Using channel direction based on topic '$(gear.channel_topic)'.")
end
# GRUG: Step along tangent, then project back to surface
current = current .+ step_size .* (tangent_dir ./ td_norm)