WikifitaGitHub live67e8de5
pesquisa · kaggle/pokemon_tcg_agent_architecture

Pokémon TCG AI Battle — Current Agent Architecture

Evidence-based description of the entity/action Transformer running today: sizes, token schema, embedding zones, aux heads, scratch registers, positional strategy (no RoPE/RoPE-ND), meta-buckets, split heads.

Baixar raw

Pokémon TCG AI Battle — Current Agent Architecture

August 14 boundary update

This page still describes the live entity/action Transformer. The August 14 handoff adds a documented future architecture, not a silent replacement of this one. A direct source-tree check found no tracked rl/ropend, rl/moe, rl/policy_moe_torch.py, rl/policy_moe_mlx.py or rl/deck/vehicle_draft.py implementation. The corresponding RoPE-ND, four-expert MoE, vehicle-draft and Apex documents remain preserved as planned or experimental work in pokemon_tcg_aug14_architecture_and_handoff_audit.

The current source-backed model still uses the existing scratch and auxiliary-head path. The current FP32 inference contract and the August 14 database, ablation and tournament evidence are cataloged in pokemon_tcg_aug14_data_etl_database_audit and pokemon_tcg_aug14_ablations_tournaments.

Architecture identity

Entity/action Transformer trained by behavioral cloning over a structured, externally reconstructed belief state. Not a causal language decoder. It exposes a recurrent memory interface through scratch registers and TBPTT, but it has no dedicated recurrent cell. The exact register count belongs to the run configuration: the default is 16 and the session configuration used 32.

Current build (config default → session baseline in configs/train_config.json):

d_model              128
attention heads      4
Transformer layers   3        (session runs 4)
FFN width            512      (4 × d_model)
scratch registers    16       (session runs 32)
static card features enabled
split policy/value   enabled  (dedicated VALUE_TOK and SUBMIT_TOK heads)
structured head      disabled for current BC baseline
options              up to MAX_OPTIONS=192 plus SUBMIT_ACTION=192
total trainable      ~1.30M params  (~845k Muon-routed + ~456k AdamW-routed)

Positional information does not use RoPE, sinusoidal or RoPE-ND. Positions are carried by zone-typed token embeddings (type_emb, sel_type_emb, sel_ctx_emb) plus explicit gather from opt_src_pos / opt_tgt_pos — each option token points at the exact state token of the card/unit it references, so the option's src/tgt embedding IS that state token, not a rebuilt copy. RoPE-ND is deferred backlog, not shipped (pokemon_tcg_ladder_and_research).

The token sequence has approximately 337 positions before options:

[CLS] [SELECT_TYPE] [SELECT_CONTEXT]
[SELF_DECK ×60] [OPP_DECK ×60]
[SELF_PRIZE ×6] [OPP_PRIZE ×6]
[SELF_HAND ×30] [OPP_HAND ×30]
[SELF_DISCARD ×60] [OPP_DISCARD ×60]
[STADIUM ×2] [EFFECT ×2]
[SELF_UNITS ×(1+8)] [OPP_UNITS ×(1+8)]
[SCRATCH ×N_SCRATCH]
[OPTIONS ×(up to 192)]

Padding capacity is part of the storage format; not every position holds a real entity. padding_idx=0 semantics apply — card and attack ID zero embed to a zero vector, not a learned "absence" embedding. The option surface holds up to 192 tokens plus one reserved SUBMIT_ACTION index at position 192.

What the model actually receives

The encoder combines several kinds of information:

InputRole
Card and attack IDsCategorical identity embeddings
Static card featuresDeterministic domain attributes projected into model space
Unit attributesDynamic HP, energy, effects, status and other board state
Zone/type embeddingsOwnership and semantic role of deck, hand, discard, active, bench, stadium and effects
Selection contextCurrent action phase and selection type
Source/target positionsExplicit references from an option to state tokens
Action maskExact legality boundary supplied by the engine/encoder
Tracker flagsInformation about visibility, drawability and certainty

