Complete typed tactics status views

ober

5b130d74a1e361bdb8fee34ee5c3c5e3c9a0e88a

diff --git a/templates/original-tactics-parts/BridgeHudView.kt.ss b/templates/original-tactics-parts/BridgeHudView.kt.ss
index 958d5c8..499f149 100644
--- a/templates/original-tactics-parts/BridgeHudView.kt.ss
+++ b/templates/original-tactics-parts/BridgeHudView.kt.ss
@@ -112,6 +112,14 @@
         (extern (bhvStringReplace (text : String) (old : String)
                                   (replacement : String)) : String
           (kotlin-member-call replace))
+        (extern (bhvStringUppercase (text : String)) : String
+          (kotlin-member-call uppercase))
+        (extern (bhvStringStartsWith (text : String) (prefix : String)) : Bool
+          (kotlin-member-call startsWith))
+        (extern (bhvStringContains (text : String) (part : String)) : Bool
+          (kotlin-member-call contains))
+        (extern (bhvJsonHas (json : JSONObject) (key : String)) : Bool
+          (kotlin-member-call has))
         (extern (bhvCurrentBattleRange (battle : (Nullable JSONObject))) : String
           (kotlin-call currentBattleRange))
         (extern (bhvShipDisplayLabel (ship : (Nullable JSONObject))
@@ -498,7 +506,22 @@
                             (- (bhvRectWidth rect) (float32 16.0)) "ARMED"
                             (counterTotal (bhvNullableAnyField ship "armed-weapons"))
                             (bhvMaxInt (int32 1)
-                              (counterTotal (bhvNullableAnyField ship "weapons")))))))))))
+                              (bhvMaxInt
+                                (counterTotal (bhvNullableAnyField ship "weapons"))
+                                (weaponInstalledTotal
+                                  (bhvNullableArrayField statusShip "weapon-status")))))
+                          (let ((weaponText
+                                  (weaponFamilySummary
+                                    (bhvNullableArrayField statusShip "weapon-status"))))
+                            (if (string-blank? weaponText)
+                              (begin)
+                              (begin
+                                (bhvPaintTextAlignSet smallPaint (bhvPaintAlignLeft))
+                                (bhvCanvasDrawText canvas
+                                  (fitText (string-append "WPN " weaponText) smallPaint
+                                    (- (bhvRectWidth rect) (float32 16.0)))
+                                  (+ (bhvRectLeft rect) (float32 8.0))
+                                  (+ barY (float32 99.0)) smallPaint)))))))))))
 
           (def (drawCenterBlock (canvas : Canvas) (rect : RectF)
                                 (current : JSONObject) (battle : JSONObject)
@@ -508,7 +531,12 @@
               (bhvCanvasDrawRoundRect canvas rect (float32 8.0) (float32 8.0) panelAltPaint)
               (bhvCanvasDrawRoundRect canvas rect (float32 8.0) (float32 8.0) borderPaint)
               (let ((compact (< (bhvRectHeight rect) (float32 72.0)))
-                    (orders (json-object-opt-json-object current "orders")))
+                    (orders (json-object-opt-json-object current "orders"))
+                    (phaseText
+                      (phaseSlotSummary
+                        (bhvNullableArrayField
+                          (bhvNullableObjectField dashboard "impulse-activity")
+                          "stage-slots"))))
                 (let ((fireImpulse
                         (bhvAnyText (bhvNullableAnyField orders "fire-impulse") "-"))
                       (seekers
@@ -541,9 +569,17 @@
                               labelPaint (- (bhvRectWidth rect) (float32 14.0)))
                             (bhvRectCenterX rect)
                             (+ (bhvRectTop rect) (float32 38.0)) labelPaint))
-                        (let ((y (+ (bhvRectTop rect) (float32 49.0))))
+                        (var ((y (+ (bhvRectTop rect) (float32 49.0))))
                           (begin
                             (bhvPaintTextAlignSet smallPaint (bhvPaintAlignCenter))
+                            (if (string-blank? phaseText)
+                              (begin)
+                              (begin
+                                (bhvCanvasDrawText canvas
+                                  (fitText phaseText smallPaint
+                                    (- (bhvRectWidth rect) (float32 14.0)))
+                                  (bhvRectCenterX rect) y smallPaint)
+                                (set! y (+ y (float32 18.0)))))
                             (bhvCanvasDrawText canvas
                               (fitText
                                 (string-append
@@ -720,6 +756,106 @@
                                           ignored))))))
                               (begin))))))))))
 
