Don't exit on losing all lemmings (feature development)

Started by Simon, January 10, 2024, 11:42:06 AM

Previous topic - Next topic

0 Members and 1 Guest are viewing this topic.

Simon

Rant.

When your lemmings die, play exits to postview, and you can't rewind. Many people have reported this bug, and it's still open 3.5 years after.

2020: WillLem, Simon, Proxima, Dullstar, mobius
2023: Crane, Simon, WillLem, Proxima, Dullstar

This is particularly nasty because you don't see it coming. Oh, split-second too late releasing the finger off timeskip? Tough luck, plow through the menu, start play from the beginning and fast-forward to where you want to go. Oh, you want to go to almost the end? Better take extra care to not run into the bug a second time!

Solution is to stay in play, but prevent further physics advances. It's only okay to exit here if I've already won, or if I've nuked.

I'll paypal 200 Euros to whoever fixes this (Edit 2024-05-15: WillLem got it and donated a share to namida), releases code and binaries, and prods namida to include it upstream (whether successfully or not). Never exit an unsolved unnuked level until I exit manually. Caveat: If namida gets the 200 Euros, 100 of them must go to LF hosting.

Every time:
  • You promise people to play their levels.
  • You finally find an hour of free time, and load the level in NL.
  • You play for 3 minutes.
  • You run into such a UI problem and resign.
It's beyond me how anybody can play under this bug without tearing all hair out. And this sounds familiar: I feel the same about the unfixed zoom bug. The workaround to the zoom bug is to never zoom, and instead play fullscreen on the biggest monitor you can buy. Yes, play fullscreen ... until you run into the exit-on-losing-lemmings bug.

There is also the slippery right-click scrolling, but that is hard to debug (happens under Windows or only under Linux) and hard to fix (freeze the OS cursor?). Again mitigated by big monitor. Not that big of a deal as the other two bugs (exit on losing all lemmings, and the zoom bug), but it aggravates the zoom bug because the zoom bug breaks the alternative scrolling via zoom-out-zoom-in.

Not sure what is 4th-most agonizing bug.

I've told Flopsy in IRC: NL has improved a lot since 2014. Yes, it has. It merely still has a few bugs that lead to dents in office furniture.

I understand that it's hard to make software bearable. I should write about and debug the laggy mouse cursor in Lix. Even the Allegro 5 example for endless mouse movement has that bug. Looks like it's worse on Linux than on Windows. I'm surprised nobody has ranted about that in Lix.

-- Simon

Simon

Forwarding all the way to the end is useful. I get to see by how many saved lemmings I fall short of the goal.

Again (as with the zoom bug) I assume that nobody else plays like I do because everybody is already conditioned to avoid running into these things. You can gain serious value here that isn't immediately obvious.

--  Simon

WillLem

N.B. I'm posting this with full acknowledgement of the fact that namida has repeatedly stated that he won't be updating NeoLemmix any further than has already been approved and planned. So, this post exists simply because I agree with Simon that this feature ought to be addressed, and I know of several possible fixes - I do not expect namida to act on this, and I'm sure he's more than capable of providing his own code/fixes should he wish to address it himself.

With that said, if namida does approve these changes, I'm happy to offer any help and support where necessary. And if not, he has stated that he's happy for NeoLemmix to be forked anyway, so it's possible that we can continue some sort of "Community Edition" of NeoLemmix using only Community-approved changes/updates such as this one.

Quote from: Simon on January 10, 2024, 11:42:06 AM
I'll paypal 200 Euros to whoever fixes this

Happy to take you up on this! The following will prevent the level from ending when (the save requirement hasn't been met) and (there are no lems):


{ add to procedure TLemmingGame.CheckForGameFinished; }

  // Stops levels ending when no lemmings remain onscreen and the save requirement has not yet been met
  if (Level.Info.RescueCount > LemmingsIn) and (LemmingsOut = 0) then
      Exit;


Removing this from the existing code may also work:


{ remove from procedure TLemmingGame.CheckForGameFinished; }

  if ((Level.Info.LemmingsCount + LemmingsCloned - fSpawnedDead) - (LemmingsRemoved) = 0) and (DelayEndFrames = 0) then
  begin
    Finish(GM_FIN_LEMMINGS);
    Exit;
  end;


I'm happy to test this in NeoLemmix and work on the feature until it's bug-free and playing nicely, of course.




Meanwhile, SuperLemmix has code for preventing single-lemming levels from ending when that lemming dies. More than happy for this to be used as well. Here it is:


{ add to procedure TLemmingGame.CheckForGameFinished; }

  // Stops single-lemming levels ending when 1 lemming has been removed and 0 are saved
  if ((Level.Info.LemmingsCount = 1) and ((LemmingsRemoved) = 1))
  and not (LemmingsIn >= 1) then
      Exit;


(In SLX, this part also checks for Classic Mode, but obviously that's not needed in NL).

namida

If code for this is developed and tested by the community, I'm happy to merge it in. If there is significant divided opinion about whether this change is desirable, I will only merge it if it's implemented as an option rather than the sole behavior (being enabled by default is acceptable).
My projects
2D Lemmings: NeoLemmix (engine) | Lemmings Plus Series (level packs) | Doomsday Lemmings (level pack)
3D Lemmings: Loap (engine) | L3DEdit (level / graphics editor) | L3DUtils (replay / etc utility) | Lemmings Plus 3D (level pack)
Non-Lemmings: Commander Keen: Galaxy Reimagined (a Commander Keen fangame)

WillLem

Quote from: namida on February 07, 2024, 09:40:44 PM
I will only merge it if it's implemented as an option rather than the sole behavior

This shouldn't be a problem, I'd suggest passing a GameParams flag and checking for this alongside the above code example. Happy to implement this as well.

WillLem

(P.S. Assuming Simon is serious about the 200 Euros and I manage to find a fix, I'm happy to donate a portion of it to site running costs :lemcat:)

namida

Basically - if you can get a diff file or a git branch or something, or even just a copy of modified files compared to a specific NeoLemmix commit, I should be able to integrate it pretty easily from there.

Implementing an option would also mean making sure it's added in the Options menu and saved in the INI file.
My projects
2D Lemmings: NeoLemmix (engine) | Lemmings Plus Series (level packs) | Doomsday Lemmings (level pack)
3D Lemmings: Loap (engine) | L3DEdit (level / graphics editor) | L3DUtils (replay / etc utility) | Lemmings Plus 3D (level pack)
Non-Lemmings: Commander Keen: Galaxy Reimagined (a Commander Keen fangame)

Simon

Happy that this gains traction, and that namida is open to merging it back!

Quote from: namidasignificant divided opinion [..] implemented as an option

At least for Lix, I haven't received any reports or any suggestions against this. It's standard and you can't turn it off (i.e., you can't make unsolved singleplayer exit without a keypress/nuke click).

But I display big fat text: "Rewind time (◀▮) or quit with [hotkey]." (Assuming you haven't turned tooltips off.) If you don't display such text, there is serious danger that the physics freeze looks like a bug. You'll have to user-test indeed.

Also, we might find NL edge cases that Lix never needed to consider. Make an option if you can't find a good rule.

Quote from: WillLemAssuming Simon is serious about the 200 Euros

Yes, I'm serious about the 200 euros. I'd be happy if you donate a share to LF. To avoid converting currencies twice, I'll send that share directly to namida.

It's becoming important to specify exactly what's required for the money. Certainly, it's required that unnuked, unsolved levels don't exit. Is it required that nuked levels exit like before? Is it required that unsolved, unnuked, lemming-less levels freeze? Do we count neutrals as lemmings here (probably, unsure)? Do we count zombies as lemmings here (probably not)? Some of these are listed before I wrote "to whoever fixes this", but I repeated only a summary (technically incomplete) in the same paragraph as "to whoever fixes this". I'll post again on the weekend at latest.

-- Simon

WillLem

Attached is the relevant modified code for NL, originally taken from the latest commit (f3d92ea33). It will need to be tested for bugs and suitability, of course, but it seems pretty solid after a quick initial test.

Quote from: Simon on February 08, 2024, 10:01:01 AM
it's required that unnuked, unsolved levels don't exit. Is it required that nuked levels exit like before? ... Do we count neutrals as lemmings here (probably, unsure)? Do we count zombies as lemmings here (probably not)?

The attached copy of LemGame will yield the following behaviour:


  • If the save requirement is met and all lemmings have been removed*, gameplay ends as usual
  • If the save requirement is not met and all lemmings have been removed, gameplay continues (unless the user has opted for gameplay to end when no lems remain, in which case gameplay ends)
  • Nuke ends gameplay as usual

* Zombies count as removed lems in NeoLemmix, Neutrals do not - this means that gameplay will end if the save requirement is met and only Zombies remain onscreen, but will continue if any number of Neutral lems remains onscreen (even if the save requirement is met). Since Neutrals can be saved and we are soon to have the Deneutralizer object in NeoLemmix, this seems to be acceptable. However, this can be changed so that gameplay will end if only neutrals and/or zombies remain and the save requirement has been met. I'll await feedback on this first.

* Exited lems count as removed


Quote from: Simon on February 08, 2024, 10:01:01 AM
Is it required that unsolved, unnuked, lemming-less levels freeze?
...
But I display big fat text: "Rewind time (◀▮) or quit with [hotkey]." (Assuming you haven't turned tooltips off.) If you don't display such text, there is serious danger that the physics freeze looks like a bug. You'll have to user-test indeed.

Although it can be achieved easily enough, I'm personally not in favour of a physics freeze; the lem count turns red when too few lems remain onscreen, and shows 0 when there are either no lems at all, or only zombies remaining. This, and the absence of assignable lems, is enough visual information IMHO. Players ought to wonder why gameplay hasn't ended in any scenario other than a pass.

With that said, if people would prefer the game to pause, it can be done.

Quote from: Simon on February 08, 2024, 10:01:01 AM
At least for Lix, I haven't received any reports or any suggestions against this. It's standard and you can't turn it off (i.e., you can't make unsolved singleplayer exit without a keypress/nuke click).
...
Make an option if you can't find a good rule.

The attached copies of FNeoLemmixConfig and GameControl make the behaviour optional (and save the choice to user config) - this seems the best approach for now, and makes testing both behaviours easier.

namida

I wonder if the option to not auto-exit even when the save requirement is met, should be added?

In no way is accepting it conditional on any particular behavior here; I just thought it's worth raising.
My projects
2D Lemmings: NeoLemmix (engine) | Lemmings Plus Series (level packs) | Doomsday Lemmings (level pack)
3D Lemmings: Loap (engine) | L3DEdit (level / graphics editor) | L3DUtils (replay / etc utility) | Lemmings Plus 3D (level pack)
Non-Lemmings: Commander Keen: Galaxy Reimagined (a Commander Keen fangame)

IchoTolot

If the save requirement is met (and all my lems are gone), I personally 100% would like to auto-exit the level.

If I fail the save-req then it is debateable and there an option for exit/not exit would be very nice to have! :)

namida

This could also be an option with three settings: "Never auto exit", "Only auto exit if save requirement met", "Always auto exit".
My projects
2D Lemmings: NeoLemmix (engine) | Lemmings Plus Series (level packs) | Doomsday Lemmings (level pack)
3D Lemmings: Loap (engine) | L3DEdit (level / graphics editor) | L3DUtils (replay / etc utility) | Lemmings Plus 3D (level pack)
Non-Lemmings: Commander Keen: Galaxy Reimagined (a Commander Keen fangame)

IchoTolot

Quote from: namida on February 09, 2024, 08:36:59 PM
This could also be an option with three settings: "Never auto exit", "Only auto exit if save requirement met", "Always auto exit".

This sounds like good solution for the issue.

Maybe with the nuke as an exception for the "Only auto exit if save requirement met" setting, as that's a player initiated end. ???

Simon

Quote from: WillLem on February 08, 2024, 03:06:02 PM
If the save requirement is met and all lemmings have been removed*, gameplay ends
If the save requirement is not met and all lemmings have been removed, gameplay continues
Nuke ends gameplay as usual
Zombies count as removed lems in NeoLemmix, Neutrals do not
continue if any number of Neutral lems remains onscreen

All of this is good.

Quote
Quote from: Simonunsolved, unnuked, lemming-less levels freeze?
not in favour of a physics freeze
is enough visual information IMHO.
With that said, if people would prefer the game to pause, it can be done.

The freeze isn't only for feedback. It's a strong convenience feature.

The most common case is fast-forwarding into the failed lemming-less state. If NL doesn't freeze here, you'll overshoot. You don't necessarily realize the failed state immediately. If it takes 1-3 seconds of realization, you've overshot by a lot. Instead, freeze, to make it much quicker to rewind to a useful state.

Compare: 1. Web browsers don't let you scroll away from the page, not even half a screen worth past the end. 2. NL already doesn't allow you to rewind to 20 seconds before hatches open. 3. We'll have a button in Lix to quickly rewind to the previous skill assignment: That quickens the rewind to a common desired point. Similarly, 4., you should prevent moving into the useless clearly failed state any deeper than necessary, to quicken the rewind to a desired point.

If you believe that you want to continue into the useless state, I warmly recommend you to try it with the freeze first. If you still want more physics advances after trying the freeze, then it's time to make the freeze optional. Or have you played Lix and wanted the physics to continue?

Marginal use case (for continuing deep into failed lemming-less state): To observe zombie behavior. But even for this, the stars must align for the freeze to be annoying: 1. Zombie behavior must be nontrivial (hard to visualize), 2. zombie behavior must be important, 3. all lemmings have already died. Aligning all three stars sounds rare enough to ignore the use case, or to send the player to the options menu.

Consider to not tie the freeze to the pause button. The freeze can prevent further updates regardless of current speed. This is to avoid clashes with manual pausing. E.g., if NL paused on reaching the failed state, we might pause ourselves, and be a tad late. The two pause commands would then cancel each other.

Quote from: Icho
Quote from: namidaoption with three settings: "Never auto exit"
This sounds like good solution for the issue.

Considerable. But beware of adding options when nobody has wanted it another way yet. Ask Proxima or other challenge solvers. If it's handy for them, you can make a case for such an option.

-- Simon

WillLem

#14
Quote from: Simon on February 10, 2024, 12:08:27 AM
All of this is good.

Glad you approve! Testing will likely further discussion around the various specifics of this feature, but we definitely have a good starting point.

Quote from: Simon on February 10, 2024, 12:08:27 AM
The freeze isn't only for feedback. It's a strong convenience feature.
...
The most common case is fast-forwarding into the failed lemming-less state. If NL doesn't freeze here, you'll overshoot. You don't necessarily realize the failed state immediately. If it takes 1-3 seconds of realization, you've overshot by a lot. Instead, freeze, to make it much quicker to rewind to a useful state.

It's possible to check for fast-forward game speed, so maybe we only freeze physics in the event of the player using fast-forward? Otherwise, it feels more natural to simply let gameplay continue (as is the case when Blockers remain, for example).

Quote from: Simon on February 10, 2024, 12:08:27 AM
If you believe that you want to continue into the useless state, I warmly recommend you to try it with the freeze first. If you still want more physics advances after trying the freeze, then it's time to make the freeze optional.

Yes, there's no good reason not to give it a try. I like the idea of it being optional behaviour, as this reduces the need to show a message and allows fine tailoring of this feature by the player, which is important because it's purely a QOL. Incidentally, if we do show a message, a simple "no lemmings remain!" with an OK button should suffice, but a custom graphic could also be considered.

Quote from: Simon on February 10, 2024, 12:08:27 AM
Consider to not tie the freeze to the pause button ... This is to avoid clashes with manual pausing.

I get the point here, but tbh it seems very unlikely that the double pause would happen often enough to warrant the hard physics freeze. A softer "game has paused itself" option seems more preferable, if only because pausing solves the "might look like a crash" problem; it's clear that player input will resume a normal gameplay state - the player can see that the game has paused itself, and simply unpauses to continue. Conversely, a freeze may be jarring enough to have the player quit out altogether on the assumption that the game has crashed.

EDIT: Admittedly, making the freeze behaviour optional and off by default also solves this problem

Happy to try it both ways and see what we think. Thoughts?

Quote from: namida on February 09, 2024, 08:36:59 PM
This could also be an option with three settings: "Never auto exit", "Only auto exit if save requirement met", "Always auto exit".

Yes, good idea. We could do this as a radio button group with the three mutually exclusive options, and a checkbox for "Pause/freeze game".

At this point, I probably agree with Simon that we should wait to see if it's needed first.




I'll get on adding the auto-pause / physics freeze over the weekend.

namida

My reasoning on the option to disable it even when the save requirement is met is that it gives the same convenience to people who may be aiming for talismans or max-saved solutions etc.
My projects
2D Lemmings: NeoLemmix (engine) | Lemmings Plus Series (level packs) | Doomsday Lemmings (level pack)
3D Lemmings: Loap (engine) | L3DEdit (level / graphics editor) | L3DUtils (replay / etc utility) | Lemmings Plus 3D (level pack)
Non-Lemmings: Commander Keen: Galaxy Reimagined (a Commander Keen fangame)

WillLem

OK, I went ahead and coded this as a full-fledged feature with all 3 end-of-play options, plus the option to pause the game (or not).

It's done as a radio group in the Interface Options tab (I've also moved the Graphics options to their own tab to keep the Config menu tidy):



N.B. We can of course change the exact wording of this in the menu if necessary

"Pause Game" is unavailable for the first option, since it's irrelevant in that case. For the other two options, with Pause Game checked, the game enters a paused state which must then be backskipped or restarted in order to resume normal gameplay.

At the moment, if the player presses pause to unpause the game after all lemmings are removed, the game simply pauses again (because the criteria for triggering the paused state still applies, i.e. there are no lemmings, and the user has opted to pause the game in this case). The same is true if we suspend gameplay rather than pausing (i.e. if gameplay is resumed, it immediately gets suspended again because the check for no lemmings is still returning true with each physics update). At the moment I can't think of a sane way to fix this, but I'll come back to it when I've had some sleep*.

With "Pause Game" unchecked, the game doesn't pause and instead continues to play as normal.

See attached files for all updated code.

*Note that in order to pause/suspend the game, LemGame needs to pass a flag to GameWindow - I'm hoping that there may be a better way to achieve the desired behaviour from within LemGame itself. This would likely fix the aforementioned bug whilst also keeping the feature as simple as possible.

namida

One possibility would be to have a variable in LemGame that is set to true once the message is sent to GameWindow, and prevents it being sent again. It should be either included in save states, or specifically set back to false if the player rewinds, or some other mechanism to ensure it will trigger again if the player rewinds then again runs out of lemmings.
My projects
2D Lemmings: NeoLemmix (engine) | Lemmings Plus Series (level packs) | Doomsday Lemmings (level pack)
3D Lemmings: Loap (engine) | L3DEdit (level / graphics editor) | L3DUtils (replay / etc utility) | Lemmings Plus 3D (level pack)
Non-Lemmings: Commander Keen: Galaxy Reimagined (a Commander Keen fangame)

WillLem

Quote from: namida on February 12, 2024, 08:44:25 PM
specifically set back to false if the player rewinds, or some other mechanism to ensure it will trigger again if the player rewinds then again runs out of lemmings.

We already have this; the method in GameWindow sets the flag back to false after pausing the game, so that the player can rewind, restart, etc and the flag can be set back to true if relevant.

The issue is more to do with what happens after the game has paused. If the player tries to unpause (or, resume gameplay if we go with suspending physics), the flag is immediately set to true again since there are still no lemmings, and so the game instantly pauses (or suspends) again.

Quote from: namida on February 12, 2024, 08:44:25 PM
and prevents it being sent again.

Could you elaborate here? Is there a way to prevent the message being sent again once it's already been sent, whilst retaining the ability to be sent again if we rewind to before it was first sent? Perhaps this is where savestates come in, as you suggested...


Simon

Menu looks good to me. Looks like you can support namida's/Icho's ideas without overly much extra work or extra menu space. And you've already thought of disabling the Pause checkbox when we want to always exit.

You haven't attached binaries yet, therefore playtesting has to wait until you do. (I still haven't gotten a Delphi build toolchain to work on Linux.) I'll be happy to playtest during the weekend. Or you focus on development first, and I'll find more time later in the month.

