Author Topic: [DISCUSSION][PLAYER] Climber mechanics  (Read 6860 times)

0 Members and 1 Guest are viewing this topic.

Offline namida

  • Administrator
  • Posts: 12399
    • View Profile
    • NeoLemmix Website
[DISCUSSION][PLAYER] Climber mechanics
« on: February 13, 2016, 06:08:23 PM »
So, as mentioned in this bug report, I've noticed in general a lot of inconsistencies in regards to how climbers behave.

Essentially, these all come from two simple facts:
1. The climber checks for the top during the first 4 frames of his animation, and moves upwards on the rest.
2. Both of these have a terrain check, but they get triggered at different times.

In general - what happens is that, when a climber meets an overhang at the same height, how many frames it takes before he falls depends on whether that overhang is at the top of the climb or not. I've attached a level to illustrate this. The level has climbs with overhangs - in both "overhang is at top" and "overhang is in middle" varieties - at various heights from 7 pixels to 22 pixels.

A summary of the current behaviour, in mostly-human-readable language, and with stuff relating to the implementation of gimmicks stripped out, is:

(Keep in mind - the climber's X position is one pixel inside the wall, similar to how when walking the lemming's Y position is one pixel inside the ground.)
If the Climber animation is on frame 0, 1, 2 or 3:
>> Check for no solid pixel at the climber's X position, and (7 pixels + current frame number) above the climber's Y position
>>>> If no solid pixel is found, check for solid pixels one pixel behind the climber, and (6 pixels + current frame number) above the climber's Y position; if this is not the first cycle through its animation, also check 5 pixels above*; just remember the result for now
>>>> Move the lemming to the top of the wall
>>>> If a solid pixel was found before, move the lemming down one pixel, turn around, change to a faller, and move forward one pixel (in new direction; ie: away from the wall)
>>>> Otherwise, change to a hoister
>> If there was a solid pixel there (remember - this is the wall itself, not an overhang), do nothing except continue the animation

* Skipping this check on the first cycle is to allow them to climb through an overhang at 6 pixels from the ground.

If the Climber animation is on frame 4, 5, 6, or 7:
>> Decrease the lemming's Y position by 1
>> Check for any of the following:
      - Solid pixel 1 pixel behind the lemming and 7 above it
      - Solid pixel 1 pixel behind the lemming and 6 above it, if this is frame 4 of the first cycle*
      - A blocker or one-way-field trigger area in the opposite of the lemming's current direction, at the exact X and Y coordinates of the lemming
>>>> If any of those were the case, move downwards 1 pixel, turn around, change to a faller, and move forward one pixel (in new direction; ie: away from the wall)

* Performing this check is to prevent climbing through an overhang at exactly 7 pixels when it isn't the top of the climb



Any change here has the potential to break a lot of replays; it isn't so likely to break too many levels (but we never know - those that rely on very accurate timing may be broken). But, unless we accept this inconsistency (which I am open to as an option, but feel it needs to be discussed), some change is needed - the question is what exactly that should be.

To those who want to look at the code itself - keep in mind it has some gimmick-related stuff in there too, as well as some dud stuff like conditionals that will always be false (which I've removed now in my copy), and does not yet have the fix for the 7-pixel-not-at-top overhang in the current latest code (V1.42n's) - look at the "TLemmingGame.HandleClimbing" function, beginning at (or near) line 7090 in LemGame.pas which I've copy-pasted an up-to-date version of in this post.


EDIT: I added a second test map, with the situations described in Nepster's post in this topic.
Both test maps require the respective VGASPEC (also attached), but this aside do not require any custom styles.
« Last Edit: February 21, 2016, 10:27:28 AM by namida »
My Lemmings 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)

Offline namida

  • Administrator
  • Posts: 12399
    • View Profile
    • NeoLemmix Website
Re: [DISCUSSION][PLAYER] Climber mechanics
« Reply #1 on: February 13, 2016, 06:32:09 PM »
Added a demonstration level, and an actual description of the inconsistency rather than just describing what causes it.
My Lemmings 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)

Offline Simon

  • Administrator
  • Posts: 3879
    • View Profile
    • Lix
Re: [DISCUSSION][PLAYER] Climber mechanics
« Reply #2 on: February 13, 2016, 06:38:21 PM »
Thanks for the description.

The first hunch, obviously, is that check-for-overhang should be completely independent of check-for-top. But I haven't dived into why it's not. So, don't want to advise anything yet.

