Picture a trading workshop that wakes up once for every new bar. A simple gatekeeper called Run stands outside. It does not reason, it only checks the session stage and forwards control. When the session begins it creates a single master object, and when the session ends it destroys it. All meaningful memory lives inside that master, so the workshop stays tidy.

The master strategy acts like a conductor leading several sections. Each section is a component with a clear symbolic job. There is a pool of reusable nodes, a living tree that generates structured rhythms, three Markov memory books that learn candle transitions, a network of internal agents that evolve together, a logger that writes an audit trail, and a runtime steward that protects memory limits.

The node pool is a warehouse of parts. It stores nodes in crates and also keeps a free list of returned nodes. When the tree needs a node, the pool first tries to reuse a returned one. If none are available, it opens a new crate and hands out the next unused node. When a node is no longer needed, it is returned to the free list instead of being discarded. This reduces allocation churn and gives the tree a steady supply of building blocks.

The decision tree is a living sculpture, not a rule splitter. It grows down to a fixed depth. Each node carries a tone value and a pace value and may have children. On every evaluation, a node gathers the influences of its children and forms a local sum. It then forms a rhythmic phase driven by the current bar count and its pace. The node slowly moves its stored tone toward that phase. A depth weight table controls how strongly the update pulls at each depth, so shallow layers drift differently than deep layers.

The tree keeps an index list so it can answer which node corresponds to which agent. It estimates node predictability from child dispersion, depth, and pace suitability. Predictability is cached per bar so repeated queries are fast. The tree assigns each agent a position on an imaginary ring derived from its mapped node. The strategy follows that ring to track which internal agent currently leads attention, producing a slowly drifting cycle phase that represents rotating focus.

The Markov memory books form a trio. One book watches the current timeframe, another watches a higher timeframe sampled only on higher timeframe closes, and the third book records only when both timeframes agree on the same pattern and direction. Each book stores transition counts and row totals. On each update it converts the candle situation into a single state label. It computes many candle pattern indicators, selects the strongest absolute one, and accepts it only if it clears an acceptance threshold. If nothing is strong enough it records a none state. From the current row it estimates a bullish next chance and an uncertainty score, using smoothing so early data does not swing wildly.

The network state is a council of internal agents. There are many agents, each with a current value, a previous value, and a squared copy used by the projection system. Each agent has a small neighbor list, and those links can be rewired over time. Every agent holds knobs that decide how its next value is formed. Knobs weight self influence, two neighbor influences, two global influences, a momentum term, a tree ensemble term, and an advisor term. Additional parameters shape two nonlinear transforms that process neighbor arguments into curved outputs, and a mode flag selects which transforms are used for the first and second neighbor on that bar.

The projection system is a lantern that compresses the council into a smaller set of features. It uses a random sign table to mix squared agent values into projected channels. From the projected channels the strategy derives compact summaries that act as context drivers and feed the later update equations.

The logger is the scribe. It writes a header once, then appends sampled lines that describe internal life. A line contains bar and context identifiers, neighbor references, tree location, node depth, predictability, advisor output, attention share, mode, and Markov mood summaries. When optional expression logging is enabled, the scribe can also store a readable recipe of how an agent updates.

The runtime steward watches memory pressure and takes protective actions. It estimates fixed memory based on the known arrays, and it estimates tree memory by walking the tree. When memory approaches the budget, it first turns off plots and heavy logging. If pressure continues, it prunes the tree. Pruning is selective: at the frontier depth it keeps only the most important children and removes the rest, where importance blends predictability, signal amplitude, and depth. After pruning, the strategy reindexes the tree, rebuilds mappings, and refreshes its memory estimates so later decisions reflect the new structure.

With the parts introduced, the bar routine follows a clear rhythm.

When the session starts, initialization sets bar period and lookback, selects the asset, allocates network arrays and Markov tables, builds the tree, indexes it, maps each agent to a tree node, assigns ring positions, randomizes the projection table, computes the first projection, and performs a full initial rewire. Rewiring creates neighbor links and synthesizes knobs and parameters for every agent. A seed cache is cleared so advisory queries within a bar can be reused safely.

During warmup bars, the strategy updates Markov books and keeps projection ready, but it avoids heavy learning steps and avoids order placement.

During normal bars, the first act is to update the Markov trio. The current timeframe book updates every bar. On bars aligned with the higher timeframe close, the higher timeframe and relation books update as well. These updates refresh bullish chance and uncertainty, which later become gates and confidence modifiers. The acceptance threshold adapts slowly based on recent advisor hit quality and Markov uncertainty.

Next, the strategy computes projection summaries and derives a context driver often called lambda. It also derives another driver from direct council aggregates, often called gamma. The two drivers are blended into a single internal signal, and the blend weight adapts based on higher timeframe uncertainty.

Then come the chunked maintenance tasks. Rewiring is done on a slice of agents, not all at once. For each agent and each neighbor slot, candidate neighbors are sampled and scored using tree depth similarity, pace similarity, and node predictability. Clashes are avoided so neighbor slots do not duplicate. Links are sanitized to avoid invalid indices and self links. After links are set, the agent knobs and parameters are synthesized from a seed derived from an advisor output. Agent attention weights are also adjusted so agents with better hit histories gain a larger share, which also affects tree cycle leadership and ensemble weighting.

State updates are also chunked. For each agent, a tree ensemble term is computed by comparing the agent to all others and weighting them by depth distance and pace distance, then boosting by predictability and attention shares. This yields a blended council influence and identifies the strongest partner. The strategy obtains an advisor output only when rotation allows it and memory headroom exists, and it may skip weak advisors when their hit record is poor. Neighbor inputs are transformed according to the agent mode, global terms are added, momentum is applied, tree term and advisor term are mixed in, and the new state is clamped to a safe band before being stored.

After updating, hit rates are refreshed. The strategy compares the sign of the recent one bar return with the sign of stored advisor outputs and updates a smoothed hit estimate per agent. Hit estimates feed back into advisory gating and into the reliability boost used during synthesis. The tree cycle tracker is also advanced based on which agents currently hold the most attention.

Finally, a trade signal is produced. The internal blended signal is gated by higher timeframe bullish chance. Long intent is allowed only when bullish chance is high enough, and short intent is allowed only when it is low enough. Relation uncertainty reduces confidence. The resulting signed strength becomes a position size, opposing positions are exited, and a new position is entered only when allowed.

In symbolic terms, this system is a council guided by candle language. The Markov trio provides memory and mood, the tree provides structure and cycles, the network provides evolving voices, the pool provides material, the logger provides accountability, and the steward provides survival. The result is an adaptive engine that learns, rewires, and trades with guardrails while staying within its resource envelope.


Code
// Zorro64 C++ Strategy DLL - Alpha12 (FULL OOP Refactor)
// Compile as x64 DLL, include zorro.h
// ======================================================================
//
// Refactor goals achieved:
// - ALL prior “globals” moved into Alpha12Strategy (and its components).
// - Components: NodePool, DTree, MarkovChain, NetState, Logger, RuntimeManager.
// - No more static state inside functions (seedBar/haveSeed/seedVal moved into members).
// - run() remains a thin C bridge.
//
// Notes:
// - Logic is preserved: this is primarily an encapsulation/refactor.
// - Memory management remains malloc/free like your original (safe incremental step).
// ======================================================================

#define _CRT_SECURE_NO_WARNINGS

#include <zorro.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>

// ======================================================================
// ================= USER CONFIG =================
#define ASSET_SYMBOL "EUR/USD"
#define BAR_PERIOD 5
#define TF_H1 12

#define MC_ACT 0.30         // initial threshold on |CDL| in [-1..1] to accept a pattern
#define PBULL_LONG_TH 0.60  // Markov gate for long
#define PBULL_SHORT_TH 0.40 // Markov gate for short

// Debug toggles
#define ENABLE_PLOTS 0
#define ENABLE_WATCH 0

// ================= ENGINE PARAMETERS =================
#define MAX_BRANCHES 3
#define MAX_DEPTH 4
#define NWIN 256
#define NET_EQNS 100
#define DEGREE 4
#define KPROJ 16
#define REWIRE_EVERY 127
#define CAND_NEIGH 8

// ===== LOGGING CONTROLS =====
#define LOG_EQ_TO_ONE_FILE 1
#define LOG_EXPR_TEXT 0
#define META_EVERY 4
#define LOG_EQ_SAMPLE NET_EQNS
#define EXPR_MAXLEN 512
#define LOG_EVERY 16
#define MC_EVERY 1

// ---- DTREE feature sizes ----
#define ADV_EQ_NF 19
#define ADV_PAIR_NF 12

// ================= Candles ? 122-state Markov =================
#define MC_NPAT 61
#define MC_STATES 123
#define MC_NONE 0

// ================= Runtime Memory / Accuracy Manager =================
#define MEM_BUDGET_MB 50
#define MEM_HEADROOM_MB 5
#define DEPTH_STEP_BARS 16
#define KEEP_CHILDREN_HI 2
#define KEEP_CHILDREN_LO 1
#define RUNTIME_MIN_DEPTH 2

// ===== Chunked rewire settings =====
#define REWIRE_BATCH_EQ_5M 24
#define REWIRE_BATCH_EQ_H1 64
#define REWIRE_MIN_BATCH 8
#define REWIRE_NORM_EVERY 1
#define REWIRE_MEM_SOFT (MEM_BUDGET_MB - 4)
#define REWIRE_MEM_HARD (MEM_BUDGET_MB - 1)

// ===== Chunked update settings =====
#define UPDATE_BATCH_EQ_5M 32
#define UPDATE_BATCH_EQ_H1 96
#define UPDATE_MIN_BATCH 8
#define UPDATE_MEM_SOFT (MEM_BUDGET_MB - 4)
#define UPDATE_MEM_HARD (MEM_BUDGET_MB - 1)

