Users browsing this thread: 1 Guest(s)
Possible solution to the sap death bug

#1
Posts: 16
Threads: 3
Thanks Received: 0
Thanks Given: 0
Joined: May 2019
Reputation: 2
Status
None
Hi all,

I've been trying to patch the bug that causes "Sap" and "Poison" ticks to bypass reactive scripts, and I think I've come up with the solution. However, since it involves changes to the action queues, I think it would require significant testing before it can be considered a perfect fix. I haven't done the testing yet, which is why I'm not uploading a patch here. But I thought the explanation below might be of interest to someone, nonetheless.

The problem, in essence, is that there is handling to allow a second reactive script to be queued if the target has died, but there is not handling to allow that reactive script to fire under the same conditions.

The solution is to allow the "entity has died" override to occur before the "has reactive script fired already" check.

Code:
org $C24BFD
  TRB $3A56        ; clear "entity died since last 1F" (in vanilla, $33FC is checked first)
  BNE .has_died    ; allow script if has died
  TRB $33FC        ; clear "no 1F this batch" (in vanilla, $3A56 is checked second)
  BEQ .skip_it     ; bypass script if already run
org $C24C28
.has_died
  TRB $33FC        ; clear "no 1F this batch" (replaces redundant "LDA $3018,X")
org $C24C52
.skip_it


An attempt at describing the sequence of events that triggers the bug:
* Boss has Sap/Poison
* You attack boss, not doing enough damage to kill it, but the next sap will
* Boss's reaction script is queued
* During the attack animation, time proceeds, and the next sap tick is queued
* Boss's queue now looks like: [Reaction, Sap]
* Animation finishes, and main battle loop starts over, hitting the counterattack check
* Boss's reaction script fires, and the $33FC flag is cleared to indicate the script fired.
* Boss's sap fires, killing the boss, which adds another reaction script to the queue
* This reaction attempts to run, but is blocked by the $33FC, which won't be reset until the counterattack queue is empty.

Hopefully, my patch doesn't open up a wormhole to another dimension. And apologies if this bugfix is already common knowledge!

I think this fix would also address some other issues with reactive scripts, such as Chadarnook bypassing the short dialogue after getting killed by a reflected Bolt counter.
  Find
Quote  

#2
Posts: 200
Threads: 1
Thanks Received: 10
Thanks Given: 0
Joined: Oct 2015
Reputation: 18
Status
None
Surprised

would you mind deferring and instead beta testing MY patch? Tongue

i ask because:

1. i've studied these bugs extensively.  see a number of posts, admittedly scattered some, in this thread:
http://mnrogar.slickproductions.org/phpB...?f=2&t=108
this post is one of the most detailed:
http://mnrogar.slickproductions.org/phpB...9c9c#p7062
(i think most posts on the bugs span 2011-2013, thereabouts.  note that i've long-since abandoned the idea of separating the queues involved.)

2. i already wrote a patch in 2016 into late June 2017.

3. i've easily double- and triple- and quadruple- checked and OCD'd on this bugfix more than any other patch i've made, by a WIDE margin.  both to ensure that it fixes the issues it sets out to, and moreso that it avoids side effects (e.g. those wormholes to other dimensions), or at least keeps them known and minimal (whenever i finish my documentation, it'll cover 1-2 situations with small loopholes, 1 of those especially contrived).  mostly, my INTENSE scrutiny was in the planning and algorithmic stage; i've done in-game testing as well, albeit less.  in the former stage, the claim of quadruple-checking is no exaggeration; it's a vast understatement.

4. while you're on the right track, my patch is more comprehensive about shutting out loopholes, in accordance with #3, and probably about keeping the game working as-is in non-bug scenarios.  yes, it takes free space, claims 16 bits of unused RAM, and uses plenty more CPU cycles when the relevant queue has multiple entries.  but i'm convinced all that's necessary to address things fully and thoroughly while avoiding adverse interactions.  so paradoxically, this lets a patch larger in .IPS bytes have a smaller "footprint" than other approaches.

a couple examples of the comprehensive nature are:
a) fixes both factors contributing to the bugs
b) does not lean as much on Variable $3A56, also checking target death and unavailability in the underlying variables.  i consider this prudent/necessary given that $3A56 is more situational/narrow in context and thus less authoritative on target's condition than those.

