[?][BUG][PL] Sound for failed assignment plays for successfully queued shimmier

Started by Simon, May 13, 2025, 01:29:00 PM

Previous topic - Next topic

0 Members and 2 Guests are viewing this topic.

Simon

NL-CE 1.0.1.

Repro:

  • Have a level with climbers, shimmiers, and a ceiling to which we can climb.
  • Make a lemming climb the wall.
  • Select shimmier in the panel.
  • Near the ceiling (but before he reaches it), hover over the climber and click.
  • You'll hear the sound for a failed assignment.
  • Wait for the climber to reach the ceiling.
  • The lemming will shimmy. Edit: You'll also hear the sound for a successful assignment.

Observed: Sound for the failed assignment in step 5 and successful shimmying in step 7.

Expected instead: No sound in step 5 when we click. (Reason: The click produces an effect, it queues a shimmier, therefore it was successful.) Still successful shimmying in step 7.

-- Simon

WillLem

Do you hear the successful assignment sound when the lemming begins shimmying? I'm afk at the moment but, from memory, I think that queued assignments play the successful assignment sound when the skill action begins.

Simon

Yes, the sound for a successful assignment plays when the shimmying begins (in step 7).

-- Simon

namida

The problem with this is that a queued skill may still end up failing (if the queueing expires before the skill is able to be assigned). The only way around this would be to simulate this duration ahead every time a skill is assigned - which is doable, and code to simulate ahead already exists, but it is not quite 100% accurate; in particular, simulations do not take into account other lemmings besides the one being simulated, for the most part (one exception is that blockers still affect the simulated lemming), so for example, in the case of the Climber->Shimmier transition, this may be detected as a pass because the climber reaches the ceiling within the timeframe, but the assignment ends up failing because a bomber that was about to explode (which the simulation does not account for), does so (and removes the ceiling) inbetween the shimmier being queued and the time where it would have taken effect. It would still be an improvement over status quo, but there are edge cases it would miss (and depending how it's implemented, it may end up not playing a sound at all in these cases).
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

Ah, then queuing is more prevalent than I thought, and most queues fizzle without effect. If we want to fix the OP behavior, we'll have to distinguish now what will fizzle, and we've never had to distinguish before.

I have no good suggestion (other than your simulation) that fixes this exact problem and keeps all other failed sounds.

The only time I need explicit notification for assignment failure is: The click fails to assign because that new assignment would overwrite an existing same-tick assignment to different lemming. I don't need sounds for any other failed assignments.

In this light, I'm even beginning to think that the other failed assignment sounds are a problem. I didn't realize this before. For the same-frame problem, I'd like explicit feedback that the new assignment fails because of the existing same-frame assignment. The CE 1.0.1 fail sound doesn't tell me that; it merely tells me that there was some whatever reason to fail.

-- Simon

WillLem

IMHO, the bug here is not the assign fail sound (which correctly plays because the skill cannot be assigned on this frame), but the queued assignment itself. It's always felt like buggy behaviour to me (although I do generally choose to play with it toggled on for the few times it's useful vs. the times I have to rewind and clear the queue).

To be clear, I'm not suggesting that we get rid of queued assignments (just in case anyone is thinking that's what I'm getting at), but rather that we don't necessarily need to do anything here.

Reasoning:

1) The assign fail sound is not a bug; the assignment cannot happen on this frame, so feedback is correct. I see no reason to mess with that tbh.

2) When the assignment happens, the assignment sound plays. The user is provided with feedback correctly in both the attempted assignment (where it fails) and its eventual happening (where it happens only because queued assignments are a thing); all good. If anything, the fact that both sounds are played adds further clarification to exactly what's happened; the queued assignment is a courtesy to prevent the player having to click again at the point that the assignment becomes possible. In that light, the previous click (that created the queued assignment) is arguably irrelevant, at least for the purposes of providing immediate feedback.

Perhaps for shimmiers specifically, we could simply treat the assignment as not failed if it's made within the allowed climber>shimmier range, and play the assignment sound at the point of attempt. The only issue with this is that ideally the skill would also be deducted at the point of assignment, and since that messes with physics & replay timing, it would render CE incompatible with NL 12.14 content. So, we probably shouldn't mess with that either.

If anything, the only thing that may need to be addressed is your point here:

Quote from: SimonFor the same-frame problem, I'd like explicit feedback that the new assignment fails because of the existing same-frame assignment. The CE 1.0.1 fail sound doesn't tell me that; it merely tells me that there was some whatever reason to fail.