// ======================= Tight-memory switches =======================
#define TIGHT_MEM 1
#ifdef TIGHT_MEM
typedef float fvar;
typedef short i16;
typedef char  i8;
#else
typedef var fvar;
typedef int i16;
typedef int i8;
#endif

// ======================================================================
// Forward declarations
class Alpha12Strategy;
static Alpha12Strategy* gAlpha12 = nullptr;

// ======================================================================
// ========================= NodePool (component) ========================

struct Node {
  var v;
  var r;
  void* c;
  int n;
  int d;
};

struct NodeChunk {
  NodeChunk* next;
  int used;
  int _pad;
  Node nodes[256];
};

class NodePool {
  NodeChunk* head = 0;
  Node* freeList = 0;

public:
  ~NodePool() { freeAll(); }

  Node* allocNode() {
    if(freeList) {
      Node* n = freeList;
      freeList = (Node*)n->c;
      n->c = 0; n->n = 0; n->d = 0; n->v = 0; n->r = 0;
      return n;
    }
    if(!head || head->used >= 256) {
      NodeChunk* ch = (NodeChunk*)malloc(sizeof(NodeChunk));
      if(!ch) quit("Alpha12: OOM allocating NodeChunk");
      memset(ch, 0, sizeof(NodeChunk));
      ch->next = head;
      head = ch;
    }
    if(head->used < 0 || head->used >= 256) quit("Alpha12: Corrupt node pool state");
    return &head->nodes[head->used++];
  }

  void freeNode(Node* u) {
    if(!u) return;
    u->c = (void*)freeList;
    freeList = u;
  }

  void freeAll() {
    NodeChunk* ch = head;
    while(ch) {
      NodeChunk* nx = ch->next;
      free(ch);
      ch = nx;
    }
    head = 0;
    freeList = 0;
  }
};

// ======================================================================
// ========================= MarkovChain (component) =====================

class MarkovChain {
public:
  int* Count = 0;   // [MC_STATES*MC_STATES]
  int* RowSum = 0;  // [MC_STATES]
  int Prev = -1;
  int Cur  = 0;
  var PBullNext = 0.5;
  var Entropy = 1.0;
  var Alpha = 1.0; // Laplace smoothing

public:
  void alloc() {
    int NN = MC_STATES*MC_STATES;
    int bytesMat = NN*sizeof(int);
    int bytesRow = MC_STATES*sizeof(int);

    Count  = (int*)malloc(bytesMat);
    RowSum = (int*)malloc(bytesRow);
    if(!Count || !RowSum) quit("Alpha12: OOM in MarkovChain::alloc");
    memset(Count,0,bytesMat);
    memset(RowSum,0,bytesRow);
    Prev = -1; Cur = 0; PBullNext = 0.5; Entropy = 1.0;
  }

  void freeMem() {
    if(Count) free(Count);
    if(RowSum) free(RowSum);
    Count = RowSum = 0;
    Prev = -1; Cur = 0; PBullNext = 0.5; Entropy = 1.0;
  }

  static int isBull(int s){
    if(s<=0) return 0;
    return ((s-1)%2)==1;
  }

  static int stateFromCDL(var* cdl /*len=61*/, var thr) {
    int i, best=-1;
    var besta=0;
    for(i=0;i<MC_NPAT;i++){
      var a = abs(cdl[i]);
      if(a>besta){ besta=a; best=i; }
    }
    if(best<0) return MC_NONE;
    if(besta < thr) return MC_NONE;
    int bull = (cdl[best] > 0);
    return 1 + 2*best + bull; // 1..122
  }

  int idx(int fr,int to) const { return fr*MC_STATES + to; }

  void update(int sPrev,int sCur){
    if(sPrev<0) return;
    Count[idx(sPrev,sCur)]++;
    RowSum[sPrev]++;
  }

  var prob(int s,int t) const {
    var num = (var)Count[idx(s,t)] + Alpha;
    var den = (var)RowSum[s] + Alpha*MC_STATES;
    if(den<=0) return 1.0/MC_STATES;
    return num/den;
  }

  // robust row stats
  void rowStats(int s, var* outPBull, var* outEntropy) {
    if(outPBull) *outPBull=0.5;
    if(outEntropy) *outEntropy=1.0;
    if(!Count || !RowSum) return;
    if(!(Alpha > 0)) Alpha = 1.0;
    if(s <= 0 || s >= MC_STATES) return;
    if(RowSum[s] <= 0) return;

    var den = (var)RowSum[s] + Alpha*(var)MC_STATES;
    if(!(den > 0)) return;

    var Z=0, pBull=0;
    int t;
    for(t=1;t<MC_STATES;t++){
      var p = ((var)Count[idx(s,t)] + Alpha) / den;
      Z += p;
      if(isBull(t)) pBull += p;
    }
    if(!(Z>0)) return;

    var H=0;
    var Hmax = log((var)(MC_STATES-1));
    if(!(Hmax > 0)) Hmax = 1.0;
    for(t=1;t<MC_STATES;t++){
      var p = (((var)Count[idx(s,t)] + Alpha) / den) / Z;
      if(p>0) H += -p*log(p);
    }

    if(outPBull) *outPBull = pBull / Z;
    if(outEntropy) *outEntropy = H / Hmax;
  }
};

// ======================================================================
// =========================== Logger (component) ========================

class Alpha12Logger {
  int wroteHeader = 0;

public:
  void writeEqHeaderOnce(){
    if(wroteHeader) return;
    wroteHeader = 1;
    file_append("Log\\Alpha12_eq_all.csv",
      "Bar,Epoch,Ctx,EqCount,i,n1,n2,TreeId,Depth,Rate,Pred,Adv,Prop,Mode,WAdv,WTree,PBull,Entropy,MCState,ExprLen,ExprHash,tanhN,sinN,cosN\n");
  }

  static void strlcat_safe(string dst, string src, int cap) {
    if(!dst || !src || cap <= 0) return;
    int dl = (int)strlen(dst);
    int sl = (int)strlen(src);
    int room = cap - 1 - dl;
    if(room <= 0){ if(cap > 0) dst[cap-1] = 0; return; }
    int i;
    for(i = 0; i < room && i < sl; i++) dst[dl + i] = src[i];
    dst[dl + i] = 0;
  }

  static int countSubStr(string s, string sub){
    if(!s || !sub) return 0;
    int n=0; string p=s; int sublen = (int)strlen(sub); if(sublen<=0) return 0;
    while((p=strstr(p,sub))){ n++; p += sublen; }
    return n;
  }

  static int djb2_hash(string s){
    int h = 5381, c, i = 0;
    if(!s) return h;
    while((c = s[i++])) h = ((h<<5)+h) ^ c;
    return h & 0x7fffffff;
  }

  void appendEqMetaLine(
    int bar, int epoch, int ctx,
    int i, int n1, int n2, int tid, int depth, var rate, var pred, var adv, var prop, int mode,
    var wadv, var wtree, var pbull, var ent, int mcstate, string expr)
  {
    if(i >= LOG_EQ_SAMPLE) return;

    int eLen = 0, eHash = 0, cT = 0, cS = 0, cC = 0;
    if(expr){
      eLen  = (int)strlen(expr);
      eHash = (int)djb2_hash(expr);
      cT    = countSubStr(expr,"tanh(");
      cS    = countSubStr(expr,"sin(");
      cC    = countSubStr(expr,"cos(");
    } else {
      eHash = (int)djb2_hash("");
    }

    file_append("Log\\Alpha12_eq_all.csv",
      strf("%i,%i,%i,%i,%i,%i,%i,%i,%i,%.4f,%.4f,%.4f,%.4f,%i,%.3f,%.3f,%.4f,%.4f,%i,%i,%i,%i,%i,%i\n",
        bar, epoch, ctx, NET_EQNS, i, n1, n2, tid, depth,
        rate, pred, adv, prop, mode, wadv, wtree, pbull, ent,
        mcstate, eLen, eHash, cT, cS, cC));
  }
};

// ======================================================================
// =========================== NetState (component) ======================

class NetState {
public:
  int N = NET_EQNS;
  int D = DEGREE;
  int K = KPROJ;

  // core
  var*  State = 0;
  var*  Prev  = 0;
  var*  StateSq = 0;

  // adjacency & projection
  i16*  Adj = 0;
  fvar* RP  = 0;
  fvar* Z   = 0;
  i8*   Mode= 0;

  // weights & params
  fvar* WSelf=0; fvar* WN1=0; fvar* WN2=0; fvar* WGlob1=0; fvar* WGlob2=0; fvar* WMom=0; fvar* WTree=0; fvar* WAdv=0;

  fvar *A1x=0,*A1lam=0,*A1mean=0,*A1E=0,*A1P=0,*A1i=0,*A1c=0;
  fvar *A2x=0,*A2lam=0,*A2mean=0,*A2E=0,*A2P=0,*A2i=0,*A2c=0;
  fvar *G1mean=0,*G1E=0,*G2P=0,*G2lam=0;

  fvar* TreeTerm = 0;
  i16*  TopEq = 0;
  fvar* TopW  = 0;
  i16*  EqTreeId = 0;

  fvar* TAlpha=0;
  fvar* TBeta =0;

  fvar* PropRaw=0;
  fvar* Prop   =0;

  // expression buffers (optional)
  string* Sym = 0;
  int SymFreed = 0;

  // Hit-rate
  fvar* HitEW = 0;
  int*  HitN  = 0;
  fvar* AdvPrev = 0;
  var   Ret1 = 0;

  // Projection cache guards
  int ProjBar = -1;
  int ProjK = -1;

