Commit graph Warrior_EA/Expert/ExpertSignalAIBase.mqh
Author SHA1 Message Date
AnimateDread
450017d6fd fix(Network): pass SourceObject by reference in updateInputWeights
Changed updateInputWeights signature from accepting `CObject*` to `CObject*&` to allow the method to modify the caller's pointer, preventing potential object copying or pointer invalidation. A large block of outdated commentary in ExpertSignalAIBase.mqh was also removed, cleaning up documentation no longer applicable after previous refactoring.
2026-07-28 17:52:13 -04:00
AnimateDread
30c0aafff8 feat(Network): add DFA training and optimizer snapshot support
Introduce Direct Feedback Alignment (DFA) backward pass with gradient clipping, feedback matrix initialization, and a dedicated backPropDfa method. Add optimizer snapshot/restore hooks (CaptureOptimizerSnapshot, RestoreOptimizerSnapshot, SetOptimizerForAllNeurons) to temporarily switch the entire network's optimizer for replay-only updates during pass 2, preserving the original optimizer state. Support all neuron types including dropout, deconv, LSTM, and softmax in the snapshot logic.
2026-07-28 17:42:12 -04:00
AnimateDread
e4f88d7934 feat: replace HiddenLayersCount with AI_TOPOLOGY_PRESET for architecture-aware topology presets 2026-07-28 11:47:29 -04:00
AnimateDread
941c88a20f refactor(hyperparams): soften labels, shrink network, tune priors and gamma 2026-07-28 10:49:53 -04:00
AnimateDread
e043e565eb feat: implement hybrid AI signal with CNN-LSTM architecture and add pooling parameters 2026-07-27 22:08:55 -04:00
AnimateDread
48abb89e6a fix: skip DirectML DLL in tester, add NaN guards, improve chart cleanup
In AI/Network.mqh, return early from InitDirectML during
tester/optimization/forward runs to prevent agent-side file-lock
failures caused by rapid stop/restart cycles accessing DLL imports.

In Expert/ExpertSignalAIBase.mqh, add MathIsValidNumber checks in
CalibratedConfidenceMagnitude and SignaledConfidence to safely handle
NaN values, and refactor ShutdownChartCleanup to accept a preserve
flag, avoiding unnecessary chart purges during tester runs for faster
shutdowns. Also add m_purgeChartOnDestruct member.

In AI/NeuronDirectML.mqh, clean up a minor comment formatting issue.
2026-07-27 15:52:39 -04:00
AnimateDread
3a37b9115e fix: correct inference-only new-bar detection and add stop validation
In inference-only backtests, dtStudied could be ahead of the test range, causing new-bar detection to freeze. Replaced with m_lastBarTime to keep detection aligned with runtime history. Added diagnostic logging when a non-neutral softmax output is neutralized by prior correction. Also added validation for order_price, sl, and tp in stop-checking functions to catch non-finite or negative values.
2026-07-27 11:51:45 -04:00
AnimateDread
146881c507 fix: allow inference-only tester to trade with loaded model when trainingComplete is false
Added a new `m_modelLoadedFromDisk` flag to distinguish models loaded from a saved `.nnw` file from fresh random topologies. Modified the readiness gates in `LongCondition` and `ShortCondition` so that an inference-only tester run can execute trades using a model loaded from disk even if its persisted `trainingComplete` flag is `false`. This enables replay of a partially trained exported model without requiring full convergence, while still blocking fresh random-weight topologies from trading.
2026-07-27 11:28:23 -04:00
AnimateDread
b1dd61d0da feat: add signals visibility toggle and tester rejection tracing
Introduce a global boolean `g_signalsVisible` to control whether signal
objects are displayed across all timeframes or hidden entirely. When
enabled, chart arrows and restore objects are set to `OBJ_ALL_PERIODS`;
otherwise they use `OBJ_NO_PERIODS`, allowing signals to be shown or
hidden at runtime without losing saved state.