Perhaps, along with making the assign fail sound optional, we could also allow the user to specify different sounds for different assignment fail types. It's one of the reasons I want to play the steel sound when the assignment fails because steel, but this was ruled out as undesirable. If we allow a fully customisable sound set, we can potentially allow different sounds for different fail types.

The problem would then be determining why the assignment has failed code-side: this is not trivial. I'd probably need to see significant community support for the idea before embarking on it. There's already plenty to be getting on with CE-wise so it'd be a fairly low priority at present.

namida

One thing to keep in mind is that the Shimmier is somewhat unique in having situations that require frame-perfect assignments. Nepster designed it this way (I did the same with eg. the Slider for consistency); queueing was already a thing at the time, so I believe Nepster felt it was acceptable to require the frame-perfect assignments precisely because queueing gave a generous window to "assign" it anyway.

For SLX, I would even go as far as suggesting changing this behavior - allow Climber->Shimmier whenever the climber is less than one animation cycle away from hitting his head (internally: there is at least one solid pixel somewhere between (lemming Y - head check offset - distance climber ascends in one animation cycle), and (distance climber ascends in one animation cycle - 1) below that). Slider->Shimmier is trickier, but perhaps during the last few frames, or alternatively, introduce a "dangler" state which it can be assigned during. Obviously such a change is out of the question for NLCE.

Quote from: WillLem on May 16, 2025, 09:03:51 PMThe problem would then be determining why the assignment has failed code-side: this is not trivial. I'd probably need to see significant community support for the idea before embarking on it. There's already plenty to be getting on with CE-wise so it'd be a fairly low priority at present.

It's not trivial, but I don't think it's quite as hard as you're thinking. From memory: All attempts to assign skills go through functions that return either True or False to indicate if the assignment passed or failed. So, the way I'd approach this is - instead of returning a Boolean, make these functions return an enum, with values for the various failure types (and one for success, of course). You could also use integers and constants, but an enum is a bit more bug-resistant, especially if for whatever reason you want to change the order of the possible values later.

I'd generally approach a change like this by first making the new code use the Enum and return the correct values of it (so, I'd replace all cases of "Result := false" in the relevant functions with the specific Enum value rather than a "generic fail"), but otherwise do exactly the same thing the old code did - essentially the Enum is just a fancy Boolean where one value means true, any other value means false. Once that works, I start writing code that actually does something with the new Enum values. Although this would change the physics code, assuming there's no bugs it won't change the actual physics (as it doesn't change what's reported as a success vs failure, it only adds more detail to the failures), so wouldn't be an NLCE-incompatible change.
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 May 17, 2025, 07:17:50 AMFor SLX, I would even go as far as suggesting changing this behavior - allow Climber->Shimmier whenever the climber is less than one animation cycle away from hitting his head
...
Slider->Shimmier is trickier, but perhaps ... introduce a "dangler" state which it can be assigned during.

Both of these ideas are already implemented in SLX. Shimmiers can now be assigned to Climbers at any time (even if the shimmy action itself would fail, it remains useful as a cancelling/turning action), and the Dangler state was one of the earliest additions to SLX (implemented primarily to aid Shimmier assignability to Sliders in Classic Mode). Interesting that you'd come up with the exact same idea, up to the point of giving the state the same name; it certainly seems the most natural thing for the Slider to do.

Quote from: namida on May 17, 2025, 07:17:50 AMIt's not trivial, but I don't think it's quite as hard as you're thinking ... the way I'd approach this is - instead of returning a Boolean, make these functions return an enum, with values for the various failure types (and one for success, of course).

Yes, an enum or record is probably the best way go with this for sure, but I'd still want to see significant support for the idea before starting with it. Having many new sounds can be off-putting for some users, even if those sounds provide necessary clarification and feedback.

I suppose for future-proofing and code-streamlining purposes, the enum could be implemented anyway (even without the additional sounds). If doing this, I'd probably want to completely refactor the assigning of skills in general. Currently, there are 3 (or 4?) separate methods/functions that determine the assigning of a skill. I'm sure these have become necessary as skill assignment has become more complex (due to the replay system, highlit lems, etc), but the code has become more cumbersome to work with as a result. The assign fail sound, in particular, has had to go through several revisions and re-writes to get it to play nicely with the existing skill assignment system.

Ideally, a single method would take care of the whole thing, perhaps with functions to determine things like lemming state, priority, etc. But the result of any enum related to skill assignment should be easily accessible from a single place. So, there would be a lot of work to do. It's one for the long-term to-do list.

namida

You mentioned making these sounds customizable was a goal. If so, the default could be the same as the current hardcoded setup (or multiple default profiles can be offered, similar to what NL does for hotkeys).
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