  // effective projection dim
  int Keff = KPROJ;

public:
  void allocate() {
    int n=N, d=D, k=K;

    State   = (var*)malloc(n*sizeof(var));
    Prev    = (var*)malloc(n*sizeof(var));
    StateSq = (var*)malloc(n*sizeof(var));

    Adj  = (i16*)malloc(n*d*sizeof(i16));
    RP   = (fvar*)malloc(k*n*sizeof(fvar));
    Z    = (fvar*)malloc(k*sizeof(fvar));
    Mode = (i8*)malloc(n*sizeof(i8));

    WSelf=(fvar*)malloc(n*sizeof(fvar));
    WN1=(fvar*)malloc(n*sizeof(fvar));
    WN2=(fvar*)malloc(n*sizeof(fvar));
    WGlob1=(fvar*)malloc(n*sizeof(fvar));
    WGlob2=(fvar*)malloc(n*sizeof(fvar));
    WMom=(fvar*)malloc(n*sizeof(fvar));
    WTree=(fvar*)malloc(n*sizeof(fvar));
    WAdv=(fvar*)malloc(n*sizeof(fvar));

    A1x=(fvar*)malloc(n*sizeof(fvar)); A1lam=(fvar*)malloc(n*sizeof(fvar)); A1mean=(fvar*)malloc(n*sizeof(fvar));
    A1E=(fvar*)malloc(n*sizeof(fvar)); A1P=(fvar*)malloc(n*sizeof(fvar)); A1i=(fvar*)malloc(n*sizeof(fvar)); A1c=(fvar*)malloc(n*sizeof(fvar));

    A2x=(fvar*)malloc(n*sizeof(fvar)); A2lam=(fvar*)malloc(n*sizeof(fvar)); A2mean=(fvar*)malloc(n*sizeof(fvar));
    A2E=(fvar*)malloc(n*sizeof(fvar)); A2P=(fvar*)malloc(n*sizeof(fvar)); A2i=(fvar*)malloc(n*sizeof(fvar)); A2c=(fvar*)malloc(n*sizeof(fvar));

    G1mean=(fvar*)malloc(n*sizeof(fvar)); G1E=(fvar*)malloc(n*sizeof(fvar));
    G2P=(fvar*)malloc(n*sizeof(fvar)); G2lam=(fvar*)malloc(n*sizeof(fvar));

    TAlpha=(fvar*)malloc(n*sizeof(fvar));
    TBeta =(fvar*)malloc(n*sizeof(fvar));

    TreeTerm=(fvar*)malloc(n*sizeof(fvar));
    TopEq=(i16*)malloc(n*sizeof(i16));
    TopW =(fvar*)malloc(n*sizeof(fvar));

    PropRaw=(fvar*)malloc(n*sizeof(fvar));
    Prop   =(fvar*)malloc(n*sizeof(fvar));

    EqTreeId=(i16*)malloc(n*sizeof(i16));

    if(LOG_EXPR_TEXT) Sym = (string*)malloc(n*sizeof(char*)); else Sym = 0;

    // init adjacency
    { int t; for(t=0;t<n*d;t++) Adj[t] = -1; }

    // init core arrays
    {
      int i;
      for(i=0;i<n;i++){
        State[i]=random(); Prev[i]=State[i]; StateSq[i]=State[i]*State[i];
        Mode[i]=0;

        WSelf[i]=0.5f; WN1[i]=0.2f; WN2[i]=0.2f;
        WGlob1[i]=0.1f; WGlob2[i]=0.1f; WMom[i]=0.05f;
        WTree[i]=0.15f; WAdv[i]=0.15f;

        A1x[i]=1; A1lam[i]=0.1f; A1mean[i]=0; A1E[i]=0; A1P[i]=0; A1i[i]=0; A1c[i]=0;
        A2x[i]=1; A2lam[i]=0.1f; A2mean[i]=0; A2E[i]=0; A2P[i]=0; A2i[i]=0; A2c[i]=0;

        G1mean[i]=1.0f; G1E[i]=0.001f;
        G2P[i]=0.6f; G2lam[i]=0.3f;

        TAlpha[i]=0.8f; TBeta[i]=25.0f;
        TreeTerm[i]=0;
        TopEq[i]=-1; TopW[i]=0;

        PropRaw[i]=1; Prop[i]=(fvar)(1.0/n);

        if(LOG_EXPR_TEXT){
          Sym[i] = (char*)malloc(EXPR_MAXLEN);
          if(Sym[i]) strcpy(Sym[i],"");
        }
      }
    }

    // hit-rate arrays
    HitEW = (fvar*)malloc(n*sizeof(fvar));
    HitN  = (int*)malloc(n*sizeof(int));
    AdvPrev = (fvar*)malloc(n*sizeof(fvar));
    { int i; for(i=0;i<n;i++){ HitEW[i]=0.5f; HitN[i]=0; AdvPrev[i]=0; } }

    // projection guards
    ProjBar = -1; ProjK = -1;
  }

  void freeAll() {
    int i;
    if(State)free(State);
    if(Prev)free(Prev);
    if(StateSq)free(StateSq);
    if(Adj)free(Adj);
    if(RP)free(RP);
    if(Z)free(Z);
    if(Mode)free(Mode);

    if(WSelf)free(WSelf);
    if(WN1)free(WN1);
    if(WN2)free(WN2);
    if(WGlob1)free(WGlob1);
    if(WGlob2)free(WGlob2);
    if(WMom)free(WMom);
    if(WTree)free(WTree);
    if(WAdv)free(WAdv);

    if(A1x)free(A1x); if(A1lam)free(A1lam); if(A1mean)free(A1mean);
    if(A1E)free(A1E); if(A1P)free(A1P); if(A1i)free(A1i); if(A1c)free(A1c);

    if(A2x)free(A2x); if(A2lam)free(A2lam); if(A2mean)free(A2mean);
    if(A2E)free(A2E); if(A2P)free(A2P); if(A2i)free(A2i); if(A2c)free(A2c);

    if(G1mean)free(G1mean); if(G1E)free(G1E);
    if(G2P)free(G2P); if(G2lam)free(G2lam);
    if(TAlpha)free(TAlpha); if(TBeta)free(TBeta);

    if(TreeTerm)free(TreeTerm);
    if(TopEq)free(TopEq);
    if(TopW)free(TopW);
    if(EqTreeId)free(EqTreeId);

    if(PropRaw)free(PropRaw);
    if(Prop)free(Prop);

    if(Sym){
      for(i=0;i<N;i++) if(Sym[i]) free(Sym[i]);
      free(Sym);
    }
    Sym = 0;
    if(HitEW) free(HitEW);
    if(HitN) free(HitN);
    if(AdvPrev) free(AdvPrev);

    // null everything
    State=Prev=StateSq=0; Adj=0; RP=0; Z=0; Mode=0;
    WSelf=WN1=WN2=WGlob1=WGlob2=WMom=WTree=WAdv=0;
    A1x=A1lam=A1mean=A1E=A1P=A1i=A1c=0;
    A2x=A2lam=A2mean=A2E=A2P=A2i=A2c=0;
    G1mean=G1E=G2P=G2lam=0;
    TAlpha=TBeta=0;
    TreeTerm=0; TopEq=0; TopW=0; EqTreeId=0;
    PropRaw=0; Prop=0;
    HitEW=0; HitN=0; AdvPrev=0;
  }

  void randomizeRP(){
    int k=K, n=N;
    int kk,j;
    for(kk=0;kk<k;kk++)
      for(j=0;j<n;j++)
        RP[kk*n+j] = ifelse(random(1) < 0.5, -1.0, 1.0);
  }

  int keffClamped() const {
    int kk = Keff;
    if(kk < 0) kk = 0;
    if(kk > K) kk = K;
    return kk;
  }

  void computeProjection(){
    if(!RP || !Z || !StateSq) return;
    int kk = keffClamped();
    if(ProjBar == Bar && ProjK == kk) return;
    int k, j;
    for(k=0;k<kk;k++){
      var acc=0;
      for(j=0;j<N;j++) acc += (var)RP[k*N + j] * StateSq[j];
      Z[k] = (fvar)acc;
    }
    ProjBar = Bar;
    ProjK = kk;
  }

  void sanitizeAdjacency(){
    if(!Adj) return;
    int i,d;
    for(i=0;i<N;i++){
      for(d=0;d<D;d++){
        i16* p = &Adj[i*D + d];
        if(*p < 0 || *p >= N || *p == i){
          int r = (int)random(N);
          if(r==i) r = (r+1)%N;
          *p = (i16)r;
        }
      }
      if(D >= 2 && Adj[i*D+0] == Adj[i*D+1]){
        int r2 = (Adj[i*D+1] + 1) % N;
        if(r2 == i) r2 = (r2+1)%N;
        Adj[i*D+1] = (i16)r2;
      }
    }
  }

  int adjSafe(int i, int d) const {
    if(!Adj || N<=1 || D<=0) return 0;
    if(d<0) d=0;
    if(d>=D) d = d % D;
    int v = Adj[i*D + d];
    if(v<0 || v>=N || v==i) v = (i+1)%N;
    return v;
  }

  void normalizeProportions(){
    int i;
    var s=0;
    for(i=0;i<N;i++) s += PropRaw[i];
    if(s<=0){
      for(i=0;i<N;i++) Prop[i] = (fvar)(1.0/N);
      return;
    }
    for(i=0;i<N;i++) Prop[i] = (fvar)(PropRaw[i]/s);
  }
};

// ======================================================================
// ============================= DTree (component) =======================

class DTree {
public:
  NodePool* pool = 0;

  Node* Root = 0;

  Node** TreeIdx = 0;
  int TreeN = 0;
  int TreeCap = 0;

  var DTreeExp = 0;
  var* DepthW = 0;
  var DepthExpLast = -1.0;
  Node DummyNode;

  // Predictability cache
  var* PredNode = 0;
  int PredLen = 0;
  int PredCap = 0;
  int PredCacheBar = -1;

