Enemy scaling
Enemies get tougher as the run goes on. Verified: these values come from scaling in the game config and the scaling function in sim-BkfBhQfw.js.
Formula
These formulas describe Standard with late-game content enabled. Let a = min(seconds/60, 20). From 20:00, five-minute phases cycle through HP factors 1, 1.4, 2, 2.8 and damage factors 1, 1.6, 2.56, 4.1. Every full four-phase cycle multiplies HP by 4 and damage by 6.55. Within a phase, HP additionally multiplies by 1 + 0.05 × minutes into phase, damage by 1 + 0.04 × minutes into phase. Before 20:00 both post factors are 1. Easy's old overtime hold does not delay this new curve.[1][2][3]
- Pest HP ×=
(1 + 0.03·a + 0.009·a²) × post HP × difficulty pestHp - Enemy damage ×=
(1 + 0.0025·a²) × post damage × early × late × difficulty damage, where early rises linearly from 0.4 at 0 s to 1 at 900 s, and late =1 + 0.2 × max(0, a − 15) - Speed, count and spawn-rate scaling factors stay at 1. This removes their former overtime growth, not the ordinary spawn-wave schedule.[4]
These early and late values apply to Easy, Normal and Hard. Nightmare reaches early ×1 at 405 s, and uses late = 1 + 0.064 × max(0, a − 5).[5]
[6] Powers use the game's deterministic power function (x^y) imported from boot-CPW8VVmR.js.
Charging pests get a 0.6 s windup from 20:00 instead of 0.5 s; their dash itself is unchanged.[7]
Computed multipliers (Normal)
| Time | Pest HP × | Enemy damage × | Speed × | Count × |
|---|---|---|---|---|
| 0:00 | 0.65 | 0.22 | 1 | 1 |
| 1:00 | 0.68 | 0.24 | 1 | 1 |
| 5:00 | 0.89 | 0.35 | 1 | 1 |
| 10:00 | 1.43 | 0.55 | 1 | 1 |
| 15:00 | 2.26 | 0.86 | 1 | 1 |
| 20:00 | 3.38 | 2.2 | 1 | 1 |
| 21:00 | 3.55 | 2.29 | 1 | 1 |
| 22:00 | 3.72 | 2.38 | 1 | 1 |
| 23:00 | 3.89 | 2.46 | 1 | 1 |
| 25:00 | 4.73 | 3.52 | 1 | 1 |
Computed with the formula above, including Normal's HP and damage multipliers (see Modes).
Damage scaling by level
These values include each level's damage multiplier and early/late ramp, before curse and armor.
| Time | Easy × | Normal × | Hard × | Nightmare × |
|---|---|---|---|---|
| 0:00 | 0.16 | 0.22 | 0.24 | 0.38 |
| 5:00 | 0.255 | 0.351 | 0.383 | 0.852 |
| 6:45 | 0.299 | 0.41 | 0.448 | 1.177 |
| 10:00 | 0.4 | 0.55 | 0.6 | 1.568 |
| 15:00 | 0.625 | 0.859 | 0.938 | 2.434 |
| 20:00 | 1.6 | 2.2 | 2.4 | 3.724 |
Enemy bullets
| Setting | Value |
|---|---|
| Bullet count +/min | 0.04 |
| Max bullet count × | 2.5 |
| Fire rate +/min | 0.03 |
| Max fire rate × | 2.2 |
| Bullet speed +/min | 0.015 |
| Max bullet speed × | 1.6 |
| Bullet cap | 1024 |
| Bullet life (s) | 6 |
Elites
Elites follow a fixed curve (elites.curve). HP is the elite's HP at that minute; damage and speed are multipliers.
| Minute | Tier | HP | Damage × | Speed × |
|---|---|---|---|---|
| 1 | 2 | 300 | 1.6 | 0.95 |
| 3 | 2 | 390 | 1.6 | 0.95 |
| 5 | 3 | 750 | 1.6 | 0.95 |
| 8 | 3 | 1300 | 1.6 | 1.05 |
| 10 | 4 | 2400 | 1.6 | 1.1 |
| 15 | 5 | 10000 | 1.6 | 1.2 |
| 17 | 5 | 18000 | 1.6 | 1.25 |
| 20 | 5 | 32000 | 1.6 | 1.3 |
Verified: elite pests have pixel-art death animations with a gold rim that breaks away.[8]
Charging pest effects
Verified: charging pests kick up dust and bouncing grit when they launch and stop, with a dust trail during the charge. The renderer hides these effects with reduced motion or zero effects opacity. These are visual effects; charge timing, speed and damage are unchanged.[9][10]
Bosses
Boss HP is multiplied by ×1 from minute 0, ×3 from minute 16, ×4 from minute 18, ×6 from minute 20 (bosses.hpSteps). See the boss schedule for which boss arrives when.
Enemy level and armor
Verified: every enemy has a level used against Tibo's armor: 10 + floor(minutes × 1), +5 for elites and +10 for bosses.[11] See Stats.
Crowd limits
| Setting | Value |
|---|---|
| Base max enemies (Easy/Normal) | 500 |
| Base hard cap (Easy/Normal) | 550 |
| Hard max / hard cap | 650 / 700 |
| Nightmare max / hard cap | 800 / 850 |
| Max shooters | 30 |
| Opening (s) | 120 |
| Opening density | 0.6 |
| Early ceiling (Normal) | 35 alive at 90 s, 150 alive at 255 s |
| Max XP gems on the ground | 350 |
See late crowds for the 16:00–17:00 Hard/Nightmare ceiling ramp and measured old/new caps.
Late-run XP drops
Verified: in Standard with late-game content enabled, a pest's XP drop is multiplied by the HP phase step, without HP's within-phase 5%-per-minute growth. It stays ×1 through just before 25:00, becomes ×1.4 at 25:00, ×2 at 30:00 and ×2.8 at 35:00; the next cycle begins at ×4 at 40:00. The factor uses the time of death, not spawn time, and the biome XP multiplier still applies. Story and a disabled late-game flag do not apply it.[12][13][14]
Measured old/new original death and pickup code, seed wiki, a forced pest carrying 7 XP at (137, 41): old drops stayed 7; new drops were 7 at 24:59, 9.8 at 25:00 and 26:13, 14 at 30:00, 19.6 at 35:00 and 28 at 40:00 in the ×1 biome. Story and disabled-feature controls stayed 7. These are isolated drop probes, not evidence of natural level-up pace.
Busy-fight readability
Verified: boss ground warnings now use a separate raised floor layer, above ordinary pools, hazards and shadows. Busy-fight rendering also suppresses more small death puffs, widens damage-number merging, moves merged totals towards the contributing hits, and moves numbers away from Tibo. This changes presentation, not damage amounts.[15][16][17]
Isolated original-renderer probes checked quiet, half-pressure and full-pressure states, small/large deaths, number placement at Tibo's bounds and screen edges, and pressure decay/reset. For two merged hits of 30 and 70 at (10, 20) and (30, 70), the displayed total stayed 100; its position changed from (10, 20) to (24, 55) at full pressure. These checks do not measure frame rate, device temperature or battery use.
Damage numbers
Verified: ordinary hit numbers have a 1 s visible-age window; crits get 1.25 s. They fade in over 0.15 s, pop, rise with easing and then fade out. Merged-hit settling can delay that window by 0.3 s. This is display timing, not a change to damage.[18][19]
The base font size is 4% of the shorter viewport dimension, with a 10 CSS-pixel minimum and no former 30-pixel cap. The display budget stays between 5 and 12 totals, depending on viewport size; crowd pressure can reduce it further. Larger totals and crits take priority rather than showing every hit at once.[20][21][22][23] Reduced motion removes sideways drift and the late-game pop; it does not disable damage numbers.[24]
Original old/new renderer probes checked lifetime boundaries, fading, viewport sizing, merging, reduced motion and priority ties. At a 390-pixel shorter viewport dimension the base size changed from 24.375 to 15.6 CSS pixels; two merged hits of 30 and 70 still display 100. These are isolated rendering checks, not frame-rate or physical-device measurements.
Verified: new ordinary pest spawns are limited to 2 shooters before 3:00, 4 from 3:00 to before 6:00, then the normal cap of 30. This limits shooter selection when spawning pests, not the number of bullets, bosses or elites already present. The early caps use the difficulty's shooterCap multiplier, currently 1 on every level; Story uses these defaults too.[25][26][27]
Sources
Game version 75: main-CSuyb4iv.js / boot-CPW8VVmR.js / sim-BkfBhQfw.js from https://tibo.dog/, researched 2026-11-05. See Versions & updates.
- Game config and formulas in
sim-BkfBhQfw.js
Code references
- sim-BkfBhQfw.js
gr=[{hp:1,damage:1},{hp:1.4,damage:1.6},{hp:2,damage:2.56},{hp:2.8,damage:4.1}],_r=1200 - sim-BkfBhQfw.js
Sr(e)*(1+.05*xr(e,t)) - sim-BkfBhQfw.js
yr(t).damage*Re(6.55,br(e))*(1+.04*xr(e,t)) - sim-BkfBhQfw.js
e.speed=Math.min(r.maxSpeedMult,1),e.count=1,e.rate=1 - sim-BkfBhQfw.js
lateDamageFromMin:t.scaling.lateDamageFromMin*n.lateRampTime,lateDamagePerMin:t.scaling.lateDamagePerMin*n.lateRamp - sim-BkfBhQfw.js
e.hp=(1+r.hpLinearPerMin*a+r.hpQuadraticPerMin*a*a)*s*t.level.pestHp - sim-BkfBhQfw.js
return!Cr(e)||e.runClock<_r?t:Math.max(t,.6) - changelog-panel-DtW-mCDY.js
Elite pests fall apart in crisp pixels when they die, and their gold rim breaks off as they go. - scene-B2rwbRKj.js
var eZ=Uint8Array.from(w.enemies,e=>+(e.behavior.kind===`charger`)) - scene-B2rwbRKj.js
e.still||n.opacity<=0 - sim-BkfBhQfw.js
function Xa(e,t){let n=e.armor;return n.firstLevel+Math.floor(t/60*n.levelsPerMinute)} - sim-BkfBhQfw.js
n.xp[t]=n.xp[t]*Sr(e.runClock) - sim-BkfBhQfw.js
return t===0?1:yr(t).hp*Re(4,br(e)) - sim-BkfBhQfw.js
n.xp[t]*c.xpMult - scene-B2rwbRKj.js
renderOrder:2.9,depthTest:!0,hairline:!1,lift:.8,arcCull:!0 - scene-B2rwbRKj.js
e.x[t]=e.x[t]+(n.x[r]-e.x[t])*s,e.y[t]=e.y[t]+(n.y[r]-e.y[t])*s - scene-B2rwbRKj.js
Math.round(48-40*SY()) - boot-CPW8VVmR.js
hitSec:1,critSec:1.25,fadeInSec:.15,fadeOutSec:.35 - scene-B2rwbRKj.js
return n-e.first[t]-(e.settled?.3:0) - boot-CPW8VVmR.js
sizeShare:.04,sizeMinCssPx:10 - boot-CPW8VVmR.js
budgetMin:5,budgetMax:12,budgetCssPx:40 - scene-B2rwbRKj.js
return Math.max(n,Math.min(e.cssW,e.cssH)*t)*e.dpr - scene-B2rwbRKj.js
return e.amount[t]*(e.kind[t]===1?4:1) - scene-B2rwbRKj.js
return e.still?r:e.overkill?r*function(e) - sim-BkfBhQfw.js
shooterSteps:[{untilSec:180,max:2},{untilSec:360,max:4}] - sim-BkfBhQfw.js
if(e.runClock<i.untilSec)return Math.min(t.maxShooters,i.max) - sim-BkfBhQfw.js
if(Hl[i]===0||Kl(e)<Gl(e))return i