Tuesday, September 28, 2021

just another breakpoint in the wall

Since last week, I've been tweaking the state machine of my meka-tester trying to make it better mimmic the behaviour of Bilou when it gets into the walls. I finally get the kind of error I wanted, but it makes little sense.

. . .  Well, I initially wanted to tell you about how it suddenly hops into the wall, 4 pixels further than it was without ever seeing the 'building up' delayed move explain anything of that. but I feel so tired. s/4f.f, S/ffe6.0 ... I'd have to dig up my notebook (page 14) to decode what that means. I just can't. my eyes don't want to look at the screen anymore. See you later.

Pendant près de deux semaines, j'ai fait des essais et des ajustements pour avoir un meka-apple qui reproduise les bugs et-vlan-me-n'là-dans-l'mur que je rencontre systématiquement dès que j'essaie de jouer dans le nouveau niveau de mon fiston. J'ai fini par y parvenir, mais on ne peut pas dire que ça m'aide à comprendre ce qui se passe. Une téléportation de 4 pixels d'un coup, alors que tout semblait aller bien jusque là...

Un peu de repos, un peu d'astuce, et j'ai des pistes pour améliorer la situation. Mais alors que je refais un essai dans "Dreams.nds", je me rends compte que le problème est toujours là. Et il est très facile à reproduire manette en main sur ce niveau, en plus. Est-ce que j'ai tenté de faire du unit-testing trop tôt ?

<later> over the week, I managed to identify some issues and possibly fix some, but it is still possible (and actually easy) to trigger. 

It makes me feel like I tried to unit-test the issue too early. Maybe what I actually need is a way to get a bunch of data out of the emulator, and monitor them when I'm done reproducing the issue.

Let's see what we would need ...

Je change d'approche, du coup : il existe dans l'organisation de mes jeux une classe indépendante du Game Engine, mais qui a accès aux objets du Game Script et qui peut exécuter une méthode à chaque frame, le HUD. Si je fais tourner mon niveau dans un émulateur avec débuggeur, et si je rajoute un break pointau cas où Bilou serait arrivé dans un mur, je devrais pouvoir creuser le problème.

  • [done] access game object position and variables.
  • [done] record them, e.g. in the GameObjectState structure defined in unit-tests
  • [done] check whether game object got into a wall (cando(0,0, PLAYER_THRU)
  • [done] switch to 'investigation' mode when we're in a wall
  • track state changes and controllers reports (through a custom inspector)

edit:A class deriving from iHud met all the criterion above. If I'm okay with using ddd to inspect the content of the last GameObjectState array, I can start investigating ...

Sauf que ça n'a pas été si simple. Même en ré-important le code des tests qui enregistre les mouvements des dernières seconde de jeu, l'information restait trop sommaire pour pouvoir retracer pas à pas ce qui ne "marchait" pas au cœur de la fonction do-slopes.
Mais Murad-dib m'a sauvé la mise  (oui, depuis qu'on est allé voir Dune avec les collègues, je me suis replongé dans le bouquin.) Et si comme lui je balayais de la main toutes les règles et les conventions (du c++, ici, pas du C.H.O.M.)... si je prenais avant chaque appel à bilan.PlaY Une copie de la mémoire contenant l'objet 'Bilou' ... je pourrais en cas de mur réappeler 'play' sur la copie ! le moteur de jeu est ainsi fait qu'il n'y verrait que du feu, et je pourrais faire mon débugging pas à pas en étant sûr que je suis dans un cas où le bug va se manifester

It wasn't quite sufficient, unfortunately. Hopefully, I was reading Dune again, and going through the moment when Muad'Dib decides to break all the rules so he could get victory, I realised that I could go for Muad'dibugging too: create an empty GameObject in addition to the real one for Bilou, and memcpy a snapshot of the current state before every 'play()' call. If we turn out to end up in a wall, I could just call 'play()' on the copied state to reproduce step-by-step what the first one had encountered.

Of course, that goes against all established rules about how to deal with C++ instances, and make the 'NOCOPY(GameObject);' macro look like a fool. But it worked.

And at last, on 2021-10-13, 22:**, I think I nailed that stuck-in-the-wall bug that had been since my kid drew his pyramid level.
 

Sunday, September 19, 2021

Eclipse Operating System