5. i've put so much work into planning this damned patch that there's a good chance i would kill anybody who usurped it by throwing bloody clumps of my own hair at them.  Objection!   given my unimpressive arm, consider this a testament to the sheer adrenaline that could be involved in the rampage. Tongue  also, a radius of 5 city blocks would spontaneously be leveled from the sheer "Nooooo!!!!!" of it all without me lifting a finger.  but it's the hair clumps that are your main concern. Hit

-----------

i realize i have no actual right to claim exclusivity or to half-threaten people to stay off of "my" turf.  especially when i've sat on this project for the better part of 3.5 years.  but i'm very close to the finish line in all but the longer, more elaborate, and subtler parts of the documentation.   iow, the patch and the basic description are most likely where they need to be (uh, aside from actually released).

so please sit tight; links are imminent!  Santa

here we go:

http://assassin17.brinkster.net/forum-po...h-beta.zip
Quote  

#3
Posts: 16
Threads: 3
Thanks Received: 0
Thanks Given: 0
Joined: May 2019
Reputation: 2
Status
None
Hey, didn't mean to step on your toes -- if it weren't for your C2 disassembly, there's pretty much no way I'd even have started learning asm in the first place, much less started trying to patch bugs! So you really have my gratitude.

Could you walk me through some of the pitfalls of the simpler approach I posted above? I saw in your code that you ensured these "death override" counters use the limited script functionality (i.e. >= FC only), which is definitely something that I overlooked. But aside from that, can you help me figure out a scenario where my patch would fail?

I also am wondering whether your patch at $4CA2 would have any effect at all. Under what circumstance would a "normal" attack be preparing its counterattacks while the counterattack queue had pending periodic damage? Wouldn't the counterattack queue have to be empty before the "normal" attack queue got checked?

Edit: With regard to the limited script handling I overlooked, I think this may do the trick:


Code:
org $C24C80 : JSR LimitCodes

org !free_space
LimitCodes:
  LDA $B1          ; attack flags
  LSR              ; carry: non-normal attack
  TDC              ; zero A
  BCS .exit        ; exit without bit if non-normal attack
  LDA $3018,X      ; else, exit with target unique bit
.exit
  RTS

The above code will cause $33FE to only be flagged for regular attacks, which will ensure counters/periodic damage always use the limited reaction script.

Alternate code approach that doesn't require freespace:

Code:
org $C24C68
RewritePrepare:
  STZ $B8            ; zero targets for counterattack
  STZ $B9            ; zero targets for counterattack
  LDA $B1            ; check for "normal" attack
  LSR                ; carry: "non-normal" attack
  BCS .skip          ; branch if ^
  LDA $32E0,X        ; "hit by attack"
  BPL .skip          ; branch if not ^
  ASL                ; get attacker index
  STA $EE            ; save in scratch RAM
  CPX $EE            ; target === attacker?
  BEQ .skip          ; branch if ^
  TAY                ; attacker index
  REP #$20           ; 16-bit A
  LDA $3018,Y        ; attacker unique bit
  STA $B8            ; save target for counterattack
  LDA $3018,X        ; current target bit
  TRB $33FE          ; flag to use full reactive script
.skip
  REP #$20           ; 16-bit A
  LDA $3018,X        ; current target bit
  BIT $3A56          ; "died since last reactive script"
  SEP #$20           ; 8-bit A
  BNE .react         ; branch if "died", so force script
  LDA $B8            ; else, check if was attacked by normal
  ORA $B9            ; ^
  BEQ .next          ; no counterattack if not ^
warnpc $C24C9E
padbyte $EA
pad $C24C9D
org $C24C9D
.react
org $C24CBE
.next

(12-11-2020, 01:20 AM)Bropedio Wrote: I also am wondering whether your patch at $4CA2 would have any effect at all. Under what circumstance would a "normal" attack be preparing its counterattacks while the counterattack queue had pending periodic damage? Wouldn't the counterattack queue have to be empty before the "normal" attack queue got checked?

OK, I thought through the above, and figured out a scenario that would break. If an attack triggers a counterattack from Enemy A, and Enemy B gets a sap queued while the attack executes, then if Enemy A's reaction targets Enemy B, then the pending Sap tick will prevent Enemy B from queuing its reactive script, even if it is killed by the counterattack. (Edit: On second thought, this would still function correctly, as Enemy B's next Sap tick will trigger its reactive script, due to $3A56).