+          (def (phaseSlotSummary (slots : (Nullable JSONArray))) : String
+            (modifiers private)
+            (if (nullable-null? slots) ""
+              (phaseSlotSummaryAt (nullable-get slots) (int32 0) (int32 0) "")))
+          (def (phaseSlotSummaryAt (slots : JSONArray) (index : Int32)
+                                   (count : Int32) (out : String)) : String
+            (modifiers private)
+            (if (or (>= index (json-array-length slots)) (>= count (int32 3))) out
+              (let ((slot (json-array-opt-json-object slots index)))
+                (if (or (nullable-null? slot)
+                        (not (json-object-opt-bool-default
+                          (nullable-get slot) "implemented?" #f)))
+                  (phaseSlotSummaryAt slots (+ index (int32 1)) count out)
+                  (let ((raw (json-object-opt-string-default
+                              (nullable-get slot) "action"
+                              (json-object-opt-string-default
+                                (nullable-get slot) "engine-hook" ""))))
+                    (let ((action
+                            (bhvStringReplace
+                              (bhvStringReplace
+                                (bhvStringReplace raw "Launch " "Ln ")
+                                "Fire Weapons" "Fire")
+                              "Announce " "")))
+                      (if (string-blank? action)
+                        (phaseSlotSummaryAt slots (+ index (int32 1)) count out)
+                        (phaseSlotSummaryAt slots (+ index (int32 1)) (+ count (int32 1))
+                          (string-append out
+                            (string-append (if (> count (int32 0)) " | " "") action))))))))))
+
+          (def (weaponInstalledTotal (weapons : (Nullable JSONArray))) : Int32
+            (modifiers private)
+            (if (nullable-null? weapons) (int32 0)
+              (for/fold ((total (int32 0)))
+                        ((index (in-range (int32 0) (json-array-length (nullable-get weapons)))))
+                (+ total
+                  (bhvMaxInt (int32 0)
+                    (bhvNullableIntField
+                      (json-array-opt-json-object (nullable-get weapons) index)
+                      "installed" (int32 0)))))))
+
+          (def (weaponFamilySummary (weapons : (Nullable JSONArray))) : String
+            (modifiers private)
+            (if (nullable-null? weapons) ""
+              (weaponFamilySummaryAt (nullable-get weapons) (int32 0) (int32 0) "")))
+          (def (weaponFamilySummaryAt (weapons : JSONArray) (index : Int32)
+                                      (count : Int32) (out : String)) : String
+            (modifiers private)
+            (if (or (>= index (json-array-length weapons)) (>= count (int32 3))) out
+              (let ((weapon (json-array-opt-json-object weapons index)))
+                (if (nullable-null? weapon)
+                  (weaponFamilySummaryAt weapons (+ index (int32 1)) count out)
+                  (let ((installed (bhvNullableIntField weapon "installed" (int32 0)))
+                        (armed (bhvNullableIntField weapon "armed" (int32 0)))
+                        (fireable (bhvNullableIntField weapon "fireable" (int32 0)))
+                        (inArc (bhvNullableIntField weapon "fireable-in-arc" (int32 0)))
+                        (cooldown (bhvNullableIntField weapon "cooldown" (int32 0))))
+                    (if (<= (+ installed (+ armed (+ fireable cooldown))) (int32 0))
+                      (weaponFamilySummaryAt weapons (+ index (int32 1)) count out)
+                      (weaponFamilySummaryAt weapons (+ index (int32 1)) (+ count (int32 1))
+                        (string-append out
+                          (string-append (if (> count (int32 0)) "  " "")
+                            (string-append
+                              (shortWeaponKind
+                                (json-object-opt-string-default
+                                  (nullable-get weapon) "kind" ""))
+                              (string-append " "
+                                (string-append (int32->string fireable)
+                                  (string-append "/"
+                                    (string-append (int32->string armed)
+                                      (string-append "/"
+                                        (string-append (int32->string installed)
+                                          (string-append
+                                            (if (and (> fireable (int32 0)) (< inArc fireable))
+                                              (string-append " a" (int32->string inArc)) "")
+                                            (if (> cooldown (int32 0))
+                                              (string-append " cd" (int32->string cooldown)) ""))))))))))))))))))
+
+          (def (shortWeaponKind (kind : String)) : String
+            (modifiers private)
+            (if (equal? kind "photon") "PHT"
+              (if (equal? kind "disruptor") "DIS"
+                (if (equal? kind "hellbore") "HB"
+                  (if (equal? kind "fusion") "FUS"
+                    (if (equal? kind "tr-beam") "TR"
+                      (if (equal? kind "phaser") "PH"
+                        (if (bhvStringStartsWith kind "phaser-")
+                          (bhvStringUppercase (bhvStringReplace kind "phaser-" "PH-"))
+                          (if (equal? kind "drone") "DRN"
+                            (if (bhvStringStartsWith kind "plasma-")
+                              (bhvStringUppercase (bhvStringReplace kind "plasma-" "PL-"))
+                              (bhvStringUppercase
+                                (bhvStringReplace kind "drone-" "D-"))))))))))))
+
+          (def (weaponSortBucket (kind : String)) : Int32
+            (modifiers private)
+            (if (bhvStringStartsWith kind "phaser") (int32 2)
+              (if (or (bhvStringContains kind "drone")
+                      (bhvStringContains kind "plasma"))
+                (int32 1) (int32 0))))
+
           (def (shipName (ship : (Nullable JSONObject))
                          (statusShip : (Nullable JSONObject))) : String
             (modifiers private)
@@ -745,6 +881,44 @@
                             (bhvNumberValue counter)
                             (counterArrayTotal (nullable-get array) (int32 0))))
                       (objectTotal (nullable-some (nullable-get obj)))))))
+          (def (numberValue (value : (Nullable Any))) : Int32
+            (modifiers private)
+            (bhvNumberValue value))
+          (def (forEachCounterPair
+                 (counter : (Nullable Any))
+                 (action : (-> String (Nullable Any) Unit))) : Unit
+            (modifiers private)
+            (if (nullable-null? counter) (begin)
+              (let ((object (bhvAsJsonObject (nullable-get counter)))
+                    (array (bhvAsJsonArray (nullable-get counter))))
+                (if (not (nullable-null? object))
+                  (let ((pair (json-object-opt-json-array
+                                (nullable-get object) "$pair")))
+                    (if (nullable-null? pair)
+                      (forEachCounterObjectKeys (nullable-get object)
+                        (bhvJsonKeys (nullable-get object)) action)
+                      (invoke action
+                        (json-array-opt-string (nullable-get pair) (int32 0))
+                        (json-array-opt-any (nullable-get pair) (int32 1)))))
+                  (if (nullable-null? array) (begin)
+                    (forEachCounterArray (nullable-get array) (int32 0) action))))))
+          (def (forEachCounterObjectKeys
+                 (object : JSONObject) (keys : (Iterator String))
+                 (action : (-> String (Nullable Any) Unit))) : Unit
+            (modifiers private)
+            (if (not (bhvIteratorHasNext keys)) (begin)
+              (let ((key (bhvIteratorNext keys)))
+                (begin
+                  (invoke action key (json-object-opt-any object key))
+                  (forEachCounterObjectKeys object keys action)))))
+          (def (forEachCounterArray
+                 (array : JSONArray) (index : Int32)
+                 (action : (-> String (Nullable Any) Unit))) : Unit
+            (modifiers private)
+            (if (>= index (json-array-length array)) (begin)
+              (begin
+                (forEachCounterPair (json-array-opt-any array index) action)
+                (forEachCounterArray array (+ index (int32 1)) action))))
           (def (objectTotal (obj : (Nullable JSONObject))) : Int32
             (modifiers private)
             (if (nullable-null? obj)
@@ -804,10 +978,35 @@
           (def (paPanelCount (ship : (Nullable JSONObject))
                              (statusShip : (Nullable JSONObject))) : Int32
             (modifiers private)
-            (let ((shipCount (counterValue (bhvNullableAnyField ship "boxes") "52")))
-              (if (> shipCount (int32 0))
-                  shipCount
-                  (counterValue (bhvNullableAnyField statusShip "boxes") "52"))))
+            (let ((carrier (if (nullable-null? ship) statusShip ship)))
+              (if (and (not (nullable-null? carrier))
+                       (bhvJsonHas (nullable-get carrier) "pa-panels"))
+                (bhvMaxInt (int32 0)
+                  (bhvNumberValue (bhvNullableAnyField carrier "pa-panels")))
+                (bhvMaxInt (int32 0)
+                  (- (paPanelBoxCount ship statusShip)
+                     (paPanelCharge ship statusShip))))))
+          (def (paPanelBoxCount (ship : (Nullable JSONObject))
+                                (statusShip : (Nullable JSONObject))) : Int32
+            (modifiers private)
+            (let ((carrier (if (nullable-null? ship) statusShip ship)))
+              (let ((boxes (bhvNumberValue (bhvNullableAnyField carrier "pa-panel-boxes")))
+                    (maximum (bhvNumberValue (bhvNullableAnyField carrier "pa-panel-max")))
+                    (shipBoxes (counterValue (bhvNullableAnyField ship "boxes") "52")))
+                (if (> boxes (int32 0)) boxes
+                  (if (> maximum (int32 0)) maximum
+                    (if (> shipBoxes (int32 0)) shipBoxes
+                      (counterValue (bhvNullableAnyField statusShip "boxes") "52")))))))
+          (def (paPanelCharge (ship : (Nullable JSONObject))
+                              (statusShip : (Nullable JSONObject))) : Int32
+            (modifiers private)
+            (let ((carrier (if (nullable-null? ship) statusShip ship)))
+              (let ((charge (bhvMaxInt (int32 0)
+                              (bhvNumberValue
+                                (bhvNullableAnyField carrier "pa-panel-charge"))))
+                    (maximum (bhvMaxInt (int32 1)
+                               (paPanelBoxCount ship statusShip))))
+                (if (> charge maximum) maximum charge))))
           (def (paPanelMax (ship : (Nullable JSONObject))
                            (statusShip : (Nullable JSONObject))) : Int32
             (modifiers private)
@@ -815,7 +1014,7 @@
                     (counterValue
                       (bhvNullableAnyField ship "destroyed-boxes") "52")))
               (bhvMaxInt (int32 1)
-                (+ (paPanelCount ship statusShip)
+                (+ (paPanelBoxCount ship statusShip)
                   (if (> destroyed (int32 0))
                       destroyed
                       (counterValue
@@ -834,9 +1033,11 @@
                   (let ((panels (paPanelCount ship statusShip))
                         (maximum (paPanelMax ship statusShip)))
                     (if (or (> panels (int32 0)) (> maximum (int32 1)))
-                        (string-append
-                          (string-append "PA " (int32->string panels))
-                          (string-append "/" (int32->string maximum)))
+                        (string-append "PA chg "
+                          (string-append (int32->string (paPanelCharge ship statusShip))
+                            (string-append "/"
+                              (string-append (int32->string maximum)
+                                (string-append " av " (int32->string panels))))))
                         "SH 0")))))
           (def (shieldFacingIndex (value : (Nullable Any))) : Int32
             (modifiers private)
@@ -912,10 +1113,30 @@
                           (- (json-array-length raw) (int32 1)))))
                   (if (nullable-null? event)
                       "Recent event"
-                      (bhvStringReplace
-                        (json-object-opt-string-default
-                          (nullable-get event) "type" "event")
-                        "-" " ")))))
+                      (let ((payload
+                              (json-object-opt-json-object
+                                (nullable-get event) "payload")))
+                        (string-append
+                          (bhvStringReplace
+                            (json-object-opt-string-default
+                              (nullable-get event) "type" "event")
+                            "-" " ")
+                          (if (and (not (nullable-null? payload))
+                                   (bhvJsonHas (nullable-get payload) "damage"))
+                            (string-append " dmg "
+                              (bhvAnyText
+                                (bhvNullableAnyField payload "damage") ""))
+                            (if (and (not (nullable-null? payload))
+                                     (bhvJsonHas (nullable-get payload) "winner"))
+                              (string-append " winner "
+                                (bhvAnyText
+                                  (bhvNullableAnyField payload "winner") ""))
+                              (if (and (not (nullable-null? payload))
+                                       (bhvJsonHas (nullable-get payload) "range"))
+                                (string-append " range "
+                                  (bhvAnyText
+                                    (bhvNullableAnyField payload "range") ""))
+                                "")))))))))
           (def (fitText (text : String) (paint : Paint)
                         (maxWidth : Float32)) : String
             (modifiers private)
diff --git a/templates/original-tactics-parts/Sfc2IntelView.kt.ss b/templates/original-tactics-parts/Sfc2IntelView.kt.ss
index 02b2e9c..5305841 100644
--- a/templates/original-tactics-parts/Sfc2IntelView.kt.ss
+++ b/templates/original-tactics-parts/Sfc2IntelView.kt.ss
@@ -17,7 +17,7 @@
         (export Sfc2IntelView)
         (type Context) (type Canvas) (type Paint) (type Align) (type Style)
         (type RectF) (type View) (type Resources) (type DisplayMetrics)
-        (type JSONArray) (type JSONObject) (type Int32) (type Float32)
+        (type JSONArray) (type JSONObject) (type Int32) (type Float32) (type Any) (type Number)
         (extern (sivPaintFlag) : Int32 (kotlin-value Paint ANTI_ALIAS_FLAG))
         (extern (sivFill) : Style (kotlin-value Paint Style FILL))
         (extern (sivStroke) : Style (kotlin-value Paint Style STROKE))
@@ -37,6 +37,8 @@
           (kotlin-member-set isFakeBoldText))
         (extern (sivPaintAlignSet (paint : Paint) (value : Align)) : Unit
           (kotlin-member-set textAlign))