Add `ShouldTraceTradeRejections()` helper that returns true only when
running in the Strategy Tester, optimization, or forward testing modes.
Use it to print diagnostic messages when trades are rejected due to a
prohibition signal or when `OpenLongParams`/`OpenShortParams` fail to
produce valid stop/take-profit levels. This provides targeted debugging
output without cluttering live trading logs.
2026-07-27 11:13:19 -04:00
AnimateDread
53599d5883 Merge branch 'main' of https://forge.mql5.io/animatedread/Warrior_EA 2026-07-27 09:25:03 -04:00
AnimateDread
ff2ece0249 fix: increase min activation deriv floor and seed RNG for fresh topology
Raise MIN_ACTIVATION_DERIVATIVE from 1e-4 to 1e-3 in both Network.cl and Network.mqh
to strengthen the escape signal through saturated hidden neurons. This provides a 10x
stronger safety net against fp32 OpenCL-specific saturation, complementing the earlier
output-layer fix (logit activation instead of sigmoid) that resolved the primary neutral
collapse bug.

Add MathSrand(GetTickCount()) before BuildFreshTopology() in ExpertSignalAIBase.mqh
to guarantee genuinely random weight initialization after genetic tuner evaluations,
matching the final-retrain path and Warrior_EA.mq5's OnInit. This prevents the previous
deterministic RNG state from dominating weight init.

Also remove UTF‑8 BOM from ExpertSignalAIBase.mqh and Network.mqh for cleaner encoding.
2026-07-27 09:24:53 -04:00
b2069bcee4 feat(signals): add MACD/Ichimoku presets and Vote_Close disabled option
Add MACD_FAST, MACD_SLOW, MACD_SIGNAL presets and Ichimoku Tenkan, Kijun, Senkou presets to InputEnums.mqh. All combinations are designed to satisfy the respective indicator's validation rules (fast < slow for MACD, Tenkan < Kijun < Senkou B for Ichimoku), eliminating init errors and allowing the auto-tuner to perturb settings independently.