  // equation-cycle angles
  var* EqTheta = 0;
  int LeadEq = -1;
  var LeadTh = 0;
  var CycPh = 0;
  var CycSpd = 0;

public:
  DTree() { memset(&DummyNode,0,sizeof(DummyNode)); }

  void bindPool(NodePool* p){ pool = p; }

  void allocDepthLUT(){
    int sz = MAX_DEPTH + 1;
    if(!DepthW) DepthW = (var*)malloc(sz*sizeof(var));
    if(!DepthW) quit("Alpha12: OOM DepthW");
  }

  void freeAll(){
    if(Root) freeTree(Root);
    Root = 0;
    if(TreeIdx) free(TreeIdx);
    TreeIdx = 0; TreeN=0; TreeCap=0;
    if(DepthW) free(DepthW);
    DepthW=0;
    if(PredNode) free(PredNode);
    PredNode=0; PredLen=0; PredCap=0;
    if(EqTheta) free(EqTheta);
    EqTheta=0;
  }

  // Tree byte size
  int tree_bytes(Node* u){
    if(!u) return 0;
    int SZV = sizeof(var), SZI = sizeof(int), SZP = sizeof(void*);
    int sz_node = 2*SZV + SZP + 2*SZI;
    int total = sz_node;
    if(u->n > 0 && u->c) total += u->n * SZP;
    int i;
    for(i=0;i<u->n;i++) total += tree_bytes(((Node**)u->c)[i]);
    return total;
  }

  void refreshDepthW(){
    if(!DepthW) return;
    int d;
    for(d=0; d<=MAX_DEPTH; d++) DepthW[d] = 1.0 / pow(d+1, DTreeExp);
    DepthExpLast = DTreeExp;
  }

  Node* createNode(int depth){
    if(!pool) quit("Alpha12: DTree pool not bound");
    Node* u = pool->allocNode();
    if(!u) return 0;
    u->v = random();
    u->r = 0.01 + 0.02*depth + random(0.005);
    u->d = depth;
    if(depth > 0){
      u->n = 1 + (int)random(MAX_BRANCHES);
      u->c = (void**)malloc(u->n*sizeof(void*));
      if(!u->c){ u->n=0; u->c=0; return u; }
      int i;
      for(i=0;i<u->n;i++){
        ((Node**)u->c)[i] = createNode(depth-1);
      }
    } else {
      u->n=0; u->c=0;
    }
    return u;
  }

  var evaluateNode(Node* u){
    if(!u) return 0;
    var sum=0; int i;
    for(i=0;i<u->n;i++) sum += evaluateNode(((Node**)u->c)[i]);
    if(DepthExpLast < 0 || abs(DTreeExp - DepthExpLast) > 1e-9) refreshDepthW();
    var phase = sin(u->r * Bar + sum);
    var weight = DepthW[u->d];
    u->v = (1 - weight)*u->v + weight*phase;
    return u->v;
  }

  void freeTree(Node* u){
    if(!u) return;
    int i;
    for(i=0;i<u->n;i++) freeTree(((Node**)u->c)[i]);
    if(u->c) free(u->c);
    pool->freeNode(u);
  }

  void pushTreeNode(Node* u){
    if(TreeN >= TreeCap){
      int newCap = TreeCap*2;
      if(newCap < 64) newCap = 64;
      TreeIdx = (Node**)realloc(TreeIdx, newCap*sizeof(Node*));
      TreeCap = newCap;
    }
    TreeIdx[TreeN++] = u;
  }

  void indexTreeDFS(Node* u){
    if(!u) return;
    pushTreeNode(u);
    int i;
    for(i=0;i<u->n;i++) indexTreeDFS(((Node**)u->c)[i]);
  }

  void ensurePredCache(){
    if(PredCacheBar != Bar){
      if(PredNode){
        int i;
        for(i=0;i<PredLen;i++) PredNode[i] = -2;
      }
      PredCacheBar = Bar;
    }
  }

  var nodePredictability(Node* t){
    if(!t) return 0.5;
    var disp = 0; int n=t->n, i, cnt=0;
    if(t->c){
      for(i=0;i<n;i++){
        Node* c = ((Node**)t->c)[i];
        if(c){ disp += abs(c->v - t->v); cnt++; }
      }
      if(cnt>0) disp /= cnt;
    }
    var depthFac = 1.0/(1 + t->d);
    var rateBase = 0.01 + 0.02*t->d;
    var rateFac = exp(-25.0*abs(t->r - rateBase));
    var p = 0.5*(depthFac + rateFac);
    p = 0.5*p + 0.5*(1.0 + (-disp));
    if(p<0) p=0; if(p>1) p=1;
    return p;
  }

  var predByTid(int tid){
    if(!TreeIdx || tid < 0 || tid >= TreeN || !TreeIdx[tid]) return 0.5;
    ensurePredCache();
    if(PredNode && tid < PredLen && PredNode[tid] > -1.5) return PredNode[tid];
    Node* t = TreeIdx[tid];
    var p = nodePredictability(t);
    if(PredNode && tid < PredLen) PredNode[tid] = p;
    return p;
  }

  Node* treeAt(int tid){
    if(!TreeIdx || tid < 0 || tid >= TreeN || !TreeIdx[tid]) return &DummyNode;
    return TreeIdx[tid];
  }

  int safeTreeIndexFromEq(int eqTreeId, int treeN){
    int denom = ifelse(treeN>0, treeN, 1);
    int tid = eqTreeId;
    if(tid < 0) tid = 0;
    if(denom > 0) tid = tid % denom;
    if(tid < 0) tid = 0;
    return tid;
  }

  void maybeShrinkTreeIdx(){
    if(!TreeIdx) return;
    if(TreeCap > 64 && TreeN < (TreeCap>>1)){
      int newCap = (TreeCap>>1);
      if(newCap < 64) newCap = 64;
      TreeIdx = (Node**)realloc(TreeIdx, newCap*sizeof(Node*));
      TreeCap = newCap;
    }
  }

  void resizePredCacheToTree(){
    PredLen = TreeN; if(PredLen <= 0) PredLen = 1;
    if(PredLen > PredCap){
      if(PredNode) free(PredNode);
      PredNode = (var*)malloc(PredLen*sizeof(var));
      PredCap = PredLen;
    }
    PredCacheBar = -1;
  }

  // equation ring angles (needs EqTreeId mapping provided externally)
  void refreshEqAngles(const i16* eqTreeId, int eqN){
    if(!EqTheta) EqTheta = (var*)malloc(eqN*sizeof(var));
    if(!EqTheta) quit("Alpha12: OOM EqTheta");
    var twoPi = 2.*3.141592653589793;
    var denom = ifelse(TreeN>0,(var)TreeN,1.0);
    int i;
    for(i=0;i<eqN;i++){
      int tid = safeTreeIndexFromEq((int)eqTreeId[i], TreeN);
      var u = ((var)tid)/denom;
      EqTheta[i] = twoPi*u;
    }
  }

  static var pi(){ return 3.141592653589793; }

  static var wrapPi(var a){
    while(a <= -pi()) a += 2.*pi();
    while(a >  pi()) a -= 2.*pi();
    return a;
  }

  static var angDiff(var a, var b){ return wrapPi(b - a); }

  void updateEquationCycle(const fvar* prop, int N){
    if(!EqTheta){ CycPh = wrapPi(CycPh); return; }
    int i, bestI=0;
    var bestP=-1;
    for(i=0;i<N;i++){
      var p = (var)prop[i];
      if(p > bestP){ bestP=p; bestI=i; }
    }
    var th = EqTheta[bestI];
    var d = angDiff(LeadTh, th);
    CycSpd = 0.9*CycSpd + 0.1*d;
    CycPh = wrapPi(CycPh + CycSpd);
    LeadEq = bestI;
    LeadTh = th;
  }

  // --- prune & grow helpers (direct lift) ---
  var nodeImportance(Node* u){
    if(!u) return 0;
    var amp = abs(u->v); if(amp>1) amp=1;
    var p = nodePredictability(u);
    var depthW = 1.0/(1.0 + u->d);
    var imp = (0.6*p + 0.4*amp) * depthW;
    return imp;
  }

  Node* createLeafDepth(int d){
    Node* u = pool->allocNode();
    if(!u) return 0;
    u->v = random();
    u->r = 0.01 + 0.02*d + random(0.005);
    u->n = 0;
    u->c = 0;
    u->d = d;
    return u;
  }

  void growSelectiveAtDepth(Node* u, int frontierDepth, int addK){
    if(!u) return;
    if(u->d == frontierDepth){
      int want = addK; if(want<=0) return;
      int oldN=u->n, newN=oldN+want;
      Node** Cnew = (Node**)malloc(newN*sizeof(void*));
      if(!Cnew) return;
      if(oldN>0 && u->c) memcpy(Cnew,u->c,oldN*sizeof(void*));
      int i;
      for(i=oldN;i<newN;i++) Cnew[i] = createLeafDepth(frontierDepth-1);
      if(u->c) free(u->c);
      u->c = Cnew;
      u->n = newN;
      return;
    }
    int j;
    for(j=0;j<u->n;j++) growSelectiveAtDepth(((Node**)u->c)[j],frontierDepth,addK);
  }

  void freeChildAt(Node* parent, int idx){
    if(!parent || !parent->c) return;
    Node** C = (Node**)parent->c;
    freeTree(C[idx]);
    int i;
    for(i=idx+1;i<parent->n;i++) C[i-1]=C[i];
    parent->n--;
    if(parent->n==0){ free(parent->c); parent->c=0; }
  }