-- Simon
« Last Edit: February 13, 2016, 06:49:41 PM by Simon »

Offline namida

  • Administrator
  • Posts: 12399
    • View Profile
    • NeoLemmix Website
Re: [DISCUSSION][PLAYER] Climber mechanics
« Reply #3 on: February 13, 2016, 06:42:31 PM »
Here's the current code, including the removal of do-nothing junk and tidying up the formatting a bit so it's more readable, as well as implementing the overhang-at-7-pixel fix:

Code: [Select]
function TLemmingGame.HandleClimbing(L: TLemming): Boolean;
var
  FoundClip: Boolean;
begin

  with L do
  begin

    if (LemFrame <= 3) then
    begin
      // check if we approached the top
      if (HasPixelAt_ClipY(LemX, LemY - 7 - LemFrame, 0) = FALSE) then
      begin
        FoundClip := HasPixelAt(LemX - LemDx, LemY - 6 - Lemframe)
                  or (HasPixelAt(LemX - LemDx, LemY - 5 - Lemframe) and (LemClimbed > 0));
        LemY := LemY - LemFrame + 2;

        if FoundClip then
        begin
          if CheckGimmick(GIM_BACKWARDS) then TurnAround(L);
          if not CheckGimmick(GIM_NOGRAVITY) then
          begin
            LemY := LemY + 1;
            Transition(L, baFalling, TRUE)
          end else
            Transition(L, baWalking, TRUE);

          if CheckGimmick(GIM_BACKWARDS) then
            Dec(LemX, LemDx)
          else
            Inc(LemX, LemDx);
          Result := True;
          Exit;
        end else
          LemClimbed := 0;
          Transition(L, baHoisting);

      end;
      Result := True;
      Exit;
    end
    else begin
      Dec(LemY);
      Inc(LemClimbed);
      // check for overhang or level top boundary
      if (HasPixelAt_ClipY(LemX - LemDx, LemY - 7, -8) = true)
      or ((LemClimbed = 1) and HasPixelAt_ClipY(LemX - LemDx, LemY - 6, -8))
      or ((ReadObjectMapType(LemX, LemY) = DOM_FORCELEFT) and (LemDx > 0)) or ((ReadObjectMapType(LemX, LemY) = DOM_FORCERIGHT) and (LemDx < 0))
      or ((ReadBlockerMap(LemX, LemY) = DOM_FORCELEFT) and (LemDx > 0)) or ((ReadBlockerMap(LemX, LemY) = DOM_FORCERIGHT) and (LemDx < 0))
      or ((CheckGimmick(GIM_EXHAUSTION)) and (LemClimbed >= fFallLimit)) then
      begin
        if CheckGimmick(GIM_BACKWARDS) then TurnAround(L);
        if not CheckGimmick(GIM_NOGRAVITY) then
          begin
          LemY := LemY + 1;
          Transition(L, baFalling, TRUE);
          end
        else
          Transition(L, baWalking, TRUE);
        if CheckGimmick(GIM_BACKWARDS) then
          Dec(LemX, LemDx)
          else
          Inc(LemX, LemDx);
      end;
      Result := True;
      Exit;
    end;

  end;
end;

For the record, a "true" for the last parameter of L.Transition causes the lemming to turn around in addition to changing state.
The function as a whole returning true (which if I'm not mistaken, will always be the case for the Climber, but not nessecerially so for other actions) enables the full check for objects afterwards; otherwise, only certain types are checked for.
HasPixelAt_ClipY no longer does anything different from a regular "check if there's a pixel here" in NeoLemmix; meaning the last parameter of it is meaningless. It did special stuff compared to a regular check in vanilla Lemmix / early versions of NeoLemmix, and like many things, I haven't completely stripped it out yet.
My Lemmings 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)

Offline Nepster

  • Posts: 1829
    • View Profile
Re: [DISCUSSION][PLAYER] Climber mechanics
« Reply #4 on: February 13, 2016, 06:52:21 PM »
Will look at the code soon, but here is something else (that I wanted to post in the bug report thread, but it's closed again!):
If the wall is precisely 9 pixels high with an overhang at 8 pixels, the climber may climb this as well (at least in V1.42).

Offline namida

  • Administrator
  • Posts: 12399
    • View Profile
    • NeoLemmix Website
Re: [DISCUSSION][PLAYER] Climber mechanics
« Reply #5 on: February 13, 2016, 06:54:54 PM »
You should be able to re-open your own topics, but anyway, that's something we can look at here. I'm thinking the entire Climber should be overhauled at this point; between the inconsistencies and the bugs.
My Lemmings 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)