Un de mes objectifs en 1997, c'était de parvenir à maîtriser le mode 32-bits des PC. Windows était encore un hybride 16/32 à l'époque, et pas franchement convainquant pour la programmation de jeux. Tous les jeux dos parus sur les dernières années annonçaient "Dos4GW" ou quelque-chose de ce genre, et la taille toujours croissante des modules de mon frère me faisaient bien comprendre que "640Ko pour tout le monde", ça ne serait plus suffisant bien longtemps.

Seulement voilà: basculer du mode 16-bit au mode 32-bit (connu à l'époque sous le nom de 'mode protégé'), ça demande une rigueur à tout épreuve et ma documentation était malheureusement plutôt imprécise. En plus, les fonctions du BIOS et du MS-DOS deviennent du coup inaccessibles (plus de chargement de fichier ni de changement de mode graphique, par exemple). Et au milieu de tout ça, je tombe sur une pépite venant de la démoscène: Eclipse Operating System et son jeu-démo Greedy. Il me lancera à la recherche du Watcom C++ sans lequel on ne sait pas recompiler les exemples. Une chasse qui durera jusqu'à la Inscene, si ma mémoire est bonne.

Saturday, September 11, 2021

Wall walk test.

If I want to have An understanding of what causes Bilou to get into walls when he arrives straight into it, I need to find a way to reproduce that at will. I have to update my slopS unit tests to cover that new scenario.

The difficulty with that setup is that the mecha will have to keep walking towards the wall for some frames, and only then try to turn back. Scripting a fake game pad controller to force the mecha to do that sure won't be the easiest path to follow. But I have a plan.

ouais. Bon. 'faudra que je le traduise, lui

Let's have a second mecha that is walking "normally" on one large floor while another one, the Gob-Under-Test is tracking its position and tries to follow it, just like Berry Bats would do, except "walking". That should work, right ?

Well, reality is hitting me hard, here. I haven't managed to get the test cycle work properly. I'm caught in the process of navigating the history and running because even with the obvious sorted out, my new test gets a "memory is leaking" warning and the former 'slopes' test isn't working either.

I think from now on, I'll use the 'default' branch as a way to track what is properly passing automated tests and what isn't. Good news, commit 6ab9f8ca2931 dating from this year (April 13rd) pass the slopes tests.

Caveats when doing things like this:

  • Makefile does not detect whether I change devkitpro version, so things that used to work years ago on an older devkit may fail to build nowadays and you'll have to manually clean output/*.o
  • some tests depend on fakreoot/*.cmd, which is not automatically re-populated from SchoolTests/efsroot/ or AppleAssault/efsroot/. and in SchoolTests, the files are generated from SchoolTest/cmd/, which does not occur if you just rebuild unit tests.

18731874

edit: I identified a commit at which the TestBasicSlopes started failing. Surprisingly, it was not a side-effect or whatever: the commit directly does


       if ((cspeed&cp) && !cdata[0]) continue;// this testpoint only works with speed
-      if ((chkflr&cp) && !(checks[pos] & F_FLOOR)) {
+      if ((chkflr&cp) && (checks[pos] & F_FALLTHRU)) {
        if (isi) isi->TestPoint(xtile, ytile, pos, checks[pos], "floor");
        cdata[GOB_TESTPOINTS]|=cp;
       }

Which cannot have no effect on how testpoints behave in the slope tests. But if we compare how monsters in the green zone behave before (1873) and after (1874) this commit, it is clear that it has desirable effects. So I'm now sure I have to fix the tests, not the way testpoints are handled. 

edit++: okay. The leaking object was a 'builder' helper used by the GobState objets. The builder is a std::* object temporarily used while e.g. collecting transitions from a given state before they got 'flattened' into an array for the 'running' part. The array, just like GobStates are allocated in the 'tank', a larger memory chunk meant to host many small objects that all have the same lifetime: one level.

In the 'slope' tests, each state was used to spawn a GameObject using that state for the test. There were no transitions: we first use a forward-moving mecha to check things are fine when moving forward with the Forward state, and then a distinct backwards-moving-mecha. But for my new test, I need transitions. That means that I won't spawn for every states. That means some states are not 'frozen' into the tank, and hence still hold references to std::* objects like vectors or strings. These are the leaking ones. Let's get cracking!