  void pruneSelectiveAtDepth(Node* u, int targetDepth, int keepK){
    if(!u) return;
    if(u->d == targetDepth-1 && u->n > 0){
      int n=u->n, i, kept=0;
      int mark[16]; for(i=0;i<16;i++) mark[i]=0;
      int iter;
      for(iter=0; iter<keepK && iter<n; iter++){
        int bestI=-1; var bestImp=-1;
        for(i=0;i<n;i++){
          if(i<16 && mark[i]==1) continue;
          var imp = nodeImportance(((Node**)u->c)[i]);
          if(imp > bestImp){ bestImp=imp; bestI=i; }
        }
        if(bestI>=0 && bestI<16){ mark[bestI]=1; kept++; }
      }
      for(i=n-1;i>=0;i--) if(i<16 && mark[i]==0) freeChildAt(u,i);
      return;
    }
    int j; for(j=0;j<u->n;j++) pruneSelectiveAtDepth(((Node**)u->c)[j],targetDepth,keepK);
  }
};

// ======================================================================
// ======================== RuntimeManager (component) ===================

class RuntimeManager {
public:
  int MemFixedBytes = 0;
  int TreeBytesCached = 0;

  int ShedStage = 0;
  int LastDepthActBar = -999999;
  int ChartsOff = 0;
  int LogsOff = 0;
  int RT_TreeMaxDepth = MAX_DEPTH;

  // Accuracy sentinel
  var ACC_mx=0, ACC_my=0, ACC_mx2=0, ACC_my2=0, ACC_mxy=0;
  var AccCorr=0;
  var AccBase=0;
  int HaveBase=0;

  // Elastic depth tuner
  var UtilBefore=0, UtilAfter=0;
  int TunePending=0;
  int TuneStartBar=0;
  int TuneAction=0;
  var DTreeExpStep = 0.05;
  int DTreeExpDir = 1;

public:
  int mem_bytes_est() const { return MemFixedBytes + TreeBytesCached; }
  int mem_mb_est() const { return mem_bytes_est()/(1024*1024); }

  void recalcTreeBytes(DTree& dt){ TreeBytesCached = dt.tree_bytes(dt.Root); }

  void computeMemFixedBytes(const NetState& net, const DTree& dt, int includeExprBytes){
    int N=net.N, D=net.D, K=net.K;
    int SZV=sizeof(var), SZF=sizeof(fvar), SZI16=sizeof(i16), SZI8=sizeof(i8), SZP=sizeof(void*);
    int b=0;

    b += N*SZV*2;       // State, Prev
    b += N*SZV;         // StateSq

    b += N*D*SZI16;     // Adj
    b += N*SZI16;       // EqTreeId
    b += N*SZI8;        // Mode

    b += K*N*SZF;       // RP
    b += K*SZF;         // Z

    b += N*SZF*(8);     // weights
    b += N*SZF*(7+7);   // A1*, A2*
    b += N*SZF*(2+2);   // G1mean,G1E,G2P,G2lam
    b += N*SZF*(2);     // TAlpha,TBeta
    b += N*SZF*(1);     // TreeTerm

    b += N*(SZI16 + SZF); // TopEq,TopW
    b += N*SZF*2;         // PropRaw,Prop

    b += N*SZF;           // HitEW
    b += N*SZF;           // AdvPrev
    b += N*sizeof(int);   // HitN

    // Markov: caller adds separately if desired (we do it in strategy since there are 3 chains)

    b += dt.TreeCap*SZP;  // Tree index capacity

    if(includeExprBytes) b += N*EXPR_MAXLEN;

    MemFixedBytes = b;
  }

  void shed_zero_cost_once(){
    if(ShedStage > 0) return;
    set(PLOTNOW|OFF);
    ChartsOff = 1;
    LogsOff = 1;
    ShedStage = 1;
  }

  void acc_update(var x, var y){
    var a=0.01;
    ACC_mx=(1-a)*ACC_mx + a*x;
    ACC_my=(1-a)*ACC_my + a*y;
    ACC_mx2=(1-a)*ACC_mx2 + a*(x*x);
    ACC_my2=(1-a)*ACC_my2 + a*(y*y);
    ACC_mxy=(1-a)*ACC_mxy + a*(x*y);
    var vx = ACC_mx2 - ACC_mx*ACC_mx;
    var vy = ACC_my2 - ACC_my*ACC_my;
    var cv = ACC_mxy - ACC_mx*ACC_my;
    if(vx>0 && vy>0) AccCorr = cv / sqrt(vx*vy);
    else AccCorr = 0;
    if(!HaveBase){ AccBase=AccCorr; HaveBase=1; }
  }

  var util_now(){
    int mb = mem_mb_est();
    var mem_pen = 0;
    if(mb > MEM_BUDGET_MB) mem_pen = (mb - MEM_BUDGET_MB)/(var)MEM_BUDGET_MB;
    return AccCorr - 0.5*mem_pen;
  }

  void depth_manager_runtime(DTree& dt){
    int trigger = MEM_BUDGET_MB - MEM_HEADROOM_MB;
    int mb = mem_mb_est();
    if(mb < trigger) return;

    if(ShedStage == 0) shed_zero_cost_once();
    if(ShedStage <= 1) ShedStage = 2;

    int overBudget = (mb >= MEM_BUDGET_MB);
    if(!overBudget && (Bar - LastDepthActBar < DEPTH_STEP_BARS)) return;

    while(RT_TreeMaxDepth > RUNTIME_MIN_DEPTH) {
      int keepK = ifelse(mem_mb_est() < MEM_BUDGET_MB + 2, KEEP_CHILDREN_HI, KEEP_CHILDREN_LO);
      dt.pruneSelectiveAtDepth(dt.Root, RT_TreeMaxDepth, keepK);
      RT_TreeMaxDepth--;
      // reindex caller does (strategy)
      mb = mem_mb_est();
      printf("\n[DepthMgr] depth=%i keepK=%i est=%i MB", RT_TreeMaxDepth, keepK, mb);
      if(mb < trigger) break;
    }
    LastDepthActBar = Bar;
  }
};

// ======================================================================
// ========================= Alpha12Strategy (owner) =====================

class Alpha12Strategy {
public:
  // Components
  NodePool        pool;
  DTree           dt;
  NetState        net;
  Alpha12Logger   log;
  RuntimeManager  rt;

  // Markov: HTF, LTF, REL
  MarkovChain MH;
  MarkovChain ML;
  MarkovChain MR;

  // Strategy runtime
  int ready = 0;
  int Epoch = 0;
  int CtxID = 0;

  // Markov adaptive knobs
  var FB_W = 0.70;
  var MC_ACT_dyn = MC_ACT;
  var MC_Alpha = 1.0;
  int CandNeigh = CAND_NEIGH;

  // Rewire/update cursors
  int RewirePos = 0;
  int RewirePasses = 0;
  int UpdatePos = 0;
  int UpdatePasses = 0;

  // Signal & trade
  var LastSig = 0;

  // Advisor budget/rotation
  int AdviseMax = 16;

  // Advisor seed cache (moved from static)
  int seedBar = -1;
  int haveSeed[NET_EQNS];
  var seedVal[NET_EQNS];

public:
  Alpha12Strategy() {
    dt.bindPool(&pool);
    memset(haveSeed,0,sizeof(haveSeed));
    memset(seedVal,0,sizeof(seedVal));
  }

  ~Alpha12Strategy(){ cleanup(); }

  // --------------------- utilities (direct lifts) ---------------------
  static var randsign(){ return ifelse(random(1) < 0.5, -1.0, 1.0); }
  static var mapUnit(var u,var lo,var hi){
    if(u<-1) u=-1;
    if(u>1) u=1;
    var t=0.5*(u+1.0);
    return lo + t*(hi-lo);
  }
  static var safeNum(var x){ if(invalid(x)) return 0; return clamp(x,-1e100,1e100); }
  static void sanitize(var* A,int n){ int k; for(k=0;k<n;k++) A[k]=safeNum(A[k]); }
  static var sat100(var x){ return clamp(x,-100.,100.); }
  static var nrm_s(var x) { return sat100(100.*tanh(x)); }
  static var nrm_scl(var x, var s) { return sat100(100.*tanh(s*x)); }