Quote from: WillLemsuspended

Is suspend the same as pause? You didn't like the absolute prevention of further physics updates (= what I called a freeze, but I don't know a best name either). You wanted the regular pause, which the player can unpause.

Quote from: WillLemimmediately gets suspended again because the check for no lemmings is still returning true
sane way to fix this

If the check works well already, you can alternatively be happy with a freeze that you can't unpause. :P Assuming you really want to add more logic here:

You want to pause when you are running out of lemmings, not when you have run out of lemmings. Therefore, it's conceivable to move the origin of this information into/near physics, because that's when we run out of lemmings.

When, at the start of a physics update, you still have lemmings, and you've run out of lemmings after this physics update, then a result of this physics update is that the UI should react (to losing all lemmings). The physics update doesn't care of how exactly to react; the UI will look at the user options.

This avoids state (no need to remember whether we've already paused for loss) but puts UI-relevant logic into physics (which we usually want to avoid). The physics already tell the UI about many things, e.g., to play sound, and this would then be one more event that the UI receives from the physics.

I haven't checked the NL source if this is easy, or even if it's sensible at all for NL.

Even in Lix, where I have a dedicated class for this concept (EffectSink) of ferrying eye/ear candy (sound commands, commands to generate explosion debris, commands to make a flying pickaxe, assignment arrows, ...) from physics into the physics-ignorant part, I'd have to write extra code to pry the message from the EffectSink into the speed logic (which so far is completely independent of the effect sink).

namida shows sensible feeling already in the wording ("message") instead of treating this purely as a state check. It may be wrong in NL to put it in the physics, but even elsewhere, it's still good to think of this particular pausing as a message/event, not as a continuous situation.

-- Simon

Simon

Another downside to pause (vs. freeze): When you hold the 10-second skip, you will skip 10 seconds regardless of the pause. You'll overshoot far into dead state.

I skip 10 seconds more often in NL than in Lix when NL's regular fast-forward isn't fast enough. It's practically a stronger but fickler fast-forward.

I don't have playtesting results from NL + pause-at-end yet. I expect to reach the end with repeated 10-second skips moderately often. Better verdict will have to wait for playtesting.

Your argument against the freeze was that you suppose player confusion. Your argument was never about the usefulness of the dead state. Thus: Have you considered the freeze plus better feedback in the UI (instead of the pause)? If you improve the UI feedback, please avoid dialog box, it eats hotkeys.

-- Simon

WillLem

#21
Quote from: Simon on February 13, 2024, 01:23:43 AM
You haven't attached binaries yet

Apologies, I'm unsure as to what binaries are in this context? ??? :forehead:

The .pas files attached above can simply replace the existing ones in the source folder, as long as nothing else changes in these files between now and such time as a playtest build is released. I'm also happy to prepare an experimental build this evening, with namida's permission.

Quote from: Simon on February 13, 2024, 01:23:43 AM
Is suspend the same as pause?

No, it's a little bit more complicated. It does pause the game, but also prevents player input and releases the mouse. It's used for dialogs such as Save/Load/Edit Replay, and any error messages. It seems to be the closest thing we already have to a "physics freeze", and wants input from the dialog before gameplay can be resumed.

Quote from: Simon on February 13, 2024, 01:23:43 AM
If the check works well already, you can alternatively be happy with a freeze that you can't unpause

I see what you mean and I did consider this, but ultimately I think that limiting player control is undesirable. The dead state could be useful for some reason, so we want to give the player the choice to unpause and resume gameplay if they wish.

Quote from: Simon on February 13, 2024, 01:23:43 AM
You want to pause when you are running out of lemmings, not when you have run out of lemmings.

Currently, the game pauses at the frame the very last lemming is removed. Would you prefer something other than this?

Quote from: Simon on February 13, 2024, 01:23:43 AM
namida shows sensible feeling already in the wording ("message") instead of treating this purely as a state check

I need a bit more info/context on what's meant here. I'm familiar with messages, but would need namida to elaborate a bit more before I go ahead and try anything.

Quote from: Simon on February 13, 2024, 02:45:33 AM
Another downside to pause (vs. freeze): When you hold the 10-second skip, you will skip 10 seconds regardless of the pause. You'll overshoot far into dead state.

EDIT: OK, I see the problem here. A time skip of, say, 1000 frames overshoots by a significant amount and then the player has to backtrack to an unknown point. Should be possible to find a way around this.

Quote from: Simon on February 13, 2024, 02:45:33 AM
Your argument against the freeze was that you suppose player confusion. Your argument was never about the usefulness of the dead state. Thus: Have you considered the freeze plus better feedback in the UI (instead of the pause)?

Both arguments are equally valid in my view; the common concern is with giving the player as much control as possible over the paused/frozen state. If we pause the game, they can simply unpause if they wish gameplay to resume into the dead state for any reason, or they can backskip or restart. Either way, we're giving the player the choice as to what is the most useful course of action at that point (many actions are possible whilst the game is in a paused state), and not introducing any new input mechanics for the player to learn (i.e. they already know what to do when the game is paused).

If we freeze the game instead, then I agree visual feedback is absolutely necessary. A dialog would be the easiest solution (and would be consistent with all other gameplay suspensions in NL), gameplay can resume pending player input from the dialog, and we can put as much information as necessary into it. However, I agree it's generally undesirable (particularly if the player has to see it multiple times in a single gameplay session), so the least intrusive alternative would be to work with the limited skill panel display (we'd have about 40 characters) to alert the player to the "no lemmings" state and give instructions for how to proceed from there (bear in mind that this all has to be decided upon, coded in, and learned by the player).

Important question at this point: maybe there is some benefit to freezing physics (as opposed to pausing the game) that I haven't yet become aware of...?

Proxima

Quote from: WillLem on February 13, 2024, 08:33:38 PMImportant question at this point: maybe there is some benefit to freezing physics that I haven't yet become aware of...?

The benefit is that if time can't go forward past when the last lemming dies, it's always easy to go back to a known length of time before the last lemming dies -- and even if, for example, that death is from a fall into the void from a high place, you just need to tap "1 second back" a few times to get back to when that lemming was on safe ground.

WillLem

#23
OK, I understand the issue now with regards to overshooting the point at which no lemmings remain. The bigger the forward skip, the longer it takes to backstep into playable level.

It's a simple enough fix - we set the HyperSpeed target to the current frame if PauseWhenNoLemmings returns true. This allows us to keep pause behaviour (easier for the player to understand and from which to resume gameplay) whilst preventing overshooting.

Here's the modified copy of GameWindow which implements this fix.

WillLem

#24
With namida's approval, I've prepared an experimental release of NeoLemmix 12.13 which includes the proposed feature for testing.

So far, we have the full feature with all 3 available options.

If either "Never Exit" or "Only Exit if Save Requirement Met" are chosen, the option to "Pause Game" becomes available. This causes the game to enter a paused state on the exact frame that the last lemming is removed, enabling the player to proceed with either backstepping, restarting or unpausing from there as they wish, and preventing gameplay from overshooting too far into the unplayable state.

By all means take a look at the experimental and post any feedback here in this topic.




Main points for testing:

  • Are the options sufficient and clear enough? (see Config menu / Interface tab)
  • Does the feature behave as expectated depending on which option is chosen?
  • Anything not behaving as expected: Does it pause when it shouldn't, or does it ever not pause when it should? Does it interact properly with skips past the frame on which the last lemming is removed (in both directions)?
  • Is the paused state sufficient for halting gameplay, or should something else happen? Do we need any further visual cues?
  • Would you like to be able to unpause the game and continue into the "no lemmings" state? Currently, the game immediately pauses again - I'm working on a fix for this, but in the meantime would it be acceptable for this state to not be un-pausable? This has been fixed in V2 - it's now possible to unpause

Proxima

Quote from: WillLem on February 13, 2024, 10:26:56 PMZombies count as "removed" lems, so gameplay will end if only zombies remain (and the relevant option is chosen) - should this be the same for neutrals?

If only neutrals remain, the level may or may not be solved, depending on whether a path has been set up to allow them to exit. The only way to know whether to award victory is either to allow the player to play it out, or to automate the process.

Simon

Quote from: WillLem on February 13, 2024, 08:33:38 PM
what binaries are in this context?

Here: An NL executable. I can't build it myself from source here.

Generally: Executables, DLLs, static libraries. Program code in CPU-consumable form instead of human-readable source code.

Ah, you've released an entire experimental build. Thanks! I'll test it this weekend at latest, and you'll have feedback by Monday.

Quote from: WillLem on February 13, 2024, 08:33:38 PM
Quote from: Simon on February 13, 2024, 01:23:43 AM
Is suspend the same as pause?
prevents player input and releases the mouse.
wants input from the dialog before gameplay can be resumed.

Okay, you decide if this is appropriate. It sounds as if NL stops reading hotkeys during suspension, which would be a problem. We want hotkeys active throughout this; we usually want to rewind.

You say it wants input from the dialog, but we don't plan to add a dialog.

Quote from: WillLem on February 13, 2024, 08:33:38 PM
Quote from: Simon on February 13, 2024, 01:23:43 AM
pause when you are running out of lemmings, not when you have run
pauses at the frame the very last lemming is removed.
prefer something other than this?

Two answers.

1. It's indeed good to end when the last lemming is removed. It may be even nicer to end when ((the last lemming is removed) and (no triggered trap is eating anymore)).

2. It's possible you didn't get the nuance. To be safe, I'll rephrase: Your problem is that your code re-pauses too often. My reply was that this pausing should originate from actively running out of lemmings (which can only happen if we still had lemmings before advancing physics during this very iteration of the program main loop) instead of from having run out of lemmings (which is still true in future main loop iterations, regardless of whether we advanced physics again or not).

namida and I recommend nearly the same solution. The difference is: namida wants to track state (has he messaged you before). I want to generate the pause as a result of advancing physics, which won't happen again until you rewind. My solution needs no extra state, but ties the check to parts of the code (physics, or at least the game code that triggers physics advances) that don't care about UI. I don't know enough NL internals to tell you what's ultimately better.

Quote from: WillLem on February 13, 2024, 08:33:38 PM
skip of, say, 1000 frames overshoots by a significant amount

Yes, this is problem #1 that you should address.

Problem #2 arises from holding a skip hotkey down. The held key generates keyboard input for every iteration of the program loop. In 2023 NL, such skip input will advance physics even during pause, which is fundamentally good. But here, the skip will ignore your pausing. I hold the key down, I reach the end, your code pauses, my held key generates more inputs and completely bypasses your pause. Now I've overshot by many multiples of the single skip length.

Quote from: WillLem on February 13, 2024, 08:33:38 PM
freeze the game instead, then I agree visual feedback is absolutely necessary.
work with the limited skill panel display

Yes, 100 % agree that this is the biggest task in implementing a freeze. To make it lovely, you'll have to design visual feedback unlike anything in NL. I have no easy answer. More on this later.

Quote
benefit to freezing physics (as opposed to pausing the game) that I haven't yet become aware of...?

Also more on this later.

-- Simon

namida

Even more important than what WillLem mentioned as focuses, is looking for any bugs. In particular - does it pause when it shouldn't, or does it ever not pause when it should, does it interact properly with skips past the frame on which the last lemming is removed (in both directions), etc.

Neutrals should not be treated the same way as zombies. Although no skills can be assigned to them, they could still be saved if they have a path to the exit, or possibly via RR manipulation or nuking.
My projects
2D Lemmings: NeoLemmix (engine) | Lemmings Plus Series (level packs) | Doomsday Lemmings (level pack)
3D Lemmings: Loap (engine) | L3DEdit (level / graphics editor) | L3DUtils (replay / etc utility) | Lemmings Plus 3D (level pack)
Non-Lemmings: Commander Keen: Galaxy Reimagined (a Commander Keen fangame)