My next thought is -- why not remove the check at $4CA2 altogether? If a duplicate reaction script is queued, it will inevitably be aborted due to the $33FC flag. And the $3A56 flag will only override the $33FC flag once (per my changes above).
  Find
Quote  

#4
Posts: 200
Threads: 1
Thanks Received: 10
Thanks Given: 0
Joined: Oct 2015
Reputation: 18
Status
None
1)
Quote:Hey, didn't mean to step on your toes -- if it weren't for your C2 disassembly, there's pretty much no way I'd even have started learning asm in the first place, much less started trying to patch bugs! So you really have my gratitude.

no problem.  it's mainly a matter of me having such a large backlog of unreleased patches, which are almost always waiting on the Readmes.  those in turn are usually drawn out by tweaking and refining the main description or, especially in this patch's case, writing the FAQ section or sections dealing with detailed oddities of admittedly low consequence.  by waiting indefinitely to finish and release these things, i run the risk of somebody else tackling it in the meanwhile.  it's frustrating on any patch, but magnified umpteen times for this one, due to said bazillion hours of planning it.

i appreciate the kind words; glad C2 was inspiring.

2)
Quote:Could you walk me through some of the pitfalls of the simpler approach I posted above? I saw in your code that you ensured these "death override" counters use the limited script functionality (i.e. >= FC only), which is definitely something that I overlooked. But aside from that, can you help me figure out a scenario where my patch would fail?

you mentioned a key one regarding limited script functionality.

back in 2015-2017, when i was in my heyday of planning my patch, and all sorts of variants that would never see the light of day, i could come up with numerous weird scenarios that'd arise from a pondered change or added feature.  sure, lots of them would be hypotheticals or only happen really narrowly.  but i was locked in when it came to exploring interactions/side effects (as bugfixes go, this one is more fraught than most).

3.5 years is a long time!  i barely remember most of this stuff.  generally, i'll have to study this further.

regarding your patched C2/4BF4, one loophole coming to mind is that you don't seem to enforce any per-batch limitation as long as the entity has died since last executing Command 1Fh, counterattack variant.  the exploiting example doesn't involve an actual monster i'm aware of, but an edited, simple counterattack script.  however, i consider it fair game, because the game's assembly functions should be robust to valid scripts, in the concept of modularity.  (that is, it's a bit different from expecting Assembly Function A to be robust to alterations to Assembly Function B.  things such as stats and scripts are more like input data, while a fellow assembly function is more of a peer that's part of the same processing structure.)

hack a monster appearing in a like group to have a counterattack script of:
Code:
FC 12 00 00 (If the following monster(s) is/are dead (target last attacker): )
15          (Quake)
F1 36       (Set Target to Self)
30          (Life)
FF          (End If, End Script)

whittle away much of each monster's HP.  kill one of them.  its Quake will kill the others, it'll revive itself, another one will counter with Quake and revive itself, etc., etc.  an infinite loop until they run out of MP! Surprised  (Note: i manually typed your C2/4BF4 patch into a different computer for testing, so there's a 1% chance i flubbed something. Hmm )

i suspect that the "One Command 1Fh Per Batch" limitation was originally created by Square to avoid endless strings of counters (mainly finals).  so they were rightfully wary about things running away and getting all-consuming.

3)
Quote:
Quote:I also am wondering whether your patch at $4CA2 would have any effect at all. Under what circumstance would a "normal" attack be preparing its counterattacks while the counterattack queue had pending periodic damage? Wouldn't the counterattack queue have to be empty before the "normal" attack queue got checked?
OK, I thought through the above, and figured out a scenario that would break. If an attack triggers a counterattack from Enemy A, and Enemy B gets a sap queued while the attack executes, then if Enemy A's reaction targets Enemy B, then the pending Sap tick will prevent Enemy B from queuing its reactive script, even if it is killed by the counterattack.
thank you for that example!  i could swear that i'd come up with at least one breakage scenario, but it's been so long, and my notes are too long and detailed and tangential for me to readily find it.  i'd have to slow down and comb through them for awhile to be more sure.  that's if they even include it in the first place; some key investigations regarding the bug and patch actually preceded my note-taking, while occurring after my mnrogar forum posts.  it's like i thought i could remember certain things indefinitely, or that i'd have refined them into a patch Readme section before they were forgotten.

