Original bugs (rewrite)¶
The classic crimsonland.exe has a few behaviors that look like genuine bugs
once you read the decompile and trace their gameplay impact.
In the rewrite, we fix these by default. For parity work and future
differential testing, you can re-enable them with --preserve-bugs.
1) Bonus drop suppression: amount == current weapon id¶
Native behavior:
- In
bonus_try_spawn_on_kill(0x0041f8d0), after spawning a bonus, the exe clears the spawned entry if either: - there’s already another bonus of the same
bonus_idon the ground (duplicate suppression), or bonus.amount == player1.weapon_idregardless of bonus type.
Why it’s likely a bug:
- For non-weapon bonuses,
amountis usually the bonus metadata “default amount”, which lives in a different integer domain than weapon ids. - This creates accidental “hard bans” where certain bonuses never drop while holding specific weapons, and it can also reduce the overall drop rate (the drop is canceled, not rerolled).
Examples of the accidental hard bans (native metadata):
Reflex Boost(amount=3) while holdingShotgun(weapon_id=3)Fire Bullets(amount=4) while holdingSawed-off Shotgun(weapon_id=4)Freeze(amount=5) while holdingSubmachine Gun(weapon_id=5)Shield(amount=7) while holdingMean Minigun(weapon_id=7)Speed(amount=8) while holdingFlamethrower(weapon_id=8)Weapon Power Up/MediKit(amount=10) while holdingMulti-Plasma(weapon_id=10)
Rewrite behavior:
- Default: only suppress
Weapondrops that match the current weapon id. - With
--preserve-bugs: re-enable the exe’samount == weapon_idsuppression rule for all bonus types.
2) Greater Regeneration has no runtime effect¶
Native behavior:
perk_id_greater_regenerationis defined and unlockable, but no gameplay tick logic reads it.perks_update_effectsonly checksperk_id_regeneration.perk_applyonly touches Greater Regeneration indirectly via Death Clock clearing both regen perk counts.
Why it’s likely a bug:
- The in-game description says Greater Regeneration should replenish health “faster than ever.”
- It has a prerequisite (
Regeneration), so the intended design is clearly an upgrade path, but the effect implementation is missing.
Rewrite behavior:
- Default: Greater Regeneration upgrades Regeneration heal ticks from
+dtto+2*dt(same RNG gate/timing as base Regeneration). - With
--preserve-bugs: keep original behavior where Greater Regeneration is a no-op.
3) Bandage applies a health multiplier instead of a heal¶
Native behavior:
perk_applycomputesroll = (crt_rand() % 50) + 1.- It multiplies each alive player's health by
roll, then clamps to100.
Why it’s likely a bug:
- The perk text says it “restores up to 50% health.”
- A ×1..×50 multiplier is wildly different from a bounded heal and can jump from low health to full almost every time.
Rewrite behavior:
- Default: heal each alive player by
+1..+50HP (1-50% of a 100-HP bar), then clamp to100. - With
--preserve-bugs: keep the original multiplier behavior.
4) Player-facing text typos are preserved in native data¶
Native behavior:
- User-facing strings include spelling/grammar mistakes in both:
- gameplay data tables (perk/weapon/bonus labels/descriptions), and
- screen/UI copy.
- Source evidence:
analysis/ghidra/raw/crimsonland.exe_strings.txt.
Why it’s likely a bug:
- These are straightforward spelling/wording mistakes in user-facing text, not gameplay semantics.
Rewrite behavior:
- Default: display corrected text in the rewrite.
- With
--preserve-bugs: keep the original misspelled strings for parity captures/testing.
Full gated text-fix list:
| Area | Native text (--preserve-bugs) |
Default rewrite text |
|---|---|---|
| Perk name | Fire Caugh |
Fire Cough |
| Weapon name | Plague Sphreader Gun |
Plague Spreader Gun |
| Weapon name | Lighting Rifle |
Lightning Rifle |
| Weapon name | Fire bullets |
Fire Bullets |
Perk description (Anxious Loader) |
waiting your gun to be reloaded |
waiting for your gun to be reloaded |
Perk description (Dodger) |
attacks you you have a chance |
attacks you, you have a chance |
Perk description (Ninja) |
have really hard time |
have a really hard time |
Perk description (Living Fortress) |
It comes a time ... Being living fortress ... You do the more damage ... |
There comes a time ... Being a living fortress ... You do more damage ... |
Bonus description (Weapon Power Up) |
Your firerate and load time increase for a short period. |
Your fire rate and load time increase for a short period. |
Bonus description (Fire Bullets) |
For few seconds -- make them count. |
For a few seconds -- make them count. |
| End note line | You've completed all the levels but the battle |
You've completed all the levels, but the battle |
| Quest failed line | Persistence will be rewared. |
Persistence will be rewarded. |
| Tutorial hint | Picking it you gets a new weapon. |
Picking it up gives you a new weapon. |
| Tutorial hint | exposion |
explosion |
| Weapon database panel label | wepno #<id> |
weapon #<id> |
| Weapon database panel label | Firerate |
Fire rate |
| Perk database panel label | perkno #<id> |
perk #<id> |
| Quest results prompt | State your name trooper! |
State your name, trooper! |
| Game over hit-ratio tooltip | The % of shot bullets hit the target |
The % of bullets that hit the target |
| Statistics panel line | played for 1 hours 1 minutes |
played for 1 hour 1 minute |
5) Stationary Reloader can finish a reload without refilling ammo¶
Native behavior:
player_updatepreloads ammo whenreload_timer - frame_dt < 0(unscaledframe_dt), then later applies Stationary Reloader by decrementingreload_timerusingreload_scale * frame_dt(withreload_scale = 3when stationary).- When Stationary Reloader is active,
reload_timercan underflow in a single tick even thoughreload_timer - frame_dtwas still non-negative. In that case ammo is never refilled when the reload completes. - This can lead to a “one shot + forced reload loop” (reload completes with
ammo == 0, the next shot underflows ammo and restarts reload).
Why it’s likely a bug:
- The intent is clearly “refill ammo when reload completes”; the underflow check just fails to account for the Stationary Reloader scale factor.
Rewrite behavior:
- Default: use the scaled reload decrement (
reload_scale * frame_dt) for the preload check so ammo is always refilled when Stationary Reloader causes same-tick completion. - With
--preserve-bugs: keep the native unscaled preload check (and the empty-reload loop).