WillLem

#28
Quote from: Simon on February 13, 2024, 11:20:21 PM
It sounds as if NL stops reading hotkeys during suspension, which would be a problem. We want hotkeys active throughout this; we usually want to rewind.

You say it wants input from the dialog, but we don't plan to add a dialog.

OK, we won't use SuspendGameplay and we agree that there shouldn't be any need for a dialog, or anything that would interfere with game hotkeys.

I had a look at what happens in Lix, and I've realised that what you're looking for with a "physics freeze" is for the game to stop altogether, and not allow any user input other than to backtrack into the level. SuspendGameplay and Pause, therefore, are not compatible with this.

I'll look closer at NL's codebase and see if there is a way to similarly halt the game.

Quote from: Simon on February 13, 2024, 11:20:21 PM
It may be even nicer to end when ((the last lemming is removed) and (no triggered trap is eating anymore))

Waiting for the animation to finish is definitely possible, might have to see if there are ways to check "is a trap animation currently playing?"

Quote from: Simon on February 13, 2024, 11:20:21 PM
namida and I recommend nearly the same solution. The difference is: namida wants to track state ... I want to generate the pause as a result of advancing physics, which won't happen again until you rewind. My solution needs no extra state, but ties the check to parts of the code (physics, or at least the game code that triggers physics advances) that don't care about UI

This is perhaps where a "message" rather than a true/false flag may be required to make the feature more sophisticated, and perhaps more malleable. Education needed, I'm more than prepared to do the necessary work.

EDIT: Note to self: Store current iteration in savestate when last lemming is lost, and then look to that iteration for whether or not to pause (namida's solution). See whether physics update can check if there were any lemmings available on the previous physics update, and only pause if there were and now there aren't (Simon's solution).

Quote from: Simon on February 13, 2024, 11:20:21 PM
Yes, this is problem #1 that you should address.

Problem #2 arises from holding a skip hotkey down ... my held key generates more inputs and completely bypasses your pause. Now I've overshot by many multiples of the single skip length.

The current fix addresses problem #1 well enough, but I can confirm that problem #2 still remains. The paused state halts the game long enough to alert the player that something has happened (about 2 seconds), but does indeed go ahead with generating more skips after that.

It looks like decisions are going to need to be made before we go ahead with anything, as it's likely that the feature will need some degree of re-writing in order to incorporate the various suggested nuances. Which nuances will determine how the re-write looks.

Quote from: Simon on February 13, 2024, 11:20:21 PM
Yes, 100 % agree that this is the biggest task in implementing a freeze. To make it lovely, you'll have to design visual feedback unlike anything in NL

Again, from looking at Lix I think the panel text is sufficient. We only need to display:

NO LEMMINGS LEFT! SKIP BACK OR RESTART

That's 38 characters, the panel allows 45.

Again again though, this is only if we decide to go with "halt, and allow no more input." My own feeling is that the player should have the option to unpause the game and continue into the "no lems" state if they wish. Anything else feels a bit "un-NeoLemmix," perhaps? I need more community feedback on this point, and namida's opinion would be particularly valuable as he will make the ultimate call on it.

Quote from: namida on February 13, 2024, 11:41:52 PM
looking for any bugs

Definitely important, but I'd say that was implied by "does it behave as expected?" ;P

Quote from: Proxima on February 13, 2024, 11:17:10 PM
If only neutrals remain ... The only way to know whether to award victory is either to allow the player to play it out

Quote from: namida on February 13, 2024, 11:41:52 PM
Neutrals should not be treated the same way as zombies ... they could still be saved if they have a path to the exit, or possibly via RR manipulation or nuking.

Yes, of course.

I've updated the testing points in response to these comments.

namida

You could achieve what Simon is talking about by simply having LemGame refuse to run the update routine if all of these conditions are met:
1. No non-removed lemmings (except zombies)
2. No traps currently animating (optional, but looks nicer if the animation can finish first; exiting currently takes this into account)
3. No lemmings (except zombies) still waiting to spawn

When I say "refuse to run", I don't mean as in gives an error; just as in calling LemGame.UpdateLemmings in this situation should do nothing. It's okay if GameWindow still attempts to re-render the new frame; this would just result in it redrawing the last one again, which is unnecessary but otherwise not a problem at all.

Specific places I would look for bugs with this implementation:
1. If the user does a large frameskip past the frame where the last lemming is lost, does the correct behavior result? Check this for conditional skips (like "skip to next shrugger") as well as for time-based ones.
2. Can the user still skip backwards and resume play properly afterwards?
3. Does mass replay check behave properly on replays where this occurs?
My projects
2D Lemmings: NeoLemmix (engine) | Lemmings Plus Series (level packs) | Doomsday Lemmings (level pack)
3D Lemmings: Loap (engine) | L3DEdit (level / graphics editor) | L3DUtils (replay / etc utility) | Lemmings Plus 3D (level pack)
Non-Lemmings: Commander Keen: Galaxy Reimagined (a Commander Keen fangame)

Simon

Quote from: namida on February 14, 2024, 07:36:00 PM
LemGame refuse to run the update routine if all:
1. 2. 3.

Yes, for the freeze, this is a good approach, and it doesn't need messaging nor extra state tracking.

Quote from: WillLem
That's 38 characters, the panel allows 45.

Sounds doable, yes.

When you overwrite this status panel, you can't display its usual values. Most values have become irrelevant by here (we know that 0 lemmings are in hatch, 0 lemmings are in level), but I like to look at by how many lemmings we fell short of the save requirement. I'd nudge you to still show this (how many more to save) at all times.

Maybe we want the time limit here, too, but I have no experience. The time limit doesn't sound as important to show (i.e., by how much we fell short of the save requirement feels more important). Or ditch half of the message, only show "skip back or restart", or "rewind or restart", or ...

In a pinch, if you don't show a value, we can rewind 1 or more ticks to view the regular panel.

Quote from: WillLemWhich nuances will determine how the re-write looks.
option to unpause the game and continue into the "no lems" state
need more community feedback

Right; others, please join the discussion.

-- Simon

WillLem

Quote from: namida on February 14, 2024, 07:36:00 PM
You could achieve what Simon is talking about by simply having LemGame refuse to run the update routine

Quote from: namida on February 14, 2024, 07:36:00 PM
Specific places I would look for bugs with this implementation

All good shouts, thanks. I'll take a look at it over the weekend.

Quote from: Simon on February 15, 2024, 04:34:31 AM
When you overwrite this status panel, you can't display its usual values ... I'd nudge you to still show this (how many more to save) at all times.

Essentially, the entire display would have be re-written for this particular instance. It's worth it though, because it would be relatively easy to manage and may come in handy for other game features as well. Also, since it's in the area of the screen that the player relies on for information, it keeps things consistent.

If you still want to display level information - OK, the message could be shrunk to something like:

"NO LEMS - BACKSKIP OR RESTART"

That's 29 characters, leaving 16 for any other info we need to display. But, if "how many more need to be saved" is the only crucial piece here, maybe we could have something like:

"NO LEMS LEFT! XXX NEEDED! BACKSKIP OR RESTART"

That uses all 45 characters and accounts for levels with lem counts in 3 digits (since 500 is the official NL limit, we don't need to account for 4 digits - we could also hard-code the display message to allow a maximum of 999 anyway).

WillLem

OK, after some thought, it seems the best way to approach showing a message is to display it in the play area of the screen rather than on the panel.

The panel display is simply not meant to be messed with; a repurposing attempt very quickly gets messy and invites re-writing of some parts of the panel code - not worth it for such an incidental feature, particularly when we take into account that players may want to still have access to some or all of the panel's information.

For this particular feature, it seems best to make a simple .png graphic with the necessary text written in, and then load it and display it when relevant. Directly above the panel seems like the best place, but centre or top of screen could also work. Here's a mockup to show how this might look:



The image will then be displayed for the duration of the "no lems" state, and will disappear if the player backskips/restarts to a playable state.

Using a .png to display the message has the added benefit of being something that the user can customise themselves - we could make multilingual versions of the message available, and if the player doesn't want it then it can just be replaced with a blank image (making it optional is also a possibility).

I'm still working on integrating this into the codebase with a minimum of impact. Ideally, we don't want this feature to piggyback on any other rendering elements (panel, shadows, helpers, etc) where it could cause bugs/issues; a unique procedure within GameWindow seems most appropriate.




Meanwhile, I've figured out how to allow the game to be unpaused into the unplayable state (should the player wish to do this for any reason) whilst retaining the ability to re-pause if the game is backskipped and another (or the same) "no lems" situation arises again.

With this is mind, do we still want to freeze physics and not allow any player input other than backstep or restart?

Feedback needed before I can progress much further with this.

Simon

Thanks for the mockup!

When this message appears, will the main map (where we play the level) jump up by the height of the message? Or do you overlay it onto the main map? Map resizing can be nasty (when to do it, lots of corner cases, violates assumptions in existing code, ...) but we aren't actively playing here, nor aiming the cursor at anything.

I agree that such extra text is better than cramming it into the panel, where it would complicate the code only to hide panel stats.

I still believe that the full freeze is the best, and I accept the inability to view future zombie behavior here. Regarding zombies, you don't worsen in comparison to existing NL, which quits here. Technically, you still accept lots of player input, e.g., skill changes, load savestate, load replays, ..., but you're right that the only useful reactions are rewind, restart, or manual quit.

Still, I, too, am interested in others' opinions. I'm biased: Lix is the only Lemmings-like that doesn't quit, and I designed the freeze for Lix, and I play it often, and I like it, and I've developed playing patterns (aggressive fast-forwarding) that rely on the freeze and take it for granted. I talked with geoo about it, and he understands how NL's quit annoys me, but the quit annoys me more than the quit annoys him. Darkshoxx played NL and was surprised (in a bad way) from the quit. Looks like he didn't expect the powerful NL to still quit here like ancient L1. But that's not an argument for the freeze in particular, only against the quit.

I'll playtest your experimental today.

-- Simon

WillLem

Quote from: Simon on February 18, 2024, 11:58:31 AM
I still believe that the full freeze is the best, and I accept the inability to view future zombie behavior here. Regarding zombies, you don't worsen in comparison to existing NL, which quits here. Technically, you still accept lots of player input, e.g., skill changes, load savestate, load replays, ..., but you're right that the only useful reactions are rewind, restart, or manual quit.

Advantages for freeze: it's impossible to overshoot into the unplayable state

Advantages for pause: it doesn't look like a bug, the player knows what to do when the game is paused so a display message is not necessary, the player can explore the unplayable state if they wish to do so

Physics freeze will require significantly more overhauling of the codebase, including writing a new procedure to display the necessary message (which, as I imagine it, would simply be overlayed onto the play area); we should be absolutely sure it's what we want before we proceed any further with it.

Test put the pause behaviour and see if it's adequate. It may be easier and less problematic to simply disallow fast-forward or forwards skipping once there are no lems left - this would combine all advantages into one feature :)

Simon

Quote from: WillLem on February 18, 2024, 01:21:12 PM
Test put the pause behaviour and see if it's adequate.

The first observation of it: The pause allows stepping into dead state, but turns even a 10-second skip into a 1-tick advance. This limits the overshooting. If I hold the 10-second skip and am 3 real-time seconds late with realizing the dead state, I've stepped ~180 ticks into the dead state, not 3*10*17 ticks. That's reasonably quick to undo with 1-second rewind.

This is solid anti-rage already, nice.

I'll play some contest levels tonight. Will see how it holds on big levels that have worse framestepping performance than the small test level. More feedback tomorrow.

Whether it's good for a release, I can't advise; I don't mind much that the 10-second skip eventually turns into a 1-tick advance, but it obviously breaks the 10-second skip's (unimportant?) promise.

-- Simon

WillLem

#36
Quote from: Simon on February 18, 2024, 02:22:20 PM
This is solid anti-rage already, nice.
...
I don't mind much that the 10-second skip eventually turns into a 1-tick advance, but it obviously breaks the 10-second skip's (unimportant?) promise.

Glad to have your approval. I've uploaded an update to the Exp this evening which prevents forward-skipping into the unplayable state at all (whilst retaining pause behaviour). It works nicely, prevents overshooting and is minimum impact on the existing codebase. Also, we arguably don't need to display a message because it's very clear what's happened.

With that said, I'll still work on getting a message displayed because it'll be good to learn how to do that sort of thing anyway, and I'll also get a version together with a full physics freeze so that we can test that behaviour as well. This will likely take longer though, later in the week I'd expect.




The attachment includes all relevant project files to date.

WillLem

Well, the message alert is proving to be the most difficult element to this feature so far.

I can overlay the panel (no good for reasons already discussed), plonk it somewhere on the playable area (no good, because it might be off-screen) or replace the cursor in a similar fashion to the splat ruler (yikes!).

Anything other than that involves creating a new canvas just for the sake of displaying this message, which seems way over the top, and adds further rendering load, which is generally not a good thing in this program.

The only reasonable place to display the message, using existing logic, is where the minimap would normally be. I didn't want to piggyback like this originally, but it actually seems like quite an elegant solution - it keeps the message completely away from both the playable area and the panel information.

Thoughts?


Simon

Feedback from yesterday's V1 testing on levels from Contest 29: No problems on large maps either. If NL never released again, I could imagine always playing this V1 in the future without raging over surprise quits. My settings are: When no lemmings remain, exit if level is solved; otherwise, pause.

Those small elegance questions remain (does it look like a hack, do people like it, are people confused by lack of fast-forward or held-down skip buttons, etc.). They don't need answers yet because you'll have more testing first, and namida isn't yet going to pull V1 into the mainline.

I'll see when I find time to test the experimental V2.

Quote from: WillLemI can overlay the panel (no good for reasons already discussed), plonk it somewhere on the playable area (no good, because it might be off-screen) or replace the cursor in a similar fashion to the splat ruler (yikes!).

All of these ideas: I agree that printing into the minimap is better than those ideas.

I don't see any big downsides for printing into the minimap. The minimap is always there, regardless of user options. Minor design questions arise, e.g., can you still scroll with it during the message?

To stick the message into a side/corner of the camera where it can't scroll away: This was my hunch, but it sounds like it's too hard to fit on top of existing drawing. In Lix, after Dave4's replay-cancelling confusion, I bit the bullet and invested all the extra work to print occasional text into a corner of the camera.

-- Simon

WillLem

Success!

After much deliberation and failed attempts, I've figured out how to draw a message to the minimap area. The method basically involves replacing the viewport rectangle with a .png image - no hackish shunting of the minimap itself, the message is simply displayed over the top of the existing minimap:



The same logic can also theoretically be used to display other messages in the minimap area. However, I'd probably caution against overuse of this feature, since whenever a message is being displayed, we lose use of the minimap. It should only really be used in very exceptional circumstances (such as unplayable states).

Anyway, I've now uploaded two new versions of the 12.13 experimental, called "PauseVersion" and "FreezeVersion" - both versions make use of the minimap message, but the Pause version pauses the game and the Freeze version performs a Lix-style physics freeze. See the exp topic for more info and to give each version a test-run.

Proxima

Very nice. The only thing I'd like to say at this point is, maybe it's not the best to use a word that's unique to NL like "backskip"? Sure, it's easy for an English speaker to work out what it means, but it is a little bit less friendly to newcomers (not all of whom will be native English speakers), which seems wrong when newcomer-friendliness was the main point of this feature in the first place. Would "rewind" be a better substitute?

WillLem

Quote from: Proxima on February 23, 2024, 04:21:56 PM
Would "rewind" be a better substitute?

Happy to change the wording to whatever people agree on.

"Rewind" is OK, but since NeoLemmix doesn't have an explicit "Rewind" feature (i.e. it's done by performing multiple backwards frame skips), this could be confusing: players may look for a "Rewind" button on the panel (they'd find one in SLX ;P).

For this reason, I ended up preferring "Backskip". But, I can absolutely see how this might be similarly confusing to anyone who isn't yet familiar with this feature, or whose first language isn't English.

Rewind probably is better.

Thoughts? Suggestions?

Simon

Rewind is a good term for this; it doesn't clash with anything in NL. I've started to use "rewind" consistently in Lix, too.

Attempting to shoehorn it for SLX: You'll have one-shot rewinds (during pause) and a continuous rewind mode.

Stepping 1 tick forward is still called "skip" in the Lix code, but it doesn't skip anything; in the English translation, I think I've started to call it a step. The 10-second skip is a skip. Similarly, stepping 1 tick backwards is more a backstep than a backskip. If you call it "backstep" or "backskip", I think it's still easy enough for non-native English speakers to guess that it is the rewind; reason: "back" is elementary vocabulary.

I'll see if I find time tomorrow to test your full freeze. If not, you'll have to wait for next Wednesday. Thanks for putting in all that hard work!

-- Simon

Simon

First test results of your experimental NL 12.13 freeze version:

Out of habit, I placed only the executable in my existing NL dir and forgot the images (for the in-game messages during the freeze). NL works well until it finally wants to show the message. This produces many exceptions as one popup each, and the level continues to run and produce more exception popups even before I close the earlier popups. NL was hard to kill from the command line (impossible to type xkill or killall -s 9) because the popups stole focus (many times per second). Mouse worked, but NL has no X button to close the window. It managed to write a total of 7,000 lines into the log until I managed to close NL.

To close NL, eventually I thought of Alt+F4, and that worked. I believe that another solution would have been: Ctrl+Shift+F2 to switch to non-GUI Linux command line, kill NL from there, Ctrl+Shift+F1 to switch back to desktop.

Okay, in hindsight, the popups are practically unrelated to the freeze feature; if we want to do anything at all about this, we should first file those repeated exception popups (1 popup is enough) as a bug against the general resource loading. By the way, I'm happy that NL loads the things lazily, to start up more quickly. Nice.

I took the lo-res and the hi-res image from the zip into the NL tree, and that fixed the exceptions.




Now back to the freeze.

Yes, it prevents any physics advances consistently.

It appears impossible to bypass the freeze. I tried to fast-forward, to unpause, and to framestep forward. Nothing bypasses, nice. Not even when I rewound ~3 frames to before the freeze, then tapped or held the 10-second skip, NL would ever advance beyond the freeze.

The freeze appears to happen 1 tick too late. Example: I see the lemming in tick n. Then it falls out of the level, dies, and is not visibile anymore in tick n + 1. The lemming count in tick n + 1 is red and shows 0, correct. Nonetheless, I can now still advance to tick n + 2 before your message appears. The late freeze is mostly cosmetical, it doesn't break my play patterns. It merely makes me wonder why it overshoots.

Nuking brings an interesting design problem. The nuke generates 5-second countdowns, and you might run into the freeze (because you lost all normal lemmings and all neutrals), but there are still unnuked zombies (with countdowns from your nuke). This freezes, but it doesn't exit. Is this intended? Do I want this myself? (Haven't made up my mind w.r.t. nuke solutions.) Would others want this?

Ah, a nuked level in general freezes now (when everybody has exploded and even if no zombies exist), and doesn't exit. I believe it should exit, as it did in your pause experimental. Or do you have a new reason to freeze here?

-- Simon

WillLem

Quote from: Simon on March 02, 2024, 04:53:51 PM
Out of habit, I placed only the executable in my existing NL dir and forgot the images (for the in-game messages during the freeze). NL works well until it finally wants to show the message. This produces many exceptions as one popup each

Since the images are loaded when needed, they'll need their own exception handling in the event of an unsuccessful load; I'll get on this.

Quote from: Simon on March 02, 2024, 04:53:51 PM
The freeze appears to happen 1 tick too late ... The late freeze is mostly cosmetical, it doesn't break my play patterns. It merely makes me wonder why it overshoots.

First thought: if it ain't broke, don't fix it ;P

Second thought: OK, I can probably figure out why this is happening. I'll have a look later. Question, though: was anything else happening besides the lemmings falling offscreen? Any trap animations it needed to wait for?

Quote from: Simon on March 02, 2024, 04:53:51 PM
Ah, a nuked level in general freezes now (when everybody has exploded and even if no zombies exist), and doesn't exit. I believe it should exit, as it did in your pause experimental. Or do you have a new reason to freeze here?

No, this is a bug resulting from the more simplistic freeze code (which simply prevents the game updating when stuff returns true); I'll need to add a check for the nuke specifically here. The pause, whilst more complex in that it needs to allow more user input, necessarily makes use of existing checks (including nuke) whilst the game determines if it's finished or not.




EDIT: Please see this poll.

Simon

Quote from: WillLem on March 02, 2024, 07:50:25 PM
was anything else happening besides the lemmings falling offscreen? Any trap animations it needed to wait for?

The level was your end-of-game test (some Crystal bars, 1 neutral, 1 zombie). No traps. The zombie was alive. The last dying lemming was regular (not the neutral; the neutral was already dead).

QuoteI'll need to add a check for the nuke specifically here.

Okay, cool, happy to re-test then.

-- Simon

Simon

Quote from: WillLem on February 18, 2024, 01:21:12 PM
Advantages for pause: it doesn't look like a bug
all advantages into one feature :)

The buggiest-looking behavior is now after unpausing in the dead state. During dead state, you're gimping the frameskips because that's the only way you can bring the pause to support real-life patterns (extrapolate current state to end of time and see by how many lemmings we come short).

There is a nasty long-term issue here: 5-10 years down the road, NL's maintainer X (neither you nor namida) gets a bug report from player Y: The frameskip after the pause is broken. This will be a perfectly nice bug report against what you've considered to look less like a bug. It's easy for X to fix, breaking the guarantees that you've added.

The pause itself looks well-defined. The freeze also looks correct with the message.

I'd even bet that anything with a message (pause or freeze) will look more intended than anything else without such a message (pause or freeze).

-- Simon

WillLem

Quote from: Simon on March 03, 2024, 03:05:19 PM
The buggiest-looking behavior is now after unpausing in the dead state. During dead state, you're gimping the frameskips

Fair point.

Quote from: Simon on March 03, 2024, 03:05:19 PM
anything with a message ... will look more intended than anything else without such a message.

Agreed; this does reduce the "looks like a crash" issue with the freeze, to the extent that from a programming point of view I'd actually much prefer to go with freeze rather than pause: much less potential for bugs, and it touches far less code. Pause, meanwhile, needs to have checks in a number of different places in order for it to behave well (and, as you've pointed out, it remains somewhat mischievous).