minute details and off-the-wall crap got *better* attention in my notes, in the fear they'd be more readily forgotten.

with some focused memory jogging, i was gradually arriving at counterattacks from multiple enemies.  but at the current pace, it would've taken me another half day, at least.

Quote:(Edit: On second thought, this would still function correctly, as Enemy B's next Sap tick will trigger its reactive script, due to $3A56).
will C2/5018 allow this?  i think the omni-counter might have to wait until *another* entity's periodic damage/healing occurs, or an entity takes a normal turn.

Quote:My next thought is -- why not remove the check at $4CA2 altogether? If a duplicate reaction script is queued, it will inevitably be aborted due to the $33FC flag. And the $3A56 flag will only override the $33FC flag once (per my changes above).
the queuing does create needless junk for C2/4B7B and C2/4BF4 to process.

there's another reason, from my October 2015 notes and semi-coherent to December 2020 me, which i'm in the process of deciphering.

i think my patch approaches C2/4BF4 properly.  it recognizes that vanilla C2/4BF4 is short-sighted in its indiscriminate treatment of queue siblings, and almost certainly not what was intended.  (that is, allowing a periodic damage and a counter to coexist in the queue does not risk whatever nightmare scenario -- perhaps an endless string of counters/finals -- the designers were trying to avoid with the queuing restriction.)  yet it also recognizes that a precaution was put there for a reason.

the two main issues with my patch's add-on function are size and speed.  i barely care about the former; free space can still be found decades after the game's release, and fitting things in-line is more a point of pride, which shouldn't trump maintaining functionality.  the latter could be obnoxious with the queue walking performed by my routine, but the shortness of the average counterattack + periodic damage/healing queue limits its harm.

both metrics could be improved by ditching the queue walking for setting and clearing a custom flag to track Command 1Fh's presence in the queue.  a tentative variant i'd written uses a free RAM bit (probably Bit 3 of $3AA1,X), clearing it at C2/4B97, and setting it at C2/4CAC.

4)
regarding your C2/4C5B changes: does your alternate code change any functionality from your first, shorter code?  i can't readily tell.

anyway, proceeding based on the simpler version: this tweak goes to something i'd thought about on MANY instances during my patch planning: Square's subtly different treatment of the "death override" in C2/4C5B versus C2/4BF4.

in the former, first-executed function, they have these criteria for queuing:
((entity hit by attack) AND (entity not targeting self) AND (conventional attack)) OR (entity died since last Command 1Fh [counterattack] execution)

then because $33FE gets a subset of the first multi-clause, (conventional attack) is dropped from the criteria for detecting "death override" in the later-executed C2/4BF4.

the asymmetry always struck me as curious, but i couldn't necessarily decide it to be unintentional or without merit.  i can see why Square might want to put a "fully-fledged" counterattack, launched by a discrete other entity, in a different category from something like periodic damage/healing, which *conceptually* isn't really a full-on attack and has no extrinsic source.  and i can see why, in the absence of an FC 12 command in the script, they might want to broaden what the dying monster can retaliate against as it shuffles off this mortal coil.  iow, they wouldn't want to take away from the bite of lesser beings that have more simplistic brains (read: scripts). Tongue  the base quest for vengeance knows no IQ!

so it's something i'd probably keep deferring to (and my patch practically always does).  though i also get why it's tempting to iron out an inconsistency, and to achieve a unified representation of the "death override" from the "transmitting" (1Fh triggering) function to the "receiving" (1Fh executing) function.
Quote  
[-] The following 1 user says Thank You to assassin for this post:
  • Bropedio (12-12-2020)

#5
Posts: 16
Threads: 3
Thanks Received: 0
Thanks Given: 0
Joined: May 2019
Reputation: 2
Status
None
Hey, thanks for the detailed reply -- that was a good read! Let me comment on a just a few pieces as I try to unwind all this code in my brain.

Quote:Whittle away much of each monster's HP.  kill one of them.  its Quake will kill the others, it'll revive itself, another one will counter with Quake and revive itself, etc., etc.  an infinite loop until they run out of MP!

This is a really good example of how my initial code snippet could go horribly, horribly wrong. Though to be honest, it's something I might be comfortable living with, if only because a monster that revives itself upon death will always need to run out of MP before the battle ends, anyway (unless, I suppose, if you want the player to run away, or use some specific means of avoiding the death counter, like Snare or insta-kill stuff). In this example, I think it may be more appropriate to consider the reactive script itself to be the bug. Maybe.