+        (extern (sivPaintMeasure (paint : Paint) (text : String)) : Float32
+          (kotlin-member-call measureText))
         (extern (sivInvalidate (view : Sfc2IntelView)) : Unit
           (kotlin-member-call invalidate))
         (extern (sivSuperDraw (canvas : Canvas)) : Unit
@@ -64,12 +66,18 @@
                              (x : Float32) (y : Float32)
                              (paint : Paint)) : Unit
           (kotlin-member-call drawText))
+        (extern (sivDrawCircle (canvas : Canvas) (x : Float32) (y : Float32)
+                               (radius : Float32) (paint : Paint)) : Unit
+          (kotlin-member-call drawCircle))
         (extern (sivRectLeft (rect : RectF)) : Float32
           (kotlin-member-get left))
         (extern (sivRectTop (rect : RectF)) : Float32
           (kotlin-member-get top))
         (extern (sivRectRight (rect : RectF)) : Float32
           (kotlin-member-get right))
+        (extern (sivRectBottom (rect : RectF)) : Float32 (kotlin-member-get bottom))
+        (extern (sivRectWidth (rect : RectF)) : Float32 (kotlin-member-call width))
+        (extern (sivRectHeight (rect : RectF)) : Float32 (kotlin-member-call height))
         (extern (sivJsonObject (object : JSONObject)
                                (key : String)) : (Nullable JSONObject)
           (kotlin-member-call optJSONObject))
@@ -79,6 +87,39 @@
         (extern (sivJsonBoolean (object : JSONObject) (key : String)
                                 (fallback : Bool)) : Bool
           (kotlin-member-call optBoolean))
+        (extern (sivJsonArray (object : JSONObject) (key : String)) : (Nullable JSONArray)
+          (kotlin-member-call optJSONArray))
+        (extern (sivJsonAny (object : JSONObject) (key : String)) : (Nullable Any)
+          (kotlin-member-call opt))
+        (extern (sivJsonString (object : JSONObject) (key : String)
+                               (fallback : String)) : String
+          (kotlin-member-call optString))
+        (extern (sivJsonHas (object : JSONObject) (key : String)) : Bool
+          (kotlin-member-call has))
+        (extern (sivArrayLength (array : JSONArray)) : Int32 (kotlin-member-call length))
+        (extern (sivArrayObject (array : JSONArray) (index : Int32)) : (Nullable JSONObject)
+          (kotlin-member-call optJSONObject))
+        (extern (sivArrayAny (array : JSONArray) (index : Int32)) : (Nullable Any)
+          (kotlin-member-call opt))
+        (extern (sivArrayPut (array : JSONArray) (value : JSONObject)) : JSONArray
+          (kotlin-member-call put))
+        (extern (sivMutableObjectAddAt (items : (MutableList JSONObject))
+                                       (index : Int32) (value : JSONObject)) : Unit
+          (kotlin-member-call add))
+        (extern (sivFloatInt (value : Float32)) : Int32 (kotlin-member-call toInt))
+        (extern (sivAsNumber (value : Any)) : (Nullable Number) (kotlin-safe-cast Number))
+        (extern (sivAsObject (value : Any)) : (Nullable JSONObject) (kotlin-safe-cast JSONObject))
+        (extern (sivAsString (value : Any)) : (Nullable String) (kotlin-safe-cast String))
+        (extern (sivNumberInt (value : Number)) : Int32 (kotlin-member-call toInt))
+        (extern (sivAnyText (value : Any)) : String (kotlin-member-call toString))
+        (extern (sivKeys (object : JSONObject)) : (Iterator String) (kotlin-member-call keys))
+        (extern (sivHasNext (iterator : (Iterator String))) : Bool (kotlin-member-call hasNext))
+        (extern (sivNext (iterator : (Iterator String))) : String (kotlin-member-call next))
+        (extern (sivReplace (text : String) (old : String) (replacement : String)) : String
+          (kotlin-member-call replace))
+        (extern (sivUppercase (text : String)) : String (kotlin-member-call uppercase))
+        (extern (sivDropLast (text : String) (count : Int32)) : String
+          (kotlin-member-call dropLast))
         (extern (sivShipTechnicalLabel (actor : (Nullable JSONObject))
                                        (fallback : String)) : String
           (kotlin-call shipTechnicalLabel))
