Indicator · Pine Script v6 · On the chart

SMC Decision Board

by skipp-dev· code credited to preuss_steffen

SMC Decision Board is an on-chart decision-support dashboard designed for Smart Money Concepts analysis.

View on GitHub

GitHub stars1on the repository
Pine versionv6current
Lines of code2,098
Settings40inputs
LicenceNone statedfile: MPL-2.0
Last update2 days ago26 Oct 2026

Add It to Your TradingView Chart

  1. 1Copy the scriptPress Copy script above, or the copy button on the code below.
  2. 2Open the Pine EditorOpen a TradingView chart and choose Pine Editor at the bottom of the screen.
  3. 3Paste over the templateCreate a new indicator, select all of the template text and paste. Press Save and give it a name.
  4. 4Add it to the chartPress Add to chart, then adjust the settings with the gear icon next to its name.

Source Code

2,098 lines of Pine Script v6 from SMC_Decision_Board.pine, published under MPL-2.0 (file header). Keep the author's credit and licence lines when you reuse it.

SMC_Decision_Board.pine
// This Pine Script® code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © preuss_steffen

//@version=6

// Product role: pro companion surface. Hidden BUS bindings remain operator-only; the visible surface mode controls the user-facing dashboard cut.
// Product scope: this dashboard reflects the long-dip specialist mainline of SMC Core.
//   Short-parity would require a dedicated companion track; it is not an implicit promise of this surface.

indicator("SMC Decision Board", overlay = true, max_labels_count = 50)

import preuss_steffen/smc_micro_profiles_generated/569 as mp
import preuss_steffen/smc_utils/4 as u

var string g_surface = "1. Product Surface"
// BUS binding groups are ordered so that reading the settings panel from top to
// bottom walks the Suite's BUS plot() order 0..63 without ever going backwards.
// TradingView lists a source study's outputs as ONE FLAT list in plot order — it
// knows nothing about these groups — so any group whose plot range is out of
// sequence forces the operator to scroll back up in the dropdown. With 64 rows
// that is the difference between a mechanical top-to-bottom pass and a hunt.
// The former "Diagnostic Support" group held two disjoint ranges (14-16 and
// 40-43) and is therefore split: state codes stay here, blocker/regime codes get
// their own group.
// Group LABELS are customer-facing text; the group VARIABLES below are the
// surface-vs-internal partition that spec/hero_surface_input_map.json and
// tests/test_customer_surface_vocabulary.py key on. Renaming a label is NOT
// free: the vocabulary guard scans ALL group labels (internal groups too),
// and tv_preflight reports group TITLES from their copy in
// scripts/smc_bus_manifest.py (DASHBOARD_GROUP_TITLES_BY_KEY), so that copy
// moves in the same change. The onboarding automation binds by input TITLE
// (SSOT: automation/tradingview/lib/bus_binding_labels.mjs, the 'BUS '/'CTX '
// prefix parser shared by verify + packager); renaming a TITLE moves the
// frozen 64-channel binding contract: matcher, drift monitor and the saved
// TradingView copies in one step.
var string g_bus_lifecycle = "2. Chart Link - Decision State"
var string g_bus_diag = "3. Chart Link - Context Signals"
var string g_bus_plan = "4. Chart Link - Trade Plan"
var string g_bus_diag_rows = "5. Chart Link - Context Rows"
var string g_bus_blockers = "6. Chart Link - Blocker Codes"
var string g_bus_detail = "7. Chart Link - Detail Surface"
var string g_bus_lean = "8. Chart Link - Lean Surface"
var string g_bus_preset = "9. Chart Link - Preset Contract"
var string g_local_debug = "10. Advanced - Debug Mirrors"
var string g_trade_mgmt = "11. Trade Management (optional, mirrors SMC_Hold_Manager)"
var string g_operator_ops = "12. Advanced - Manual Overrides"

// ── Trade Management mirror inputs ────────────────────────────
// READ-ONLY mirror of SMC_Hold_Manager.pine state machine. Renders 2 extra
// rows (Trade-State + live Stop) at the bottom of the dashboard table when
// enabled. NO alerts here — Mobile/Dashboard surfaces are read-only by
// design. For audible alerts, link SMC_Hold_Manager on the same chart.
// MIRROR-OF: SMC_Hold_Manager.pine §state-machine (FLAT/ARMED/IN_TRADE)
tm_enable      = input.bool(false, "Enable Trade-Mgmt rows",            group = g_trade_mgmt, tooltip = "Adds 2 read-only rows (Trade-State + live Stop) showing the same FLAT/ARMED/IN-TRADE machine SMC_Hold_Manager runs. No alerts; for audible alerts add SMC_Hold_Manager.")
tm_tp1_r       = input.float(1.5,  "TP1 (R-multiple)",                  group = g_trade_mgmt, minval = 0.25, step = 0.25, active = tm_enable)
tm_tp2_r       = input.float(3.0,  "TP2 (R-multiple)",                  group = g_trade_mgmt, minval = 0.5,  step = 0.25, active = tm_enable)
tm_be_after_t1 = input.bool(true,  "Move stop to break-even after TP1", group = g_trade_mgmt, active = tm_enable)

surface_mode = input.string("Decision Brief", "View", options = ["Focus", "Hero", "Explain", "Decision Brief", "Audit View"], group = g_surface, tooltip = "Focus: 3-row traffic light — just the essentials.\nHero: 7-row decision-first surface (action, market, quality, trust, why now, risk, plan) reading the hero state contract from the generated library.\nExplain: checklist showing why the current zone state holds.\nDecision Brief: compact setup overview with context.\nAudit View: full expert table for review.")
show_table = input.bool(true, "Show Brief Panel", group = g_surface)
show_risk_lines = input.bool(true, "Show Trade Plan", group = g_surface)
show_state_bg = input.bool(false, "Highlight Live Setup", group = g_surface)
compact_dashboard = input.bool(false, "Compact Dashboard", group = g_surface, tooltip = "Show only 5 key rows for mobile or small screens. Overrides Decision Brief when enabled.")
color_theme = input.string("Standard", "Color Theme", options = ["Standard", "Colorblind Safe"], group = g_surface, tooltip = "Match this to the SMC Long-Dip Suite color theme. Colorblind Safe replaces red/green with orange/blue.")
hero_token_order = input.string("BIAS,TRUST,FAM,ZONE,BLOCKER", "Hero Token Order", group = g_surface, tooltip = "Comma-separated tokens that compose the Hero one-liner after the fixed 'SMC' prefix. Vocabulary: BIAS, TRUST, FAM (top-family hit-rate %), ZONE (rank), BLOCKER (warning), SHA (calibration reference, requires the Calibration SHA input under Manual Overrides). Unknown tokens are ignored. Default 'BIAS,TRUST,FAM,ZONE,BLOCKER' matches the original order — build your own Hero by reordering or dropping tokens.")

// ── Manual overrides (internal operations) ────────────────────
// Not part of the customer first-run path: each one is filled by hand from an
// internal source and stays empty/off for everyone else. They live in their own
// group so "1. Product Surface" contains only controls a customer can act on.
calibration_sha = input.string("", "Calibration SHA", group = g_operator_ops, display = display.none, tooltip = "Internal: paste the short reference of the calibration report you trust (e.g. 'a1b2c3d'). When set and the SHA token is present in Hero Token Order, the Hero one-liner appends 'sha:<7chars>' so a screenshot stays anchored. Empty by default → the segment is omitted.")
calibration_breach_banner = input.bool(false, "Calibration Breach Banner", group = g_operator_ops, display = display.none, tooltip = "Internal: manual switch for the rolling-30-day smECE SLO breach. Flip on when the mitigation pipeline is triggered. Hero one-liner then prepends '⚠CAL-BREACH' to the BLOCKER token.")
fvg_context_health_text = input.string("", "FVG Context Health", group = g_operator_ops, display = display.none, tooltip = "Internal: paste the current FVG context-health summary line (e.g. '⚠ FVG weak in ETH+HIGH (41% HR, 12 events); OK in RTH+NORMAL (72% HR, 34 events)'). Renders as a tooltip on the FVG Status row and an extra hint row. Empty by default → the tooltip falls back to the single-number summary.")

