Integrate compiled typed Android client modules

ober

d168c109aad40c40e46d636b8eafcce3a2b6c1c1

diff --git a/templates/original-tactics.ss b/templates/original-tactics.ss
index d438b54..6606266 100644
--- a/templates/original-tactics.ss
+++ b/templates/original-tactics.ss
@@ -1,1052 +1,10 @@
 (import (jerboa prelude))
 
 (def fragment
-     '((kotlin-file-lines
-         "com/jerboa/originaltactics/BattleCommandView.kt"
-         ("package com.jerboa.originaltactics" ""
-          "import android.content.Context"
-          "import android.graphics.Canvas"
-          "import android.graphics.Color"
-          "import android.graphics.Paint"
-          "import android.graphics.RectF" "import android.view.View"
-          "import org.json.JSONArray" "import org.json.JSONObject"
-          "import kotlin.math.max" "import kotlin.math.min" ""
-          "class BattleCommandView(context: Context) : View(context) {"
-          "    private var game: JSONObject? = null"
-          "    private var events: JSONArray = JSONArray()"
-          "    private var tacticalStatus: JSONObject? = null"
-          "    private var drawWidth = 0f"
-          "    private var drawHeight = 0f" ""
-          "    private val backgroundPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {"
-          "        color = Color.rgb(23, 29, 39)"
-          "        style = Paint.Style.FILL" "    }"
-          "    private val panelPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {"
-          "        color = Color.rgb(34, 42, 55)"
-          "        style = Paint.Style.FILL" "    }"
-          "    private val borderPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {"
-          "        color = Color.rgb(83, 98, 122)"
-          "        style = Paint.Style.STROKE"
-          "        strokeWidth = 2f" "    }"
-          "    private val alphaPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {"
-          "        color = Color.rgb(47, 128, 222)"
-          "        style = Paint.Style.FILL" "    }"
-          "    private val betaPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {"
-          "        color = Color.rgb(215, 92, 72)"
-          "        style = Paint.Style.FILL" "    }"
-          "    private val trackPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {"
-          "        color = Color.rgb(58, 68, 84)"
-          "        style = Paint.Style.FILL" "    }"
-          "    private val fillPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {"
-          "        color = Color.rgb(70, 167, 106)"
-          "        style = Paint.Style.FILL" "    }"
-          "    private val accentPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {"
-          "        color = Color.rgb(230, 155, 63)"
-          "        style = Paint.Style.FILL" "    }"
-          "    private val titlePaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {"
-          "        color = Color.WHITE" "        textSize = 27f"
-          "        isFakeBoldText = true" "    }"
-          "    private val labelPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {"
-          "        color = Color.rgb(213, 221, 234)"
-          "        textSize = 20f" "    }"
-          "    private val smallPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {"
-          "        color = Color.rgb(165, 177, 195)"
-          "        textSize = 17f" "    }"
-          "    private val valuePaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {"
-          "        color = Color.WHITE" "        textSize = 19f"
-          "        isFakeBoldText = true" "    }" ""
-          "    fun setGame(nextGame: JSONObject?, nextEvents: JSONArray = JSONArray(), nextStatus: JSONObject? = tacticalStatus) {"
-          "        game = nextGame" "        events = nextEvents"
-          "        tacticalStatus = nextStatus" "        invalidate()"
-          "    }" ""
-          "    fun setTacticalStatus(nextStatus: JSONObject?) {"
-          "        tacticalStatus = nextStatus" "        invalidate()"
-          "    }" "" "    override fun onDraw(canvas: Canvas) {"
-          "        super.onDraw(canvas)"
-          "        val canvasState = canvas.save()"
-          "        val scale = resources.displayMetrics.density.coerceAtLeast(1f)"
-          "        drawWidth = width.toFloat() / scale"
-          "        drawHeight = height.toFloat() / scale"
-          "        canvas.scale(scale, scale)" "        try {"
-          "            drawContent(canvas)" "        } finally {"
-          "            canvas.restoreToCount(canvasState)" "        }"
-          "    }" "" "    private fun drawContent(canvas: Canvas) {"
-          "        canvas.drawRoundRect(RectF(0f, 0f, drawWidth, drawHeight), 10f, 10f, backgroundPaint)"
-          "" "        val current = game"
-          "        val battle = current?.optJSONObject(\"battle-state\")"
-          "        if (current == null || battle == null) {"
-          "            drawEmpty(canvas)" "            return"
-          "        }" "" "        drawHeader(canvas, current, battle)"
-          "        drawOrderStrip(canvas, current, battle)"
-          "        val orders = current.optJSONObject(\"orders\")"
-          "        val currentImpulse = current.optInt(\"impulse\", 0)"
-          "" "        val gap = 10f" "        val top = 82f"
-          "        val feedHeight = min(54f, drawHeight * 0.32f)"
-          "        val cardsBottom = max(top + 76f, drawHeight - feedHeight - 8f)"
-          "        val cardWidth = (drawWidth - gap * 3f) / 2f"
-          "        drawShipCard(" "            canvas,"
-          "            RectF(gap, top, gap + cardWidth, cardsBottom),"
-          "            \"ALPHA\","
-          "            battle.optJSONObject(\"alpha\"),"
-          "            battle.optJSONObject(\"alpha-position\"),"
-          "            tacticalStatus?.optJSONObject(\"alpha\"),"
-          "            orders," "            \"alpha\","
-          "            currentImpulse," "            alphaPaint"
-          "        )" "        drawShipCard(" "            canvas,"
-          "            RectF(gap * 2f + cardWidth, top, gap * 2f + cardWidth * 2f, cardsBottom),"
-          "            \"BETA\","
-          "            battle.optJSONObject(\"beta\"),"
-          "            battle.optJSONObject(\"beta-position\"),"
-          "            tacticalStatus?.optJSONObject(\"beta\"),"
-          "            orders," "            \"beta\","
-          "            currentImpulse," "            betaPaint"
-          "        )"
-          "        drawEventFeed(canvas, RectF(gap, cardsBottom + 6f, drawWidth - gap, drawHeight - 8f))"
-          "    }" "" "    private fun drawEmpty(canvas: Canvas) {"
-          "        titlePaint.textAlign = Paint.Align.CENTER"
-          "        canvas.drawText(\"Battle command\", drawWidth / 2f, drawHeight / 2f - 2f, titlePaint)"
-          "        smallPaint.textAlign = Paint.Align.CENTER"
-          "        canvas.drawText(\"Start an engagement\", drawWidth / 2f, drawHeight / 2f + 24f, smallPaint)"
-          "    }" ""
-          "    private fun drawHeader(canvas: Canvas, current: JSONObject, battle: JSONObject) {"
-          "        val turn = current.optInt(\"turn\", 1)"
-          "        val impulse = current.optInt(\"impulse\", 0)"
-          "        val range = currentBattleRange(battle)"
-          "        val winner = current.optString(\"winner\", \"undecided\")"
-          "        val fired = current.optBoolean(\"fired?\", false)"
-          "        val fireState = if (fired) \"FIRE RESOLVED\" else \"FIRE READY\""
-          "        val outcome = if (winner == \"undecided\") \"NO DECISION\" else \"${winner.uppercase()} WINS\""
-          "" "        titlePaint.textAlign = Paint.Align.LEFT"
-          "        canvas.drawText(\"T$turn.$impulse\", 12f, 29f, titlePaint)"
-          "        valuePaint.textAlign = Paint.Align.CENTER"
-          "        canvas.drawText(\"RANGE $range\", drawWidth / 2f, 29f, valuePaint)"
-          "        val statusText = \"$fireState  $outcome\""
-          "        valuePaint.textAlign = Paint.Align.RIGHT"
-          "        canvas.drawText(fitText(statusText, valuePaint, drawWidth * 0.44f), drawWidth - 12f, 29f, valuePaint)"
-          "    }" ""
-          "    private fun drawOrderStrip(canvas: Canvas, current: JSONObject, battle: JSONObject) {"
-          "        val orders = current.optJSONObject(\"orders\") ?: return"
-          "        val currentImpulse = current.optInt(\"impulse\", 0)"
-          "        val fireImpulse = orders.optInt(\"fire-impulse\", 32)"
-          "        val alpha = maneuverPlotText(orders.opt(\"alpha-maneuvers\"), currentImpulse, 3)"
-          "        val beta = maneuverPlotText(orders.opt(\"beta-maneuvers\"), currentImpulse, 3)"
-          "        val ordersText = \"FIRE I$fireImpulse    A $alpha    B $beta\""
-          "        val forecastText = rangeForecastLine(battle, orders, currentImpulse)"
-          ""
-          "        val rect = RectF(10f, 39f, drawWidth - 10f, 74f)"
-          "        canvas.drawRoundRect(rect, 8f, 8f, panelPaint)"
-          "        canvas.drawRoundRect(rect, 8f, 8f, borderPaint)"
-          "        if (forecastText.isBlank()) {"
-          "            valuePaint.textAlign = Paint.Align.CENTER"
-          "            canvas.drawText(fitText(ordersText, valuePaint, rect.width() - 18f), rect.centerX(), rect.top + 24f, valuePaint)"
-          "        } else {"
-          "            valuePaint.textAlign = Paint.Align.LEFT"
-          "            canvas.drawText(fitText(ordersText, valuePaint, rect.width() - 18f), rect.left + 9f, rect.top + 16f, valuePaint)"
-          "            smallPaint.textAlign = Paint.Align.LEFT"
-          "            canvas.drawText(fitText(forecastText, smallPaint, rect.width() - 18f), rect.left + 9f, rect.top + 32f, smallPaint)"
-          "        }" "    }" "" "    private fun drawShipCard("
-          "        canvas: Canvas," "        rect: RectF,"
-          "        side: String," "        ship: JSONObject?,"
-          "        position: JSONObject?,"
-          "        statusActor: JSONObject?,"
-          "        orders: JSONObject?," "        actor: String,"
-          "        currentImpulse: Int," "        sidePaint: Paint"
-          "    ) {"
-          "        canvas.drawRoundRect(rect, 9f, 9f, panelPaint)"
-          "        canvas.drawRoundRect(rect, 9f, 9f, borderPaint)"
-          "        canvas.drawRoundRect(RectF(rect.left, rect.top, rect.right, rect.top + 28f), 9f, 9f, sidePaint)"
-          "" "        valuePaint.textAlign = Paint.Align.LEFT"
-          "        canvas.drawText(side, rect.left + 9f, rect.top + 20f, valuePaint)"
-          "" "        val name = shipDisplayLabel(ship)"
-          "        labelPaint.textAlign = Paint.Align.LEFT"
-          "        canvas.drawText(fitText(name, labelPaint, rect.width() - 18f), rect.left + 9f, rect.top + 50f, labelPaint)"
-          "" "        val speed = position?.optInt(\"speed\", 0) ?: 0"
-          "        val facing = position?.optInt(\"facing\", 0) ?: 0"
-          "        val helm = maneuverPlotText(orders?.opt(\"$actor-maneuvers\"), currentImpulse, 3)"
-          "        val nextMove = nextMoveImpulse(position, currentImpulse)?.let { \"M I$it\" } ?: \"HOLD\""
-          "        smallPaint.textAlign = Paint.Align.LEFT"
-          "        canvas.drawText(fitText(\"SPD $speed   F$facing   $nextMove   HELM $helm\", smallPaint, rect.width() - 18f), rect.left + 9f, rect.top + 70f, smallPaint)"
-          ""
-          "        val internalNow = ship?.optInt(\"remaining-internals\", 0) ?: 0"
-          "        val internalMax = maxInternals(ship, internalNow)"
-          "        drawMetricBar(canvas, rect.left + 9f, rect.top + 84f, rect.width() - 18f, \"HULL\", internalNow, internalMax)"
-          "" "        val shieldNow = shieldTotal(ship, false)"
-          "        val shieldMax = max(shieldTotal(ship, true), shieldNow)"
-          "        val paPanels = counterValue(ship?.opt(\"boxes\"), \"52\")"
-          "        val paMax = paPanelMax(ship)"
-          "        if (shieldMax <= 0 && (paPanels > 0 || paMax > 1)) {"
-          "            drawMetricBar(canvas, rect.left + 9f, rect.top + 116f, rect.width() - 18f, \"PA PANELS\", paPanels, paMax)"
-          "        } else {"
-          "            drawMetricBar(canvas, rect.left + 9f, rect.top + 116f, rect.width() - 18f, \"SHIELDS\", shieldNow, shieldMax)"
-          "        }" ""
-          "        val weaponTotal = counterTotal(ship?.optJSONObject(\"weapons\"))"
-          "        val armedTotal = counterTotal(ship?.optJSONObject(\"armed-weapons\"))"
-          "        drawMetricBar(canvas, rect.left + 9f, rect.top + 148f, rect.width() - 18f, \"ARMED\", armedTotal, max(1, weaponTotal))"
-          ""
-          "        val fireSolution = actorFireSolution(statusActor)"
-          "        val fireLine = when {"
-          "            fireSolution.installed > 0 -> \"LOCK ${fireSolution.inArc}/${fireSolution.ready}   READY ${fireSolution.ready}/${fireSolution.armed}\""
-          "            else -> \"LOCK -   READY -\"" "        }"
-          "        smallPaint.textAlign = Paint.Align.LEFT"
-          "        canvas.drawText(fitText(fireLine, smallPaint, rect.width() - 18f), rect.left + 9f, min(rect.bottom - 30f, rect.top + 180f), smallPaint)"
-          ""
-          "        val cool = counterTotal(ship?.optJSONObject(\"direct-fire-cooldown\"))"
-          "        val launch = counterTotal(ship?.optJSONObject(\"seeking-launch-limits\"))"
-          "        val families = weaponFamilyLine(statusActor)"
-          "        val primary = primaryWeaponLine(statusActor)"
-          "        val footer = when {"
-          "            families.isNotBlank() -> \"$families   COOL $cool\""
-          "            primary.isNotBlank() -> \"$primary   COOL $cool\""
-          "            else -> \"COOL $cool   LAUNCH $launch\""
-          "        }"
-          "        canvas.drawText(fitText(footer, smallPaint, rect.width() - 18f), rect.left + 9f, min(rect.bottom - 10f, rect.top + 201f), smallPaint)"
-          "    }" ""
-          "    private fun drawMetricBar(canvas: Canvas, x: Float, y: Float, barWidth: Float, label: String, value: Int, maxValue: Int) {"
-          "        val safeMax = max(1, maxValue)"
-          "        val ratio = (value.toFloat() / safeMax.toFloat()).coerceIn(0f, 1f)"
-          "        labelPaint.textAlign = Paint.Align.LEFT"
-          "        canvas.drawText(label, x, y + 13f, smallPaint)"
-          "        valuePaint.textAlign = Paint.Align.RIGHT"
-          "        canvas.drawText(\"$value/$safeMax\", x + barWidth, y + 13f, valuePaint)"
-          "" "        val top = y + 18f"
-          "        val rect = RectF(x, top, x + barWidth, top + 9f)"
-          "        canvas.drawRoundRect(rect, 5f, 5f, trackPaint)"
-          "        if (ratio > 0f) {"
-          "            fillPaint.color = barColor(ratio)"
-          "            canvas.drawRoundRect(RectF(rect.left, rect.top, rect.left + rect.width() * ratio, rect.bottom), 5f, 5f, fillPaint)"
-          "        }" "    }" ""
-          "    private fun drawEventFeed(canvas: Canvas, rect: RectF) {"
-          "        canvas.drawRoundRect(rect, 8f, 8f, panelPaint)"
-          "        canvas.drawRoundRect(rect, 8f, 8f, borderPaint)"
-          "        smallPaint.textAlign = Paint.Align.LEFT"
-          "        smallPaint.color = Color.rgb(165, 177, 195)"
-          "        canvas.drawText(\"TACTICAL LOG\", rect.left + 9f, rect.top + 18f, smallPaint)"
-          "" "        val lines = recentEventLines(3)"
-          "        if (lines.isEmpty()) {"
-          "            labelPaint.textAlign = Paint.Align.LEFT"
-          "            canvas.drawText(\"No combat events yet\", rect.left + 9f, rect.top + 42f, labelPaint)"
-          "        } else {" "            var y = rect.top + 40f"
-          "            lines.forEachIndexed { index, line ->"
-          "                if (y > rect.bottom - 6f) return@forEachIndexed"
-          "                if (index == lines.lastIndex) {"
-          "                    canvas.drawCircle(rect.left + 10f, y - 5f, 4f, accentPaint)"
-          "                }"
-          "                labelPaint.textAlign = Paint.Align.LEFT"
-          "                canvas.drawText(fitText(line, labelPaint, rect.width() - 28f), rect.left + 20f, y, labelPaint)"
-          "                y += 19f" "            }" "        }"
-          "        smallPaint.color = Color.rgb(165, 177, 195)"
-          "    }" ""
-          "    private fun recentEventLines(limit: Int): List<String> {"
-          "        val out = mutableListOf<String>()"
-          "        for (index in events.length() - 1 downTo 0) {"
-          "            val event = events.optJSONObject(index) ?: continue"
-          "            val type = event.optString(\"type\", \"\")"
-          "            if (type.isBlank() || type == \"impulse-activity\") continue"
-          "            out.add(0, summarizeEvent(event))"
-          "            if (out.size >= limit) break" "        }"
-          "        return out" "    }" ""
-          "    private fun summarizeEvent(event: JSONObject): String {"
-          "        val turn = event.optInt(\"turn\", -1)"
-          "        val impulse = event.optInt(\"impulse\", -1)"
-          "        val actor = event.optString(\"actor\", \"\")"
-          "        val type = event.optString(\"type\", \"event\")"
-          "        val payload = event.optJSONObject(\"payload\")"
-          "        val prefix = when {"
-          "            turn >= 0 && impulse >= 0 -> \"T$turn.$impulse\""
-          "            impulse >= 0 -> \"I$impulse\""
-          "            else -> \"LOG\"" "        }"
-          "        val actorText = if (actor.isBlank()) \"\" else \" ${actor.uppercase()}\""
-          "        val detail = when (type) {"
-          "            \"fire-weapons\" -> fireSummary(payload)"
-          "            \"seeking-launch\" -> launchSummary(payload)"
-          "            \"seeking-impact\" -> impactSummary(payload)"
-          "            \"movement\" -> movementSummary(payload)"
-          "            \"turn\", \"sideslip\", \"high-energy-turn\" -> movementSummary(payload)"
-          "            \"turn-blocked\", \"maneuver-blocked\", \"fire-control-blocked\" -> blockedSummary(payload)"
-          "            \"ew-control\", \"ew-support\", \"ew-support-rejected\", \"ew-support-lost\","
-          "            \"lock-on-control\", \"shield-control\", \"energy-allocation\", \"wild-swac\" -> controlSummary(payload)"
-          "            \"web-created\", \"web-reduced\", \"web-caster\", \"web-snare\", \"web-breaker\" -> webSummary(payload)"
-          "            \"turn-completed\" -> \"turn complete\""
-          "            \"orders-submitted\" -> \"orders committed\""
-          "            else -> payload?.optString(\"reason\", type)?.takeIf { it.isNotBlank() } ?: type"
-          "        }" "        return \"$prefix$actorText $detail\""
-          "    }" ""
-          "    private fun fireSummary(payload: JSONObject?): String {"
-          "        if (payload == null) return \"weapons fire\""
-          "        val summaries = payload.optJSONArray(\"fire-target-summaries\")"
-          "        if (summaries != null && summaries.length() > 0) {"
-          "            val parts = mutableListOf<String>()"
-          "            for (index in 0 until summaries.length()) {"
-          "                val summary = summaries.optJSONObject(index) ?: continue"
-          "                val attacker = sideLabel(summary.optString(\"attacker\", \"\"))"
-          "                val target = sideLabel(summary.optString(\"target\", \"\"))"
-          "                val damage = numberValue(summary.opt(\"damage\"))"
-          "                val internals = numberValue(summary.opt(\"internals\"))"
-          "                val face = shieldFaceLabel(summary.opt(\"shield-facing\"))"
-          "                val before = numberValue(summary.opt(\"shield-before\"))"
-          "                val after = numberValue(summary.opt(\"shield-after\"))"
-          "                if (damage <= 0 && internals <= 0) continue"
-          "                val shield = if (face.isBlank()) \"\" else \" $face $before>$after\""
-          "                val internalText = if (internals > 0) \" int $internals\" else \"\""
-          "                parts.add(\"$attacker>$target dmg $damage$shield$internalText\")"
-          "                if (parts.size >= 2) break" "            }"
-          "            if (parts.isNotEmpty()) return parts.joinToString(\"  \")"
-          "        }"
-          "        val alphaDamage = payload.optJSONObject(\"alpha-volley\")?.optInt(\"damage\", 0) ?: 0"
-          "        val betaDamage = payload.optJSONObject(\"beta-volley\")?.optInt(\"damage\", 0) ?: 0"
-          "        val range = payload.opt(\"range-before\") ?: payload.opt(\"range\") ?: \"-\""
-          "        return \"weapons fire R$range A:$alphaDamage B:$betaDamage\""
-          "    }" ""
-          "    private fun launchSummary(payload: JSONObject?): String {"
-          "        val seeker = payload?.optJSONObject(\"seeker\") ?: return \"seeker launch\""
-          "        val kind = seeker.optString(\"kind\", \"seeker\")"
-          "        val id = seeker.opt(\"id\") ?: seeker.opt(\"seeking-id\") ?: \"?\""
-          "        val target = seeker.optString(\"target\", \"?\")"
-          "        return \"launch $kind #$id to $target\"" "    }" ""
-          "    private fun impactSummary(payload: JSONObject?): String {"
-          "        if (payload == null) return \"seeker impact\""
-          "        val kind = payload.optString(\"kind\", payload.optString(\"seeker-kind\", \"seeker\"))"
-          "        val target = payload.optString(\"target\", \"?\")"
-          "        val damage = payload.opt(\"damage\") ?: \"-\""
-          "        val face = shieldFaceLabel(payload.opt(\"shield-facing\"))"
-          "        val before = numberValue(payload.opt(\"shield-before\"))"
-          "        val after = numberValue(payload.opt(\"shield-after\"))"
-          "        val shield = if (face.isBlank()) \"\" else \" $face $before>$after\""
-          "        return \"$kind impact $damage on $target$shield\""
-          "    }" ""
-          "    private fun movementSummary(payload: JSONObject?): String {"
-          "        val after = payload?.optJSONObject(\"position-after\") ?: return \"maneuver\""
-          "        return \"move q${after.optInt(\"q\")} r${after.optInt(\"r\")} f${after.optInt(\"facing\")}\""
-          "    }" ""
-          "    private fun blockedSummary(payload: JSONObject?): String {"
-          "        if (payload == null) return \"blocked\""
-          "        val maneuver = payload.optString(\"maneuver\", \"\").uppercase()"
-          "        val reason = payload.optString(\"reason\", \"blocked\").replace('-', ' ')"
-          "        return listOf(maneuver, reason).filter { it.isNotBlank() }.joinToString(\" \")"
-          "    }" ""
-          "    private fun controlSummary(payload: JSONObject?): String ="
-          "        payloadFieldSummary(" "            payload,"
-          "            listOf(\"mode\", \"reason\", \"ecm\", \"eccm\", \"ew-shift\", \"power\", \"reserve-power\", \"fire-control-active?\", \"shield\", \"target\")"
-          "        ).ifBlank { \"control\" }" ""
-          "    private fun webSummary(payload: JSONObject?): String ="
-          "        payloadFieldSummary(payload, listOf(\"strength\", \"web-strength\", \"web-hexes\", \"hexes\", \"damage\", \"range\", \"reason\"))"
-          "            .ifBlank { \"web action\" }" ""
-          "    private fun payloadFieldSummary(payload: JSONObject?, keys: List<String>): String {"
-          "        if (payload == null) return \"\""
-          "        val parts = mutableListOf<String>()"
-          "        keys.forEach { key ->"
-          "            if (!payload.has(key)) return@forEach"
-          "            val value = payload.opt(key)"
-          "            if (value == null || value == JSONObject.NULL) return@forEach"
-          "            parts.add(\"${fieldLabel(key)} ${compactPayloadValue(value)}\")"
-          "        }"
-          "        return parts.take(3).joinToString(\" \")" "    }"
-          "" "    private fun fieldLabel(key: String): String ="
-          "        key.removeSuffix(\"?\")"
-          "            .replace(\"-active\", \"\")"
-          "            .replace(\"shield-facing\", \"shield\")"
-          "            .replace('-', ' ')" "            .uppercase()"
-          ""
-          "    private fun compactPayloadValue(value: Any?): String ="
-          "        when (value) {" "            is JSONObject -> {"
-          "                val q = value.opt(\"q\")"
-          "                val r = value.opt(\"r\")"
-          "                val facing = value.opt(\"facing\")"
-          "                if (q != null && r != null) \"q$q/r$r/f${facing ?: \"-\"}\" else \"{${value.length()}}\""
-          "            }"
-          "            is JSONArray -> value.length().toString()"
-          "            is Boolean -> if (value) \"Y\" else \"N\""
-          "            else -> value.toString().replace('-', ' ')"
-          "        }" ""
-          "    private fun maneuverPlotText(raw: Any?, currentImpulse: Int, limit: Int): String {"
-          "        val entries = maneuverEntries(raw)"
-          "            .filter { it.first > currentImpulse && it.second.isNotBlank() }"
-          "            .sortedBy { it.first }"
-          "            .take(limit)"
-          "        if (entries.isEmpty()) return \"CLEAR\""
-          "        return entries.joinToString(\"/\") { (impulse, maneuver) ->"
-          "            \"I$impulse ${maneuverAbbrev(maneuver)}\""
-          "        }" "    }" ""
-          "    private fun rangeForecastLine(battle: JSONObject, orders: JSONObject, currentImpulse: Int): String {"
-          "        val forecast = projectedRangeForecast("
-          "            battle.optJSONObject(\"alpha-position\"),"
-          "            battle.optJSONObject(\"beta-position\"),"
-          "            orders.opt(\"alpha-maneuvers\"),"
-          "            orders.opt(\"beta-maneuvers\"),"
-          "            currentImpulse," "            maxMoves = 8,"
-          "            labelLimit = 5" "        )"
-          "        if (forecast.isEmpty()) return \"\""
-          "        val currentRange = hexRangeFromPositions("
-          "            battle.optJSONObject(\"alpha-position\"),"
-          "            battle.optJSONObject(\"beta-position\")"
-          "        )" "        val finalRange = forecast.last().range"
-          "        val trend = when {"
-          "            currentRange == null -> \"FORECAST\""
-          "            finalRange < currentRange -> \"CLOSING\""
-          "            finalRange > currentRange -> \"OPENING\""
-          "            else -> \"RANGE\"" "        }"
-          "        val points = forecast.take(4).joinToString(\"  \") { \"I${it.impulse} R${it.range}\" }"
-          "        return \"$trend $points\"" "    }" ""
-          "    private fun maneuverEntries(raw: Any?): List<Pair<Int, String>> {"
-          "        val entries = mutableListOf<Pair<Int, String>>()"
-          "        when (raw) {" "            is JSONArray -> {"
-          "                for (index in 0 until raw.length()) {"
-          "                    val item = raw.opt(index)"
-          "                    when (item) {"
-          "                        is JSONObject -> {"
-          "                            val pair = item.optJSONArray(\"\\$pair\")"
-          "                            if (pair != null) {"
-          "                                val impulse = pair.optInt(0, -1)"
-          "                                val maneuver = pair.optString(1, \"\")"
-          "                                if (impulse > 0 && maneuver.isNotBlank()) entries.add(impulse to maneuver)"
-          "                            }" "                        }"
-          "                        is JSONArray -> {"
-          "                            val impulse = item.optInt(0, -1)"
-          "                            val maneuver = item.optString(1, \"\")"
-          "                            if (impulse > 0 && maneuver.isNotBlank()) entries.add(impulse to maneuver)"
-          "                        }" "                    }"
-          "                }" "            }"
-          "            is JSONObject -> {"
-          "                val keys = raw.keys()"
-          "                while (keys.hasNext()) {"
-          "                    val key = keys.next()"
-          "                    val impulse = key.toIntOrNull() ?: continue"
-          "                    val maneuver = raw.optString(key, \"\")"
-          "                    if (impulse > 0 && maneuver.isNotBlank()) entries.add(impulse to maneuver)"
-          "                }" "            }" "        }"
-          "        return entries" "    }" ""
-          "    private fun maneuverAbbrev(maneuver: String): String ="
-          "        maneuverAbbreviation(maneuver)" ""
-          "    private fun maxInternals(ship: JSONObject?, current: Int): Int {"
-          "        val explicit = ship?.optInt(\"max-internals\", 0) ?: 0"
-          "        if (explicit > 0) return explicit"
-          "        return max(1, current + counterTotal(ship?.optJSONObject(\"destroyed-boxes\")))"
-          "    }" ""
-          "    private fun shieldTotal(ship: JSONObject?, maxValues: Boolean): Int {"
-          "        val preferred = if (maxValues) ship?.optJSONObject(\"max-shields\") else ship?.optJSONObject(\"shields\")"
-          "        val fallback = ship?.optJSONObject(\"shields\")"
-          "        return counterTotal(preferred ?: fallback)" "    }"
-          "" "    private fun paPanelMax(ship: JSONObject?): Int {"
-          "        val current = counterValue(ship?.opt(\"boxes\"), \"52\")"
-          "        val destroyed = counterValue(ship?.opt(\"destroyed-boxes\"), \"52\")"
-          "        return max(1, current + destroyed)" "    }" ""
-          "    private fun counterTotal(counter: Any?): Int {"
-          "        var total = 0"
-          "        forEachCounterPair(counter) { _, value ->"
-          "            total += numberValue(value).coerceAtLeast(0)"
-          "        }" "        return total" "    }" ""
-          "    private fun counterValue(counter: Any?, wantedKey: String): Int {"
-          "        var total = 0"
-          "        forEachCounterPair(counter) { key, value ->"
-          "            if (key == wantedKey) total += numberValue(value).coerceAtLeast(0)"
-          "        }" "        return total" "    }" ""
-          "    private fun forEachCounterPair(counter: Any?, action: (String, Any?) -> Unit) {"
-          "        when (counter) {" "            is JSONObject -> {"
-          "                val pair = counter.optJSONArray(PAIR_MARKER)"
-          "                if (pair != null) {"
-          "                    action(pair.opt(0)?.toString() ?: \"\", pair.opt(1))"
-          "                } else {"
-          "                    val keys = counter.keys()"
-          "                    while (keys.hasNext()) {"
-          "                        val key = keys.next()"
-          "                        action(key, counter.opt(key))"
-          "                    }" "                }" "            }"
-          "            is JSONArray -> {"
-          "                for (index in 0 until counter.length()) {"
-          "                    val pair = counter.optJSONObject(index)?.optJSONArray(PAIR_MARKER) ?: continue"
-          "                    action(pair.opt(0)?.toString() ?: \"\", pair.opt(1))"
-          "                }" "            }" "        }" "    }" ""
-          "    private fun numberValue(value: Any?): Int ="
-          "        when (value) {"
-          "            is Number -> value.toInt()"
-          "            is String -> value.toIntOrNull() ?: 0"
-          "            else -> 0" "        }" ""
-          "    private fun sideLabel(value: String): String ="
-          "        when (value.lowercase()) {"
-          "            \"alpha\" -> \"A\""
-          "            \"beta\" -> \"B\""
-          "            else -> value.uppercase().take(6)" "        }"
-          "" "    private fun shieldFaceLabel(value: Any?): String {"
-          "        val text = value?.toString() ?: return \"\""
-          "        if (text == \"#f\" || text == \"false\") return \"\""
-          "        if (text == \"all\") return \"ALL\""
-          "        val number = text.removePrefix(\"shield-\").toIntOrNull() ?: return text.uppercase()"
-          "        return \"SH$number\"" "    }" ""
-          "    private fun actorFireSolution(actor: JSONObject?): FireSolution {"
-          "        val weapons = actor?.optJSONArray(\"weapon-status\") ?: return FireSolution()"
-          "        var installed = 0" "        var armed = 0"
-          "        var ready = 0" "        var inArc = 0"
-          "        for (index in 0 until weapons.length()) {"
-          "            val weapon = weapons.optJSONObject(index) ?: continue"
-          "            installed += weapon.optInt(\"installed\", 0).coerceAtLeast(0)"
-          "            armed += weapon.optInt(\"armed\", 0).coerceAtLeast(0)"
-          "            ready += weapon.optInt(\"fireable\", 0).coerceAtLeast(0)"
-          "            inArc += weapon.optInt(\"fireable-in-arc\", 0).coerceAtLeast(0)"
-          "        }"
-          "        return FireSolution(installed, armed, ready, inArc)"
-          "    }" ""
-          "    private fun primaryWeaponLine(actor: JSONObject?): String {"
-          "        val weapons = actor?.optJSONArray(\"weapon-status\") ?: return \"\""
-          "        var best: JSONObject? = null"
-          "        for (index in 0 until weapons.length()) {"
-          "            val weapon = weapons.optJSONObject(index) ?: continue"
-          "            val current = best"
-          "            if (current == null || weaponScore(weapon) > weaponScore(current)) {"
-          "                best = weapon" "            }" "        }"
-          "        val weapon = best ?: return \"\""
-          "        val kind = shortWeapon(weapon.optString(\"kind\", \"-\"))"
-          "        val arc = compactArray(weapon.optJSONArray(\"arc-codes\"), 2)"
-          "        return \"$kind ${weapon.optInt(\"fireable-in-arc\", 0)}/${weapon.optInt(\"armed\", 0)}${if (arc.isBlank()) \"\" else \" $arc\"}\""
-          "    }" ""
-          "    private fun weaponFamilyLine(actor: JSONObject?): String {"
-          "        val weapons = actor?.optJSONArray(\"weapon-status\") ?: return \"\""
-          "        val entries = mutableListOf<JSONObject>()"
-          "        for (index in 0 until weapons.length()) {"
-          "            val weapon = weapons.optJSONObject(index) ?: continue"
-          "            val installed = weapon.optInt(\"installed\", 0).coerceAtLeast(0)"
-          "            val armed = weapon.optInt(\"armed\", 0).coerceAtLeast(0)"
-          "            val fireable = weapon.optInt(\"fireable\", 0).coerceAtLeast(0)"
-          "            val cooldown = weapon.optInt(\"cooldown\", 0).coerceAtLeast(0)"
-          "            if (installed + armed + fireable + cooldown > 0) entries.add(weapon)"
-          "        }" "        return entries"
-          "            .sortedWith(compareByDescending<JSONObject> { weaponScore(it) }.thenBy { it.optString(\"kind\", \"\") })"
-          "            .take(3)"
-          "            .joinToString(\"  \") { weapon ->"
-          "                val kind = shortWeapon(weapon.optString(\"kind\", \"-\"))"
-          "                val fireable = weapon.optInt(\"fireable-in-arc\", weapon.optInt(\"fireable\", 0)).coerceAtLeast(0)"
-          "                val armed = weapon.optInt(\"armed\", 0).coerceAtLeast(0)"
-          "                val installed = weapon.optInt(\"installed\", 0).coerceAtLeast(0)"
-          "                val cooldown = weapon.optInt(\"cooldown\", 0).coerceAtLeast(0)"
-          "                val cooldownText = if (cooldown > 0) \" cd$cooldown\" else \"\""
-          "                \"$kind $fireable/$armed/$installed$cooldownText\""
-          "            }" "    }" ""
-          "    private fun weaponScore(weapon: JSONObject): Int ="
-          "        weapon.optInt(\"fireable-in-arc\", 0) * 1000 +"
-          "            weapon.optInt(\"fireable\", 0) * 100 +"
-          "            weapon.optInt(\"armed\", 0) * 10 +"
-          "            weapon.optInt(\"installed\", 0)" ""
-          "    private fun shortWeapon(kind: String): String ="
-          "        kind.replace(\"phaser-\", \"PH-\")"
-          "            .replace(\"photon\", \"PHOT\")"
-          "            .replace(\"disruptor\", \"DISR\")"
-          "            .replace(\"plasma-\", \"PL-\")"
-          "            .replace(\"drone-\", \"DR-\")"
-          "            .uppercase()" ""
-          "    private fun compactArray(array: JSONArray?, limit: Int): String {"
-          "        if (array == null || array.length() == 0) return \"\""
-          "        val out = mutableListOf<String>()"
-          "        for (index in 0 until minOf(array.length(), limit)) {"
-          "            out.add(array.opt(index).toString())"
-          "        }"
-          "        if (array.length() > limit) out.add(\"+${array.length() - limit}\")"
-          "        return out.joinToString(\"/\")" "    }" ""
-          "    private fun barColor(ratio: Float): Int ="
-          "        when {"
-          "            ratio > 0.66f -> Color.rgb(70, 167, 106)"
-          "            ratio > 0.33f -> Color.rgb(230, 155, 63)"
-          "            else -> Color.rgb(215, 92, 72)" "        }" ""
-          "    private fun fitText(text: String, paint: Paint, maxWidth: Float): String {"
-          "        if (paint.measureText(text) <= maxWidth) return text"
-          "        var candidate = text"
-          "        while (candidate.length > 4 && paint.measureText(\"$candidate...\") > maxWidth) {"
-          "            candidate = candidate.dropLast(1)" "        }"
-          "        return if (candidate.length <= 4) candidate else \"$candidate...\""
-          "    }" "" "    private data class FireSolution("
-          "        val installed: Int = 0,"
-          "        val armed: Int = 0," "        val ready: Int = 0,"
-          "        val inArc: Int = 0" "    )" ""
-          "    companion object {"
-          "        private const val PAIR_MARKER = \"$\" + \"pair\""
-          "    }" "}"))
-        (kotlin-file-lines
-          "com/jerboa/originaltactics/BridgeHudView.kt"
-          ("package com.jerboa.originaltactics" ""
-           "import android.content.Context"
-           "import android.graphics.Canvas"
-           "import android.graphics.Color"
-           "import android.graphics.Paint"
-           "import android.graphics.RectF" "import android.view.View"
-           "import org.json.JSONArray" "import org.json.JSONObject"
-           "import kotlin.math.max" ""
-           "class BridgeHudView(context: Context) : View(context) {"
-           "    private var game: JSONObject? = null"
-           "    private var dashboard: JSONObject? = null"
-           "    private var events: JSONArray = JSONArray()"
-           "    private var drawWidth = 0f"
-           "    private var drawHeight = 0f" ""
-           "    private val backgroundPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {"
-           "        color = Color.rgb(8, 13, 21)"
-           "        style = Paint.Style.FILL" "    }"
-           "    private val panelPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {"
-           "        color = Color.rgb(18, 27, 40)"
-           "        style = Paint.Style.FILL" "    }"
-           "    private val panelAltPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {"
-           "        color = Color.rgb(23, 34, 49)"
-           "        style = Paint.Style.FILL" "    }"
-           "    private val borderPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {"
-           "        color = Color.rgb(80, 102, 132)"
-           "        style = Paint.Style.STROKE"
-           "        strokeWidth = 2f" "    }"
-           "    private val alphaPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {"
-           "        color = Color.rgb(54, 139, 232)"
-           "        style = Paint.Style.FILL" "    }"
-           "    private val betaPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {"
-           "        color = Color.rgb(217, 84, 69)"
-           "        style = Paint.Style.FILL" "    }"
-           "    private val readyPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {"
-           "        color = Color.rgb(76, 181, 114)"
-           "        style = Paint.Style.FILL" "    }"
-           "    private val warningPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {"
-           "        color = Color.rgb(224, 161, 69)"
-           "        style = Paint.Style.FILL" "    }"
-           "    private val dangerPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {"
-           "        color = Color.rgb(191, 68, 62)"
-           "        style = Paint.Style.FILL" "    }"
-           "    private val trackPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {"
-           "        color = Color.rgb(50, 63, 83)"
-           "        style = Paint.Style.FILL" "    }"
-           "    private val titlePaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {"
-           "        color = Color.WHITE" "        textSize = 25f"
-           "        isFakeBoldText = true" "    }"
-           "    private val valuePaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {"
-           "        color = Color.WHITE" "        textSize = 20f"
-           "        isFakeBoldText = true" "    }"
-           "    private val labelPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {"
-           "        color = Color.rgb(202, 214, 232)"
-           "        textSize = 17f" "    }"
-           "    private val smallPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {"
-           "        color = Color.rgb(145, 162, 188)"
-           "        textSize = 15f" "    }"
-           "    private val shieldTextPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {"
-           "        color = Color.WHITE" "        textSize = 12f"
-           "        isFakeBoldText = true"
-           "        textAlign = Paint.Align.CENTER" "    }"
-           "    private val shieldHitPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {"
-           "        color = Color.rgb(255, 229, 120)"
-           "        style = Paint.Style.STROKE"
-           "        strokeWidth = 2.4f" "    }" ""
-           "    fun setData(nextGame: JSONObject?, nextDashboard: JSONObject?, nextEvents: JSONArray = JSONArray()) {"
-           "        game = nextGame"
-           "        dashboard = nextDashboard"
-           "        events = nextEvents" "        invalidate()" "    }"
-           "" "    override fun onDraw(canvas: Canvas) {"
-           "        super.onDraw(canvas)"
-           "        val canvasState = canvas.save()"
-           "        val scale = resources.displayMetrics.density.coerceAtLeast(1f)"
-           "        drawWidth = width.toFloat() / scale"
-           "        drawHeight = height.toFloat() / scale"
-           "        canvas.scale(scale, scale)" "        try {"
-           "            drawContent(canvas)" "        } finally {"
-           "            canvas.restoreToCount(canvasState)" "        }"
-           "    }" "" "    private fun drawContent(canvas: Canvas) {"
-           "        canvas.drawRect(0f, 0f, drawWidth, drawHeight, backgroundPaint)"
-           "        val current = game"
-           "        val battle = current?.optJSONObject(\"battle-state\")"
-           "        if (current == null || battle == null) {"
-           "            drawEmpty(canvas)" "            return"
-           "        }" ""
-           "        val status = dashboard?.optJSONObject(\"status\")"
-           "        drawHeader(canvas, current, battle, status)" ""
-           "        val gap = 9f" "        val top = 38f"
-           "        val bottom = drawHeight - 8f"
-           "        if (drawWidth < 520f) {"
-           "            val shipBottom = top + (bottom - top) * 0.62f"
-           "            val cardWidth = (drawWidth - gap * 3f) / 2f"
-           "            drawShipBlock(" "                canvas,"
-           "                RectF(gap, top, gap + cardWidth, shipBottom),"
-           "                \"ALPHA\","
-           "                battle.optJSONObject(\"alpha\"),"
-           "                status?.optJSONObject(\"alpha\"),"
-           "                shieldFacingIndex(battle.opt(\"alpha-shield-facing-hit\")),"
-           "                alphaPaint" "            )"
-           "            drawShipBlock(" "                canvas,"
-           "                RectF(gap * 2f + cardWidth, top, gap * 2f + cardWidth * 2f, shipBottom),"
-           "                \"BETA\","
-           "                battle.optJSONObject(\"beta\"),"
-           "                status?.optJSONObject(\"beta\"),"
-           "                shieldFacingIndex(battle.opt(\"beta-shield-facing-hit\")),"
-           "                betaPaint" "            )"
-           "            drawCenterBlock(canvas, RectF(gap, shipBottom + gap, drawWidth - gap, bottom), current, battle, status)"
-           "        } else {"
-           "            val leftWidth = drawWidth * 0.36f"
-           "            val rightWidth = drawWidth * 0.36f"
-           "            val centerLeft = gap + leftWidth + gap"
-           "            val centerRight = drawWidth - gap - rightWidth - gap"
-           "            drawShipBlock(" "                canvas,"
-           "                RectF(gap, top, gap + leftWidth, bottom),"
-           "                \"ALPHA\","
-           "                battle.optJSONObject(\"alpha\"),"
-           "                status?.optJSONObject(\"alpha\"),"
-           "                shieldFacingIndex(battle.opt(\"alpha-shield-facing-hit\")),"
-           "                alphaPaint" "            )"
-           "            drawCenterBlock(canvas, RectF(centerLeft, top, centerRight, bottom), current, battle, status)"
-           "            drawShipBlock(" "                canvas,"
-           "                RectF(drawWidth - gap - rightWidth, top, drawWidth - gap, bottom),"
-           "                \"BETA\","
-           "                battle.optJSONObject(\"beta\"),"
-           "                status?.optJSONObject(\"beta\"),"
-           "                shieldFacingIndex(battle.opt(\"beta-shield-facing-hit\")),"
-           "                betaPaint" "            )" "        }"
-           "    }" "" "    private fun drawEmpty(canvas: Canvas) {"
-           "        titlePaint.textAlign = Paint.Align.CENTER"
-           "        canvas.drawText(\"TACTICAL COMMAND\", drawWidth / 2f, drawHeight / 2f - 5f, titlePaint)"
-           "        smallPaint.textAlign = Paint.Align.CENTER"
-           "        canvas.drawText(\"No active engagement\", drawWidth / 2f, drawHeight / 2f + 21f, smallPaint)"
-           "    }" ""
-           "    private fun drawHeader(canvas: Canvas, current: JSONObject, battle: JSONObject, status: JSONObject?) {"
-           "        val turn = current.optInt(\"turn\", status?.optInt(\"turn\", 0) ?: 0)"
-           "        val impulse = current.optInt(\"impulse\", status?.optInt(\"impulse\", 0) ?: 0)"
-           "        val range = currentBattleRange(battle).ifBlank { (status?.opt(\"range\") ?: \"-\").toString() }"
-           "        val winner = current.optString(\"winner\", status?.optString(\"winner\", \"undecided\") ?: \"undecided\")"
-           "        val outcome = if (winner == \"undecided\") \"NO DECISION\" else \"${winner.uppercase()} WINS\""
-           "" "        titlePaint.textAlign = Paint.Align.LEFT"
-           "        canvas.drawText(\"TACTICAL COMMAND\", 10f, 26f, titlePaint)"
-           "        valuePaint.textAlign = Paint.Align.CENTER"
-           "        canvas.drawText(\"T$turn.$impulse  RANGE $range\", drawWidth / 2f, 25f, valuePaint)"
-           "        valuePaint.textAlign = Paint.Align.RIGHT"
-           "        canvas.drawText(fitText(outcome, valuePaint, drawWidth * 0.34f), drawWidth - 10f, 25f, valuePaint)"
-           "    }" "" "    private fun drawShipBlock("
-           "        canvas: Canvas," "        rect: RectF,"
-           "        side: String," "        ship: JSONObject?,"
-           "        statusShip: JSONObject?," "        hitShield: Int,"
-           "        sidePaint: Paint" "    ) {"
-           "        canvas.drawRoundRect(rect, 8f, 8f, panelPaint)"
-           "        canvas.drawRoundRect(rect, 8f, 8f, borderPaint)"
-           "        canvas.drawRoundRect(RectF(rect.left, rect.top, rect.right, rect.top + 25f), 8f, 8f, sidePaint)"
-           "" "        valuePaint.textAlign = Paint.Align.LEFT"
-           "        canvas.drawText(side, rect.left + 8f, rect.top + 18f, valuePaint)"
-           "        smallPaint.textAlign = Paint.Align.RIGHT"
-           "        canvas.drawText(fitText(shipId(ship, statusShip), smallPaint, rect.width() * 0.46f), rect.right - 8f, rect.top + 17f, smallPaint)"
-           "" "        var y = rect.top + 45f"
-           "        labelPaint.textAlign = Paint.Align.LEFT"
-           "        canvas.drawText(fitText(shipName(ship, statusShip), labelPaint, rect.width() - 16f), rect.left + 8f, y, labelPaint)"
-           "        y += 19f" ""
-           "        val movement = statusShip?.optJSONObject(\"movement\")"
-           "        val power = statusShip?.optJSONObject(\"power-status\")"
-           "        val speed = movement?.opt(\"current-speed\") ?: power?.opt(\"planned-speed\") ?: \"-\""
-           "        val planned = movement?.opt(\"planned-speed\") ?: \"-\""
-           "        val powerText = \"SPD $speed/$planned  PWR ${power?.optInt(\"unspent-energy\", 0) ?: 0}/${power?.optInt(\"total-power\", 0) ?: 0}\""
-           "        smallPaint.textAlign = Paint.Align.LEFT"
-           "        canvas.drawText(fitText(powerText, smallPaint, rect.width() - 16f), rect.left + 8f, y, smallPaint)"
-           "        y += 20f" ""
-           "        val hullNow = ship?.optInt(\"remaining-internals\", statusShip?.optInt(\"remaining-internals\", 0) ?: 0) ?: 0"
-           "        val hullMax = max(hullNow, counterTotal(ship?.opt(\"boxes\")))"
-           "        if (rect.height() < 118f) {"
-           "            val armed = counterTotal(ship?.optJSONObject(\"armed-weapons\"))"
-           "            smallPaint.textAlign = Paint.Align.LEFT"
-           "            canvas.drawText(fitText(\"INT $hullNow  ${defenseSummary(ship, statusShip)}  WPN $armed\", smallPaint, rect.width() - 16f), rect.left + 8f, y, smallPaint)"
-           "            return" "        }"
-           "        drawBar(canvas, rect.left + 8f, y, rect.width() - 16f, \"HULL\", hullNow, max(1, hullMax))"
-           "        y += 29f" ""
-           "        drawShieldStrip(canvas, rect.left + 8f, y, rect.width() - 16f, ship, statusShip, hitShield)"
-           "        y += 29f" "" "        val weaponTotal = max("
-           "            counterTotal(ship?.optJSONObject(\"weapons\")),"
-           "            weaponInstalledTotal(statusShip?.optJSONArray(\"weapon-status\"))"
-           "        )"
-           "        val armed = counterTotal(ship?.optJSONObject(\"armed-weapons\"))"
-           "        drawBar(canvas, rect.left + 8f, y, rect.width() - 16f, \"ARMED\", armed, max(1, weaponTotal))"
-           "        y += 29f" ""
-           "        val weaponText = weaponFamilySummary(statusShip?.optJSONArray(\"weapon-status\"))"
-           "        if (weaponText.isNotBlank() && y <= rect.bottom - 8f) {"
-           "            smallPaint.textAlign = Paint.Align.LEFT"
-           "            canvas.drawText(fitText(\"WPN $weaponText\", smallPaint, rect.width() - 16f), rect.left + 8f, y + 12f, smallPaint)"
-           "        }" "    }" ""
-           "    private fun drawCenterBlock(canvas: Canvas, rect: RectF, current: JSONObject, battle: JSONObject, status: JSONObject?) {"
-           "        canvas.drawRoundRect(rect, 8f, 8f, panelAltPaint)"
-           "        canvas.drawRoundRect(rect, 8f, 8f, borderPaint)" ""
-           "        val compact = rect.height() < 72f"
-           "        val orders = current.optJSONObject(\"orders\")"
-           "        val fireImpulse = orders?.opt(\"fire-impulse\") ?: \"-\""
-           "        val fireText = if (current.optBoolean(\"fired?\", false)) \"FIRE RESOLVED\" else \"FIRE I$fireImpulse\""
-           "        val phaseText = phaseSlotSummary(dashboard?.optJSONObject(\"impulse-activity\")?.optJSONArray(\"stage-slots\"))"
-           "        valuePaint.textAlign = Paint.Align.CENTER"
-           "        canvas.drawText(fitText(fireText, valuePaint, rect.width() - 12f), rect.centerX(), rect.top + if (compact) 18f else 24f, valuePaint)"
-           "" "        val counts = status?.optJSONObject(\"counts\")"
-           "        val seekers = counts?.optInt(\"active-seeking\", countUnits(battle.opt(\"seeking-weapons\"))) ?: countUnits(battle.opt(\"seeking-weapons\"))"
-           "        val smallUnits = counts?.optInt(\"active-small-units\", countUnits(battle.opt(\"shuttles\"))) ?: countUnits(battle.opt(\"shuttles\"))"
-           "        val webs = counts?.optInt(\"active-webs\", 0) ?: 0"
-           "        val mines = counts?.optInt(\"active-mines\", 0) ?: 0"
-           "        val hidden = counts?.optInt(\"detected-hidden-units\", 0) ?: 0"
-           "" "        var y = rect.top + if (compact) 38f else 49f"
-           "        if (!compact && phaseText.isNotBlank()) {"
-           "            smallPaint.textAlign = Paint.Align.CENTER"
-           "            canvas.drawText(fitText(phaseText, smallPaint, rect.width() - 14f), rect.centerX(), y, smallPaint)"
-           "            y += 18f" "        }" "        if (!compact) {"
-           "            val commandText = \"CMD A ${availableActionCount(\"alpha\")}/${totalActionCount(\"alpha\")}   B ${availableActionCount(\"beta\")}/${totalActionCount(\"beta\")}\""
-           "            smallPaint.textAlign = Paint.Align.CENTER"
-           "            canvas.drawText(fitText(commandText, smallPaint, rect.width() - 14f), rect.centerX(), y, smallPaint)"
-           "            y += 18f" "        }" "        if (compact) {"
-           "            val countText = \"SEEK $seekers   UNITS $smallUnits   WEB $webs   MINES $mines   HIDDEN $hidden\""
-           "            labelPaint.textAlign = Paint.Align.CENTER"
-           "            canvas.drawText(fitText(countText, labelPaint, rect.width() - 14f), rect.centerX(), y, labelPaint)"
-           "            if (rect.height() < 58f) return"
-           "            y += 18f" "        } else {"
-           "            y = drawCountPill(canvas, rect, y, \"SEEK\", seekers, if (seekers > 0) warningPaint else readyPaint)"
-           "            y = drawCountPill(canvas, rect, y, \"UNITS\", smallUnits, if (smallUnits > 0) warningPaint else readyPaint)"
-           "            y = drawCountPill(canvas, rect, y, \"WEB\", webs, if (webs > 0) warningPaint else readyPaint)"
-           "            y = drawCountPill(canvas, rect, y, \"MINES\", mines, if (mines > 0) dangerPaint else readyPaint)"
-           "            y = drawCountPill(canvas, rect, y, \"HIDDEN\", hidden, if (hidden > 0) dangerPaint else readyPaint)"
-           "        }" ""
-           "        val eventText = latestEventText(status?.optJSONArray(\"recent-events\") ?: events)"
-           "        smallPaint.textAlign = Paint.Align.CENTER"
-           "        val eventY = if (compact) rect.bottom - 10f else minOf(rect.bottom - 12f, y + 10f)"
-           "        canvas.drawText(fitText(eventText, smallPaint, rect.width() - 14f), rect.centerX(), eventY, smallPaint)"
-           "    }" ""
-           "    private fun drawCountPill(canvas: Canvas, rect: RectF, y: Float, label: String, value: Int, paint: Paint): Float {"
-           "        val pill = RectF(rect.left + 8f, y - 14f, rect.right - 8f, y + 8f)"
-           "        canvas.drawRoundRect(pill, 8f, 8f, trackPaint)"
-           "        val accent = RectF(pill.left, pill.top, pill.left + 5f, pill.bottom)"
-           "        canvas.drawRoundRect(accent, 8f, 8f, paint)"
-           "        labelPaint.textAlign = Paint.Align.LEFT"
-           "        canvas.drawText(label, pill.left + 11f, y + 2f, labelPaint)"
-           "        valuePaint.textAlign = Paint.Align.RIGHT"
-           "        canvas.drawText(value.toString(), pill.right - 8f, y + 2f, valuePaint)"
-           "        return y + 26f" "    }" ""
-           "    private fun drawBar(canvas: Canvas, x: Float, y: Float, width: Float, label: String, value: Int, maxValue: Int) {"
-           "        val safeMax = max(1, maxValue)"
-           "        val ratio = (value.toFloat() / safeMax.toFloat()).coerceIn(0f, 1f)"
-           "        smallPaint.textAlign = Paint.Align.LEFT"
-           "        canvas.drawText(label, x, y + 12f, smallPaint)"
-           "        valuePaint.textAlign = Paint.Align.RIGHT"
-           "        canvas.drawText(\"$value/$safeMax\", x + width, y + 12f, valuePaint)"
-           "        val bar = RectF(x, y + 16f, x + width, y + 25f)"
-           "        canvas.drawRoundRect(bar, 5f, 5f, trackPaint)"
-           "        val fillPaint = when {"
-           "            ratio > 0.55f -> readyPaint"
-           "            ratio > 0.25f -> warningPaint"
-           "            else -> dangerPaint" "        }"
-           "        canvas.drawRoundRect(RectF(bar.left, bar.top, bar.left + bar.width() * ratio, bar.bottom), 5f, 5f, fillPaint)"
-           "    }" "" "    private fun drawShieldStrip("
-           "        canvas: Canvas," "        x: Float,"
-           "        y: Float," "        width: Float,"
-           "        ship: JSONObject?,"
-           "        statusShip: JSONObject?," "        hitShield: Int"
-           "    ) {"
-           "        val shields = ship?.optJSONObject(\"shields\") ?: statusShip?.optJSONObject(\"shields\")"
-           "        val maxShields = ship?.optJSONObject(\"max-shields\") ?: statusShip?.optJSONObject(\"max-shields\")"
-           "        val totalNow = objectTotal(shields)"
-           "        val totalMax = max(totalNow, objectTotal(maxShields))"
-           "        smallPaint.textAlign = Paint.Align.LEFT"
-           "        valuePaint.textAlign = Paint.Align.RIGHT" ""
-           "        if (totalMax <= 0) {"
-           "            val paPanels = paPanelCount(ship, statusShip)"
-           "            val paMax = paPanelMax(ship, statusShip)"
-           "            val ratio = (paPanels.toFloat() / paMax.toFloat()).coerceIn(0f, 1f)"
-           "            canvas.drawText(\"DEFENSE\", x, y + 12f, smallPaint)"
-           "            canvas.drawText(if (paPanels > 0 || paMax > 1) \"PA $paPanels/$paMax\" else \"NO SHIELDS\", x + width, y + 12f, valuePaint)"
-           "            val bar = RectF(x, y + 16f, x + width, y + 25f)"
-           "            canvas.drawRoundRect(bar, 5f, 5f, trackPaint)"
-           "            if (ratio > 0f) {"
-           "                canvas.drawRoundRect(RectF(bar.left, bar.top, bar.left + bar.width() * ratio, bar.bottom), 5f, 5f, warningPaint)"
-           "                shieldTextPaint.textSize = 11f"
-           "                canvas.drawText(\"PA PANELS\", bar.centerX(), y + 24f, shieldTextPaint)"
-           "            }" "            return" "        }" ""
-           "        canvas.drawText(\"SHIELDS\", x, y + 12f, smallPaint)"
-           "        canvas.drawText(\"$totalNow/$totalMax\", x + width, y + 12f, valuePaint)"
-           "" "        val gap = 2f"
-           "        val cellWidth = (width - gap * 5f) / 6f"
-           "        val top = y + 16f" "        val bottom = y + 25f"
-           "        shieldTextPaint.textSize = if (cellWidth >= 34f) 11f else 9f"
-           "        for (index in 0 until 6) {"
-           "            val shieldNumber = index + 1"
-           "            val key = \"shield-$shieldNumber\""
-           "            val now = numberValue(shields?.opt(key)).coerceAtLeast(0)"
-           "            val maxValue = max(now, numberValue(maxShields?.opt(key)).coerceAtLeast(0))"
-           "            val ratio = if (maxValue > 0) (now.toFloat() / maxValue.toFloat()).coerceIn(0f, 1f) else 0f"
-           "            val left = x + index * (cellWidth + gap)"
-           "            val cell = RectF(left, top, left + cellWidth, bottom)"
-           "            canvas.drawRoundRect(cell, 3f, 3f, trackPaint)"
-           "            if (maxValue > 0 && ratio > 0f) {"
-           "                val fillPaint = when {"
-           "                    ratio > 0.55f -> readyPaint"
-           "                    ratio > 0.25f -> warningPaint"
-           "                    else -> dangerPaint"
-           "                }"
-           "                canvas.drawRoundRect(RectF(cell.left, cell.top, cell.left + cell.width() * ratio, cell.bottom), 3f, 3f, fillPaint)"
-           "            }"
-           "            if (hitShield == shieldNumber) {"
-           "                canvas.drawRoundRect(RectF(cell.left - 1f, cell.top - 2f, cell.right + 1f, cell.bottom + 2f), 4f, 4f, shieldHitPaint)"
-           "            }" "            if (cellWidth >= 24f) {"
-           "                val label = if (cellWidth >= 38f) \"$shieldNumber:$now\" else shieldNumber.toString()"
-           "                canvas.drawText(label, cell.centerX(), bottom - 1f, shieldTextPaint)"
-           "            }" "        }" "    }" ""
-           "    private fun phaseSlotSummary(slots: JSONArray?): String {"
-           "        if (slots == null || slots.length() == 0) return \"\""
-           "        val names = mutableListOf<String>()"
-           "        for (index in 0 until slots.length()) {"
-           "            val slot = slots.optJSONObject(index) ?: continue"
-           "            if (!slot.optBoolean(\"implemented?\", false)) continue"
-           "            val action = slot.optString(\"action\", \"\").ifBlank { slot.optString(\"engine-hook\", \"\") }"
-           "                .replace(\"Launch \", \"Ln \")"
-           "                .replace(\"Fire Weapons\", \"Fire\")"
-           "                .replace(\"Announce \", \"\")"
-           "            if (action.isNotBlank() && !names.contains(action)) names.add(action)"
-           "            if (names.size >= 3) break" "        }"
-           "        return names.joinToString(\" | \")" "    }" ""
-           "    private fun availableActionCount(actor: String): Int {"
-           "        val actions = dashboard?.optJSONObject(\"actions\")?.optJSONArray(actor) ?: return 0"
-           "        var count = 0"
-           "        for (index in 0 until actions.length()) {"
-           "            if (actions.optJSONObject(index)?.optBoolean(\"available?\", false) == true) count += 1"
-           "        }" "        return count" "    }" ""
-           "    private fun totalActionCount(actor: String): Int ="
-           "        dashboard?.optJSONObject(\"actions\")?.optJSONArray(actor)?.length() ?: 0"
-           ""
-           "    private fun shipName(ship: JSONObject?, statusShip: JSONObject?): String {"
-           "        val fromShip = shipDisplayLabel(ship, \"\")"
-           "        if (fromShip.isNotBlank()) return fromShip"
-           "        return shipDisplayLabel(statusShip)" "    }" ""
-           "    private fun shipId(ship: JSONObject?, statusShip: JSONObject?): String {"
-           "        val fromShip = shipTechnicalLabel(ship, \"\")"
-           "        if (fromShip.isNotBlank()) return fromShip"
-           "        return shipTechnicalLabel(statusShip)" "    }" ""
-           "    private fun counterTotal(counter: Any?): Int {"
-           "        var total = 0"
-           "        forEachCounterPair(counter) { _, value ->"
-           "            total += numberValue(value).coerceAtLeast(0)"
-           "        }" "        return total" "    }" ""
-           "    private fun objectTotal(obj: JSONObject?): Int {"
-           "        var total = 0"
-           "        forEachCounterPair(obj) { _, value ->"
-           "            total += numberValue(value).coerceAtLeast(0)"
-           "        }" "        return total" "    }" ""
-           "    private fun paPanelCount(ship: JSONObject?, statusShip: JSONObject?): Int ="
-           "        counterValue(ship?.opt(\"boxes\"), \"52\").takeIf { it > 0 }"
-           "            ?: counterValue(statusShip?.opt(\"boxes\"), \"52\")"
-           ""
-           "    private fun paPanelMax(ship: JSONObject?, statusShip: JSONObject?): Int {"
-           "        val current = paPanelCount(ship, statusShip)"
-           "        val destroyed = counterValue(ship?.opt(\"destroyed-boxes\"), \"52\").takeIf { it > 0 }"
-           "            ?: counterValue(statusShip?.opt(\"destroyed-boxes\"), \"52\")"
-           "        return max(1, current + destroyed)" "    }" ""
-           "    private fun counterValue(counter: Any?, wantedKey: String): Int {"
-           "        var total = 0"
-           "        forEachCounterPair(counter) { key, value ->"
-           "            if (key == wantedKey) total += numberValue(value).coerceAtLeast(0)"
-           "        }" "        return total" "    }" ""
-           "    private fun forEachCounterPair(counter: Any?, action: (String, Any?) -> Unit) {"
-           "        when (counter) {" "            is JSONObject -> {"
-           "                val pair = counter.optJSONArray(PAIR_MARKER)"
-           "                if (pair != null) {"
-           "                    action(pair.opt(0)?.toString() ?: \"\", pair.opt(1))"
-           "                } else {"
-           "                    val keys = counter.keys()"
-           "                    while (keys.hasNext()) {"
-           "                        val key = keys.next()"
-           "                        action(key, counter.opt(key))"
-           "                    }" "                }" "            }"
-           "            is JSONArray -> {"
-           "                for (index in 0 until counter.length()) {"
-           "                    val pair = counter.optJSONObject(index)?.optJSONArray(PAIR_MARKER) ?: continue"
-           "                    action(pair.opt(0)?.toString() ?: \"\", pair.opt(1))"
-           "                }" "            }" "        }" "    }" ""
-           "    private fun defenseSummary(ship: JSONObject?, statusShip: JSONObject?): String {"
-           "        val shieldNow = objectTotal(ship?.optJSONObject(\"shields\") ?: statusShip?.optJSONObject(\"shields\"))"
-           "        if (shieldNow > 0) return \"SH $shieldNow\""
-           "        val paPanels = paPanelCount(ship, statusShip)"
-           "        val paMax = paPanelMax(ship, statusShip)"
-           "        return if (paPanels > 0 || paMax > 1) \"PA $paPanels/$paMax\" else \"SH 0\""
-           "    }" ""
-           "    private fun shieldFacingIndex(value: Any?): Int ="
-           "        when (value) {"
-           "            is Number -> value.toInt()"
-           "            is String -> {"
-           "                val digits = value.removePrefix(\"shield-\")"
-           "                digits.toIntOrNull() ?: -1" "            }"
-           "            else -> -1"
-           "        }.takeIf { it in 1..6 } ?: -1" ""
-           "    private fun countUnits(raw: Any?): Int ="
-           "        when (raw) {"
-           "            is JSONArray -> raw.length()"
-           "            is JSONObject -> {"
-           "                if (raw.has(\"position\")) 1 else {"
-           "                    var total = 0"
-           "                    val keys = raw.keys()"
-           "                    while (keys.hasNext()) {"
-           "                        if (raw.opt(keys.next()) != null) total += 1"
-           "                    }" "                    total"
-           "                }" "            }" "            else -> 0"
-           "        }" ""
-           "    private fun weaponInstalledTotal(weapons: JSONArray?): Int {"
-           "        if (weapons == null) return 0"
-           "        var total = 0"
-           "        for (index in 0 until weapons.length()) {"
-           "            total += weapons.optJSONObject(index)?.optInt(\"installed\", 0)?.coerceAtLeast(0) ?: 0"
-           "        }" "        return total" "    }" ""
-           "    private fun weaponFamilySummary(weapons: JSONArray?): String {"
-           "        if (weapons == null) return \"\""
-           "        val entries = mutableListOf<WeaponFamily>()"
-           "        for (index in 0 until weapons.length()) {"
-           "            val weapon = weapons.optJSONObject(index) ?: continue"
-           "            val installed = weapon.optInt(\"installed\", 0).coerceAtLeast(0)"
-           "            val armed = weapon.optInt(\"armed\", 0).coerceAtLeast(0)"
-           "            val fireable = weapon.optInt(\"fireable\", 0).coerceAtLeast(0)"
-           "            val inArc = weapon.optInt(\"fireable-in-arc\", 0).coerceAtLeast(0)"
-           "            val cooldown = weapon.optInt(\"cooldown\", 0).coerceAtLeast(0)"
-           "            if (installed + armed + fireable + cooldown <= 0) continue"
-           "            entries.add(WeaponFamily(weapon.optString(\"kind\", \"\"), installed, armed, fireable, inArc, cooldown))"
-           "        }" "        return entries"
-           "            .sortedWith(compareBy<WeaponFamily>({ weaponSortBucket(it.kind) }, { it.kind }))"
-           "            .take(3)"
-           "            .joinToString(\"  \") { entry ->"
-           "                val arc = if (entry.fireable > 0 && entry.inArc < entry.fireable) \" a${entry.inArc}\" else \"\""
-           "                val cooldown = if (entry.cooldown > 0) \" cd${entry.cooldown}\" else \"\""
-           "                \"${shortWeaponKind(entry.kind)} ${entry.fireable}/${entry.armed}/${entry.installed}$arc$cooldown\""
-           "            }" "    }" ""
-           "    private fun shortWeaponKind(kind: String): String ="
-           "        when (kind) {" "            \"photon\" -> \"PHT\""
-           "            \"disruptor\" -> \"DIS\""