Aller au contenu principal

Session 113 — Phase 2.D : ASM-fidelity fixes pour l’IA alien

Contexte

Apres test in-game de la phase 2.C, deux problemes majeurs identifies :

  1. Tous les aliens se comportent pareil : Red Aliens et Guards aggressent
    et chargent de la meme facon, alors que dans le jeu original, les Guards
    tirent avec leur arme (Shotgun, Blaster, Mind Zap), tandis que les Red
    Aliens chargent en corps-a-corps.
  2. Distance de detection : la limite LOS_MAX_DIST_JME = 100f etait
    arbitraire et beaucoup plus courte que dans l’ASM original (qui n’a
    aucune limite distance, juste un test PVS de visibilite).

Cette phase 2.D reprend l’ASM (modules/ai.s) ligne par ligne et corrige
ces 4 ecarts :

# Probleme Avant ASM-fidele
1 Distance LOS hardcap 100 JME pas de cap (PVS-based) ; on cap a 200 JME
2 Modele de damage hp -= damage chaque coup cumul/4 >= HP_init (HP immuable)
3 Field of view aucun ai_CheckInFront : dot(forward, delta) > 0
4 Tir alien non-implemente attackWithGun (hitscan) / spawnAlienProjectile

1. Modele de damage : cumul/4 vs HP_init

L’ASM original (modules/ai.s::ai_TakeDamage ligne ~100) ne fait jamais
HP -= damage. Au lieu de cela :

ai_TakeDamage:
    add.w   d0, AI_Damaged_vw[idx]    ; cumul += damageTaken (ce coup)
    move.w  AI_Damaged_vw[idx], d0
    asr.w   #2, d0                     ; d0 = cumul/4
    moveq   #0, d1
    move.b  EntT_HitPoints_b(a0), d1   ; d1 = HP_init (jamais decremente)
    move.b  #0, EntT_DamageTaken_b(a0)
    cmp.w   d0, d1
    ble     ai_JustDied                ; si HP_init <= cumul/4 -> mort

Donc EntT_HitPoints_b est fixe = HP_init, c’est juste le seuil de
mort. Le cumul de tous les degats recus est dans AI_Damaged_vw[idx]
(notre totalDamageDone), et on divise par 4 avant de comparer.

Resultats :
Red Alien (HP=2) meurt quand totalDamage >= 8 (= 8 plasmas a 1 dmg).
Mantis Boss (HP=125) meurt quand totalDamage >= 500 (= 500 plasmas).

Avant on faisait HP -= 1 par plasma, donc Red Alien mourait apres 2 tirs.
Maintenant il faut 8 tirs. Plus realiste, plus fidele a l’experience
originale.

2. Field of view (ai_CheckInFront)

L’ASM original a une routine ai_CheckInFront qui filtre les detections de
joueur. Sans elle, les aliens detectent le joueur a 360deg (ils « voient
derriere eux »). Avec, ils n’agressent que si le joueur est dans leur
demi-plan frontal :

ai_CheckInFront:
    dx = playerX - alienX
    dz = playerZ - alienZ
    sin = SinCosTable[currentAngle]
    cos = SinCosTable[currentAngle + COSINE_OFS]
    dot = dx*sin + dz*cos
    sgt d0     ; D0 = -1 si dot > 0 (joueur devant), 0 sinon

C’est un produit scalaire entre le vecteur « forward » de l’alien (oriente
par currentAngle) et le vecteur delta (alien -> joueur). Positif = devant.

Ajoute dans 3 endroits : doDefault, doFollowup, doResponse (toutes
les transitions vers RESPONSE/agression sont gatees par isPlayerInFront).
Dans doResponse, l’alien tourne face au joueur a chaque frame, donc une
fois qu’il a aggrer une fois, il garde le contact tant que le joueur est
suffisamment proche.

3. Tir alien (ai_AttackWithGun / ai_AttackWithProjectile)

L’ASM dispatche la routine d’attaque selon AlienT_BulType_w :

ai_AttackCommon:
    move.l  GLF_DatabasePtr_l, a1
    lea     GLFT_BulletDefs_l(a1), a1
    muls    #BulT_SizeOf_l, d0
    add.l   d0, a1
    tst.l   BulT_IsHitScan_l(a1)
    beq     ai_AttackWithProjectile      ; bullet projectile
    ; sinon : ai_AttackWithHitScan       ; bullet hitscan

Hitscan (Machine Gun=1, Shotgun=7, MindZap=12) : test de probabilite
base sur la distance :

ai_AttackWithHitScan:
    jsr     GetRand
    and.w   #$7fff, d0           ; rand 0..32767
    move.w  (a6), d1             ; dx (rotated)
    muls    d1, d1               ; dx^2
    move.w  2(a6), d2            ; dz (rotated)
    muls    d2, d2               ; dz^2
    add.l   d2, d1               ; dist^2
    asr.l   #6, d1               ; dist^2 / 64
    ext.l   d0
    asl.l   #2, d0               ; rand * 4
    cmp.l   d1, d0
    bgt.s   .hit_player          ; rand*4 > dist^2/64 -> HIT