The option token is a structured candidate, not a fixed semantic class tied to an array index. It combines source and target representations, structural attributes, verb identity and attack identity. The output head scores those candidates and applies the legal-action mask.

Mathematically, the current policy is close to:

[ \pi_\theta(a_t\mid\hat b_t), \qquad \hat b_t=\mathcal T(o_t,\text{logs}t,H{t-1}), ]

where GameTracker and AbilityTracker construct the working belief state before the Transformer is called.

External state is a strength and a boundary

Keeping the exact rules transition outside the neural network is appropriate. The engine should remain the sound authority for legal actions and state transitions:

[ s_{t+1}=P(s_t,a_t), \qquad m(s_t,a_t)\in{0,1}. ]

The current limitation is that the external system also supplies most of the agent's temporal belief compression. If two histories produce the same tracker output, the current policy cannot distinguish them:

[ \mathcal T(H_1)=\mathcal T(H_2) \Rightarrow \pi(\cdot\mid H_1)=\pi(\cdot\mid H_2). ]

This is not a claim that the tracker is wrong. It is a statement about what information is available to the neural policy. The current model is therefore best described as an autoregressive action scorer over each externally reconstructed belief-state substep, with bounded learned memory rather than a full internal game-state model.

Scratch/register tokens

Scratch registers are learned workspace vectors inserted between state and option tokens. In the current implementation they are initialized from learned parameters at match start, then the final substep's output becomes the next decision's memory_in:

[ J_0^{\mathrm{in}}=J_{\mathrm{init}}, \qquad J_{t+1}^{\mathrm{in}}=J_t^{\mathrm{out}}. ]

They support intrapass integration and inter-decision memory. agent/main.py stores the memory per tracked side, and the inference model carries it through each autoregressive selection. This memory interface does not prove that the learned state is strategically sufficient.

Action semantics

The dataset expands multi-select actions into sequential substeps:

picked=[]       -> option_1
picked=[1]      -> option_2
picked=[1,2]    -> SUBMIT

The earlier live path used one forward pass and topk(count). The current agent/main.py path recomputes the encoded observation and logits after each selected option, masks already-picked options, and terminates on legal SUBMIT_ACTION. The old topk(count) behavior remains a historical defect recorded in the migration pages, not the current action contract.

The corrected factorization is:

[ \pi(a_1\mid s), \quad \pi(a_2\mid s,a_1), \quad \ldots, \quad \pi(\mathrm{SUBMIT}\mid s,a_{<k}). ]

Each substep must update picked, the option mask and the option tokens, then run the next forward pass.

Evidence and current ceiling

The repository's local evaluation artifacts show strong performance against the included public opponents and a smaller set of difficult matchups. The recorded tournament results are useful regression evidence, but they are not a causal attribution of which architectural component produced them. In particular, local win rate is not interchangeable with the competition's proprietary ladder rating.

The current performance can be explained without assuming a hidden breakthrough:

  1. the rules engine removes illegal actions;
  2. the encoder provides typed entities and action references;
  3. static card attributes reduce the burden of learning domain facts;
  4. replay labels provide a strong behavioral prior;
  5. interchangeable actions can be grouped or deduplicated;
  6. the model only needs to rank a reduced candidate surface.

The architecture's likely ceiling is temporal rather than purely computational. It preserves a bounded learned memory, but the rules engine and tracker still perform most belief reconstruction, and the train/inference feature contract must remain aligned. The current code passes the observation logs into the tracker:

[ \hat b_{\mathrm{train}}=\mathcal T(o_t,\mathrm{logs}t,H{t-1}), \qquad \hat b_{\mathrm{live}}=\mathcal T(o_t,\mathrm{logs}t,H{t-1}). ]

The immediate repair is to pass the complete observation through the same tracker and encoder path used during dataset construction.

Embeddings by zone

The token vocabulary is grouped into families, each with its own learned embedding table. All embed to d_model unless otherwise noted:

familytablenotes
card identitycard_emb: Embedding(vocab+1, d_model)padding_idx=0
card static featuresstatic_proj: Linear(24, d_model)fed by EN_Card_Data.csv buffer
zone typetype_emb: Embedding(N_TTYPES, d_model)which zone this token belongs to (SELF_DECK, OPP_HAND, ...)
select typesel_type_emb: Embedding(N_SELECT_TYPES=16, d_model)wire select.type ordinal
select ctxsel_ctx_emb: Embedding(N_SELECT_CTX=64, d_model)wire select.context ordinal
unit attributesunit_attr_proj: Linear(24, d_model)dynamic HP, energy, status, next-turn dmg-reduce buff
visibility flagsdrawable_emb / opp_drawable_emb / hand_certain_embscalar mx.array, added when the row's visibility flag is set
option verbopt_verb_emb: Embedding(17, d_model)option-type ordinal (0..16 incl SPECIAL_CONDITION)
option src/tgtopt_src_proj / opt_tgt_proj: Linear(d_model, d_model)gathered from resolved state token, not rebuilt
option structuralopt_attr_proj: Linear(36, d_model)36 float feats/option (would_ko trio, structural, already-picked flag)
attack identityattack_emb: Embedding(2048, d_model)attack-id (padding_idx=0)
CLS scalarsscalar_proj: Linear(19, d_model)13 board/turn + 5 select-dynamics + 1 our-turn offensive buff
meta bucketsmeta_bucket_emb / agent_bucket_emb / deck_bucket_emb: Embedding(11, d_model)0..9 Elo decile + UNKNOWN
day scalarday_proj: Linear(1, d_model)normalized competition-day integer
meta context basemeta_ctx_base: mx.array(d_model)learned bias mixed into the meta context
scratchscratch: mx.array(N_SCRATCH, d_model) + learned_init: mx.array(N_SCRATCH, d_model)see below

N_META_BUCKETS = 11 — Elo deciles 0–9 (0 = strongest) plus slot 10 (UNKNOWN_BUCKET) for domain-legitimate "not observed on this day yet" cases (newly released cards, first-game opponents, etc.). Cards / agents / decks all share this schema.

Scratch registers

A small block of learned workspace tokens inserted between the state stream and the option stream:

J_0_in     = learned_init             (seed at match/decision start)
J_{t+1}    = scratch_slice(model_output_t)
memory_in  is the previous chunk's scratch output
memory_out is the current chunk's scratch output

--scratch-registers N controls the count. Range 0–64. Config default: 16. Current session config: 32.

When TBPTT is on (see pokemon_tcg_tbptt_training_contract), memory is carried per (episode_id, side) lane across chunks, with stop_gradient only at the chunk boundary. Reset on new_episode, isolated per side. Not shared across matches or processes.

Inspired by "ViTs Need Registers": the scratch tokens are non-semantic workspace positions the model can use as scratchpad without stealing capacity from state or option tokens.

Split heads (dedicated VALUE and SUBMIT)

split_heads=true reserves two token slots at the head of the sequence with dedicated output heads:

  • VALUE_TOK — feeds the value head (scalar or categorical). Short residual keeps prize-count signal clean.
  • SUBMIT_TOK — feeds the submit_head (single logit that is spliced into the option scoring at index SUBMIT_ACTION).

Off (split_heads=false): a single CLS token feeds both value and action heads (2 × d_model input to the value head instead of d_model).

Auxiliary heads

Four Linear(d_model, 1) heads applied to the CLS output (or VALUE_TOK when split_heads=true):

headtarget columnlossdefault weight
ko_head_auxaux_ko (int8)BCE0.5
prize_head_auxaux_prize_delta (float)MSE0.5
terminal_head_auxaux_terminal (int8)BCE0.5
return_head_auxaux_return (float)MSE1.0

All four masked by aux_valid per row. Fixed loss weights via --aux-*-weight flags — automatic uncertainty weighting (Kendall & Gal) is deferred backlog.

