Where's my Brain (WMB)

Where's my Brain (WMB)

Mod

Smart mob AI optimization mod

Client or server ManagementOptimizationUtility

351 downloads
6 followers
Follow Save

WHERE'S MY BRAIN (WMB)

A lightweight, loader-agnostic Minecraft 1.20.1 optimization mod focused on smarter mob AI scheduling, distance-aware throttling, and adaptive auto-tuning. Keep gameplay feel near players while cutting wasted CPU on far-away or idle mobs. Works on both Forge and Fabric via Architectury.

Join our Discord

Features

  • Async Entity Tracking

    • Off-thread per-player entity visibility/tracking decisions to reduce main thread work.
    • Global or per-dimension thread pools with adaptive backpressure (queue-aware scheduling).
    • Respects asyncTracker.updateInterval, cacheDuration, and maxTrackingDistance.
    • Integrates with AutoTuner to adjust scheduling under load.
    • Timeouts and graceful fallbacks keep the main thread safe.
  • Regional/Chunk-based TPS Manager (R‑TPS)

    • Divides each dimension into square regions (default: regionalTPS.regionSize = 4 chunks per side) and continuously measures load per region.
    • Tracks per-region metrics: average AI time (ms), average memory (MB), player presence, redstone updates, BE/scheduled tick counts, and activity recency.
    • Applies scalable tick multipliers per region using named levels (e.g., normal=1, warning=2, critical=4).
    • Hysteresis (regionalTPS.hysteresisPct, default 0.15) prevents flip‑flops when load hovers near thresholds.
    • Static overrides let you pin specific regions to a level.
    • Safety: global TPS is computed over a sliding window and emergency triggers are gated by warm‑up and cooldown to avoid false alarms.
    • Gating integrates with chunk random/scheduled ticks to cut work in stressed regions while remaining invisible near players.
  • Distance-based AI Bucketing (DAB)

    • Dynamically throttles AI ticks based on each mob’s nearest-player distance.
    • Per-dimension and per-entity overrides.
    • Hysteresis deadband to prevent thrashing near thresholds.
    • Sensible exemptions (leashed, named, owned/tamed, persistent, and configurable boss exemptions).
  • Proximity Snapshot Service

    • Centralizes nearest-player computations to a periodic snapshot, reducing redundant work.
    • Mob-side cache reduced to 2 ticks for responsive DAB updates with minimal overhead.
  • Pathfinding Optimizations

    • Minimum per-mob path recalculation interval and grouping window.
    • Optional experimental path sharing.
    • Caching with TTL to avoid repeated path solves.
  • Auto-Tuning (PID)

    • Targets a desired average tick time (ms) and gently adjusts pathfinding recalc interval.
    • PID controller (kp/ki/kd) with integral clamping and step-limits for smooth convergence.
    • Transparently falls back to a simple step controller if PID gains are zero.
  • Optional AI Culling (Conservative)

    • Skip AI processing for far, idle, and unengaged mobs behind multiple safety gates.
  • Optional Visibility Culling (Conservative)

    • Skip AI for mobs far outside any player’s chunk range, with the same safety gates.
  • Metrics and Command

    • Lightweight internal counters for /wmb stats.
    • See DAB thresholds, proximity settings, pathfinding parameters, and tuner state.

How It Works

  • DAB continuously places each mob into a distance bucket (near/mid/far/distant) relative to the nearest player and assigns a tick interval multiplier per bucket.
  • Async Entity Tracking computes per-player tracked entity sets off-thread and applies updates post-tick; intervals adapt via tuner and backpressure.
  • The proximity service snapshots player positions every N ticks (configurable) to amortize distance queries.
  • Pathfinding and AI recalculations are rate-limited with per-mob minimum intervals and optional grouping.
  • The Auto Tuner reads average tick time over a window and adjusts pathfinding.minRecalcInterval to steer the server towards tuning.targetTickMs.
  • Culling and visibility features add optional, conservative gates to skip AI when it’s safe to do so.

Diagrams

Async Entity Tracking: Flow

