This is the mail archive of the xconq7@sources.redhat.com mailing list for the Xconq project.


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]
Other format: [Raw text]

Major bug and what to do about it (long)


I've been doing some heavy game testing and found a large number of bugs. I
will check in fixes to most of them soon. However, one bug (or perhaps
rather a design flaw) is particularly insidious, and will require major
code surgery to fix. I would therefore like to get some feedback before I
proceed.

The problem has to do with the difference between unit views and real
units, and the two types of attacks that are possible (both for normal
attacks and fire actions, respectively).

Typically, the AI will look for possible targets near a unit and then set a
hit_unit task. This task specifies the position, type and side of the
target, but not (and this is important) the actual unit. The reason for
this is that the AI sees only unit views, which may be real or not.

So far so good. However, when do_hit_unit_task is executed, the kernel
cheats and looks up the actual unit. The reason for this is that both
do_attack_action and do_fire_at_action require a pointer to a real unit as
argument. A unit view or just a position will not do. This is because unit
views was a rather late addition to Xconq, which was grafted onto the
existing code.

The bug that I found works like this. First, the AI finds a target at
position (x, y) and sets a hit_unit task. However, when do_hit_unit_task
looks up the actual unit, it finds that the unit no longer exists or has
moved. The call to check_attack_action (or check_fire_at_action) fails and
the task itself also fails after 3 attempts (the latter restriction is a
recent hack by Eric who may have stumbled across the same bug).

Now the ball returns to the AI, who should find something else for our unit
to do. However, the AI still sees the same unit view and doesn't know that
the task failed, so it sets the same hit_unit task again. Since no acps are
consumed by failed tasks, this vicious cycle goes on until we hit the
ceiling on plan executions per turn (1000 attempts). At that point, our
unit is put into reserve mode, and rests until the next turn. This despite
the fact that it has not done anything and therefore not consumed a single
acp!

So what can we do about this? The obvious solution is to use
unit-independent versions of do_attack_action and do_fire_at_action, which
already exist in Xconq. They are called do_overrun_action and
do_fire_into_action, respectively.

Here is how these unit-independent actions work. The unit attacks (or fires
into) a cell (x, y). The code then attempts to hit each unit at (x, y) in
stack order. One round of ammo is consumed for each unit (whether you hit
it or not), so these kinds of attacks are more ammo-expensive then hitting
a single unit. OTOH, the same amount of acps are consumed at the end, so
you get a free shot at all units at (x, y) for the same acp cost as when
you attack a single unit! This is a huge advantage when you are dealing
with stacked units, as you are in many games. On the balance, I would
therfore say that these attack actions are more efficient than those who
target single units.

However, the AI does not use unit-independent attacks a lot. It relies
mostly on do_hit_unit_task, which targets single units. Hence the above
bug. In the human case, unit-indpendent normal attacks (overrun actions)
are favoured  by the interface code. Thus, clicking on an enemy unit
schedules an overrun action into that cell. To schedule an attack on a
single unit, you have to use the 'a' command instead. OTOH, if you do the
simplest fire command, i.e. press 'f' with a unit that can fire, you will
schedule a unit-dependent fire-at action. To do a fire-into action, you
must press 'ctrl-f'. So the interface code is not very consistent about
what it prefers.

I have given the above bug and its consequences some serious thought. Here
is what I think should be done:

1. We should eliminate the attack unit/attack position code split. Instead
of the four actions that are possible today, we should just have two:
do_attack_action and do_fire_action. These actions should be
unit-independent, i.e. work pretty much like the old do_overrun_action and
do_fire_into_action.

This would have a number of advantages. First, we could do away with unit
pointers in the combat code. The bug I just found would be fixed, and no
doubt many other bugs yet to be discovered. Writing new AI code would be
much easier without the code bifurcation. The interface could also be
simpler. For example, clicking on an enemy unit could schedule a normal
attack (overrun action) and right-clicking on it a fire action. No need to
use the keyboard at all. Yet another advantage with this scheme is that it
would make combat model 0 more similar to combat model 1, which only uses
overrun actions. This in turn would make it a lot easier to improve both
the AI and the combat code in the future.

2. I'm not sure, however, that do_attack_action and do_fire_action should
work exactly like do_overrun_action and do_fire_into_action do today.
Specifically, I don't like the fact that you get a free shot at every unit
in the cell for just one acp consumed, even if you have to pay for it in
ammo. It seems more logical that one attack (either normal or round of
fire) is just that, and that you consume 1 acp and 1 round of ammo each
time you attack. As a consequence, you should also be able to hit only one
unit (and possibly its occupants) each time you attack.

3. The question is then how to pick the unit to hit. I can see three
possibilities. The first is to hit units in stack order. This is easy to
implement and is therefore how things frequently work in the kernel, but I
don't think it is a good idea in this case. The second possibility is to
pick a unit at random, perhaps weighted according to unit-size-in-terrain.
The third possibility is to always pick the best defender, on the theory
that this is the unit that the defending side would send forward to meet
the enemy.

Combat model 1 already works like the third alternative (as does Civ2 on
which it is modelled). I think this would be a good way to handle normal
attacks also in combat model 0. This would make it even easier to deal with
the two co-existing combat models. The main difference between them would
remain the fact that a model 1 attack continues until death, while a model
0 attack continues only as long as the attacker wishes to continue and has
acps left.

4. However, I doubt that this is a good solution for fire actions, which by
their very nature are more random. In that case, I'm leaning towards a
size-weighted random target, as described above. In addition, the
probability of hitting something should be greater if there are several
units in the cell (or, in other words, the probability of hitting one unit
should not be reduced by the presence of other units in the same cell). One
could therefore imagine a scheme similar to do_fire_into_action where the
fire code tests all units in the cell. The main difference would be that
once a unit is hit, the iteration would stop, since the same round of ammo
cannot hit several different units. I think that this scheme would most
closely resemble the real life situation of artillery firing into a crowded
position occupied by several units.

It should be noted that fire actions are currently not supported in combat
model 1, so we don't need to worry about that aspect. OTOH, when we add
fire actions to combat model 1, which I plan to do, they should preferably
work exactly as in combat model 0.

These are my thoughts so far. The proposed changes would have profound
consequences for all games in the library, so they require some careful
thought. I cannot at present see a better way to deal with the underlying
problems, though.

Hans



























Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]