There are 5 main reasons an assignment fails:

1) Same-frame assignment whilst in Replay Insert mode
2) Lemming already has the skill, or is already performing the skill
3) New skill is incompatible with current skill action
4) Lemming is adjacent to steel
5) Lemming is adjacent to incompatible OWW

For the purposes of audio feedback, I'd suggest that 2 and 3 could keep the same, current assign fail sound. 4 should be a steel "dink", 1 and 5 could each be something else entirely.

That's 4 sounds. Let's say steel and OWW could have the same sound to get it down to 3 (I can't remember the reason people didn't want the steel sound, but we could just give it the same sound as OWWs, whatever that ends up being).

This should add further clarification to what's going on when an assignment fails. The "incompatible with current action" sound would, to some extent, become a "this skill assignment has entered the queue" sound by proxy (and, in fact, that's exactly what's happened). As long as it's different from "same frame" and "steel/OWW", this provides enough user feedback and addresses the original bug, albeit not exactly as suggested.

Conversely, if we remove the sound altogether for successfully queued assignments, we're back to the problem that the assign fail sound is there to fix in the first place, i.e. no feedback at all, no successfully assigned skill, click seems to have done nothing. The dual effect of "fails here" and "is eventually assigned here" each providing audio feedback works much better, and that's what we already have anyway.

Simon

Quote from: WillLem on May 18, 2025, 12:40:08 PMThe dual effect of "fails here" and "is eventually assigned here" each providing audio feedback works much better

Better in what way? The click succeeds; it queues, which is what I want. Before, successful queuing played no sound, which was odd indeed. But now, the success sounds like failing, and it feels buggier than before, not better.

If the queue fizzles due to time-out ... well, should it ever time-out in the first place? The queued shimmer should stay in the queue until the climber is at a ceiling. If he falls/hoists, then we can clear the queue.

QuoteThe only issue with this is that ideally the skill would also be deducted at the point of assignment, and since that messes with physics & replay timing, it would render CE incompatible with NL 12.14 content. So, we probably shouldn't mess with that either.

I assume "point of assignment" is the time of the click mid-climb, not the physics update when he starts shimmying.

It's conceivable to lie in the skill panel, and show fewer skills on the skill button even though they're unspent in the physics. Can't tell from the outside how elegant that would be. Can be fertile ground for bugs.

You can queue shimmier to a climber. What other skills can you queue? Is the queue part of the physics? If it's not, we should be able design it so that it doesn't feel like a bug, yet complies with the NL physics.

Quote1) Same-frame assignment whilst in Replay Insert mode
1 and 5 could each be something else entirely.

Hmm. Yes, better feedback for same-frame assignment should give unique feedback. I don't believe sound alone will be enough. But that's for another topic.

-- Simon

namida

Quote from: Simon on May 18, 2025, 11:20:32 PMYou can queue shimmier to a climber. What other skills can you queue? Is the queue part of the physics? If it's not, we should be able design it so that it doesn't feel like a bug, yet complies with the NL physics.

Any skill can be queued, and it is not part of the physics. If the user attempts to assign a skill that is not currently a valid assignment to the lemming in question, it gets queued for roughly half an in-game second, and if the lemming becomes able to be assigned the skill during that time, it gets assigned at this point. As far as physics (and replay data) are concerned, the assignment didn't happen until the frame on which it was a valid assignment.

Shimmiers are somewhat unique in that some transitions from other states (specifically, Climber and Slider) to Shimmier only have a one-frame window to be assigned. My understanding is that Nepster implemented them this way on the basis that this queueing ability would make these assignments non-problematic, even in real-time play.
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: Simon on May 18, 2025, 11:20:32 PMBetter in what way? The click succeeds; it queues, which is what I want. Before, successful queuing played no sound, which was odd indeed. But now, the success sounds like failing, and it feels buggier than before, not better.

It's probably a matter of opinion to be honest. I think it's good that we hear the assignment audibly fail, even if it gets queued. If we have a choice between nothing and an audible fail, the latter is preferable. It's difficult to say exactly what's better about it, I suppose it just makes the game more tactile.

There may be a way to see whether the queue frame is >= 1, and if so then play a different sound as opposed to the regular fail sound (assuming we figure out a way to "know" the reason for the failed assignment).

Quote from: Simon on May 18, 2025, 11:20:32 PMIf the queue fizzles due to time-out ... well, should it ever time-out in the first place? The queued shimmer should stay in the queue until the climber is at a ceiling. If he falls/hoists, then we can clear the queue.