[Server Tick]
  ├─ PreTick
  │   ├─ shouldSchedulePlayer?(interval + tuner + backpressure)
  │   ├─ Snapshot(Player)
  │   ├─ CollectEntitySnapshots (AABB around player)
  │   └─ Submit Task → Executor
  │        └─ [Off-thread]
  │            ├─ Filter by distance ≤ maxTrackingDistance (2D)
  │            ├─ Diff with current tracked set → {toAdd, toRemove}
  │            └─ Enqueue TrackingResult
  └─ PostTick
      ├─ Drain completed results (batched)
      ├─ Apply adds/removes to tracking sets
      ├─ Update metrics (durations, queue size, counts)
      └─ Periodic maintenance (cleanup/disconnects)

Threading Model: Global vs Per-Dimension Pools

Option A: Global Pool

  [AsyncTracker Executor (N threads)]
      ↑ tasks from all dimensions

Option B: Per-Dimension Pools

  [Overworld Executor (M)]   [Nether Executor (M)]   [End Executor (M)]
       ↑ OW tasks                  ↑ Nether tasks          ↑ End tasks

Backpressure: bounded queues + CallerRunsPolicy; scheduling slows when queues grow.

Auto‑Tuner: Control Loop

tickNanos → EMA → window(avg)
            │
            ▼
      Controller (PID or Simple)
            │
     ┌───────┴─────────────────────────────┐
     │                                     │
pathfinding.minRecalcInterval += step   trackerIntervalAddend = clamp(-step)
     │                                     │
cooldown ticks prevent rapid toggling      additive scheduling shift for tracker

DAB Bucketing and Hysteresis

distance (blocks) → 0 ── d0 ── d1 ── d2 ──▶
bucket             NEAR    MID    FAR  DISTANT
multiplier         x1      x2     x4   x8   (defaults; configurable)

hysteresisBlocks (±h) creates a deadband around d0/d1/d2 to reduce bucket flipping.

Regional TPS: Decision Pipeline

[Server Tick]
  ├─ PreTick
  │   ├─ Global TPS sample ← steady after 5+ samples; clamped [0..20]; warm‑up (≥200 ticks) before emergency checks
  │   └─ Update player→region mapping
  ├─ PostTick (per region)
  │   ├─ Accumulate AI nanos, BE & scheduled ticks, redstone updates, memory, players
  │   ├─ Windowed averages (regionalTPS.windowTicks)
  │   ├─ Decide target level using thresholds + hysteresis
  │   ├─ Smoothly adjust region multiplier toward target (±1 per tick)
  │   └─ Apply gating hooks (e.g., chunk random/scheduled ticks)
  └─ Global
      ├─ Emergency trigger if: warm‑up passed ∧ samples≥20 ∧ TPS<15 with cooldown
      └─ Periodic logging & eviction of stale regions

Heatmap: Client Flow & Interactions

/wmb heatmap [radius] → server builds window around player
                        ↓
                 send Heatmap to client
                        ↓
                HeatmapScreen (press H to reopen last)
                        ↓
   Interactions:
   - Mouse wheel: zoom
   - Right‑drag: pan
   - Left‑click on header: toggle Mode (AI ms, Mem MB, Players, Priority)
   - Click LOD: toggle merged vs per‑region view
   - Hover: tooltips (merged + region details)
   - Sidebar: detailed panel (emergency, target multiplier, good/bad streaks, etc.)
   - Global TPS shown in header

Installation

  • Minecraft: 1.20.1
  • Loaders: Fabric or Forge (Architectury)
  • Drop the mod jar into your mods/ folder.
  • Start the game once to generate the wmb.toml config with inline comments.

Config location by loader:

  • Fabric: .minecraft/config/wmb.toml
  • Forge: <instance>/config/wmb.toml

Commands

  • /wmb regions here

    • Prints the current region’s coordinates, level, and summary.
  • /wmb regions top [n]

    • Lists the top N heaviest regions by average AI ms.
  • /wmb heatmap [radius]

    • Generates a heatmap around your position (default radius 8 regions) and sends it to your client.
  • /wmb reload

    • Reloads wmb.toml from disk and applies runtime values.
  • /wmb metrics on|off

    • Toggle internal metrics collection used by stats commands.
  • /wmb tuning on|off

    • Enable/disable the AutoTuner at runtime.
  • /wmb preset <vanilla|balanced|aggressive>

    • Apply a runtime preset of conservative to stronger optimizations. Use /wmb reload to revert to file values.
  • /wmb stats

    • Shows DAB thresholds & multipliers, proximity snapshot interval, async spawn/tracker thread settings, pathfinding and tuner parameters, and metrics (if enabled).
  • /wmb trackerstats

    • Prints tracker status and queue/task metrics.
  • /wmb debug entity

    • Inspect the nearest mob’s effective DAB interval near the player.
  • /wmb debug chunk

    • Summarize average DAB interval for mobs in your current chunk.

