Bug Fixes
- Occasional crash when quitting on macOS
f90f1ca20aClosing the window on exit could crash, because the window's view was used after the window had already freed it.
Regressed in 3.0.0-prerelease.161+2026-02-16 (c7a2ccbb84).
- Leaked memory when loading quests with custom type names
1bd16af581Loading a quest that has custom names for item classes, combo types, map flags, etc. leaked those names every time it was loaded.
Regressed in 2.55-alpha-107 (6b0abe1f8a).
Player
- Leaked memory every time the subscreen was opened
d343169afdEach time the subscreen opened, a copy of the screen it slides over was leaked (45-60 KB per open).
Regressed in 3.0.0-prerelease.89+2025-02-18 (6b5e98dd70).
- Subscreen widgets randomly hidden in older quests
ce9dcc2891Subscreen widgets loaded from older quests (old-style subscreens, or subscreens saved before widget conditions existed) could randomly fail their display conditions and not be drawn. For example, the DMap title in Yuurand or the item selector in Crucible Quest would sometimes be missing, depending on the platform and build.
Bug introduced when subscreen widget conditions were added in 3.0.0-prerelease.108+2025-05-30 (474322fb17).
- Reflected weapons could randomly run their death effects
67984dc363When a weapon was reflected as a copy (e.g. with the "Old Weapon Reflection" rule), the copy started with leftover memory for a few properties. It could then randomly spawn its death item/sprite/sound when it died, draw the wrong shadow tile, or use the wrong weapon/defense type for bomb-style weapons.
Bug introduced when lifted combos as bombs were added in 2.55-alpha-114 (21447f7517).
ZScript
- Passing
Game->AllocateBitmap()twice in a row gave the same bitmapbe638eba1bCalling a function with two Game->AllocateBitmap() arguments in a row, like
f(Game->AllocateBitmap(), Game->AllocateBitmap())or the same in printf/sprintf arguments, allocated only one bitmap and passed it twice.Regressed in 3.0.0-prerelease.15+2023-12-17 (d79b131ab7).
- ZASM optimizer dropped global variable assignments
1dfb9de1e5Assigning a global variable and then passing it straight to a built-in function that takes its arguments on the stack (like
Distance,ArcTanorGame->GetDMapMusicFilename) could lose the assignment, so the variable kept its old value. The common enhanced music script does exactly this with the current DMap. Found in 4 quests.Regressed in 3.0.0-prerelease.25+2024-01-13 (4429211711).
- Comparing two known bools could be folded wrong by ZASM optimizer
335f45edefA comparison of two bools (
==or!=) whose result is used as an int could get the wrong value once the optimizer already knew both sides. For example<int>((<int>(x < y) != 2) == true) > 0.5was always false, though<int>(x < y) != 2is always true.Bug introduced when comparison reduction was added to the optimizer in 3.0.0-prerelease.21+2024-01-06 (419aac8040).
- Experimental ZASM optimizer could repeat or skip creating things
0293f09e4aWith -optimize-ZASM-experimental, storing
Game->AllocateBitmap()into an array element allocated a second bitmap: the array got the second one and the first leaked. CallingGame->AllocateBitmap(),Game->CreateBitmap(),Screen->CreateLWeaponDx()orGame->IncrementQuest()without using the result did nothing. Stellar Seas hits the first case.Regressed in 3.0.0-prerelease.30+2024-01-21 (dddd96cf11).
- Script errors under the JIT could name an unrelated command
a141d817baWith the JIT, a script error could start with the name of a command the script ran earlier, such as "Waitframe(), sprite::X | ..." instead of "sprite::X | ...". Errors recorded in replays then differed between the JIT and the interpreter.
Bug introduced when error context was added in 3.0.0-prerelease.104+2025-05-14 (b7829c0f8e).
- Prevent crash running scripts with broken jumps
fda7f8134bA bug in the 2.55 alphas' Export/Import ZASM feature made every jump in a script go to -1 (an invalid position). The only known quest with this is Rites of Lineage. The interpreter ends the script with an error when it reaches such a jump, but the JIT crashed the game.
Such scripts are now disabled when the quest loads, with a warning in the log. Nothing is lost: they never ran past their first jump.
Regressed in 3.0.0-prerelease.132+2025-09-09 (daa1161618).
- Prevent crash analyzing scripts with broken conditional jumps
2bacc6ecfbA conditional jump to a pc outside of the script (which only malformed quests have) that sits right before a jump target gave the control flow graph an edge to a block past its end, which the ZASM optimizer and the JIT then read out of bounds.
Bug introduced when the ZASM control flow graph was added in 3.0.0-prerelease.10+2023-12-24.z3 (08254299cb).
Build
- Catch invalid bool loads in the Asan config
926e658ed5Add -fsanitize=bool (non-recoverable) to the non-MSVC Asan flags. ASan fills new heap memory with 0xbe, so reading an uninitialized bool member now aborts instead of silently reading garbage.
Tests
- Print the stopped thread first in lldb wrapper backtraces
8a17dacce1When a process stops under the lldb wrapper, print the threads that have a stop reason (signal, exception) before the idle ones, and repeat them at the end of the output. The replay test runner only shows the last 50 lines of stderr, so the faulting thread used to be cut off and only idle worker threads were visible. This hid the cause of the "failed w/ exit code 255" shutdown stops in the mac no-headless replay step.
- Give replay runs started in the same second their own results folder
5dde5262ff
CI
- Fix gcc-only replay exclusion applying to every compiler
3d2806dbc4The replay-tests steps never passed --compiler, so ci.py fell back to its "gcc" default on every runner and dropped yuurand_riviere and crucible_quest_short from the no-JIT and no-optimizer runs everywhere, not just under gcc. Pass the compiler through.
The gcc exclusion itself is still needed: both replays diverge under gcc without the JIT (frames 38 and 1062), same as the known gcc floating-point playback differences.
- Run yuurand and crucible replays w/o JIT/optimizer under gcc too
adc7503b75These were excluded under gcc because they failed there, which was assumed to be gcc's known floating-point playback difference. The real cause was uninitialized subscreen widget fields, fixed in the previous commit.