Offline IchoTolot

  • Global Moderator
  • Posts: 3613
    • View Profile
Re: [DISCUSSION][PLAYER] Climber mechanics
« Reply #6 on: February 13, 2016, 07:01:31 PM »
So as I see the example level: It would affect short height climbs with an ongoing wall up to 9 pixels. Prob short height climbers with this ongoing wall will behave as the normal climbers then (stop climbing a few frames earlier).

First things first: No player will notice this at all, but this could be worth fixing and if I would combine this with the 7 pixel high climbs fix (to minimize checking everything again to only 1 more time).

It will most certainly not break 99,9% of the levels only the ones with this exactly short climbs + pixel perfection.
But a big bunch of replays will break for sure!

I am not 100% sure: It is an inconsistancy that should be fixed, but it is also really unimportant to the overall gameplay so dont bother to fix. hmm



Offline Nepster

  • Posts: 1829
    • View Profile
Re: [DISCUSSION][PLAYER] Climber mechanics
« Reply #7 on: February 13, 2016, 07:13:48 PM »
I am trying to understand the code first, before forming an opinion on possible changes:
- During frames 0-3 the climber does not change position, but the checks to transition to a hoister are made higher and higher, i.e. for hoister purposes, the lemming climbs on frames 0-3.
- During frames 4-7, the position of the lemming changes. However during frames 4-6 he cannot transition to hoister (assuming the terrain stays constant), because the terrain checks are actually made for lower pixels than during frame 3.
Is this correct?

Offline namida

  • Administrator
  • Posts: 12399
    • View Profile
    • NeoLemmix Website
Re: [DISCUSSION][PLAYER] Climber mechanics
« Reply #8 on: February 13, 2016, 07:19:41 PM »
Correct; he does not change position during frames 0-3, but does indeed check higher and higher.
During frames 4-7, he cannot become a hoister ever - there is no Transition(L, baHoisting) in there. If the terrain is removed, he will continue to move upwards until he reaches frame 0 again, at which point (assuming he hasn't moved beyond the gap) he will become a hoister.

If I remember correctly, the underlying movement and check positions are unaltered from vanilla Lemmix. The changes (apart from those relating to gimmicks) are related to preventing him from ignoring overhangs in some cases.
My Lemmings 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)

Offline Nepster

  • Posts: 1829
    • View Profile
Re: [DISCUSSION][PLAYER] Climber mechanics
« Reply #9 on: February 13, 2016, 08:28:40 PM »
Here a list of the frames, positions and check-locations (as I understand it - so no guarantee). If a Hoist-check is made, the Terrain-check only occurs if the Hoist-check was successful.
Frame    Y-Pos    Hoist-Check    Terrain-Check
00-7never occurs
10-8-7
20-9-8
30-10-9
4-1none-7, -8
5-2none-9
6-3none-10
7-4none-11
8-4-11-9, -10
9-4-12-10, -11
10-4-13-11, -12
11-4-14-12, -13
12-5none-12
13-6none-13
14-7none-14
15-8none-15

This tells me to expect the following irregular behaviors in addition to the one described by namida:
1) If the wall is 8 pixels high, the code does nowhere check for terrain at height 7.
2) If the wall is 9 pixels high, the code does nowhere check for terrain at heights 7 or 8.
3) If the wall is (10+4k) pixels high, but there is an overhang at height (11+4k), the terrain check at frame (7+8k) detects this overhang and the lemming falls down. If this setup appears for any other height of the wall, the lemming may climb it.

Finally in your code snippet for frames 4-7 you have the line:
Code: [Select]
or ((LemClimbed = 1) and HasPixelAt_ClipY(LemX - LemDx, LemY - 6, -8))Why is it LemClimbed = 1 and not LemClimbed = 0, if you want to check for the first cycle?

« Last Edit: February 13, 2016, 08:40:48 PM by Nepster »

Offline namida

  • Administrator
  • Posts: 12399
    • View Profile
    • NeoLemmix Website
Re: [DISCUSSION][PLAYER] Climber mechanics
« Reply #10 on: February 13, 2016, 08:36:09 PM »
Notice just above that:

Code: [Select]
Inc(LemClimbed);
Because of this, LemClimbed will never be 0 at the time it reaches that line - it will be 1 the first time that line is reached.
My Lemmings 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)

Offline Nepster

  • Posts: 1829
    • View Profile
