;; ported-from: src/model.ts @ v1.0.0 (extracted-from: bot/src/rl/policy.ts @ fa686ee)
;;
;; Model shapes shared between a trainer (which writes them) and live bots
;; (which only ever run inference). Checkpoints are plain JSON — a trainer may
;; use tfjs to fit a net, but what lands on disk is framework-free and the
;; bots replay it with the tiny forward pass in forward.cljs.
;;
;; The MlpJson / MlpModel / TabularModel / AnyModel shapes are documented in
;; types/index.d.ts — the runtime here only guards structure.
(ns ardegazu.train.model)
(defn is-mlp-model
"Structural guard for an MLP checkpoint; `game` (optional) also narrows."
([m] (is-mlp-model m js/undefined))
([m game]
(let [x m]
(if ^boolean (js* "(!!~{})" x)
(and (identical? (.-v ^js x) 1)
(string? (.-game ^js x))
(or (undefined? game) (identical? (.-game ^js x) game))
(let [net (.-net ^js x)]
(and (js/Array.isArray (when (some? net) (.-sizes ^js net)))
(js/Array.isArray (when (some? net) (.-layers ^js net))))))
false))))
(defn is-tabular-model
"Structural guard for a tabular-Q checkpoint; `game` (optional) also narrows."
([m] (is-tabular-model m js/undefined))
([m game]
(let [x m]
(if ^boolean (js* "(!!~{})" x)
(and (identical? (.-v ^js x) 1)
(string? (.-game ^js x))
(or (undefined? game) (identical? (.-game ^js x) game))
^boolean (js* "(typeof ~{} === 'object' && ~{} !== null)" (.-q ^js x) (.-q ^js x)))
false))))
(defn is-model
"Any structurally valid checkpoint (callers narrow with the guards above)."
[m]
(or (is-mlp-model m) (is-tabular-model m)))
(defn pick-action
"Epsilon-greedy over action values (ties break to the first max)."
([qs epsilon] (pick-action qs epsilon js/Math.random))
([qs epsilon rand]
(if (< (rand) epsilon)
(js/Math.floor (* (rand) (.-length ^js qs)))
(let [n (.-length ^js qs)]
(loop [i 1 best 0]
(if (< i n)
(recur (inc i) (if (> (aget qs i) (aget qs best)) i best))
best))))))