@@ -100,6 +141,10 @@
         (def (sivDensity (view : Sfc2IntelView)) : Float32
           (let ((value (sivDensityValue (sivMetrics (sivResources view)))))
             (if (< value (float32 1.0)) (float32 1.0) value)))
+        (def (sivMaxInt (a : Int32) (b : Int32)) : Int32 (if (> a b) a b))
+        (def (sivObjectInt (object : (Nullable JSONObject)) (key : String)) : Int32
+          (if (nullable-null? object) (int32 0)
+            (sivJsonInt (nullable-get object) key (int32 0))))
         (class Sfc2IntelView ((context : Context))
           (extends View context)
           (var statusReport : (Nullable JSONObject) (nullable-none JSONObject)
@@ -125,6 +170,18 @@
           (val betaPaint : Paint
             (sivPaint (sivRgb (int32 205) (int32 82) (int32 66)) (sivFill))
             (modifiers private))
+          (val gridPaint : Paint
+            (sivPaint (sivRgb (int32 42) (int32 55) (int32 73)) (sivFill))
+            (modifiers private))
+          (val readyPaint : Paint
+            (sivPaint (sivRgb (int32 76) (int32 177) (int32 112)) (sivFill))
+            (modifiers private))
+          (val warningPaint : Paint
+            (sivPaint (sivRgb (int32 222) (int32 158) (int32 68)) (sivFill))
+            (modifiers private))
+          (val blockedPaint : Paint
+            (sivPaint (sivRgb (int32 191) (int32 71) (int32 64)) (sivFill))
+            (modifiers private))
           (val titlePaint : Paint
             (sivTextPaint (sivWhite) (float32 25.0) #t) (modifiers private))
           (val labelPaint : Paint
@@ -200,16 +257,31 @@
                 (+ (/ drawHeight (float32 2.0)) (float32 20.0)) smallPaint)))
           (def (drawHeader (canvas : Canvas) (status : JSONObject)) : Unit
             (modifiers private)
-            (begin
+            (let ((counts (sivJsonObject status "counts")))
+             (begin
               (sivPaintAlignSet titlePaint (sivLeft))
               (sivDrawText canvas "SFC2 INTEL" (float32 12.0) (float32 27.0) titlePaint)
-              (sivPaintAlignSet smallPaint (sivRight))
+              (sivPaintAlignSet smallPaint (sivCenter))
               (sivDrawText canvas
                 (string-append "T"
                   (string-append (int32->string (sivJsonInt status "turn" (int32 0)))
                     (string-append "."
-                      (int32->string (sivJsonInt status "impulse" (int32 0))))))
-                (- drawWidth (float32 12.0)) (float32 25.0) smallPaint)))
+                      (string-append (int32->string (sivJsonInt status "impulse" (int32 0)))
+                        (string-append "  R"
+                          (if (nullable-null? (sivJsonAny status "range")) "-"
+                            (sivAnyText (nullable-get (sivJsonAny status "range")))))))))
+                (/ drawWidth (float32 2.0)) (float32 25.0) smallPaint)
+              (sivPaintAlignSet smallPaint (sivRight))
+              (sivDrawText canvas
+                (string-append "S:"
+                  (string-append (int32->string (sivObjectInt counts "active-seeking"))
+                    (string-append " U:"
+                      (string-append (int32->string (sivObjectInt counts "active-small-units"))
+                        (string-append " W:"
+                          (string-append (int32->string (sivObjectInt counts "active-webs"))
+                            (string-append " M:"
+                              (int32->string (sivObjectInt counts "active-mines")))))))))
+                (- drawWidth (float32 12.0)) (float32 25.0) smallPaint))))
           (def (drawActorIntel (canvas : Canvas) (rect : RectF)
                                (side : String)
                                (actor : (Nullable JSONObject))
@@ -226,17 +298,270 @@
               (sivPaintAlignSet labelPaint (sivLeft))
               (sivDrawText canvas side
                 (+ (sivRectLeft rect) (float32 8.0))
-                (+ (sivRectTop rect) (float32 20.0)) labelPaint)
-              (sivPaintAlignSet smallPaint (sivLeft))
+                (+ (sivRectTop rect) (float32 18.0)) labelPaint)
+              (sivPaintAlignSet dimPaint (sivRight))
               (sivDrawText canvas
-                (if (or (nullable-null? actor)
-                        (not (sivJsonBoolean (nullable-get actor) "ship-present?" #f)))
-                  "No ship report"
-                  (sivShipDisplayLabel (nullable-get actor)))
-                (+ (sivRectLeft rect) (float32 8.0))
-                (+ (sivRectTop rect) (float32 52.0)) smallPaint)
+                (fitText (sivShipTechnicalLabel actor "-") dimPaint
+                  (* (sivRectWidth rect) (float32 0.48)))
+                (- (sivRectRight rect) (float32 8.0))
+                (+ (sivRectTop rect) (float32 18.0)) dimPaint)
+              (if (or (nullable-null? actor)
+                      (not (sivJsonBoolean (nullable-get actor) "ship-present?" #f)))
+                (begin
+                  (sivPaintAlignSet smallPaint (sivLeft))
+                  (sivDrawText canvas "No ship report"
+                    (+ (sivRectLeft rect) (float32 8.0))
+                    (+ (sivRectTop rect) (float32 53.0)) smallPaint)
+                  (drawTruth canvas rect truth (+ (sivRectTop rect) (float32 82.0))))
+                (let ((ship (nullable-get actor)))
+                  (var ((y (+ (sivRectTop rect) (float32 49.0))))
+                    (begin
+                      (sivPaintAlignSet smallPaint (sivLeft))
+                      (sivDrawText canvas
+                        (fitText (sivShipDisplayLabel ship) smallPaint
+                          (- (sivRectWidth rect) (float32 16.0)))
+                        (+ (sivRectLeft rect) (float32 8.0)) y smallPaint)
+                      (set! y (+ y (float32 18.0)))
+                      (set! y (drawVitals canvas rect ship y))
+                      (drawWeaponHeader canvas rect y)
+                      (set! y (+ y (float32 18.0)))
+                      (let ((weapons (sortedWeapons
+                                      (let ((raw (sivJsonArray ship "weapon-status")))
+                                        (if (nullable-null? raw) (new JSONArray) (nullable-get raw))))))
+                        (begin
+                          (if (> (sivArrayLength weapons) (int32 0))
+                            (let ((maxRows
+                                    (sivMaxInt (int32 1)
+                                      (sivFloatInt (/ (- (- (sivRectBottom rect) (float32 61.0))
+                                                          (+ y (float32 42.0)))
+                                                       (float32 18.0))))))
+                              (begin
+                                (drawWeaponRows canvas rect y weapons (int32 0)
+                                  (if (> maxRows (int32 5)) (int32 5) maxRows))
+                                (set! y (+ y (* (float32
+                                  (if (> (sivArrayLength weapons) maxRows) maxRows
+                                    (sivArrayLength weapons))) (float32 18.0))))))
+                            (begin
+                              (sivPaintAlignSet dimPaint (sivLeft))
+                              (sivDrawText canvas "No weapon status"
+                                (+ (sivRectLeft rect) (float32 8.0)) y dimPaint)))
+                          (set! y (+ y (float32 21.0)))))
+                      (set! y (drawSpecialSystems canvas rect ship y))
+                      (drawTruth canvas rect truth
+                        (if (> (+ y (float32 4.0)) (- (sivRectBottom rect) (float32 61.0)))
+                          (+ y (float32 4.0)) (- (sivRectBottom rect) (float32 61.0))))))))))
+
+          (def (drawVitals (canvas : Canvas) (rect : RectF)
+                           (actor : JSONObject) (y : Float32)) : Float32
+            (modifiers private)
+            (let ((power (sivJsonObject actor "power-status"))
+                  (movement (sivJsonObject actor "movement"))
+                  (shields (sivJsonObject actor "shields"))
+                  (maximum (sivJsonObject actor "max-shields")))
+              (begin
+                (sivPaintAlignSet smallPaint (sivLeft))
+                (sivDrawText canvas
+                  (fitText
+                    (string-append "HULL "
+                      (string-append (int32->string
+                        (sivJsonInt actor "remaining-internals" (int32 0)))
+                        (string-append "  SH "
+                          (string-append (int32->string (objectTotal shields))
+                            (string-append "/" (int32->string
+                              (sivMaxInt (objectTotal maximum) (objectTotal shields))))))))
+                    smallPaint (- (sivRectWidth rect) (float32 16.0)))
+                  (+ (sivRectLeft rect) (float32 8.0)) y smallPaint)
+                (sivDrawText canvas
+                  (fitText
+                    (string-append "PWR "
+                      (string-append (int32->string (sivObjectInt power "unspent-energy"))
+                        (string-append "/" (int32->string (sivObjectInt power "total-power")))))
+                    smallPaint (- (sivRectWidth rect) (float32 16.0)))
+                  (+ (sivRectLeft rect) (float32 8.0)) (+ y (float32 17.0)) smallPaint)
+                (+ y (float32 38.0)))))
+
+          (def (drawWeaponHeader (canvas : Canvas) (rect : RectF) (y : Float32)) : Unit
+            (modifiers private)
+            (begin
               (sivPaintAlignSet dimPaint (sivLeft))
-              (sivDrawText canvas
-                (if (nullable-null? truth) "No truth report" "Truth report loaded")
-                (+ (sivRectLeft rect) (float32 8.0))
-                (+ (sivRectTop rect) (float32 78.0)) dimPaint))))))))
+              (sivDrawText canvas "BANK" (+ (sivRectLeft rect) (float32 8.0)) y dimPaint)
+              (sivDrawText canvas "READY" (+ (sivRectLeft rect) (* (sivRectWidth rect) (float32 0.48))) y dimPaint)
+              (sivDrawText canvas "ARC" (+ (sivRectLeft rect) (* (sivRectWidth rect) (float32 0.70))) y dimPaint)))
+
+          (def (drawWeaponLine (canvas : Canvas) (rect : RectF) (y : Float32)
+                               (weapon : (Nullable JSONObject))) : Unit
+            (modifiers private)
+            (if (nullable-null? weapon) (begin)
+              (let ((ready (sivObjectInt weapon "fireable"))
+                    (armed (sivObjectInt weapon "armed"))
+                    (installed (sivObjectInt weapon "installed"))
+                    (inArc (sivObjectInt weapon "fireable-in-arc")))
+                (begin
+                  (sivPaintAlignSet smallPaint (sivLeft))
+                  (sivDrawText canvas
+                    (shortWeapon (sivJsonString (nullable-get weapon) "kind" "-"))
+                    (+ (sivRectLeft rect) (float32 8.0)) y smallPaint)
+                  (drawStatusDot canvas (+ (sivRectLeft rect) (* (sivRectWidth rect) (float32 0.45)))
+                    (- y (float32 5.0)) ready armed inArc)
+                  (sivDrawText canvas
+                    (string-append (int32->string ready)
+                      (string-append "/" (string-append (int32->string armed)
+                        (string-append "/" (int32->string installed)))))
+                    (+ (sivRectLeft rect) (* (sivRectWidth rect) (float32 0.48))) y smallPaint)))))
+
+          (def (drawStatusDot (canvas : Canvas) (x : Float32) (y : Float32)
+                              (ready : Int32) (armed : Int32) (inArc : Int32)) : Unit
+            (modifiers private)
+            (sivDrawCircle canvas x y (float32 5.0)
+              (if (and (> ready (int32 0)) (> inArc (int32 0))) readyPaint
+                (if (or (> armed (int32 0)) (> ready (int32 0))) warningPaint blockedPaint))))
+
+          (def (drawSpecialSystems (canvas : Canvas) (rect : RectF)
+                                   (actor : JSONObject) (y : Float32)) : Float32
+            (modifiers private)
+            (if (> y (- (sivRectBottom rect) (float32 85.0))) y
+              (let ((tractor (sivJsonObject actor "tractor-status"))
+                    (transporter (sivJsonObject actor "transporter-status"))
+                    (repair (sivJsonObject actor "repair-status")))
+                (begin
+                  (sivDrawText canvas
+                    (fitText (string-append "TRAC "
+                      (string-append (int32->string (sivObjectInt tractor "available-boxes"))
+                        (string-append "  TRAN "
+                          (string-append (int32->string (sivObjectInt transporter "available-boxes"))
+                            (string-append "  LAB " (int32->string (sivObjectInt repair "lab-count")))))))
+                      smallPaint (- (sivRectWidth rect) (float32 16.0)))
+                    (+ (sivRectLeft rect) (float32 8.0)) y smallPaint)
+                  (+ y (float32 35.0))))))
+
+          (def (drawTruth (canvas : Canvas) (rect : RectF)
+                          (truth : (Nullable JSONObject)) (y : Float32)) : Unit
+            (modifiers private)
+            (let ((box (new RectF (+ (sivRectLeft rect) (float32 8.0)) y
+                              (- (sivRectRight rect) (float32 8.0))
+                              (- (sivRectBottom rect) (float32 8.0)))))
+              (if (< (sivRectHeight box) (float32 28.0)) (begin)
+                (begin
+                  (sivDrawRoundRect canvas box (float32 6.0) (float32 6.0) gridPaint)
+                  (sivPaintAlignSet labelPaint (sivLeft))
+                  (sivDrawText canvas
+                    (if (nullable-null? truth) "SSD truth not linked"
+                      (let ((summary (sivJsonObject (nullable-get truth) "summary")))
+                        (string-append "SSD "
+                          (string-append (int32->string (sivObjectInt summary "group-count"))
+                            (string-append "g "
+                              (string-append (int32->string (sivObjectInt summary "box-count")) " boxes"))))))
+                    (+ (sivRectLeft box) (float32 6.0))
+                    (+ (sivRectTop box) (float32 18.0)) labelPaint)))))
+
+          (def (drawWeaponRows (canvas : Canvas) (rect : RectF) (y : Float32)
+                               (weapons : JSONArray) (index : Int32)
+                               (limit : Int32)) : Unit
+            (modifiers private)
+            (if (or (>= index limit) (>= index (sivArrayLength weapons))) (begin)
+              (begin
+                (drawWeaponLine canvas rect (+ y (* (float32 index) (float32 18.0)))
+                  (sivArrayObject weapons index))
+                (drawWeaponRows canvas rect y weapons (+ index (int32 1)) limit))))
+          (def (sortedWeapons (raw : JSONArray)) : JSONArray
+            (modifiers private)
+            (let ((items (mutable-list-empty JSONObject)) (out (new JSONArray)))
+              (begin
+                (collectSortedWeapons raw (int32 0) items)
+                (appendWeapons out items (int32 0))
+                out)))
+          (def (collectSortedWeapons (raw : JSONArray) (index : Int32)
+                                     (items : (MutableList JSONObject))) : Unit
+            (modifiers private)
+            (if (>= index (sivArrayLength raw)) (begin)
+              (begin
+                (let ((weapon (sivArrayObject raw index)))
+                  (if (nullable-null? weapon) (begin)
+                    (insertSortedWeapon items (nullable-get weapon))))
+                (collectSortedWeapons raw (+ index (int32 1)) items))))
+          (def (insertSortedWeapon (items : (MutableList JSONObject))
+                                   (weapon : JSONObject)) : Unit
+            (modifiers private)
+            (let ((at (findWeaponInsert items weapon (int32 0))))
+              (if (>= at (list-size items)) (mutable-list-add! items weapon)
+                (sivMutableObjectAddAt items at weapon))))
+          (def (findWeaponInsert (items : (List JSONObject)) (weapon : JSONObject)
+                                 (index : Int32)) : Int32
+            (modifiers private)
+            (if (>= index (list-size items)) index
+              (if (weaponBefore? weapon (list-ref items index)) index
+                (findWeaponInsert items weapon (+ index (int32 1))))))
+          (def (weaponBefore? (a : JSONObject) (b : JSONObject)) : Bool
+            (modifiers private)
+            (let ((aa (sivJsonInt a "fireable-in-arc" (int32 0)))
+                  (ba (sivJsonInt b "fireable-in-arc" (int32 0)))
+                  (af (sivJsonInt a "fireable" (int32 0)))
+                  (bf (sivJsonInt b "fireable" (int32 0))))
+              (if (not (= aa ba)) (> aa ba)
+                (if (not (= af bf)) (> af bf)
+                  (< (weaponSortBucket (sivJsonString a "kind" ""))
+                     (weaponSortBucket (sivJsonString b "kind" "")))))))
+          (def (appendWeapons (out : JSONArray) (items : (List JSONObject))
+                              (index : Int32)) : Unit
+            (modifiers private)
+            (if (>= index (list-size items)) (begin)
+              (begin
+                (sivArrayPut out (list-ref items index))
+                (appendWeapons out items (+ index (int32 1))))))
+          (def (weaponSortBucket (kind : String)) : Int32 (modifiers private)
+            (if (string-starts-with? kind "phaser") (int32 2)
+              (if (or (string-contains? kind "drone") (string-contains? kind "plasma"))
+                (int32 1) (int32 0))))
+          (def (shortWeapon (kind : String)) : String (modifiers private)
+            (sivUppercase
+              (sivReplace (sivReplace (sivReplace (sivReplace kind
+                "phaser-" "PH-") "photon" "PHOT") "disruptor" "DISR") "plasma-" "PL-")))
+          (def (compactArray (array : (Nullable JSONArray)) (limit : Int32)) : String
+            (modifiers private)
+            (if (or (nullable-null? array) (= (sivArrayLength (nullable-get array)) (int32 0))) ""
+              (if (nullable-null? (sivArrayAny (nullable-get array) (int32 0))) ""
+                (sivAnyText (nullable-get (sivArrayAny (nullable-get array) (int32 0)))))))
+          (def (objectTotal (object : (Nullable JSONObject))) : Int32
+            (modifiers private)
+            (if (nullable-null? object) (int32 0)
+              (objectTotalKeys (nullable-get object) (sivKeys (nullable-get object)) (int32 0))))
+          (def (objectTotalKeys (object : JSONObject) (keys : (Iterator String))
+                                (total : Int32)) : Int32
+            (modifiers private)
+            (if (not (sivHasNext keys)) total
+              (objectTotalKeys object keys
+                (+ total (sivMaxInt (int32 0)
+                  (numberValue (sivJsonAny object (sivNext keys))))))))
+          (def (counterValue (counter : (Nullable Any)) (wantedKey : String)) : Int32
+            (modifiers private)
+            (if (nullable-null? counter) (int32 0)
+              (let ((object (sivAsObject (nullable-get counter))))
+                (if (nullable-null? object) (int32 0)
+                  (numberValue (sivJsonAny (nullable-get object) wantedKey))))))
+          (def (numberValue (value : (Nullable Any))) : Int32
+            (modifiers private)
+            (if (nullable-null? value) (int32 0)
+              (let ((number (sivAsNumber (nullable-get value))))
+                (if (nullable-null? number) (int32 0) (sivNumberInt (nullable-get number))))))
+          (def (truthGroupSample (groups : (Nullable JSONArray))) : String (modifiers private) "")
+          (def (shortTruthLabel (label : String)) : String (modifiers private)
+            (sivUppercase (sivReplace (sivReplace label "phaser-" "ph-") "transporter" "tran")))
+          (def (truthMatchTag (truth : (Nullable JSONObject))) : String
+            (modifiers private)
+            (if (nullable-null? truth) ""
+              (let ((kind (sivJsonString (nullable-get truth) "_matched-by" "")))
+                (if (equal? kind "template-id") "LINK"
+                  (if (equal? kind "box-signature")
+                    (string-append "SIG" (int32->string
+                      (sivJsonInt (nullable-get truth) "_match-overlap" (int32 0)))) "")))))
+          (def (fitText (text : String) (paint : Paint) (maxWidth : Float32)) : String
+            (modifiers private)
+            (if (<= (sivPaintMeasure paint text) maxWidth) text
+              (fitTextAt text paint maxWidth (- (string-length-int32 text) (int32 1)))))
+          (def (fitTextAt (text : String) (paint : Paint)
+                          (maxWidth : Float32) (end : Int32)) : String
+            (modifiers private)
+            (if (<= end (int32 4)) (string-take text end)
+              (let ((candidate (string-append (string-take text end) "...")))
+                (if (<= (sivPaintMeasure paint candidate) maxWidth) candidate
+                  (fitTextAt text paint maxWidth (- end (int32 1))))))))))))
diff --git a/templates/original-tactics-parts/SystemsReadinessView.kt.ss b/templates/original-tactics-parts/SystemsReadinessView.kt.ss
index ba200c6..a6ee0da 100644
--- a/templates/original-tactics-parts/SystemsReadinessView.kt.ss
+++ b/templates/original-tactics-parts/SystemsReadinessView.kt.ss
@@ -14,10 +14,18 @@
         (org json JSONArray)
         (org json JSONObject))
       (typed-library (com jerboa originaltactics)
-        (export SystemsReadinessView)
+        (export SystemsReadinessView
+                make-ReadinessWeaponFamily ReadinessWeaponFamily?)
         (type Context) (type Canvas) (type Paint) (type Align) (type Style)
         (type RectF) (type View) (type Resources) (type DisplayMetrics)
         (type JSONArray) (type JSONObject) (type Int32) (type Float32)
+        (record ReadinessWeaponFamily
+          ((kind : String)
+           (installed : Int32)
+           (armed : Int32)
+           (fireable : Int32)
+           (inArc : Int32)
+           (cooldown : Int32)))
         (extern (srvPaintFlag) : Int32 (kotlin-value Paint ANTI_ALIAS_FLAG))
         (extern (srvFill) : Style (kotlin-value Paint Style FILL))
         (extern (srvStroke) : Style (kotlin-value Paint Style STROKE))
@@ -37,6 +45,8 @@
           (kotlin-member-set isFakeBoldText))
         (extern (srvPaintAlignSet (paint : Paint) (value : Align)) : Unit
           (kotlin-member-set textAlign))
