Repository navigation
Upstream bugs from the 2026-10-15 level-20 log, round two: AI training drop, stale chicken and trap targets, hero heart, explosions - #37
Merged
Conversation
…picked for The computer keeper's MOVE CREATURE TO ROOM check (MvTrain1, room kind 6) picks up whichever creature most needs placing in some room, for any job, and then drops it in the task's own room, the training room. Upstream #4079 taught the drop to refuse a creature that cannot do that job there (a level 10 creature, or training the keeper cannot afford), but the refusal dropped it at a position that was never set. That always failed, logged "Could not find valid position in PLAYER1 TRAINING for <creature> to be dropped", and dumped the creature on the dungeon heart. The drop now tries the task's room, then the room the creature was picked up for, which has a job it can do; only when neither takes it does it go to the heart, as before. Seen 355 times in a keeporig level 20 log from stable 5660. Measured on a fresh level 20 with blue's MvTrain1 firing every 50 turns, MOVE CREATURE TO BEST ROOM off, and four level 10 creatures plus two level 3 controls added: alpha logged 69 of those errors in about 4000 turns, with the level 10 creatures back at the heart each time; the fix logged none, and the level 10 creatures went to research, the guard post and the workshop. The controls trained on both builds.
A hungry creature chooses the nearest grown chicken, remembers only its thing
index and walks to it. If another creature eats that chicken first, the free
thing slot goes to the next thing created, and in a hatchery that is usually
the next chicken it grows. On arrival the old code found a thing at that index
and either refused it ("Tried to eat object CHICKEN_GRW index N which cannot
be eaten now", 21 times in a keeporig level 20 log from stable 5660) and left
hungry, or ate whatever grown thing the index held, wherever it stood.
The chosen chicken's creation turn is now kept beside its index (inside the
creature's job union, so CreatureControl and saves keep their size), and a
creature that finds its chicken gone or replaced chooses again from where it
stands.
Measured on keeporig level 1 with a 5x3 hatchery that grows nothing by
itself: a bile demon chooses the only grown chicken; just before it gets
there that chicken is replaced, in the same index, by a growing one, and a
second grown chicken is put in the room. Alpha: "Tried to eat object
CHICKEN_CRK index 13 which cannot be eaten now", and the demon leaves still
hungry. Fixed: no warning, and it eats the second chicken. Save format
unchanged (sizeof(struct Game) 53384602).
An imp carrying a crate to an empty trap holds only the trap's thing index. If the trap is sold or destroyed on the way, that index goes to the next thing created, often a hatchery chicken, and creature_arms_trap() checked only that something existed there. It then looked for a trap of the chicken's model under the imp, found none, logged "The CHICKEN_GRW has moved or been already rearmed" and dropped the crate; the path for a possessed imp checked nothing at all and would have "rearmed" whatever the index held. It now checks the class and that the crate arms that trap model, before either path, as the pickup step already does. Measured on keeporig level 1: an imp carries a boulder crate to an empty boulder trap, and on the way the trap is deleted and a chicken created in its index. Alpha: "The object CHICKEN_MAT has moved or been already rearmed". Fixed: nothing logged.
The last step of a dungeon heart's destruction deletes the heart thing, and object_update_dungeon_heart() then returned TUFRet_Modified, so update_object() went on with the emptied thing and move_object() logged "Attempt to move non-existing object". It now returns TUFRet_Deleted. Seen twice in a keeporig level 20 log from stable 5660, once per hero heart destroyed. Measured on a fresh level 20 by setting the heroes' heart to 0 health: alpha logs the error once; the fix does not.
Upstream 6d4f458b2 (#5454) hunks for these two files, taken as they are: "Nothing to do for player N request" and "Player N tried to build LIGHTNING but has none to place" are ordinary clicks, now at debug level.
Script actions that react to a heart's destruction run after the heart thing is deleted. Level 20's DISPLAY_OBJECTIVE(135,PLAYER_GOOD), shown once the heroes' dungeon is destroyed, then found no location for PLAYER_GOOD's heart and logged "Dungeon Heart location for player 4 not found" once per player slot (18 lines in a keeporig level 20 log from stable 5660). Both heart location lookups now fall back to the dungeon's essential_pos, which the destruction sets to the heart's position, so the objective points there. Measured on a fresh level 20 by setting the heroes' heart to 0 health: alpha logs that warning 9 times; the fix does not.
…ealth A magic door takes an eighth of a boulder's damage, and the boulder pays 8 health for each point of door it breaks. The eighth was rounded down, so a door that held took floor(H/8) and the boulder kept H mod 8 health: "MAGIC door (health N) and SHOT_BOULDER shot (health N) both survived a collision", 4 times in a keeporig level 20 log from stable 5660 (the boulder left with 2 or 4). Rounded up, a door that holds takes all of the boulder. Not measured in a game; the arithmetic is the whole change.
…her thing explosion_affecting_map_block() and poison_cloud_affecting_map_block() walked a subtile's thing list while hitting, reading the next thing before hitting the current one. A hit that deleted that next thing (a death, an object dropped or destroyed) ended the walk with "Jump to non-existing thing", and everything after it on the subtile escaped the blast; once in a keeporig level 20 log from stable 5660. The things an area effect may hit are now listed first and then hit in the same order, each one skipped if an earlier hit removed it or its index has been given to something created since. Not measured in a game.
… check The combat check looks past a slap's cowering, and past preparing a spell, to what a creature is really doing, so it can decide that a slapped imp next to an enemy should fight. Entering combat then failed, since those states cannot be left until they end, and logged "Failed to enter combat state for creature IMP" (3 times in a keeporig level 20 log from stable 5660, early in the level when the player slaps imps). set_creature_in_combat() now checks first and returns quietly; a later check, once the state has ended, starts the fight as before. Not measured in a game.
…hat room Workers walking between spots in their room sometimes step off its edge for a moment. upstream #4701 caught that inside creature_job_in_room_no_longer_possible(), looking up the room at the creature's destination, but only for its own check: the state that asked went on with the room it had looked up itself, which was none. In training that meant the fee was charged for no experience (the missing room has no efficiency) and "Received nonexisting room" was logged when it chose its next spot; 11 times in a keeporig level 20 log from stable 5660. The ten job states that make this check now look the room up once, through get_room_creature_works_on(): the room the creature stands on or, if none, the one it is heading for. Read from the code; staging the step-off by teleporting a trainee off its room did not reproduce it (the trainee stops training instead), on either build.
"The DRAGON index 351 active state 0 (Unused) is out of range" appeared twice in a keeporig level 20 log from stable 5660, for one tick, and what put the creature in state 0 is not known. The warning now also gives its continue state and backed-up states, so the next time it appears it points at the cause.
thing_can_be_picked_to_place_in_player_room_of_role() warned for each of three cases that are normal play: the keeper's own box on neutral ground, an enemy's box on the keeper's ground (59 lines in a keeporig level 20 log from stable 5660, for special boxes the heroes owned), and the keeper's own box outside its library. It is asked for each such thing whenever an imp looks for work, so one box gave a warning every few turns until it was picked up. They are debug messages now, and the enemy case says whose ground the box is on.
…g .lgtfx load_and_setup_map_info() tells a level in KeeperFX's own format from one in the original format by trying the .lgtfx file and falling back to the .lgt, but the .lgtfx was loaded as mandatory, so every original level logged "Map file mapNNNNN.lgtfx doesn't exist or is too small" on loading. It is loaded as optional now; the .tngfx and .aptfx files, read only for a level in the new format, stay mandatory.
…g tally trap_amount_placeable, the number in the workshop tab, is how many crates a player can still put into new traps: those stored in workshops or held off map, less those needed for empty traps. It was kept by adding and subtracting at each event, and some events were missed: - an imp that dropped a crate on its way to a trap left the trap's claim on a crate in place, and the dropped crate counted again once an imp brought it back; - a trap sold or destroyed while its crate was on the way did the same; - a crate taken from the floor rather than a workshop released nothing. The first is what the test below measures; the other two are from the code. The count crept above what the workshops held, and the tab offered traps there were no crates for, until the next crate in clamped it: "Placeable BOULDER traps amount for player 0 was too large; fixed", 10 lines in one level-20 log. recount_trap_amount_placeable() now works it out from the things themselves: stored plus off map, less the player's empty traps of that model that no imp is already carrying a crate to. It runs wherever the old code adjusted the count, and where it missed: a crate picked up for a trap, a crate dropped, a trap armed, destroyed or sold. The two clamps and their warnings go, since there is nothing left to clamp. A trap left waiting with no crate to spare for it is normal (more traps can run empty than there are crates), so that is a debug message now instead of an error. Doors are unchanged. Measured with rearm_1016.py on keeporig level 1: two boulder crates in a workshop, a boulder blueprint behind the keeper's own door, the door locked while an imp carries a crate to it, unlocked ten seconds later, and a third crate brought in at the end. Before, the tab showed 2 placeable with 1 crate stored from the drop until the third crate, and that crate logged "too large; fixed". After, it showed 0 while the dropped crate lay on the floor (the stored crate was the trap's), 1 once an imp took a crate to the trap, 2 with two stored at the end, and no warning. Save format unchanged: it is the same field.
…rror An imp taking a crate to a trap walks backwards, dragging it. The route is checked when the move is set up. If it closes on the way, as when the keeper locks a door on it, the move fails: the creature restarts, and the crate is dropped where it stands for an imp to bring back later. The forward move handles the same failure with a debug message. The backwards one also logged it as an error, "move_backwards_to_position: Bad place (38016,48000) to move creature IMP backwards to", three times in one level-20 log, although the place was fine. The route warning just before it (log_route_failure) already names the cause, such as "own doors 1 locked", so the error line goes and nothing else changes. That log is from 5660, which did not have the warning yet, so it does not say what closed the route there. Measured with rearm_1016.py on keeporig level 1: locking the door in front of an imp carrying a boulder crate logged the route warning and the error before, and only the warning after. On both, the crate was brought back and the trap armed.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The second round of bugs from the player's 2026-10-15 log of keeporig level 20 on stable 5660. They are all in upstream code the fork had not changed. One commit per bug, on alpha c97a945. It merges cleanly with each of #31–#36, and on top of all six. #32 has the rest of upstream #5431 and is not repeated here.
Measured, alpha vs this branch (each staged on a fresh level through the JSON API and Lua; scripts in the QA harness):
Could not find valid position in PLAYER1 TRAINING for <creature> to be dropped). No position check was failing. MOVE CREATURE TO ROOM picks up whichever creature most needs placing in any room, then drops it in the training room. Upstream #4079 made the drop refuse a creature that cannot train (level 10, or training the keeper cannot pay for). When it refused, it dropped the creature at a position that was never set, so the drop always failed, and the creature was dumped on the heart. Now the drop tries the task's room, then the room the creature was picked up for.Level 20, blue's check firing every 50 turns, four level-10 creatures and two level-3 controls: alpha logged 69 errors in about 4000 turns, with the creatures back on the heart each time; this branch logged none, and the level-10 creatures went to research, the guard post and the workshop. The controls trained on both.
Tried to eat object CHICKEN_GRW ... cannot be eaten now). It remembered only the chicken's thing index. The freed slot goes to the next thing created, usually the hatchery's next chicken, so on arrival it found that chicken and either refused it and left hungry, or ate whatever grown thing the index held, wherever it was. The chosen chicken's creation turn is now kept beside the index, and the creature chooses again.Level 1, a hatchery that grows nothing; the chosen chicken is replaced in the same index just before the bile demon arrives, and a second grown chicken is added: alpha logged
Tried to eat object CHICKEN_CRK index 13and the demon left hungry; this branch logged nothing, and the demon ate the second chicken.The object CHICKEN_GRW has moved or been already rearmed). This is the same stale index, for a trap sold or destroyed while the crate was on its way. The crate was dropped as neutral; a possessed imp's path checked nothing. Now the target's class and model are checked before either path.Level 1, the trap deleted and a chicken created in its index while the imp carries the crate: alpha
The object CHICKEN_MAT has moved or been already rearmed; this branch nothing.move_object: Attempt to move non-existing object). Its update returned Modified after the heart was deleted, so the emptied thing was moved; it now returns Deleted.Dungeon Heart location for player 4 not found). Heart locations now fall back to where the heart stood (essential_pos).Level 20, the heroes' heart set to 0 health (covers 4 and 5): alpha logged 1 move_object error and 9 location warnings; this branch logged none.
Placeable BOULDER traps amount for player 0 was too large; fixed). The count of crates a player can still put into new traps was kept by adding and subtracting at each event, and some events were missed: an imp that dropped a crate on its way to a trap left the trap's claim on a crate in place, and the dropped crate counted again once it was brought back. A trap sold or destroyed while its crate was on the way, and a crate taken from the floor, slipped the same way (these two are from the code). The tab offered traps there were no crates for, until the next crate in clamped the count. It is now worked out from the things themselves: crates stored plus off map, less the empty traps no imp is already carrying a crate to. A trap left waiting with no crate to spare is normal, so that is a debug message now instead of an error.Level 1, a boulder blueprint behind the keeper's own door, the door locked while an imp carries a crate to it, unlocked ten seconds later, then a third crate: alpha showed 2 placeable with 1 crate stored from the drop until the third crate, which logged
too large; fixed; this branch showed 0 while the dropped crate lay on the floor (the stored crate was the trap's), then 1, then 2 with two stored, and no warning.From the code, not reproduced in a game:
Received nonexisting room). Upstream #4701 looked up the room at the creature's destination, but only inside its own check; the state that asked carried on with no room. In training, that charged the fee for no experience. The ten job states now look the room up once, throughget_room_creature_works_on(). Teleporting a trainee off its room did not reproduce it, on either build: the trainee just stops training.Jump to non-existing thing). A hit that deleted the next thing on the subtile ended the pass, and the rest escaped the blast. The targets are now listed first, then hit, and any that were removed or replaced are skipped.both survived a collision). The eighth of the boulder's damage was rounded down, so a door that held left the boulder with 1–7 health. It is rounded up now.Failed to enter combat state). The combat check looks past a slap's cowering, but cowering can't be left until it ends.set_creature_in_combat()now waits quietly, and a later check starts the fight as before.Log only: the out-of-range state warning now names the continue and backed-up states, so its next appearance points at the cause (2 lines, cause unknown). Two warnings for ordinary clicks become debug messages: selling nothing, and placing a trap with none left; these are upstream 6d4f458b2's hunks, unchanged. So do the 59 special-box pickup warnings, which fired for a box a hero dropped on the player's ground. Original-format levels no longer warn about a missing
.lgtfx; that file is how the format is detected. And a crate carrier's route that closes on the way (3 lines:move_backwards_to_position: Bad place ... to move creature IMP backwards to) is no longer an error: the place was fine, the route closed. The test above reproduces it by locking the door: the forward move handles the same failure with a debug message, and the route warning just before it names the cause (own doors 1 locked). Alpha logged the warning and the error; this branch logs the warning. On both, the crate was brought back and the trap armed. The 5660 log predates that warning, so it doesn't say what closed the route there.Not changed:
count_workers_in_room), not a KeeperFX bug.Save format: unchanged (
check-save-format.shOK,sizeof(struct Game)53384602). The chicken's creation turn fits in the creature's job union.Regression check (alpha c97a945 vs this branch, clean builds of both):
qa_sync_check: 12/12.qa_new: all pass.qa_common: clean.levels_sweep: 35/35 on both, with the same warnings, except that the.lgtfxwarning is gone..lgtfxwarning.qa_newfirst failed at its first step, opening the save menu. The harness's "Options is open" check was being fooled by the dungeon heart's flames behind the menu title at level 1's start, on either build. That check is fixed in the harness; afterwards current alpha and this branch pass on every run.