  // --------------- Candlestick pattern builder (unchanged) ------------
  int buildCDL_TA61(var* out, string* names)
  {
    int n = 0;
    #define ADD(Name, Call) do{ var v = (Call); if(out) out[n] = v/100.; if(names) names[n] = Name; n++; }while(0)

    ADD("CDL2Crows",              CDL2Crows());
    ADD("CDL3BlackCrows",         CDL3BlackCrows());
    ADD("CDL3Inside",             CDL3Inside());
    ADD("CDL3LineStrike",         CDL3LineStrike());
    ADD("CDL3Outside",            CDL3Outside());
    ADD("CDL3StarsInSouth",       CDL3StarsInSouth());
    ADD("CDL3WhiteSoldiers",      CDL3WhiteSoldiers());
    ADD("CDLAbandonedBaby",       CDLAbandonedBaby(0.3));
    ADD("CDLAdvanceBlock",        CDLAdvanceBlock());
    ADD("CDLBeltHold",            CDLBeltHold());
    ADD("CDLBreakaway",           CDLBreakaway());
    ADD("CDLClosingMarubozu",     CDLClosingMarubozu());
    ADD("CDLConcealBabysWall",    CDLConcealBabysWall());
    ADD("CDLCounterAttack",       CDLCounterAttack());
    ADD("CDLDarkCloudCover",      CDLDarkCloudCover(0.3));
    ADD("CDLDoji",                CDLDoji());
    ADD("CDLDojiStar",            CDLDojiStar());
    ADD("CDLDragonflyDoji",       CDLDragonflyDoji());
    ADD("CDLEngulfing",           CDLEngulfing());
    ADD("CDLEveningDojiStar",     CDLEveningDojiStar(0.3));
    ADD("CDLEveningStar",         CDLEveningStar(0.3));
    ADD("CDLGapSideSideWhite",    CDLGapSideSideWhite());
    ADD("CDLGravestoneDoji",      CDLGravestoneDoji());
    ADD("CDLHammer",              CDLHammer());
    ADD("CDLHangingMan",          CDLHangingMan());
    ADD("CDLHarami",              CDLHarami());
    ADD("CDLHaramiCross",         CDLHaramiCross());
    ADD("CDLHignWave",            CDLHignWave());
    ADD("CDLHikkake",             CDLHikkake());
    ADD("CDLHikkakeMod",          CDLHikkakeMod());
    ADD("CDLHomingPigeon",        CDLHomingPigeon());
    ADD("CDLIdentical3Crows",     CDLIdentical3Crows());
    ADD("CDLInNeck",              CDLInNeck());
    ADD("CDLInvertedHammer",      CDLInvertedHammer());
    ADD("CDLKicking",             CDLKicking());
    ADD("CDLKickingByLength",     CDLKickingByLength());
    ADD("CDLLadderBottom",        CDLLadderBottom());
    ADD("CDLLongLeggedDoji",      CDLLongLeggedDoji());
    ADD("CDLLongLine",            CDLLongLine());
    ADD("CDLMarubozu",            CDLMarubozu());
    ADD("CDLMatchingLow",         CDLMatchingLow());
    ADD("CDLMatHold",             CDLMatHold(0.5));
    ADD("CDLMorningDojiStar",     CDLMorningDojiStar(0.3));
    ADD("CDLMorningStar",         CDLMorningStar(0.3));
    ADD("CDLOnNeck",              CDLOnNeck());
    ADD("CDLPiercing",            CDLPiercing());
    ADD("CDLRickshawMan",         CDL_RickshawMan());
    ADD("CDLRiseFall3Methods",    CDL_RiseFall3Methods());
    ADD("CDLSeperatingLines",     CDL_SeperatingLines());
    ADD("CDLShootingStar",        CDL_ShootingStar());
    ADD("CDLShortLine",           CDL_ShortLine());
    ADD("CDLSpinningTop",         CDL_SpinningTop());
    ADD("CDLStalledPattern",      CDL_StalledPattern());
    ADD("CDLStickSandwhich",      CDL_StickSandwhich());
    ADD("CDLTakuri",              CDL_Takuri());
    ADD("CDLTasukiGap",           CDL_TasukiGap());
    ADD("CDLThrusting",           CDL_Thrusting());
    ADD("CDLTristar",             CDL_Tristar());
    ADD("CDLUnique3River",        CDL_Unique3River());
    ADD("CDLUpsideGap2Crows",     CDLUpsideGap2Crows());
    ADD("CDLXSideGap3Methods",    CDL_XSideGap3Methods());

    #undef ADD
    return n;
  }

  // Some Zorro candle functions have exact names; if your original compile used
  // "CDLRickshawMan()" etc. then replace above stubs with exact spelling.
  // To preserve your original code, you can revert those 10 lines back to your originals.
  // --- quick aliases (in case of underscore variants) ---
  var CDL_RickshawMan(){ return CDLRickshawMan(); }
  var CDL_RiseFall3Methods(){ return CDLRiseFall3Methods(); }
  var CDL_SeperatingLines(){ return CDLSeperatingLines(); }
  var CDL_ShootingStar(){ return CDLShootingStar(); }
  var CDL_ShortLine(){ return CDLShortLine(); }
  var CDL_SpinningTop(){ return CDLSpinningTop(); }
  var CDL_StalledPattern(){ return CDLStalledPattern(); }
  var CDL_StickSandwhich(){ return CDLStickSandwhich(); }
  var CDL_Takuri(){ return CDLTakuri(); }
  var CDL_TasukiGap(){ return CDLTasukiGap(); }
  var CDL_Thrusting(){ return CDLThrusting(); }
  var CDL_Tristar(){ return CDLTristar(); }
  var CDL_Unique3River(){ return CDLUnique3River(); }
  var CDL_XSideGap3Methods(){ return CDLXSideGap3Methods(); }

  // --------------- Markov relation mapping (unchanged) ----------------
  int relFromHL(int sL, int sH){
    if(sL <= 0 || sH <= 0) return MC_NONE;
    int idxL = (sL - 1)/2; int bullL = ((sL - 1)%2)==1;
    int idxH = (sH - 1)/2; int bullH = ((sH - 1)%2)==1;
    if(idxL == idxH && bullL == bullH) return sL;
    return MC_NONE;
  }

  int is_H1_close(){ return (Bar % TF_H1) == 0; }

  // ------------------ memory estimator integration --------------------
  int mem_mb_est() { return rt.mem_mb_est(); }

  // ----------------------- strategy init/cleanup ----------------------
  // ----------------------- strategy init/cleanup ----------------------
  // ----------------------- strategy init/cleanup ----------------------
  void init() {
    if(ready) return;

    // IMPORTANT: all bar-generation settings must be set BEFORE asset()
    BarPeriod = BAR_PERIOD;
    LookBack  = max(300, NWIN);

    // Optional, but if you use them they MUST also be before asset()
    // StartDate = 20210101;
    // EndDate   = 20251231;

    set(PLOTNOW);

    // asset() must come AFTER BarPeriod/LookBack/StartDate/EndDate
    asset(ASSET_SYMBOL);

    // effective K
    net.Keff = KPROJ;
    if(net.Keff < 1) net.Keff = 1;
    if(net.Keff > net.K) net.Keff = net.K;

    // allocate components
    net.allocate();

    MH.alloc(); ML.alloc(); MR.alloc();
    MH.Alpha = MC_Alpha; ML.Alpha = MC_Alpha; MR.Alpha = MC_Alpha;

    dt.allocDepthLUT();
    dt.DTreeExp = 0;
    dt.Root = dt.createNode(MAX_DEPTH);
    rt.RT_TreeMaxDepth = MAX_DEPTH;
    dt.refreshDepthW();

    // index tree and map EqTreeId
    reindexTreeAndMap();

    // projection matrix
    net.randomizeRP();
    net.computeProjection();

    // initial full rewire
    rewireEpoch(0,0,0,0);

    log.writeEqHeaderOnce();

    RewirePos=0; RewirePasses=0;
    UpdatePos=0; UpdatePasses=0;

    // seed cache reset
    seedBar = -1;
    memset(haveSeed,0,sizeof(haveSeed));

    ready = 1;

    printf("\n[Alpha12-OOP] init done: N=%i D=%i K=%i (Keff=%i) Depth=%i est=%i MB",
    net.N, net.D, net.K, net.Keff, rt.RT_TreeMaxDepth, mem_mb_est());
  }



  void cleanup() {
    if(!ready) return;

    MH.freeMem(); ML.freeMem(); MR.freeMem();

    dt.freeAll();
    pool.freeAll();
    net.freeAll();

    ready = 0;
  }

  // --------------------- core tree reindex & mapping -------------------
  void reindexTreeAndMap(){
    dt.TreeN = 0;
    dt.indexTreeDFS(dt.Root);
    if(dt.TreeN <= 0){
      dt.TreeN = 1;
      if(dt.TreeIdx) dt.TreeIdx[0] = dt.Root;
    }
    // map eq -> tree id
    { int i; for(i=0;i<net.N;i++) net.EqTreeId[i] = (i16)(i % dt.TreeN); }

    dt.resizePredCacheToTree();
    dt.refreshEqAngles(net.EqTreeId, net.N);
    dt.maybeShrinkTreeIdx();

    rt.recalcTreeBytes(dt);

    // recompute fixed bytes (include markov storage and expr storage if enabled)
    int includeExpr = (LOG_EXPR_TEXT && net.Sym && !net.SymFreed) ? 1 : 0;
    rt.computeMemFixedBytes(net, dt, includeExpr);

    // add Markov bytes (3 chains)
    rt.MemFixedBytes += 3*(MC_STATES*MC_STATES*(int)sizeof(int) + MC_STATES*(int)sizeof(int));
  }

  // --------------------- Markov updates (OOP) --------------------------
  void updateMarkov_5M(){
    static var CDL_L[MC_NPAT];
    buildCDL_TA61(CDL_L,0);
    int s = MarkovChain::stateFromCDL(CDL_L, MC_ACT_dyn);

    if(Bar > LookBack) ML.update(ML.Prev, s);
    ML.Prev = s;

    if(s > 0 && s < MC_STATES){
      if(ML.RowSum[s] > 0) ML.rowStats(s, &ML.PBullNext, &ML.Entropy);
      ML.Cur = s;
    }
  }

  void updateMarkov_1H(){
    int saveTF = TimeFrame;
    TimeFrame = TF_H1;

    static var CDL_H[MC_NPAT];
    buildCDL_TA61(CDL_H,0);
    int sH = MarkovChain::stateFromCDL(CDL_H, MC_ACT_dyn);

    if(Bar > LookBack) MH.update(MH.Prev, sH);
    MH.Prev = sH;

    if(sH > 0 && sH < MC_STATES){
      if(MH.RowSum[sH] > 0) MH.rowStats(sH, &MH.PBullNext, &MH.Entropy);
      MH.Cur = sH;
    }

    TimeFrame = saveTF;
  }

  void updateMarkov_REL(){
    int r = relFromHL(ML.Cur, MH.Cur);
    if(Bar > LookBack) MR.update(MR.Prev, r);
    MR.Prev = r;

    if(r > 0 && r < MC_STATES){
      if(MR.RowSum[r] > 0) MR.rowStats(r, &MR.PBullNext, &MR.Entropy);
      MR.Cur = r;
    }
  }

  void updateAllMarkov(){
    // propagate Laplace alpha
    MH.Alpha = MC_Alpha;
    ML.Alpha = MC_Alpha;
    MR.Alpha = MC_Alpha;

    updateMarkov_5M();
    if(is_H1_close()){
      updateMarkov_1H();
      updateMarkov_REL();
    }
  }