+        (extern (srvPaintMeasure (paint : Paint) (text : String)) : Float32
+          (kotlin-member-call measureText))
         (extern (srvInvalidate (view : SystemsReadinessView)) : Unit
           (kotlin-member-call invalidate))
         (extern (srvSuperDraw (canvas : Canvas)) : Unit
@@ -72,6 +82,8 @@
         (extern (srvRectLeft (rect : RectF)) : Float32 (kotlin-member-get left))
         (extern (srvRectTop (rect : RectF)) : Float32 (kotlin-member-get top))
         (extern (srvRectRight (rect : RectF)) : Float32 (kotlin-member-get right))
+        (extern (srvRectBottom (rect : RectF)) : Float32 (kotlin-member-get bottom))
+        (extern (srvRectWidth (rect : RectF)) : Float32 (kotlin-member-call width))
         (extern (srvJsonObject (object : JSONObject)
                                (key : String)) : (Nullable JSONObject)
           (kotlin-member-call optJSONObject))
@@ -84,8 +96,34 @@
         (extern (srvJsonBoolean (object : JSONObject) (key : String)
                                 (fallback : Bool)) : Bool
           (kotlin-member-call optBoolean))
+        (extern (srvJsonString (object : JSONObject) (key : String)
+                               (fallback : String)) : String
+          (kotlin-member-call optString))
+        (extern (srvArrayObject (array : JSONArray)
+                                (index : Int32)) : (Nullable JSONObject)
+          (kotlin-member-call optJSONObject))
         (extern (srvArrayLength (array : JSONArray)) : Int32
           (kotlin-member-call length))
+        (extern (srvMutableFamilyAddAt (items : (MutableList ReadinessWeaponFamily))
+                                       (index : Int32)
+                                       (value : ReadinessWeaponFamily)) : Unit
+          (kotlin-member-call add))
+        (extern (srvMutableStringAddAt (items : (MutableList String))
+                                       (index : Int32) (value : String)) : Unit
+          (kotlin-member-call add))
+        (extern (srvMapKeys (items : (Map String Int32))) : (Set String)
+          (kotlin-member-get keys))
+        (extern (srvSetToList (items : (Set String))) : (List String)
+          (kotlin-member-call toList))
+        (extern (srvStringReplace (text : String) (old : String)
+                                  (replacement : String)) : String
+          (kotlin-member-call replace))
+        (extern (srvStringDropLast (text : String) (count : Int32)) : String
+          (kotlin-member-call dropLast))
+        (extern (srvStringStartsWith (text : String) (prefix : String)) : Bool
+          (kotlin-member-call startsWith))
+        (extern (srvStringContains (text : String) (part : String)) : Bool
+          (kotlin-member-call contains))
         (extern (srvShipDisplayLabel (actor : JSONObject)) : String
           (kotlin-call shipDisplayLabel))
         (def (srvPaint (color : Int32) (style : Style)) : Paint
@@ -109,6 +147,22 @@
             (if (nullable-null? array)
               (int32 0)
               (srvArrayLength (nullable-get array)))))