A majority of people seem to prefer pause, according to the poll results so far. As a player, I much prefer it.

I'll look for ways to streamline the Pause code, and try to remove the need to nerf the forward frameskips. There may even be a way to make use of both freeze and pause - freeze until the player has manually inputted something (so, no automatic overshooting with large/held skip hotkeys - keydown/keyup would help here), then pause - that could work.

Simon

Quote from: WillLem on March 03, 2024, 04:46:56 PM
people seem to prefer pause, according to the poll results so far.

Because you've framed the question.

"Pause - that way, I can decide" -- you can decide even if we implement the freeze, because the freeze is optional anyway. Also, tasty swizzle: Who wouldn't want to decide.

"Also, it looks less like a bug/crash" -- we've both realized today that the current designs (of freeze and pause) have bug-lookingness the other way around. Feels like you're straw-manning the current (2024-02-23) freeze implementation with an early idea of the freeze that had no message.

"Freeze - [...] I'll only ever need to" -- that's a powerful nudge away from this option. Who has had the time to try both experimental variants? If you haven't played both, why should you waive your rights (by picking this option) to instead of being allowed to "I can decide"?

Even I am not sure if I ever need to bypass! E.g., nuke and zombies.




There is more to write about zombies and nuke. Either later tonight or these days. Exciting design ahead!

There is more to write about false dichotomies. Across both experimentals, you already have a 7-way option here (3 radio buttons + 1 freeze radio button, and 3 out of those 4 listen to the checkmark), with at most 2-3 of the 7 useful in practice, and now you're making such polls based on two unfinished designs. Put them both into git as two branches and see.

There is more to drill out of you here because you like the pause even after all of this. Again, later.

-- Simon

WillLem

Quote from: Simon on March 03, 2024, 06:07:07 PM
Because you've framed the question.

OK, I've removed the poll.

Ultimately, you've offered to pay for this feature so I'm happy to go ahead with whatever version of it you prefer.

You prefer freeze, so let's go with that; it's much easier to work with from a programming point of view anyway. I'll proceed with that version unless anybody else strongly wants pause, and cares enough to make a post specifically requesting it. Freeze is already better than exit anyway, and it's what people are used to from Lix.

Let's move on.

Quote from: Simon on March 02, 2024, 04:53:51 PM
Ah, a nuked level in general freezes now (when everybody has exploded and even if no zombies exist), and doesn't exit. I believe it should exit, as it did in your pause experimental. Or do you have a new reason to freeze here?

I can't seem to reproduce this. Nuked levels exit as usual...

Simon

No worries with the poll; thanks for it, it helped me second-guess all this.

The poll said 4:1 in favor of the pause. I can't blame every single one of the 4 pause answers on the framing, and you preferred the pause before the poll.

My worry is now: If we make the freeze, but don't nail it perfectly, yes, we'll still be better than quitting, but we aren't as good as pausing (for a reason that I don't see yet). Are the pause wanters worried about a concrete reason? Are the pause wanters worried about a possible reason that might exist? Are you worried about this? What are the reasons?

Do you (or others) have time to hop on Mumble tomorrow, Monday, 19:00 UTC?

Quote from: WillLem
Quote from: SimonAh, a nuked level in general freezes now (when everybody has exploded and even if no zombies exist), and doesn't exit.]
I can't seem to reproduce this. Nuked levels exit as usual...

You are right, I can't reproduce this either. Sorry, I don't remember why I wrote this, either. Consider it fine, and If I ever find anything like it, I'll post full instructions to reproduce it in the future.

Quote from: SimonThere is more to write about zombies and nuke.

Regardless of freeze or pause, there is the decision if there is something of interest still happening. For this, we don't treat zombies as interesting.

Now I believe: Zombies become interesting once we are nuking. As a result, the nuke prevents the pause/freeze when only zombies remain. When everybody (regular, neutral, zombies) is dead, we quit. Reworded: In the decision if there is something of interest still happening, we test for regular lemmings alive, or neutrals alive, or traps eating, or nuke.

The fun part will then be: During the freeze with only zombies left alive, can we nuke? We won't advance to the tick in which the nuke is actively preventing the freeze. >_>;; In Lix, during the freeze, the nuke becomes a button to exit.

-- Simon

WillLem

Quote from: Simon on March 03, 2024, 09:26:04 PM
My worry is now: If we make the freeze, but don't nail it perfectly, yes, we'll still be better than quitting, but we aren't as good as pausing (for a reason that I don't see yet)

To be honest, I'm at the point where I feel we just need to make a decision and run with it.

I failed to persuade you (i.e. the person who originally requested this feature and has offered to fund it!) that pause is better. I also now see that freeze is both easier to manage and (much) easier to debug.

So, I've pretty much done a 180 on wanting pause. I know I can do it if it's requested later, but for now I just want to get the "no lems" feature stable and ready for release.

After a lot of deliberation and testing of the new behaviour, I'm even beginning to think that the physics freeze shouldn't be optional. The choice should be: exit to postview, or freeze the game. Allowing gameplay to continue into an unplayable state has the potential to be very confusing. If I get no feedback on this, I'll likely run with it and remove the option.

Incidentally, we can also re-use the minimap message code for other "unplayable states": only Blockers remain (and no Walkers in skillset), only Neutrals remain, only Zombies remain (debateable), etc - obviously, game doesn't freeze in these examples, but the message can be displayed if we wish.

Quote from: Simon on March 03, 2024, 09:26:04 PM
Do you (or others) have time to hop on Mumble tomorrow, Monday, 19:00 UTC?

Potentially, yes. I'll move a few things around and see you then.

Quote from: Simon
Ah, a nuked level in general freezes now
Quote from: WillLem
I can't seem to reproduce this. Nuked levels exit as usual...
Quote from: Simon on March 03, 2024, 09:26:04 PM
You are right, I can't reproduce this either. Sorry, I don't remember why I wrote this, either.

OK - wierdly, I can now reproduce this bug!

It's fixed; the end-of-level check now factors in nuke. However, I'm now seeing possibilities to streamline and simplify these checks further. It won't make a difference up front, but the whole thing should be easier to debug.

Quote from: Simon on March 03, 2024, 09:26:04 PM
During the freeze with only zombies left alive, can we nuke?

At the moment, no. All controls except for backskip and restart have no effect.

Incidentally, in NL, nuking zombies causes the level to immediately exit to postview anyway: we don't get to see the zombies explode. I fixed this in SLX, happy to apply the fix in NL as well.

Quote from: Simon on March 03, 2024, 09:26:04 PM
We won't advance to the tick in which the nuke is actively preventing the freeze

Correct - although, we can allow this by specifically calling the update procedure if the nuke is activated, thus cancelling the freeze state. Note: we could even cancel the freeze state with pause, thus achieving both freeze and pause in the same feature...?

Quote from: Simon on March 03, 2024, 09:26:04 PM
In Lix, during the freeze, the nuke becomes a button to exit.

This is a good idea; I've added this by setting the nuke flag to true and calling the update procedure for nuke button/hotkey iff the unplayable state has been reached. Bit of a hack, maybe - there's probably a better way to achieve this same thing. Note: we can't call for Game.Finish because it needs a reason, which is undeclared in GBSP.

Simon

Quote from: WillLem on March 03, 2024, 11:38:52 PM
Quote from: Simon on March 03, 2024, 09:26:04 PM
Do you (or others) have time to hop on Mumble tomorrow, Monday, 19:00 UTC?
Potentially, yes. I'll move a few things around and see you then.

All right, cool. If 19:00 UTC is hard, I can make 18:00 UTC or 20:00 UTC or 21:00 UTC. Let me know by the afternoon what's best, or hop on IRC tomorrow.

Everything else, I'll come back to it later, but it all sounds excellent and promising!

-- Simon

WillLem


Simon

Minutes from the Mumble session.

Nobody has wished to continue into the dead state explicitly. Still, some people might want to do it (e.g., watch zombies walk?), but a prominent possibility to continue into dead state is confusing on its own. The freeze avoids this particular confusion, let's try it. If somebody really wants to continue into the dead state, we'll implement an option to neither freeze nor quit when no lemmings remain. The freeze is too useful to weaken it for the presumably rare wish to continue.

That means we choose the freeze over the pause to make a first PR against NL. That's because the pause would fiddle with too many assumptions of other speed controls. Nonetheless, neither of us wants to hammer any decision in stone here. NL can get additional solutions or improve on the freeze should the freeze suck.

