Countdown timer error

Square
Square Posts: 380 Mover and Shaker
Two days in a row I've seen this glitch:
Countdown timer count down in order from left to right and then down the screen. The error is when a timer blows up an area of the the board, and a timer from above falls to after where the blown up countdown timer was... it will then count down a second time, going down by two in a single turn. It's a hard error to replicate on purpose, but I just lost a big chunk of health from a countdown that was 'safe.'

It's hard to describe, but hopefully the devs can understand this and fix it.

Comments

  • simonsez
    simonsez Posts: 4,663 Chairperson of the Boards
    Square wrote:
    It's hard to describe, but hopefully the devs can understand this and fix it.
    It's been described before, they do understand it, and they show no interest in acknowledging it or fixing it.
  • Nightglider1
    Nightglider1 Posts: 703 Critical Contributor
    simonsez wrote:
    Square wrote:
    It's hard to describe, but hopefully the devs can understand this and fix it.
    It's been described before, they do understand it, and they show no interest in acknowledging it or fixing it.

    To be fair, if they attempted to fix this they'd probably just manage to mess something else up in the process.
  • BlackSheep101
    BlackSheep101 Posts: 2,025 Chairperson of the Boards
    Something related to stunning, no doubt.
  • zodiac339
    zodiac339 Posts: 1,948 Chairperson of the Boards
    No, it's not about stunning. The countdown resolution was simply designed to sweep the board left-right, top-bottom without being able to recognize a tile that has already resolved. Someone at D3 has probably tried to figure out coding to change how the resolution works, but hasn't found anything adequate. Like mentioned above, any solutions attempted have resulted in game-breaking errors. Look how they've tried to fix passives. Attempt to stop them and end of round cased passives to completely shut down on the last wave of survival missions. That was corrected, but Teisatsus still Turn to Smoke when you kill them, even though no other passives activate. And does anyone remember when those damn Teisatsu were activating when already down from AOE and cascades? Anyone end up with 12 attack tiles from one Teisatsu? But they've fixed problems we weren't having (passives when mission ends) so there's hope they'll fix a problem we are having.
  • BlackSheep101
    BlackSheep101 Posts: 2,025 Chairperson of the Boards
    Let's try that again...
    simonsez wrote:
    Square wrote:
    It's hard to describe, but hopefully the devs can understand this and fix it.
    It's been described before, they do understand it, and they show no interest in acknowledging it or fixing it.

    To be fair, if they attempted to fix this they'd probably just manage to mess something else up in the process.
    Something related to stunning, no doubt.
  • abmoraz
    abmoraz Posts: 712 Critical Contributor
    zodiac339 wrote:
    No, it's not about stunning. The countdown resolution was simply designed to sweep the board left-right, top-bottom without being able to recognize a tile that has already resolved. Someone at D3 has probably tried to figure out coding to change how the resolution works, but hasn't found anything adequate. Like mentioned above, any solutions attempted have resulted in game-breaking errors. Look how they've tried to fix passives. Attempt to stop them and end of round cased passives to completely shut down on the last wave of survival missions. That was corrected, but Teisatsus still Turn to Smoke when you kill them, even though no other passives activate. And does anyone remember when those damn Teisatsu were activating when already down from AOE and cascades? Anyone end up with 12 attack tiles from one Teisatsu? But they've fixed problems we weren't having (passives when mission ends) so there's hope they'll fix a problem we are having.

    *** DISCLAIMER: While I am a developer, I do NOT work for D3 or Demiurge and have no affiliation with Marvel Puzzle Quest. My opinions below are based solely on my knowledge as a coder and observations on how the game functions from playing it. I have NOT looked at the MPQ code at all ***

    Without diving too much into coding theory, my guess is that the individual tiles are not unique objects, but references to a location (again, without getting too deep, this makes sense from a memory management point of view).

    If the tiles were unique objects, at the start of the round, they could do something like:
    countdownTileArray = getAllFriendlyCountdownTiles()
    for tile in countdownTileArray:
      if tile:
         deincrement(tile)
         updateBoard()
    
    In english (rather than code): "Get every countdown tile that is owned by the current team. Then the next timer in that list, Check to see if it still exists. If it does, reduce its timer by 1, resolve any effects, then update the board and repeat the process for the next tile. This has the benefit of not deincrementing the same timer twice in the same round due to the board rearranging.

    This is what you (zodiac339) are expecting to happen. This is also VERY memory intensive as it requires every tile to be loaded as a unique object. What appears to be happening is the following:
    for i = 0 to 63:
       currentTile = getTile(i)
       if isFriendlyCountdownTile(currentTile):
          deincrement(currentTile)
          updateBoard()
    

    In english: Start in the upper left corner. Scan the board from left to right, then top to bottom. If you hit a countdown tile, reduce its timer by one, resolve any actions and update the board. Once that is complete, continue from where you were. This has the side effect of "if a tile falls from above due to the resolved actions of a timer after it, it could deincrement twice int he same round."

    Now, the question you are asking is "Why would they use the second method rather than the first?"... the answer: memory management. The second method requires 1 state (the entire board) that can be updated fairly easily. The first method requires 65 states (1 for each tile, and one for the board to tell it where those tiles are). In addition, when the board changes, under the second method, the state can be updated to reflect the fact that tiles have been destroyed, changed, added, etc... without taking any more memory. It's a flat memory requirement. In the first state, every time a tile gets destroyed, the memory object for that tile would get destroyed and new one created, then the board state would have to have that new object added to it. This is a very slow and resource intensive process.

    tl;dr version:
    To treat each timer as it's own entity, rather than a location on the board would be like printing a book, then editing it on paper vs in a word processing program then printing it. It's the difference between tearing out the old page, retyping the correction, printing it out (retyping any pages after it and inserting them as well to maintain consistency in the typeset, spacing, etc...), inserting the updated pages, then rebinding the book. All that versus using a word processor, cutting/pasting/editing the text, then printing the book out all at once at the end.

    When printing the book first, you have the entire book (i.e. "The board") and the individual pages (i.e. "The individual tiles"). When doing the editing first, then printing, you only have 1 object (the "board").

    I hope this helps in understanding why it behaves this way.
  • Dayv
    Dayv Posts: 4,449 Chairperson of the Boards
    I'm not a coder, but I play one on TV.

    If I were a coder, I would resolve this by adding an invisible status to a tile that marks a tile as "decremented". In the back end, this would be added to a tile in a similar method to fortifying or locking a tile, but the player would never see it. This would be done as the tile is decremented, and any tile the countdown decrement-and-resolve pass reaches with this state would be skipped. Then, after completing the pass to countdown-and-resolve all locations on the board, do a fast second pass that only removes all "decremented" states from the board.
  • Dayv
    Dayv Posts: 4,449 Chairperson of the Boards
    Problems with IM40 getting stunned in the middle of his countdowns going off are a separate issue to resolve. They kinda dug themselves a hole by having a character put out three countdown with a self-stun component, because they created an exception case where those have to countdown after the first one should have stunned him.

    To me, the easiest way to fix it is to make his countdowns unaffected by his stun state, but then you could fire yellow on consecutive turns and have the second wave resolve while he's still stunned, which would be a nice buff on a power that already got massively improved.
  • abmoraz
    abmoraz Posts: 712 Critical Contributor
    DayvBang wrote:
    I'm not a coder, but I play one on TV.

    If I were a coder, I would resolve this by adding an invisible status to a tile that marks a tile as "decremented". In the back end, this would be added to a tile in a similar method to fortifying or locking a tile, but the player would never see it. This would be done as the tile is decremented, and any tile the countdown decrement-and-resolve pass reaches with this state would be skipped. Then, after completing the pass to countdown-and-resolve all locations on the board, do a fast second pass that only removes all "decremented" states from the board.

    That is a good solution, IMHO. Doesn't require new objects, just an additional property (which are cheap, especially since this would be a boolean). Simple and elegant.
  • turul
    turul Posts: 1,622 Chairperson of the Boards
    This would handle the most exotic cases properly.
    function ResolveBombs() {
        removedecrementedStatuses();
        while (B = findFirstUndecrementedBomb()) {
            B.timer--;
            B.decremented = true;
            B.everyTurnAction();
            Board.Cascade();
            if (B.timer < 1 && B.parent.still_exists()) B.ZeroTurnAction();
            Board.Cascade();
        }
    }