Quote:
Quote:QuoteSadEdit: On second thought, this would still function correctly, as Enemy B's next Sap tick will trigger its reactive script, due to $3A56).
will C2/5018 allow this?  i think the omni-counter might have to wait until *another* entity's periodic damage/healing occurs, or an entity takes a normal turn.

Even if $5018 aborts the command, I think the calling routine will still proceed to the "prepare counterattacks" routine (at $140C), which will check for newly dead entities anyway.

Quote:4)

regarding your C2/4C5B changes: does your alternate code change any functionality from your first, shorter code?  i can't readily tell.

The alternate code does have one additional side effect -- that `B8/B9` is not set for counterattacks triggered by other counterattacks. However, I think that since counters are not meant to trigger other counters under normal circumstances, this is potentially a plus. So if a death counter is triggered by another counterattack, the death script will proceed as though the death were caused by a sap tick, and will randomize targets if necessary.
  Find
Quote  

#6
Posts: 200
Threads: 1
Thanks Received: 10
Thanks Given: 0
Joined: Oct 2015
Reputation: 18
Status
None
pardon the delayed reply.

(12-12-2020, 02:15 AM)Bropedio Wrote:
assassin Wrote:Whittle away much of each monster's HP.  kill one of them.  its Quake will kill the others, it'll revive itself, another one will counter with Quake and revive itself, etc., etc.  an infinite loop until they run out of MP!

This is a really good example of how my initial code snippet could go horribly, horribly wrong. Though to be honest, it's something I might be comfortable living with, if only because a monster that revives itself upon death will always need to run out of MP before the battle ends, anyway (unless, I suppose, if you want the player to run away, or use some specific means of avoiding the death counter, like Snare or insta-kill stuff). In this example, I think it may be more appropriate to consider the reactive script itself to be the bug. Maybe.

well, my example script was kept short for simplicity and to show a worst-case scenario.  worst case isn't necessary to have bugginess.  suppose we provided an escape hatch from the infinite loop, in the form of Life only being cast some fraction of the time, or it targeting random allies as opposed to always oneself.

we'd no longer have the sustained, wide open wormhole to the other dimension.  but we could still have a given enemy doing 2, 3, or more final attacks in a given batch -- so a more fleeting wormhole, limiting those Gamma Quadrant menaces and setting the stage for a thrilling 7th season. Wink  these smaller yet multiple quantities are still enough to cross into "erroneous" territory, albeit not disastrous.

the way i see it:

1) Square intended only one Command 1Fh per entity per counterattack and periodic damage/healing batch.
2) Square also intended enemy finals to get different, preferential treatment from normal counters, as evidenced by the "catch-all"/"death override" established in Function C2/4C5B (and processed further in C2/4BF4), which ascribes lofty omni-counter powers to $3A56.

a strict adherence to #1 can undermine Square's goal in #2.  the result is also intuitively dumb, weird, and anti-climactic, because unlike having one less FC 05 command (for instance) execute during the course of battle, a monster that departs without a final is missing its single chance at a signature send-off (and in many cases, its most lethal counterattack, or attack period).  it's a blatant loose end, incomplete story, etc. -- often an effect distinct from that of reducing a more commoditized counter.

thus, we need to situationally waive/bend #1 to allow the foe to execute its final attack once.  this goal informed my patch's title (contained in the download URL in my 12/10/20 post).

at the same time, a change that subverts #1 without being necessary to provide that chance (singular) is excessive in my view; the climax has already happened, the potentially nasty attack has been uncorked, and the foe has said its piece (sometimes literally, if it's a boss with dialogue).  iow, the border between 0 and 1 successful FC 12 executions is significantly different from that between some non-zero N and N+1 executions, and merits unique treatment.

thus, my patch does the bare minimum to permit the monster's final.

now, i've keyed in on the importance of ensuring a once-per-battle occurrence, as it's unique and distinct from multiple-per-battle ones.  i get that lines can also be drawn between once- and multiple- per-monster-life-cycle commands.  however, once we've seen Final #2 or beyond, it's becoming commoditized to where it's hard to justify breaking Square's One Command 1Fh Per Batch rule (and allowing the back-and-forths, sometimes indefinite, that they were apparently trying to avoid), as what we're enabling is no longer rare or priceless.  worth executing when no rule-breaking is needed?  of course.