Nuke will always prevent the freeze. Nuke will lead to quitting to postview. If you nuke with zombies, you'll see the zombies explode before exiting to postview. If you've already reached the freeze, the nuke will either exit to postview if no zombies exist, or unfreeze to show the zombies exploding, then exit to postview. (Technically: Activating the nuke forces NL to start another physics update despite the freeze. Without zombies, that extra frame of water/exit animation isn't shown then, not even during fade-out, nice.)

This design accepts that precise nuke solutions need manual pausing to prevent exiting to postview. It sounds practical because nuke levels are rare, and it doesn't worsen the user experience in nuke levels compared to 2023 NL. The fireworks take a few seconds, you have plenty of time to pause.

It would be nice if the freeze can work even if NL fails to load the graphic for the message on the minimap, e.g., when people update by putting only the NL executable into an existing NL tree. Our main worry is that the freeze will then look buggy. We have some ideas, possibly a once-per-NL-session dialog box, but no solution seems perfect. WillLem will ask namida what namida prefers for a PR here. Do others have a suggestion?

-- Simon

WillLem

I've now uploaded Version 4 of the NeoLemmix 12.13 Experimental, implementing changes discussed with Simon plus a few others.

Please do check it out and offer feedback as the features in this experimental are likely to make it into NeoLemmix, so it would be better if they've been fully tested and discussed prior to final implementation.

namida

I will also note that if we're still on 12.12 when this feature is ready, I'm quite happy to release it as a minor update if it merges (or cherry picks) fairly cleanly. If not it will need to wait for 12.13.
My projects
2D Lemmings: NeoLemmix (engine) | Lemmings Plus Series (level packs) | Doomsday Lemmings (level pack)
3D Lemmings: Loap (engine) | L3DEdit (level / graphics editor) | L3DUtils (replay / etc utility) | Lemmings Plus 3D (level pack)
Non-Lemmings: Commander Keen: Galaxy Reimagined (a Commander Keen fangame)

Simon

Thanks to both of you for pushing this!

I'll be busy until Monday. During that next week, I'll stream more contest levels with that experimental version 4, and I'll test offline in addition. Thus, expect detailed feedback in roughly a week.

-- Simon

Simon

Playtesting stream: This Wednesday, March 13, starting 18:00 UTC. https://www.twitch.tv/simonnaar

-- Simon

Simon

The stream will stay up for 14 days:
https://www.twitch.tv/videos/2089736199

00:01:00: I dumped the exp 3's config into the exp 4, then played without looking at the exp 4's options dialog. NL freezes both if won (wrong) and if lost (correct). But if we look into the options dialog, the middle option (Exit to Postwiew if Save Requirement Met) is selected. This doesn't match what we just saw in play. To make the game obey the middle option, I clicked the middle option (that was already chosen), then clicked OK. Now, play correctly froze after losing, and play correctly exited after winning.

00:04:40: It still overshoots by one frame.

Sometimes it doesn't overshoot. In particular: If it has already overshot once, then we rewound, and we haven't quit to menu since the overshooting, then it doesn't seem to overshoot.

00:05:00–00:10:00: The nuke works as designed: It always exits to postview, regardless of the 3-way option. Good.

Much later, in kaywhyn's Fun Teleportation Race in Space:

02:00:45: If you win with the final lemming that goes into the exit, and there are no more lemmings, and you don't run into the overshooting bug, NL tells you: No more lemmings, rewind or quit. NL won't quit here even though I've won; NL even shows 0 flagpole left to save. I've played this with the option: Exit to Postwiew if Save Requirement Met.

-- Simon

kaywhyn

@Simon

Correct, that is a very bad backroute for my R3 - Fun Teleportation Race in Space as you have guessed. Good find! :thumbsup: I'll need to think of a fix for the level. I will release an update when the LOTY2023 playing phase starts in a few weeks or so.
https://www.youtube.com/channel/UCPMqwuqZ206rBWJrUC6wkrA - My YouTube channel and you can also find my playlists of Lemmings level packs that I have LPed
kaywhyn's blog: https://www.lemmingsforums.net/index.php?topic=5363.0

WillLem

Quote from: Simon on March 13, 2024, 09:18:56 PM
I dumped the exp 3's config into the exp 4, then played without looking at the exp 4's options dialog. NL freezes both if won (wrong) and if lost (correct). But if we look into the options dialog, the middle option (Exit to Postwiew if Save Requirement Met) is selected. This doesn't match what we just saw in play

This is a difficult bug to replicate, and from what I can see the flag is being loaded from and saved to config correctly. Outputting to debug also indicates that the flag is set correctly when gameplay starts. I might need a second pair of eyes on this one.

Quote from: Simon on March 13, 2024, 09:18:56 PM
It still overshoots by one frame ... Sometimes it doesn't overshoot

Yes, I can replicate this. Best guess: it takes a tick to register the flags (but this wouldn't explain why it sometimes doesn't happen). Unsure exactly how to solve this tbh.

Quote from: Simon on March 13, 2024, 09:18:56 PM
If you win with the final lemming that goes into the exit, and there are no more lemmings ... NL tells you: No more lemmings, rewind or quit. NL won't quit here even though I've won

Again, having a hard time replicating this. In all tests I performed, levels always exit to postview correctly.

We check for the option flag and the rescue count being equal to or more than the requirement. Here it is, maybe you can see if there's something wrong?

// End of gameplay options for when no lemmings remain
  if ((Level.Info.LemmingsCount + LemmingsCloned - fSpawnedDead) - LemmingsRemoved = 0)
    and (DelayEndFrames = 0) and not UserSetNuking then
  begin
    if GameParams.AlwaysEndGameplay
      or ((LemmingsIn >= Level.Info.RescueCount) and GameParams.EndIfLevelPassed)
        then ExitToPostview
    else if GameParams.NeverEndGameplay
      or ((LemmingsIn < Level.Info.RescueCount) and GameParams.EndIfLevelPassed)
        then begin
          if not ShowNoLemsMessage then ShowNoLemsMessage := True;
          Exit;
        end;
  end;

Simon

Quote from: WillLem on March 13, 2024, 11:30:55 PM
"I dumped the exp 3's config into the exp 4": This is a difficult bug to replicate, and from what I can see the flag is being loaded from and saved to config correctly. Outputting to debug also indicates that the flag is set correctly when gameplay starts. I might need a second pair of eyes on this one.

I've attached my settings.ini from the exp 3.

Ideas: Assuming you load, save, and use the option properly, there is a fourth thing to it: What happens to this option when we start NL with empty options file/new installation/...?

Quote from: Simon on March 13, 2024, 09:18:56 PM
If you win with the final lemming that goes into the exit, and there are no more lemmings ... NL tells you: No more lemmings, rewind or quit. NL won't quit here even though I've won
Quote from: WillLem on March 13, 2024, 11:30:55 PM
tests I performed, levels always exit to postview correctly.
maybe you can see if there's something wrong?

When exactly do you call into the code that you showed?

When does NL update LemmingsIn? Reason: In my vod at 02:00:45 (the same time that I wrote into my previous post), we clearly see 0 flagpole and the message. According to your code, this can only happen when LemmingsIn isn't 10 yet, and the level requires to save 10/10. Do you call into your code too early?

What is DelayEndFrames? Do you expect it to be 0 in my vod around 02:00:45 when I run into the bug?

QuoteBest guess: it takes a tick to register the flags (but this wouldn't explain why it sometimes doesn't happen)

Correct, this doesn't explain why it sometimes freezes correctly (instead of overshooting by 1 frame).

But you make it sound as if you test for end-of-game during the physics updates, not in-between the physics updates. It's okay if you call it after a physics update and cache your results, but still, caching means more mutable state, and mutable state is a source of bugs.

Consider to factor this code into two methods: One to test for end-of-game (regardless of how we react to that), and one to tell you whether we should show the message (that, among other things, asks the end-of-game method). In effect, try to refactor the flag ShowNoLemsMessage into a method, to avoid mutable state, and call this method where you would have read the flag.

-- Simon

WillLem

Quote from: Simon on March 14, 2024, 12:49:10 AM
What happens to this option when we start NL with empty options file/new installation/...?

Same thing as with every other option: a default is set (in this case, Always Exit To Postview).

Quote from: Simon on March 13, 2024, 09:18:56 PM
When exactly do you call into the code that you showed?

It's a check that happens every update (Update Lemmings / CheckForGameFinished), to see if the end-of-gameplay conditions have been met.

Quote from: Simon on March 13, 2024, 09:18:56 PM
When does NL update LemmingsIn?

UpdateLemmings / CheckLemmings / CheckTriggerArea / HandleExit / HandleExiting / RemoveLemming(Saved) / Inc(LemmingsIn)

So, every update. However, within UpdateLemmings, CheckLemmings is called after CheckForGameFinished. So, in theory, that might explain why it takes another update to set everything correctly and why the game didn't exit properly when you saved the last lem. If we move the checks around though, it may give rise to other bugs...

Quote from: Simon on March 13, 2024, 09:18:56 PM
What is DelayEndFrames? Do you expect it to be 0 in my vod around 02:00:45 when I run into the bug?

DelayEndFrames specifically waits for trap animations to finish.

Quote from: Simon on March 13, 2024, 09:18:56 PM
It's okay if you call it after a physics update and cache your results, but still, caching means more mutable state, and mutable state is a source of bugs.

Consider to factor this code into two methods: One to test for end-of-game (regardless of how we react to that), and one to tell you whether we should show the message (that, among other things, asks the end-of-game method). In effect, try to refactor the flag ShowNoLemsMessage into a method, to avoid mutable state, and call this method where you would have read the flag.


Simon

Quotewithin UpdateLemmings, CheckLemmings is called after CheckForGameFinished.

Advancing physics has nothing to do with deciding whether we have reached dead state.

Reason: When we have reached dead state, you don't even begin advancing physics. You can decide about whether a state is dead well after advancing physics. In no way advancing physics depends on being dead; originally, in your pause design, you considered continuing into dead state.

Indeed, move the test for dead state out of advancing physics altogether. Fear not!

Quotecat

Your code does: Test for dead state once per update and set some variables (e.g., ShowNoLemsMessage). Later, read these variables.

You should do: Remove the variables. Later, in the code that needed to read them, instead re-test for dead state.

-- Simon

WillLem

Quote from: Simon on March 14, 2024, 06:40:09 AM
Indeed, move the test for dead state out of advancing physics altogether

The physics freeze is achieved by exiting from the update procedure (i.e. the physics advance), so some check needs to be performed at the start of the update procedure. Maybe I've misunderstood?

Quote from: Simon on March 14, 2024, 06:40:09 AM
Remove the variables. Later, in the code that needed to read them, instead re-test for dead state.

We need to achieve 6 goals:

1) Define the dead state (no active lems, no animations that need to finish, SR isn't met)
2) Prevent physics updates when the dead state is reached
3) Prevent overshooting into the dead state
4) Show a message informing the player
5) Allow the player to rewind into the live state
6) Allow all of the above to happen again after rewinding

The various checks implemented so far are to make sure that all of the above happens. I tried what you suggested, and it broke goals (5) and (6), also meaning that testing for (3) became impossible.




Meanwhile, the config changed slightly between Exp 3 and 4 (notably, the Freeze option was removed) - it's possible that this had something to do with the settings bug you encountered...

Anyways, I placed your Exp 3 settings file into a fresh copy of Exp 4 and the game behaved as expected: exit to postview for a win, freeze for a loss. I also had difficulty replicating the "one frame overshoot" bug when testing for this as well. I'm not sure why it sometimes happens and sometimes doesn't - this makes it particularly difficult to identify the cause!

WillLem

Happily, whilst checking for possible solutions to the bugs reported by Simon, I discovered that it's not actually necessary to call the update procedure again when nuking; nuke still acts as a "cancel" button when the freeze state is entered.

We also now check for (GameFinished or CheckForUnplayableState) at the same time, at the beginning of the update procedure. This doesn't fix the single frame overshoot, unfortunately, but it seems to make more sense and it might prevent the bug Simon encountered when playing kaywhyn's level (i.e. the game froze when the final lemming exited, even though it was a win).

NOTE: I tried moving CheckForUnplayableState to GameWindow's Application_Idle instead of LemGame's UpdateLemmings. It made no difference; overshooting by 1 frame still happened. So, it's moved back to UpdateLemmings (again, it makes more sense in the context of this feature's wider implementation - ideally, we don't want GameWindow to deal with any of this, but it's currently necessary to prevent overshooting with large frame skips).

I've attached the updated copies of GameWindow and LemGame here.

Methinks, a screen-sharing session is in order to try and get to grips with the bugs. Simon?

Simon

It sounds like you're still setting variables, whose contents then go out of date and you have those bugs as in broken (5) and (6), instead of retesting for dead state whenever necessary.

Move the code for (1) into its own function and call it from several places if necessary. No caching! Re-test every time!

Do you have a public repository? Can you show me git history until here? Reason: That (5) and (6) are even a concern makes me wonder about the architecture. If you don't have history, I'll have to work with those 7,000-line files. Possible, but takes longer to figure out what's going on.

Screensharing: Do you have tooling for it?

-- Simon

WillLem

Happily, whilst checking for possible solutions to the bugs reported by Simon, I discovered that it's not actually necessary to call the update procedure again when nuking; nuke still acts as a "cancel" button when the freeze state is entered. No, it turns out I was wrong about this, the nuke hack is currently still necessary - I'll try to work out a better way to do this.

We also now check for (GameFinished or CheckForUnplayableState) at the same time, at the beginning of the update procedure. This doesn't fix the single frame overshoot, unfortunately, but it seems to make more sense and it might prevent the bug Simon encountered when playing kaywhyn's level (i.e. the game froze when the final lemming exited, even though it was a win).

NOTE: I tried moving CheckForUnplayableState to GameWindow's Application_Idle instead of LemGame's UpdateLemmings. It made no difference; overshooting by 1 frame still happened. So, it's moved back to UpdateLemmings (again, it makes more sense in the context of this feature's wider implementation - ideally, we don't want GameWindow to deal with any of this, but it's currently necessary to prevent overshooting with large frame skips).

I've attached the updated copies of GameWindow and LemGame here.

Methinks, a screen-sharing session is in order to try and get to grips with the bugs. Simon?

WillLem

Quote from: Simon on March 14, 2024, 03:52:26 PM
Move the code for (1) into its own function and call it from several places if necessary. No caching! Re-test every time!

Yep, this is exactly how the feature is implemented. CheckForUnplayableState is used for both the physics freeze and the display message, and it gets its info from various sources including UpdateLemmings and CheckForGameFinished.

Quote from: Simon on March 14, 2024, 03:52:26 PM
Do you have a public repository? Can you show me git history until here?

Bitbucket repository / commits - let me know if you need permissions.

Quote from: Simon on March 14, 2024, 03:52:26 PM
Screensharing: Do you have tooling for it?

Discord preferably, Zoom if there's no other option.

Simon

Okay, I'm cloning the repo. I'll have ~1 hour free now; I'm sitting in Mumble, hop on Mumble if you see this and are free.

I'll make my mind up about Discord or Zoom, both are nonfree with insidious privacy policies.

-- Simon

WillLem

Quote from: Simon on March 14, 2024, 04:07:14 PM
I'm sitting in Mumble, hop on Mumble if you see this and are free.

Busy now unfortunately, later tonight or sometime tomorrow?

Simon

Tonight 21:00 UTC (= 22:00 my local time), yes. Otherwise tomorrow, anything between 17:00 and 24:00 UTC as it suits you.

Yes, you have a method CheckForUnplayableState for the unplayable state, good. Now, across the entire LemGame.pas, remove the variable fShowNoLemsMessage and its getter/setter entirely. As a result, in CheckForUnplayableState, you'll have one fewer condition that gets and-ed together. That should remove those state-caching bugs. Does it introduce any new bugs?

-- Simon

WillLem


Simon

We've reviewed code via Mumble and git.

WillLem had the factoring practically right even before I looked at it. There were already no bugs with the rewind-or-restart message.

We found and fixed the overshooting. The overshooting came from leftover caching information from end-of-game considerations at the start of each physics update. Such end-of-game tests must either happen at the end of a physics update, or outside the physics update. We still have to reconcile the nuke with this; the nuke shall ignore the unplayable state and continue into nuking zombies, then exit. Because of this, we'd rather not tie these decisions to the physics update -- after all, activating the nuke to continue is UI, not physics, and only then it adds the nuke entry to the replay and nuke in the physics.

NL's 2023 architecture tied the exiting to the start of a new physics update. In light of our feature -- stop updating, but don't exit on losing all lemmings -- it looks necessary to change the exiting architecture in some way; still, we should review it with namida on a high level. For now, we renamed CheckForGameFinished to MaybeExitToPostview, to distinguish it clearly from the new end-of-game behaviors that don't exit, and moved it out of the physics update, into the window's main loop, as a public method call to LemGame.