Introduce VOTE_CLOSE_PRESETS enum with a Disabled option (value 101) that bypasses vote-driven position closing via arithmetic thresholding, removing the need for a separate boolean flag. This ensures positions exit only via stop-loss, take-profit, or trailing when disabled.
2026-07-26 18:33:12 -04:00
8d4fe088b8 refactor: unify AI and classic vote parameters to Min_Vote_Open/Close
Removes standalone AI confidence parameters (MinAIConfidence, MinAIExitConfidence) and replaces them with unified Min_Vote_Open and Min_Vote_Close thresholds that apply to both AI and classic engines. Updates all code comments, report suggestions, and market descriptions accordingly, simplifying configuration and ensuring consistent vote requirements across entry and exit logic.
2026-07-26 17:27:51 -04:00
a17f8f1e15 fix(signal): snapshot alternation gate to prevent premature consumption on discarded votes
Add BeginVote/RevokeVote lifecycle hooks to ExpertSignalCustom and ExpertSignalAIBase.
Snapshot m_lastNonNeutralSignal before condition evaluation in Direction(), and restore
the snapshot if the vote is later discarded (e.g., Hybrid quorum shortfall).
Previously, a discarded vote still consumed the alternation gate, which could
permanently gate out valid signals until the opposite direction appeared.
2026-07-26 17:09:13 -04:00
AnimateDread
3c8f41e6ab fix: preserve historical arrows on rescan and remove temporary throughput diagnostics
- Arrow deletion now scoped to rescan window (barsNow) instead of all arrows, preventing loss of multi-year arrow history on Show Signals click.
- Removed temporary throughput diagnostic counters and logging added to investigate performance regression; issue resolved.
2026-07-26 14:08:59 -04:00
AnimateDread
0233664d29 feat: add raw argmax tally to rescan logs, raise train time budget to 200ms
The commit adds three counters (`m_rescanRawBuy`, `m_rescanRawSell`, `m_rescanRawNeutral`) that accumulate the raw argmax signal before the prior-correction step in `AdvanceChartSignalRescan`. This distinguishes a model that outputs mostly Neutral from one where adjusted signals suppress non-Neutral arrows. The rescan completion log now reports both the pre-decluttering tallies and the time. Additionally, `TRAIN_TIME_BUDGET_MS` is increased from 120 to 200 ms to improve throughput (as noted in the comment), balancing UI responsiveness and training speed.
2026-07-26 13:58:05 -04:00
AnimateDread
f193429299 refactor: make RescanPending() public and update comments for deferred rescan logic 2026-07-26 12:55:31 -04:00
AnimateDread
1bef4fea08 refactor(expert): split RescanChartSignals into async start/advance methods
The old RescanChartSignals ran the entire per-bar inference loop synchronously,
blocking the button-click handler for a potentially long duration on large lookbacks.
Replaced with StartChartSignalRescan (cheap setup) and AdvanceChartSignalRescan
(time-boxed slices) so the heavy work is drained from PollTraining's timer without
freezing the UI.
2026-07-26 12:52:56 -04:00
AnimateDread
15226b64df feat: add manual rescan of chart signal arrows from deployed model
Introduce `RescanChartSignals()` method and `SIGNAL_RESCAN_LOOKBACK_BARS` define to allow operators to replace stale historical arrows (e.g., from years-old training runs) with fresh inferences from the currently deployed weights on recent bars. This prevents outdated signals from lingering on the chart and ensures the displayed set matches what a live re-render would produce.
2026-07-26 12:36:56 -04:00
AnimateDread
5247c34fe9 fix: add error logging for buffer failures and reject trades on invalid stop loss 2026-07-26 12:12:14 -04:00
AnimateDread
fc0b244aa0 feat: implement deferred chart arrow restore to avoid frozen OnInit 2026-07-26 11:17:55 -04:00
AnimateDread
2cf68e2e87 fix: skip weight save on inference-only runs and detect stale cache
PersistWeightsOnShutdown now returns early when m_inferenceOnly is true,
preventing the tester cache from being overwritten with untrained state.
InitNeuralNetwork compares modification timestamps of the production model
(FILE_COMMON) and the agent-local cache, re-seeding when the production
copy is newer. This ensures backtests always run the latest deployed
weights even when the cache file persists across runs. Also moved
LoadChartSignals() call to after the config fingerprint is appended,
so that persisted arrows are correctly keyed per configuration.
2026-07-26 10:59:46 -04:00
AnimateDread
b7ef83f8c6 fix(ai,expert): add FILE_SHARE flags to avoid 5004 errors on deployed models
Add FILE_SHARE_READ and FILE_SHARE_WRITE to all FileOpen calls in CNet::Load,
CNet::LoadCheckpoint, and in ExpertSignalAIBase (LoadModelStats, LoadChartSignals)
so that reading model files does not fail with error 5004 when another process
(e.g., a live chart) holds the file. Replace FileCopy with the new CopySharedFile
function for sidecar files (.cfg, .stats, _shadow.nnw) to perform share-aware
copying, preventing the same failure during tester seeding. This ensures the EA
can read and copy model artifacts even while a deployed instance is running.
2026-07-26 10:46:19 -04:00
AnimateDread
6e47019be5 refactor: add exponential backoff retry logic and bar-close autosave
Replace fixed short-delay retries in CopyFileWithRetry and LoadNetWithRetry with exponential backoff (8 attempts, cap at 2s) to reliably handle transient file locks from AV/EDR or concurrent saves.
Change autosave trigger from a fixed 300-second wall-clock timer to firing on new bar close, reducing unnecessary overwrites and narrowing the collision window with backtest FileCopy operations.
2026-07-26 10:27:38 -04:00
AnimateDread
377aa6bbc4 fix: handle transient file locking during concurrent model saves and loads
Add retry logic (CopyFileWithRetry, LoadNetWithRetry) to the tester's seeding path in ExpertSignalAIBase.mqh, and print diagnostic error messages in CNet::Load (Network.mqh) when FileOpen fails. This addresses silent failures caused by transient Windows sharing violations that occur when a live chart's atomic Save() overlaps with a Strategy Tester agent's FileCopy or FileOpen on the same production file. The new helpers retry the operation a few times with a short pause instead of silently falling back to an untrained model.
2026-07-26 10:15:56 -04:00
AnimateDread
d6bbf26785 fix(ExpertSignalAIBase): add primary occurrence flag to correct IS accuracy under oversampling
The queue duplicates minority bars up to repCount (~21x at 30.7:1 imbalance), causing IS accuracy to be scored over an ~58%-directional set while OOS counts the real ~6% distribution. This made the IS/OOS gap misleading (e.g. 77% vs 12%), appearing as catastrophic overfitting when OOS was actually stronger (1.97x vs 1.33x lift). By marking only the first occurrence per bar with `m_isTrainQueuePrimary` and using that flag in the counter, both metrics now measure the same natural class distribution, making the gap directly interpretable as generalization. Backprop and training remain unaffected—every occurrence still trains as before.
2026-07-25 19:34:24 -04:00
AnimateDread
69307ff121 feat: implement manual deploy and retrain in ExpertSignalAIBase
Extract shared persistence logic into PersistDeployedModel() to guarantee consistency across all deploy paths (ladder, era-cap, stop, and manual). Add DeployNow() to let the panel button finalize the current best checkpoint as the live model, and RetrainDeployed() to revert deployment and resume training from the deployed weights without starting from scratch. This enables operator-controlled deployment while preserving online continual learning.
2026-07-25 16:39:11 -04:00
AnimateDread
c7e533a880 feat(ExpertSignalAIBase): add plateau ladder escalation and legacy MinWR shim
Implement a plateau detection mechanism that escalates through warm restart, gamma annealing, and eventual deployment when balanced accuracy stagnates for `PLATEAU_PATIENCE_ERAS`. Also add a compatibility shim for the removed `MinWR` input to preserve model filenames and `.cfg` layout.
2026-07-25 15:55:56 -04:00
AnimateDread
d5575db339 fix: suppress repeated OpenCL/DirectML probe messages across multiple CNet instances
A single EA run instantiates several CNet objects (main net, EMA shadow, OOS clones, etc.). Each previously ran its own OpenCL/DirectML probe, printing redundant error banners (e.g., "OpenCL not found") and compute tier messages.