  // --------------------- Advisor rotation (unchanged) ------------------
  int allowAdvise(int i){
    int groups = net.N / AdviseMax;
    if(groups < 1) groups = 1;
    return ((i / AdviseMax) % groups) == (Bar % groups);
  }

  // --------------------- DTREE advisor wrappers ------------------------
  var adviseEq(int i, var lambda, var mean, var energy, var power){
    if(!allowAdvise(i)) return 0;
    if(is(INITRUN)) return 0;

    int tight = (mem_mb_est() >= MEM_BUDGET_MB - MEM_HEADROOM_MB);
    if(tight) return 0;

    if(net.HitN[i] > 32){
      var h = (var)net.HitEW[i];
      var gate = 0.40 + 0.15*(1.0 - MH.Entropy);
      if(h < gate){
        if(random() >= 0.5) return 0;
      }
    }

    int tid = dt.safeTreeIndexFromEq((int)net.EqTreeId[i], dt.TreeN);
    var pred = dt.predByTid(tid);

    var S[ADV_EQ_NF];
    buildEqFeatures(i, lambda, mean, energy, power, pred, S);

    var obj = 0;
    if(Train){
      obj = sat100(100.0*tanh(0.6*lambda + 0.4*mean));
      var prior = 0.75 + 0.5*((var)net.HitEW[i] - 0.5);
      obj *= prior;
      // cycle priors
      { var th_i = (dt.EqTheta ? dt.EqTheta[i] : 0);
        var dphi = DTree::angDiff(dt.CycPh, th_i);
        var align = 0.90 + 0.20*(0.5*(cos(dphi)+1.0));
        var spdOK = 0.90 + 0.20*clamp(abs(dt.CycSpd)/(0.15), 0., 1.);
        obj *= align * spdOK;
      }
    }

    int objI = (int)obj;
    var a = adviseLong(DTREE, objI, S, ADV_EQ_NF);
    return a/100.;
  }

  var adviseSeed(int i, var lambda, var mean, var energy, var power){
    if(seedBar != Bar){
      int k; for(k=0;k<net.N;k++) haveSeed[k]=0;
      seedBar = Bar;
    }
    if(i < 0) i=0; if(i>=net.N) i=i % net.N;
    if(!allowAdvise(i)) return 0;
    if(!haveSeed[i]){
      seedVal[i] = adviseEq(i,lambda,mean,energy,power);
      haveSeed[i] = 1;
    }
    return seedVal[i];
  }

  static var mix01(var a, int salt){
    var z = sin(123.456*a + 0.001*salt) + cos(98.765*a + 0.002*salt);
    return tanh(0.75*z);
  }
  static var mapA(var a,var lo,var hi){ return mapUnit(a,lo,hi); }

  // ------------------- Feature builders (unchanged logic) ---------------
  void buildEqFeatures(int i, var lambda, var mean, var energy, var power, var pred, var* S /*ADV_EQ_NF*/) {
    int tid = dt.safeTreeIndexFromEq((int)net.EqTreeId[i], dt.TreeN);
    Node* t = dt.treeAt(tid);

    var th_i = (dt.EqTheta ? dt.EqTheta[i] : 0);
    var dphi = DTree::angDiff(dt.CycPh, th_i);
    var alignC = cos(dphi);
    var alignS = sin(dphi);

    S[0]  = nrm_s(net.State[i]);
    S[1]  = nrm_s(mean);
    S[2]  = nrm_scl(power,0.05);
    S[3]  = nrm_scl(energy,0.01);
    S[4]  = nrm_s(lambda);
    S[5]  = sat100(200.0*(pred-0.5));
    S[6]  = sat100(200.0*((var)t->d/MAX_DEPTH)-100.0);
    S[7]  = sat100(1000.0*t->r);
    S[8]  = nrm_s((var)net.TreeTerm[i]);
    S[9]  = sat100( (200.0/3.0) * (var)((int)net.Mode[i]) - 100.0 );

    // HTF
    S[10] = sat100(200.0*(MH.PBullNext-0.5));
    S[11] = sat100(200.0*(MH.Entropy-0.5));

    S[12] = sat100(200.0*((var)net.HitEW[i] - 0.5));
    S[13] = sat100(100.*alignC);
    S[14] = sat100(100.*alignS);

    // 5M & Relation
    S[15] = sat100(200.0*(ML.PBullNext - 0.5));
    S[16] = sat100(200.0*(ML.Entropy   - 0.5));
    S[17] = sat100(200.0*(MR.PBullNext - 0.5));
    S[18] = sat100(200.0*(MR.Entropy   - 0.5));

    sanitize(S,ADV_EQ_NF);
  }

  // ---------------- adjacency scoring (heuristic only) ------------------
  var scorePairSafe(int i, int j, var lambda, var mean, var energy, var power){
    int ti = dt.safeTreeIndexFromEq((int)net.EqTreeId[i], dt.TreeN);
    int tj = dt.safeTreeIndexFromEq((int)net.EqTreeId[j], dt.TreeN);
    Node* ni = dt.treeAt(ti);
    Node* nj = dt.treeAt(tj);

    var simD = 1.0 / (1.0 + abs((var)ni->d - (var)nj->d));
    var dr = 50.0*abs(ni->r - nj->r);
    var simR = 1.0 / (1.0 + dr);

    var predi = dt.predByTid(ti);
    var predj = dt.predByTid(tj);
    var pred  = 0.5*(predi + predj);

    var score = 0.5*pred + 0.3*simD + 0.2*simR;
    return 2.0*score - 1.0;
  }

  void rewireAdjacency_DTREE_range(int i0,int i1, var lambda, var mean, var energy, var power){
    int i,d,c,best,cand;
    if(i0<0) i0=0; if(i1>net.N) i1=net.N;
    for(i=i0;i<i1;i++){
      for(d=0; d<net.D; d++){
        var bestScore = -2; best=-1;
        for(c=0;c<CandNeigh;c++){
          cand = (int)random(net.N);
          if(cand==i) continue;
          int clash=0,k;
          for(k=0;k<d;k++){
            int prev = net.Adj[i*net.D + k];
            if(prev>=0 && prev==cand){ clash=1; break; }
          }
          if(clash) continue;
          var s = scorePairSafe(i,cand,lambda,mean,energy,power);
          if(s > bestScore){ bestScore=s; best=cand; }
        }
        if(best<0){ do{ best=(int)random(net.N);} while(best==i); }
        net.Adj[i*net.D + d] = (i16)best;
      }
    }
  }

  // ---------------- coefficient synthesis (unchanged) -------------------
  void synthesizeEquationFromDTREE(int i, var lambda, var mean, var energy, var power){
    var seed = adviseSeed(i,lambda,mean,energy,power);

    net.Mode[i] = (int)(abs(1000*seed)) & 3;

    net.WSelf[i]  = (fvar)mapA(mix01(seed, 11), 0.15, 0.85);
    net.WN1[i]    = (fvar)mapA(mix01(seed, 12), 0.05, 0.35);
    net.WN2[i]    = (fvar)mapA(mix01(seed, 13), 0.05, 0.35);
    net.WGlob1[i] = (fvar)mapA(mix01(seed, 14), 0.05, 0.30);
    net.WGlob2[i] = (fvar)mapA(mix01(seed, 15), 0.05, 0.30);
    net.WMom[i]   = (fvar)mapA(mix01(seed, 16), 0.02, 0.15);
    net.WTree[i]  = (fvar)mapA(mix01(seed, 17), 0.05, 0.35);
    net.WAdv[i]   = (fvar)mapA(mix01(seed, 18), 0.05, 0.35);

    net.A1x[i]   = (fvar)(randsign()*mapA(mix01(seed, 21), 0.6, 1.2));
    net.A1lam[i] = (fvar)(randsign()*mapA(mix01(seed, 22), 0.05,0.35));
    net.A1mean[i]= (fvar) mapA(mix01(seed, 23),-0.30,0.30);
    net.A1E[i]   = (fvar) mapA(mix01(seed, 24),-0.0015,0.0015);
    net.A1P[i]   = (fvar) mapA(mix01(seed, 25),-0.30,0.30);
    net.A1i[i]   = (fvar) mapA(mix01(seed, 26),-0.02,0.02);
    net.A1c[i]   = (fvar) mapA(mix01(seed, 27),-0.20,0.20);

    net.A2x[i]   = (fvar)(randsign()*mapA(mix01(seed, 31), 0.6, 1.2));
    net.A2lam[i] = (fvar)(randsign()*mapA(mix01(seed, 32), 0.05,0.35));
    net.A2mean[i]= (fvar) mapA(mix01(seed, 33),-0.30,0.30);
    net.A2E[i]   = (fvar) mapA(mix01(seed, 34),-0.0015,0.0015);
    net.A2P[i]   = (fvar) mapA(mix01(seed, 35),-0.30,0.30);
    net.A2i[i]   = (fvar) mapA(mix01(seed, 36),-0.02,0.02);
    net.A2c[i]   = (fvar) mapA(mix01(seed, 37),-0.20,0.20);

    net.G1mean[i] = (fvar) mapA(mix01(seed, 41), 0.4, 1.6);
    net.G1E[i]    = (fvar) mapA(mix01(seed, 42),-0.004,0.004);
    net.G2P[i]    = (fvar) mapA(mix01(seed, 43), 0.1, 1.2);
    net.G2lam[i]  = (fvar) mapA(mix01(seed, 44), 0.05, 0.7);

    net.TAlpha[i] = (fvar) mapA(mix01(seed, 51), 0.3, 1.5);
    net.TBeta[i]  = (fvar) mapA(mix01(seed, 52), 6.0, 50.0);

    net.PropRaw[i] = (fvar)(0.01 + 0.99*(0.5*(seed+1.0)));

    // reliability boost
    { var boost = 0.75 + 0.5*(var)net.HitEW[i];
      net.PropRaw[i] = (fvar)((var)net.PropRaw[i] * boost);
    }
  }