WillLem has tested the interactive game and found no problems with the moved exit-to-postview decision. We haven't yet tested mass replay verification, we'll still have to do that. Likely, it will work because the verification carries its own exiting logic.

Thus, to do:
  • Reconcile nuke-to-exit with the moved exiting logic.
  • Test mass replay verification. Most likely, it's fine.
  • Test other ways to exit LemGame, e.g., pressing Esc, Hyper Speed into death/win.
  • Fix my observed mismatch between default option and game exiting.
  • Stream more playtesting!
  • Review the high-level achitecture of the feature with namida.
-- Simon

WillLem

@Simon - Let me know if you'd like another session on this. I'll have a look at the Nuke code in the meantime and see if I can figure something out. The rest of it is mainly bug-testing.

Not sure how to test/replicate the settings loading bug, but hopefully this will fix itself with the recently modified logic!

Simon

Quote from: WillLem on March 14, 2024, 02:10:47 AM
Quote from: Simon on March 14, 2024, 12:49:10 AM
What happens to this option when we start NL with empty options file/new installation/...?
Same thing as with every other option: a default is set (in this case, Always Exit To Postview).

Consider to change the default to: Exit only if won, freeze if lost. namida agreed that this can be a sensible default.

That will then happen to match what I saw in the settings menu after supplying the exp 3 config. If we want to debug this properly: My hunch is that you hardcoded the defaults in two different places (e.g., once in the options menu, and once in code that loads config files) and the two defaults don't agree.

Quote@Simon - Let me know if you'd like another session on this.

I'll be available tomorrow (Tuesday) evening from 18:00 UTC through 23:00 UTC. I'll also be available Wednesday from 18:00 UTC through 23:00 UTC. I'll sit in Mumble at those times.

Alternatively, at these same times, I can stream playtesting. If you're really fast with the bugfixes, a stream may be more useful than code review in Mumble. But I'll plan for looking at code via Mumble.

-- Simon

WillLem

Quote from: Simon on March 18, 2024, 07:58:43 PM
Consider to change the default to: Exit only if won, freeze if lost. namida agreed that this can be a sensible default

OK, but it would still be good to identify the cause of the bug.

Quote from: Simon on March 18, 2024, 07:58:43 PM
My hunch is that you hardcoded the defaults in two different places (e.g., once in the options menu, and once in code that loads config files) and the two defaults don't agree

Double-checked this, and as far as I can tell, the default is definitely only set once.

Did you get the "NeoLemmix Welcome Screen" when you first loaded "V4 + V3 config"? If so, this would indicate that the settings file has been ignored. If not, then the settings should absolutely have been loaded correctly unless I've missed something. The fact that manually re-setting the option fixed the issue would indicate that it's a loading (rather than saving) problem.

Let's see if the updates we made in the session the other day have fixed the issue - it's a possibility.

Quote from: Simon on March 18, 2024, 07:58:43 PM
I'll be available tomorrow (Tuesday) evening from 18:00 UTC through 23:00 UTC

I'm free now for a bit, I'll jump on Mumble soon.

Best bet for Nuke is to simply exit to postview when the game is frozen and nuke is activated. It's by far the simplest option.

WillLem

@Simon - in case you see this before looking through the code - I checked everything, and the very latest pushed commit works exactly as we want. So - for whatever reason, UserSetNuking seems to be needed wherever it currently appears in the latest commit. Removing it breaks one or more of the nuke/exit-to-postview-related behaviours.

Simon

QuoteSo - for whatever reason, UserSetNuking seems to be needed wherever it currently appears in the latest commit.

I agree! From digging the source: UserSetNuking is indeed part of the physics, not the UI. It's good that it's part of the physics, then it makes it the prime candidate to check in StateIsUnplayable, ShouldWeExitBecauseOfOptions, ..., which all care only about a reached physics state (and maybe options), never about UI.

UserSetNuking tells you if a nuke has been applied from the replay to the physics state, either now or during any earlier physics update. This is exactly what you want to know: Is the nuke active. (And it's not: Has the user double-clicked the nuke button on this main loop iteration.)

There is also ExploderAssignInProgress, which becomes true when UserSetNuking becomes true, but eventually becomes false when physics have run out of lemmings that can receive countdowns. Thus, this ExploderAssignInProgress isn't useful for us.

Let's still have a session tomorrow as planned.

-- Simon

Simon

function TLemmingGame.StateIsUnplayable: Boolean;
begin
  Result := (LemmingsOut = 0)
            and (LemmingsToSpawn = 0)
            and (DelayEndFrames = 0)
            and (fParticleFinishTimer = 0)
            and not (UserSetNuking and CheckIfZombiesRemain);
end;


procedure TLemmingGame.MaybeExitToPostview;
begin
  if fGameFinished then
    Exit;
  if ShouldWeExitBecauseOfOptions then
    Finish(GM_FIN_LEMMINGS);
end;


According to my understanding, this is now all that we need. I've moved your zombie logic into StateIsUnplayable, which is where I really believe it belongs. Let's chat about it tomorrow evening. E.g., it's still not clear if LemmingsOut = 0 is good, or if you need that lengthy addition.

ShouldWeExitBecauseOfOptions isn't touched; it's the same as in your latest commit 6091bd7887.

-- Simon

WillLem

@Simon - thanks for the code snippets, I gave them a try and could only find 1 issue:

In a level with >1 active lem and 1 unavoidable zombie, nuking with >0 active lems lets the nuke countdown and animation play out in full even after the active lem(s) become zombies - this is good. However, once the ParticleTimer is at 0 (so, all debris is finished), the game doesn't exit to postview. It also doesn't freeze or show the minimap message, it just continues updating.

The most recent commit is the only version that fully works (so far, with minimal testing); I've attached a compiled .exe of this version. I'm sure that some of the simplifications to the code that you've suggested can be made, we'll just have to be careful not to accidentally omit something - or, make sure we factor everything in to the simplifications.

Hope this helps. Speak tomorrow, WL :lemcat:

Simon

I found no bugs in your attached executable. Nice! Haven't tested for the config-loading bug yet. Happy to playtest more.

Issues with my snippet: Ah, right, I forgot that we already talked about how NL's nuke doesn't set LemmingsToSpawn to 0; NL's nuke merely prevents the unspawned lemmings from spawning. We must account for this.

Replace: and (LemmingsToSpawn = 0)
with this: and ((LemmingsToSpawn = 0) or UserSetNuking)

It looks strange to test the nuke in two different lines of this expression, but it expresses reasonably succinct: No more lems to spawn and no more zombies about to get nuked.

-- Simon

WillLem

Quote from: Simon on March 19, 2024, 11:05:42 PM
It looks strange to test the nuke in two different lines of this expression

To be fair, in one line we're testing for (true or something else), and in the other line we're testing for (false and not something else). So, they are absolutely two different conditions, good work! :P

Quote from: Simon on March 19, 2024, 11:05:42 PM
Replace: and (LemmingsToSpawn = 0)
with this: and ((LemmingsToSpawn = 0) or UserSetNuking)

Tried this - the result is that the game freezes when it gets to the end of the nuke animation...

Better, but ideally we always want the game to exit to postview in the event of a nuke, regardless of the option. So, we need to factor this in to the two options that sometimes (or always) won't end gameplay. The following chunk fixes the issue and maintains most of your refactoring. If you can see a way to reduce the presence of "UserSetNuking" further, I'm all for it! In the meantime, this revision and the attached .exe seem to be working exactly as we want:


function TLemmingGame.StateIsUnplayable: Boolean;
begin
  Result := (LemmingsOut = 0)
            and ((LemmingsToSpawn = 0) or UserSetNuking)
            and (DelayEndFrames = 0)
            and (fParticleFinishTimer = 0)
            and not (UserSetNuking and CheckIfZombiesRemain);
end;

function TLemmingGame.ShouldWeExitBecauseOfOptions: Boolean;
begin
  Result := False;

  if not StateIsUnplayable then
    Exit;

  if GameParams.AlwaysEndGameplay then
  begin
    Result := True;
    Exit;
  end;

  if GameParams.EndIfLevelPassed then
  begin
    Result := (LemmingsIn >= Level.Info.RescueCount) or UserSetNuking;
    Exit;
  end;

  if GameParams.NeverEndGameplay then
  begin
    Result := UserSetNuking;
    Exit;
  end;
end;

procedure TLemmingGame.MaybeExitToPostview;
begin
  if fGameFinished then
    Exit;
   
  if ShouldWeExitBecauseOfOptions then
    Finish(GM_FIN_LEMMINGS);
end;


Let me know if you find any issues. From some quick testing, this seems to be the one. I've pushed the commit (820872bcb)

I'll look into some catchier names for these methods as well ;P




Attachment removed. Instead, you can get version 5 here.

Simon

I playtested version 5 on stream on Monday, April 1st. This was short notice and I didn't expect anybody to see this in time. Sorry, was busy all week!

Stream is over, a short stream of 1.5 hours.

Recording will remain for 14 days at:
https://www.twitch.tv/simonnaar

-- Simon

kaywhyn

Quote from: Simon on April 01, 2024, 04:38:01 PM
Stream is over, a short stream of 1.5 hours.

Recording will remain for 14 days at:
https://www.twitch.tv/simonnaaar

-- Simon

The link doesn't work. I always get a "content is unavailable" error
https://www.youtube.com/channel/UCPMqwuqZ206rBWJrUC6wkrA - My YouTube channel and you can also find my playlists of Lemmings level packs that I have LPed
kaywhyn's blog: https://www.lemmingsforums.net/index.php?topic=5363.0

Simon

Good catch, thanks. Link is fixed. I had put 3 'a' in a row by mistake.

WillLem: Written summary of the feature testing will follow here tonight. Then you need not watch it all.

-- Simon

WillLem

A somewhat interesting turn of events...

In SuperLemmix, there is an option to hide the minimap and thus reduce the width of the game window. So, out of necessity, I found a way to draw the message to the panel area instead!



This way, we get to see the minimap after all! Happy to backport this to the NL version if we think it necessary?




Meanwhile, I watched some of  the twitch stream @Simon - we can look at not adding the Nuke to the replay if it's done after the unplayable state has been reached. I agree, this is not necessary and looks odd. (EDIT: This was an easy fix, I've committed and pushed this.)

The fall distance ruler, meanwhile, can be changed back to solid colour - no problem. Namida likely just wouldn't merge that part of the code anyway.

Simon

Position of the message: No preference, happy to test the UI again after you put it into the stats.

Habitually, I never look at the minimap and instead scroll/zoom much more on large levels. But others like the minimap.

Yeah, I have the preference for the solid color of the splat ruler. I grant that the rainbow cycling isn't as intrusive as I thought, but it's still more intrusive than I like.




Bugfixes: Had a busy day at work, but you've already caught everything. I'll summarize nonetheless:

Overshooting is fixed entirely in Experimental 5. Good!

The following bug is now fixed, too: In earlier stream, on kaywhyn's Fun Teleportation Race in Space, we saw the Experimental 4 freeze after the last lemming left by exiting. The lemming scored a point and made the score equal to the save requirement. Nonetheless, Experimental 4 didn't exit, and instead showed the message. The Experimental 5 exits here always, which is correct.

All 3 user options do what it says on the tin, with the designed exception that the nuke overrides the user option and exits. No bugs here.

The only bug is what you've already seen: When I nuke during the freeze (to exit immediately without seeing explosions), NL writes the nuke into the replay. When we watch this replay, the red R will stay after the final assignment (because there is the nuke coming up) and, when we reach the freeze, the nuke button will have the white square, but we won't see any nuking.

If you have fixed this by not adding the nuke to the replay: Excellent, I believe that is the cleanest fix. I'll test it. I'll have time to stream playtesting on Friday, Saturday, or Sunday evening.

Do you want a Mumble session with code rewiew? Again, Friday, Saturday, or Sunday evening is good.

-- Simon

WillLem

Quote from: Simon on April 03, 2024, 12:09:34 AM
Position of the message: No preference, happy to test the UI again after you put it into the stats.

It takes a little bit more work for this to behave since it has to override stuff that can still be accessed during the frozen state (i.e. hovering the mouse over a zombie will show "ZOMBIE" in the same area unless we tell it not to), so I'd probably suggest not going ahead with this for NL unless there's demand.

The minimap message works, we've ironed out all bugs, let's leave it as it is.

Quote from: Simon on April 03, 2024, 12:09:34 AM
Do you want a Mumble session with code rewiew? Again, Friday, Saturday, or Sunday evening is good.

Sure, that sounds good. Let's catch up again again nearer the time.

WillLem

Fixed another bug today - we need the Mass Replay Check to finish checking the current level when the unplayable state is reached. Wouldn't have spotted this if I wasn't releasing SLX 2.7 later today! ;P

NL 12.13 Commit 8b2e5a66c applies this fix

Simon

It's now nearer the time. Tonight, Saturday, 19:00 UTC is good.

Quote from: WillLem on April 03, 2024, 01:25:39 PM
Fixed another bug today - we need the Mass Replay Check to finish checking the current level when the unplayable state is reached.

Please build NL with this for me. I'll run 2 or more of Icho's packs through both the stable 12.12.5 and this release, and look for different behavior.

-- Simon

WillLem

Quote from: Simon on April 06, 2024, 09:50:48 AM
Please build NL with this for me

Here you go.

EDIT: Attachment removed due to release of NL 12.13-RC

Simon

Thanks. I'll call that executable nl-2024-04-08.

Icho has sent me his replay coverage for Lemmings United. I've looked at his 208 replays for what is not in the bonus rank of United. These 208 replays are mostly 1 per level, sometimes 2 per level.

I've ran those 208 Lemmings United replays separately through each of the following:
  • NL 12.12.5 stable,
  • nl-2024-04-08 with the option: Always Exit to Postview,
  • nl-2024-04-08 with the option: Exit if Save Requirement Met,
  • nl-2024-04-08 with the option: Never Exit to Postview.
Find attached NL's text output. First findings:

All 208 replays pass (solve their level) in all of the 4 runs.

Your nl-2024-04-08 produces output independent of the 3-way option. In more detail: When I run mass replay verification in the nl-2024-04-08 with the option to Exit if Save Requirement Met, I get the same output (i.e., identical text file) as when I run mass replay verification in the nl-2024-04-08 with the option Never Exit to Postview, or with the option Always Exit to Postview. This is good.

Your nl-2024-04-08 produces different output than NL 12.12.5. In NL 12.12.5, most (all?) replays run for exactly 1 physics update longer than in nl-2024-04-08. It's drudgework to check this claim for all 208 lines, I haven't written a script to verify that claim for me.

For example, NL 12.12.5 produces:
Pacifism 8:  10000 B.C..nxrp   (1882 frames) LvV 0000000000000000 / RpV: 0000000000000000

nl-2024-04-08 produces:
Pacifism 8:  10000 B.C..nxrp   (1881 frames) LvV 0000000000000000 / RpV: 0000000000000000

The only difference here is that NL 12.12.5 runs for 1882 physics updates ("frames") and nl-2024-04-08 runs for 1881 physics updates before both conclude that the replay passes its level.

We can explain this with the reworked end-of-level behavior: NL 12.12.5 must start a new physics update before it can conclude that the map is over. nl-2024-04-08 can test for completion in between two physics updates. Therefore, nl-2024-04-08 needs one physics update fewer. Thus, I believe: There is nothing to worry here. Do you agree?

The high-level result (pass or fail) is identical across NL 12.12.5 and nl-2024-04-08. All levels pass.

This doesn't conclude the testing of mass replay verification yet. Reason: I've only tested solving replays. I haven't tested a big bucket of failing replays, of indeterminate replays (that run for too long after final skill), of weird nukes in the replays, ...

And I have replays for 4 more Icho packs. That's for next week.