i do think the fact that the game won't check for battle end at C2/47ED (or process prerequisites at C2/5C73) until this batch is done processing probably figures into Square's desire to put a firebreak on the batch.  iow, INTER-batch shenanigans apparently bothered them less than INTRA.


Bropedio Wrote:
assassin Wrote:
Bropedio Wrote:(Edit: On second thought, this would still function correctly, as Enemy B's next Sap tick will trigger its reactive script, due to $3A56).
will C2/5018 allow this?  i think the omni-counter might have to wait until *another* entity's periodic damage/healing occurs, or an entity takes a normal turn.

Even if $5018 aborts the command, I think the calling routine will still proceed to the "prepare counterattacks" routine (at $140C), which will check for newly dead entities anyway.

ah, good point.

to be sure: by "next Sap tick", did you mean "the pending Sap tick [that] will prevent Enemy B from queuing its reactive script"?

at first, i thought you meant the one after that. in that case, i was gonna say that the next tick opportunity only pertains to start-up, inherent Seizure (or Regen).  if Seizure/Regen is given mid-battle, or Poison is given at any time (as it's not a permanent start-up status; see C2/2675), dying should remove the status, and the next tick never happens -- making us dependent on other entities for the omni-counter.


assassin Wrote:
Bropedio Wrote:My next thought is -- why not remove the check at $4CA2 altogether? If a duplicate reaction script is queued, it will inevitably be aborted due to the $33FC flag. And the $3A56 flag will only override the $33FC flag once (per my changes above).

the queuing does create needless junk for C2/4B7B and C2/4BF4 to process.

there's another reason, from my October 2015 notes and semi-coherent to December 2020 me, which i'm in the process of deciphering.

made some progress about a month ago.  here goes:

Code:
  - dropping "not queueing Command 1Fh when one already in buffer" restriction when "one Command
    1Fh per batch" limit still in place.  can cause potential problem if two non-dead entries
    occurring now, then would-be dead entry in future part of batch gets screwed since we already
    had one execution.
     - not exactly foreseeable
     - should have no problem if one of the "now" entries is would-be dead, since death will
       carry thru to execution of non-dead-when-queued Command 1Fh.

unfortunately, many of my notes require me to remember at least 40% or so of what i was talking about, and then they "complete the puzzle".

i'll elaborate on the carry through, as that's a part i actually DO remember. suppose you have a sequence of queued and potentially executed Command 1Fh counterattacks, looking like this chronologically:
trigger1 trigger2 counter1 counter2

("trigger" is the act causing the Command 1Fh to be queued, and "counter" is the actual executing of the Command 1Fh.)

if the entity isn't dead at trigger1 but is dead at trigger2, and there is a limit of 1 execution per batch, we might be apprehensive about one of those being wasted on a non-dead (as in non-final) counter.  however, we actually won't be cheated when counter2 is squelched by the per-batch limitation, because, due to chronology, counter1 observes the conditions that were prevalent at trigger2 (i.e. the entity being dead).  this is even though it's arguably more *logical*, imho, for it to inherit the conditions prevailing during trigger1.

that carry-through elaboration explains why the 3rd bulletpoint is a counterexample to the concerns in the 1st.  what ELUDED me is why the 1st bulletpoint is any worse off for allowing two queued counters than if just one was permitted.

my best stab at it:
allowing trigger2 to queue our 2nd counter somehow prolongs Batch N, to where a future 3rd counter is now considered part of that same batch alongside Counter #1 -- and where it wouldn't have been had counter2 been stifled, and our queue kept shorter.  that is, if the 3rd counter were put into Batch N+1, it would've been free to execute.  i think the following sequence could result in a wasting of Counter #3:
trigger1 trigger2 counter1 trigger3 counter2 counter3
(where death occurs like halfway through sequence or later)

yeah, trigger3's timing is pretty specific and tight.  and there are limited candidates for trigger3: probably Special Action queue stuff (e.g. status auto-expiration).

it's also possible that i reasoned myself into a pretzel back in October 2015; e.g.: how trigger2's proximity to trigger1 might screw former ==> why counter1 saves the day ==> when later death means it can't.  but i think i'm onto something 5+ years later, and it does seem ever-so-slightly familiar. ;P

------

to be thorough, i'm gonna try answering a sibling of your earlier question:

Bropedio Wrote:
Bropedio Wrote:I also am wondering whether your patch at $4CA2 would have any effect at all. Under what circumstance would a "normal" attack be preparing its counterattacks while the counterattack queue had pending periodic damage? Wouldn't the counterattack queue have to be empty before the "normal" attack queue got checked?
OK, I thought through the above, and figured out a scenario that would break. If an attack triggers a counterattack from Enemy A, and Enemy B gets a sap queued while the attack executes, then if Enemy A's reaction targets Enemy B, then the pending Sap tick will prevent Enemy B from queuing its reactive script, even if it is killed by the counterattack. (Edit: On second thought, this would still function correctly, as Enemy B's next Sap tick will trigger its reactive script, due to $3A56).

the sibling is: what about a case where the payload of a counterattack (as opposed to the invoking Command 1Fh) is in the queue?  this is potentially problematic because if the entity didn't die until more recently, the counter has missed its chance to be of the coveted FC 12 variety (whereas a waiting Command 1Fh will adapt to a since-occurred death).  (i'm disregarding the lingering $3A56 flag and its bail-out powers.)

