RetroAchievements / RAIntegration

The DLL responsible to integrate emulators with RetroAchievements.org
https://retroachievements.org
MIT License
97 stars 23 forks source link

Update delta values of non-PauseIf conditions while group is paused #144

Closed Jamiras closed 5 years ago

Jamiras commented 6 years ago

As a developer, when a trigger becomes unpaused, I don't want unexpected delta conditions to trigger.

The problem is that when a PauseIf it true, the other conditions aren't evaluated at all, which leaves their delta state unmodified. there's three potential implementations:

Switching to either of the other methods had the potential to break existing achievements relying on the first functionality. It's unclear if it's being intentionally used in this manner.

For example, if a memory address increments on each frame:

Frame 1: 1
Frame 2: 2
Frame 3: 3
Frame 4: 4

If the achievement is paused on frame 3, and unpaused on frame 4, these are the results: option 1: delta will be "1" in frame 4 [it was captured on frame 2, and ignored when paused on frame 3]. option 2: delta will be "3" in frame 4 option 3: delta will be "4" in frame 4

The design of "delta" is to return the value from the previous frame, which is the behavior described in option 2. Unless there are any objections, that's the functionality I'd like to implement.

Keltron3030 commented 6 years ago

Thanks for this. I'll stay tuned for a nightly with this update.

Keltron3030 commented 5 years ago

@jamiras do you think this update may be implemented in the near future?

Jamiras commented 5 years ago

Every time someone asks about PSX support, it's delayed by a month.

Or something like that. Things will get done when they get done. The more I get asked about the status of something, the less I want to work on it.

Keltron3030 commented 5 years ago

No problem, I totally get it.

Thoooreau commented 5 years ago

I wanna state I used the fact the delta doesn't update when the group is paused as a feature to fix a group of 7 achievements that share the same logic. If the way this currently works changes, these cheevos will be broken, and I couldn't think of another way to solve this. Link of the first one: http://retroachievements.org/achievement/31512

I had to compare the quantity of candy canes when the player entered the bonus to when the player exit the bonus, so I paused the achievement while the player was in the bonus stage. Then in the first frame after the bonus ended, I compared the candy canes address to its delta, as the delta held the value from when the bonus started.

The standard way would be to just count each time the candy cane address changed, counting the hits. There was a problem, though: there were sometimes where the player could get two candy canes in the same frame, and it was quite frequent, actually.

The way this pause/delta currently works can be used as a trick to "store" a value you need to compare to later.

On the other side, I agree it is more intuitive to think deltas would be updated even if the group is paused, and the fact that it doesn't currently causes some confusion for devs until they find this out. It caused confusion for me in the past and I've seen causing for others. As far as I remember, though, this was never a clear information in the docs.

@GameDragon2k suggested in the past creating a new type called TrueDelta that would work as people expected (and as @Jamiras suggested here), but keeping Delta as it is. It makes sense to me, both types can be useful.

meleu commented 5 years ago

@Jamiras I completely understand that the current implementation doesn't "respect" the definition of Delta Values (the value in the previous frame), but once I knew the trick used by @Thoooreau in that achievement I started to see it as something really useful.

It is a clever way to "memorize" a value from an arbitrary frame in the past and it has some use (like circumventing the problem of a counter address bumping twice in the same frame).

Here's my suggestion:

Once prior comes alive we can properly document this behavior in the docs.

Jamiras commented 5 years ago

For most use cases, people expect previous (and prior) to not be affected by the pause. @Thoooreau's use case can probably be solved with AndNext: only increment the hitcount if the previous condition were true AndNext not in the bonus stage.

meleu commented 5 years ago

In the achievement mentioned by Thoreau, the problem to be solved is:

  1. a bonus stage has 99 candies
  2. your candy counter goes from 0 to 99
  3. there are situations where you can collect 2 candies simultaneously, causing the counter to double increment in the same frame.

Question: How to create an achievement to precisely trigger when the player collects all 99 candies in the end of the bonus stage?

The solution he found to circumvent the problem described in the point 3 above depends on the behavior described in the OP. And can be "translated" as:

  1. "Memorize" the ammount of candies the player have when the bonus stage starts.
  2. When the bonus stage ends, subtract the memorized value from the current ammount.
  3. Depending on the result of the subtraction above the achievement triggers.

How can we get a similar result with AndNext (or any other logic feature)?

Jamiras commented 5 years ago

I missed the comment about the double increment. You could probably do something with AddHits:

SubSource Delta Candies
AddHits Candies = 2 
Candies != Delta Candies HitCount 99
ResetIf Stage != Bonus Stage

Each double increment would be tallied once by the first two lines, and again by the third line, resulting in two total hits. A single increment would only be tallied by the third line. As a result, the target HitCount on the third line should still be achievable.

Even if that doesn't work, I still think making this change is beneficial to the larger audience. There's been much confusion around the behavior, leading to the creation and repeated linking of this issue.

Unfortunately, the changes made in the PR eliminate the old logic entirely. Each address is given a single reference in the achievement where the current, delta, and prior values are stored and updated for each frame (regardless of the pause state). Then each condition can look at that reference without having to reach out to the emulator for every evaluation.

meleu commented 5 years ago

I forgot to mention here that the solution proposed by @Jamiras above indeed works really fine. It was added to the docs, by the way: https://docs.retroachievements.org/Circumvent-the-Problem-of-a-Counter-Incrementing-Twice-in-the-Same-Frame/