+        (def (srvMaxInt (a : Int32) (b : Int32)) : Int32 (if (> a b) a b))
+        (def (srvClampFloat (value : Float32) (low : Float32)
+                            (high : Float32)) : Float32
+          (if (< value low) low (if (> value high) high value)))
+        (def (srvObjectInt (object : (Nullable JSONObject))
+                           (key : String) (fallback : Int32)) : Int32
+          (if (nullable-null? object)
+            fallback
+            (srvJsonInt (nullable-get object) key fallback)))
+        (def (srvObjectArray (object : JSONObject)
+                             (key : String)) : JSONArray
+          (let ((value (srvJsonArray object key)))
+            (if (nullable-null? value) (new JSONArray) (nullable-get value))))
+        (def (srvMapValue (values : (Map String Int32)) (key : String)) : Int32
+          (let ((value (map-ref-or-null values key)))
+            (if (nullable-null? value) (int32 0) (nullable-get value))))
         (class SystemsReadinessView ((context : Context))
           (extends View context)
           (var report : (Nullable JSONObject) (nullable-none JSONObject)
@@ -189,14 +243,27 @@
                 (+ (/ drawHeight (float32 2.0)) (float32 6.0)) textPaint)))
           (def (drawHeader (canvas : Canvas) (current : JSONObject)) : Unit
             (modifiers private)
-            (begin
+            (let ((counts (srvJsonObject current "counts")))
+             (begin
               (srvPaintAlignSet textPaint (srvLeft))
-              (srvDrawText canvas "SYSTEMS" (float32 10.0) (float32 24.0) textPaint)
+              (srvDrawText canvas
+                (string-append "SYSTEMS  S:"
+                  (string-append
+                    (int32->string (srvObjectInt counts "active-seeking" (int32 0)))
+                    (string-append " U:"
+                      (string-append
+                        (int32->string (srvObjectInt counts "active-small-units" (int32 0)))
+                        (string-append " W:"
+                          (string-append
+                            (int32->string (srvObjectInt counts "active-webs" (int32 0)))
+                            (string-append " M:"
+                              (int32->string (srvObjectInt counts "active-mines" (int32 0))))))))))
+                (float32 10.0) (float32 24.0) textPaint)
               (srvPaintAlignSet valuePaint (srvRight))
               (srvDrawText canvas
                 (string-append "events "
                   (int32->string (srvJsonInt current "event-count" (int32 0))))
-                (- drawWidth (float32 10.0)) (float32 24.0) valuePaint)))
+                (- drawWidth (float32 10.0)) (float32 24.0) valuePaint))))
           (def (drawActorCard (canvas : Canvas) (rect : RectF)
                               (side : String)
                               (actor : (Nullable JSONObject))
@@ -215,22 +282,364 @@
                 (+ (srvRectLeft rect) (float32 8.0))
                 (+ (srvRectTop rect) (float32 18.0)) valuePaint)
               (srvPaintColorSet valuePaint (srvRgb (int32 20) (int32 28) (int32 40)))
-              (srvPaintAlignSet smallPaint (srvLeft))
               (if (or (nullable-null? actor)
                       (not (srvJsonBoolean (nullable-get actor) "ship-present?" #f)))
-                (srvDrawText canvas "No ship report"
-                  (+ (srvRectLeft rect) (float32 8.0))
-                  (+ (srvRectTop rect) (float32 52.0)) smallPaint)
+                (begin
+                  (srvPaintAlignSet smallPaint (srvLeft))
+                  (srvDrawText canvas "No ship report"
+                    (+ (srvRectLeft rect) (float32 8.0))
+                    (+ (srvRectTop rect) (float32 52.0)) smallPaint))
                 (let ((ship (nullable-get actor)))
-                  (begin
-                    (srvDrawText canvas (srvShipDisplayLabel ship)
-                      (+ (srvRectLeft rect) (float32 8.0))
-                      (+ (srvRectTop rect) (float32 52.0)) smallPaint)
-                    (srvDrawText canvas
-                      (string-append "WEAP "
-                        (string-append
-                          (int32->string (srvArrayCount ship "weapon-status"))
-                          (string-append "  SEEK "
-                            (int32->string (srvArrayCount ship "seeking-status")))))
-                      (+ (srvRectLeft rect) (float32 8.0))
-                      (+ (srvRectTop rect) (float32 80.0)) smallPaint)))))))))))
+                  (let ((power (srvJsonObject ship "power-status"))
+                        (weapons (srvObjectArray ship "weapon-status"))
+                        (seeking (srvObjectArray ship "seeking-status"))
+                        (seekingSlots (srvObjectArray ship "seeking-launch-slots"))
+                        (fighterBays (srvObjectArray ship "fighter-bays"))
+                        (tractor (srvJsonObject ship "tractor-status"))
+                        (transporter (srvJsonObject ship "transporter-status"))
+                        (repair (srvJsonObject ship "repair-status")))
+                    (let ((totalPower (srvObjectInt power "total-power" (int32 0)))
+                          (usedBattery (srvObjectInt power "battery-used" (int32 0)))
+                          (unspent (srvObjectInt power "unspent-energy" (int32 0)))
+                          (weaponReady (sumArray weapons "fireable"))
+                          (weaponArmed (sumArray weapons "armed"))
+                          (weaponInArc (sumArray weapons "fireable-in-arc"))
+                          (seekerLoaded (sumArray seeking "loaded-ammo"))
+                          (seekerLaunchable (countTrue seeking "launchable-now?"))
+                          (readyShuttles (srvMaxInt (int32 0)
+                            (srvJsonInt ship "ready-shuttles" (int32 0))))
+                          (launchCapacity (srvMaxInt (int32 0)
+                            (srvJsonInt ship "total-shuttle-launch-capacity"
+                              (srvJsonInt ship "shuttle-launch-capacity" (int32 0)))))
+                          (recoveryOpen (srvMaxInt (int32 0)
+                            (srvJsonInt ship "shuttle-recovery-open-capacity" (int32 0))))
+                          (fighterStock (sumArray fighterBays "available"))
+                          (fcs (if (srvJsonBoolean ship "fire-control-active?" #t)
+                                 "ON" "OFF")))
+                      (var ((y (+ (srvRectTop rect) (float32 49.0))))
+                        (begin
+                          (srvPaintAlignSet smallPaint (srvLeft))
+                          (srvDrawText canvas
+                            (fitText (srvShipDisplayLabel ship) smallPaint
+                              (- (srvRectWidth rect) (float32 16.0)))
+                            (+ (srvRectLeft rect) (float32 8.0)) y smallPaint)
+                          (set! y (+ y (float32 22.0)))
+                          (drawReadinessBar canvas (+ (srvRectLeft rect) (float32 8.0)) y
+                            (- (srvRectWidth rect) (float32 16.0)) "POWER"
+                            unspent (srvMaxInt (int32 1) totalPower))
+                          (set! y (+ y (float32 33.0)))
+                          (drawReadinessBar canvas (+ (srvRectLeft rect) (float32 8.0)) y
+                            (- (srvRectWidth rect) (float32 16.0)) "WEAP"
+                            weaponReady (srvMaxInt (int32 1) weaponArmed))
+                          (set! y (+ y (float32 33.0)))
+                          (drawReadinessBar canvas (+ (srvRectLeft rect) (float32 8.0)) y
+                            (- (srvRectWidth rect) (float32 16.0)) "ARC"
+                            weaponInArc (srvMaxInt (int32 1) weaponReady))
+                          (set! y (+ y (float32 33.0)))
+                          (let ((weaponText (weaponFamilySummary weapons)))
+                            (if (string-blank? weaponText)
+                              (begin)
+                              (begin
+                                (srvDrawText canvas
+                                  (fitText (string-append "WPN " weaponText) smallPaint
+                                    (- (srvRectWidth rect) (float32 16.0)))
+                                  (+ (srvRectLeft rect) (float32 8.0)) y smallPaint)
+                                (set! y (+ y (float32 21.0))))))
+                          (let ((profileText (slotProfileSummary seekingSlots)))
+                            (srvDrawText canvas
+                              (fitText
+                                (string-append "SEEK launch "
+                                  (string-append (int32->string seekerLaunchable)
+                                    (string-append " loaded "
+                                      (string-append (int32->string seekerLoaded)
+                                        (if (string-blank? profileText) ""
+                                          (string-append "  " profileText))))))
+                                smallPaint (- (srvRectWidth rect) (float32 16.0)))
+                              (+ (srvRectLeft rect) (float32 8.0)) y smallPaint))
+                          (set! y (+ y (float32 21.0)))
+                          (srvDrawText canvas
+                            (fitText
+                              (string-append "UNITS sh "
+                                (string-append (int32->string readyShuttles)
+                                  (string-append " launch "
+                                    (string-append (int32->string launchCapacity)
+                                      (string-append
+                                        (if (> (srvArrayLength fighterBays) (int32 0))
+                                          (string-append " bays "
+                                            (string-append (int32->string (srvArrayLength fighterBays))
+                                              (string-append " stock " (int32->string fighterStock))))
+                                          "")
+                                        (string-append " rec " (int32->string recoveryOpen)))))))
+                              smallPaint (- (srvRectWidth rect) (float32 16.0)))
+                            (+ (srvRectLeft rect) (float32 8.0)) y smallPaint)
+                          (set! y (+ y (float32 21.0)))
+                          (srvDrawText canvas
+                            (fitText
+                              (string-append "TRAC "
+                                (string-append (int32->string
+                                  (srvObjectInt tractor "available-boxes" (int32 0)))
+                                  (string-append "  TRAN "
+                                    (string-append (int32->string
+                                      (srvObjectInt transporter "available-boxes" (int32 0)))
+                                      (string-append "  DC " (int32->string