At present, the queue length can be customised up to a maximum of 20 frames. I guess we could make it a maximum of 100 frames if you'd prefer a longer queue time?

Quote from: Simon on May 18, 2025, 11:20:32 PMIt's conceivable to lie in the skill panel, and show fewer skills on the skill button even though they're unspent in the physics. Can't tell from the outside how elegant that would be. Can be fertile ground for bugs.

I'm 50/50 on this. On the one hand, if the game is paused at the point of assignment and we assume that the queued skill will be successfully assigned, the pre-deducted skill is more useful UI to the player. On the other, yeah this sounds like bug city. I suppose we could show the number in a different colour for the duration of the queue; if the skill doesn't get assigned, revert colour and skill count. If it does, simply update the colour to default (the skill count having already been deducted as per your suggestion). This way, there's more state tracking going on and debugging becomes easier.

Some interesting ideas here, but I still think that this particular part of the UI (i.e. skill assignment feedback) is good enough as it is. Perhaps this is one to sit with for a while and see if it comes up regularly enough to be a problem worth solving. Better and more specific ideas may come up later.

namida

Quote from: WillLem on May 20, 2025, 08:45:24 PMThere may be a way to see whether the queue frame is >= 1, and if so then play a different sound as opposed to the regular fail sound (assuming we figure out a way to "know" the reason for the failed assignment).

From memory, what I recalled was that the assignment always gets queued as long as the player actually has at least 1 of the skill they're trying to assign.

I wasn't 100% sure on this, so I checked the code and did some testing. Note that this is based on 12.14.0, not NLCE or SLX. It turns out there are three criteria - and "having at least 1 of the skill" is not one of them, which can be confirmed by trying to assign a skill you have none of, then gaining one via a pickup skill during the queue timeframe. The criteria are:
- There's actually a lemming to try and assign to (ie: the assignment isn't failing because there's no lemming under the cursor). Common sense and I don't think NL would get as far as even trying to do this, but this is explicitly checked again when queueing.
- The skill must not be a permanent skill
- The lemming must not be neutral or zombie

Otherwise, the skill will be queued. I would actually argue that, with deneutralizers existing (plus the pickup skill edge case being a thing), the permanent skill and neutral cases should be allowed to queue too; keeping only the "lemming doesn't exist" or "lemming is a zombie" cases. On the other hand, we can also add the cases of "lemming has been removed" (yes, this is actually not checked currently - though of course, the queued assignment will always expire without happening in such a case, if you can even find a way to queue such an assignment in the first place), "lemming is exiting", "lemming is burning or splatting", and "lemming is drowning AND the skill is not Swimmer".

Note that "skill is a permanent skill the lemming already has" (or is blocked due to floater/glider exclusivity) is a borderline case as to whether we can exclude it or not. Technically, that assignment CAN become valid, due to the skill removers. However, unless the remover's trigger area is only 1px wide, the assignment would just immediately get eaten again by the remover, and thus be wasted. But technically, the skill does get assigned successfully. Nonetheless, I think I'd lean towards the practical side here, and say that this case could be blocked from queueing too.

All other cases, you'd need to actually simulate ahead (and keep in mind, simulations are not 100% accurate, especially when it comes to interactions between multiple lemmings or the effects of their ongoing skills, not to mention it can't predict other assignments the player might make to other lemmings during that time) in order to determine if the queued assignment will succeed or not.

I think ultimately - a solution needs to be found that relies only on what can be determined at the time the player attempts to assign the skill, and not make assumptions either way about what happens afterwards.

Keeping in mind both this restriction, and that queueing is basically universal, perhaps the approach to take is to visually indicate that the lemming has a queued skill in some way (whether it's showing the specific skill, or just a general "queued skill" indicator that could be eg. an icon displayed above the lemming's head similar to the countdown digits / highlight icon). We then have three sounds - "success", "queued", and "failed". Failed would play if either (a) one of the conditions to not even queue the assignment is met, or (b) when the queue duration runs out, if the skill did not successfully assign at any point during it. Otherwise, if the assignment doesn't succeed immediately, the queued sound is played. Maybe it's also worth considering different sounds for immediate success vs queued success.

This does raise a difficulty with the situation-specific fail sounds. If the fail sound is only played at the end of the queue duration, what if the fail reason was different on different frames? Does the frame you tried to assign it on take priority, or the one the queue expired on; or is it a matter of certain reasons having higher priority and the sound is based on the highest-priority one encountered? Or perhaps which one occurred on the most frames during the queue time? I'm not really sure what the right answer is here - perhaps if anything it's a good reason to just rule out the situation-specific sounds altogether.
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)