Separate actor / critic encoders. Two independent SnapshotEncoder modules. The actor encoder is updated by actor_loss; the critic encoder by the TD signal. Removes the conflicting gradient signals that fought on the shared module in v2 / v2.1.
SnapshotEncoder simplified. Linear → GELU MLP residual → LayerNorm → concat[base, symbol_emb, meta]. Replaces the v2.1 FiLM(γ,β) + SwiGLU FFN scheme. seq_len = 1 (single-snapshot input).
Cross-symbol mixer. TransformerEncoder × 2 with Pre-LN, GELU activation, FFN expansion ×4 — one mixer per actor / critic path. Replaces the LatentReadoutAdapter from v2 / v2.1.
Actor head. 2-layer MLP — Linear(feat → feat/2) → GELU → Linear(feat/2 → 1) — with a LayerNorm on the input (Phase 14a fix).
Twin scalar Q. Two independent q1_mlp / q2_mlp 3-layer MLPs with scalar output. Replaces the distributional ensemble from v2.1.
Twin global Q. q1g_mlp / q2g_mlp on top of SymbolAttentionPool (learnable-query attention pooling across symbols) plus the full action vector.
Global Q detach (Phase 14b). qg_new.detach() inside the actor objective: portfolio-level Q does not steer per-symbol decisions of the actor. Removes the per-sym vs global signal conflict.
World model. SymDynamicsMLP (latent dynamics) + RewardVecMLP (per-symbol) + RewardGlobalMLP (portfolio-level reward shaping) using mean-pool aggregation.