Skip to content

Perk implementation matrix (rewrite)

This page tracks where perks are wired in the Python rewrite under src/, and which perks have direct scenario tests.

Generated by uv run python scripts/gen_perk_matrix.py.

Notes: - Port refs excludes perk metadata (perks.py), quests, modes, and UI views. - Original hook is not filled yet; use the decompile + runtime evidence to promote it.

ID PerkId Name Original hook Port refs Tests
0 ANTIPERK AntiPerk Never offered: perk_can_offer explicitly rejects ANTIPERK. src/crimson/gameplay.py:perk_can_offer tests/test_antiperk_perk.py
1 BLOODY_MESS_QUICK_LEARNER Bloody Mess CreaturePool._start_death: if the killer has Bloody Mess, uses xp_base = int(reward_value * 1.3). GameWorld._queue_projectile_decals: when active, spawns extra random decals and blood splatter particles on projectile hits. src/crimson/creatures/runtime.py:CreaturePool._start_death
src/crimson/game_world.py:GameWorld._queue_projectile_decals
tests/test_bloody_mess_quick_learner_perk.py
tests/test_radioactive_perk.py
2 SHARPSHOOTER Sharpshooter player_fire_weapon: if Sharpshooter is active, multiplies shot_cooldown by 1.05 and does not increase player.spread_heat by weapon.spread_heat * 1.3 after firing. player_update: while active, forces player.spread_heat = 0.02. src/crimson/gameplay.py:perk_generate_choices
src/crimson/gameplay.py:player_fire_weapon
src/crimson/gameplay.py:player_update
tests/test_perk_selection.py
tests/test_sharpshooter_perk.py
3 FASTLOADER Fastloader player_start_reload (0x00413430): if Fastloader is active, multiplies weapon reload_time by 0.7. src/crimson/gameplay.py:player_start_reload tests/test_fastloader_perk.py
4 LEAN_MEAN_EXP_MACHINE Lean Mean Exp Machine perks_update_effects: every 0.25 seconds, each player with this perk gains +perk_count * 10 experience. src/crimson/gameplay.py:perks_update_effects tests/test_lean_mean_exp_machine.py
5 LONG_DISTANCE_RUNNER Long Distance Runner player_update: while moving, move_speed normally increases by frame_dt * 5.0 (clamped to 2.0). With Long Distance Runner active, move_speed continues to ramp up above 2.0 by frame_dt per frame, clamped to 2.8. src/crimson/gameplay.py:perk_generate_choices
src/crimson/gameplay.py:player_update
tests/test_long_distance_runner_perk.py
tests/test_perk_selection.py
6 PYROKINETIC Pyrokinetic perks_update_effects: picks creature_find_in_radius(&aim, 12.0, 0); while aiming at a creature, decrements collision_timer by dt and, when it wraps, sets it to 0.5 and spawns 5 particles (intensity 0.8, 0.6, 0.4, 0.3, 0.2), plus fx_queue_add_random at the creature position. src/crimson/gameplay.py:perks_update_effects tests/test_pyrokinetic_perk.py
7 INSTANT_WINNER Instant Winner perk_apply (0x004055e0): grants +2500 experience to the perk owner. src/crimson/gameplay.py:perk_apply tests/test_instant_winner_perk.py
tests/test_perk_selection.py
8 GRIM_DEAL Grim Deal perk_apply (0x004055e0): sets player.health = -1.0 and grants +int(experience * 0.18). src/crimson/gameplay.py:perk_apply tests/test_grim_deal_perk.py
9 ALTERNATE_WEAPON Alternate Weapon player_apply_move_with_spawn_avoidance (0x0041e290): scales movement delta by 0.8. player_update: pressing reload swaps the primary and alternate weapon runtime blocks, plays the new weapon's reload SFX, and adds +0.1 to shot_cooldown. src/crimson/gameplay.py:bonus_apply
src/crimson/gameplay.py:player_update
tests/test_alternate_weapon_perk.py
tests/test_game_mode_ids.py
10 PLAGUEBEARER Plaguebearer Sets player_plaguebearer_active (DAT_004908b9). In creature_update_all, infected creatures (collision_flag != 0) take 15 damage every 0.5 seconds via collision_timer; on an infection kill, increments plaguebearer_infection_count. While plaguebearer_infection_count < 60, plaguebearer_spread_infection spreads infection between creatures within 45 units when the target has <150 HP. While plaguebearer_infection_count < 50, the player infects nearby creatures (<30 units) with <150 HP. src/crimson/creatures/runtime.py:CreaturePool.update
src/crimson/gameplay.py:perk_apply
tests/test_plaguebearer_perk.py
11 EVIL_EYES Evil Eyes perks_update_effects sets evil_eyes_target_creature (DAT_00490bbc) to creature_find_in_radius(&aim, 12.0, 0) when the perk is active (else -1). creature_update_all checks this index and skips the target's AI update. src/crimson/creatures/runtime.py:CreaturePool.update
src/crimson/gameplay.py:perk_generate_choices
src/crimson/gameplay.py:perks_update_effects
tests/test_evil_eyes_perk.py
tests/test_perk_selection.py
12 AMMO_MANIAC Ammo Maniac perk_apply (0x004055e0): on pick, calls weapon_assign_player(player_index, weapon_id) for each player using their current weapon id. weapon_assign_player (0x00452d40): when Ammo Maniac is active, increases clip_size by max(1, int(clip_size * 0.25)) before refilling ammo. src/crimson/gameplay.py:perk_apply
src/crimson/gameplay.py:weapon_assign_player
tests/test_ammo_maniac_perk.py
13 RADIOACTIVE Radioactive creature_update_all: if a creature is within 100 units and Radioactive is active, decrement collision_timer by dt * 1.5; when it wraps, set it to 0.5 and deal (100 - dist) * 0.3 damage. Kills bypass creature_handle_death: experience = int(float(experience) + creature.reward_value); start death via hitbox_size -= dt. src/crimson/creatures/runtime.py:CreaturePool.update
src/crimson/gameplay.py:perk_generate_choices
src/crimson/render/world_renderer.py:WorldRenderer._draw_player_trooper_sprite
tests/test_perk_selection.py
tests/test_radioactive_perk.py
14 FASTSHOT Fastshot player_fire_weapon: while active, multiplies shot_cooldown by 0.88. src/crimson/gameplay.py:perk_generate_choices
src/crimson/gameplay.py:player_fire_weapon
tests/test_fastshot_perk.py
tests/test_perk_selection.py
15 FATAL_LOTTERY Fatal Lottery perk_apply (0x004055e0): rolls crt_rand() & 1. If the result is 0, grants +10000 experience; otherwise sets player.health = -1.0. src/crimson/gameplay.py:perk_apply tests/test_fatal_lottery_perk.py
16 RANDOM_WEAPON Random Weapon perk_apply (0x004055e0): picks a random available weapon (retries up to 100), skipping the pistol and the currently equipped weapon, then calls weapon_assign_player. src/crimson/gameplay.py:perk_apply tests/test_random_weapon_perk.py
17 MR_MELEE Mr. Melee creature_update_all (0x00426220): on a melee hit, if Mr. Melee is active, deals creature_apply_damage(creature, 25.0, damage_type=2, impulse=(0,0)) to the attacker. src/crimson/creatures/runtime.py:CreaturePool.update tests/test_mr_melee_perk.py
18 ANXIOUS_LOADER Anxious Loader player_update (0x004136b0): while reloading (reload_timer > 0), fire_pressed subtracts 0.05 from reload_timer. src/crimson/gameplay.py:player_update tests/test_anxious_loader_perk.py
19 FINAL_REVENGE Final Revenge player_take_damage (0x00425e50): on death (health < 0), if Final Revenge is active, spawns an explosion burst (scale 1.8), sets bonus_spawn_guard, and applies radius damage within 512 units: damage = (512 - dist) * 5.0 via creature_apply_damage(damage_type=3, impulse=(0,0)); plays sfx_explosion_large and sfx_shockwave. src/crimson/sim/world_state.py:WorldState.step tests/test_final_revenge_perk.py
20 TELEKINETIC Telekinetic bonus_telekinetic_update: aim at a bonus within 24 units for >650ms to pick it up remotely. src/crimson/gameplay.py:bonus_telekinetic_update tests/test_telekinetic_perk.py
21 PERK_EXPERT Perk Expert perk_choice_count: while active, offers 6 perk choices per selection (vs 5 baseline). src/crimson/gameplay.py:perk_choice_count tests/test_perk_expert_perk.py
tests/test_perk_master_perk.py
22 UNSTOPPABLE Unstoppable player_take_damage (0x00425e50): while active, suppresses the on-hit heading jitter + spread heat increase. src/crimson/player_damage.py:player_take_damage tests/test_highlander_perk.py
tests/test_unstoppable_perk.py
23 REGRESSION_BULLETS Regression Bullets player_update: while reloading with an empty clip, firing a shot costs experience int(experience - weapon.reload_time * factor) where factor=200 for most weapons and factor=4 when weapon_ammo_class == 1 (fire). src/crimson/gameplay.py:player_fire_weapon
src/crimson/gameplay.py:player_start_reload
tests/test_regression_bullets_perk.py
24 INFERNAL_CONTRACT Infernal Contract perk_apply: grants the perk owner +3 levels (and +3 pending perk picks), and sets all alive players to health = 0.1. src/crimson/gameplay.py:perk_apply tests/test_infernal_contract_perk.py
tests/test_perk_selection.py
25 POISON_BULLETS Poison Bullets projectile_update: on creature hit, if Poison Bullets is active and (crt_rand() & 7) == 1, sets creature.flags \|= 0x01 (self-damage tick). creature_update_all applies this via creature_apply_damage(creature, frame_dt * 60.0, damage_type=0, impulse=(0,0)). src/crimson/projectiles.py:ProjectilePool.update tests/test_poison_bullets_perk.py
26 DODGER Dodger player_take_damage (0x00425e50): if Dodger is active and Ninja is not, ⅕ chance to ignore the hit (crt_rand() % 5 == 0). src/crimson/player_damage.py:player_take_damage tests/test_player_damage.py
27 BONUS_MAGNET Bonus Magnet BonusPool.try_spawn_on_kill: if the base roll fails (crt_rand() % 9 != 1) but any player has Bonus Magnet, a second roll (crt_rand() % 10 == 2) can still spawn a bonus. src/crimson/gameplay.py:BonusPool.try_spawn_on_kill tests/test_bonus_magnet_perk.py
28 URANIUM_FILLED_BULLETS Uranium Filled Bullets creature_apply_damage (0x004207c0): when damage_type == 1 and Uranium Filled Bullets is active, doubles the applied damage (damage = damage + damage). src/crimson/creatures/damage.py:creature_apply_damage tests/test_uranium_filled_bullets_perk.py
29 DOCTOR Doctor creature_apply_damage (0x004207c0): when damage_type == 1 and Doctor is active, multiplies damage by 1.2. src/crimson/creatures/damage.py:creature_apply_damage tests/test_doctor_perk.py
30 MONSTER_VISION Monster Vision creature_render_all (0x00419680): when active, draws a yellow 90x90 quad behind each active creature using effect_select_texture(0x10), with alpha fading by clamp((hitbox_size + 10) * 0.1) during corpse despawn. creature_render_type (0x00418b60): disables the creature shadow pass while Monster Vision is active. src/crimson/render/world_renderer.py:WorldRenderer.draw tests/test_monster_vision_perk.py
31 HOT_TEMPERED Hot Tempered player_update: periodically spawns an 8-shot ring alternating projectile types 0x0b/0x09; the interval is randomized to (crt_rand() % 8) + 2 seconds. src/crimson/gameplay.py:player_update tests/test_game_world_audio.py
tests/test_player_update.py
+1 more
32 BONUS_ECONOMIST Bonus Economist bonus_apply: while active, scales bonus timer increments by 1.0 + 0.5 * perk_count. src/crimson/gameplay.py:bonus_apply tests/test_bonus_economist_perk.py
33 THICK_SKINNED Thick Skinned perk_apply: on pick, scales player.health *= 2/3 (clamped to >=1). player_take_damage (0x00425e50): scales incoming damage by 2/3. src/crimson/gameplay.py:perk_apply
src/crimson/player_damage.py:player_take_damage
tests/test_perk_selection.py
34 BARREL_GREASER Barrel Greaser creature_apply_damage (0x004207c0): when damage_type == 1 and Barrel Greaser is active, multiplies damage by 1.4. projectile_update (0x00420b90): when Barrel Greaser is active and projectile.owner_id < 0, doubles the per-frame movement step count (steps *= 2). src/crimson/creatures/damage.py:creature_apply_damage
src/crimson/projectiles.py:ProjectilePool.update
tests/test_barrel_greaser_perk.py
35 AMMUNITION_WITHIN Ammunition Within player_update (0x004136b0): while reloading with an empty clip, if Ammunition Within is active and experience > 0, firing a shot costs health via player_take_damage (cost = 1.0 normally, 0.15 when weapon_ammo_class == 1). Regression Bullets takes precedence when both are active. src/crimson/gameplay.py:player_fire_weapon
src/crimson/gameplay.py:player_start_reload
tests/test_ammunition_within_perk.py
36 VEINS_OF_POISON Veins of Poison creature_update_all: on a melee hit (when player.shield_timer <= 0), if Veins of Poison is active and Toxic Avenger is not, sets creature.flags \|= 0x01 (self-damage tick, frame_dt * 60). src/crimson/creatures/runtime.py:CreaturePool.update tests/test_veins_of_poison_perk.py
37 TOXIC_AVENGER Toxic Avenger creature_update_all: on a melee hit (when player.shield_timer <= 0), if Toxic Avenger is active sets creature.flags \|= 0x03, enabling the strong self-damage tick (frame_dt * 180). src/crimson/creatures/runtime.py:CreaturePool.update tests/test_toxic_avenger_perk.py
38 REGENERATION Regeneration perks_update_effects (0x00406b40): if Regeneration is active and (crt_rand() & 1) != 0, heals each alive player with 0 < health < 100 by +dt (clamped to 100). src/crimson/gameplay.py:perk_apply
src/crimson/gameplay.py:perks_update_effects
tests/test_death_clock_perk.py
tests/test_regeneration_perk.py
39 PYROMANIAC Pyromaniac creature_apply_damage (0x004207c0): when damage_type == 4 and Pyromaniac is active, multiplies damage by 1.5 and consumes one crt_rand(). src/crimson/creatures/damage.py:creature_apply_damage tests/test_pyromaniac_damage_perk.py
40 NINJA Ninja player_take_damage (0x00425e50): ⅓ chance to ignore the hit (crt_rand() % 3 == 0) (takes precedence over Dodger). src/crimson/player_damage.py:player_take_damage tests/test_player_damage.py
41 HIGHLANDER Highlander player_take_damage (0x00425e50): when active, does not subtract damage; instead crt_rand() % 10 == 0 sets health = 0.0 (instant death). src/crimson/player_damage.py:player_take_damage tests/test_highlander_perk.py
42 JINXED Jinxed Global timer data_4aaf1c in perks_update_effects: every ~2.0–3.9s, 1/10 chance to deal 5 self-damage and call fx_queue_add_random twice at the player position. If Freeze bonus is inactive, kills a random active creature (index = rand() % 0x17f, 10 retries) by setting health=-1 and decrementing hitbox_size -= dt*20, then awards experience = int(float(experience) + creature.reward_value) and plays sfx_trooper_inpain_01. src/crimson/gameplay.py:perks_update_effects tests/test_jinxed_perk.py
43 PERK_MASTER Perk Master perk_choice_count: while active, offers 7 perk choices per selection (vs 5 baseline). src/crimson/gameplay.py:perk_choice_count tests/test_perk_master_perk.py
44 REFLEX_BOOSTED Reflex Boosted Main loop (grim_update): when in gameplay (game_state_id == 9) and Reflex Boosted is active, scales frame_dt *= 0.9. src/crimson/sim/world_state.py:WorldState.step tests/test_reflex_boosted_perk.py
45 GREATER_REGENERATION Greater Regeneration No runtime hook found in this build: perk_id_greater_regeneration is only referenced in perk selection and perk_apply (Death Clock clears it). No reads appear in perks_update_effects / player_update. src/crimson/gameplay.py:perk_apply tests/test_death_clock_perk.py
46 BREATHING_ROOM Breathing Room perk_apply (0x004055e0): scales player.health down by ⅔ for each player, then for every active creature does hitbox_size -= frame_dt (starting death staging without XP/bonus logic) and clears bonus_spawn_guard. src/crimson/gameplay.py:perk_apply tests/test_breathing_room_perk.py
47 DEATH_CLOCK Death Clock player_take_damage (0x00425e50): if Death Clock is active, returns immediately (immune to damage). perk_apply (0x004055e0): clears Regeneration and Greater Regeneration perk counts and sets player.health = 100.0 when health > 0.0. src/crimson/gameplay.py:bonus_pick_random_type
src/crimson/gameplay.py:perk_apply
src/crimson/player_damage.py:player_take_damage
tests/test_death_clock_perk.py
48 MY_FAVOURITE_WEAPON My Favourite Weapon perk_apply (0x004055e0): on pick, increases clip_size by 2 for each player. weapon_assign_player (0x00452d40): also applies +2 to clip_size on every weapon assignment while the perk is active. src/crimson/gameplay.py:bonus_pick_random_type
src/crimson/gameplay.py:perk_apply
src/crimson/gameplay.py:weapon_assign_player
tests/test_my_favourite_weapon_perk.py
49 BANDAGE Bandage perk_apply (0x004055e0): multiplies player.health by (crt_rand() % 50 + 1) and clamps to 100.0, then calls effect_spawn_burst(player.pos, 8). src/crimson/gameplay.py:perk_apply tests/test_bandage_perk.py
50 ANGRY_RELOADER Angry Reloader Spawns a ring of projectile type 0x0b when the reload timer crosses the half threshold. src/crimson/gameplay.py:player_update tests/test_game_world_audio.py
tests/test_player_update.py
+1 more
51 ION_GUN_MASTER Ion Gun Master projectile_update (0x00420b90): sets ion_aoe_scale to 1.2 when active, scaling ion AoE radii (Ion Rifle: 88, Ion Minigun: 60, Ion Cannon: 128). creature_apply_damage (0x004207c0): when damage_type == 7 and Ion Gun Master exists (global perk count), multiplies damage by 1.2. src/crimson/creatures/damage.py:creature_apply_damage
src/crimson/projectiles.py:ProjectilePool.update
tests/test_ion_gun_master_perk.py
52 STATIONARY_RELOADER Stationary Reloader player_update (0x004136b0): while stationary, applies reload_scale = 3.0 when decrementing reload_timer. src/crimson/gameplay.py:player_update tests/test_player_update.py
tests/test_stationary_reloader_perk.py
53 MAN_BOMB Man Bomb Burst spawns projectile types 0x15/0x16. src/crimson/gameplay.py:player_update tests/test_game_world_audio.py
tests/test_player_update.py
+1 more
54 FIRE_CAUGH Fire Caugh Uses projectile type 0x2d (see Fire Bullets in the atlas notes). src/crimson/gameplay.py:player_update tests/test_player_update.py
55 LIVING_FORTRESS Living Fortress player_update (0x00412d70): while stationary and Living Fortress is active, increments player.living_fortress_timer += frame_dt (caps at 30.0; resets to 0.0 when moving). creature_apply_damage (0x004207c0): for damage_type == 1, multiplies damage by (living_fortress_timer * 0.05 + 1.0) for each alive player. src/crimson/creatures/damage.py:creature_apply_damage
src/crimson/gameplay.py:player_update
tests/test_living_fortress_perk.py
56 TOUGH_RELOADER Tough Reloader player_take_damage (0x00425e50): if reload_active is set, multiplies incoming damage by 0.5. src/crimson/player_damage.py:player_take_damage tests/test_tough_reloader_perk.py
57 LIFELINE_50_50 Lifeline 50-50 perk_apply (0x004055e0): iterates creature_pool in slot order, deactivating every other slot (bVar8 toggles each iteration) when it is active, has health <= 500.0, and (flags & 4) == 0; for each deactivated creature it calls effect_spawn_burst(creature.pos, 4). src/crimson/gameplay.py:perk_apply tests/test_lifeline_50_50_perk.py