Keybinds

  • Heatmap: H (default)

    • Opens the last received heatmap on the client. If you haven’t requested one yet, use /wmb heatmap first.
  • /wmb profile start|stop

    • Lightweight profiler for AI step timing; prints a summary when stopped.
  • /wmb stresstest start <entity> [count] [radius] [duration] [pathEvery] [waypoints]

    • Spawns a controlled scenario for stress testing. Use status, report, and stop to manage.

Configuration Reference

All options are documented inline in the generated wmb.toml. Below is a structured overview.

Distance-based AI Bucketing (DAB)

  • dab.enabled (bool)

    • Turn distance-aware AI throttling on/off globally.
  • dab.d0, dab.d1, dab.d2 (double, blocks)

    • Distance thresholds separating near/mid/far/distant buckets.
  • dab.multipliers.near|mid|far|distant (int)

    • AI tick interval per bucket. 1 means every tick (no throttle), higher means less frequent AI updates.
  • dab.hysteresisBlocks (double, blocks)

    • Deadband around the thresholds to avoid rapid bucket flipping near the boundary.
  • dab.perEntity["namespace:id"]

    • Per-entity overrides and exemptions.
    • Keys: exempt (bool), highPriority (bool), near, mid, far, distant (ints).
    • If exempt or highPriority is true, DAB is disabled for that entity type.
  • dab.perDimension["namespace:dimension"]

    • Per-dimension overrides for d0/d1/d2 and nested multipliers.near|mid|far|distant.

Notes:

  • Some high-importance entities (e.g., bosses) can be exempted from DAB by default in code or via per-entity overrides.
  • Leashed, named, owned/tamed, and persistent entities are not throttled.

Proximity

  • proximity.updateIntervalTicks (int)
    • How often player positions are snapshotted. Larger = less CPU, slower DAB responsiveness. Typical: 3–10.

Pathfinding

  • pathfinding.enabled (bool)

    • Enable optimizations around path recalculations and caching.
  • pathfinding.minRecalcInterval (int, ticks)

    • Minimum ticks between recalculations per mob. This is the actuator adjusted by the tuner.
  • pathfinding.groupWindowTicks (int, ticks)

    • Group/stagger recalcs within a small window to spread CPU spikes.
  • pathfinding.cacheTtlTicks (int, ticks)

    • Keep solved paths for this long before expiring.
  • pathfinding.experimentalShareEnabled (bool)

    • Experimental: allow similar mobs to share path results.
  • pathfinding.shareTtlTicks (int, ticks)

    • TTL for shared path entries if sharing is enabled.

Auto Tuning

  • tuning.enabled (bool)

    • Enable closed-loop tuning of pathfinding.minRecalcInterval towards a target tick duration.
  • tuning.targetTickMs (double, ms)

    • Desired average tick duration (50.0 ms = 20 TPS).
  • tuning.windowTicks (int)

    • Number of ticks per averaging window.
  • tuning.cooldownTicks (int)

    • Minimum ticks between successive adjustments.
  • tuning.minRecalcMin / tuning.minRecalcMax (int)

    • Clamp the actuator (min recalc interval) within this range.
  • tuning.kp, tuning.ki, tuning.kd (double)

    • PID gains. If all are zero, the tuner falls back to a simple, step-based controller.
  • tuning.maxStepPerWindow (int)

    • Maximum absolute change to the actuator per window.
  • tuning.integralMaxAbs (double)

    • Absolute clamp for integral term to avoid windup.

Checks

  • checks.suffocationInterval (int, ticks)
    • Tuning of vanilla suffocation checks. Higher = less CPU, slightly slower detection.

