Add typed tactical map geometry helpers

ober

be53343ec8d724305f7ef91dff0f902b70c461e8

diff --git a/templates/original-tactics-parts/TacticalMapView.kt.ss b/templates/original-tactics-parts/TacticalMapView.kt.ss
new file mode 100644
index 0000000..1f002c5
--- /dev/null
+++ b/templates/original-tactics-parts/TacticalMapView.kt.ss
@@ -0,0 +1,246 @@
+(import (jerboa prelude))
+
+(def fragment
+  '((typed-kotlin-file "com/jerboa/originaltactics/TacticalMapView.kt"
+      (kotlin-imports
+        (android animation ValueAnimator)
+        (android content Context)
+        (android content res Resources)
+        (android graphics Canvas)
+        (android graphics Color)
+        (android graphics DashPathEffect)
+        (android graphics Paint)
+        (android graphics Paint Align)
+        (android graphics Paint Cap)
+        (android graphics Paint Join)
+        (android graphics Paint Style)
+        (android graphics Path)
+        (android graphics PathEffect)
+        (android graphics RectF)
+        (android os SystemClock)
+        (android util DisplayMetrics)
+        (android view MotionEvent)
+        (android view ScaleGestureDetector)
+        (android view ScaleGestureDetector SimpleOnScaleGestureListener)
+        (android view View)
+        (android view ViewParent)
+        (android view animation DecelerateInterpolator)
+        (org json JSONArray)
+        (org json JSONObject))
+      (typed-library (com jerboa originaltactics)
+        (export make-TacticalPoint TacticalPoint?
+                make-TacticalShipPose TacticalShipPose?
+                make-TacticalFireSolution TacticalFireSolution?
+                make-TacticalArcFan TacticalArcFan?
+                make-TacticalArcSector TacticalArcSector?
+                make-TacticalProjectedPose TacticalProjectedPose?
+                make-TacticalProjectedMove TacticalProjectedMove?
+                make-TacticalBlockedManeuverBadge TacticalBlockedManeuverBadge?
+                make-TacticalMapTransform TacticalMapTransform?
+                tmvRawHexPoint tmvRawHexPointInt tmvTransformPoint
+                tmvHexRange tmvPoseFrom tmvPoseChanged
+                tmvLerp tmvLerpFacing tmvFacingAngleRadians
+                tmvAnimatedPose)
+
+        (type Any)
+        (type Int32)
+        (type Int)
+        (type Float32)
+        (type Float)
+        (type Context)
+        (type Resources)
+        (type DisplayMetrics)
+        (type Canvas)
+        (type Color)
+        (type Paint)
+        (type Style)
+        (type Align)
+        (type Cap)
+        (type Join)
+        (type PathEffect)
+        (type DashPathEffect)
+        (type Path)
+        (type RectF)
+        (type View)
+        (type ViewParent)
+        (type MotionEvent)
+        (type ScaleGestureDetector)
+        (type SimpleOnScaleGestureListener)
+        (type ValueAnimator)
+        (type DecelerateInterpolator)
+        (type JSONArray)
+        (type JSONObject)
+        (type Number)
+
+        (record TacticalPoint
+          ((x : Float32)
+           (y : Float32)))
+        (record TacticalShipPose
+          ((q : Float32)
+           (r : Float32)
+           (facing : Float32)))
+        (record TacticalFireSolution
+          ((installed : Int32)
+           (armed : Int32)
+           (ready : Int32)
+           (inArc : Int32)))
+        (record TacticalArcFan
+          ((code : String)
+           (strength : Int32)))
+        (record TacticalArcSector
+          ((centerOffset : Float32)
+           (sweep : Float32)))
+        (record TacticalProjectedPose
+          ((q : Int32)
+           (r : Int32)
+           (facing : Int32)
+           (speed : Int32)
+           (turnMode : String)
+           (distanceSinceTurn : Int32)))
+        (record TacticalProjectedMove
+          ((impulse : Int32)
+           (maneuver : (Nullable String))
+           (pose : TacticalProjectedPose)))
+        (record TacticalBlockedManeuverBadge
+          ((actor : String)
+           (text : String)))
+        (record TacticalMapTransform
+          ((scale : Float32)
+           (origin : TacticalPoint)))
+
+        (extern (tmvAsNumber (value : Any)) : (Nullable Number)
+          (kotlin-safe-cast Number))
+        (extern (tmvAsString (value : Any)) : (Nullable String)
+          (kotlin-safe-cast String))
+        (extern (tmvAsJsonArray (value : Any)) : (Nullable JSONArray)
+          (kotlin-safe-cast JSONArray))
+        (extern (tmvAsJsonObject (value : Any)) : (Nullable JSONObject)
+          (kotlin-safe-cast JSONObject))
+        (extern (tmvNumberToInt32 (value : Number)) : Int32
+          (kotlin-member-call toInt))
+        (extern (tmvAnyToString (value : Any)) : String
+          (kotlin-member-call toString))
+        (extern (tmvJsonKeys (json : JSONObject)) : (Iterator String)
+          (kotlin-member-call keys))
+        (extern (tmvIteratorHasNext (items : (Iterator String))) : Bool
+          (kotlin-member-call hasNext))
+        (extern (tmvIteratorNext (items : (Iterator String))) : String
+          (kotlin-member-call next))
+        (extern (tmvSqrt (value : Float)) : Float
+          (kotlin-call kotlin math sqrt))
+        (extern (tmvSin (value : Float)) : Float
+          (kotlin-call kotlin math sin))
+        (extern (tmvCos (value : Float)) : Float
+          (kotlin-call kotlin math cos))
+        (extern (tmvPi) : Float
+          (kotlin-value kotlin math PI))
+
+        (def (tmvMaxInt (left : Int32) (right : Int32)) : Int32
+          (if (> left right) left right))
+        (def (tmvMinInt (left : Int32) (right : Int32)) : Int32
+          (if (< left right) left right))
+        (def (tmvMaxFloat (left : Float32) (right : Float32)) : Float32
+          (if (> left right) left right))
+        (def (tmvMinFloat (left : Float32) (right : Float32)) : Float32
+          (if (< left right) left right))
+        (def (tmvClampInt (value : Int32) (low : Int32) (high : Int32)) : Int32
+          (tmvMinInt high (tmvMaxInt low value)))
+        (def (tmvClampFloat (value : Float32)
+                            (low : Float32)
+                            (high : Float32)) : Float32
+          (tmvMinFloat high (tmvMaxFloat low value)))
+        (def (tmvAbsInt (value : Int32)) : Int32
+          (if (< value (int32 0)) (- (int32 0) value) value))
+        (def (tmvNumberValue (value : (Nullable Any))) : Int32
+          (if (nullable-null? value)
+              (int32 0)
+              (let ((number (tmvAsNumber (nullable-get value))))
+                (if (nullable-null? number)
+                    (let ((text (tmvAsString (nullable-get value))))
+                      (if (nullable-null? text)
+                          (int32 0)
+                          (let ((parsed (string->int32-or-null (nullable-get text))))
+                            (if (nullable-null? parsed)
+                                (int32 0)
+                                (nullable-get parsed)))))
+                    (tmvNumberToInt32 (nullable-get number))))))
+        (def (tmvAnyText (value : (Nullable Any))
+                         (fallback : String)) : String
+          (if (nullable-null? value)
+              fallback
+              (tmvAnyToString (nullable-get value))))
+
+        (def (tmvRawHexPointInt (q : Int32) (r : Int32)) : TacticalPoint
+          (tmvRawHexPoint (float32 q) (float32 r)))
+        (def (tmvRawHexPoint (q : Float32) (r : Float32)) : TacticalPoint
+          (make-TacticalPoint
+            (float32
+              (* (tmvSqrt 3.0)
+                 (+ (exact->inexact q) (/ (exact->inexact r) 2.0))))
+            (float32 (* 1.5 (exact->inexact r)))))
+        (def (tmvTransformPoint (transform : TacticalMapTransform)
+                                (q : Float32)
+                                (r : Float32)) : TacticalPoint
+          (let ((raw (tmvRawHexPoint q r)))
+            (make-TacticalPoint
+              (+ (TacticalPoint-x (TacticalMapTransform-origin transform))
+                 (* (TacticalPoint-x raw) (TacticalMapTransform-scale transform)))
+              (+ (TacticalPoint-y (TacticalMapTransform-origin transform))
+                 (* (TacticalPoint-y raw) (TacticalMapTransform-scale transform))))))
+        (def (tmvHexRange (q1 : Int32) (r1 : Int32)
+                          (q2 : Int32) (r2 : Int32)) : Int32
+          (let ((dq (- q2 q1))
+                (dr (- r2 r1)))
+            (let ((ds (- (int32 0) (+ dq dr))))
+              (/ (+ (+ (tmvAbsInt dq) (tmvAbsInt dr)) (tmvAbsInt ds))
+                 (int32 2)))))
+        (def (tmvPoseFrom (position : JSONObject)) : TacticalShipPose
+          (make-TacticalShipPose
+            (json-object-opt-float32-default position "q" (float32 0.0))
+            (json-object-opt-float32-default position "r" (float32 0.0))
+            (json-object-opt-float32-default position "facing" (float32 0.0))))
+        (def (tmvPoseChanged (left : TacticalShipPose)
+                             (right : TacticalShipPose)) : Bool
+          (or (not (= (TacticalShipPose-q left) (TacticalShipPose-q right)))
+              (or (not (= (TacticalShipPose-r left) (TacticalShipPose-r right)))
+                  (not (= (TacticalShipPose-facing left)
+                          (TacticalShipPose-facing right))))))
+        (def (tmvLerp (start : Float32) (end : Float32)
+                      (progress : Float32)) : Float32
+          (+ start (* (- end start) progress)))
+        (def (tmvFacingDelta (raw : Float32)) : Float32
+          (if (> raw (float32 3.0))
+              (- raw (float32 6.0))
+              (if (< raw (float32 -3.0))
+                  (+ raw (float32 6.0))
+                  raw)))
+        (def (tmvNormalizeFacingFloat (facing : Float32)) : Float32
+          (if (< facing (float32 0.0))
+              (tmvNormalizeFacingFloat (+ facing (float32 6.0)))
+              (if (>= facing (float32 6.0))
+                  (tmvNormalizeFacingFloat (- facing (float32 6.0)))
+                  facing)))
+        (def (tmvLerpFacing (start : Float32) (end : Float32)
+                            (progress : Float32)) : Float32
+          (tmvNormalizeFacingFloat
+            (+ start (* (tmvFacingDelta (- end start)) progress))))
+        (def (tmvFacingAngleRadians (facing : Float32)) : Float
+          (* (- 90.0
+                (* (exact->inexact (tmvNormalizeFacingFloat facing)) 60.0))
+             (/ (tmvPi) 180.0)))
+        (def (tmvAnimatedPose (current : TacticalShipPose)
+                              (start : (Nullable TacticalShipPose))
+                              (end : (Nullable TacticalShipPose))
+                              (movementProgress : Float32)) : TacticalShipPose
+          (if (or (nullable-null? start) (nullable-null? end))
+              current
+              (let ((progress
+                      (tmvClampFloat movementProgress (float32 0.0) (float32 1.0))))
+                (make-TacticalShipPose
+                  (tmvLerp (TacticalShipPose-q (nullable-get start))
+                           (TacticalShipPose-q (nullable-get end)) progress)
+                  (tmvLerp (TacticalShipPose-r (nullable-get start))
+                           (TacticalShipPose-r (nullable-get end)) progress)
+                  (tmvLerpFacing
+                    (TacticalShipPose-facing (nullable-get start))
+                    (TacticalShipPose-facing (nullable-get end)) progress)))))))))