Added process-wide static flags `s_openclUnavailable` and `s_computeTierLogged` to skip subsequent probes and log once per process. This eliminates log clutter and avoids unnecessary probe overhead on hosts without OpenCL.
2026-07-25 13:03:45 -04:00
AnimateDread
582e1dec59 fix: correct CLayer::CreateElement signature to prevent model load failures
Rename the original `CreateElement` with a defaulted `weighScale` parameter to `CreateElementScaled` and provide a proper virtual override that matches the base `CArrayObj::CreateElement` signature exactly. This ensures `CArrayObj::Load()` dispatches correctly, fixing a bug where every saved model load failed at the first layer. Update all call sites in `CNeuronBase::Init` and `CNeuronPool::Init`. Additionally, enhance error diagnostics in `CNet::Load` to distinguish between file truncation and code faults (such as the signature mismatch).
2026-07-25 12:02:38 -04:00
AnimateDread
b09c3a5f4e feat(network): add quiet parameter to CNet::Load for graceful miss handling
The `Load` method now accepts an optional `quiet` flag that suppresses diagnostic
`Print` statements when set to `true`. This is used by callers that anticipate
a load failure and handle it gracefully — for example, the EMA shadow-net
bootstrap on a CPU-DLL box, which cannot hold a second full network. The main
model load keeps `quiet=false` so actual failures remain visible.
2026-07-25 11:05:28 -04:00
AnimateDread
703a1f46d6 fix: improve load diagnostics and prevent shutdown crash
- In Network.mqh: Add upfront file size check to distinguish truncated files from backend allocation failures during model load, improving error diagnosis.
- In ExpertSignalAIBase.mqh: Remove redundant shadow net save on shutdown to avoid doubling shutdown cost and exceeding MT5's deinit budget, preventing abnormal termination and subsequent retrain.
- In Warrior_EA.mq5: Reduce training timer interval from 5s to 250ms to allow more frequent training cycles instead of sitting idle ~98% of the time.
2026-07-25 02:01:05 -04:00
AnimateDread
5d6f03014e fix: implement atomic file save in CNet::Save to prevent data corruption
The previous save directly wrote to the target file, which could leave a truncated/partial file if the process was force-killed (e.g., MT5 deinit timeout). Now the save writes to a temporary file (.savetmp) and only renames it over the real file after a successful write. This ensures that an interrupted save never corrupts the last good model. Additionally, improved diagnostics in CNet::Load to distinguish corrupt/incompatible files from compute errors.
2026-07-25 01:21:05 -04:00
AnimateDread
5fda52b212 docs: clarify cumulative accuracy as directional win-rate excluding neutral calls
Updated comments across ExpertSignalAIBase.mqh to explicitly state that the
compounded accuracy metric is a directional win-rate (Buy/Sell predictions only)
and that neutral/no-trade calls are excluded to avoid inflating the rate (since
neutral is the ~94% majority). The cumulative counters now only increment when
the prediction is directional, ensuring the panel reflects genuine trade quality
rather than overall label accuracy.
2026-07-25 01:07:21 -04:00
AnimateDread
3f0d1557de feat: add compounded accuracy counters and remove fired-hitrate threshold
Replace the display-only MIN_FIRED_FOR_HITRATE with persistent cumulative accuracy counters (m_cumIsCorrect, m_cumIsTotal, m_cumOosCorrect, m_cumOosTotal) that survive era-to-era and restart via .stats. New ComputeCompoundedAccuracyLine() builds the panel line from these counts, providing a stable "accuracy over all validated signals so far" metric.
2026-07-25 00:02:34 -04:00
AnimateDread
b9fe2d865f fix: reorder shutdown to ensure chart cleanup before weight persistence
Split `PersistOnShutdown` into `PersistWeightsOnShutdown` (heavy weights save) and `ShutdownChartCleanup` (save arrows + purge chart).  In `OnDeinit`, run panel destruction and per‑signal chart cleanup **before** the weight persistence, preventing leftover chart objects when the weight save stalls or faults past MT5's deinit budget.
2026-07-24 21:56:53 -04:00
AnimateDread
aba963a97d feat: require minimum fired calls for hit-rate display
Add MIN_FIRED_FOR_HITRATE constant (10) to the simple panel and training status hit-rate display. Update ScheduleTrainingIfNeeded and UpdateTrainingStatusLabel to only show a percentage when a side has at least MIN_FIRED_FOR_HITRATE live-fired calls; otherwise show "n/a" or "measuring". This prevents misleading early "0%" or "measuring" output from a single sample, while keeping full detail in logs and verbose panel.
2026-07-24 21:42:26 -04:00
AnimateDread
635cb555c1 fix: force isInitialized=false on weight reset to avoid config mismatch
- ResetWeights now passes `false` (not `m_isInitialized`) to SaveTopologyConfiguration so the config fingerprint matches the fresh-init write (always false). Prevents spurious "Configuration mismatch" that would discard the just-reset weights.
- UpdateTrainingStatusLabel: collapse hit-rate display to "measuring..." when neither side has fired yet; once at least one side fired, show both with "n/a" for missing side.
2026-07-24 21:36:02 -04:00
AnimateDread
d7c550c470 feat: add in-memory weight snapshot/restore for mid-run stability
Add CaptureWeights and RestoreWeights methods that snapshot every neuron's weights into host arrays (CArrayDouble per neuron) and restore them in-place via setWeights. This replaces the file-based SaveCheckpoint/LoadCheckpoint for the mid-run best-era rollback, because the file path re-creates neurons (CLayer+Init) which fails on the multithreaded CPU-DLL backend (CDirectMLMy/WarriorCPU.dll) that cannot allocate a second full set of neuron tensors while the live set exists. In-memory weight copy uses only getWeights/setWeights, already proven by the per-era shadow blend. Snapshots weights only (not Adam moments); the regression handler decays eta on restore and clips per-step deltas to prevent stale-moment overshoot. Snapshot is valid only within a single Train() run. Also adds HaveWeightSnapshot() query and the m_weightSnapshot / m_haveWeightSnapshot member variables.
2026-07-24 21:21:47 -04:00
AnimateDread
339369fe99 feat: add common parameter to checkpoint save/load for consistent file location
SaveCheckpoint and LoadCheckpoint in Network.mqh now accept a `bool common` flag. When true, the ephemeral checkpoint is stored in FILE_COMMON (co-located with the model's .nnw/.cfg/.stats/_shadow.nnw), preventing a parallel folder tree under the terminal-local Files directory. ExpertSignalAIBase.mqh is updated to pass the same flag to file existence checks and deletions, ensuring all sidecar files use the correct location.
2026-07-24 18:56:20 -04:00
AnimateDread
b78bb5d2ec feat: support pure-MQL5 CPU inference for OCL neuron layers without GPU backend 2026-07-24 11:52:19 -04:00
AnimateDread
303d46da8b fix: reset training state on failed network load to prevent untrained model deployment
A failed load of a corrupt/empty network could leave trainingComplete=true, causing the freshly built topology to be treated as already converged. This forced the model to never train, run inference on random weights, and delete all chart arrows. The fix ensures the state is reset to a genuine fresh start so BuildFreshTopology() actually gets trained.
2026-07-23 21:46:38 -04:00
AnimateDread
72797ab7ba feat: prevent saving empty network and add logit adjustment calibration
- Guard CNet::Save to refuse writing a 0-layer network (prevents overwriting ~18MB model with empty stub)
- In CNet::Load and LoadCheckpoint, treat 0-layer files as load failure (older stubs still on disk)
- Introduce LOGIT_PRIOR_STRENGTH_PRESETS enum (0–100%) to control logit adjustment tau
- Prepare member variables and AdjustedSignalFromSoftmax for prior-corrected posterior at inference
- Ensure raw argmax scoring for recall/convergence remains unchanged; correction only affects live signal
2026-07-23 19:36:34 -04:00
AnimateDread
4bc196d5d4 feat: add unified MA type support to indicator tuner
Add `MA_TYPE_PRESETS` enum covering advanced (ALMA, DEMA, ZLEMA, T3, Kalman) and standard (SMA, EMA, SMMA, LWMA) moving averages. Integrate `maType` and `bestMaType` into `CADIndicatorTuner` struct, update flatten/unflatten routines, and bump `AD_TUNE_PARAM_COUNT` to 33. This allows the auto-tuner to search over MA type alongside period, improving feature discovery.
2026-07-23 15:02:09 -04:00
AnimateDread
6c396862c9 fix: split AI model weight into 4 confidence tiers to fix quadratic derating
Replace single `m_pattern_0` weight with four confidence-tier weights (`m_pattern_0` through `m_pattern_3`) so that `UpdateSignalsWeights()` blends across multiple patterns like classic indicators. Previously, a single pattern caused the same win rate to be written to both the pattern weight and module weight, resulting in a quadratic derating (e.g., 70% win rate scored as 49 instead of 70). The new tiers bucket confidence into four equal bands between the minimum AI confidence and 1.0, with defaults 80/87/93/100. Added `ConfidenceTier()` and `PatternWeightForTier()` helpers, and changed default pattern count from 1 to 4.
2026-07-23 08:21:41 -04:00
AnimateDread
771c9b58ec feat: add weight scaling parameter to neuron initialization for improved training stability
- Added optional `weighScale` parameter (default -1.0) to `CNeuronBase::Init` and `CLayer::CreateElement`.
- Updated `CNeuronPool::Init` to use LeCun-uniform scaling (1/sqrt(window+1)) for its base initialization.
- Updated `CNet::CNet` to use He-scaled initialization (sqrt(2/neurons)) for dense layers.
- These changes enable more flexible and statistically sound weight initialization, matching the rationale used in OCL-based implementations, leading to better training stability and convergence.
2026-07-22 22:51:04 -04:00
AnimateDread
d667896457 refactor(AI): clean up comments and add conditional compilation guards
Remove verbose book references from input parameter comments in
Network.mqh for clarity. Add #ifndef guard around ENUM_OPTIMIZATION
to allow inclusion from multiple headers without redefinition.
Document the MQL5 Market DLL restriction in NeuronDirectML.mqh and
introduce WARRIOR_MARKET_BUILD macro to conditionally compile out
DirectML DLL imports for Market-compliant builds.
2026-07-22 17:17:23 -04:00
AnimateDread
0f0958856c feat: restructure input enums with intelligent SL/TP and AI exit
Replace old ATR_MULTIPLIER, THRESHOLDS_PRESET enums with new
STOP_LOSS_MODE, TAKE_PROFIT_MODE, AI_EXIT_MODE enums that support
ATR-based, intelligent confidence-scaled, and swing-anchored modes.
Also fix LSTM signal identity string.
2026-07-22 13:33:56 -04:00
AnimateDread
e2df5cfdc5 fix: improve OOS accuracy regression logging to include balanced accuracy and blended values 2026-07-21 14:23:13 -04:00
AnimateDread
3d5475ebcb refactor: defer arrow drawing to era-end sweep when NMS enabled
When `m_signalClusterWindow > 0`, training passes (1,2,3) now only record signal scores into `m_arrowSignalCache[]` and skip drawing arrows. The end-of-era `PruneDirectionalClusters()` sweep handles all rendering, preventing raw mid-era clusters from appearing and decoupling NMS from pass 2’s shuffled draw order.
2026-07-21 13:18:35 -04:00