Async Spawn

  • asyncSpawn.enabled (bool)

    • Enable off-thread spawn preparation work.
  • asyncSpawn.threadPoolSize (int)

    • Worker threads to use for async spawn tasks. Recommended: 1–4.

Async Entity Tracker

  • asyncTracker.enabled (bool)

    • Enable asynchronous per-player entity tracking.
  • asyncTracker.threadPoolSize (int)

    • Worker threads for the global tracker executor when perDimensionPools = false.
  • asyncTracker.updateInterval (int, ticks)

    • Minimum ticks between recalculations per player before cache reuse.
  • asyncTracker.cacheDuration (int, ticks)

    • How long to reuse the last visibility result before forcing a recalculation.
  • asyncTracker.perDimensionPools (bool)

    • If true, use one executor per dimension to isolate load.
  • asyncTracker.threadPoolSizePerDim (int)

    • Core threads for each per-dimension executor when perDimensionPools = true.
  • asyncTracker.maxTrackingDistance (double, blocks)

    • Maximum 2D horizontal distance from the player for an entity to be considered tracked.

Culling (Optional)

  • culling.enabled (bool)

    • Conservative AI skipping for far, idle mobs with multiple safety gates.
  • culling.minDistance (double, blocks)

    • Only consider mobs at least this far from the nearest player.
  • culling.requireNoTarget / requireNoPath / requireLowMotion (bool)

    • Safety gates to ensure we skip AI only for idle, unengaged mobs.
  • culling.lowMotionSpeed (double, blocks/tick)

    • Threshold used by requireLowMotion.
  • culling.allowEveryNTicks (int, ticks)

    • Even when eligible, allow full AI every N ticks to avoid starvation.
  • culling.exemptNamed|Leashed|Owned|Persistent (bool)

    • Protective exemptions to never skip important mobs.

Visibility (Optional)

  • visibility.enabled (bool)

    • Apply similar skipping rules to mobs far outside any player’s chunk range.
  • visibility.playerChunkRange (int, chunks)

    • Chebyshev chunk distance around players considered "active". Outside this range, mobs may be skipped subject to gates.
  • visibility.requireNoTarget / requireNoPath / requireLowMotion (bool)

    • Same safety gates as culling.
  • visibility.lowMotionSpeed (double, blocks/tick)

    • Threshold used by requireLowMotion.
  • visibility.allowEveryNTicks (int, ticks)

    • Even when eligible, allow full AI every N ticks to avoid starvation.
  • visibility.exemptNamed|Leashed|Owned|Persistent (bool)

    • Protective exemptions to never skip important mobs.

Regional TPS

  • regionalTPS.enabled (bool)

    • Master switch for the chunk‑based TPS system.
  • regionalTPS.regionSize (int, chunks)

    • Side length of a square region in chunks. Default: 4.
  • regionalTPS.windowTicks (int, ticks)

    • Averaging window for per‑region metrics and decisions.
  • regionalTPS.thresholds.maxTickMs (double, ms)

    • Soft ceiling for average AI time in a region before increasing level.
  • regionalTPS.thresholds.maxEntities (int)

    • Heuristic cap for entity counts within the region window.
  • regionalTPS.thresholds.maxBlockEntities (int)

    • Heuristic cap for block entities within the region window.
  • regionalTPS.hysteresisPct (double, 0.0–0.45)

    • Percentage deadband applied around thresholds to prevent rapid level flip‑flopping. Default: 0.15 (15%).
  • regionalTPS.scaling.levels.<name> (int)

    • Map of named levels to tick multipliers, e.g., normal=1, warning=2, critical=4.
  • regionalTPS.staticRegions["dim:rx,rz"] = "levelName"

    • Pin specific regions (by dimension and region coords) to a chosen level.

Config Versioning and Auto‑Merge

  • The config file includes configVersion. As of v1.2‑SNAPSHOT, the schema version is 5.
  • On load, WMB auto‑merges any missing keys with safe defaults and bumps configVersion forward.
  • Existing user values are preserved. Keys are not removed automatically.
  • The generated wmb.toml includes inline comments for every option.