Approximations :
– A dist=100 (proche) : dist^2/64 = 156, max rand4 = 131068, ~99.9%
chance de hit
– A dist=1000 : dist^2/64 = 15625, ~88% chance de hit
– A dist=4000 : dist^2/64 = 250000, ~0% chance de hit (rand
4 max = 131068)

Projectile (Plasma=0, Rocket=2, Blaster=9, Lazer=14, Grenade=8…) :
spawn une bullet alien qui voyage vers le joueur (delegue a
spawnAlienProjectile dans le port). Pour la 2.D initiale, on simule
avec 50% chance de hit + damage immediat (placeholder, vraie pool en 2.E).

Differenciation par alien :
– Red Alien : bulType=0, responseBehaviour=0 (Charge) -> charge melee
– Guard : bulType=9 (Blaster), respBeh=2 (AttackWithGun) -> tire projectile
– ‘Ard Guard : bulType=7 (Shotgun), respBeh=2 -> tire hitscan
– Mind Priest : bulType=12 (MindZap), respBeh=2 -> tire hitscan
– Mantis Boss : bulType=2 (Rocket), respBeh=2 -> tire projectile

4. Trigger de tir (ai_DoAction_b)

Dans l’ASM, le tir n’a lieu QUE pendant les frames « kick » de l’animation
d’attaque, marquees par un byte ai_DoAction_b non-zero dans la table
AlienAnimPtr_l. Sans porter les tables d’animations completes, on simule
par : tirer une fois quand timer2 traverse FIRE_FRAME=4. Une seule
fois par cycle d’attaque (de 8 frames).

boolean fireTrigger = (prevTimer2 < FIRE_FRAME && a.timer2 >= FIRE_FRAME);
if (fireTrigger && a.seesPlayer && isPlayerInFront(a)) {
    performAttack(a);  // dispatch melee/hitscan/projectile
}

Fichiers modifies

  1. core/ai/AlienRuntimeState.java : hitPoints documente comme
    IMMUABLE (= HP_init, jamais decremente). isAlive() reecrit en
    mode != DIE && (totalDamageDone >> 2) < hitPoints.

  2. core/ai/AlienAI.java :
    applyDamage() reecrit : totalDamageDone += damageTaken, mort si
    totalDamageDone >> 2 >= hitPoints. HP_init n’est plus decremente.
    – Ajout isPlayerInFront(a) : produit scalaire forward.delta
    doDefault, doFollowup, doResponse : ajout du gate FOV avant
    toute transition vers RESPONSE
    doResponse() reecrit avec :

    • FIRE_FRAME=4 trigger pour simuler ai_DoAction_b
    • Dispatch performAttack(a) : melee si !attacksWithGun, hitscan
      si isHitscanBullet(bulType), sinon projectile
    • Ajout attackWithGun(a) : formule ASM (rand*256f) > dist^2,
      applique damage via world.applyDamageToPlayer
    • Ajout attackWithProjectile(a) : delegate vers world.spawnAlienProjectile
    • Ajout attackMelee(a) : si dist^2 < 80*80, applique MELEE_DAMAGE=2
    • Ajout damageForBulletType(bulType) : lookup hardcode des degats GLF
  3. core/ai/AiWorld.java : ajout 2 methodes default :
    applyDamageToPlayer(damage, fromX, fromZ) (no-op default)
    spawnAlienProjectile(bulletType, damage, fromX, fromY, fromZ) (no-op)

  4. world/AiWorldAdapter.java :
    LOS_MAX_DIST_JME passe de 100 a 200 (pas de cap dans l’ASM)
    – Ajout champ playerHealth + setter setPlayerHealth(ph)
    – Implementation applyDamageToPlayer : appelle playerHealth.takeDamage(damage)
    – Implementation spawnAlienProjectile : 50% chance + takeDamage (placeholder 2.D)

  5. app/GameAppState.java : apres creation de l’AiWorldAdapter, branche
    aiWorld.setPlayerHealth(healthState) pour que les tirs alien fassent
    vraiment des degats.

Tests

