Skip to content

Performance Tuning

This guide helps you optimize Glamour’s particle system for the best balance between visual quality and server performance.

Glamour’s performance impact comes from three main sources:

  1. Particle Rendering: Sending particle packets to clients
  2. Condition Checking: Evaluating persistent particle conditions
  3. State Management: Reading/writing particle state to database

Monitor performance with /persistentparticles stats:

=== Persistent Particle Stats ===
Loaded Configs: 12
Active Pokemon: 47
Renders This Tick: 28
Max Renders/Tick: 100
Max Renders/Config: 50
LOS Tests: 15/50
Cache Hits: 142
Cache Misses: 23

Key Metrics:

  • Renders This Tick: Current particle spawns this tick
  • Max Renders/Tick: Global limit (100 default)
  • LOS Tests: Line-of-sight checks performed
  • Cache Hits/Misses: Condition matching cache efficiency

intervalTicks has the biggest performance impact.

Performance Cost by Interval:

intervalTicksSpawns/SecondPerformance Cost
102.0Very High ❌
201.0High ⚠️
400.5Medium ✅
600.33Low ✅
1000.2Very Low ✅

Recommendations:

// Ambient particles (continuous)
{
"intervalTicks": 40 // Good balance for ambient
}
// Sendout particles (one-time)
{
"intervalTicks": 100 // Can be higher, plays once
}
// Persistent particles (always active)
{
"intervalTicks": 60 // Conservative for always-on effects
}

particleCount multiplies network traffic.

Impact Analysis:

// Low impact (recommended for common particles)
{
"particleCount": 1,
"intervalTicks": 40
}
// = 1 particle every 2 seconds
// Medium impact (good for rare particles)
{
"particleCount": 3,
"intervalTicks": 40
}
// = 3 particles every 2 seconds
// High impact (use sparingly)
{
"particleCount": 5,
"intervalTicks": 20
}
// = 5 particles per second

Guideline: particleCount * (20 / intervalTicks) should be < 2 for most particles.

maxDistance determines render range.

Distance vs Performance:

maxDistancePerformanceUse Case
16Excellent ✅Subtle effects
24Very Good ✅Common particles
32Good ✅Standard distance
48Moderate ⚠️Rare particles
64Poor ❌Epic only
96+Very Poor ❌Avoid

Recommendations:

// Common/Uncommon
{
"maxDistance": 24
}
// Rare/Epic
{
"maxDistance": 32
}
// Legendary (if must use long range)
{
"maxDistance": 48,
"onlyWhenVisible": true // REQUIRED at this distance
}

radius affects visual spread but not performance directly. However, larger radius with high particle count = more visual clutter.

Balanced Approach:

// Subtle effect
{
"radius": 0.5,
"particleCount": 1
}
// Standard effect
{
"radius": 1.0,
"particleCount": 3
}
// Large effect (reduce count!)
{
"radius": 2.0,
"particleCount": 2 // Fewer particles for larger area
}
Check TypeCPU CostAccuracyWhen to Use
NoneFreeN/AAlways-on particles
onlyWhenVisibleVery Low~80%Most particles
requiresLOSHigh100%Epic+ only

Cheap view-cone check - Use for most particles:

{
"onlyWhenVisible": true,
"requiresLOS": false
}

Benefits:

  • Minimal CPU cost
  • Culls ~80% of off-screen particles
  • No visual impact when looking at Pokemon

Recommendation: Enable for all particles with intervalTicks < 60.

Expensive raytrace check - Use sparingly:

{
"onlyWhenVisible": true,
"requiresLOS": true
}

When to Use:

  • Legendary/Mythical rarity only
  • intervalTicks >= 40
  • maxDistance >= 48
  • Visual effect justifies cost

Global LOS Budget: Default 50 tests/tick. When exceeded, falls back to view-cone check.

Tier 1: Common/Uncommon (No visibility check)

{
"intervalTicks": 60,
"particleCount": 1,
"maxDistance": 24,
"onlyWhenVisible": false,
"requiresLOS": false
}

Tier 2: Rare (View-cone check)

{
"intervalTicks": 40,
"particleCount": 3,
"maxDistance": 32,
"onlyWhenVisible": true,
"requiresLOS": false
}

Tier 3: Epic (View-cone check, longer range)

{
"intervalTicks": 40,
"particleCount": 4,
"maxDistance": 48,
"onlyWhenVisible": true,
"requiresLOS": false
}

Tier 4: Legendary/Mythical (Full LOS)

{
"intervalTicks": 50,
"particleCount": 5,
"maxDistance": 48,
"onlyWhenVisible": true,
"requiresLOS": true
}

Higher priority = checked first. Optimize by priority:

High Priority (Checked Often):

{
"priority": 200,
"conditions": {
"species": ["mewtwo"], // Very specific
"isShiny": true
}
}

Only checks Mewtwo, skips others quickly.

Low Priority (Checked Less):

{
"priority": 50,
"conditions": {
"isShiny": true // Checks all Pokemon
}
}

Checked last, so other configs short-circuit first.

Fast Conditions:

  • isShiny (single boolean check)
  • species (simple string match)
  • level.exact (single int comparison)

Medium Conditions:

  • types (array contains)
  • forms (array contains)
  • healthPercent (calculation required)

Slow Conditions:

  • biomes (world query)
  • weather (world query)
  • timeOfDay (world query)

Optimization: Combine fast conditions first:

// Good: Fast condition first
{
"conditions": {
"species": ["charizard"], // Fast, eliminates most
"biomes": ["nether"] // Slow, checked only for Charizard
}
}
// Poor: Slow condition affects all
{
"conditions": {
"biomes": ["nether"], // Checked for every Pokemon!
"species": ["charizard"]
}
}