-- Simon

WillLem

Quote from: Simon on April 18, 2024, 04:13:39 AM
Therefore, nl-2024-04-08 needs one physics update fewer. Thus, I believe: There is nothing to worry here. Do you agree?

Agreed, this looks so far like everything is behaving as expected; I appreciate how thorough you've been :thumbsup:

I first spotted the MRC bug when implementing this feature in SLX 2.7 - since I'd committed to actually releasing this, I did an MRC check as standard. I checked using a batch of replays some of which aren't level-solvers (I have these ready-to-go for doing quick MRC checks) and MRC is behaving as expected in SLX.

Note, though, that the SLX version of this feature doesn't include the 3-way option. "Exit if level passed" is the general standard behaviour, with Classic Mode providing "always exit to postview" behaviour as its own standard. Not that this should matter for MRC, but it might be worth noting anyway.

Note also, there may well be things that you would know to look out for that I don't. As far as I'm concerned, if the MRC checks every replay successfully and spits out a result, then it's working. I don't tend to check any further than that simply because I wouldn't know what else to look for.

We should catch up again properly about this soon, let me know when you're available.

Simon

If you have failing replays, they too should run until they reach unplayable state, then exit. Look for identical high-level results: You want to see the replay pass in your work-in-progress NL if and only if it passes in NL 12.12.5.

It's theoretically possible for the same replay to be indeterminate in one NL and to clearly fail in the other NL. I don't expect this to happen often, maybe 1 case in 1,000. Even then, both NLs would agree that it's not a winning replay, and that's good. If it happens, I'd examine it case-by-case.

On most (not all) replays (passing or failing), I expect the number of physics updates to be 1 smaller in your WIP NL than in NL 12.12.5. But it's not a necessity; I realize now: When you nuke zombies, we watch the nuke animation in WIP NL, and then the WIP NL won't take 1 fewer, but instead many more physics updates. Thus: If you see a wild difference in physics updates, look in the level for zombies, and look in the replay for nukes.

That's practically what I will do with the remaining packs next week: Compare the high-level results, and investigate by hand the cases where the high-level result differs or where the number of physics updates has not shrunk by exactly 1. Maybe I'll write a script for that.

Quote from: WillLem on April 18, 2024, 11:47:38 AM
We should catch up again properly about this soon, let me know when you're available.

Yes, for code review and to plan the pull request.

I'll be busy this weekend. Which of the following suits you best?

Monday, April 22, 15:00 UTC
Monday, April 22, 20:00 UTC
Wednesday, April 24, 15:00 UTC
Wednesday, April 24, 20:00 UTC

-- Simon

Simon

Quote from: Simon on April 18, 2024, 08:43:36 PM
Quote from: WillLem on April 18, 2024, 11:47:38 AM
We should catch up again properly about this soon, let me know when you're available.
I'll be busy this weekend. Which of the following suits you best?
[...]
Wednesday, April 24, 20:00 UTC

I can't do much more than offer these times. I expected you to agree on one or propose something else.

Friday, 26th, 17:00 UTC I can make. Otherwise it will have to be next week.

-- Simon

WillLem

Quote from: Simon on April 24, 2024, 07:38:16 PM
I can't do much more than offer these times. I expected you to agree on one or propose something else.

Friday, 26th, 17:00 UTC I can make. Otherwise it will have to be next week.

Apologies, I missed your reply! :forehead:

Should be able to make Friday, I'll let you know first thing tomorrow (Thursday) if I can't.

WillLem

OK, I've gone ahead and enabled notifications for this topic so I won't miss another post. Only just realised this is possible!

WillLem

Hi Simon, confirming 1700 UTC tomorrow (Friday). Again,  many apologies for missing your earlier message. See you tomorrow, W

Simon


Simon

I call a replay oneframediff if and only if both of the following hold:
  • During mass-replay verification in NL 12.12.5, it shows the same high-level result (pass, fail, undetermined, ...) as during mass-replay verification in nl-2024-04-08.
  • Mass-replay verification in NL 12.12.5 takes exactly one physics update longer to run this replay than the mass-replay verification in nl-2024-04-08.
See my earlier long post for why I expect oneframediff replays.

Now:
  • All 208 replays in Lemmings United (excluding Bonus rank) are oneframediff.
  • All 120 replays in Lemmings Introduction pack are oneframediff.
  • I have 3 more Icho replay collections that I'll test for oneframediff-ness. Expect more results in the next 0-3 days.
Thus: Excellent so far!

Here is a Bash script that takes two NL output files (pass them as argument 1 and argument 2 to this script) and tells me if the replays are oneframediff:

#!bin/bash
paste "$1" "$2" \
    | sed 's%[^(]* (%,%' \
    | sed 's% [^(]*(%,%' \
    | sed 's% .*$%%' \
    | grep ^, \
    | grep -v "none" \
    | grep -v "TALISMAN" \
    | awk 'BEGIN { FS="," } { print $2 - $3; }'


The script prints the difference (see item 2 in my first enumerated list in this post) in run frames of the replay between the two NL versions. For a oneframediff replay, the script will print 1. If everything is oneframediff, you get a long stream of 1s.

The NL replay results format isn't the nicest to parse. But this script isn't particularly smart either; I should have relied on the consistently-appearing "... frames" in the NL replay format. Instead, the script cuts at open parentheses (, which will occiasionally appear in the level names. The script won't halt on such extra parentheses, but will produce garbage results over 1,000 or under −1,000 that we must examine by hand. All were fine.

-- Simon

WillLem

Quote from: Simon on April 30, 2024, 11:14:42 PM
All were fine.

I've also tested a handful of replays in 12.12.5 and the 12.13 Exp (8/4/24) - confirmed, they all run for 1 frame longer in 12.12.5 and produce the same results.

However, I made a level specifically for the purpose of testing replays, and 5 replays each of which should produce a different result (Pass, Talisman, Fail, Undetermined, Level Not Found - not sure how to test for "Error" tbh). The Replay marked "Should Fail" does indeed Fail in 12.12.5, but returns Undetermined in the 12.13 Exp (8/4/24).

I'm not sure why this is happening, and can no longer compile NL in my copy of RAD without accepting the GR32 PR.

Typical :eyeroll:

I guess I'll get my NL repo updated and just hope that it doesn't touch any of the code in the PR, then I can try to find a fix.

WillLem

OK, successfully updated my working copy NL to GR32 v3. It didn't take long thankfully, and I can now compile.

From a quick glance, I'd say the crux of the matter is here, in GameReplayCheckScreen's very long RunTests procedure:


            while Game.MessageQueue.HasMessages do
              if Game.MessageQueue.NextMessage.MessageType = GAMEMSG_FINISH then
              begin
                if Game.GameResultRec.gSuccess then
                begin
                  if Game.GameResultRec.gGotTalisman then
                    fReplays[i].ReplayResult := CR_PASS_TALISMAN
                  else
                    fReplays[i].ReplayResult := CR_PASS;
                end else
                  fReplays[i].ReplayResult := CR_FAIL;
              end;
            if fReplays[i].ReplayResult <> CR_UNDETERMINED then Break;


Best guess is that because we're now stopping gameplay 1 frame earlier (due to the unplayable state having been reached), it never gets a chance to pass the GAMEMSG_FINISH message.

So, perhaps this might fix it:


            while Game.MessageQueue.HasMessages do
              if (Game.MessageQueue.NextMessage.MessageType = GAMEMSG_FINISH)
                or Game.StateIsUnplayable then <----------------------------------------------------- added this condition
                begin


This should allow the check for a pass/fail to run, and return FAILED correctly. I'll check this now and report back with an updated .exe if it works.

WillLem

#104
No, unfortunately that didn't work.

The code snippet does deal with PASS(TALISMAN)/FAIL/UNDETERMINED, so that's almost certainly what we need to be looking at.

I thought that maybe StateIsUnplayable needs to explicitly pass a FINISH message. But, if we zoom out and examine more of the RunTests method, we can see that this should indeed be happening:


            if (Game.CurrentIteration > CutoffFrame) or
               Game.IsOutOfTime or Game.StateIsUnplayable then <------------------- N.B. this line checks for StateIsUnplayable
            begin
              Game.Finish(GM_FIN_TERMINATE); <------------------------------------- So, the message is passed
              if Game.GameResultRec.gSuccess then
                fReplays[i].ReplayResult := CR_PASS;
              if Game.GameResultRec.gGotTalisman then
                fReplays[i].ReplayResult := CR_PASS_TALISMAN;
              Break;
            end;

            while Game.MessageQueue.HasMessages do
              if (Game.MessageQueue.NextMessage.MessageType = GAMEMSG_FINISH) then
              begin
                if Game.GameResultRec.gSuccess then
                begin
                  if Game.GameResultRec.gGotTalisman then
                    fReplays[i].ReplayResult := CR_PASS_TALISMAN
                  else
                    fReplays[i].ReplayResult := CR_PASS;
                end else
                  fReplays[i].ReplayResult := CR_FAIL;
              end;
            if fReplays[i].ReplayResult <> CR_UNDETERMINED then Break;


I'm going to try adding the fail result to the first of these two code blocks and see what happens.

Suggestions welcome in the meantime.

WillLem

#105
OK, this fixes the problem. We specifically check for (StateIsUnplayable and a non-Success result) at the point that the replay check stops because of StateIsUnplayable, and return a CR_FAIL result:


            if (Game.CurrentIteration > CutoffFrame) or
               Game.IsOutOfTime or Game.StateIsUnplayable then
            begin
              Game.Finish(GM_FIN_TERMINATE);
              if Game.GameResultRec.gSuccess then
                fReplays[i].ReplayResult := CR_PASS;
              if Game.GameResultRec.gGotTalisman then
                fReplays[i].ReplayResult := CR_PASS_TALISMAN;
              if (not Game.GameResultRec.gSuccess) and Game.StateIsUnplayable then  <----- added these lines---
                fReplays[i].ReplayResult := CR_FAIL;                              <-----------------------------
              Break;
            end;


New .exe attached. We'll call this one Exp 13 (1/5/24).

EDIT: Maybe IsOutOfTime should also be factored in, and return a CR_FAIL result?

WillLem

#106
OK, even better.

This time, we factor in IsOutOfTime as well.


            if (Game.CurrentIteration > CutoffFrame) or
               Game.IsOutOfTime or Game.StateIsUnplayable then
            begin
              Game.Finish(GM_FIN_TERMINATE);
              if Game.GameResultRec.gSuccess then
                fReplays[i].ReplayResult := CR_PASS;
              if Game.GameResultRec.gGotTalisman then
                fReplays[i].ReplayResult := CR_PASS_TALISMAN;
              if (Game.StateIsUnplayable or Game.IsOutOfTime) <------------------------------------
                and not Game.GameResultRec.gSuccess then      <--------changed these lines------
                  fReplays[i].ReplayResult := CR_FAIL;        <------------------------------------
              Break;
            end;


This now only returns CR_UNDETERMINED if the level has an infinite time limit. Otherwise, if the time limit has been reached and the result is non-Success, it returns CR_FAIL. Not sure why it wasn't always like this tbh, there are probably reasons. But, this works nicely.

New .exe attached. We'll call this one The 4pm Version 8-)

EDIT: Attachment removed due to release of NL 12.13-RC

Simon

Thanks. Both packs that I tested yesterday (the NL Introduction pack and Lemmings United) produce identical text outputs during mass-replay-verification in your new nl-4pm as they produced in nl-2024-04-08. In other words, in nl-4pm, the replays are still oneframediff, which is good.

Your code (that you posted with nl-4pm) looks reasonable. I've only looked at your snippet, I haven't looked at the codebase. In those GameResultRec that you get there, does gGotTalisman always imply gSuccess? Then I don't see any problems.

I haven't looked at your test cases yet. The idea is excellent. I wouldn't worry much about the "Error" high-level result.

-- Simon

WillLem

Quote from: Simon on May 01, 2024, 06:44:19 PM
does gGotTalisman always imply gSuccess?

As far as I can tell, yes. We first check for Success, then check for a Talisman, wherever it's relevant. So, if we're checking for a Talisman then we have to have reached a Success result.

Simon

Quote from: WillLem on May 03, 2024, 12:41:22 AM
As far as I can tell, yes. We first check for Success, then check for a Talisman, wherever it's relevant. So, if we're checking for a Talisman then we have to have reached a Success result.

That doesn't answer my worry.

Reason: You're not exiting the monster, you're continuing into tests that care for (not success), but not for (not talisman) and overwrite the result that depended on your test for success:

... things that depend on success but don't exit early ...
if (Game.StateIsUnplayable or Game.IsOutOfTime)
    and not Game.GameResultRec.gSuccess then
        fReplays[i].ReplayResult := CR_FAIL;


This is a problem when the supplier of that result struct GameResultRec allows to have (gSuccess false at the same time as g.GotTalisman true). When I wrote "imply", I meant this supplier's side of concerns, not the consuming code that you showed. Your code seems to assume that the supplier always has gSuccess whenever he has g.GotTalisman. I ask you if that assumption is justified.

Your unassuming line would be:

and not (Game.GameResultRec.gSuccess or Game.GameResultRec.gGotTalisman) then

I'll have time for code review tonight, Friday 3rd, 17:00 UTC, or anytime on the weekend.

-- Simon

WillLem

Quote from: Simon on May 03, 2024, 05:11:00 AM
You're not exiting the monster, you're continuing into tests that care for (not success), but not for (not talisman) and overwrite the result that depended on your test for success:

I see what you mean, but this happens in the very next block:


                if Game.GameResultRec.gSuccess then
                begin
                  if Game.GameResultRec.gGotTalisman then


This makes me wonder whether gGotTalisman does indeed imply gSuccess. However, a quick search elsewhere in the codebase doesn't make it clear that these conditions are definitely mutually inclusive.

So, I've committed and pushed your suggested change. You're right, let's not assume anything.

Quote from: Simon on May 03, 2024, 05:11:00 AM
I'll have time for code review tonight, Friday 3rd, 17:00 UTC, or anytime on the weekend.

Weekend is good for me too, shall we aim for 1700 UTC Saturday?

Simon

All right, 17:00 UTC on Saturday is it!

-- Simon

WillLem

#112
12.13 Exp (Build 4098 | Commit 00a392b)



EDIT: Attachment removed due to release of NL 12.13-RC

Simon

A few bugs in the options management, and I shed light on the lingering mismatching config (where we see X in the config menu but the game does Y). We'll start with the easiest.




Wrong default out of box.

1. Freshly extract Exp 6 from your full release.
2. Run NL.
3. Play a level (e.g., the included Mazulems level 1).
4. Fast-forward into death.

Expected: We get the panel message to rewind or quit.
Observed: We exit to postview.

IIRC, we agreed that the default should be to exit if we have won, and show the panel message when we lose. This is the most appropriate default. We had newbies (Darkshoxx in one of his streams) shout in surprise when the level exited on losing.




File contains three options, not one

1. Freshly extract Exp 6 from your full release.
2. Run NL.
3. Open the options menu (click on the cog in the main menu).
4. Press OK to save options.
5. Exit NL.
6. Open in a text editor: ./settings/settings.ini

Expected: One of the lines is for the end-of-level behavior.
Observed: Three of the lines are for the end-of-level behavior, e.g.:

AlwaysEndGameplay=1
EndIfLevelPassed=0
NeverEndGameplay=0


This invites to put nonsensical values in the file, e.g., set them all to 0, or set several of them to 1.

I recommend to store one line, e.g., with values 0, 1, 2. Call it, e.g., ExitToPostviewBehavior, or, if you'd like 0 to mean always-exit and want to keep that nuance in the text file, call it, e.g., StayInGameAfterLemmingsDied, although that's contrived.

Certainly, the user can still put nonsense values even when we use only a single line. E.g., he can text-edit the line to put negative values, or 3 or greater. It's probably okay to do whatever you want then, as long as it means the same as exactly one out of 0, 1, or 2. Maybe fix it under the hood, e.g., convert it to 0, 1, or 3 at options-loading time?

In particular, avoid the following mismatching bug for such nonsense inputs. It's the uncanny bug from months ago:




Mismatch from more than one 1

1. Produce a settings file with the Exp 6. (E.g., do all steps (1-6) from previous bug: File contains three option lines for the end-of-level behavior.)
2. Text-edit the file: Make all three lines end in 1 (none in 0).
3. Run NL.
4. Play a level and lose, e.g., kill all lemmings.
5. You automatically exit to postview.
6. Return to the NL main menu.
7. Open the options menu and go to the interface tab.

Expected: "Always Exit to Postview" is selected, matching our step 5.
Observed: "Never Exit to Postview" is selected.

Certainly, the user had to do something nonsensical to get here, and you can probably treat the garbage as what you like. But be consistent across game and options dialog.

-- Simon

Simon

In Exp 6 the following are all oneframediff (= the good, expected result):
  • All replays for Lemmings United non-bonus ranks,
  • all replays for NeoLemmix Introduction Pack,
  • all replays for PimoLems except for the a single replay for Straight Forward (in the rank called "One", level 7).
  • the 3 of your 5 replays from reply #102 ("Replay Check") that passed or won talismans.
All of these replays (including Straight Forward) test identical (down to the frame) in Exp 6 as they tested in 4 pm. These two NL versions generate byte-for-byte identical verification summary files.

Your undetermined replay from reply #102 is still undetermined, with the same number of physics updates, not oneframediff. That's probably fine? Your level-not-found replay from reply #102 still finds no level, good.

The only eyebrow-raiser is Straight Forward. This is a one-minute level in PimoLems. The replay is attached: This replay wins, then has only one blocker left standing that is never nuked, then runs over the time limit. WillLem, do you think that it's plausible that the NL replay verification takes the same number of physics updates, 1021 updates, in both NL 12.12.5 and Exp 6? We have:

1021 physics updates / 17 physics updates per second
= 60.0588 seconds
= 1 minute + 1/17 seconds
= 1 minute + 1 extra physics update

This sounds reasonable for a one-minute level. I think there is no need to worry here, especially since your undetermined replay from #102 also ran for identical physics update numbers in 12.12.5 and in Exp 6.

-- Simon

Edit 23:10 UTC: Added results for the 5 replays from WillLem's reply #102, a.k.a., "Replay Check" replays.

WillLem

#115
Quote from: Simon on May 06, 2024, 04:16:09 PM
Wrong default out of box.

...

we agreed that the default should be to exit if we have won, and show the panel message when we lose. This is the most appropriate default.

Agreed, "End if Level Passed" should be the default - this has now been fixed, committed and pushed. I think I set it to something else during testing and forgot to switch it back. Good catch.

Quote from: Simon on May 06, 2024, 04:16:09 PM
File contains three options, not one

...

Expected: One of the lines is for the end-of-level behavior.
Observed: Three of the lines are for the end-of-level behavior

...

I recommend to store one line

Yes, very good shout.

The option is now stored as "ExitToPostviewOption" with 3 possible strings, one for each option ['Always', 'IfLevelPassed', 'Never']. If the string in the config file is one of these 3, the correct option is loaded. If it's is anything other that these 3 (so, blank or nonsensical), we simply load the default. I believe this should also address the third of the concerns in Reply #113. Committed and pushed.

EDIT - Also, if the user writes multiple lines into the text file for the same option, existing behaviour is that only the first one is loaded & handled, and the rest are discarded on saving. Perfectly elegant, does the job.

Here's V6-A which implements these fixes (to Commit a2610dc). Meanwhile, it sounds as if the MRC is behaving as we'd like (apart from the bug you reported here, which I'll probably fix myself in SLX and can always port across to NL at a later date).

I'm thinking we're ready for the PR now. Do let me know if there's any other last-minute tweaks you'd like to get sorted before we next review.

EDIT: Attachment removed due to release of NL 12.13-RC

Simon

Quote from: WillLem on May 08, 2024, 12:22:28 AM
"End if Level Passed" should be the default

3 possible strings, one for each option ['Always', 'IfLevelPassed', 'Never'].
blank or nonsensical), we simply load the default.

multiple lines [...] only the first one is loaded & handled

Yes, all of this sounds great. Thanks!

QuoteMRC is behaving as we'd like (apart from the bug you reported here

I agree, it all feels correct.

I agree, we shouldn't worry about that bug (= talismans are not reported if we verify the replays under another username) because it sounds unrelated to our exit-to-postview work. I've already worked around it by changing my username.

QuoteThe option is now stored as "ExitToPostviewOption"

With text strings as values (they are better than my earlier-suggested 0, 1, 2), I'd now name the option in the text file "ExitToPostview" (without the redundant "Option" at its end; everything in the file is an option). This is not a big worry.

QuoteHere's V6-A which implements these fixes (to Commit a2610dc).
ready for the PR now. Do let me know if there's any other last-minute tweaks

I can spare 2-3 hours tonight for testing; I'll post findings before UTC midnight.

After that, I'll be busy the next 4 days.

-- Simon

Simon

You still have 3 internal variables, which can be a similar source of bugs as the 3 exposed options from the text file.

For example, here:

if not MatchStr(sOption, sPossibleOptions) then
    EndifLevelPassed := True
else begin
    AlwaysEndGameplay := (sOption = 'Always');
    EndIfLevelPassed := (sOption = 'IfLevelPassed');
    NeverEndGameplay := (sOption = 'Never');
end;


... you risk a bug whenever this runs while ((AlwaysEndGameplay is true) or (NeverEndGameplay is true)) and you run into the 'if'. I don't know if the code ever runs in that case. I know that it parses options during startup. But if it does run at another time, you'd again violate the invariant that exactly one of the three is true. This is a maintenance trap: The option-loading code now has a silent requirement that it may only be called once.

One way to avoid that worry is to remove the if/else entirely and write:


AlwaysEndGameplay := (sOption = 'Always');
NeverEndGameplay := (sOption = 'Never');
EndIfLevelPassed := not AlwaysEndGameplay and not NeverEndGameplay;


But even better: Track the option internally with one variable. Then it's absolutely impossible to have more than one of them true, it'll be obvious anywhere in the program and to any maintainer. Consider an enumeration; Delphi 6 should support them:

type TExitToPostview = (
    etpAlways,
    etpIfPassed,
    etpNever
);
var ExitToPostview : TExitToPostview;

if sOption = 'Always' then
    ExitToPostview := etpAlways;
else if sOption = 'Never' then
    ExitToPostview := etpNever;
else
    ExitToPostview := etpIfPassed;


I'll sit in Mumble for the night from 19:00 UTC to around 22:00 UTC. Catch me in Mumble spontaneously; otherwise, we'll review next week.

-- Simon

Simon

I tested the Exp 6-A. Everything looks good:
  • Default option out-of-box is exit if save requirement met.
  • All three choices of the option behave during play as they should.
  • Text file contains one line only, which loads back as it should.
  • Mass replay verification produces byte-for-byte-identical files like the Exp 6, at least for Lemmings United and PimoLems. Haven't re-tested the others yet with the Exp 6-A.
In the end, it's up to you how much of today's feedback (my 2 earlier posts today) you still want to implement.

I can't estimate, e.g., how hard it is in the NL source (mostly in the code for the options dialog) to turn the 3 bools into an enum. Maybe it's sensible to address only the worry ("One way to avoid that worry is to remove the if/else entirely and write:").

If you touch the source at all before making the PR, then I'd rename the text-file option from "ExitToPostviewOption" to "ExitToPostview". It's easy now to rename it (there are 4 occurrences in the code), it'll be harder after release when people have the old string in their files.

Remember to rebase/to add the recent commits to the ExitToPostview upstream branch when you make the PR. They're still sitting on top of the Graphics32 update, but that was fine for me today to review.

Happy to schedule the evening on Monday, 13th for a review session in Mumble. Anything from Monday, 16:00 UTC through Monday, 22:00 UTC I can make.

-- Simon

WillLem

Quote from: Simon on May 08, 2024, 07:17:52 AM
I'd now name the option in the text file "ExitToPostview" (without the redundant "Option" at its end; everything in the file is an option)

Agreed, this is now done. Committed and pushed.

Quote from: Simon on May 08, 2024, 05:43:39 PM
You still have 3 internal variables, which can be a similar source of bugs as the 3 exposed options from the text file.
...
Track the option internally with one variable ... Consider an enumeration

Quote from: Simon on May 08, 2024, 08:35:17 PM
I tested the Exp 6-A. Everything looks good:
...
In the end, it's up to you how much of today's feedback ... you still want to implement.

This might be a case of "if it ain't broke, don't fix it!" The options all play nicely now, and I've tested thoroughly for nonsensical values, setting all three to true, etc. The codebase already has it built-in to ignore all but the first declaration of the option in settings; this should be sufficient for preventing the option from being set to two things at once.

And OK, there's some risk that it might be set elsewhere during gameplay, but tbh I can't see how that would happen. If the user changes the options between levels, for example, it's saved correctly. If they change the option via the text Editor, they must then re-load NL for it to acknowledge the option change (or at least keep using NL until the options are re-loaded, at which point it will be correctly set). AFAIK, default options are only ever explicitly set upon loading a brand new copy of NL, and never again after that.

I understand your desire for the singular option, but there are benefits to keeping them separate (and, identified by strings rather than numbers). My instinct on this one is, let's keep it as it is at least for now.

That's not to rush things, it's genuinely what I think is best.

Monday is fine for another Mumble sesh. I'll need help doing the PR anyway, and we can go over anything else that might need to be looked at first. Speak to you then if not before, W.

Simon

I'm sitting in Mumble. Join anytime.

BRB in 20 minutes (17:30 UTC). Back.

-- Simon

Dullstar

Quote from: WillLem on May 09, 2024, 12:09:56 AM
I understand your desire for the singular option, but there are benefits to keeping them separate (and, identified by strings rather than numbers). My instinct on this one is, let's keep it as it is at least for now.

I agree with Simon here regarding the use of an enum. There *should* always be one and exactly one of the options active, but an enum will consistently enforce it across the entire codebase. Even if everything's correct now, this has maintenance benefits long-term. It shows that it's intended that exactly one of the options is true, and you won't have to worry about any bad behavior as a result of attempting to run multiple options at once (sure, maybe setting them all to true doesn't misbehave for now... until some other change happens and it no longer gets handled gracefully but nobody checked).