Re: [DISCUSSION][PLAYER] Climber mechanics
« Reply #11 on: February 13, 2016, 08:41:19 PM »
Thanks. I corrected my table above accordingly.

Offline Nepster

  • Posts: 1829
    • View Profile
Re: [DISCUSSION][PLAYER] Climber mechanics
« Reply #12 on: February 14, 2016, 12:23:59 PM »
Please chack the following suggestions, whether they are practicable and don't introduce new glitches.

Overhang middle/top make difference in trigger falling:
I can see only rather elaborate fixes to this. The first idea would be to keep climbing instead of falling, when on frames 0-3 the hoister-check was successful, but terrain was encountered. Then ideally the terrain checks for frames 4-7 would see the overhang and trigger the transition to faller.
Problems appear when the overhang gets removed in the meantime, but the gap remains: Then the climber would simply continue climbing over the gap in the wall without transitioning to faller or hoister at any time. So one would have to include hoister checks for frames 4-7 as well...
Thus I vote for keeping this inconsistency.

Walls of heights 8 or 9:
I cannot think of anything that is simpler than adding special terrain checks for frames 1, 2 and 3 on the first cycle. To keep the code readable, perhaps one could deal with the first 4 frames separately, e.g. in the form
Code: [Select]
if (LemClimbed = 0) then
  // Check for hoister transition on first cycle
  ...
else if (LemFrame <= 3) then
  // Check for hoister transition on all other cycles
  ...
else
  // Move lemming and check for overhangs
  ...
end

Wall is (10+4k) pixels high, but overhang at height (11+4k):
This seems surprisingly difficult to fix. The best I could come up with, is the following:
Change the terrain check on frame 7 to
Code: [Select]
(HasPixelAt_ClipY(LemX - LemDx, LemY - 7, -8) = TRUE) and (HasPixelAt_ClipY(LemX, LemY - 7, 0) = FALSE)i.e. only transition to faller if the lemming will not become a hoister in the next frame.

Again we have a problem, if the terrain is modified between frame 7 and the following frame 0: If the gap in the wall (which should trigger the hoister transition) is filled during this one frame, the lemming continues climbing and thus passes through the overhang.
So on frame 0, one needs an additional check for
Code: [Select]
(LemClimbed > 0) and (HasPixelAt_ClipY(LemX - LemDx, LemY - 7, -8) = TRUE)which triggers falling, regardless what the hoister-check does.

At first glance a stricter terrain check on frame 4 seems to work as well, similarly to the one done in the first cycle. So instead of
Code: [Select]
or ((LemClimbed = 1) and HasPixelAt_ClipY(LemX - LemDx, LemY - 6, -8))we would have:
Code: [Select]
or ((LemFrame = 4) and HasPixelAt_ClipY(LemX - LemDx, LemY - 6, -8))However then the following weird glitch appears: If there are gaps in the wall at heights 11+4k and 14+4k and overhang at height 11+4k. If the gap at height 11+4k is filled in frame 7+8k, the lemming continues in the climbing animation. However on frame 11+8k, i.e. one frame before the additional terrain check would trigger the falling animation, the hoister-check finds the gap at height 14+4k. The following terrain checks check only at heights 12+4k and 13+4k, so succeeds and the lemming will transition to a hoister.


An alternative would be to disallow hoisting in such cases in general by adding a terrain check at the same height as the hoist-check. However this might break much more replays or even levels than always allowing hoister-transitions.


Btw: What is the the third argument in HasPixelAt_ClipY?

Offline namida

  • Administrator
  • Posts: 12399
    • View Profile
    • NeoLemmix Website
Re: [DISCUSSION][PLAYER] Climber mechanics
« Reply #13 on: February 14, 2016, 12:46:34 PM »
In vanilla Lemmix, HasPixelAt_ClipY was used to limit the region that would be checked - I'm not sure exactly what it was relative to (the top of the level, I think). However, in NeoLemmix, HasPixelAt_ClipY is much simpler, and the last parameter does nothing - I just haven't got around to stripping that out yet.

Code: [Select]
function TLemmingGame.HasPixelAt_ClipY(X, Y, minY: Integer; SwimTest: Boolean = false): Boolean;
begin
  Result := HasPixelAt(X, Y, SwimTest);
  // need to remove this function; NeoLemmix doesn't ever clip Y in solidity tests
end;
My Lemmings 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)

Offline namida

  • Administrator
  • Posts: 12399
    • View Profile
    • NeoLemmix Website