what's coming to mind (relying on memory more than starting from the ground up):
1) monster with a string of consecutive counters (no FEs or FDs between them in script), a non-final (as in, not last in queue) one of those fatally hitting itself.
2) contrasting #1, monster with a string of consecutive counters, 2 of those hitting a different monster, 1 fatally.  this is an example of a present Command 1Fh blocking a queuing.
3) something with a character multi-targeting enemies, multiple or all of them having a counterattack (Command 1Fh) triggered, and then the first acting enemy hitting one that was due to act later?  argh; i thought i'd come up a variant of #1 way back when, but all i can see it accomplishing today is a variant of the more benign #2.  it's possible the memory is from a time where i thought things were more FIFO, so if Monster A expanded its queue (e.g. from hitting itself on a counter), that Monster B's initially-triggered counterattack would run before these additions.

you were good at coming up with that blocked-by-queued-Sap scenario.  any idea of more qualifying cases here?  in particular, something resembling #3 and multi-targeting. Laugh
Quote  

#7
Posts: 16
Threads: 3
Thanks Received: 0
Thanks Given: 0
Joined: May 2019
Reputation: 2
Status
None
Quote:to be sure: by "next Sap tick", did you mean "the pending Sap tick [that] will prevent Enemy B from queuing its reactive script"?

Ah, I was not clear there. Yes, that's what I meant.


Quote:my best stab at it:
allowing trigger2 to queue our 2nd counter somehow prolongs Batch N, to where a future 3rd counter is now considered part of that same batch alongside Counter #1 -- and where it wouldn't have been had counter2 been stifled, and our queue kept shorter.  that is, if the 3rd counter were put into Batch N+1, it would've been free to execute.  i think the following sequence could result in a wasting of Counter #3:
trigger1 trigger2 counter1 trigger3 counter2 counter3
(where death occurs like halfway through sequence or later)

Since the counterattack queue is processed in between each "normal" attack, the only possible "trigger2" would be special actions or another entity's counter, neither of which can trigger a counter unless the target has died as a result. So in this example, "counter1" would execute with the enemy dead, allowing the full reactive script, including on-death. It would also clear "has died" and "no counter yet" flags, so both "counter2" and any potential "counter3" would be ignored. It seems like the behavior is the same whether 4CA2 is checked or not.


Quote:what's coming to mind (relying on memory more than starting from the ground up):
1) monster with a string of consecutive counters (no FEs or FDs between them in script), a non-final (as in, not last in queue) one of those fatally hitting itself.
2) contrasting #1, monster with a string of consecutive counters, 2 of those hitting a different monster, 1 fatally.  this is an example of a present Command 1Fh blocking a queuing.
3) something with a character multi-targeting enemies, multiple or all of them having a counterattack (Command 1Fh) triggered, and then the first acting enemy hitting one that was due to act later?  argh; i thought i'd come up a variant of #1 way back when, but all i can see it accomplishing today is a variant of the more benign #2.  it's possible the memory is from a time where i thought things were more FIFO, so if Monster A expanded its queue (e.g. from hitting itself on a counter), that Monster B's initially-triggered counterattack would run before these additions.