You can still identify them with strings in the options file, and the enum values have names so it's not like you need to remember that "OptionA" is 0, "OptionB" is 1... keeping track of that is the compiler's responsibility.

WillLem

Quote from: Simon on May 13, 2024, 05:11:04 PM
I'm sitting in Mumble. Join anytime.

Hi Simon, I've ended up being super busy this evening with IRL stuff. If you're free another evening this week I'll make sure to get it in the calendar. I could try to do the PR by myself, but would much prefer to do it with your guidance.

Quote from: Dullstar on May 13, 2024, 07:55:33 PM
I agree with Simon here regarding the use of an enum. There *should* always be one and exactly one of the options active, but an enum will consistently enforce it across the entire codebase. Even if everything's correct now, this has maintenance benefits long-term.

OK, I'll give it a try. Can't promise anything, because I currently have no idea what an enum is, let alone how it works or how to properly implement it. With that said, lack of prior knowledge hasn't exactly stopped me so far ;P

I'll aim to get something done before Simon and I next meet.

Simon

All right, leaving Mumble and going to bed.

Quote from: WillLem on May 13, 2024, 09:58:26 PM
another evening this week I'll make sure to get it in the calendar.

Wednesday, 16th, from 16:00 UTC is good. I'll have time until 21:00 UTC. Please choose a definite starting time beforehand, and I'll confirm it.

-- Simon

Dullstar

Quote from: WillLem on May 13, 2024, 09:58:26 PM
OK, I'll give it a try. Can't promise anything, because I currently have no idea what an enum is, let alone how it works or how to properly implement it. With that said, lack of prior knowledge hasn't exactly stopped me so far ;P

The syntax will of course vary by language, but I think an example will be the easiest way to show it:

Note that this example isn't Delphi, as it's not a language I've worked with. This is more about the concept of enums in general, not how to write them in your language of choice.
enum Colors
{
    Red,
    Orange,
    Yellow,
    Green,
    Blue,
    Purple
}


Basically we've just created a new type* that can only hold these values**. Internally, they're probably just ints where Red = 0, Orange = 1... Purple = 5, but code that wants a Color doesn't need to care about that. When saving/loading from a file, we can convert them to/from strings as needed; some languages offer features to do this automatically, but if not, well, if we had to do some if/else to read/write anyway without the enums, we can do it with enums too. Many languages allow you to explicitly choose an underlying type and assign your own values, but there will usually be sensible defaults.

*Well, symbolically anyway. Languages vary in whether their type system considers enums to be different from their underlying types.

**Among languages that consider it to be a separate type, they also vary in whether or not you can force it to convert a value from the underlying type to the enum type even if that value isn't part of the enum, but fortunately in many (most?) languages it's difficult to do this accidentally.


---

Well, there's also the somewhat more complex Rust enums which basically merge unions and enums into one feature, but let's not worry about those.

Simon


WillLem

Quote from: Simon on May 13, 2024, 10:42:22 PM
Wednesday, 16th, from 16:00 UTC is good. I'll have time until 21:00 UTC. Please choose a definite starting time beforehand, and I'll confirm it.

I assume you mean Weds 15th? I should be able to make 1900 GMT (so, 1800 UTC?) that day, possibly even earlier. I've added it to the calendar. W

Simon

Yes, Wednesday, 15th, not 16th, sorry. All right, 18:00 UTC it is, i.e., 19:00 English daylight savings.

See you tomorrow!

-- Simon

WillLem

Quote from: Simon on May 14, 2024, 06:46:50 PM
Yes, Wednesday, 15th ... 18:00 UTC it is, i.e., 19:00 English daylight savings.

See you tomorrow!

Confirmed, see you then! :lemcat:

WillLem

#129
NL 12.13 Exp V8 (commit a9709ba)

EDIT: Attachment removed due to release of NL 12.13-RC

Simon

We refactored the three bools into one enum.

I tested the Exp 8 (from WillLem's previous post) and it all looks good. The default behavior is to exit if save requirement met. Options load and save correctly, and behave accordingly during play. Icho's Lemmings United mass-tests identically in this Exp 8 as it did in the Exp 6, i.e., everything looks good here, too.

Pull request is up for namida to review. WillLem and I are both happy to give high-level overviews of the source changes; namida: Ask here or in IRC or anywhere. The diff is moderately large: 9 files changed (7 source files and 2 new images), 211 insertions, 61 deletions, but overall it stayed surprisingly manageable.

Thanks, WillLem, for your patience and for ironing it all out!

-- Simon

namida

I've merged the pull request (along with the GR32v3 update, which at a quick check had one minor, purely visual bug that I quickly fixed but otherwise works fine). It will be more difficulty than it's worth to backport this to 12.12; instead I'm going to try and wrap up the remaining issues for 12.13 and move that into RC soon.
My projects
2D Lemmings: NeoLemmix (engine) | Lemmings Plus Series (level packs) | Doomsday Lemmings (level pack)
3D Lemmings: Loap (engine) | L3DEdit (level / graphics editor) | L3DUtils (replay / etc utility) | Lemmings Plus 3D (level pack)
Non-Lemmings: Commander Keen: Galaxy Reimagined (a Commander Keen fangame)

Simon


namida

My projects
2D Lemmings: NeoLemmix (engine) | Lemmings Plus Series (level packs) | Doomsday Lemmings (level pack)
3D Lemmings: Loap (engine) | L3DEdit (level / graphics editor) | L3DUtils (replay / etc utility) | Lemmings Plus 3D (level pack)
Non-Lemmings: Commander Keen: Galaxy Reimagined (a Commander Keen fangame)