Re: [DISCUSSION][PLAYER] Climber mechanics
« Reply #14 on: February 18, 2016, 04:13:13 AM »
I put up a better climber test map, that rather than being kludgily thrown together out of pieces and erasers, uses a VGASPEC so it can be more precisely designed (and labelled).

I'll make one soon that includes the cases Nepster mentions, and perhaps some other similar cases. This way we can better discuss what we think should happen in each case shown in such a map.
My Lemmings 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)

Offline ccexplore

  • Posts: 5311
    • View Profile
Re: [DISCUSSION][PLAYER] Climber mechanics
« Reply #15 on: February 18, 2016, 06:01:43 AM »
In vanilla Lemmix, HasPixelAt_ClipY was used to limit the region that would be checked - I'm not sure exactly what it was relative to (the top of the level, I think). However, in NeoLemmix, HasPixelAt_ClipY is much simpler

As I recall, the clipY part is necessary in vanilla Lemmix to emulate some behaviors around the top boundary.  Specifically Eric identified an existing solution related to bashing at the top of the level that isn't wasn't working in Lemmix but works in DOS Lemmings, and it turns out with further disassembly that the game effectively look at the pixel at y = 0 instead when the specified location has y < 0 (details may be slightly inaccurate as stated, but you get the general idea).

I don't remember anything about SwimTest though, is that part really present in vanilla Lemmix? ???
« Last Edit: February 18, 2016, 06:09:10 AM by ccexplore »

Offline namida

  • Administrator
  • Posts: 12399
    • View Profile
    • NeoLemmix Website
Re: [DISCUSSION][PLAYER] Climber mechanics
« Reply #16 on: February 18, 2016, 06:16:41 AM »
Quote
I don't remember anything about SwimTest though, is that part really present in vanilla Lemmix?

Nope; it just gets passed as-is to HasPixelAt (well, everything does now, but at one point HasPixelAt_ClipY did stuff). In there, it's used for a check that detects water as well as terrain, for Swimmer purposes.
My Lemmings 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)

Offline namida

  • Administrator
  • Posts: 12399
    • View Profile
    • NeoLemmix Website
Re: [DISCUSSION][PLAYER] Climber mechanics
« Reply #17 on: February 21, 2016, 10:28:01 AM »
I put up a second test map with the situations that Nepster mentioned, and indeed, all of these predictions were correct. Now I can start working on how to deal with these...
My Lemmings 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)

Offline namida

  • Administrator
  • Posts: 12399
    • View Profile
    • NeoLemmix Website
Re: [DISCUSSION][PLAYER] Climber mechanics
« Reply #18 on: February 21, 2016, 11:15:35 AM »
I've made fixes to these three exceptional cases discovered by Nepster, as well as to the general inconsistency of faller transition timing (this was achieved by simply always doing the terrain checks on frames 0~3, rather than only if a hoist will occur if no terrain is in the way).

A new experimental version has been uploaded that includes the new climber behaviour. This will probably break a lot of replays, sorry (it broke 8 replays in PSYCHO (Lemmings Plus I) alone, despite LPI's replays having been completely unbroken pretty much the entire time LPI has been a NeoLemmix pack), but it does make the climber extremely consistent now. I doubt it will break many levels, though I have a feeling it may break Genius 19. I'll await further discussion regarding this, so for now, don't assume it's going to be the finalized behaviour - I might bring back the inconsistency (but leave the other fixes, of course) if the general feeling is that it's doing more damage than it's worth.

The current (and previous) behaviours on the test levels are:

Test Map 1
All lemmings should fail to climb. All lemmings on the same structure should become fallers on the same frame; their timing relative to lemmings on other structures is not important apart from that a higher overhang should never cause fallers earlier than a lower overhang does (causing them at the same time is acceptable).
Previously: The bottom lemming on the 7 pixel overhang structure would continue to climb. Timings between lemmings on the same structure may not be equal.

Test Map 2
The four lemmings on the left should all fail to climb. The six lemmings on the right should all succeed.
Previously: The four lemmings on the left all succeed in climbing. Of the right lemmings, 10+1 and 14+1 fail, while the other four succeed.

Overhang at 6 pixels from ground
Lemmings should climb through this, regardless of how tall the wall is. I haven't made a proper test map for this one yet.
Previously: This was already fixed as of V1.40n-C; but prior to this, they would fail if the wall was 7 pixels tall, but succeed if it was any taller than that.
« Last Edit: February 21, 2016, 11:37:42 AM by namida »
My Lemmings 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)