  void synthesizeEquation_range(int i0,int i1, var lambda, var mean, var energy, var power){
    if(i0<0) i0=0; if(i1>net.N) i1=net.N;
    int i;
    for(i=i0;i<i1;i++) synthesizeEquationFromDTREE(i,lambda,mean,energy,power);
  }

  // ------------------- DTREE ensemble term (unchanged) ------------------
  var dtreeTerm(int i, int* outTopEq, var* outTopW){
    int j;
    int tid_i = dt.safeTreeIndexFromEq((int)net.EqTreeId[i], dt.TreeN);
    Node* ti = dt.treeAt(tid_i);
    int di = ti->d; var ri=ti->r;
    var predI = dt.predByTid(tid_i);

    var alpha = (var)net.TAlpha[i];
    var beta  = (var)net.TBeta[i];

    var sumw=0, acc=0, bestW=-1; int bestJ=-1;

    for(j=0;j<net.N;j++){
      if(j==i) continue;

      int tid_j = dt.safeTreeIndexFromEq((int)net.EqTreeId[j], dt.TreeN);
      Node* tj=dt.treeAt(tid_j);
      int dj=tj->d; var rj=tj->r;
      var predJ = dt.predByTid(tid_j);

      var w = exp(-alpha*abs(di-dj)) * exp(-beta*abs(ri-rj));
      var predBoost = 0.5 + 0.5*(predI*predJ);
      var propBoost = 0.5 + 0.5*( (net.Prop[i] + net.Prop[j]) );
      w *= predBoost * propBoost;

      var pairAdv = scorePairSafe(i,j,0,0,0,0);
      var pairBoost = 0.75 + 0.25*(0.5*(pairAdv+1.0));
      w *= pairBoost;

      sumw += w;
      acc  += w*net.State[j];
      if(w>bestW){ bestW=w; bestJ=j; }
    }

    if(outTopEq) *outTopEq = bestJ;
    if(outTopW)  *outTopW  = ifelse(sumw>0, bestW/sumw, 0);
    if(sumw>0) return acc/sumw;
    return 0;
  }

  // ------------------- expression builder (optional) --------------------
  void buildSymbolicExpr(int i, int n1, int n2){
    if(!LOG_EXPR_TEXT) return;
    if(!net.Sym) return;

    string s = net.Sym[i];
    s[0]=0;

    string a1 = strf("(%.3f*x[%i] + %.3f*lam + %.3f*mean + %.5f*E + %.3f*P + %.3f*i + %.3f)",
      (var)net.A1x[i], n1, (var)net.A1lam[i], (var)net.A1mean[i], (var)net.A1E[i], (var)net.A1P[i], (var)net.A1i[i], (var)net.A1c[i]);
    string a2 = strf("(%.3f*x[%i] + %.3f*lam + %.3f*mean + %.5f*E + %.3f*P + %.3f*i + %.3f)",
      (var)net.A2x[i], n2, (var)net.A2lam[i], (var)net.A2mean[i], (var)net.A2E[i], (var)net.A2P[i], (var)net.A2i[i], (var)net.A2c[i]);

    Alpha12Logger::strlcat_safe(s, "x[i]_next = ", EXPR_MAXLEN);
    Alpha12Logger::strlcat_safe(s, strf("%.3f*x[i] + ", (var)net.WSelf[i]), EXPR_MAXLEN);

    if(net.Mode[i]==1){
      Alpha12Logger::strlcat_safe(s, strf("%.3f*tanh%s + ", (var)net.WN1[i], a1), EXPR_MAXLEN);
      Alpha12Logger::strlcat_safe(s, strf("%.3f*sin%s + ",  (var)net.WN2[i], a2), EXPR_MAXLEN);
    } else if(net.Mode[i]==2){
      Alpha12Logger::strlcat_safe(s, strf("%.3f*cos%s + ",  (var)net.WN1[i], a1), EXPR_MAXLEN);
      Alpha12Logger::strlcat_safe(s, strf("%.3f*tanh%s + ", (var)net.WN2[i], a2), EXPR_MAXLEN);
    } else {
      Alpha12Logger::strlcat_safe(s, strf("%.3f*sin%s + ",  (var)net.WN1[i], a1), EXPR_MAXLEN);
      Alpha12Logger::strlcat_safe(s, strf("%.3f*cos%s + ",  (var)net.WN2[i], a2), EXPR_MAXLEN);
    }

    Alpha12Logger::strlcat_safe(s, strf("%.3f*tanh(%.3f*mean + %.5f*E) + ",
      (var)net.WGlob1[i], (var)net.G1mean[i], (var)net.G1E[i]), EXPR_MAXLEN);
    Alpha12Logger::strlcat_safe(s, strf("%.3f*sin(%.3f*P + %.3f*lam) + ",
      (var)net.WGlob2[i], (var)net.G2P[i], (var)net.G2lam[i]), EXPR_MAXLEN);
    Alpha12Logger::strlcat_safe(s, strf("%.3f*(x[i]-x_prev[i]) + ", (var)net.WMom[i]), EXPR_MAXLEN);
    Alpha12Logger::strlcat_safe(s, strf("Prop[i]=%.4f; ", (var)net.Prop[i]), EXPR_MAXLEN);
    Alpha12Logger::strlcat_safe(s, strf("%.3f*DT(i) + ", (var)net.WTree[i]), EXPR_MAXLEN);
    Alpha12Logger::strlcat_safe(s, strf("%.3f*DTREE(i)", (var)net.WAdv[i]), EXPR_MAXLEN);
  }

  void buildSymbolicExpr_range(int i0,int i1){
    if(!LOG_EXPR_TEXT) return;
    if(i0<0) i0=0; if(i1>net.N) i1=net.N;
    int i;
    for(i=i0;i<i1;i++){
      int n1 = net.adjSafe(i,0);
      int n2 = ifelse(net.D>=2, net.adjSafe(i,1), n1);
      buildSymbolicExpr(i,n1,n2);
    }
  }

  // ------------------- chunked rewire orchestrator ----------------------
  int rewireEpochChunk(var lambda, var mean, var energy, var power, int batch){
    if(net.N <= 0) return 0;
    if(batch < REWIRE_MIN_BATCH) batch = REWIRE_MIN_BATCH;

    if(RewirePos >= net.N) RewirePos = 0;
    int i0 = RewirePos;
    int i1 = i0 + batch; if(i1 > net.N) i1 = net.N;

    CandNeigh = ifelse(MH.Entropy < 0.45, CAND_NEIGH+4, CAND_NEIGH);

    rewireAdjacency_DTREE_range(i0,i1, lambda,mean,energy,power);
    net.sanitizeAdjacency();
    synthesizeEquation_range(i0,i1, lambda,mean,energy,power);
    buildSymbolicExpr_range(i0,i1);

    RewirePos = i1;
    if(RewirePos >= net.N){
      RewirePos = 0;
      RewirePasses++;
      return 1;
    }
    return 0;
  }

  void rewireEpoch(var lambda, var mean, var energy, var power){
    int done=0;
    while(!done){
      done = rewireEpochChunk(lambda,mean,energy,power, REWIRE_BATCH_EQ_H1);
    }
    net.normalizeProportions();

    // context hash (unchanged)
    {
      int D = net.D, i, total = net.N*D;
      unsigned int h = 2166136261u;
      for(i=0;i<total;i++){
        unsigned int x = (unsigned int)net.Adj[i];
        h ^= x + 0x9e3779b9u + (h<<6) + (h>>2);
      }
      CtxID = (int)((h ^ ((unsigned int)Epoch<<8)) & 0x7fffffff);
    }
  }

  // ------------------- coarse net projection -> gamma -------------------
  var projectNet(){
    int i;
    var sum=0,sumsq=0,cross=0;
    for(i=0;i<net.N;i++){
      sum += net.State[i];
      sumsq += net.State[i]*net.State[i];
      if(i+1<net.N) cross += net.State[i]*net.State[i+1];
    }
    var mean = sum/net.N;
    var corr = cross/(net.N-1);
    return 0.6*tanh(mean + 0.001*sumsq) + 0.4*sin(corr);
  }

  // ------------------- heavy update chunk (unchanged) -------------------
  var nonlin1(int i, int n1, var lam, var mean, var E, var P){
    var x = net.State[n1];
    var arg = (var)net.A1x[i]*x + (var)net.A1lam[i]*lam + (var)net.A1mean[i]*mean + (var)net.A1E[i]*E + (var)net.A1P[i]*P + (var)net.A1i[i]*i + (var)net.A1c[i];
    return arg;
  }
  var nonlin2(int i, int n2, var lam, var mean, var E, var P){
    var x = net.State[n2];
    var arg = (var)net.A2x[i]*x + (var)net.A2lam[i]*lam + (var)net.A2mean[i]*mean + (var)net.A2E[i]*E + (var)net.A2P[i]*P + (var)net.A2i[i]*i + (var)net.A2c[i];
    return arg;
  }

  int heavyUpdateChunk(var lambda, var mean, var energy, var power, int batch){
    if(net.N <= 0) return 0;
    if(batch < UPDATE_MIN_BATCH) batch = UPDATE_MIN_BATCH;

    if(UpdatePos >= net.N) UpdatePos = 0;
    int i0 = UpdatePos;
    int i1 = i0 + batch; if(i1 > net.N) i1 = net.N;

    net.computeProjection();

    int i;
    for(i=i0;i<i1;i++){
      int n1 = net.adjSafe(i,0);
      int n2 = ifelse(net.D>=2, net.adjSafe(i,1), n1);

      int topEq=-1; var topW=0;
      var treeT = dtreeTerm(i, &topEq, &