core/ai/AlienAITest.java mis a jour : 17 -> 22 tests

  • 3 nouveaux tests dans class FieldOfView :
  • playerInFrontTriggersResponse
  • playerBehindDoesNotTrigger
  • playerToTheSide

  • 5 nouveaux tests dans class AlienShooting :

  • ardGuardShootsHitscanCloseRange (‘Ard Guard, Shotgun=hitscan, courte distance)
  • ardGuardMissesAtLongRange (dist=10000 -> miss garanti, formule rand*256 > dist^2)
  • guardShootsProjectile (Guard, Blaster=projectile, spawnAlienProjectile appele)
  • noShootBeforeFireFrame (timer2=2 ne declenche pas, timer2 doit traverser 4)
  • onlyOneShotPerCycle (timer2=5 deja passe ne re-tire pas)

  • Test lethalDamageKills corrige : verifie maintenant hitPoints==0
    apres doDie (= ASM-fidele, cf. ai.s::ai_DoDie .still_dying)

  • Test damageReducesHP modifie : verifie que hitPoints reste FIXE
    (= HP_init) en cas de dommages non letaux, et que totalDamageDone
    s’accumule

  • Nouveau test redAlienDiesAfter8Plasmas : valide la formule cumul/4
    (Red Alien HP=2 meurt apres 8 plasmas, pas 2)

  • FakeWorld etend les capture des appels de combat :

  • applyDamageCount, lastDamageAmount pour applyDamageToPlayer
  • spawnProjectileCount, lastBulletType pour spawnAlienProjectile

Resultat in-game attendu

  1. Red Aliens : chargent toujours en ligne droite, font des degats melee
    quand ils touchent le joueur (= 2 HP par contact). Plus difficile a
    tomber : 8 plasmas au lieu de 2. Sans gun, jamais de tir distant.

  2. Guards (Blaster) : s’arretent a portee, tirent un projectile vers
    le joueur. Le projectile a 50% chance de hit (placeholder). Damage = 2
    (Blaster Bolt).

  3. ‘Ard Guards (Shotgun) : s’arretent a portee, tirent un hitscan.
    Le hit est probabiliste : ~99% a courte distance, ~88% a 1000 unites,
    ~0% au-dela de 4000 unites. Damage = 1.

  4. Mind Priests (MindZap) : tirent un hitscan tres puissant (3 dmg)
    avec memes proba que Shotgun.

  5. Field of view : si on est derriere un alien, il ne reagit pas.
    Ils faut entrer dans son demi-plan frontal pour declencher l’aggression.

  6. HP joueur : visible dans l’overlay debug (touche F3) sous forme
    HP:N/200. Decremente quand on est touche.

Limitations connues (a faire en phase 2.E ou plus tard)

  • Pas de shield logic dans applyDamageToPlayer : les degats vont
    directement a la sante, sans consommer le shield d’abord. A implementer
    quand on rebranche le HUD complet.
  • Animation trigger simplifie : on tire au FIRE_FRAME=4 fixe, alors
    que l’ASM utilise des tables AlienAnimPtr_l avec un byte par frame.
    Pour porter ca correctement, il faut extraire les tables de l’ASM
    (« draw.s » cote draw + tables alimentees par newaliencontrol).
  • Projectile 50% hit placeholder : il n’y a pas de vrai pool de
    bullets aliens (AlienShotPool symetrique a PlayerShotPool).
    Placeholder simplifie : 50% chance de hit + damage direct. La 2.E
    ajoutera un vrai pool + AlienBulletUpdateSystem pour faire voyager
    les bullets et tester collision avec le joueur.
  • Pas de red flash overlay quand le joueur est touche.
  • Pas de knockback visuel (ASM utilise EntT_ImpactX/Z pour pousser
    le joueur dans la direction du tir).
  • Pas de SFX positionnel (cris d’alien, son de tir aliens).
  • Boss death spawn non implemente : si splatType >= NUM_BULLET_DEFS,
    l’alien parent doit spawner 2 plus petits aliens. Pour l’instant tous
    les aliens font juste un fade out simple.
  • Movement collision : aliens traversent toujours les murs (deplacement
    lineaire vers la cible). La 2.F portera MoveObject + Obj_DoCollision.
  • Sprite 4-directionnel : les sprites sont toujours frontaux. La 2.G
    portera ViewpointToDraw (TOWARDS=0/RIGHT=1/AWAY=2/LEFT=3) qui choisit
    le sprite selon l’angle relatif joueur-alien.

Statistiques cumulees session 113 (4 phases)

  • Phases livrees : 2.A (machine d’etats) + 2.B (integration runtime)
  • 2.C (tirs joueur -> aliens) + 2.D (ASM-fidelity fixes)
  • 8 fichiers nouveaux : 6 dans core/ai/, 1 dans world/, 1 dans combat/
  • 6 fichiers modifies dont 2 fois pour 2.D : AlienRuntimeState,
    AlienAI, AiWorld, AiWorldAdapter, GameAppState,
    BulletUpdateSystem, HitscanTracerSystem, PlayerShootSystem
  • 3 fichiers test : 22 tests AlienAI + 7 integration AlienDefLoader
  • 11 unitaires AlienHitDetector = 40 tests au total
  • CHANGELOG : 4 sections (2.A -> 2.B -> 2.C -> 2.D) toutes datees
    2026-04-28

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *