DSB - CSB

This forum is for the Lua scriptable clone of DM/CSB called Dungeon Strikes Back by Sophia. Use DSB to build your own highly customised games.

Moderator: Sophia

Forum rules
Please read the Forum rules and policies before posting.
Post Reply
User avatar
Paul Stevens
CSBwin Guru
Posts: 4318
Joined: Sun Apr 08, 2001 6:00 pm
Location: Madison, Wisconsin, USA

DSB - CSB

Post by Paul Stevens »

I fetched the DSB version of CSB from the DSB Download page.
I found it to be quite different from the original CSB. I had to
change my tactics completely in the opening Worm Room and
found that I had to change my entire game strategy later in the
game. I suspect that DSB's DM has the same problem but I did
not try it.

The Worms had zero or nearly zero delay between movement and
attack. The original CSB worms had parameters which the
encyclopaedia calls 'Animation Speed'. See:

http://dmweb.free.fr/?q=node/1363

and which I called "AdditionalDelayFromMoveToAttack()'.
This parameter is very different for a Worm and a Couatl.
Ignoring this additional delay (As DSB appears to do) makes
Worms behave very much the same as Couatls. This is not
true to the original CSB.

The Worm pairs in DSB separate into two individual Worms.
This, also, is not like the original CSB and can change a fight
rather dramatically.

I have no objection to this DSB game. But perhaps it ought not to
be called "Chaos Strikes Back". Maybe it should have some name
that reflects its character a bit better:

"Chaos Strikes Back with a Vengeance"
"Chaos Strikes Back for Experts"
"This Ain't Chaos Strikes Back" (Like "This Ain't Star Trek")
"A Meaner Chaos Strikes Back"
"An Improved Chaos Strikes Back"
"Chaos Strikes Back - Squared"
or something
User avatar
Sophia
Concise and Honest
Posts: 4239
Joined: Thu Sep 12, 2002 9:50 pm
Location: Nowhere in particular
Contact:

Re: DSB - CSB

Post by Sophia »

Well, to be technical about it, the game is called "CSB for DSB," and the title screen says "Dungeon Strikes Back," not "Chaos Strikes Back." If only someone had made an exact reproduction of the Atari ST version playable on modern computers... :mrgreen:

But anyway...
Paul Stevens wrote:The original CSB worms had parameters which the
encyclopaedia calls 'Animation Speed'. See:

http://dmweb.free.fr/?q=node/1363

and which I called "AdditionalDelayFromMoveToAttack()'.
I downloaded CSBwin_SRC_20160325.zip which seemed to be the newest version of the source code and could not find the string 'AdditionalDelayFromMoveToAttack' anywhere in it, or any permutation that I tried (additionalDelay, additional_delay, etc.) So, I'm not really sure what you're talking about.

It may not be relevant, anyway. I think the 'Animation Speed' parameter is the speed that the monster fidgets around on the title, and the parameters you were concerned with at the moment are 'Movement Duration' and 'Attack Duration,' which are reflected faithfully in DSB.

So what's wrong with worms? Trusting people's vague memories of CSB, honestly. I had a couple of early DSB testers insist to me that worms occasionally jumped out at you and attacked immediately after they moved, so, finding CSBwin's AI code to be mostly incomprehensible to me (which isn't your fault at all, reverse-engineered code is never going to be pretty) I just shrugged and gave them that ability. If we're quite sure this is not a thing and is just another case of knowing somebody who knows somebody who made Ful Bombs, I'll shrug again, and remove it.

As for monsters grouping up, I feel like monsters not being able to change their groups was nothing but a technological limitation in the original engine. Casual players probably weren't even aware that monsters couldn't do that. I understand that it's strictly not "authentic," but I feel like it's inauthentic in a way that enhances the game experience without detracting from the feel of DM.
User avatar
Paul Stevens
CSBwin Guru
Posts: 4318
Joined: Sun Apr 08, 2001 6:00 pm
Location: Madison, Wisconsin, USA

Re: DSB - CSB

Post by Paul Stevens »

Remember....I wasn't asking that DSB be changed. Only that the
name reflect its additional difficulty. (There are some folks who
have complained about the difficulty of the original ;-) )
could not find the string 'AdditionalDelayFromMoveToAttack' anywhere
That is perfectly understandable. I renamed a lot of things as I
attempted to determine how CSB added those delays. The old
names were "word20 bits 4_7 and word 20 bits 8_11". That's
how much I knew about it on my previous attempts to understand.

It would seem that you like CSB for DSB as it is and are not striving
to make it more faithful to the original. So I am going to
save my energy to finish the DSB version. It is different enough
to be interesting. I have not yet obtained even a single Corbum.

But....if someday you do decide to address this issue then I will
be happy to take time to outline the code that handles the timing
and explain how I think it works. And I'd be happy to post the
more recent code.

I think I have said this before but I will say it again. DSB is a
remarkable machine. I recommend it to anyone wanting to
make a DM-like game. I am not asking that it be changed.
User avatar
Paul Stevens
CSBwin Guru
Posts: 4318
Joined: Sun Apr 08, 2001 6:00 pm
Location: Madison, Wisconsin, USA

Re: DSB - CSB

Post by Paul Stevens »

isn't your fault at all, reverse-engineered code is never going to be pretty
You give me too much credit. There was no engineering taking place.
I copied the original instruction-for-instruction even though I had no
idea what those instructions accomplished. The code is still rife with
68000 CPU register names!
User avatar
Sophia
Concise and Honest
Posts: 4239
Joined: Thu Sep 12, 2002 9:50 pm
Location: Nowhere in particular
Contact:

Re: DSB - CSB

Post by Sophia »

I admit that I'm not aiming for 100% accuracy, but I do want to try to replicate the experience of playing the original, which means that getting stuff like monster timing accurate is actually a high priority because it's so essential to the feel of DM. As such, I would very much like to remedy any discrepancies between DSB and the original in this regard. My current understanding of how DM works (and thus, how DSB works) is quite different from what you've said, so I'm definitely interested in what you think is going on.

My understanding of the CSB code (and what is currently posted on the Encyclopaedia) is that the number of ticks until a monster acts again after movement is stored in byte 6, called movementTicks06 in CSBwin, and the number of ticks until a monster acts again after attacking is stored in byte 7, called attackTicks07 in CSBwin. I can't follow the CSB AI in detail, but in searching for these values, they seem to be used in the way we'd expect; attackTicks07 is much easier to follow because it's only used in two places, and it's pretty clearly used to set the monster's timer (along with a small random factor) after a check if the monster is attacking:

Code: Select all

if (pI16A2->singleMonsterStatus[D4W].TestAttacking())
    {
      mmr.Setflg(TT29to41_attacking);
      TIMERTRACE(0xd8a6);
      i_60 = NextMonsterUpdateTime(pI16A2, D4W, false);
      w_90 = STRandom0_3();
      D4W = (UI8)(mtDescLocal.attackTicks07); // uByte7);
      //timer_70.timerTime += uw(D4W + w_90 - 1);
      timer_70.Time(timer_70.Time() + uw(D4W + w_90 - 1));
My current understanding is that word20 does not have anything to do with how often a monster moves or attacks. As I mentioned before, I think (and this is what the Encyclopaedia says, too, for whatever that's worth) is that the bits we're looking at are simply the delay for how often a monster fidgets around or mirrors itself, and this is purely cosmetic. The main place these bits of word20 get used is in NextMonsterUpdateTime, which handles the fidgeting around and mirroring, and perhaps some other status information, but it does not seem to do anything substantive with the monster's AI, at least that I could see. I also suspect that these only being 4-bit values rules them out for having anything to do with timing movement or attacking, because monsters like screamers and rock piles move around very slowly, but, once they start attacking, they're able to do at a decent pace. The difference between their movement speed and their attacking speed is definitely more than 15 ticks, so it's quite unlikely 4 bits of word20 could contain anything useful there.
User avatar
Paul Stevens
CSBwin Guru
Posts: 4318
Joined: Sun Apr 08, 2001 6:00 pm
Location: Madison, Wisconsin, USA

Re: DSB - CSB

Post by Paul Stevens »

I will try to tell you what I know in as few words as possible.
Then we can begin pointing to particular points in the code
to substantiate (or disprove) my understanding.

There are two timers active for each monster: Attack Timer
and Move Timer.

When the Move Timer expires, the monster makes some sort of
movement. It then examines the Attack Timer and ADDS one of
those 4-bit fields to it (AdditionalDelayFromMoveToAttack).
Similarly for AdditionalDelayFromAttackToMove. Each of these timers
is what the comments in my code refer to as the 'Alternate Timer'.
There is also a random possibility of adding one additional tick
to the Alternate Timer.

As a result, the worms are inhibited from attacking for 5 or 6
ticks after any movement. Moreover, this is what I see when
I play CSBwin's version of the game.

I will post the source code tonight. I cannot do it from here.
My CSBwin code is kept on a Windows XP system elsewhere.
(It's a long story.) After you look at it, we can discuss our
understanding of what it says in detail.
Sophia wrote:finding CSBwin's AI code to be mostly incomprehensible to me
Don't feel lonely. Do you remember the original simplified shape of the code
before I rearranged it:

http://www.dianneandpaul.net/CSBwin/doc ... ster_A.gif
User avatar
Paul Stevens
CSBwin Guru
Posts: 4318
Joined: Sun Apr 08, 2001 6:00 pm
Location: Madison, Wisconsin, USA

Re: DSB - CSB

Post by Paul Stevens »

I just discovered this. I don't remember writing any of it.
http://dmweb.free.fr/?q=node/1135
User avatar
Paul Stevens
CSBwin Guru
Posts: 4318
Joined: Sun Apr 08, 2001 6:00 pm
Location: Madison, Wisconsin, USA

Re: DSB - CSB

Post by Paul Stevens »

User avatar
Sophia
Concise and Honest
Posts: 4239
Joined: Thu Sep 12, 2002 9:50 pm
Location: Nowhere in particular
Contact:

Re: DSB - CSB

Post by Sophia »

Paul Stevens wrote:There are two timers active for each monster: Attack Timer
and Move Timer.
I don't think this is the case.

There are definitely two distinct timer 'modes' that the monster AI knows about, which you called A and B, and I'll use those names because I don't really understand all of what they're doing either. Their functionality does not at all seem like they are distinct modes for movement and attacking, though, as I'll explain.

Let's try to unravel this by assuming we're starting at the beginning. When you first enter a level (or load a savegame), ProcessMonstersOnLevel is called. This goes through every monster on the level, and, among other things, destroys every monster group's timer and creates a new B-timer associated with that monster group. ProcessMonstersOnLevel also calls AttachItem16ToMonster which calls NextMonsterUpdateTime and throws away its return value. It does this because a side effect of NextMonsterUpdateTime is to tweak the monster's visual properties, like shift the monster around on the tile and possibly randomly flip it, and it's the only place in the code where this happens, so this function is called to randomize the monsters' positions on the tile when entering a new level.

At this point, the monster group is "alive" and the only timer that has been created is a B-timer. This in itself doesn't disprove anything, because it could very well be that the monsters don't get an attack timer until they want to attack. So, what happens when these monsters want to attack? Walking up to a monster calls ProcessTimers29to41 with a timer type of TT_M1, which gets turned into TT_31 by InitialChecks. The handler for TT_31 checks if the monster is afraid (StateOfFear5) or already enraged (StateOfFear6) and if not, it deletes all the timers for that monster group and calls MaybeDelTimers_Fear6_TurnIndividuals. This enrages the monster group (sets their "fear state" to 6), then iterates over the entire monster group and possibly turns them to face the party. It then creates an individual timer event for each monster in the group, also calling NextMonsterUpdateTime so they might fidget around or flip at the same time. Depending on the results of some randomness, this might be an A-timer or a B-timer. All the A-timer for a monster who already wants to attack does is call NextMonsterUpdateTime and trigger the B-timer after another short delay returned by that function, so let's just, for simplicity, assume that it's a B-timer.

This means the time delay returned by NextMonsterUpdateTime is used for more than I thought it was, and is not purely cosmetic, because the delay returned is used as a sort of 'reaction time' to determine when the monsters might get to react... which is pretty interesting! However, the important thing here is that, while there's now a timer for each individual monster as opposed to one for the entire group, there's still only one timer for each monster. What happens when that B-timer fires? That is deep inside ProcessTimers29to41, starting at around line 3753. The first big check is that we're not in a group B-timer; we aren't, since we're firing off events for all monsters in the group individually. The next check is if the monster is afraid, i.e., in StateOfFear5. If it is, it checks if the group has more monsters in it and deletes all their timers if there are, and then calls Try_To_Move, presumably to run away. The next check is whether the monster is attacking, via TestAttacking. This does some interesting stuff but for this test case our monster is not attacking, so, we go to the else, and do a whole bunch of checks I don't really understand to see if the monsters are in the right position and able to attack. At line 3902, it checks whether the xDistance or yDistance is 0, which means the monster can attack. It then does some more stuff I don't understand (this is a bit of a recurring theme) but, at line 3999, we call MonsterAttacks. This is the only place in the code that MonsterAttacks gets called, so, when a monster's going to attack, it happens right here!

However, in the various else blocks for if the monster can't attack, if it's out of position, or, as mentioned above, if it's too frightened to attack, then movement happens. The various timers still in the queue for the individual monsters get deleted via ClearAttacking_DeleteMovementTimers and a single new movement timer gets set. From this, it's clear (well, as clear as any of this can be...) that a single firing of a B-timer event is able to result in either attacks or movement, depending on circumstances. There is not a separate attack timer and movement timer. There is some weirdness between A-timers and B-timers, but that's different, and that's all I can really say about it because there's still a lot I don't understand. In general, A-timers seem like they just change the monster's state a bit, while all the actual movement and attacking happens in B-timers. I'm pretty sure that monsters don't have concurrently running A-timers and B-timers, either, because it seems like timeUntilAlternateUpdate is mainly used by an A-timer to tell when to schedule the next B-timer.

In all this digging around, I think I found a bit more out about what the delay generated by NextMonsterUpdateTime is actually for, too. As mentioned above, it seems to be a sort of reaction time. When MonsterAttacks gets called, its return value is passed to NextMonsterUpdateTime, which stores a delay in i_60. As a side effect, since NextMonsterUpdateTime is where various graphical tweaking happens, the monster's attack animation gets set. This is important, because, while you were correct in your feeling that the small number returned by NextMonsterUpdateTime is used to set a timer after a monster attacks, the monster's attack animation is still set, so the next timer event had better unset the attack animation or things will look pretty stupid, and it had better do it quick.

A small number of ticks later (after whatever delay NextMonsterUpdateTime returns, possibly with some other tweaks I don't really understand) the monster's B-timer fires again. This time, the TestAttacking check is true, because the monster is still in its attacking animation. At that point, NextMonsterUpdateTime is called again, to turn off the monster's attacking animation, storing its result in i_60, while attackTicks07 is added to timer_70's time, and then Set_Monster_Timer gets called. What I believe this ends up doing is scheduling an A-timer some short number of ticks in the future. The A-timer doesn't do much, and the value of attackTicks07 got stored as the timer's UByte8 (i.e., timeUntilAlternateUpdate) so after that, the B-timer goes off and the monster gets another turn. That means that while NextMonsterUpdate's return value is definitely used, it's mostly just to schedule animation stuff and various status updates and whatever else the A-timer does, and it's ultimately still basically moveTicks06 and attackTicks07 that control when the B-timers go off and the monsters get actual turns.

With all the different A-timers and B-timers and such going off at various times, most of them calling NextMonsterUpdateTime, monsters fidget and shift around because doing that is a side effect of NextMonsterUpdateTime. This also means that if a monster moves a little or flips to its mirror image version, but doesn't do anything else, it probably just had an A-timer event. Which is kind of interesting that you can see that, I guess! It almost seems like the A-timer is there solely so the monsters can animate, and run a few simple checks at the same time, because the events do so much less than the B-timer.

I feel like I understand the DM AI more than I ever have, and I still don't really understand it.

When I started delving into this, it was 8:00. I figured I'd spend an hour or two on it and call it a night. It's now 1 am! I think I'm done. :mrgreen:
User avatar
Paul Stevens
CSBwin Guru
Posts: 4318
Joined: Sun Apr 08, 2001 6:00 pm
Location: Madison, Wisconsin, USA

Re: DSB - CSB

Post by Paul Stevens »

Oh, my God! It will take me days to understand all that you
have said. Perhaps weeks. Perhaps never. We have been at this
for decades so I guess it is OK if it takes a while.

I have a crises in my alternate role as Dungeon Craft Guru. I
have to tend to that first. But I will be back.

Thank you very much for your work. Amazing work.
User avatar
ChristopheF
Encyclopedist
Posts: 1537
Joined: Sun Oct 24, 1999 2:36 pm
Location: France
Contact:

Re: DSB - CSB

Post by ChristopheF »

In your research to understand the creature AI in DM, you may, or may not, be interested to take a look at my own ReDMCSB source code (latest work-in-progress here: http://dmweb.free.fr/Stuff/ReDMCSB_WIP20160915.7z)
In the archive, open the SOURCE folder and search in GROUP.C for the function named F0209_GROUP_ProcessEvents29to41.
I gave names to variables and constants and made comments from my own understanding (already a long time ago). However I may be wrong because I don't think I fully understand the AI either. I'd be happy to fix any mistakes I made that you may find.
However, I can guarantee that the code in this function is 100% faithful to the original, because once compiled, the machine code produced is the same as in the original executables.
In fact, this function is the reason why I started rewriting source code and compiling it with the original compiler. I did so to make sure the source code I wrote was 100% accurate so that I would not lose time trying to understand code that may contain translation mistakes.
User avatar
Sophia
Concise and Honest
Posts: 4239
Joined: Thu Sep 12, 2002 9:50 pm
Location: Nowhere in particular
Contact:

Re: DSB - CSB

Post by Sophia »

ChristopheF wrote:In your research to understand the creature AI in DM, you may, or may not, be interested to take a look at my own ReDMCSB source code
Yes, thank you! I actually did look at it a bit while I was doing this analysis. It was a valuable resource as a "second opinion." Having two different pieces of code that looked rather different but (ostensibly) do the same thing often helped shed light on what the code was actually for. :)
User avatar
Paul Stevens
CSBwin Guru
Posts: 4318
Joined: Sun Apr 08, 2001 6:00 pm
Location: Madison, Wisconsin, USA

Re: DSB - CSB

Post by Paul Stevens »

I made a tiny dungeon and put a single worm into it on
level 1. Then I turned on the timer and AI traces and took
the stairs from level 0 to level 1.

Now I am going through the traces one timer at a time
and making comment in the traces as to what I think
is happening. Later, I will turn to the code itself to
explain what I see in the trace that I don't understand.

But this I can say unequivocally: It is easier for me to
understand all of this if I envision two timers for each
group or single monster. There is only one 'physical'
timer running but there are two 'virtual' timers. Byte
8 of each timer entry provides the delay for the
'alternate' timer. For example: let us suppose that
at time 0 the A timer is set to expire at 10 and the B
timer is set to expire at 15. Then the 'physical' timer
will be set to type A and set to expire at time 10.
Byte 8 will be set to 5.

At time 10 the physical timer A expires. Let us say
that we want to set another A timer to expire at time 13.
Then a physical A timer will be set to expire at time 13
and Byte 8 will be set to 2.

At time 13 the A timer expires. Let us say that we want
another A event at time 21. Then a B timer will be set
to expire at time 15 and Byte 8 will be set to 6.

This is what the code does. It manages to keep two
timers going CONSTANTLY.....both are always active.
But it does this so as to use only one physical timer
and one 'alternate' timer. When a level is entered, both
timers are started; there is no way to start only one!
When a level is entered it appears that Byte 8 is set
to zero (at least that is what happened in my test)
so that both the A and B timers will expire simultaneously
the first time around.

Edit:
Ooooops....I forgot to say that Sophia's analysis has been
very helpful. I have yet to grasp all of it so as to be able
to agree or disagree, But I, too, am beginning to feel
more comfortable with this aspect of CSB. I think we are
nearing a critical point in our mutual understanding.
User avatar
Sophia
Concise and Honest
Posts: 4239
Joined: Thu Sep 12, 2002 9:50 pm
Location: Nowhere in particular
Contact:

Re: DSB - CSB

Post by Sophia »

Paul Stevens wrote:But this I can say unequivocally: It is easier for me to
understand all of this if I envision two timers for each
group or single monster. There is only one 'physical'
timer running but there are two 'virtual' timers. Byte
8 of each timer entry provides the delay for the
'alternate' timer. For example: let us suppose that
at time 0 the A timer is set to expire at 10 and the B
timer is set to expire at 15. Then the 'physical' timer
will be set to type A and set to expire at time 10.
Byte 8 will be set to 5.
I agree with this, and it's a good way to think about it.

However, this is where I'm less clear...
Paul Stevens wrote:This is what the code does. It manages to keep two
timers going CONSTANTLY.....both are always active.
But it does this so as to use only one physical timer
and one 'alternate' timer. When a level is entered, both
timers are started; there is no way to start only one!
When a level is entered it appears that Byte 8 is set
to zero (at least that is what happened in my test)
so that both the A and B timers will expire simultaneously
the first time around.
Does this mean that a B-timer with a uByte8 of 0 will instantly create an A-Timer that expires on the very same tick? My reading of the code was that the "virtual" A-timer didn't exist until the first time the B-timer expired and the flip-flopping started, but I'm not totally sure on that so if the trace shows that's what's happening I certainly won't argue.
User avatar
Paul Stevens
CSBwin Guru
Posts: 4318
Joined: Sun Apr 08, 2001 6:00 pm
Location: Madison, Wisconsin, USA

Re: DSB - CSB

Post by Paul Stevens »

If Byte 8 exists in a (B) timer entry then the virtual (A) timer is
active. And, since Byte 8 always exists (There are no 'undefined'
values), then both timers are active. You could think about it in
other, equivalent, ways but so far my way of thinking about has
led to no surprises.

Timer (B) code always is cognizant of the timer (A) presence
and vise versa.

The code could easily (and patiently) be rewritten to use two
physical timers with timer indexes to connect the two. And
there would always be two such timers active.

That's my current understanding.
User avatar
Paul Stevens
CSBwin Guru
Posts: 4318
Joined: Sun Apr 08, 2001 6:00 pm
Location: Madison, Wisconsin, USA

Re: DSB - CSB

Post by Paul Stevens »

I posted a discussion and some scribbles at:

http://dianneandpaul.net/CSBwin/documen ... MonsterAI/

There is also a small game with playfile.log that I used to
help me understand a bit of what is going on. The major
result for our current purposes is that the Worms in CSB
always seem to pause after a move and before an attack
and I think I know what part of the code accomplishes this.

I refactored a lot of the monster AI to help me see what parts
are used by the various timers. If anybody wants those
sources, I'll post them, too.
User avatar
Sophia
Concise and Honest
Posts: 4239
Joined: Thu Sep 12, 2002 9:50 pm
Location: Nowhere in particular
Contact:

Re: DSB - CSB

Post by Sophia »

This is good stuff!

I don't think we've figured out how DM works 100% but I've definitely gained enough insight from our research and our discussions that I'm able to revise DSB to hopefully exhibit much more authentic behavior regarding monster movement timing.
User avatar
Paul Stevens
CSBwin Guru
Posts: 4318
Joined: Sun Apr 08, 2001 6:00 pm
Location: Madison, Wisconsin, USA

Re: DSB - CSB

Post by Paul Stevens »

I don't think we've figured out how DM works 100%
Goodness! No! I have ignored completely the three virtual
timers (timeFunc < 0) that do some processing and then
create timers (timefunc = 29, 30, and 31), that are later
restored to negative values, that serve to delay
some of the effects associated with the 'virtual' timers.
These three 'virtual' timers all result from interaction with
the party such as encounters with missiles, the party bumping
into the monster, etc. I think that these three 'virtual' timers
are probably very important parts of the MonsterAI.
These timers masquerade as 'A' timers for some purposes
and, as such, probably affect the state of the monsters without
directly causing movement or attacks. But that is a guess.

Having said that I wonder if the two major timer types might
be usefully referred to as 'thought' and 'action'. It always seems
helpful to me if I can find appropriate words to describe
algorithms. ('FearFactor' might then become 'StateOfMind').
The term you used, 'Fidget', was already a big improvement
over my term, 'A').
Post Reply