Glamour caches condition matches. View cache performance:

Cache Hits: 142
Cache Misses: 23

Hit Rate: 142/(142+23) = 86% (good!)

Target: >80% cache hit rate

If Low:

  • Pokemon are changing conditions frequently
  • Consider less dynamic conditions (avoid weather/time if possible)
  • Increase cache TTL (requires code change)

Edit Glamour’s main config to set global limits:

{
"particleSystem": {
"maxRendersPerTick": 100,
"maxRendersPerConfig": 50,
"maxLOSTestsPerTick": 50
}
}

Conservative Settings (Small server, < 10 players):

{
"maxRendersPerTick": 50,
"maxRendersPerConfig": 25,
"maxLOSTestsPerTick": 25
}

Balanced Settings (Medium server, 10-30 players):

{
"maxRendersPerTick": 100,
"maxRendersPerConfig": 50,
"maxLOSTestsPerTick": 50
}

High Performance (Large server, 30+ players):

{
"maxRendersPerTick": 200,
"maxRendersPerConfig": 100,
"maxLOSTestsPerTick": 100
}

Pros:

  • Simple setup
  • No external dependencies
  • Good for < 20 players

Cons:

  • Single-threaded writes
  • Can lock under high load

Optimization:

{
"database": {
"type": "sqlite",
"path": "glamour_data.db",
"pragmas": {
"journal_mode": "WAL",
"synchronous": "NORMAL"
}
}
}

Pros:

  • Concurrent writes
  • Scales well
  • Good for 20+ players

Setup:

{
"database": {
"type": "mysql",
"host": "localhost",
"port": 3306,
"database": "glamour",
"username": "glamour_user",
"password": "secure_password",
"poolSize": 10
}
}

Optimization:

  • Use connection pooling (poolSize: 10)
  • Place MySQL on same machine as game server
  • Use SSD for MySQL data directory

Pros:

  • Horizontal scaling
  • Excellent for 50+ players
  • Document-based (natural fit for JSON state)

Setup:

{
"database": {
"type": "mongodb",
"connectionString": "mongodb://localhost:27017/glamour",
"poolSize": 20
}
}

Educate players on client performance:

Low-End PCs:

  • Video Settings → Particles: Minimal
  • Reduce render distance
  • Disable smooth lighting

Medium PCs:

  • Video Settings → Particles: Decreased

High-End PCs:

  • Video Settings → Particles: All
  • Full render distance

If players report FPS drops:

  1. Reduce Global Particle Count:

    • Lower particleCount in all configs by 1
    • Example: 3 → 2
  2. Increase Intervals:

    • Increase intervalTicks by 10-20
    • Example: 40 → 50
  3. Reduce Range:

    • Lower maxDistance by 8 blocks
    • Example: 32 → 24
  4. Disable High-Tier Effects:

    • Temporarily disable Legendary/Mythical particles
    • Re-enable after optimization

Every Hour:

/persistentparticles stats

Check if renders are hitting limits.

Daily: Review server TPS:

/tps

If TPS < 18, investigate particle impact.

Weekly: Analyze logs for warnings:

Terminal window
grep "Glamour.*performance" logs/latest.log

Red Flags:

  • Renders hitting max limit (100/100)
  • LOS tests hitting limit (50/50)
  • TPS drops when many Pokemon spawn
  • Players reporting FPS issues

Yellow Flags:

  • Renders consistently > 80
  • LOS tests > 40
  • Cache hit rate < 70%

If performance issues persist:

  1. Use Spark Profiler:

    /spark profiler start
    # Wait 60 seconds
    /spark profiler stop
  2. Check Glamour’s CPU Usage: Look for aster.amo.glamour in profile

  3. Identify Hotspots:

    • Particle rendering?
    • Condition checking?
    • Database writes?
{
"id": "laggy_effect",
"priority": 100,
"settings": {
"intervalTicks": 10, // Too frequent!
"particleCount": 8, // Too many!
"maxDistance": 96, // Too far!
"onlyWhenVisible": false, // No culling!
"requiresLOS": false
},
"conditions": {
"types": ["normal"] // Matches too many Pokemon!
}
}

Performance: 🔴 Very Poor

  • Spawns 16 particles/second per Pokemon
  • Long render distance
  • No visibility optimization
  • Matches most Pokemon
{
"id": "optimized_effect",
"priority": 100,
"settings": {
"intervalTicks": 50, // ✅ More reasonable
"particleCount": 3, // ✅ Reduced
"maxDistance": 32, // ✅ Standard distance
"onlyWhenVisible": true, // ✅ View-cone culling
"requiresLOS": false
},
"conditions": {
"species": ["snorlax", "blissey"] // ✅ More specific
}
}

Performance: 🟢 Good

  • Spawns 1.2 particles/second per Pokemon
  • Reasonable range
  • Visibility optimization
  • Specific targets

Impact Reduction: ~93% fewer particles spawned!

  • intervalTicks >= 40 for common particles
  • intervalTicks >= 60 for persistent particles
  • particleCount <= 3 for common particles
  • maxDistance <= 32 for most particles
  • onlyWhenVisible: true for particles with interval < 60
  • requiresLOS only on Epic+ rarity
  • Specific conditions (avoid broad matches)
  • Priority structure prevents unnecessary checks
  • Database connection optimized
  • Regular monitoring with /persistentparticles stats

For a 20-player server:

Conservative Budget:

  • 10 persistent particle configs
  • 50 total renders/tick
  • 25 LOS tests/tick

Balanced Budget:

  • 20 persistent particle configs
  • 100 total renders/tick
  • 50 LOS tests/tick

Aggressive Budget:

  • 30+ persistent particle configs
  • 200 total renders/tick
  • 100 LOS tests/tick

Scale based on server hardware and player count.