Tuning Guide

  • Start with tuning.enabled = false. Observe baseline with /wmb stats.
  • Set a realistic tuning.targetTickMs for your hardware and modpack. 50.0 ms is 20 TPS; aim slightly below your typical average load.
  • Begin with a simple controller: kp > 0, ki = 0, kd = 0. Suggested: kp = 0.25.
  • If you see steady-state error, gently introduce ki (e.g., 0.01–0.05).
  • If you overshoot or oscillate, add kd (e.g., 0.05–0.2).
  • Keep maxStepPerWindow small (1–2) for smoothness. Clamp integral via integralMaxAbs.
  • Always bound the actuator with minRecalcMin/minRecalcMax.
  • Tracker integration: the tuner also adjusts tracker scheduling additively; leave asyncTracker.updateInterval moderate and let the tuner smooth load.

Compatibility

  • Designed to be conservative and safe around gameplay-critical AI.
  • Bosses, named, leashed, owned/tamed, and persistent mobs are protected by default.
  • Should be broadly compatible; experimental features (like path sharing) are opt-in.

FAQ

  • Does DAB change combat behavior near the player?

    • No. The near bucket usually uses a multiplier of 1, keeping AI behavior responsive up close.
  • Does async tracking cause entity pop‑in or desync?

    • No. Tracking sets are computed off‑thread but applied safely on the main thread each tick. Timeouts fall back gracefully.
  • What if my server has multiple heavy dimensions?

    • Enable asyncTracker.perDimensionPools to isolate tracker load by dimension and size threadPoolSizePerDim accordingly.
  • Will auto-tuning fight my manual settings?

    • The tuner only adjusts pathfinding.minRecalcInterval. Everything else remains exactly as configured.
  • What if I don’t want any culling?

    • Both culling systems are disabled by default. They’re strictly opt-in and conservative.
  • Is this a magic TPS booster?

    • No. It’s a set of intelligent trade-offs to save CPU in situations where it doesn’t affect gameplay feel.

Changelog

1.2 (2025-09-04)

  • New — Regional/Chunk-based TPS Manager (R‑TPS)

    • Per‑region load tracking (avg AI ms, memory MB, players, redstone updates, BE/scheduled ticks, activity).
    • Named scaling levels (e.g., normal=1, warning=2, critical=4) applied as tick multipliers per region.
    • Hysteresis via regionalTPS.hysteresisPct (default 0.15) to stabilize level transitions.
    • Static region overrides to pin hotspots to specific levels.
    • Integrates with chunk random/scheduled tick gating; invisible near players.
  • New — Heatmap UI for Regional TPS

    • Request with /wmb heatmap [radius]; reopen last heatmap with the H key.
    • Mouse wheel zoom; right‑click drag to pan; live window requests while panning/zooming.
    • Modes: AI ms, Mem MB, Players, Priority; LOD toggle (merged vs per‑region tiles).
    • Tooltips and a detailed side panel (emergency state, target multiplier, consecutive good/bad ticks, histories).
    • Global TPS display in the header.
  • Improvements — Global TPS measurement and safety

    • Lazy‑init tick timing; compute TPS only after 5+ samples; clamp to [0..20].
    • Gate emergency triggers until warm‑up (≥200 ticks) and require ≥20 samples; add cooldown to avoid log spam.
  • Improvements — Regional decisions and sampling

    • Applied hysteresis to regional scaling decisions; introduced levelIndex to track transitions.
    • Normalized Y sampling for region gating to level.getMinBuildHeight() + 1.
  • Networking & Client

    • Updated heatmap/detail serialization to include memory, player counts, priority, global TPS, target multiplier, and histories.
    • Client can refresh the existing screen (setHeatmap) without reopening; remembers last heatmap for the H key.
  • Fixes

    • Heatmap tooltip compile error fixed by initializing local variables.
    • Eliminated false global TPS emergencies at startup or sporadic spikes; reduced warning spam.
  • Config

    • Config schema bumped to 5; added regionalTPS.* keys including hysteresisPct.
    • Defaults and inline comments updated in wmb.toml.
  • Migration Notes

    • Existing configs auto‑merge new keys and bump configVersion safely. Review regionalTPS.thresholds and regionalTPS.scaling.levels for your pack.

License

See LICENSE.txt in the repository.


Project members

Bobcat

Member

Details

Licensed ARR
Published 2 months ago
Updated 2 months ago