// ── Dashboard color palette ───────────────────────────────────
var bool _cb = color_theme == "Colorblind Safe"
var color CLR_BULL      = _cb ? #1f77b4 : color.green
var color CLR_BULL_ALT  = _cb ? color.new(#1f77b4, 20) : color.teal
var color CLR_WARN      = _cb ? #17becf : color.aqua
var color CLR_CAUTION   = _cb ? #bcbd22 : color.orange
var color CLR_BUILDING  = _cb ? color.new(#bcbd22, 20) : color.yellow
var color CLR_BEAR      = _cb ? #ff7f0e : color.red
var color CLR_NEUTRAL   = color.gray

// ── Trust-Tier color tokens (Plan 1.6 visual consolidation) ──
// Used by hero one-liner / future per-tier badges so users can read
// trust on 10m distance without reading the table.
var color CLR_TIER_T1   = _cb ? #1f77b4 : color.new(color.green,  0)
var color CLR_TIER_T2   = _cb ? #17becf : color.new(color.teal,   0)
var color CLR_TIER_T3   = _cb ? #bcbd22 : color.new(color.orange, 0)
var color CLR_TIER_T4   = _cb ? #ff7f0e : color.new(color.red,    0)
// Icon tokens — single source of truth so dashboards/labels stay consistent.
var string ICON_UP   = "⬆"
var string ICON_DOWN = "⬇"
var string ICON_WARN = "⚠"
var string ICON_OK   = "✅"

src_bus_schema_version = input.source(close, "BUS SchemaVersion", group = g_bus_lifecycle, display = display.none, tooltip = "Bind first — must match the schema version published by SMC Core.")
src_zone_active = input.source(close, "BUS ZoneActive", group = g_bus_lifecycle, display = display.none, tooltip = "Bind this section top-to-bottom to the matching hidden BUS series published by SMC Core.")
src_armed = input.source(close, "BUS Armed", group = g_bus_lifecycle, display = display.none)
src_confirmed = input.source(close, "BUS Confirmed", group = g_bus_lifecycle, display = display.none)
src_ready = input.source(close, "BUS Ready", group = g_bus_lifecycle, display = display.none)
src_entry_best = input.source(close, "BUS EntryBest", group = g_bus_lifecycle, display = display.none)
src_entry_strict = input.source(close, "BUS EntryStrict", group = g_bus_lifecycle, display = display.none)
src_trigger = input.source(close, "BUS Trigger", group = g_bus_lifecycle, display = display.none)
src_invalidation = input.source(close, "BUS Invalidation", group = g_bus_lifecycle, display = display.none)
src_quality_score = input.source(close, "BUS QualityScore", group = g_bus_lifecycle, display = display.none)
src_source_kind = input.source(close, "BUS SourceKind", group = g_bus_lifecycle, display = display.none)
src_state_code = input.source(close, "BUS StateCode", group = g_bus_lifecycle, display = display.none)
src_trend_pack = input.source(close, "BUS TrendPack", group = g_bus_lifecycle, display = display.none)
src_meta_pack = input.source(close, "BUS MetaPack", group = g_bus_lifecycle, display = display.none)
src_ltf_delta_state = input.source(close, "BUS LtfDeltaState", group = g_bus_diag, display = display.none, tooltip = "Keep binding straight down the panel — the rows follow the Suite's output order in the dropdown.")
src_safe_trend_state = input.source(close, "BUS SafeTrendState", group = g_bus_diag, display = display.none)
src_micro_profile_code = input.source(close, "BUS MicroProfileCode", group = g_bus_diag, display = display.none)
src_stop_level = input.source(close, "BUS StopLevel", group = g_bus_plan, display = display.none, tooltip = "Trade-plan bindings. They stay in Suite output order like every other group.")
src_target_1 = input.source(close, "BUS Target1", group = g_bus_plan, display = display.none)
src_target_2 = input.source(close, "BUS Target2", group = g_bus_plan, display = display.none)
src_session_gate_row = input.source(close, "BUS SessionGateRow", group = g_bus_diag_rows, display = display.none, tooltip = "Continue binding top-to-bottom — the rows follow the Suite's output order in the dropdown.")
src_market_gate_row = input.source(close, "BUS MarketGateRow", group = g_bus_diag_rows, display = display.none)
src_vola_gate_row = input.source(close, "BUS VolaGateRow", group = g_bus_diag_rows, display = display.none)
src_micro_session_gate_row = input.source(close, "BUS MicroSessionGateRow", group = g_bus_diag_rows, display = display.none)
src_micro_fresh_row = input.source(close, "BUS MicroFreshRow", group = g_bus_diag_rows, display = display.none)
src_volume_data_row = input.source(close, "BUS VolumeDataRow", group = g_bus_diag_rows, display = display.none)
src_quality_env_row = input.source(close, "BUS QualityEnvRow", group = g_bus_diag_rows, display = display.none)
src_quality_strict_row = input.source(close, "BUS QualityStrictRow", group = g_bus_diag_rows, display = display.none)
src_close_strength_row = input.source(close, "BUS CloseStrengthRow", group = g_bus_diag_rows, display = display.none)
src_ema_support_row = input.source(close, "BUS EmaSupportRow", group = g_bus_diag_rows, display = display.none)
src_adx_row = input.source(close, "BUS AdxRow", group = g_bus_diag_rows, display = display.none)
src_relvol_row = input.source(close, "BUS RelVolRow", group = g_bus_diag_rows, display = display.none)
src_vwap_row = input.source(close, "BUS VwapRow", group = g_bus_diag_rows, display = display.none)
src_context_quality_row = input.source(close, "BUS ContextQualityRow", group = g_bus_diag_rows, display = display.none)
src_quality_clean_row = input.source(close, "BUS QualityCleanRow", group = g_bus_diag_rows, display = display.none)
src_quality_score_row = input.source(close, "BUS QualityScoreRow", group = g_bus_diag_rows, display = display.none)
src_sd_confluence_row = input.source(close, "BUS SdConfluenceRow", group = g_bus_diag_rows, display = display.none)
src_sd_osc_row = input.source(close, "BUS SdOscRow", group = g_bus_diag_rows, display = display.none)
src_vol_regime_row = input.source(close, "BUS VolRegimeRow", group = g_bus_diag_rows, display = display.none)
src_vol_squeeze_row = input.source(close, "BUS VolSqueezeRow", group = g_bus_diag_rows, display = display.none)
src_ready_blocker_code = input.source(close, "BUS ReadyBlockerCode", group = g_bus_blockers, display = display.none, tooltip = "Blocker / regime codes. Split out of Context Signals so every group covers one contiguous range of Suite outputs.")
src_strict_blocker_code = input.source(close, "BUS StrictBlockerCode", group = g_bus_blockers, display = display.none)
src_vol_expansion_state = input.source(close, "BUS VolExpansionState", group = g_bus_blockers, display = display.none)
src_ddvi_context_state = input.source(close, "BUS DdviContextState", group = g_bus_blockers, display = display.none)
src_zone_ob_top = input.source(close, "BUS ZoneObTop", group = g_bus_detail, display = display.none)
src_zone_ob_bottom = input.source(close, "BUS ZoneObBottom", group = g_bus_detail, display = display.none)
src_zone_fvg_top = input.source(close, "BUS ZoneFvgTop", group = g_bus_detail, display = display.none)
src_zone_fvg_bottom = input.source(close, "BUS ZoneFvgBottom", group = g_bus_detail, display = display.none)
src_session_vwap = input.source(close, "BUS SessionVwap", group = g_bus_detail, display = display.none)
src_adx_value = input.source(close, "BUS AdxValue", group = g_bus_detail, display = display.none)
src_relvol_value = input.source(close, "BUS RelVolValue", group = g_bus_detail, display = display.none)
src_stretch_z = input.source(close, "BUS StretchZ", group = g_bus_detail, display = display.none)
src_stretch_support_mask = input.source(close, "BUS StretchSupportMask", group = g_bus_detail, display = display.none)
src_ltf_bull_share = input.source(close, "BUS LtfBullShare", group = g_bus_detail, display = display.none)
src_ltf_bias_hint = input.source(close, "BUS LtfBiasHint", group = g_bus_detail, display = display.none)
src_ltf_volume_delta = input.source(close, "BUS LtfVolumeDelta", group = g_bus_detail, display = display.none)
src_objects_count_pack = input.source(close, "BUS ObjectsCountPack", group = g_bus_detail, display = display.none)
src_lean_pack_a = input.source(close, "BUS LeanPackA", group = g_bus_lean, display = display.none, tooltip = "Lean surface bindings are grouped last because they are additive hero-surface feeds.")
src_lean_pack_b = input.source(close, "BUS LeanPackB", group = g_bus_lean, display = display.none)

// Plan §2.5 H5 — Preset contract bindings. Bind these to the Engine's
// 'BUS Preset*' hidden plots so the Hero can detect CUSTOM (class code 0)
// and surface the 4-Click onboarding tooltip. Optional bindings: a missing
// bind defaults to close → class code != 0 by accident, so the onboarding
// tooltip falls back to the always-safe Hero text. No engine change.
src_preset_class_code = input.source(close, "BUS PresetClassCode", group = g_bus_preset, display = display.none, tooltip = "Bind to the Suite's 'BUS PresetClassCode' hidden plot. 0 = Custom (onboarding tooltip), 1 = Mega-Cap US Tech, 2 = Financial Services, 3 = Energy.")
src_preset_rvol_min = input.source(close, "BUS PresetRvolMin", group = g_bus_preset, display = display.none)
src_preset_htf_bias_min = input.source(close, "BUS PresetHtfBiasMin", group = g_bus_preset, display = display.none)
src_preset_fvg_qual_gate = input.source(close, "BUS PresetFvgQualGate", group = g_bus_preset, display = display.none)
src_preset_vol_regime_def = input.source(close, "BUS PresetVolRegimeDef", group = g_bus_preset, display = display.none)

local_ob_debug_enabled = input.bool(false, "OB Debug Enabled", group = g_local_debug, display = display.none, tooltip = "Mirror the core's effective debug flags here if you want the local Debug Flags and Long Debug rows to match the core setup.")
local_fvg_debug_enabled = input.bool(false, "FVG Debug Enabled", group = g_local_debug, display = display.none)
local_engine_debug_enabled = input.bool(false, "Long Engine Debug Enabled", group = g_local_debug, display = display.none)

// ── BUS Schema Version Check ──────────────────────────────────────
int expected_bus_version = 7001
bool bus_version_ok = math.round(src_bus_schema_version) == expected_bus_version
var label bus_version_warning = na
if barstate.islast and not bus_version_ok
    if not na(bus_version_warning)
        label.delete(bus_version_warning)
    bus_version_warning := label.new(bar_index, high,
        "⚠ Chart Link Version Mismatch — Update Dashboard to match SMC Long-Dip Suite v" +
        str.tostring(math.round(src_bus_schema_version)),
        color = color.red, textcolor = color.white,
        style = label.style_label_down, size = size.small)

state_on(float value) =>
    not na(value) and value > 0.5

pack_slot(float packed_value, int slot) =>
    int packed = int(math.round(nz(packed_value, 0.0)))
    int slot_value = 0
    if slot == 0
        slot_value := int(math.floor(packed / 1000000000))
    else if slot == 1
        slot_value := int(math.floor(packed / 1000000)) % 1000
    else if slot == 2
        slot_value := int(math.floor(packed / 1000)) % 1000
    else
        slot_value := packed % 1000
    slot_value

count_pack_slot(float packed_value, int slot) =>
    int packed = int(math.round(nz(packed_value, 0.0)))
    int slot_value = 0
    if slot == 0
        slot_value := int(math.floor(packed / 1000000))
    else
        slot_value := packed % 1000000
    slot_value

trend_slot(float packed_value, int slot) =>
    int packed = int(math.round(nz(packed_value, 0.0)))
    int slot_value = 0
    if slot == 0
        slot_value := int(math.floor(packed / 1000)) % 10
    else if slot == 1
        slot_value := int(math.floor(packed / 100)) % 10
    else if slot == 2
        slot_value := int(math.floor(packed / 10)) % 10
    else
        slot_value := packed % 10
    slot_value

meta_slot(float packed_value, int slot) =>
    int packed = int(math.round(nz(packed_value, 0.0)))
    int slot_value = 0
    if slot == 0
        slot_value := int(math.floor(packed / 1000)) % 10
    else if slot == 1
        slot_value := int(math.floor(packed / 100)) % 10
    else if slot == 2
        slot_value := int(math.floor(packed / 10)) % 10
    else
        slot_value := packed % 10
    slot_value

row_status(int row_code) =>
    int(math.floor(row_code / 100)) - 1

row_reason(int row_code) =>
    row_code % 100

pack_bus_row(int row_state, int reason_code) =>
    (row_state + 1) * 100 + reason_code

debug_engine_enabled(int row_code) =>
    int(math.floor(row_reason(row_code) / 4)) % 2 == 1

resolve_debug_flags_row_code(bool ob_debug_enabled, bool fvg_debug_enabled, bool engine_debug_enabled) =>
    int reason_code = (ob_debug_enabled ? 1 : 0) + (fvg_debug_enabled ? 2 : 0) + (engine_debug_enabled ? 4 : 0)
    pack_bus_row(reason_code > 0 ? 5 : 0, reason_code)

resolve_debug_state_row_code(int debug_flags_row_code, int state_code, bool armed_state, bool confirmed_state, bool ready_state) =>
    if not debug_engine_enabled(debug_flags_row_code)
        pack_bus_row(0, 0)
    else if state_code == -1
        pack_bus_row(-1, 2)
    else if armed_state or confirmed_state or ready_state
        pack_bus_row(5, 3)
    else
        pack_bus_row(2, 4)

resolve_long_triggers_row_code(float trigger_level, float invalidation_level) =>
    if na(trigger_level) and na(invalidation_level)
        pack_bus_row(0, 2)
    else if na(trigger_level) or na(invalidation_level)
        pack_bus_row(2, 3)
    else
        pack_bus_row(5, 1)

resolve_risk_plan_row_code(float trigger_level, float invalidation_level, float stop_level, float target_1, float target_2) =>
    if na(trigger_level) and na(invalidation_level) and na(stop_level) and na(target_1) and na(target_2)
        pack_bus_row(0, 2)
    else if na(trigger_level) or na(invalidation_level)
        pack_bus_row(2, 3)
    else if not na(stop_level) and not na(target_1) and not na(target_2)
        pack_bus_row(5, 1)
    else
        pack_bus_row(2, 4)

ltf_delta_row_code_from_state(int state_code) =>
    if state_code == 2
        pack_bus_row(0, 2)
    else if state_code == 3
        pack_bus_row(2, 3)
    else if state_code == 4
        pack_bus_row(0, 4)
    else if state_code == 5
        pack_bus_row(5, 5)
    else if state_code == 6
        pack_bus_row(-1, 6)
    else
        pack_bus_row(0, 1)

swing_row_code_from_state(int state_code) =>
    state_code == 1 ? pack_bus_row(5, 1) : state_code == 2 ? pack_bus_row(-1, 2) : pack_bus_row(0, 3)

micro_profile_row_code_from_code(int profile_code) =>
    if profile_code <= 0
        pack_bus_row(0, 0)
    else
        bool has_modifiers = profile_code >= 10
        int base_code = has_modifiers ? profile_code - 10 : profile_code
        bool known_profile = base_code >= 1 and base_code <= 6
        int profile_state = has_modifiers or base_code != 1 ? 5 : 3
        known_profile ? pack_bus_row(profile_state, has_modifiers ? base_code + 10 : base_code) : pack_bus_row(0, 0)

ready_gate_row_code_from_blocker(int blocker_code) =>
    if blocker_code == 1
        pack_bus_row(5, 1)
    else if blocker_code >= 10 and blocker_code <= 24
        pack_bus_row(-1, blocker_code)
    else if blocker_code >= 2 and blocker_code <= 9
        pack_bus_row(2, blocker_code)
    else
        pack_bus_row(2, 9)

strict_gate_row_code_from_blocker(int blocker_code) =>
    if blocker_code == 1
        pack_bus_row(5, 1)
    else if blocker_code == 2
        pack_bus_row(2, 2)
    else if blocker_code >= 3 and blocker_code <= 10
        pack_bus_row(-1, blocker_code)
    else
        pack_bus_row(2, 2)

vol_expand_row_code_from_state(int state_code) =>
    state_code == 2 ? pack_bus_row(5, 2) : state_code == 3 ? pack_bus_row(3, 3) : state_code == 4 ? pack_bus_row(0, 4) : pack_bus_row(0, 1)

ddvi_row_code_from_state(int state_code) =>
    if state_code == 2
        pack_bus_row(5, 2)
    else if state_code == 3
        pack_bus_row(4, 3)
    else if state_code == 4
        pack_bus_row(4, 4)
    else if state_code == 5
        pack_bus_row(3, 5)
    else if state_code == 6
        pack_bus_row(0, 6)
    else
        pack_bus_row(0, 1)

resolve_event_risk_diag_row_code(int lean_erl_row_code) =>
    int lean_state = row_status(lean_erl_row_code)
    int lean_reason = row_reason(lean_erl_row_code)
    if lean_state == -1 and lean_reason == 1
        pack_bus_row(-1, 4)
    else if lean_state == -1 and lean_reason == 6
        pack_bus_row(-1, 5)
    else if lean_state == 2 and lean_reason == 3
        pack_bus_row(2, 2)
    else if lean_state == 3 and lean_reason == 4
        pack_bus_row(2, 3)
    else
        pack_bus_row(5, 1)

resolve_objects_row_code(int ob_count, int fvg_count) =>
    if ob_count > 0 and fvg_count > 0
        pack_bus_row(5, 3)
    else if ob_count > 0
        pack_bus_row(3, 1)
    else if fvg_count > 0
        pack_bus_row(3, 2)
    else
        pack_bus_row(0, 4)

zone_row_code(int zone_code) =>
    int zone_state = 0
    if zone_code == 2 or zone_code == 3
        zone_state := 4
    else if zone_code == 4
        zone_state := 5
    pack_bus_row(zone_state, zone_code)

status_bg(int state_value) =>
    color bg = color.new(CLR_NEUTRAL, 92)
    if state_value >= 6
        bg := color.new(CLR_BULL, 70)
    else if state_value == 5
        bg := color.new(CLR_BULL_ALT, 75)
    else if state_value == 4
        bg := color.new(CLR_BULL, 82)
    else if state_value == 3
        bg := color.new(CLR_WARN, 80)
    else if state_value == 2
        bg := color.new(CLR_CAUTION, 82)
    else if state_value == 1
        bg := color.new(CLR_BUILDING, 84)
    else if state_value == -1
        bg := color.new(CLR_BEAR, 82)
    bg

universe_status_state(int status_code) =>
    status_code == 3 ? 5 : status_code == 2 ? 2 : -1

dashboard_row(table tbl, int row, string label_text, string value_text, color bg, color txt) =>
    table.cell(tbl, 0, row, label_text, bgcolor = bg, text_color = txt, text_halign = text.align_left)
    table.cell(tbl, 1, row, value_text, bgcolor = bg, text_color = txt, text_halign = text.align_left)

// Plan 1.5 — Explain-popup variant: same row but with hover tooltip on both
// cells. Pine label/cell tooltips have a hard 4096-char limit; we trim to
// 2000 to stay safely below.
dashboard_row_tt(table tbl, int row, string label_text, string value_text, color bg, color txt, string tt) =>
    string _tt_safe = str.length(tt) > 2000 ? str.substring(tt, 0, 2000) + "…" : tt
    table.cell(tbl, 0, row, label_text, bgcolor = bg, text_color = txt, text_halign = text.align_left, tooltip = _tt_safe)
    table.cell(tbl, 1, row, value_text, bgcolor = bg, text_color = txt, text_halign = text.align_left, tooltip = _tt_safe)

// Plan 1.1 follow-up — Hero one-liner composer. Walks user-defined
// token order and emits the matching segment. Vocabulary fixed to keep
// the surface tested; unknown tokens are ignored (no compile error so
// users can copy-paste between layouts). The 'SMC' prefix is fixed.
// Plan §3.1.1 — added SHA token (optional, gated by non-empty sha arg).
compose_hero_one_liner(string order_csv, string bias, string trust, string fam, int fam_pct, string zone, string blocker, string sha) =>
    string _out = "SMC"
    array<string> _toks = str.split(order_csv, ",")
    for _i = 0 to array.size(_toks) - 1
        string _tok = str.upper(str.replace_all(array.get(_toks, _i), " ", ""))
        string _seg = ""
        if _tok == "BIAS"
            _seg := bias != "" ? bias : "FLAT"
        else if _tok == "TRUST"
            _seg := trust != "" ? trust : "—"
        else if _tok == "FAM"
            _seg := "Top " + (fam != "" ? fam : "—") + " " + str.tostring(fam_pct) + "%"
        else if _tok == "ZONE"
            _seg := "Zone " + (zone != "" ? zone : "—")
        else if _tok == "BLOCKER"
            _seg := blocker != "" ? "⚠" + blocker : ""
        else if _tok == "SHA"
            string _sha_trim = str.length(sha) > 7 ? str.substring(sha, 0, 7) : sha
            _seg := _sha_trim != "" ? "sha:" + _sha_trim : ""
        if _seg != ""
            _out := _out + " · " + _seg
    _out

section_row(table tbl, int row, string title, color bg, color txt) =>
    dashboard_row(tbl, row, title, "", bg, txt)

primary_metric_bg(int state_value) =>
    color.new(status_bg(state_value), 0)

diagnostic_metric_bg(int state_value) =>
    color.new(status_bg(state_value), 8)

dashboard_product_state_text(int state_code) =>
    string state_text = "WAIT"
    if state_code == -1
        state_text := "BLOCKED"
    else if state_code >= 6
        state_text := "ENTER LONG"
    else if state_code == 5
        state_text := "READY LONG"
    else if state_code >= 2
        state_text := "PREPARE LONG"
    state_text

// Pine helper resolution is order-sensitive here; keep these decoders above compact helper callers.
decode_lean_sq_text(int row_code) =>
    int s = row_status(row_code)
    int r = row_reason(row_code)
    string tier = r == 4 ? "high" : r == 3 ? "good" : r == 2 ? "ok" : "low"
    tier

decode_lean_freshness_text(int row_code) =>
    int s = row_status(row_code)
    int r = row_reason(row_code)
    string freshness = s >= 5 ? "fresh" : s == 3 ? "aging" : "stale"
    string bias = r == 1 ? "bull" : r == 2 ? "bear" : r == 3 ? "mixed" : "neutral"
    freshness + " | " + bias

decode_lean_erl_text(int row_code) =>
    int s = row_status(row_code)
    int r = row_reason(row_code)
    if s == -1 and r == 1
        "BLOCKED (Market)"
    else if s == -1 and r == 6
        "BLOCKED (Symbol)"
    else if s == 2 and r == 2
        "ACTIVE Window"
    else if s == 2 and r == 3
        "PRE_EVENT Caution"
    else if s == 3 and r == 4
        "COOLDOWN"
    else
        "CLEAR"

decode_lean_struct_text(int row_code) =>
    int s = row_status(row_code)
    int r = row_reason(row_code)
    string event = r == 1 ? "BOS_BULL" : r == 2 ? "CHOCH_BULL" : r == 3 ? "BOS_BEAR" : r == 4 ? "CHOCH_BEAR" : "NONE"
    string strength = s >= 5 ? "Strong" : s >= 4 ? "Medium" : "Weak"
    strength + " | " + event

decode_lean_ob_text(int row_code) =>
    int s = row_status(row_code)
    int r = row_reason(row_code)
    if s == 5
        "BULL Fresh | " + (r == 1 ? "fresh" : r == 2 ? "touched" : "stale")
    else if s == 3 and r == 2
        "BULL Aging"
    else if s == 2 and r == 4
        "BEAR"
    else
        "None"

decode_lean_fvg_text(int row_code) =>
    int s = row_status(row_code)
    int r = row_reason(row_code)
    if s == 2 and r == 1
        "Invalidated"
    else if s == 5 and r == 2
        "BULL Fresh"
    else if s == 4 and r == 3
        "BULL Aging"
    else if s == 2 and r == 4
        "BEAR"
    else
        "None"

decode_lean_session_text(int row_code) =>
    int score = row_status(row_code)
    int kz = row_reason(row_code)
    string kz_text = kz == 1 ? "KZ" : "noKZ"
    str.tostring(score) + " | " + kz_text

decode_ensemble_text(int row_code) =>
    int ensemble_state = row_status(row_code)
    ensemble_state >= 5 ? "high" : ensemble_state == 4 ? "good" : ensemble_state == 3 ? "ok" : ensemble_state == 2 ? "low" : "n/a"

decode_library_vol_text(int row_code) =>
    int reason_code = row_reason(row_code)
    int regime_code = int(math.floor(reason_code / 10))
    int source_code = reason_code % 10
    string regime_text = regime_code == 4 ? "EXTREME" : regime_code == 3 ? "HIGH_VOL" : regime_code == 2 ? "NORMAL" : regime_code == 1 ? "LOW_VOL" : "n/a"
    string source_text = source_code == 1 ? "arch" : source_code == 2 ? "ATR fallback" : "n/a"
    regime_code == 0 ? "n/a" : regime_text + " | " + source_text

zone_reason_text(int zone_code) =>
    string label_text = "No Long Zone"
    if zone_code == 1
        label_text := "Pullback zone touched"
    else if zone_code == 2
        label_text := "In fair value gap zone (FVG)"
    else if zone_code == 3
        label_text := "In order block zone (OB)"
    else if zone_code == 4
        label_text := "In order block + fair value gap zone"
    label_text

plan_text(bool active, float trigger_level, float stop_level, float target_1, float target_2) =>
    if not active
        "n/a"
    else
        "Entry " + str.tostring(trigger_level, format.mintick) + " | Stop " + str.tostring(stop_level, format.mintick) + " | T1 " + str.tostring(target_1, format.mintick) + " | T2 " + str.tostring(target_2, format.mintick)

trigger_text(bool active, float trigger_level, float invalidation_level) =>
    if not active
        "n/a"
    else
        "Trigger " + str.tostring(trigger_level, format.mintick) + " | Invalidation " + str.tostring(invalidation_level, format.mintick)

// Keep gate decoders above compact dashboard helpers; TradingView resolves these order-sensitively.
decode_ready_gate_text(int row_code) =>
    int reason_code = row_reason(row_code)
    string label_text = "Ready"
    if reason_code == 2
        label_text := "Need confirmed setup"
    else if reason_code == 3
        label_text := "Use close-safe mode"
    else if reason_code == 4
        label_text := "Wait one bar"
    else if reason_code == 5
        label_text := "Confirm expired"
    else if reason_code == 6
        label_text := "Setup stale"
    else if reason_code == 7
        label_text := "Bearish guard"
    else if reason_code == 8
        label_text := "Need main BOS"
    else if reason_code == 9
        label_text := "Lifecycle not ready"
    else if reason_code == 10
        label_text := "Setup hard gate"
    else if reason_code == 11
        label_text := "Session blocked"
    else if reason_code == 12
        label_text := "Micro session blocked"
    else if reason_code == 13
        label_text := "Micro fresh blocked"
    else if reason_code == 14
        label_text := "Overhead blocked"
    else if reason_code == 15
        label_text := "Trade hard gate"
    else if reason_code == 16
        label_text := "Market gate"
    else if reason_code == 17
        label_text := "Vola gate"
    else if reason_code == 18
        label_text := "Environment gate"
    else if reason_code == 19
        label_text := "Quality gate"
    else if reason_code == 20
        label_text := "Accel gate"
    else if reason_code == 21
        label_text := "SD gate"
    else if reason_code == 22
        label_text := "Vol gate"
    else if reason_code == 23
        label_text := "Stretch gate"
    else if reason_code == 24
        label_text := "DDVI gate"
    label_text

decode_strict_gate_text(int row_code) =>
    int reason_code = row_reason(row_code)
    string label_text = "Strict"
    if reason_code == 2
        label_text := "Need Ready"
    else if reason_code == 3
        label_text := "Signal Quality blocked"
    else if reason_code == 4
        label_text := "LTF blocked"
    else if reason_code == 5
        label_text := "HTF blocked"
    else if reason_code == 6
        label_text := "Accel blocked"
    else if reason_code == 7
        label_text := "SD blocked"
    else if reason_code == 8
        label_text := "Vol blocked"
    else if reason_code == 9
        label_text := "Stretch blocked"
    else if reason_code == 10
        label_text := "DDVI blocked"
    else if reason_code == 11
        label_text := "Trust Insufficient"
    label_text

dashboard_compact_why_now_text(int state_code, int zone_code, int lean_struct_row_code, int lean_ob_row_code, int lean_fvg_row_code, int ready_blocker_code, int strict_blocker_code) =>
    string why_text = "Waiting for reclaim confirmation"
    string ready_blocker_text = decode_ready_gate_text(ready_blocker_code)
    string strict_blocker_text = decode_strict_gate_text(strict_blocker_code)
    if state_code >= 6
        why_text := "Execution conditions passed; follow the trade levels"
    else if state_code == 5
        why_text := "Confirmed setup is ready; watch the trigger break"
    else if state_code == -1
        if ready_blocker_text != "Ready"
            why_text := ready_blocker_text
        else if strict_blocker_text != "Strict"
            why_text := strict_blocker_text
        else
            why_text := "Setup invalidated; wait for a fresh reclaim"
    else if state_code == 4
        why_text := ready_blocker_text != "Ready" ? ready_blocker_text : "Confirmed setup; wait for READY LONG"
    else if zone_code > 0
        why_text := zone_reason_text(zone_code)
    else if row_status(lean_ob_row_code) > 0
        why_text := decode_lean_ob_text(lean_ob_row_code)
    else if row_status(lean_fvg_row_code) > 0
        why_text := decode_lean_fvg_text(lean_fvg_row_code)
    else if row_status(lean_struct_row_code) > 0
        why_text := decode_lean_struct_text(lean_struct_row_code)
    why_text

dashboard_compact_structure_text(int state_code, int zone_code, int lean_struct_row_code, int reclaim_code) =>
    string structure_text = "not ready"
    if state_code == -1
        structure_text := "blocked"
    else if state_code >= 5
        structure_text := "supports long"
    else if state_code == 2 or state_code == 3 or state_code == 4
        structure_text := "building support"
    else if zone_code > 0 or reclaim_code >= 4 or row_status(lean_struct_row_code) >= 5
        structure_text := "supports long"
    else if reclaim_code >= 2 or row_status(lean_struct_row_code) >= 3
        structure_text := "building support"
    structure_text

dashboard_compact_session_market_text(int session_row_code, int market_row_code) =>
    int session_state = row_status(session_row_code)
    int market_state = row_status(market_row_code)
    bool session_off = row_reason(session_row_code) == 2
    bool market_off = row_reason(market_row_code) == 1 and market_state == 0
    string context_text = "mixed"
    if session_off and market_off
        context_text := "off"
    else if session_state == -1 or market_state == -1
        context_text := "blocked"
    else if (session_state >= 5 or session_off) and (market_state >= 5 or market_off)
        context_text := "clear"
    context_text

dashboard_compact_session_market_state(int session_row_code, int market_row_code) =>
    int session_state = row_status(session_row_code)
    int market_state = row_status(market_row_code)
    bool session_off = row_reason(session_row_code) == 2
    bool market_off = row_reason(market_row_code) == 1 and market_state == 0
    int compact_state = 3
    if session_off and market_off
        compact_state := 0
    else if session_state == -1 or market_state == -1
        compact_state := -1
    else if (session_state >= 5 or session_off) and (market_state >= 5 or market_off)
        compact_state := 5
    compact_state

dashboard_compact_event_risk_text(int row_code) =>
    int reason_code = row_reason(row_code)
    string risk_text = "mixed"
    if reason_code == 1 and row_status(row_code) >= 4
        risk_text := "clear"
    else if reason_code == 2 or reason_code == 3 or reason_code == 6
        risk_text := "caution"
    else if reason_code == 4 or reason_code == 5
        risk_text := "blocked"
    risk_text

dashboard_compact_data_quality_text(int volume_data_row_code, int micro_fresh_row_code) =>
    int reason_code = row_reason(volume_data_row_code)
    int micro_state = row_status(micro_fresh_row_code)
    string quality_text = "mixed"
    if reason_code == 1 and (micro_state == 0 or micro_state >= 3)
        quality_text := "clear"
    else if reason_code == 2 or micro_state == 2
        quality_text := "mixed"
    else
        quality_text := "thin"
    quality_text

dashboard_provider_state_text(string event_provider_status) =>
    string provider_text = "degraded"
    if event_provider_status == "ok"
        provider_text := "ok"
    provider_text

resolve_dashboard_trust_tier(string signal_quality_tier, string signal_freshness, string provider_state) =>
    string trust_tier = "insufficient"
    if provider_state != "ok" or signal_freshness == "stale"
        trust_tier := signal_quality_tier == "high" or signal_quality_tier == "good" or signal_quality_tier == "ok" ? "degraded" : "insufficient"
    else if signal_quality_tier == "high" and signal_freshness == "fresh"
        trust_tier := "high"
    else if signal_quality_tier == "high" or signal_quality_tier == "good" or signal_quality_tier == "ok"
        trust_tier := "guarded"
    trust_tier

// Moved ABOVE dashboard_compact_main_blocker_text (2026-07-13, CE10271):
// Pine requires user functions to be DEFINED before they are referenced —
// these two decoders were defined ~250 lines below their first use inside
// dashboard_compact_main_blocker_text, so the script failed to compile
// ("Could not find function or function reference"). Guarded by
// tests/test_pine_function_definition_order.py.
decode_volume_data_text(int row_code) =>
    int reason_code = row_reason(row_code)
    string label_text = "OK"
    if reason_code == 1
        label_text := "OK"
    else if reason_code == 2
        label_text := "Weak feed quality"
    else if reason_code == 3
        label_text := "Chart OK | LTF no-vol"
    else if reason_code == 4
        label_text := "Price-only LTF"
    else if reason_code == 5 or reason_code == 0
        label_text := "No current bar volume"
    label_text

decode_event_risk_text(int row_code) =>
    int reason_code = row_reason(row_code)
    string label_text = "Blocked"
    if reason_code == 1
        label_text := row_status(row_code) >= 4 ? "OK" : "Low"
    else if reason_code == 2
        label_text := "Incoming Event"
    else if reason_code == 3
        label_text := "Cooldown"
    else if reason_code == 4
        label_text := "Market Event Blocked"
    else if reason_code == 5
        label_text := "Symbol Earnings Blocked"
    else if reason_code == 6
        label_text := "High Macro Risk"
    label_text

dashboard_compact_main_blocker_text(int event_risk_row_code, int ready_blocker_code, int strict_blocker_code, int volume_data_row_code) =>
    string blocker_text = "no active blocker"
    string ready_blocker_text = decode_ready_gate_text(ready_blocker_code)
    string strict_blocker_text = decode_strict_gate_text(strict_blocker_code)
    string event_risk_text = decode_event_risk_text(event_risk_row_code)
    string volume_text = decode_volume_data_text(volume_data_row_code)
    if event_risk_text != "OK"
        blocker_text := event_risk_text
    else if ready_blocker_text != "Ready"
        blocker_text := ready_blocker_text
    else if strict_blocker_text != "Strict"
        blocker_text := strict_blocker_text
    else if volume_text != "OK"
        blocker_text := volume_text
    blocker_text

dashboard_compact_trust_text(int lean_sq_row_code, int lean_freshness_row_code, int volume_data_row_code, int event_risk_row_code, int ready_blocker_code, int strict_blocker_code, string ensemble_tier) =>
    string signal_quality_tier = decode_lean_sq_text(lean_sq_row_code)
    string signal_freshness = row_status(lean_freshness_row_code) >= 5 ? "fresh" : row_status(lean_freshness_row_code) == 3 ? "aging" : "stale"
    string provider_state = dashboard_provider_state_text(mp.EVENT_PROVIDER_STATUS)
    string trust_tier = resolve_dashboard_trust_tier(signal_quality_tier, signal_freshness, provider_state)
    string main_blocker = dashboard_compact_main_blocker_text(event_risk_row_code, ready_blocker_code, strict_blocker_code, volume_data_row_code)
    string ens_suffix = ensemble_tier != "" and ensemble_tier != "n/a" ? " | ens:" + ensemble_tier : ""
    trust_tier + ens_suffix + " | blocker: " + main_blocker + " | provider: " + provider_state

dashboard_compact_trust_state(int lean_sq_row_code, int lean_freshness_row_code) =>
    string signal_quality_tier = decode_lean_sq_text(lean_sq_row_code)
    string signal_freshness = row_status(lean_freshness_row_code) >= 5 ? "fresh" : row_status(lean_freshness_row_code) == 3 ? "aging" : "stale"
    string provider_state = dashboard_provider_state_text(mp.EVENT_PROVIDER_STATUS)
    string trust_tier = resolve_dashboard_trust_tier(signal_quality_tier, signal_freshness, provider_state)
    int state_code = -1
    if trust_tier == "high"
        state_code := 5
    else if trust_tier == "guarded"
        state_code := 4
    else if trust_tier == "degraded"
        state_code := 2
    state_code

// ── Product Trust Badge (ENG-WS2-03) ──────────────────────────────
// Reads the canonical product trust state emitted by the generated library
// (mp.TRUST_STATE / mp.TRUST_ACTION_IMPACT / mp.TRUST_DEGRADATION_REASON,
// per ENG-WS2-02) and renders a compact, single-glance badge that the
// default surfaces (Decision Brief + Hero) and the mobile dashboard can
// suffix onto their existing trust rows. The badge is purely additive: it
// never replaces the existing operator-grade trust rendering, so existing
// substring-based UI contracts stay intact.
product_trust_badge_text(string trust_state, string degradation_reason) =>
    string emoji = trust_state == "healthy" ? "✓" : trust_state == "degraded" ? "⚠ degraded" : trust_state == "stale" ? "⚠ stale" : trust_state == "watch_only" ? "⛔ watch-only" : trust_state == "unavailable" ? "⛔ no data" : "?"
    string suffix = degradation_reason != "" ? " (" + degradation_reason + ")" : ""
    emoji + suffix

product_trust_badge_state(string trust_state) =>
    int s = 5
    if trust_state == "degraded"
        s := 3
    else if trust_state == "stale"
        s := 2
    else if trust_state == "watch_only"
        s := 0
    else if trust_state == "unavailable"
        s := -1
    s

dashboard_compact_pressure_text(int ltf_delta_row_code, float ltf_volume_delta, int swing_row_code) =>
    int reason_code = row_reason(ltf_delta_row_code)
    int delta_state = row_status(ltf_delta_row_code)
    int swing_reason = row_reason(swing_row_code)
    string pressure_text = "mixed"
    if delta_state == -1 or ltf_volume_delta <= -0.05 or swing_reason == 2
        pressure_text := "against long"
    else if delta_state >= 5 and ltf_volume_delta >= 0.05
        pressure_text := "supports long"
    else if swing_reason == 1 and delta_state >= 0
        pressure_text := "supports long"
    else if reason_code == 2 or reason_code == 3 or reason_code == 4
        pressure_text := "thin"
    pressure_text

dashboard_compact_risk_plan_text(bool actionable_state, float trigger_level, float stop_level, float target_1, float target_2) =>
    actionable_state ? "Trigger " + str.tostring(trigger_level, format.mintick) + " | Stop " + str.tostring(stop_level, format.mintick) + " | T1 " + str.tostring(target_1, format.mintick) + " | T2 " + str.tostring(target_2, format.mintick) : "not ready"

source_kind_text(int source_code) =>
    string label_text = "None"
    if source_code == 1
        label_text := "Order Block (OB)"
    else if source_code == 2
        label_text := "Fair Value Gap (FVG)"
    else if source_code == 3
        label_text := "Swing Low"
    else if source_code == 4
        label_text := "Internal Low"
    label_text

trend_text(int trend_code) =>
    string label_text = "Neutral"
    if trend_code == 2
        label_text := "Bullish"
    else if trend_code == 0
        label_text := "Bearish"
    label_text

freshness_text(int freshness_code) =>
    string label_text = "n/a"
    if freshness_code == 1
        label_text := "confirm fresh"
    else if freshness_code == 2
        label_text := "confirm stale"
    else if freshness_code == 3
        label_text := "armed fresh"
    else if freshness_code == 4
        label_text := "armed stale"
    label_text

source_state_text(int source_state_code) =>
    string label_text = "n/a"
    if source_state_code == 1
        label_text := "source alive"
    else if source_state_code == 2
        label_text := "source invalid"
    else if source_state_code == 3
        label_text := "source lost"
    label_text

reclaim_reason_text(int reclaim_code) =>
    string label_text = "No Reclaim"
    if reclaim_code == 1
        label_text := "Reclaim recent"
    else if reclaim_code == 2
        label_text := "Internal Low Reclaimed"
    else if reclaim_code == 3
        label_text := "Swing Low Reclaimed"
    else if reclaim_code == 4
        label_text := "FVG Reclaimed"
    else if reclaim_code == 5
        label_text := "OB Reclaimed"
    label_text

long_visual_text(int state_code) =>
    string label_text = "Neutral"
    if state_code == -1
        label_text := "Fail"
    else if state_code == 1
        label_text := "In Zone"
    else if state_code == 2
        label_text := "Armed"
    else if state_code == 3
        label_text := "Building"
    else if state_code == 4
        label_text := "Confirmed"
    else if state_code >= 5
        label_text := "Ready"
    label_text

exec_tier_text(int state_code) =>
    string label_text = "n/a"
    if state_code >= 7
        label_text := "Strict"
    else if state_code == 6
        label_text := "Best"
    else if state_code == 5
        label_text := "Ready"
    else if state_code == 4
        label_text := "Confirmed"
    else if state_code == 2 or state_code == 3
        label_text := "Armed"
    else if state_code == 1
        label_text := "Watchlist"
    label_text

setup_text(int state_code, int source_code) =>
    string source_text = source_kind_text(source_code)
    string label_text = "No Setup"
    if state_code == -1
        label_text := "Invalidated"
    else if state_code == 1
        label_text := "In Zone"
    else if state_code == 2
        label_text := "Armed | " + source_text
    else if state_code == 3
        label_text := "Building | " + source_text
    else if state_code == 4
        label_text := "Confirmed | " + source_text
    else if state_code == 5
        label_text := "Ready | " + source_text
    else if state_code == 6
        label_text := "Entry Best | " + source_text
    else if state_code >= 7
        label_text := "Entry Strict | " + source_text
    label_text

setup_age_text(int state_code, int freshness_code) =>
    if state_code >= 4
        freshness_text(freshness_code)
    else if state_code == 2 or state_code == 3
        freshness_text(freshness_code)
    else
        "n/a"

decode_session_text(int row_code) =>
    int reason_code = row_reason(row_code)
    string label_text = "OR blocked"
    if reason_code == 1
        label_text := row_status(row_code) == 5 ? "OK" : "n/a"
    else if reason_code == 2
        label_text := "off"
    else if reason_code == 3
        label_text := "Entry session blocked"
    label_text

decode_market_text(int row_code) =>
    int reason_code = row_reason(row_code)
    string label_text = "Breadth blocked"
    if reason_code == 1
        label_text := row_status(row_code) == 5 ? "OK" : "off"
    else if reason_code == 2
        label_text := "Missing symbol"
    else if reason_code == 3
        label_text := "Index blocked"
    else if reason_code == 4
        label_text := "Sector blocked"
    else if reason_code == 6
        label_text := "Valuation caution"
    label_text

decode_vola_text(int row_code) =>
    int reason_code = row_reason(row_code)
    string label_text = "Blocked"
    if reason_code == 1
        label_text := row_status(row_code) == 0 ? "off" : "OK"
    else if reason_code == 2
        label_text := "Compression -> Expansion"
    else if reason_code == 3
        label_text := "Compression context"
    label_text

decode_micro_session_text(int row_code) =>
    int reason_code = row_reason(row_code)
    string label_text = "Weak after-hours"
    if reason_code == 1
        label_text := row_status(row_code) == 0 ? "off" : "OK"
    else if reason_code == 2
        label_text := "RTH only"
    else if reason_code == 3
        label_text := "Midday block"
    else if reason_code == 4
        label_text := "Weak premarket"
    label_text

decode_micro_fresh_text(int row_code, int freshness_code, int source_state_code) =>
    if row_reason(row_code) == 1 and row_status(row_code) == 0
        "off"
    else
        freshness_text(freshness_code) + " | " + source_state_text(source_state_code)



decode_quality_env_text(int row_code) =>
    int reason_code = row_reason(row_code)
    string label_text = "Mixed"
    if reason_code == 1
        label_text := "Passed"
    else if reason_code == 2
        label_text := "Market context blocked"
    else if reason_code == 3
        label_text := "Volatility regime blocked"
    label_text

decode_quality_strict_text(int row_code) =>
    int reason_code = row_reason(row_code)
    string label_text = "Volume divergence blocked (DDVI)"
    if reason_code == 1
        label_text := "Passed"
    else if reason_code == 2
        label_text := "Eligible"
    else if reason_code == 3
        label_text := "Lower-timeframe support blocked (LTF)"
    else if reason_code == 4
        label_text := "Higher-timeframe trend blocked (HTF)"
    else if reason_code == 5
        label_text := "Flow acceleration blocked"
    else if reason_code == 6
        label_text := "Divergence support blocked (SD)"
    else if reason_code == 7
        label_text := "Volatility context blocked"
    else if reason_code == 8
        label_text := "Stretch context blocked"
    label_text

decode_close_text(int row_code) =>
    int reason_code = row_reason(row_code)
    reason_code == 1 and row_status(row_code) == 0 ? "off" : reason_code == 1 ? "Strong Close" : "Weak Close"

decode_ema_text(int row_code) =>
    int reason_code = row_reason(row_code)
    reason_code == 1 and row_status(row_code) == 0 ? "off" : reason_code == 1 ? "OK" : "No"

format_level_text(float value) =>
    na(value) ? "n/a" : str.tostring(value, format.mintick)

format_number_text(float value) =>
    na(value) ? "n/a" : str.tostring(value, "#.##")

format_relvol_text(float value) =>
    na(value) ? "n/a" : str.tostring(value, "#.##") + "x"

format_percent_text(float value) =>
    na(value) ? "n/a" : str.format("{0,number,percent}", value)

format_percent_whole_text(float value) =>
    na(value) ? "n/a" : str.tostring(value * 100.0, "#") + "%"

format_zscore_text(float value) =>
    na(value) ? "n/a" : str.tostring(value, "#.##") + "z"

mask_has_flag(float mask_value, int flag_weight) =>
    int mask = int(math.round(nz(mask_value, 0.0)))
    int(math.floor(mask / flag_weight)) % 2 == 1

resolve_stretch_row_code(float stretch_z, float stretch_support_mask) =>
    bool use_stretch_context = mask_has_flag(stretch_support_mask, 1)
    bool in_lower_extreme = mask_has_flag(stretch_support_mask, 2)
    bool lower_extreme_recent = mask_has_flag(stretch_support_mask, 4)
    bool anti_chase_ok_ready = mask_has_flag(stretch_support_mask, 8)
    bool anti_chase_ok_entry_best = mask_has_flag(stretch_support_mask, 16)
    if not use_stretch_context
        pack_bus_row(0, 1)
    else if na(stretch_z)
        pack_bus_row(0, 2)
    else if anti_chase_ok_entry_best and in_lower_extreme
        pack_bus_row(5, 3)
    else if anti_chase_ok_entry_best and lower_extreme_recent
        pack_bus_row(5, 4)
    else if anti_chase_ok_ready
        pack_bus_row(3, 5)
    else
        pack_bus_row(-1, 6)

resolve_ltf_bias_row_code(int ltf_delta_row_code, float ltf_bull_share, float ltf_bias_hint) =>
    int delta_reason = row_reason(ltf_delta_row_code)
    if delta_reason == 1
        pack_bus_row(0, 1)
    else if delta_reason == 2 or delta_reason == 4 or na(ltf_bull_share) or na(ltf_bias_hint)
        pack_bus_row(0, 2)
    else if delta_reason == 3 and ltf_bull_share >= ltf_bias_hint
        pack_bus_row(5, 3)
    else if ltf_bull_share >= ltf_bias_hint
        pack_bus_row(5, 4)
    else if ltf_bull_share >= 0.50
        pack_bus_row(3, 5)
    else
        pack_bus_row(-1, 6)

zone_detail_text(int zone_code, float ob_top, float ob_bottom, float fvg_top, float fvg_bottom) =>
    bool show_ob = (zone_code == 3 or zone_code == 4) and not na(ob_top) and not na(ob_bottom)
    bool show_fvg = (zone_code == 2 or zone_code == 4) and not na(fvg_top) and not na(fvg_bottom)
    string label_text = zone_code == 0 ? "No Long Zones" : zone_reason_text(zone_code)
    if show_ob and show_fvg
        label_text := "OB " + format_level_text(ob_top) + " / " + format_level_text(ob_bottom) + " | FVG " + format_level_text(fvg_top) + " / " + format_level_text(fvg_bottom)
    else if show_ob
        label_text := "OB " + format_level_text(ob_top) + " / " + format_level_text(ob_bottom)
    else if show_fvg
        label_text := "FVG " + format_level_text(fvg_top) + " / " + format_level_text(fvg_bottom)
    label_text

decode_adx_text(int row_code, float adx_value) =>
    int reason_code = row_reason(row_code)
    string label_text = "off"
    if reason_code == 2
        label_text := "n/a"
    else if reason_code == 3
        label_text := format_number_text(adx_value) + " | Bearish pressure"
    else if reason_code == 4
        label_text := format_number_text(adx_value) + " | Strong"
    else if reason_code == 5
        label_text := format_number_text(adx_value) + " | Trend"
    else if reason_code == 6
        label_text := format_number_text(adx_value) + " | Trend building"
    label_text

decode_relvol_text(int row_code, float relvol_value) =>
    int reason_code = row_reason(row_code)
    string label_text = "off"
    if reason_code == 2
        label_text := "fallback"
    else if reason_code == 3
        label_text := "blocked"
    else if reason_code == 4
        label_text := format_relvol_text(relvol_value)
    else if reason_code == 5
        label_text := format_relvol_text(relvol_value) + " | Weak"
    label_text

decode_vwap_text(int row_code, float session_vwap) =>
    int reason_code = row_reason(row_code)
    string label_text = "off"
    if reason_code == 2
        label_text := "n/a"
    else if reason_code == 3
        label_text := "off session"
    else if reason_code == 4
        label_text := na(session_vwap) ? "Above VWAP" : "Above VWAP | " + format_level_text(session_vwap)
    else if reason_code == 5
        label_text := na(session_vwap) ? "Below VWAP" : "Below VWAP | " + format_level_text(session_vwap)
    label_text

decode_context_quality_text(int row_code) =>
    int reason_code = row_reason(row_code)
    reason_code == 1 ? "off" : reason_code == 2 ? "Supportive" : "Weak"

decode_quality_clean_text(int row_code) =>
    row_reason(row_code) == 1 ? "Clean" : "No"

decode_sd_confluence_text(int row_code) =>
    int reason_code = row_reason(row_code)
    string label_text = "none"
    if reason_code == 1
        label_text := "off"
    else if reason_code == 2
        label_text := "Bull div + HL"
    else if reason_code == 3
        label_text := "Bull div"
    else if reason_code == 4
        label_text := "Higher lows"
    else if reason_code == 5
        label_text := "Support any"
    label_text

decode_sd_osc_text(int row_code) =>
    int reason_code = row_reason(row_code)
    string label_text = "flat"
    if reason_code == 1
        label_text := "off"
    else if reason_code == 2
        label_text := "n/a"
    else if reason_code == 3
        label_text := "Above zero | rising"
    else if reason_code == 4
        label_text := "Rising"
    else if reason_code == 5
        label_text := "Below zero | falling"
    else if reason_code == 6
        label_text := "Mixed"
    label_text

decode_vol_regime_text(int row_code) =>
    int reason_code = row_reason(row_code)
    reason_code == 1 ? "off" : reason_code == 2 ? "Bull stack" : "Weak stack"

decode_vol_regime_stack_text(int row_code, int transport_row_code) =>
    string local_text = decode_vol_regime_text(row_code)
    string library_text = decode_library_vol_text(transport_row_code)
    library_text == "n/a" ? local_text : local_text + " | Lib " + library_text

decode_vol_squeeze_text(int row_code) =>
    int reason_code = row_reason(row_code)
    string label_text = "No squeeze"
    if reason_code == 1
        label_text := "off"
    else if reason_code == 2
        label_text := "Squeeze on"
    else if reason_code == 3
        label_text := "Released"
    else if reason_code == 4
        label_text := "Recent squeeze"
    label_text

decode_vol_expand_text(int row_code) =>
    int reason_code = row_reason(row_code)
    reason_code == 1 ? "off" : reason_code == 2 ? "Expansion" : reason_code == 3 ? "Building" : "No Expansion"

decode_stretch_text(int row_code, float stretch_z) =>
    int reason_code = row_reason(row_code)
    string label_text = "chasing"
    if reason_code == 1
        label_text := "off"
    else if reason_code == 2
        label_text := "n/a"
    else if reason_code == 3
        label_text := format_zscore_text(stretch_z) + " | lower extreme"
    else if reason_code == 4
        label_text := format_zscore_text(stretch_z) + " | recent extreme"
    else if reason_code == 5
        label_text := format_zscore_text(stretch_z) + " | anti-chase ok"
    else if reason_code == 6
        label_text := format_zscore_text(stretch_z) + " | chasing"
    label_text

decode_ddvi_text(int row_code) =>
    int reason_code = row_reason(row_code)
    string label_text = "Weak"
    if reason_code == 1
        label_text := "off"
    else if reason_code == 2
        label_text := "Bull bias + div"
    else if reason_code == 3
        label_text := "Bull bias"
    else if reason_code == 4
        label_text := "Bull div"
    else if reason_code == 5
        label_text := "Lower extreme"
    label_text

decode_ltf_bias_text(int row_code, float ltf_bull_share) =>
    int reason_code = row_reason(row_code)
    string label_text = "off"
    if reason_code == 2
        label_text := "n/a"
    else if reason_code == 3
        label_text := format_percent_whole_text(ltf_bull_share) + " price-only"
    else if reason_code == 4
        label_text := format_percent_whole_text(ltf_bull_share)
    else if reason_code == 5
        label_text := format_percent_whole_text(ltf_bull_share)
    else if reason_code == 6
        label_text := format_percent_whole_text(ltf_bull_share)
    label_text

decode_ltf_delta_text(int row_code, float ltf_volume_delta) =>
    int reason_code = row_reason(row_code)
    string label_text = "off"
    if reason_code == 2
        label_text := "n/a"
    else if reason_code == 3
        label_text := "no-vol"
    else if reason_code == 4
        label_text := "n/a"
    else if reason_code == 5
        label_text := format_percent_text(ltf_volume_delta)
    else if reason_code == 6
        label_text := format_percent_text(ltf_volume_delta)
    label_text

decode_objects_text(int row_code, int ob_count, int fvg_count) =>
    int reason_code = row_reason(row_code)
    string label_text = "No active objects"
    if reason_code == 1
        label_text := "OB " + str.tostring(ob_count)
    else if reason_code == 2
        label_text := "FVG " + str.tostring(fvg_count)
    else if reason_code == 3
        label_text := "OB " + str.tostring(ob_count) + " | FVG " + str.tostring(fvg_count)
    label_text

decode_swing_text(int row_code) =>
    int reason_code = row_reason(row_code)
    reason_code == 1 ? "Bullish structure" : reason_code == 2 ? "Bearish structure" : "Neutral"

decode_micro_profile_text(int row_code) =>
    int reason_code = row_reason(row_code)
    bool has_modifiers = reason_code >= 10
    int base_reason = has_modifiers ? reason_code - 10 : reason_code
    string label_text = "off"
    if base_reason == 1
        label_text := "Default"
    else if base_reason == 2
        label_text := "Stop-Hunt Sensitive"
    else if base_reason == 3
        label_text := "Clean Reclaim"
    else if base_reason == 4
        label_text := "RTH Only"
    else if base_reason == 5
        label_text := "Midday Dead"
    else if base_reason == 6
        label_text := "Fast Decay"
    if has_modifiers
        label_text += " | mod"
    label_text

decode_debug_flags_text(int row_code) =>
    int reason_code = row_reason(row_code)
    string label_text = "off"
    if reason_code > 0
        label_text := ""
        if reason_code % 2 == 1
            label_text += "OB"
        if int(math.floor(reason_code / 2)) % 2 == 1
            label_text += str.length(label_text) > 0 ? " | FVG" : "FVG"
        if int(math.floor(reason_code / 4)) % 2 == 1
            label_text += str.length(label_text) > 0 ? " | Engine" : "Engine"
    label_text

// ── Phase H: Calibration confidence indicator ─────────────────
calibration_confidence_tier(float cal_weight) =>
    cal_weight >= 0.80 ? "high" : cal_weight >= 0.65 ? "good" : cal_weight >= 0.50 ? "ok" : "low"

calibration_confidence_state(float cal_weight) =>
    cal_weight >= 0.80 ? 5 : cal_weight >= 0.65 ? 4 : cal_weight >= 0.50 ? 3 : 2

resolve_family_cal_weight(string family) =>
    float w = family == "OB" ? mp.ZONE_CAL_OB : family == "FVG" ? mp.ZONE_CAL_FVG : family == "BOS" ? mp.ZONE_CAL_BOS : family == "SWEEP" ? mp.ZONE_CAL_SWEEP : 0.50
    w

// ── Q3 F2: session-aware family weight ────────────────────────
// Picks the session-specific calibrated weight (ZONE_CAL_<FAM>_<SESSION>)
// when mp.SESSION_CONTEXT matches a promoted bucket (ASIA/LONDON/NY_AM,
// see scripts/smc_zone_priority_calibration.py); falls back to the
// global ZONE_CAL_<FAM> for NY_PM/NONE so unmeasured contexts keep
// the smoothed prior. Audit motivation: NY_AM FVG calibration is
// 0.4961 vs ASIA 0.6948 — the global 0.5773 was hiding a 20pp HR
// spread (docs/FVG_LABEL_AUDIT_Q3.md §2).
resolve_family_cal_weight_session(string family, string session_context) =>
    float global_w = resolve_family_cal_weight(family)
    float w = global_w
    if session_context == "ASIA"
        w := family == "OB" ? mp.ZONE_CAL_OB_ASIA : family == "FVG" ? mp.ZONE_CAL_FVG_ASIA : family == "BOS" ? mp.ZONE_CAL_BOS_ASIA : family == "SWEEP" ? mp.ZONE_CAL_SWEEP_ASIA : global_w
    else if session_context == "LONDON"
        w := family == "OB" ? mp.ZONE_CAL_OB_LONDON : family == "FVG" ? mp.ZONE_CAL_FVG_LONDON : family == "BOS" ? mp.ZONE_CAL_BOS_LONDON : family == "SWEEP" ? mp.ZONE_CAL_SWEEP_LONDON : global_w
    else if session_context == "NY_AM"
        w := family == "OB" ? mp.ZONE_CAL_OB_NY_AM : family == "FVG" ? mp.ZONE_CAL_FVG_NY_AM : family == "BOS" ? mp.ZONE_CAL_BOS_NY_AM : family == "SWEEP" ? mp.ZONE_CAL_SWEEP_NY_AM : global_w
    w

format_cal_weight(float w) =>
    str.tostring(w * 100.0, "#.#") + "%"

family_performance_text(string family, float cal_weight) =>
    string tier = calibration_confidence_tier(cal_weight)
    family + " " + format_cal_weight(cal_weight) + " (" + tier + ")"

// ── Phase H: FVG health warning ───────────────────────────────
resolve_fvg_health_score() =>
    float score = 0.0
    if mp.PRIMARY_FVG_SIDE == "NONE"
        score := 0.0
    else
        score += mp.FVG_FRESH ? 30.0 : 0.0
        score += mp.FVG_INVALIDATED ? -40.0 : 0.0
        score += (1.0 - mp.FVG_FILL_PCT) * 30.0
        score += math.min(mp.FVG_MATURITY_LEVEL, 3) * 10.0
        score += mp.FVG_NET_IMBALANCE > 0 ? 10.0 : mp.FVG_NET_IMBALANCE < 0 ? -10.0 : 0.0
    math.max(score, 0.0)

fvg_health_tier(float score) =>
    score >= 60.0 ? "healthy" : score >= 30.0 ? "aging" : score > 0.0 ? "weak" : "none"

fvg_health_state(float score) =>
    score >= 60.0 ? 5 : score >= 30.0 ? 3 : score > 0.0 ? 2 : 0

fvg_health_text(float score) =>
    if mp.PRIMARY_FVG_SIDE == "NONE"
        "No active FVG"
    else
        string side = mp.PRIMARY_FVG_SIDE
        string tier = fvg_health_tier(score)
        string fill = str.tostring(mp.FVG_FILL_PCT * 100.0, "#") + "% filled"
        string fresh_label = mp.FVG_FRESH ? "fresh" : mp.FVG_INVALIDATED ? "INVALIDATED" : "aging"
        side + " | " + tier + " | " + fill + " | " + fresh_label

fvg_health_warning_text(float score) =>
    if mp.FVG_INVALIDATED
        "⚠ FVG invalidated — zone reliability degraded"
    else if mp.FVG_FILL_PCT >= 0.75 and not mp.FVG_FRESH
        "⚠ FVG heavily filled (" + str.tostring(mp.FVG_FILL_PCT * 100.0, "#") + "%) — weakening support"
    else if score < 30.0 and mp.PRIMARY_FVG_SIDE != "NONE"
        "⚠ FVG health weak — consider reduced confidence"
    else
        ""

// Phase H4 calibration-driven FVG warning. Fires when the calibrated
// per-family hit rate drops below 65% (the strategy-doc threshold) so
// users see a system-wide signal even when no live FVG is active.
// Renders nothing when ZONE_HR_FVG is unset (== 0.0 default) so the
// dashboard stays quiet on first install / before calibration runs.
fvg_calibration_warning_text(float zone_hr_fvg) =>
    if zone_hr_fvg <= 0.0
        ""
    else if zone_hr_fvg < 0.65
        "⚠ FVG zones underperforming (" + str.tostring(zone_hr_fvg * 100.0, "#") + "% HR) — prefer OB/BOS setups"
    else
        ""

// Phase H follow-on (issue #16 c) — generalized per-family hit-rate
// warning. Returns the family ticker tag when the calibrated HR is
// below the 65% threshold and the field is initialised; empty otherwise.
zone_hr_family_below_threshold(float zone_hr) =>
    zone_hr > 0.0 and zone_hr < 0.65

// Aggregate calibration warning across all four zone families. Returns
// a single warning string listing every family currently underperforming
// so the dashboard surfaces system-wide cal degradation in one slot,
// rather than four duplicated warnings. Quiet when all families are
// healthy or uninitialised.
zone_cal_aggregate_warning_text() =>
    string tags = ""
    if zone_hr_family_below_threshold(mp.ZONE_HR_FVG)
        tags := "FVG"
    if zone_hr_family_below_threshold(mp.ZONE_HR_OB)
        tags := tags == "" ? "OB" : tags + "/OB"
    if zone_hr_family_below_threshold(mp.ZONE_HR_BOS)
        tags := tags == "" ? "BOS" : tags + "/BOS"
    if zone_hr_family_below_threshold(mp.ZONE_HR_SWEEP)
        tags := tags == "" ? "SWEEP" : tags + "/SWEEP"
    tags == "" ? "" : "⚠ Cal-HR weak: " + tags

// Map mp.ZONE_CAL_TRUST to a compact glyph for the audit row suffix.
// "OK" → 🔒, "DEGRADED" → ⚠, "UNAVAILABLE" → ❓, anything else → "?".
// Empty when the field is the default empty string (uninitialised).
zone_cal_trust_glyph(string trust_state) =>
    trust_state == "OK" ? "🔒" : trust_state == "DEGRADED" ? "⚠" : trust_state == "UNAVAILABLE" ? "❓" : trust_state == "" ? "" : "?"

// Phase H follow-on (issue #16, deferred tooltip patch) — render the
// per-family calibration confidence (H1, 0–1) and trend (H3, IMPROVING/
// STABLE/DEGRADING) as a single tooltip line. Returns "" when the
// confidence field is at its default sentinel (0.0) so the dashboard
// stays quiet on first install / before calibration runs.
zone_cal_diagnostics_tooltip_text(float zone_cal_confidence, string zone_cal_trend) =>
    if zone_cal_confidence <= 0.0
        ""
    else
        "Cal conf " + str.tostring(zone_cal_confidence, "#.##") + " · trend " + zone_cal_trend

// Combine live-zone and calibration warnings — calibration takes
// precedence when both fire because it speaks to system reliability
// rather than a single zone's lifecycle.
fvg_combined_warning_text(float score, float zone_hr_fvg) =>
    string cal_warn = fvg_calibration_warning_text(zone_hr_fvg)
    if cal_warn != ""
        cal_warn
    else
        fvg_health_warning_text(score)

quality_bounds_text(float score_value) =>
    str.tostring(score_value, "#.##") + "/100 | min 25"

quality_stack_text(float score_value, int ensemble_row_code) =>
    string ensemble_text = decode_ensemble_text(ensemble_row_code)
    ensemble_text == "n/a" ? quality_bounds_text(score_value) : quality_bounds_text(score_value) + " | Ensemble " + ensemble_text

bool zone_active = state_on(src_zone_active)
bool armed = state_on(src_armed)
bool confirmed = state_on(src_confirmed)
bool ready = state_on(src_ready)
bool entry_best = state_on(src_entry_best)
bool entry_strict = state_on(src_entry_strict)
int state_code = int(math.round(nz(src_state_code, 0.0)))
int source_code = int(math.round(nz(src_source_kind, 0.0)))
int trend_now = trend_slot(src_trend_pack, 0)
int trend_htf_1 = trend_slot(src_trend_pack, 1)
int trend_htf_2 = trend_slot(src_trend_pack, 2)
int trend_htf_3 = trend_slot(src_trend_pack, 3)
int freshness_code = meta_slot(src_meta_pack, 0)
int source_state_code = meta_slot(src_meta_pack, 1)
int reclaim_code = meta_slot(src_meta_pack, 2)
int zone_code = meta_slot(src_meta_pack, 3)
int session_row_code = int(math.round(nz(src_session_gate_row, 0.0)))
int market_row_code = int(math.round(nz(src_market_gate_row, 0.0)))
int vola_row_code = int(math.round(nz(src_vola_gate_row, 0.0)))
int micro_session_row_code = int(math.round(nz(src_micro_session_gate_row, 0.0)))
int micro_fresh_row_code = int(math.round(nz(src_micro_fresh_row, 0.0)))
int volume_data_row_code = int(math.round(nz(src_volume_data_row, 0.0)))
int quality_env_row_code = int(math.round(nz(src_quality_env_row, 0.0)))
int quality_strict_row_code = int(math.round(nz(src_quality_strict_row, 0.0)))
int close_row_code = int(math.round(nz(src_close_strength_row, 0.0)))
int ema_row_code = int(math.round(nz(src_ema_support_row, 0.0)))
int adx_row_code = int(math.round(nz(src_adx_row, 0.0)))
int relvol_row_code = int(math.round(nz(src_relvol_row, 0.0)))
int vwap_row_code = int(math.round(nz(src_vwap_row, 0.0)))
int context_quality_row_code = int(math.round(nz(src_context_quality_row, 0.0)))
int quality_clean_row_code = int(math.round(nz(src_quality_clean_row, 0.0)))
int quality_score_row_code = int(math.round(nz(src_quality_score_row, 0.0)))
int sd_confluence_row_code = int(math.round(nz(src_sd_confluence_row, 0.0)))
int sd_osc_row_code = int(math.round(nz(src_sd_osc_row, 0.0)))
int vol_regime_row_code = int(math.round(nz(src_vol_regime_row, 0.0)))
int vol_squeeze_row_code = int(math.round(nz(src_vol_squeeze_row, 0.0)))
int ltf_delta_state_code = int(math.round(nz(src_ltf_delta_state, 0.0)))
int ltf_delta_row_code = ltf_delta_row_code_from_state(ltf_delta_state_code)
int stretch_row_code = resolve_stretch_row_code(src_stretch_z, src_stretch_support_mask)
int safe_trend_state_code = int(math.round(nz(src_safe_trend_state, 0.0)))
int swing_row_code = swing_row_code_from_state(safe_trend_state_code)
int ltf_bias_row_code = resolve_ltf_bias_row_code(ltf_delta_row_code, src_ltf_bull_share, src_ltf_bias_hint)
int objects_ob_count = count_pack_slot(src_objects_count_pack, 0)
int objects_fvg_count = count_pack_slot(src_objects_count_pack, 1)
int objects_row_code = resolve_objects_row_code(objects_ob_count, objects_fvg_count)
int micro_profile_encoded = int(math.round(nz(src_micro_profile_code, 0.0)))
int micro_profile_row_code = micro_profile_row_code_from_code(micro_profile_encoded)
int long_zones_row_code = zone_row_code(zone_code)
int long_triggers_row_code = resolve_long_triggers_row_code(src_trigger, src_invalidation)
int risk_plan_row_code = resolve_risk_plan_row_code(src_trigger, src_invalidation, src_stop_level, src_target_1, src_target_2)
int debug_flags_row_code = resolve_debug_flags_row_code(local_ob_debug_enabled, local_fvg_debug_enabled, local_engine_debug_enabled)
int ready_blocker_code = int(math.round(nz(src_ready_blocker_code, 0.0)))
int strict_blocker_code = int(math.round(nz(src_strict_blocker_code, 0.0)))
int vol_expansion_state_code = int(math.round(nz(src_vol_expansion_state, 0.0)))
int ddvi_context_state_code = int(math.round(nz(src_ddvi_context_state, 0.0)))
int ready_gate_row_code = ready_gate_row_code_from_blocker(ready_blocker_code)
int strict_gate_row_code = strict_gate_row_code_from_blocker(strict_blocker_code)
int vol_expand_row_code = vol_expand_row_code_from_state(vol_expansion_state_code)
int ddvi_row_code = ddvi_row_code_from_state(ddvi_context_state_code)
int debug_state_row_code = resolve_debug_state_row_code(debug_flags_row_code, state_code, armed, confirmed, ready)
// v5.5 Lean Pack unpacking
int lean_sq_row_code = pack_slot(src_lean_pack_a, 0)
int lean_freshness_row_code = pack_slot(src_lean_pack_a, 1)
int lean_erl_row_code = pack_slot(src_lean_pack_a, 2)
int lean_struct_row_code = pack_slot(src_lean_pack_a, 3)
int event_risk_row_code = resolve_event_risk_diag_row_code(lean_erl_row_code)
int lean_ob_row_code = pack_slot(src_lean_pack_b, 0)
int lean_fvg_row_code = pack_slot(src_lean_pack_b, 1)
int lean_session_row_code = pack_slot(src_lean_pack_b, 2)
int lean_transport_row_code = pack_slot(src_lean_pack_b, 3)
int htf_bull_count = (trend_htf_1 == 2 ? 1 : 0) + (trend_htf_2 == 2 ? 1 : 0) + (trend_htf_3 == 2 ? 1 : 0)
int setup_state = state_code >= 6 ? 5 : state_code == 5 or state_code == 4 ? 4 : state_code == 3 ? 3 : state_code == 2 ? 2 : state_code == 1 ? 1 : state_code == -1 ? -1 : 0
int exec_state = state_code >= 6 ? 5 : state_code == 5 or state_code == 4 ? 4 : state_code == 2 or state_code == 3 ? 2 : state_code == 1 ? 1 : 0
int setup_age_state = state_code >= 4 ? freshness_code == 1 ? 5 : 2 : state_code == 2 or state_code == 3 ? freshness_code == 3 ? 4 : 2 : 0
int htf_state = htf_bull_count >= 2 ? 5 : htf_bull_count == 1 ? 3 : -1
int reclaim_state = reclaim_code >= 4 ? 5 : reclaim_code >= 2 ? 4 : reclaim_code == 1 ? 2 : 0
string source_label = source_kind_text(source_code)
string long_zones_text = zone_detail_text(row_reason(long_zones_row_code), src_zone_ob_top, src_zone_ob_bottom, src_zone_fvg_top, src_zone_fvg_bottom)
string long_debug_text = row_status(debug_state_row_code) == 0 ? "off" : "state=" + long_visual_text(state_code) + " | src=" + source_label + " | fresh=" + freshness_text(freshness_code) + " | " + source_state_text(source_state_code)

bool actionable_state = state_code >= 5
string dashboard_symbol_key = syminfo.ticker
string dashboard_symbol_key_qualified = syminfo.tickerid
bool dashboard_universe_initialized = mp.UNIVERSE_TICKERS != ""
bool dashboard_ticker_in_universe = dashboard_universe_initialized and u.csv_has_symbol_token(mp.UNIVERSE_TICKERS, dashboard_symbol_key, dashboard_symbol_key_qualified)
bool dashboard_universe_partial_context = dashboard_ticker_in_universe and (mp.PROVIDER_COUNT == 0 or mp.STALE_PROVIDERS != "" or mp.EVENT_PROVIDER_STATUS != "ok")
int dashboard_universe_status_code = u.universe_status_code(dashboard_universe_initialized, dashboard_ticker_in_universe, dashboard_universe_partial_context)
int dashboard_universe_state = universe_status_state(dashboard_universe_status_code)
string dashboard_universe_badge = u.universe_status_badge(dashboard_universe_status_code)
string dashboard_universe_detail = u.universe_status_dashboard_text(dashboard_universe_status_code)
string dashboard_universe_matrix = u.universe_status_feature_matrix(dashboard_universe_status_code)
string dashboard_universe_tt = dashboard_universe_detail + "\n\n" + dashboard_universe_matrix

plot(show_risk_lines and actionable_state ? src_trigger : na, "Trade Trigger", color = color.new(CLR_WARN, 0), linewidth = 2, style = plot.style_linebr)
plot(show_risk_lines and actionable_state ? src_invalidation : na, "Trade Invalidation", color = color.new(CLR_CAUTION, 0), linewidth = 2, style = plot.style_linebr)
bgcolor(show_state_bg and state_code >= 5 ? color.new(CLR_BULL, 90) : na)

var table smc_dashboard = table.new(position.bottom_right, 2, 80, border_width = 0)

if show_table and barstate.islast
    color txt = chart.fg_color
    color header_bg = color.new(color.black, 100)
    if surface_mode == "Focus"
        // ── FOCUS MODE (v7.1): 3-row traffic light — just the essentials ──
        // Row 0: Traffic light (🟢 / 🟡 / 🔴) + one-word action
        // Row 1: Price levels (trigger + invalidation) if actionable
        // Row 2: Market context (regime + trust) one-liner
        string focus_action = state_code >= 6 ? "🟢  ENTRY" : state_code == 5 ? "🟢  READY" : state_code >= 2 ? "🟡  PREPARE" : state_code == -1 ? "🔴  BLOCKED" : "⚪  WAIT"
        color focus_action_bg = state_code >= 5 ? color.new(CLR_BULL, 70) : state_code >= 2 ? color.new(CLR_CAUTION, 70) : state_code == -1 ? color.new(CLR_BEAR, 70) : color.new(CLR_NEUTRAL, 80)
        string focus_levels = actionable_state and not na(src_trigger) ? "Entry " + str.tostring(src_trigger, format.mintick) + " | Stop " + str.tostring(src_invalidation, format.mintick) : "—"
        string focus_context = mp.MARKET_REGIME + " · " + mp.TRADE_STATE
        color focus_context_bg = mp.TRADE_STATE == "BLOCKED" ? color.new(CLR_BEAR, 80) : mp.TRADE_STATE == "DISCOURAGED" ? color.new(CLR_CAUTION, 80) : color.new(CLR_BULL_ALT, 85)
        dashboard_row_tt(smc_dashboard, 0, "SMC", focus_action + " | " + dashboard_universe_badge, focus_action_bg, txt, dashboard_universe_tt)
        dashboard_row(smc_dashboard, 1, "Plan", focus_levels, actionable_state ? color.new(CLR_BULL_ALT, 85) : header_bg, txt)
        dashboard_row(smc_dashboard, 2, "Market", focus_context, focus_context_bg, txt)
        table.clear(smc_dashboard, 0, 3, 1, 79)
    else if surface_mode == "Explain"
        // ── EXPLAIN MODE (v7.2): Checklist explaining why the current zone state holds ──
        // Shows each major criterion as ✅ / ❌ with what's met, what's missing, and the next step.
        string ex_state_label = state_code >= 6 ? "🟢  ENTER LONG" : state_code == 5 ? "🟢  READY LONG" : state_code == 4 ? "🟡  CONFIRMED" : state_code == 3 ? "🟡  BUILDING" : state_code == 2 ? "🟡  ARMED" : state_code == 1 ? "⚪  IN ZONE" : state_code == -1 ? "🔴  BLOCKED" : "⚪  WAIT"
        color ex_state_bg = state_code >= 5 ? color.new(CLR_BULL, 70) : state_code >= 2 ? color.new(CLR_CAUTION, 70) : state_code == -1 ? color.new(CLR_BEAR, 70) : color.new(CLR_NEUTRAL, 80)
        string ex_source = source_kind_text(source_code)
        // ── Criterion checks ──
        bool ex_struct_ok = row_status(lean_struct_row_code) >= 3
        string ex_struct_event = row_reason(lean_struct_row_code) == 1 ? "BOS Bull" : row_reason(lean_struct_row_code) == 2 ? "CHoCH Bull" : row_reason(lean_struct_row_code) == 3 ? "BOS Bear" : row_reason(lean_struct_row_code) == 4 ? "CHoCH Bear" : "—"
        bool ex_zone_ok = zone_code > 0
        string ex_zone_type = zone_reason_text(zone_code)
        bool ex_quality_ok = row_status(lean_sq_row_code) >= 3
        string ex_quality_tier = decode_lean_sq_text(lean_sq_row_code)
        bool ex_fresh_ok = row_status(lean_freshness_row_code) >= 3
        string ex_fresh_label = row_status(lean_freshness_row_code) >= 5 ? "fresh" : row_status(lean_freshness_row_code) == 3 ? "aging" : "stale"
        bool ex_session_ok = row_status(session_row_code) >= 5 or row_reason(session_row_code) == 2
        bool ex_market_ok = row_status(market_row_code) >= 5 or (row_reason(market_row_code) == 1 and row_status(market_row_code) == 0)
        bool ex_event_ok = row_status(event_risk_row_code) >= 4 or row_reason(event_risk_row_code) == 1
        string ex_event_text = dashboard_compact_event_risk_text(event_risk_row_code)
        int ex_htf_ok_count = (trend_htf_1 == 2 ? 1 : 0) + (trend_htf_2 == 2 ? 1 : 0) + (trend_htf_3 == 2 ? 1 : 0)
        bool ex_htf_ok = ex_htf_ok_count >= 2
        bool ex_pressure_ok = row_status(ltf_delta_row_code) >= 3
        string ex_ready_blocker = decode_ready_gate_text(ready_gate_row_code)
        string ex_strict_blocker = decode_strict_gate_text(strict_gate_row_code)
        // ── Next step derivation ──
        string ex_next_step = "Wait for a bullish structure break"
        if state_code >= 6
            ex_next_step := "Execute — follow the trade plan levels"
        else if state_code == 5
            ex_next_step := "Watch trigger level for break above"
        else if state_code == 4
            if ex_ready_blocker != "Ready"
                ex_next_step := "Blocked by: " + ex_ready_blocker
            else
                ex_next_step := "Wait for Ready gate to clear"
        else if state_code == 3 or state_code == 2
            ex_next_step := "Zone reclaim in progress — wait for confirmation"
        else if state_code == -1
            ex_next_step := "Setup invalidated — wait for a fresh reclaim"
        else if not ex_struct_ok
            ex_next_step := "Need bullish BOS or CHoCH"
        else if not ex_zone_ok
            ex_next_step := "Price must enter a demand zone (OB/FVG)"
        // ── Render ──
        color pass = color.new(CLR_BULL, 75)
        color fail = color.new(CLR_BEAR, 75)
        dashboard_row_tt(smc_dashboard, 0, "SMC Explain", ex_state_label + " | " + dashboard_universe_badge, ex_state_bg, txt, dashboard_universe_tt)
        dashboard_row(smc_dashboard, 1, "Zone Source", ex_source, header_bg, txt)
        section_row(smc_dashboard, 2, "[ Checklist ]", header_bg, txt)
        dashboard_row(smc_dashboard, 3, ex_struct_ok ? "✅ Structure" : "❌ Structure", ex_struct_event, ex_struct_ok ? pass : fail, txt)
        dashboard_row(smc_dashboard, 4, ex_zone_ok ? "✅ Zone" : "❌ Zone", ex_zone_type, ex_zone_ok ? pass : fail, txt)
        dashboard_row(smc_dashboard, 5, ex_quality_ok ? "✅ Signal Quality" : "❌ Signal Quality", ex_quality_tier, ex_quality_ok ? pass : fail, txt)
        dashboard_row(smc_dashboard, 6, ex_fresh_ok ? "✅ Freshness" : "❌ Freshness", ex_fresh_label, ex_fresh_ok ? pass : fail, txt)
        dashboard_row(smc_dashboard, 7, ex_session_ok ? "✅ Session" : "❌ Session", ex_session_ok ? "open" : "closed", ex_session_ok ? pass : fail, txt)
        dashboard_row(smc_dashboard, 8, ex_market_ok ? "✅ Market Gate" : "❌ Market Gate", ex_market_ok ? "clear" : "blocked", ex_market_ok ? pass : fail, txt)
        dashboard_row(smc_dashboard, 9, ex_event_ok ? "✅ Event Risk" : "❌ Event Risk", ex_event_text, ex_event_ok ? pass : fail, txt)
        dashboard_row(smc_dashboard, 10, ex_htf_ok ? "✅ HTF Trend" : "❌ HTF Trend", str.tostring(ex_htf_ok_count) + "/3 bullish", ex_htf_ok ? pass : fail, txt)
        dashboard_row(smc_dashboard, 11, ex_pressure_ok ? "✅ Pressure" : "❌ Pressure", ex_pressure_ok ? "supports long" : "mixed / against", ex_pressure_ok ? pass : fail, txt)
        // Phase H: FVG health checklist item (Phase-H follow-on issue #16:
        // now also surfaces aggregate cal-HR warnings for OB/BOS/SWEEP and
        // the ZONE_CAL_TRUST glyph as a row suffix.)
        float _ex_fvg_health = resolve_fvg_health_score()
        bool ex_fvg_ok = mp.PRIMARY_FVG_SIDE == "NONE" or _ex_fvg_health >= 30.0
        string ex_fvg_label = mp.PRIMARY_FVG_SIDE == "NONE" ? "no FVG" : fvg_health_tier(_ex_fvg_health)
        string _ex_fvg_warn = fvg_combined_warning_text(_ex_fvg_health, mp.ZONE_HR_FVG)
        string _ex_zone_cal_warn = zone_cal_aggregate_warning_text()
        string _ex_trust_glyph = zone_cal_trust_glyph(mp.ZONE_CAL_TRUST)
        string _ex_fvg_combined = _ex_fvg_warn != "" and _ex_zone_cal_warn != "" ? _ex_fvg_warn + " | " + _ex_zone_cal_warn : _ex_fvg_warn != "" ? _ex_fvg_warn : _ex_zone_cal_warn
        string ex_fvg_detail = _ex_fvg_combined != "" ? ex_fvg_label + " — " + _ex_fvg_combined : ex_fvg_label
        ex_fvg_detail := _ex_trust_glyph != "" ? ex_fvg_detail + " " + _ex_trust_glyph : ex_fvg_detail
        // Calibrated-HR drives a hard fail too — not just live-zone health.
        // Any family below threshold OR DEGRADED/UNAVAILABLE trust state fails the row.
        bool ex_fvg_cal_ok = mp.ZONE_HR_FVG <= 0.0 or mp.ZONE_HR_FVG >= 0.65
        bool ex_zone_cal_ok = _ex_zone_cal_warn == ""
        bool ex_trust_ok = mp.ZONE_CAL_TRUST != "DEGRADED" and mp.ZONE_CAL_TRUST != "UNAVAILABLE"
        bool ex_fvg_pass = ex_fvg_ok and ex_fvg_cal_ok and ex_zone_cal_ok and ex_trust_ok
        // Tooltip carries the calibration reliability score (H1; a sample-size x
        // smECE readiness HEURISTIC, not a probability — confidence-vocabulary
        // program) + trend (H3) for diagnostic depth; falls back to plain
        // dashboard_row when the field is uninitialised (sentinel 0.0).
        string _ex_fvg_tt = zone_cal_diagnostics_tooltip_text(mp.ZONE_CAL_RELIABILITY_SCORE, mp.ZONE_CAL_TREND)
        if _ex_fvg_tt == ""
            dashboard_row(smc_dashboard, 12, ex_fvg_pass ? "✅ FVG Health" : "⚠ FVG Health", ex_fvg_detail, ex_fvg_pass ? pass : color.new(CLR_CAUTION, 75), txt)
        else
            dashboard_row_tt(smc_dashboard, 12, ex_fvg_pass ? "✅ FVG Health" : "⚠ FVG Health", ex_fvg_detail, ex_fvg_pass ? pass : color.new(CLR_CAUTION, 75), txt, _ex_fvg_tt)
        section_row(smc_dashboard, 13, "[ Next Step ]", header_bg, txt)
        dashboard_row(smc_dashboard, 14, "→", ex_next_step, ex_state_bg, txt)
        table.clear(smc_dashboard, 0, 15, 1, 79)
    else if compact_dashboard
        // WP-OV5: 5-row compact dashboard for mobile / small screens
        string lib_regime = mp.MARKET_REGIME
        float lib_vix = mp.VIX_LEVEL
        string lib_tone_c = mp.TONE
        string market_line = lib_regime + ' · VIX ' + str.tostring(lib_vix, '#.#') + ' · ' + lib_tone_c
        color market_bg = mp.TRADE_STATE == "BLOCKED" ? color.new(CLR_BEAR, 70) : mp.TRADE_STATE == "DISCOURAGED" ? color.new(CLR_CAUTION, 70) : color.new(CLR_BULL_ALT, 80)
        // ENG-WS2-03: append the canonical product trust badge to the Trust /
        // Data row so stale/degraded/watch-only is visible without entering
        // Audit View. The legacy operator-grade trust text stays as the
        // prefix; the badge is suffixed and the row state is clamped to the
        // worse of the legacy state and the product trust state.
        int _compact_legacy_trust_state = dashboard_compact_trust_state(lean_sq_row_code, lean_freshness_row_code)
        int _compact_product_trust_state = product_trust_badge_state(mp.TRUST_STATE)
        int _compact_trust_state = _compact_legacy_trust_state < _compact_product_trust_state ? _compact_legacy_trust_state : _compact_product_trust_state
        string _compact_trust_text = dashboard_compact_trust_text(lean_sq_row_code, lean_freshness_row_code, volume_data_row_code, event_risk_row_code, ready_gate_row_code, strict_gate_row_code, mp.ENSEMBLE_QUALITY_TIER) + " | data: " + product_trust_badge_text(mp.TRUST_STATE, mp.TRUST_DEGRADATION_REASON)
        dashboard_row_tt(smc_dashboard, 0, "SMC Compact", "Decision Brief | " + dashboard_universe_badge, header_bg, txt, dashboard_universe_tt)
        dashboard_row(smc_dashboard, 1, "Market", market_line, market_bg, txt)
        dashboard_row(smc_dashboard, 2, "Structure", dashboard_compact_structure_text(state_code, zone_code, lean_struct_row_code, reclaim_code), status_bg(setup_state), txt)
        dashboard_row_tt(smc_dashboard, 3, "Trust / Data", _compact_trust_text + " | " + dashboard_universe_badge, status_bg(_compact_trust_state < dashboard_universe_state ? _compact_trust_state : dashboard_universe_state), txt, dashboard_universe_tt)
        dashboard_row(smc_dashboard, 4, "Risk Plan", dashboard_compact_risk_plan_text(actionable_state, src_trigger, src_stop_level, src_target_1, src_target_2), status_bg(actionable_state ? 5 : 0), txt)
        table.clear(smc_dashboard, 0, 5, 1, 79)
    else if surface_mode == "Decision Brief"
        // Market Context from Library
        string lib_regime = mp.MARKET_REGIME
        float lib_vix = mp.VIX_LEVEL
        string lib_trade_state = mp.TRADE_STATE
        bool lib_macro_event = mp.HIGH_IMPACT_MACRO_TODAY
        string lib_event_name = mp.MACRO_EVENT_NAME
        string lib_tone_db = mp.TONE
        float lib_heat_db = mp.GLOBAL_HEAT
        string lib_vol_regime_db = mp.VOLATILITY_REGIME
        string market_line = lib_regime + ' · VIX ' + str.tostring(lib_vix, '#.#') + ' · ' + lib_tone_db + ' · Heat ' + str.tostring(lib_heat_db, '#.##')
        if lib_vol_regime_db != "" and lib_vol_regime_db != "NORMAL"
            market_line += ' · Vol:' + lib_vol_regime_db
        if lib_macro_event
            market_line += ' · ' + lib_event_name
        color market_bg = lib_trade_state == "BLOCKED" ? color.new(CLR_BEAR, 70) : lib_trade_state == "DISCOURAGED" ? color.new(CLR_CAUTION, 70) : color.new(CLR_BULL_ALT, 80)

        dashboard_row(smc_dashboard, 0, "SMC Decision Board v7", "Decision Brief | Linked setup active", header_bg, txt)
        dashboard_row(smc_dashboard, 1, "Market", market_line, market_bg, txt)
        dashboard_row(smc_dashboard, 2, "Structure", dashboard_compact_structure_text(state_code, zone_code, lean_struct_row_code, reclaim_code), status_bg(setup_state), txt)
        dashboard_row(smc_dashboard, 3, "Session / Market", dashboard_compact_session_market_text(session_row_code, market_row_code), status_bg(dashboard_compact_session_market_state(session_row_code, market_row_code)), txt)
        dashboard_row(smc_dashboard, 4, "Event Risk", dashboard_compact_event_risk_text(event_risk_row_code), status_bg(row_status(event_risk_row_code)), txt)
        // Zone Priority from generated library (C9)
        string zp_rank = mp.ZONE_PRIORITY_RANK
        int zp_score = mp.ZONE_PRIORITY_SCORE
        string zp_catalyst = mp.ZONE_PRIORITY_CATALYST
        string zp_line = zp_rank + " (" + str.tostring(zp_score) + "/100)"
        if zp_catalyst != "NONE" and zp_catalyst != ""
            zp_line += " · " + zp_catalyst
        color zp_bg = zp_rank == "A" ? color.new(CLR_BULL_ALT, 75) : zp_rank == "B" ? color.new(CLR_BULL_ALT, 85) : zp_rank == "C" ? color.new(CLR_CAUTION, 80) : color.new(CLR_BEAR, 80)
        // Plan 1.5 — Explain-popup: hover shows family/HR/trust/reason for this zone.
        string zp_brief_tt = "Zone " + zp_rank + " — Why?\nTop Family: " + mp.ZONE_PRIORITY_TOP_FAMILY + "\nScore: " + str.tostring(zp_score) + "/100\nTrust Tier: " + mp.HERO_TRUST + (mp.ZONE_PRIORITY_REASON != "" ? "\nReason: " + mp.ZONE_PRIORITY_REASON : "") + (zp_catalyst != "NONE" and zp_catalyst != "" ? "\nCatalyst: " + zp_catalyst : "")
        dashboard_row_tt(smc_dashboard, 5, "Zone Priority", zp_line, zp_bg, txt, zp_brief_tt)
        // ENG-WS2-03: same product trust badge augmentation as the SMC
        // Compact view (see comment above). Decision Brief is also a
        // default surface, so a degraded data lage must be visible here
        // without switching to Audit View.
        int _brief_legacy_trust_state = dashboard_compact_trust_state(lean_sq_row_code, lean_freshness_row_code)
        int _brief_product_trust_state = product_trust_badge_state(mp.TRUST_STATE)
        int _brief_trust_state = _brief_legacy_trust_state < _brief_product_trust_state ? _brief_legacy_trust_state : _brief_product_trust_state
        string _brief_trust_text = dashboard_compact_trust_text(lean_sq_row_code, lean_freshness_row_code, volume_data_row_code, event_risk_row_code, ready_gate_row_code, strict_gate_row_code, mp.ENSEMBLE_QUALITY_TIER) + " | data: " + product_trust_badge_text(mp.TRUST_STATE, mp.TRUST_DEGRADATION_REASON)
        dashboard_row_tt(smc_dashboard, 6, "Trust / Data", _brief_trust_text + " | " + dashboard_universe_badge, status_bg(_brief_trust_state < dashboard_universe_state ? _brief_trust_state : dashboard_universe_state), txt, dashboard_universe_tt)
        dashboard_row(smc_dashboard, 7, "Short-term Pressure", dashboard_compact_pressure_text(ltf_delta_row_code, src_ltf_volume_delta, swing_row_code), status_bg(row_status(ltf_delta_row_code)), txt)
        dashboard_row(smc_dashboard, 8, "Risk Plan", dashboard_compact_risk_plan_text(actionable_state, src_trigger, src_stop_level, src_target_1, src_target_2), status_bg(actionable_state ? 5 : 0), txt)
        table.clear(smc_dashboard, 0, 9, 1, 79)
    else if surface_mode == "Hero"
        // ── Hero Surface (PR 2) ──
        // Reads the hero state contract from the generated library and exposes
        // the seven decision-first signals: Market, Action, Why now, Setup Quality,
        // Trust, Risk, Risk Plan. Row positions match the existing audit-view
        // labelling convention so substring-based UI contracts remain intact.
        string h_action = mp.HERO_ACTION
        string h_bias = mp.HERO_BIAS
        // Issue #55 — "UNKNOWN"/"FLAT" are both omitted from the appended bias chip;
        // UNKNOWN means "no enrichment run yet", FLAT means "resolved flat".
        string h_action_line = h_action + ((h_bias != "FLAT" and h_bias != "UNKNOWN") ? " · " + h_bias : "")
        color h_action_bg = h_action == "ACTIVE" ? color.new(CLR_BULL, 70) : h_action == "BLOCKED" ? color.new(CLR_BEAR, 70) : h_action == "AVOID" ? color.new(CLR_BEAR, 80) : color.new(CLR_NEUTRAL, 75)

        string h_market = mp.HERO_MARKET_MODE
        string h_vol = mp.VOLATILITY_REGIME
        // Issue #55 waiting-state rendering — grey "awaiting first enrichment run".
        bool h_market_waiting = h_market == "UNKNOWN"
        string h_market_line = h_market_waiting ? "⚪ awaiting data" : h_market
        if not h_market_waiting and h_vol != "" and h_vol != "NORMAL"
            h_market_line += " · Vol:" + h_vol
        color h_market_bg = h_market_waiting ? color.new(color.gray, 80) : h_market == "BULLISH" ? color.new(CLR_BULL_ALT, 80) : (h_market == "BEARISH" or h_market == "RISK_OFF") ? color.new(CLR_BEAR, 80) : color.new(CLR_NEUTRAL, 80)

        string h_why = mp.HERO_WHY_NOW
        string h_why_line = h_why != "" ? h_why : "—"
        color h_why_bg = h_why != "" ? color.new(CLR_BULL_ALT, 85) : header_bg

        string h_quality = mp.HERO_SETUP_QUALITY
        // Issue #55 — "unavailable" is the waiting-state sentinel (distinct from
        // "low"); it MUST resolve to a neutral background, not the bearish -1
        // bucket that status_bg renders for actual negative states.
        int h_quality_state = (h_quality == "high" or h_quality == "good") ? 5 : h_quality == "ok" ? 3 : 0
        string h_quality_line = h_quality == "unavailable" ? "⚪ awaiting data" : h_quality
        color h_quality_bg = h_quality == "unavailable" ? color.new(color.gray, 80) : status_bg(h_quality_state)

        string h_trust = mp.HERO_TRUST
        // ENG-WS2-03: hero trust row now also carries the canonical product
        // trust state + degradation reason coming from the generated library
        // (mp.TRUST_STATE / mp.TRUST_DEGRADATION_REASON). Hero State stays
        // the leading signal — the product trust badge is appended after a
        // separator so a single-glance read still works.
        int h_trust_state_legacy = h_trust == "healthy" ? 5 : h_trust == "warmup" ? 3 : h_trust == "degraded" ? 2 : -1
        int h_trust_state_product = product_trust_badge_state(mp.TRUST_STATE)
        int h_trust_state = h_trust_state_legacy < h_trust_state_product ? h_trust_state_legacy : h_trust_state_product
        string h_trust_line = h_trust + ((h_trust == "degraded" or h_trust == "stale" or h_trust == "unavailable") ? " ⚠" : "") + " · data: " + product_trust_badge_text(mp.TRUST_STATE, mp.TRUST_DEGRADATION_REASON)

        string h_risk = mp.HERO_RISK
        string h_risk_line = h_risk != "" ? h_risk : "—"
        int h_risk_state = (h_risk == "DATA_STALE" or h_risk == "EVENT_RISK") ? -1 : (h_risk == "VOLATILITY" or h_risk == "PROVIDER_GAPS") ? 2 : h_risk == "" ? 5 : 3

        dashboard_row(smc_dashboard, 0, "SMC Decision Board v7", "Hero | Decision-first surface", header_bg, txt)
        // Plan 1.1 — product hero one-liner subtitle: SMC · BIAS · TIER · Top FAM HR% · Zone #N · ⚠BLOCKER
        // Reads from generated mp.* contract. Cheap to render, no extra state.
        string _hero_fam = mp.ZONE_PRIORITY_TOP_FAMILY
        float _hero_fam_w = resolve_family_cal_weight(_hero_fam)
        int _hero_fam_pct = int(math.round(_hero_fam_w * 100))
        string _hero_blocker = mp.HERO_RISK != "" ? mp.HERO_RISK : (mp.HERO_TRUST == "degraded" or mp.HERO_TRUST == "stale" ? "TRUST" : "")
        // Plan §3.1.2 — CAL-BREACH overrides any other blocker because the
        // operator has explicitly flagged a 30-day smECE SLO breach.
        if calibration_breach_banner
            _hero_blocker := "CAL-BREACH"
        string _hero_one = compose_hero_one_liner(hero_token_order, mp.HERO_BIAS, mp.HERO_TRUST, _hero_fam, _hero_fam_pct, mp.ZONE_PRIORITY_RANK, _hero_blocker, calibration_sha)
        color _hero_one_bg = mp.HERO_TRUST == "healthy" ? color.new(CLR_TIER_T1, 70) : mp.HERO_TRUST == "warmup" ? color.new(CLR_TIER_T2, 75) : mp.HERO_TRUST == "degraded" ? color.new(CLR_TIER_T3, 75) : color.new(CLR_TIER_T4, 75)
        // Plan §2.5 H5 — Onboarding tooltip on Hero row when Custom preset
        // is active. Reads BUS PresetClassCode (0 = Custom). When a curated
        // preset is bound, the tooltip switches to a calibrated-defaults
        // hint instead of the onboarding nudge, so the surface stays useful
        // for advanced users.
        int _hero_preset_code = int(math.round(src_preset_class_code))
        string _hero_one_tt = (_hero_preset_code == 0 ? "Onboarding · 4 Clicks to first calibrated signal:\n  1. Open Settings → Quickstart Preset.\n  2. Pick Mega-Cap US Tech / Financial Services / Energy.\n  3. Confirm — preset raises floors (RVOL, HTF Bias, FVG Quality), never lowers your inputs.\n  4. Watch the Hero row update; Trust badge turns T1/T2 once the calibrated stack agrees.\nSource: the Quickstart Preset input in SMC Long-Dip Suite." : "Curated preset active (class " + str.tostring(_hero_preset_code) + ") — calibrated floors applied to RVOL/HTF/FVG/Vol inputs. You can still tighten any input above the preset floor; the preset never lowers your value.\nSource: preset floors published by SMC Long-Dip Suite.") + "\n\n" + dashboard_universe_tt
        string _hero_one_display = _hero_one + " · " + dashboard_universe_badge
        dashboard_row_tt(smc_dashboard, 1, "Hero", _hero_one_display, _hero_one_bg, txt, _hero_one_tt)
        dashboard_row(smc_dashboard, 2, "Market", h_market_line, h_market_bg, txt)
        dashboard_row(smc_dashboard, 3, "Action", h_action_line, h_action_bg, txt)
        dashboard_row(smc_dashboard, 4, "Why now", h_why_line, h_why_bg, txt)
        dashboard_row(smc_dashboard, 5, "Setup Quality", h_quality_line, h_quality_bg, txt)
        dashboard_row(smc_dashboard, 6, "Trust", h_trust_line, status_bg(h_trust_state), txt)
        dashboard_row(smc_dashboard, 7, "Risk", h_risk_line, status_bg(h_risk_state), txt)
        dashboard_row(smc_dashboard, 8, "Risk Plan", dashboard_compact_risk_plan_text(actionable_state, src_trigger, src_stop_level, src_target_1, src_target_2), status_bg(actionable_state ? 5 : 0), txt)
        table.clear(smc_dashboard, 0, 9, 1, 79)
    else
        dashboard_row(smc_dashboard, 0, "SMC Decision Board v7", "Audit View | Expert review only", header_bg, txt)
        // ── AUDIT: decision detail first, then lean review, diagnostics, risk plan, and explicit debug ──
        section_row(smc_dashboard, 1, "[ Decision Detail ]", header_bg, txt)
        dashboard_row(smc_dashboard, 2, "Action", dashboard_product_state_text(state_code), status_bg(state_code), txt)
        dashboard_row(smc_dashboard, 3, "Why now", dashboard_compact_why_now_text(state_code, zone_code, lean_struct_row_code, lean_ob_row_code, lean_fvg_row_code, ready_gate_row_code, strict_gate_row_code), status_bg(actionable_state ? 5 : state_code >= 2 ? 3 : row_status(ready_gate_row_code)), txt)
        dashboard_row(smc_dashboard, 4, "Trend", trend_text(trend_now), status_bg(trend_now == 2 ? 5 : trend_now == 0 ? -1 : 0), txt)
        // Addendum 2.8 (2026-04-21) — intentional 3-layer ICT trend stack;
        // see docs/smc_improvement_plan_addendum_2_8_mtf_scope_2026-04-21.md
        string _htf_trend_tt = "Trend Stack: 4H · 1D · 1W (ICT-standard 3-layer, factor-~4 spacing).\nIntentional scope: 3 layers + adaptive IPDA, not a 7-TF stack. A 4th intermediate layer (2H) is scaffolded behind the 'Enable 2H pre-trend layer (Q4-gated)' input in SMC Long-Dip Suite (default OFF) and activates only after its evaluation gate returns GO (G1 ≥3pp HR uplift / G2 Brier ≤0.02 / G3 ≥30 events per bucket)."
        dashboard_row_tt(smc_dashboard, 5, "HTF Trend", "HTF1:" + trend_text(trend_htf_1) + " | HTF2:" + trend_text(trend_htf_2) + " | HTF3:" + trend_text(trend_htf_3), status_bg(htf_state), txt, _htf_trend_tt)
        dashboard_row(smc_dashboard, 6, "Long Setup", setup_text(state_code, source_code), status_bg(setup_state), txt)
        dashboard_row(smc_dashboard, 7, "Setup Age", setup_age_text(state_code, freshness_code), status_bg(setup_age_state), txt)
        dashboard_row(smc_dashboard, 8, "Long Visual", long_visual_text(state_code), status_bg(setup_state), txt)
        dashboard_row(smc_dashboard, 9, "Exec Tier", exec_tier_text(state_code), status_bg(exec_state), txt)
        section_row(smc_dashboard, 10, "[ Lean Surface ]", header_bg, txt)
        dashboard_row(smc_dashboard, 11, "Lean | Signal Quality", decode_lean_sq_text(lean_sq_row_code), status_bg(row_status(lean_sq_row_code)), txt)
        dashboard_row(smc_dashboard, 12, "Lean | Freshness", decode_lean_freshness_text(lean_freshness_row_code), status_bg(row_status(lean_freshness_row_code)), txt)
        dashboard_row(smc_dashboard, 13, "Lean | Event Risk", decode_lean_erl_text(lean_erl_row_code), status_bg(row_status(lean_erl_row_code)), txt)
        dashboard_row(smc_dashboard, 14, "Lean | Structure", decode_lean_struct_text(lean_struct_row_code), status_bg(row_status(lean_struct_row_code)), txt)
        dashboard_row(smc_dashboard, 15, "Lean | Order Block", decode_lean_ob_text(lean_ob_row_code), status_bg(row_status(lean_ob_row_code)), txt)
        dashboard_row(smc_dashboard, 16, "Lean | FVG", decode_lean_fvg_text(lean_fvg_row_code), status_bg(row_status(lean_fvg_row_code)), txt)
        dashboard_row(smc_dashboard, 17, "Lean | Session", decode_lean_session_text(lean_session_row_code), status_bg(row_status(lean_session_row_code)), txt)
        // ── Trust & Provider surface (v5.5b) — read-only visibility, no signal model change ──
        string _audit_sq_tier = decode_lean_sq_text(lean_sq_row_code)
        string _audit_sq_fresh = row_status(lean_freshness_row_code) >= 5 ? "fresh" : row_status(lean_freshness_row_code) == 3 ? "aging" : "stale"
        string _audit_prov = dashboard_provider_state_text(mp.EVENT_PROVIDER_STATUS)
        string _audit_trust = resolve_dashboard_trust_tier(_audit_sq_tier, _audit_sq_fresh, _audit_prov)
        int _audit_trust_state = dashboard_compact_trust_state(lean_sq_row_code, lean_freshness_row_code)
        int _audit_prov_state = _audit_prov == "ok" ? 5 : -1
        bool _audit_degraded = _audit_trust == "degraded" or _audit_trust == "insufficient"
        string _audit_warn_raw = mp.SIGNAL_WARNINGS
        string _audit_warn_suffix = _audit_warn_raw != "" and _audit_warn_raw != "n/a" ? " | ⚠ " + _audit_warn_raw : ""
        string _audit_degrad_text = (_audit_degraded ? (_audit_trust == "insufficient" ? "Insufficient — no measurement data" : "Degraded — " + (_audit_prov != "ok" ? "provider issue" : "data stale")) : "No degradation") + _audit_warn_suffix
        int _audit_degrad_state = _audit_degraded ? (_audit_trust == "insufficient" ? -1 : 2) : _audit_warn_raw != "" and _audit_warn_raw != "n/a" ? 3 : 5
        section_row(smc_dashboard, 18, "[ Trust & Provider ]", header_bg, txt)
        // Plan §3.1.1 — append SHA-anchored calibration receipt so a screenshot
        // of row 19 is independently verifiable against zone_priority_calibration.json.
        // Empty calibration_sha input → suffix omitted (no surface change).
        string _audit_cal_sha_trim = str.length(calibration_sha) > 7 ? str.substring(calibration_sha, 0, 7) : calibration_sha
        string _audit_cal_sha_suffix = _audit_cal_sha_trim != "" ? " (cal sha " + _audit_cal_sha_trim + ")" : ""
        dashboard_row(smc_dashboard, 19, "Trust Tier", _audit_trust + " | quality " + _audit_sq_tier + " | " + _audit_sq_fresh + _audit_cal_sha_suffix, status_bg(_audit_trust_state), txt)
        dashboard_row(smc_dashboard, 20, "Provider State", _audit_prov, status_bg(_audit_prov_state), txt)
        dashboard_row(smc_dashboard, 21, "Degradation", _audit_degrad_text, status_bg(_audit_degrad_state), txt)
        // Zone Priority from generated library (C9)
        string _audit_zp_rank = mp.ZONE_PRIORITY_RANK
        int _audit_zp_score = mp.ZONE_PRIORITY_SCORE
        string _audit_zp_family = mp.ZONE_PRIORITY_TOP_FAMILY
        string _audit_zp_catalyst = mp.ZONE_PRIORITY_CATALYST
        string _audit_zp_reason = mp.ZONE_PRIORITY_REASON
        string _audit_zp_line = _audit_zp_rank + " (" + str.tostring(_audit_zp_score) + "/100) · " + _audit_zp_family
        if _audit_zp_catalyst != "NONE" and _audit_zp_catalyst != ""
            _audit_zp_line += " · " + _audit_zp_catalyst
        if _audit_zp_reason != ""
            _audit_zp_line += " — " + _audit_zp_reason
        color _audit_zp_bg = _audit_zp_rank == "A" ? color.new(CLR_BULL_ALT, 75) : _audit_zp_rank == "B" ? color.new(CLR_BULL_ALT, 85) : _audit_zp_rank == "C" ? color.new(CLR_CAUTION, 80) : color.new(CLR_BEAR, 80)
        // Plan 1.5 — Explain-popup with full reason + calibrated weight context.
        float _audit_zp_top_w = resolve_family_cal_weight(_audit_zp_family)
        string _audit_zp_tt = "Zone " + _audit_zp_rank + " — Why?\nTop Family: " + _audit_zp_family + " (calibrated weight " + format_cal_weight(_audit_zp_top_w) + ")\nScore: " + str.tostring(_audit_zp_score) + "/100\nTrust Tier: " + _audit_trust + " · Provider: " + _audit_prov + (_audit_zp_reason != "" ? "\nReason: " + _audit_zp_reason : "") + (_audit_zp_catalyst != "NONE" and _audit_zp_catalyst != "" ? "\nCatalyst: " + _audit_zp_catalyst : "") + "\nSource: zone_priority_calibration.json"
        dashboard_row_tt(smc_dashboard, 22, "Zone Priority", _audit_zp_line, _audit_zp_bg, txt, _audit_zp_tt)
        // ── Phase H: Calibration Confidence + Per-Family Performance ──
        float _audit_top_cal = resolve_family_cal_weight(_audit_zp_family)
        int _audit_top_cal_state = calibration_confidence_state(_audit_top_cal)
        string _audit_top_cal_text = _audit_zp_family + " " + format_cal_weight(_audit_top_cal) + " (" + calibration_confidence_tier(_audit_top_cal) + ")"
        float _audit_cal_avg = (mp.ZONE_CAL_OB + mp.ZONE_CAL_FVG + mp.ZONE_CAL_BOS + mp.ZONE_CAL_SWEEP) / 4.0
        int _audit_cal_avg_state = calibration_confidence_state(_audit_cal_avg)
        section_row(smc_dashboard, 23, "[ Calibration Confidence ]", header_bg, txt)
        dashboard_row(smc_dashboard, 24, "Top Family Confidence", _audit_top_cal_text, status_bg(_audit_top_cal_state), txt)
        dashboard_row(smc_dashboard, 25, "Composite Confidence", format_cal_weight(_audit_cal_avg) + " (" + calibration_confidence_tier(_audit_cal_avg) + ")", status_bg(_audit_cal_avg_state), txt)
        section_row(smc_dashboard, 26, "[ Per-Family Performance ]", header_bg, txt)
        // Plan 1.5 — per-family tooltips: calibrated weight + tier in hover.
        dashboard_row_tt(smc_dashboard, 27, "OB",    family_performance_text("OB",    mp.ZONE_CAL_OB),    status_bg(calibration_confidence_state(mp.ZONE_CAL_OB)),    txt, "OB — calibrated weight "    + format_cal_weight(mp.ZONE_CAL_OB)    + " (" + calibration_confidence_tier(mp.ZONE_CAL_OB)    + ") · source: zone_priority_calibration.json")
        dashboard_row_tt(smc_dashboard, 28, "FVG",   family_performance_text("FVG",   mp.ZONE_CAL_FVG),   status_bg(calibration_confidence_state(mp.ZONE_CAL_FVG)),   txt, "FVG — calibrated weight "   + format_cal_weight(mp.ZONE_CAL_FVG)   + " (" + calibration_confidence_tier(mp.ZONE_CAL_FVG)   + ") · source: zone_priority_calibration.json")
        dashboard_row_tt(smc_dashboard, 29, "BOS",   family_performance_text("BOS",   mp.ZONE_CAL_BOS),   status_bg(calibration_confidence_state(mp.ZONE_CAL_BOS)),   txt, "BOS — calibrated weight "   + format_cal_weight(mp.ZONE_CAL_BOS)   + " (" + calibration_confidence_tier(mp.ZONE_CAL_BOS)   + ") · source: zone_priority_calibration.json")
        dashboard_row_tt(smc_dashboard, 30, "SWEEP", family_performance_text("SWEEP", mp.ZONE_CAL_SWEEP), status_bg(calibration_confidence_state(mp.ZONE_CAL_SWEEP)), txt, "SWEEP — calibrated weight " + format_cal_weight(mp.ZONE_CAL_SWEEP) + " (" + calibration_confidence_tier(mp.ZONE_CAL_SWEEP) + ") · source: zone_priority_calibration.json")
        // ── Phase H: FVG Health Warning ──
        float _audit_fvg_health = resolve_fvg_health_score()
        string _audit_fvg_warn = fvg_combined_warning_text(_audit_fvg_health, mp.ZONE_HR_FVG)
        section_row(smc_dashboard, 31, "[ FVG Health ]", header_bg, txt)
        // Plan §D2 — per-context tooltip + optional warn-row from operator-pasted
        // pine/generated/fvg_context_health.json sidecar. Empty input → legacy
        // single-number FVG Status row only (no surface change).
        string _audit_fvg_status_label = fvg_health_text(_audit_fvg_health)
        string _audit_fvg_ctx_tt = fvg_context_health_text != "" ? "FVG per-context (D2):\n" + fvg_context_health_text : "FVG Status — single-number summary. Paste per-context detail into the 'FVG Context Health' input to see the D2 breakdown."
        dashboard_row_tt(smc_dashboard, 32, "FVG Status", _audit_fvg_status_label, status_bg(fvg_health_state(_audit_fvg_health)), txt, _audit_fvg_ctx_tt)
        if _audit_fvg_warn != ""
            dashboard_row(smc_dashboard, 33, "FVG Warning", _audit_fvg_warn, color.new(CLR_CAUTION, 75), txt)
        else
            dashboard_row(smc_dashboard, 33, "FVG Warning", "—", header_bg, txt)
        section_row(smc_dashboard, 34, "[ Gates ]", header_bg, txt)
        dashboard_row(smc_dashboard, 35, "Session", decode_session_text(session_row_code), status_bg(row_status(session_row_code)), txt)
        dashboard_row(smc_dashboard, 36, "Market Gate", decode_market_text(market_row_code), status_bg(row_status(market_row_code)), txt)
        dashboard_row(smc_dashboard, 37, "Vola Regime", decode_vola_text(vola_row_code), status_bg(row_status(vola_row_code)), txt)
        dashboard_row(smc_dashboard, 38, "Micro Session", decode_micro_session_text(micro_session_row_code), status_bg(row_status(micro_session_row_code)), txt)
        dashboard_row(smc_dashboard, 39, "Micro Fresh", decode_micro_fresh_text(micro_fresh_row_code, freshness_code, source_state_code), status_bg(row_status(micro_fresh_row_code)), txt)
        dashboard_row(smc_dashboard, 40, "Volume Data", decode_volume_data_text(volume_data_row_code), status_bg(row_status(volume_data_row_code)), txt)
        dashboard_row(smc_dashboard, 41, "Event Risk", decode_event_risk_text(event_risk_row_code), status_bg(row_status(event_risk_row_code)), txt)
        section_row(smc_dashboard, 42, "[ Quality Rows ]", header_bg, txt)
        dashboard_row(smc_dashboard, 43, "Primary | Quality Env", decode_quality_env_text(quality_env_row_code), primary_metric_bg(row_status(quality_env_row_code)), txt)
        dashboard_row(smc_dashboard, 44, "Diag | Quality Strict", decode_quality_strict_text(quality_strict_row_code), diagnostic_metric_bg(row_status(quality_strict_row_code)), txt)
        dashboard_row(smc_dashboard, 45, "Diag | Close Strength", decode_close_text(close_row_code), diagnostic_metric_bg(row_status(close_row_code)), txt)
        dashboard_row(smc_dashboard, 46, "Diag | EMA Support", decode_ema_text(ema_row_code), diagnostic_metric_bg(row_status(ema_row_code)), txt)
        dashboard_row(smc_dashboard, 47, "Diag | ADX", decode_adx_text(adx_row_code, src_adx_value), diagnostic_metric_bg(row_status(adx_row_code)), txt)
        dashboard_row(smc_dashboard, 48, "Diag | Rel Volume", decode_relvol_text(relvol_row_code, src_relvol_value), diagnostic_metric_bg(row_status(relvol_row_code)), txt)
        dashboard_row(smc_dashboard, 49, "Diag | VWAP Filter", decode_vwap_text(vwap_row_code, src_session_vwap), diagnostic_metric_bg(row_status(vwap_row_code)), txt)
        dashboard_row(smc_dashboard, 50, "Diag | Context Quality", decode_context_quality_text(context_quality_row_code), diagnostic_metric_bg(row_status(context_quality_row_code)), txt)
        dashboard_row(smc_dashboard, 51, "Primary | Signal + Ensemble", quality_stack_text(src_quality_score, lean_transport_row_code), primary_metric_bg(row_status(quality_score_row_code)), txt)
        dashboard_row(smc_dashboard, 52, "Primary | Quality Clean", decode_quality_clean_text(quality_clean_row_code), primary_metric_bg(row_status(quality_clean_row_code)), txt)
        section_row(smc_dashboard, 53, "[ Support / Metrics ]", header_bg, txt)
        dashboard_row(smc_dashboard, 54, "SD Confluence", decode_sd_confluence_text(sd_confluence_row_code), status_bg(row_status(sd_confluence_row_code)), txt)
        dashboard_row(smc_dashboard, 55, "SD Osc", decode_sd_osc_text(sd_osc_row_code), status_bg(row_status(sd_osc_row_code)), txt)
        dashboard_row(smc_dashboard, 56, "Vol Regime | Lib", decode_vol_regime_stack_text(vol_regime_row_code, lean_transport_row_code), status_bg(row_status(vol_regime_row_code)), txt)
        dashboard_row(smc_dashboard, 57, "Vol Squeeze", decode_vol_squeeze_text(vol_squeeze_row_code), status_bg(row_status(vol_squeeze_row_code)), txt)
        dashboard_row(smc_dashboard, 58, "Vol Expand", decode_vol_expand_text(vol_expand_row_code), status_bg(row_status(vol_expand_row_code)), txt)
        dashboard_row(smc_dashboard, 59, "Stretch", decode_stretch_text(stretch_row_code, src_stretch_z), status_bg(row_status(stretch_row_code)), txt)
        dashboard_row(smc_dashboard, 60, "DDVI", decode_ddvi_text(ddvi_row_code), status_bg(row_status(ddvi_row_code)), txt)
        dashboard_row(smc_dashboard, 61, "LTF Bias", decode_ltf_bias_text(ltf_bias_row_code, src_ltf_bull_share), status_bg(row_status(ltf_bias_row_code)), txt)
        dashboard_row(smc_dashboard, 62, "LTF Delta", decode_ltf_delta_text(ltf_delta_row_code, src_ltf_volume_delta), status_bg(row_status(ltf_delta_row_code)), txt)
        dashboard_row(smc_dashboard, 63, "Swing H/L", decode_swing_text(swing_row_code), status_bg(row_status(swing_row_code)), txt)
        section_row(smc_dashboard, 64, "[ Risk / Plan ]", header_bg, txt)
        dashboard_row(smc_dashboard, 65, "Long Zones", long_zones_text, status_bg(row_status(long_zones_row_code)), txt)
        dashboard_row(smc_dashboard, 66, "Long Triggers", trigger_text(row_status(long_triggers_row_code) == 5, src_trigger, src_invalidation), status_bg(row_status(long_triggers_row_code)), txt)
        dashboard_row(smc_dashboard, 67, "Micro Profile", decode_micro_profile_text(micro_profile_row_code), status_bg(row_status(micro_profile_row_code)), txt)
        dashboard_row(smc_dashboard, 68, "Risk Plan", plan_text(row_status(risk_plan_row_code) == 5, src_trigger, src_stop_level, src_target_1, src_target_2), status_bg(row_status(risk_plan_row_code)), txt)
        section_row(smc_dashboard, 69, "[ Debug ]", header_bg, txt)
        dashboard_row(smc_dashboard, 70, "Ready Gate", decode_ready_gate_text(ready_gate_row_code), status_bg(row_status(ready_gate_row_code)), txt)
        dashboard_row(smc_dashboard, 71, "Strict Gate", decode_strict_gate_text(strict_gate_row_code), status_bg(row_status(strict_gate_row_code)), txt)
        dashboard_row(smc_dashboard, 72, "Debug Flags", decode_debug_flags_text(debug_flags_row_code), status_bg(row_status(debug_flags_row_code)), txt)
        dashboard_row(smc_dashboard, 73, "Long Debug", long_debug_text, status_bg(row_status(debug_state_row_code)), txt)
        // Plan 1.5 follow-up — Why this Tier? drill-down. Pure read-only on the
        // already-published ZONE_CAL_* contract — no engine change. Sorts the
        // four families by calibrated weight and shows the top-3 inline so an
        // operator can answer "why does the engine pick OB over FVG today?"
        // in one glance. The full ranked breakdown lives in the tooltip.
        section_row(smc_dashboard, 74, "[ Why this Tier? ]", header_bg, txt)
        // Q3 F2 wiring: surface session-aware weights when the live
        // session matches a promoted bucket; otherwise fall back to the
        // global ZONE_CAL_<FAM>. The original ZONE_CAL_<FAM> reads
        // are preserved by resolve_family_cal_weight_session() for any
        // unmatched session (NY_PM/NONE).
        float _wt_ob    = resolve_family_cal_weight_session("OB",    mp.SESSION_CONTEXT)
        float _wt_fvg   = resolve_family_cal_weight_session("FVG",   mp.SESSION_CONTEXT)
        float _wt_bos   = resolve_family_cal_weight_session("BOS",   mp.SESSION_CONTEXT)
        float _wt_sweep = resolve_family_cal_weight_session("SWEEP", mp.SESSION_CONTEXT)
        array<string> _wt_names = array.from("OB", "FVG", "BOS", "SWEEP")
        array<float>  _wt_vals  = array.from(_wt_ob, _wt_fvg, _wt_bos, _wt_sweep)
        // Simple O(n²) sort — n=4, no perf concern, no extra dependency.
        for _wi = 0 to array.size(_wt_vals) - 2
            for _wj = _wi + 1 to array.size(_wt_vals) - 1
                if array.get(_wt_vals, _wj) > array.get(_wt_vals, _wi)
                    float _tmp_v = array.get(_wt_vals, _wi)
                    string _tmp_n = array.get(_wt_names, _wi)
                    array.set(_wt_vals,  _wi, array.get(_wt_vals, _wj))
                    array.set(_wt_names, _wi, array.get(_wt_names, _wj))
                    array.set(_wt_vals,  _wj, _tmp_v)
                    array.set(_wt_names, _wj, _tmp_n)
        string _wt_top3 = array.get(_wt_names, 0) + " " + format_cal_weight(array.get(_wt_vals, 0)) + " · " + array.get(_wt_names, 1) + " " + format_cal_weight(array.get(_wt_vals, 1)) + " · " + array.get(_wt_names, 2) + " " + format_cal_weight(array.get(_wt_vals, 2))
        // April-2026 multiline template + str.format instead of one giant
        // \n-concatenation. Leading spaces on the template's inner lines are
        // LITERAL string content (that is the multiline-string contract),
        // which is why they sit hard-left of this block's indentation: the
        // two spaces before each rank ARE the tooltip's list indent.
        string _wt_full = str.format("""Why this Tier? Ranked calibrated weights driving the Zone Priority pick:
  1. {0} — {1} ({2})
  2. {3} — {4} ({5})
  3. {6} — {7} ({8})
  4. {9} — {10} ({11})
Source: zone_priority_calibration.json""",
             array.get(_wt_names, 0), format_cal_weight(array.get(_wt_vals, 0)), calibration_confidence_tier(array.get(_wt_vals, 0)),
             array.get(_wt_names, 1), format_cal_weight(array.get(_wt_vals, 1)), calibration_confidence_tier(array.get(_wt_vals, 1)),
             array.get(_wt_names, 2), format_cal_weight(array.get(_wt_vals, 2)), calibration_confidence_tier(array.get(_wt_vals, 2)),
             array.get(_wt_names, 3), format_cal_weight(array.get(_wt_vals, 3)), calibration_confidence_tier(array.get(_wt_vals, 3)))
        dashboard_row_tt(smc_dashboard, 75, "Top-3 Weights", _wt_top3, status_bg(calibration_confidence_state(array.get(_wt_vals, 0))), txt, _wt_full)
        section_row(smc_dashboard, 76, "[ Universe Status ]", header_bg, txt)
        dashboard_row_tt(smc_dashboard, 77, "Universe Status", dashboard_universe_badge, status_bg(dashboard_universe_state), txt, dashboard_universe_tt)
else if barstate.islast
    // Clear the full table (incl. universe + trade-mgmt rows 76..79) when show_table is off.
    table.clear(smc_dashboard, 0, 0, 1, 79)
// ───────────────────────── Trade Management mirror ─────────────────────────
// Read-only mirror of SMC_Hold_Manager state machine. Renders 2 rows
// (Trade-State + live Stop) at table rows 78/79 when tm_enable is on.
// MIRROR-OF: SMC_Hold_Manager.pine §state-machine (FLAT/ARMED/IN_TRADE).
// No alerts here — by design. For audible alerts add SMC_Hold_Manager
// on the same chart.
tm_armed_flag     = not na(src_armed) and src_armed > 0.5
tm_confirmed_flag = not na(src_confirmed) and src_confirmed > 0.5
tm_ready_flag     = not na(src_ready) and src_ready > 0.5
tm_any_state      = tm_armed_flag or tm_confirmed_flag or tm_ready_flag
tm_risk_ok        = not na(src_trigger) and not na(src_invalidation) and src_trigger > src_invalidation

var int   tm_state       = 0
var float tm_entry_price = na
var float tm_entry_stop  = na
var float tm_tp1_price   = na
var float tm_tp2_price   = na
var bool  tm_tp1_hit     = false

if tm_enable
    if tm_state == 0 and tm_any_state and tm_risk_ok
        tm_state := 1
        tm_entry_price := src_trigger
        tm_entry_stop  := src_invalidation
        tm_tp1_price   := src_trigger + (src_trigger - src_invalidation) * tm_tp1_r
        tm_tp2_price   := src_trigger + (src_trigger - src_invalidation) * tm_tp2_r
        tm_tp1_hit     := false
    if tm_state == 1 and tm_risk_ok
        tm_entry_price := src_trigger
        tm_entry_stop  := src_invalidation
        tm_tp1_price   := src_trigger + (src_trigger - src_invalidation) * tm_tp1_r
        tm_tp2_price   := src_trigger + (src_trigger - src_invalidation) * tm_tp2_r
    if tm_state == 1 and not na(tm_entry_price) and high >= tm_entry_price
        tm_state := 2
    if tm_state == 1 and not tm_any_state
        tm_state := 0
        tm_entry_price := na, tm_entry_stop := na, tm_tp1_price := na, tm_tp2_price := na
    if tm_state == 2
        if not tm_tp1_hit and not na(tm_tp1_price) and high >= tm_tp1_price
            tm_tp1_hit := true
            if tm_be_after_t1 and not na(tm_entry_price)
                tm_entry_stop := math.max(tm_entry_stop, tm_entry_price)
        bool tm_full_exit = (not na(tm_entry_stop) and close < tm_entry_stop) or (not na(tm_tp2_price) and high >= tm_tp2_price)
        if tm_full_exit
            tm_state := 0
            tm_entry_price := na, tm_entry_stop := na, tm_tp1_price := na, tm_tp2_price := na, tm_tp1_hit := false

if show_table and barstate.islast and tm_enable
    string tm_state_txt = tm_state == 0 ? "FLAT" : tm_state == 1 ? "ARMED" : "IN-TRADE"
    color  tm_state_bg  = tm_state == 0 ? color.new(CLR_NEUTRAL, 80) : tm_state == 1 ? color.new(CLR_CAUTION, 70) : color.new(CLR_BULL, 65)
    string tm_stop_txt  = na(tm_entry_stop) ? "—" : str.tostring(tm_entry_stop, format.mintick) + (tm_tp1_hit and tm_be_after_t1 ? " (BE)" : "")
    color  tm_stop_bg   = tm_state == 2 ? color.new(CLR_BEAR, 65) : color.new(color.black, 100)
    dashboard_row(smc_dashboard, 78, "Trade", tm_state_txt, tm_state_bg, chart.fg_color)
    dashboard_row(smc_dashboard, 79, "Stop",  tm_stop_txt,  tm_stop_bg,  chart.fg_color)
else if barstate.islast and not tm_enable
    table.clear(smc_dashboard, 0, 78, 1, 79)

Overview

SMC Decision Board processes aggregated signal states from linked chart scripts to display a structured HUD table, trade plan risk lines, and live setup highlights directly on the price chart. The indicator features optional trade management preview rows, compact layout settings, and customizable visual themes. It provides read-only workflow support for evaluating order blocks, fair value gaps, and market structure without executing automated trades.

Details

Published by

skipp-dev on GitHub

Code credit

preuss_steffen, as written in the script's header

Type

Indicator, Pine Script v6

Draws

On the price chart

Category

SMC / ICT / price action, Screeners & dashboards, Support & resistance

Markets

Any symbol

Timeframes

Any timeframe

Risk Notes

No repainting, lookahead or stacked entries were found in its code or README. That says nothing about how well it works: test it first.

Settings

The inputs you can change from the gear icon on the chart. Defaults are the author's.

SettingTypeDefaultVariable
Enable Trade-Mgmt rowsboolfalsetm_enable
TP1 (R-multiple)float1.5tm_tp1_r
TP2 (R-multiple)float3.0tm_tp2_r
Move stop to break-even after TP1booltruetm_be_after_t1
ViewstringDecision Briefsurface_mode
Show Brief Panelbooltrueshow_table
Show Trade Planbooltrueshow_risk_lines
Highlight Live Setupboolfalseshow_state_bg
Compact Dashboardboolfalsecompact_dashboard
Color ThemestringStandardcolor_theme
Hero Token OrderstringBIAS,TRUST,FAM,ZONE,BLOCKERhero_token_order
Calibration SHAstringcalibration_sha
Show all 40 settings
SettingTypeDefaultVariable
Calibration Breach Bannerboolfalsecalibration_breach_banner
FVG Context Healthstringfvg_context_health_text
BUS SchemaVersionsourceclosesrc_bus_schema_version
BUS ZoneActivesourceclosesrc_zone_active
BUS Armedsourceclosesrc_armed
BUS Confirmedsourceclosesrc_confirmed
BUS Readysourceclosesrc_ready
BUS EntryBestsourceclosesrc_entry_best
BUS EntryStrictsourceclosesrc_entry_strict
BUS Triggersourceclosesrc_trigger
BUS Invalidationsourceclosesrc_invalidation
BUS QualityScoresourceclosesrc_quality_score
BUS SourceKindsourceclosesrc_source_kind
BUS StateCodesourceclosesrc_state_code
BUS TrendPacksourceclosesrc_trend_pack
BUS MetaPacksourceclosesrc_meta_pack
BUS LtfDeltaStatesourceclosesrc_ltf_delta_state
BUS SafeTrendStatesourceclosesrc_safe_trend_state
BUS MicroProfileCodesourceclosesrc_micro_profile_code
BUS StopLevelsourceclosesrc_stop_level
BUS Target1sourceclosesrc_target_1
BUS Target2sourceclosesrc_target_2
BUS SessionGateRowsourceclosesrc_session_gate_row
BUS MarketGateRowsourceclosesrc_market_gate_row
BUS VolaGateRowsourceclosesrc_vola_gate_row
BUS MicroSessionGateRowsourceclosesrc_micro_session_gate_row
BUS MicroFreshRowsourceclosesrc_micro_fresh_row
BUS VolumeDataRowsourceclosesrc_volume_data_row
Third-party code. ForexR did not write, test or endorse this script, and the summary above was generated automatically from its code and README. Indicators describe the past, and a strategy's backtest can look better than live trading, especially when a script repaints. Check it in the Strategy Tester and on a demo account before you rely on it.

Similar Pine Scripts

575 stars

This overlay indicator scans a pasted list of symbols for ICT liquidity sweep patterns using prior swing sweeps. The screening method looks back one, two, or three swings and can list long and short signals in a results table with adjustable rows, position, an

Indicator v5 SMC / ICT / price actionScreeners & dashboards
Updated 1 year ago · MPL-2.0 GitHub repository of ICT Liquidity Sweep Pattern Scanner

Golden Bullet Strategy

by TradersPost
218 stars

The Golden Bullet Strategy is an overlay signal indicator based on Smart Money Concepts and time-based liquidity models. It identifies liquidity sweeps of a designated reference session range during a specified hunt window. It then confirms Market Structure Sh

Indicator v6 SMC / ICT / price actionSessions & time
Updated 3 months ago · None stated GitHub repository of Golden Bullet Strategy
56 stars

This indicator identifies fractal-based pivot highs and lows to map out market structure directly on the price chart. It detects bullish and bearish structure breaks, automatically labeling Break of Structure (BOS) and Change of Character (ChoCH) events when p

Indicator v5 SMC / ICT / price actionSupport & resistance
Updated 2 months ago · None stated GitHub repository of Market Structure (Fractal)
53 stars

Apex Trend & Liquidity Master calculates a trend cloud using customizable moving averages expanded by a volatility multiplier directly on the main price chart. It identifies dynamic support and resistance liquidity zones from swing high and low pivots, extendi

Indicator v6 TrendSupport & resistance
Updated 2 months ago · MPL-2.0 GitHub repository of Apex Trend & Liquidity Master
23 stars

MTF State of Delivery tracks lower timeframe and higher timeframe orderflow delivery states by identifying bullish and bearish delivery levels across timeframes. It plots confirmed and pending orderflow lines directly on the price chart and provides an optiona

Indicator v5 SMC / ICT / price actionSupport & resistance
Updated 3 weeks ago · Custom licence GitHub repository of MTF State of Delivery by traderprimez

More indicators

Trade Your Charts With a Regulated Broker

Compare brokers that connect to TradingView, with spreads, minimum deposits and leverage side by side.

Compare brokers