Example One:
1. Enemy A gets hit, queues #1F
2. A's #1F fires, queuing 2 attacks -- 1st hit to itself, 2nd elsewhere
3. Counter1 hits and kills Enemy A, setting 3A56
4. Prepare-Counter routine runs -- since 3A56 is set, attempt to add #1F to queue
5. Without $4CA2, the new #1F is added to the queue.
6. Counter2 fires, prompting another "Prepare-Counter" routine.
7. Since 3A56 hasn't been cleared yet, another #1F is added to the queue.
7b. Note that even if $4CA2 had blocked step 5, step 7 would add #1F here.
8. The first of two #1Fs runs, and is allowed to process, due to 3A56.
9. The second #1F runs, but aborts due to $3A56 and $33FC being set/cleared by step 8.

Example Two:
1. Enemy A gets hit, queues #1F
2. A's #1F fires, queuing 2 attackes -- both hit Enemy B
3. Hit 1 kills B, queuing #1F
4. Hit 2 doesn't kill B, but 3A56 is still set, so it queues #1F, too
4b. If $4CA2 blocks step 4 above, only one #1F will have been queued
5. Enemy B runs #1F, allowing full reactive script due to 3A56
5b. Note that if Hit 2 had been the fatal blow (instead of Hit 1), step 5 is unchanged
6. If a second #1F was queued, it is aborted due to $3A56 and $33FC

I can't think of an example three that would differ substantially from the examples above. I'm still thinking $4CA2 can be removed safely, given my changes above, but it's not necessary to remove it, so I'd just assume leave it in, since it does keep the stack cleaner.

Thanks for taking the time to reply again!
  Find
Quote  
[-] The following 1 user says Thank You to Bropedio for this post:
  • assassin (01-22-2021)

#8
Posts: 377
Threads: 34
Thanks Received: 10
Thanks Given: 7
Joined: Dec 2018
Reputation: 18
Status
Moog
I tried Bropedio's approach and I ran into trouble with it; I don't know if it's an edge case or a conflict with his Informative Miss (which I believe alters some monster script reactional behavior).

Basically, I couldn't get Vargas to trigger his second phase from the "Below X HP" counter. I think it's because there's another invisible monster in my battle formation making status checks and counters every time it takes a turn. It might have been flushing any other viable counter trigger in the monster scripts. Reversing the code in the first post resolved this hangup.

Can't get any more technical than that because I'm having trouble wrapping my head around how all of this queuing up works. It's just something you might want to look out for.
  Find
Quote  

#9
Posts: 200
Threads: 1
Thanks Received: 10
Thanks Given: 0
Joined: Oct 2015
Reputation: 18
Status
None
- do you have any other patches applied?  anyway, from kjinn22's post:
https://www.ff6hacking.com/forums/thread...l#pid39311 ,

it seems similar things can happen with just Informative Miss applied.

- the Vargas script skipping stuff is familiar, so this ancient exchange could be worth a read:
http://mnrogar.slickproductions.org/phpB...477&p=6627

dunno whether you have Master ZED's FC 05 bugfix applied, but the subject matter seems to overlap.  i spent 50 posts or so proposing asm fixes with their own shortcomings, eventually coming up with a pretty good alpha, probably untested approach that uses a new RAM variable and modifies Function C2/35E3.  meanwhile, Hollywood Narrator aka C.V. Reynolds avoided the problem by using monster script reorderings many months prior. Tongue
Quote  

#10
Posts: 377
Threads: 34
Thanks Received: 10
Thanks Given: 7
Joined: Dec 2018
Reputation: 18
Status
Moog
(01-20-2021, 09:44 AM)assassin Wrote: - do you have any other patches applied?  anyway, from kjinn22's post:
https://www.ff6hacking.com/forums/thread...l#pid39311 ,
Yeah, I'm aware of that bug.  It only applies to the "When Attacked" condition FC 05, so now that you say it I imagine that is a conflict with the FC 05 bugfix (I did use C.V. Reynold's bugfix compilation and it does have that patch in it).  I hid the bug by swapping out the generic counter command with FC 01 conditionals which check the last command used instead.

It's still concerning that reordering the checks would make other FC conditionals fail, though.  I wonder how the two might be interacting.  I might anti-patch MasterZED's bugfix and try it again, when I get some time.
  Find
Quote  



Forum Jump:

Users browsing this thread: 1 Guest(s)


Theme by Madsiur2017Custom Graphics by JamesWhite