Attention mechanics

  • Additive mask — the padded-key mask is added to attention scores. The shared finite negative sentinel is -65504.0; 0 marks valid keys. The current trainer/inference model is strict FP32 even though this sentinel is also finite in FP16.
  • MHA bias — attention projections carry a bias term (bias=True), matching the PyTorch reference. The MLX side instantiates this explicitly to avoid inheriting a different default.
  • FP32 current path — the trainer sets model parameters and forward activations to FP32, and the PyTorch converter validates FP32 floating tensors. The historical FP16 path remains documented in pokemon_tcg_torch_inference.

Static card features

EN_Card_Data.csv is loaded once as an np.ndarray and stored as a buffer (_card_feat_np), not a trainable parameter. A learned Linear(24, d_model) projects it into model space when --static true. The buffer is checkpointed by SHA256 in the payload so a resume with a changed CSV raises.

Structured verb head (disabled in baseline)

--structured true adds per-OptionType query and bias embeddings (type_query: Embedding(17, d_model), type_bias: Embedding(17, 1)) that gate the shared opt_head scoring. A structured_weight_decay (default 0.1, higher than the shared AdamW decay of 0.01) collapses rare verbs toward the shared fallback. Off by default in the current BC baseline.

Categorical value head (opt-in)

When instantiated with value_categorical=True, the value head is Linear(d_model, value_atoms) producing atom logits over support [-value_vmax, +value_vmax]. The runtime output is the expected scalar E[value] = softmax(atom_logits) · atom_support — atom logits are never returned as-is. Off in the current baseline (scalar value).

What the model actually receives

The encoder combines several kinds of information:

InputRole
Card and attack IDsCategorical identity embeddings
Static card featuresDeterministic domain attributes projected into model space
Unit attributesDynamic HP, energy, effects, status and other board state
Zone/type embeddingsOwnership and semantic role of deck, hand, discard, active, bench, stadium and effects
Selection contextCurrent action phase and selection type
Source/target positionsExplicit references from an option to state tokens (gathered, not rebuilt)
Action maskExact legality boundary supplied by the engine/encoder
Tracker flagsVisibility, drawability and certainty per token
Meta bucketsElo-decile bucket per card / agent / deck
Day scalarNormalized competition-day integer
Scratch registersPersistent inter-decision memory when TBPTT is on

The option token is a structured candidate, not a fixed semantic class tied to an array index. It combines source and target representations, structural attributes, verb identity and attack identity. The output head scores those candidates and applies the legal-action mask.

Mathematically, the current policy is close to:

[ \pi_\theta(a_t\mid\hat b_t), \qquad \hat b_t=\mathcal T(o_t,\text{logs}t,H{t-1}), ]

where GameTracker and AbilityTracker construct the working belief state before the Transformer is called. H_{t-1} is the previous decision's scratch memory (or learned_init at match start).

External state is a strength and a boundary

Keeping the exact rules transition outside the neural network is appropriate. The engine should remain the sound authority for legal actions and state transitions:

[ s_{t+1}=P(s_t,a_t), ]

Architecture boundaries

Current and authorized

  • Entity/action Transformer at 128/4/3-4/512.
  • Exact additive action masks and structured option tokens.
  • External rules engine and trackers (GameTracker + AbilityTracker).
  • Behavioral cloning with four aux heads.
  • 16–32 scratch tokens made persistent across decisions when TBPTT is on.
  • Sequential metadata and truncated backpropagation through time.
  • MLX training on M3 Pro, strict-FP32 PyTorch arena inference (see pokemon_tcg_current_state_reconciliation and pokemon_tcg_torch_inference).

Deferred

Mamba/Mamba-2, Hope/Nested Learning, multidimensional RoPE / RoPE-ND, Energy-Based Transformer, TRM, J-Lens, strategic MoE, PPO/GRPO/GSPO, PSRO, world models in submitted inference, symbolic regression, custom Metal and distributed Mac training remain research backlog items in pokemon_tcg_ladder_and_research. They must not be smuggled into the current port under the label of optimization.

Related pages