Wednesday, September 20, 2023

Tiny Thor, at last!

I have records of tracking Joe Manaco's "Tiny Thor" title for as long as January 2021. With gorgeous art from Henk Neiborg and brilliant soundtrack by Chris Huelsbeck, the game's author even revised his project to match the level of those invited developers. I've been waiting for it to be released on the Nintendo Switch, and yeah! Huzzah! it did!

Most of the game is amazing. Gorgeous art, (very) challenging levels and smooth action. I hope I will manage to get far enough in the game, but it is already pointing out that my skills are not on par with those of the intended audience. Holding a trigger to freeze while aiming a shot in a platformer is not a bad idea, but I struggle to explain that to my fingers ^^". As a result, I end up in spikes or with my hammer flying the wrong direction more often than I should. Note that I had the same issue with that-space-pirate-game

Rho, ce que je l'ai attendu, ce jeu! Rien que savoir qu'il y avait Henk Nieborg aux pixels ... D'autant que contrairement à d'autres projets esthétiquement attractifs (comme CyberShadow ou Flynn, son of Crismon), ici on est sur un mode de jeu qui fait véritablement partie de mes styles de prédilection au niveau du gameplay. Mais le voilà! il est sorti sur Switch et il a été rejoint par Chris Hülsbeck aux platines.

Niveau game design, c'est bien rôdé. Niveau difficulté, c'est une autre histoire. Non pas que le jeu soit injouable ou infogramesque, mais le peu d'habileté dont je dispose est mis à rude épreuve. Une des opérations de base est de maintenir une gachette enfoncée pendant que le stick directionnel indique la trajectoire que Mjolnir le marteau devra suivre une fois lancé ... manipulation qui m'avait déjà un peu donné de fil à retordre dans FlintHook et que j'ai du mal à faire entrer dans mes doigts.

Levels in Tiny Thor are quite long, as I'm approaching the second boss. It took me no less than 30 minutes for the last level I played (it took about 10 minutes to a fairly trained player). Should I leave the game on pause at one of the numerous checkpoints in the game, I will take the risk that one of my kids pick the controller to play a game of Kirby in the Lost World and zap my progress. That definitely reminds me of the level design of Donkey Kong Returns and Giana Sisters: Twisted Dreams

Spiders... One of my favourite monsters

The game also share one major skill barrier with those two other platformer: when you respawn, you start with just one hit point. Should you hit anything while you're trying to reach the location where you died, you'll die again. There are extra hitpoints to be found, and a fairly nice gameplay mechanic where you can recover your lost hitpoint à la Sonic, except it has a variable time à la Yoshi Island. That means one obstacle you could have passed in a few attempts will now take you over 10 tries just because you're super-fragile as soon as your second attempt, unless Joe granted you a health pick-up just next to your respawn point (which hopefully happens from time to time).

Une fois que cette manoeuvre est devenue un peu moins laborieuse, on arrive dans des niveaux dont la longueur elle-même devient un challenge. Pas loin de 30 minutes dans le niveau contenant le 1er boss (qu'un joueur un peu mieux entrainé atteint en 10 minutes). Il y a un bon nombre de checkpoints dans un niveau mais je ne peux m'y "arrêter" qu'à condition que mes enfants ne cherchent pas à utiliser la switch pendant l'après-midi ^^". Au moins, la console (contrairement à une PlayStation) est prévue pour des veilles prolongées, mais ça a tout de même un petit goût de Giana Sisters: Twisted Dreams. D'autant que, comme dans ce dernier, si j'échoue entre deux checkpoints, je reviens extrêmement vulnérable pour réessayer le passage délicat. J'entends par là "un-coup-t'es-mort". Comme dans Super Mario Bros direz vous. Oui, sauf qu'on voit rarement autant de pics et d'ennemis en vrac dans SMB qu'on en voit dans Tiny Thor. Et de toutes façons, SMB1 est toujours trop dur pour moi :P

Il y a tout de même moyen de ramasser un coeur-pour-un-coup-en-plus. On en a même de temps en temps un dans un bloc à côté du checkpoint. Là je pourrai encaisser un coup sans défaillir. Le coeur s'enfuit alors en rebondissant un peu comme un Yoshi dans SMW, et j'ai un temps limité pour le récupérer. Une mécanique que l'auteur avoue avoir emprunté à Yoshi's Island, les pleurs de bébé en moins. ça m'aura certainement sauvé la mise à pas mal de reprises, récupérant un coeur au bout de quelques secondes, puis me faisant à nouveau toucher un peu plus tard, le coeur se sauvant à nouveau mais me laissant cette fois moins de temps pour le récupérer jusqu'à ce que je tombe sur un bloc-coeur où je pourrai rajouter des secondes à ce précieux compteur. Mais une petite noyade, et toute cette jolie chaîne s'interrompt: on repart aussi fragile qu'un Rick Dangerous depuis le dernier point de sauvegarde. Les vies sont infinies, et ce n'est pas un hasard.

You sure have guessed now: the game is hard. It doesn't feature any 1-UP or lives counter and there's a reason for that. The developer was aware of that and provides a menu with a set of  "game genie"  options, like how much you stick to walls, how high you jump (yes, truly), whether your heart bounces much or not when you lose it, etc. A good idea, well executed, but if you ask me, it lacks one critical option: to swim out to safety. I'm glad I did this in School Rush, despite it might not be perfect and might not completely save "your" lives, but given the number of times I've drown that Tiny Thor, I now know it was good.

Just for the record, I re-played levels 1-2, 2-2 and 3-2 using the in-game time counter to measure how long I spent between two check points. The duration is comparable to the average 1:30 minutes per School rush *level*. Yes, the question of whether the game should have offered mid-level checkpoints is still itching me.

Friday, September 08, 2023

operator sockaddr*()

J'ai passé un peu de temps cet été dans du code third-party qui avait un autre dialecte c++ que moi. En particulier, ils aimaient bien faire des objets-wrappers métamorphes. Prenons par exemple une adresse réseau. C'est pénible: on peut l'avoir sous forme ASCII ou dans un entier 32-bit (ou un gros blob si on est en IPv6). Et de toutes façons, pour s'en servir il faudra qu'elle soit emballée dans un sockaddr, seule connue de l'API socket, qui reprend un identifiant de 'famille de protocole' et un numéro de port.

Tout ça semble justifier une classe NetAddress qui aurait des constructeurs NetAddress(std::string fromUserInterface) et NetAddress(uint32_t fromProtocolMessage). Très bien. On peut aussi en faire en réalité un wrapper de `struct sockaddr_storage`, et remplir les différents champs au moment de l'appel. Avec éventuellement un NetAddress.setPort() pour faire bonne mesure.

Mais ce n'est pas ça qui va changer le fait que connect() et bind() travaillent exclusivement avec des sockaddr. Là où j'ai été surpris, c'est qu'au lieu d'un NetAddress.getSockAddr(), j'ai eu droit à

operator sockaddr*() {
    return reinterpret_cast<const sockaddr*>(&address);
}

Séduisant a priori. Elégant, même. Mais à l'usage (et surtout à la lecture), ça s'est avéré être un fiasco. Je tombais sur du code du genre connect(toServer, myServerAddress, options) suffisament loin de la définition de myServerAddress et je zappais qu'il ne s'agissait pas d'un sockaddr classique, mais de l'objet emballant. Particulièrement piégeux quand on essaie de découvrir d'où provient l'exception InvalidArgument que rien dans le code de connect ne semble pouvoir générer.

Et ç'aurait pu être encore pire si myServerAddress avait été un pointeur vers une NetAddress ... Là, on aurait dû écrire connect(toServer, *myServerAddress, options), parce que c'est un NetAddress-même que le compilateur a appris à "traduire" en sockaddr* à l'aide de l'opérateur. Pas un pointeur de NetAddress. Perturbant au possible quand on se souvient qu'en C on aurait écrit connect(toServer, &server_address, opts);

...

Sinon, vous, l'été, ça a été ?

Saturday, September 02, 2023

Le dessus des arbres

Je commence à avoir une assez imposante collection d'arbres à analyser dans mes #pixelstudy, et il faudra probablement que j'ai quelque-chose de satisfaisant à dessiner pour la partie supérieure des arbres dans Bilou Dream Land... analysons donc.

Et commençons par un mockup de SnakePixel (owlboy) qui nous réinterprète pour "16-bits" l'Amazonie de Duck Tales. Avec des branches où les feuilles sont distinguées les unes des autres et une palette assez généreuse de 6 à 7 tons de verts pour chaque plan. L'arbre à un look "tileset" avec ce bloc de 64x56 (encadré) qui répété sur une même horizontale. Un autre bloc 64x? est utilisé pour les lignes au-dessus. Un bloc de 128x64 est utilisé pour la jonction avec le tronc. Je ne pousse pas plus loin, parce que plusieurs pixels remarqués ça et là me laissent croire que l'auteur n'a jamais essayé que ça tienne *vraiment* dans les 64K de VRAM de SuperNES.

One part of the current 'green zone' tileset displease me, and that is the top-of-tree tiles. I collected a few more reference pictures of trees (possibly with the canopy part visible) and since I'm sick this week-end, it seems like a good time to finally pixel-study them. Let's start with SnakePixel's mock up of Duck Tales Amazonia level, with a generous 6-7 tones of green per tile, and palette variation depending on the plane we're seeing. His trees canopy on the foreground are mostly made of 64xalmost-64 blocks repeated horizontally, but with another kind of blocks as you move up or down in the tree. Leaves are cleanly separated from each otther, although not outlined. I spot a dedicated 128x64 area for the leaves-to-trunk transition, but also lots of little details at the edges of 'common' blocks that suggest the author was more drawing inspiration from 16-bit limitation than really trying to work within them.

Un autre arbre qui m'avait tapé dans l'oeil, c'est celui d'Alwa's Legacy, dessiné par Vierbit pour le jeu de MikaelForslind. Le style est assez particulier, surtout pour la partie la plus haute de l'arbre (qui arrive assez peu souvent à l'écran, comme on peut le voir dans mon screenshot ^^".Il y a pas mal de zones "plates" de couleur unie bordée par des feuilles très larges, au design simple mais nettement détaillées. Pas de tentative de coller à un tileset réduit, mais on note clairement des réutilisations de certains motifs, soit tels quels, soit retournés, soit avec une autre teinte.

Let's move to another tree. One that really did it into a game (and which actually made me buy the game :P) : the big garden tree of Alwa's Legacy drawn by Vierbit. I've been lucky enough that the game author Mikael Forslind offered me a copy of the original in-game trees layer for detailed study. A true gem, since I feel like this style of tree would be perfectly capable of hosting some actual gameplay, with some leaves-platforms, but also some vine-ish things to hang on. At first I was surprised that it was even possible: we seem to see the tree mostly 'from below' here, except that we also see it up to the top, which seems incoherent. Except if the tree's canopy is not pointing at the zenith, but is bending towards the rear of the scene.

Et très sympatiquement, Mikael m'a envoyé le layer tel qu'il est utilisé dans le jeu, avec les deux arbres entiers et les rivières qui les séparent. On remarque que sur le haut de l'arbre, on a des feuilles plus ou moins nettes selon qu'elles sont à l'avant où à l'arrière, pouvant aller jusqu'à de simples silhouettes de feuillages superposées.
Autre élément remarquable avec cet arbre, c'est la quantité de "feuillage intérieur" qu'il nous dévoile. Vous savez, ces zones sombres ou seule une courbe de feuilles apparaît ça et là dans les branchages.

On a more macroscopic scale, it is surprising to see that mix of highly-detailed, very large leaves (with the brightest color) while some other part of the tree just use a flat color with some leave-shapes outline. Interesting to see that, while the tree has obviously not been made to fit a tiled grid, Vierbit clearly did reuse some bunch of leaves at different places, sometimes flipping them, sometimes applying a color swap.

The inner, dark area has only silhouetto of leaves, with the exception of hanging vine-styles ropes of leaves. That makes composition of the structure more free, with the extra bonus that we usually don't have to show branches to far away from the trunk because they eventually disappear into dark leaves before reaching any place where we'd have to show them too thin or mixing realistically with detailed leaves.

Les feuilles n'y sont qu'esquissées, sauf dans le cas des guirlandes de lianes qui dégoulinent des branches, .. et qui m'inspirent pas mal au niveau gameplay si je parviens à ajouter les mécaniques qui m'intéressent dans le nouveau jeu.

Les branches, aussi peuvent se permettre d'aller un peu dans le sens qu'on veut, et elles n'ont pas nécessairement besoin de se dirriger vers une partie spécifique du feuillage, puisqu'elles finiront masquées avant d'atteindre le state de brindilles.

Alors j'avoue qu'au départ, cette perspective m'a surpris. "Mais? on ne voit jamais un arbre sous cet angle !?" Eh bien, à mieux y réfléchir, si: si son tronc est penché vers le fond de la scène et que son feuillage est donc majoritairement plus loin du plan vertical où se trouve la base de son tronc. Si ça permet de mélanger art et gameplay, ça me va tout à fait.

Another set of tree tops that made me buy a switch game was those of Eagle Island. I seem to see the construction pattern, here: draw arcs, then cover them with leaves. Colors within an arc are mostly identical. Each leave is its own cluster, making it distinguishable from the others but without any per-leaf shading or texture. I wonder whether something alike could be used for Bilou. I dig the color choice a lot, though.

Je remets aussi pour le coup le haut des arbres de Eagle Island, principalement construit de feuilles larges mais moins nettes (entendez par là: une seule couleur par feuille, sans détail intérieur) qui sont disposées le long de courbes dont le rayon serait vers le haut. Du plus bel effet pour le décor, mais moins facile à intégrer, je pense.

J'aime beaucoup leur teintes, par contre, on est sur une palette de 6 couleurs allant du h156s89v46 au h167s65v27 avec un h135s67v59 et h117s70v73 pour les feuilles plus lumineuses

Another nice-looking one from Whipseey, althoug it's a bush, not a tree. Well, it did show up while looking for Eagle Island screenshots, so let's have it here, esp. since it is pretty straightforward to guess how they were drawn: 1) stack balls 2) give balls protruding 2x3/3x2 rectangles (that will be leaves pointing out), 3) draw mouth-like shadows at the bottom of the balls

Et puis, en cherchant celles d'Eagle Island, je suis retombé sur ce buisson plutôt sympa de Whipseey. Faites des ronds, ajoutez-leur des rectangles de 2x3 ou 3x2 dans la même couleur pour faire les feuilles qui dépassent, ajoutez des grosses zones d'ombre dans les bas des ronds pour faire l'ombrage et vous obtenez un buisson du plus bel effet... ou peut être un terrifiant hydre des bois prêt à dévorer les champis imprudents ... allez savoir :P

Et puis bon, je termine avec un petit screenshot tout pourri de Tiny Thor parce que je suis en train de jouer au jeu (enfin, pas là tout de suite, hein) dont les graphismes bluffants sont par le talentueux et renommé Henk Nieborg et que oui, il y a des arbres dedans ! ... sauf que ...

euh. Ouais. autant la maestria est indiscutable dans le tronc (c'est bien parce qu'on va en voir beaucoup), autant le feuillage, franchement, j'ai du mal à ratacher ce style inimitable à une quelconque forme 3D. je passerai donc mon tour sur ce coup-là.

Oh, and yeah, there are trees in Tiny Thor, the latest game I've been playing on the switch. It has stunning pixel art by Henk Nieborg globally speaking, but to be honest, I could hardly devent the shading decision in that specific big tree. So I'll just not try studying it either.

edit: I just happen to have discovered Roskur's Run a few days after this got posted and creator from Dirty Beast Games very nicely posted their placeholder bush that could also be used for a fairly interesting top-of-tree.

Et un p'tit dernier, sympatique post du dévelopeur de Roskur's Run dont j'avais apprécié le "placeholder art". Elégant, tilable, varié, déclinable... tout ce qu'il me faut ^_^

First it has low colour count, which suits me well for background purposes. It tiles well (top and sides reuse same pixels) and the central, smile-like shadow brings in interesting but freely-positionable detail to avoid monotony of a flat colour. Conceptually, it works much like the bush of whipseey, but with shapes that better match the current style of my background leaves.


Saturday, August 26, 2023

Apple Bop ?

Une pomme qui vous fonce dessus, ça fait mal. Forcément. Une qui vous tombe sur la cafetière également. Surtout quand elle est aussi grande que vous. Si vous lui tombez, dessus, là, c'est elle qui tombe dans les pommes. En disant ça, j'ai couvert 75% des interactions possibles entre Bilou et les Applemen dans Apple Assault.

If you get an apple on your head, that hurts. If you're thrown an apple in the face, that will hurt too -- especially if the apple is almost as large as yourself. Chances you won't get hurt if you stomp on an apple while falling but that will likely do the apple no good. All these cover 75% of the interactions between Bilou and the Applemen in Apple Assault.

Now, what if Bilou run into an apple. Should he still get hurt ? I know it is the default behaviour in platformers: if Mario runs into a koopa, it gets hurt no matter what direction the koopa was facing. You can only kick a koopa shell if you stomped the koopa first. You can create stories about it like "yeah, as you kicked the koopa, it clawed to the ground, turned back so fast you couldn't react and bite you. You're dead", and by '86 standards, they would work. But does it mean we should continue like that ? Can't we find something more fun ? It's an apple, after all. It's not all covered with deathly spikes!

Mais si c'est Bilou qui arrive dans le dos de la pomme ? Est-ce que le choc devrait aussi lui faire mal (à Bilou) ? dans Apple Assault, la réponse est "oui". Dans la plupart des jeux de plate-formes, la réponse serait "oui" aussi. Un koopa qui bouge, c'est un blesse, qu'on l'approche par-devant ou par-derrière. Mais est-ce que c'est forcément la bonne chose à faire ? Est-ce qu'il n'y a pas moyen d'être plus *fun* que ça ? C'est une pomme, après tout. Pas une chataigne couverte de pics.
En plus, maintenant, il y a un précédent avec les Pendats de School Rush: on est blessé quand ils nous foncent dedans ou quand on tombe sur leur pointe. C'est plutôt logique. on est blessé aussi quand ils font demi-tour, mais de nouveau, vu la violence du mouvement, un mauvais coup est vite parti. Par contre, tant qu'on est dans leur dos, on pourrait presque les pousser, on ne se fait pas mal. Alors si c'est vrai avec des soldats, ça devrait aussi être vrai avec des pommes, non?

When you consider that, in School Rush, pendats -- despite their aggressive and spiky appearance -- will not harm Bilou (only block him) if he comes in their back, it would make the whole game more coherent if we would equally have the back of Applemen harmless. Plus, it could actually be pretty fun to see an Appleman slightly bounce forward on such a collision, as if Bilou had kicked him in the back while walking, and only then actually turning back, being surpised to see Bilou and finally attacking.

Voilà donc ce que j'ai envie de faire pour Bilou: Dreamland (et la green zone dans le futur): si Bilou bouscule un Appleman dans le dos, c'est l'Appleman qui fera un bond et Bilou ne subira aucun dommage ... dans l'immédiat. Parce qu'en revenant sur le sol, l'Appleman fera alors demi-tour et, voyant Bilou, lui foncera dessus (comme il fait depuis 2009). Ce qui n'empêche pas le joueur de sauter par-dessus l'Appleman, peut-être même suffisamment tôt avant que celui-ci ne se retourne, et qu'il ne se rende donc compte de rien. ça,, ça a le potentiel pour être bien fun ^_^

Saturday, August 12, 2023

Swinging animations

Plus le design de Bilou: Dreamland avance, plus la possibilité de s'accrocher et se balancer prend de l'importance dans le gameplay envisagé. Et c'est tant mieux sauf pour une chose: mon moteur d'animations est actuellement très mal adapté pour ce genre de chose. Comprenez, on risquerait plus de se retrouver avec quelque-chose de raide comme Mickey Magical Quest plutôt que les mouvements souples de Fury of the Furries. Ce serait d'autant plus dommage que Spongebop ne souffre pas de ce genre de limitation et que ça rend furieusement bien. Mais voilà, l'image de Spongebop est indépendante de l'angle de sa corde.

The more I sketch some design ideas for Bilou: Dreamland, the more swinging with a rope-like object occurs. I won't claim it will be a primary game mechanic, but it will certainly be an important one. There's just one drawback : so far my game engine offers no support for that kind of animation. Oh, sure, I could go the Mickey Magical Quest way of hard-coding a few frames and the positions Bilou should take to swing, but given that I have a freely-swinging Spongebop in the previous game, I'd prefer go for something like what I've enjoyed in Fury of the Furries: game physics that allows 360° swinging, with free positioning anywhere on the circle defined by the current rope length and almost free extension / reduction of the rope length (Mickey can only climb).

One picture every 10° to swing a tiny
Pour Fury on a pas moins de 36 images différentes et la possibilité de faire un tour complet de son point d'accroche si la physique le permet, soit 10° entre chaque image. Mickey n'offre que 7 images différentes pour un angle de balancier de 90°, soit 15° entre chaque image. La différence devrait être légère (on passe quand-même de 60 fps à 40 fps avec un rapport pareil), mais là où ça change tout, c'est que les angles de la corde elle-même sont discrets à 15° près avec Mickey, dû au fait du hardware 'par tiles' de la super NES. Du coup, les positions de Mickey deviennent elle-même discrètes (bien qu'il puisse remonter sa corde). 

Je me suis donc repenché là-dessus pendant mes p'tites vacances, à l'abri dans mon épave de bateau pirate et j'ai peut-être bien une solution. L'idée serait de compléter les deux modes d'animation actuels (l'un qui suit une ligne du temps, l'autre qui colle à une série de déplacements) pour faire un système dans lequel on va choisir d'avancer ou non dans l'animation selon le vecteur-déplacement actuel. En gros, je fais une animation dans laquelle Bilou se balance autour de sa main et le moteur passera à l'image suivante quand Bilou va dans la même direction que celle suivie par cette image dans l'animation.

The summer idea about it was to handle that with a new way of playing back an animation: rather than enforcing a delay between two frames or a specific motion, we would compare the in-game speed vector (xg, yg) against inter-frame motion vectors (xa, ya). You'd then step to the next frame in the animation only if (xg, yg) is "beyond" the expected motion to the next frame. A bit of maths done in a sandbox (literally) shown that we can tell that just by two integer multiplications per frame. It will need separate animation for swing-to-the-left and swing-to-the-right, but I'm perfectly fine with that.

Mais comment savoir si un vecteur effectif (xg, yg) est plus proche de l'ancien vecteur de l'animation (xa, ya) ou de celui de l'étape suivante (xa', ya') ? Je suis parti de mon vieux cours d'analyse math avec "l'extrémité du nouveau vecteur (xg, yg) doit être au-dessus de la droite définie par k*xa' = k*ya' (que l'on peut réécrire y = (ya' / xa') * x). Mathématiquement parlant, on sera au-dessus si yg > ya' / xa' * xg. Programmatiquement parlant, c'est moins drôle, à cause des divisions par zéro dans les verticales et du manque de précision des divisions / multiplications quand on se contente de travailler avec des nombres entiers. J'ai donc voulu ruser et faire un peu d'algèbre pour passer à yg * xa' > ya' * xg, qui ne fait que des nombres plus grands que 1. En fait, je venais de retomber sur l'expression de l'aire signée d'un triangle, bon vieux truc du cours d'algo II à l'unif (merci, feu PAdM). ça tombe bien: elle sert justement à calculer si un point est à gauche ou à droite d'un vecteur donné.

Il faudra vérifier expérimentalement si je dois modifier le sens de l'inégalité pour le balancier-retour, trouver un moyen d'intégrer ça au scripts (probablement avec un 'speedmove' comme on a déjà un 'selfmove'). La bonne nouvelle, c'est que le même système pourrait probablement marcher aussi pour une voiture vue du haut, des segments de lianes qui se balancent et segments de ponts qui s'inclinent :-P

extra bonus with the anim.x * gob.y > anim.y * gob.x expression, is that a faster motion (e.g. longer rope) or a slower motion (e.g. shorter rope) will not affect things, because it would affect both gob.x and gob.y by an identical factor k and thus their effects will compensate in the comparison.

Saturday, July 22, 2023

DS Ram Leakage in TestNScripts

I had thought that fixing 'slope unit-tests' in addition to 'wall unit tests' would mean my fixes to get the scorpeye behaving as intended. That would mean the 'summer code review' would be over and the 'summer game dev' could start. Well, that was hoping too much, because there's actually one more test that, for some reason, doesn't show up in ./testme --list but still exists and is important: TestNScripts. This one loads the real .cmd files of SchoolRush and checks parsing went fine. And it does not only check one of them: it will reload many scripts in the same "engine", the way the game does.


while (ntests < n) {
   TestBench tc;
   for (uint i=0; i < REDO; i++) {
       printf("== R%i/%i redo%i ", ntests, n, i);
       tc.ResetEngineResources();
       ParseScript(tc, scripts[rand()%nscripts], __FUNCTION__);
       tc.CheckEngine();
   }
   ntests++;
   tc.Over();
}

No assertion failure here, instead one of the trickiest things to track: memory exhaustion. So it's time for me to learn how to use my tracking tools again. I even left documentation for my future self back in the past (thanks, past self ^^). Once again, the problem arise also in 'default'... looks like I had been over-optimistic in September 2021 when I merged sprites overlay into default.

Well, at least if gives me hope this time: on the 'newmap branch', the forgotten test terminates with 'Out of DS memory'. I managed to get a log of what is allocated at that time, but it would require filtering what has leaked from last ParseScript, and what belongs to the current, interrupted because there's no more memory, running ParseScript. Hopefully, when the bug occurs on default, it gives me a nice 'halt because of leakage' and not a 'out of DS memory' condition. 

  • [todo] understand why the new branch gets so high on memory consumption
  • [todo] make sure .spr and .cmd files in fakeroot/ are working fine (despite they now need bilou.spr and school.spr)
  • [todo] that should be the job of UnitTest.mk

Thursday, July 20, 2023

class AppleWalls : public WallTest, UsingApple

So I had managed to get shell/walls interaction mostly fixed. I thought it would be wise to get it run 'unit tests' to ensure nothing got broken by the fix, but it would do more than a few seconds of tests before failing. In Walls/Walk test , a fairly recent addition to MapTests.cpp, for which I do not seem to have any commit where that test is a success.

The test failure will produce patterns such as

--(120,0)+<-508,0>:[-224,0]     's/7f.f, S/fe08.0, '
--(118,0)+<-512,0>     's/7f.f, S/fe08.0, '
--(116,0)+<-516,0>     's/7f.f, S/fe08.0, '

--(114,0)+<-520,0>:[-232,0]     's/77.f, S/fdf8.0, '
--(112,0)     's/77.f, S/fdf8.0, '
--(110,0):[-240,0]     's/77.f, S/fdf8.0, '
--(108,0)     's/77.f, S/fdf8.0, '
--(106,0):[-248,0]     's/6f.f, S/fdf8.0, '
--(104,0)     's/6f.f, S/fdf8.0, '
--(103,0)+<248,0>:[0,0]     's/6f.f, S/fdf8.0, '

which I, too, find cryptic. I should at least inform future self that they are from saved "last state" array, dumped on exception like assert failure during the test.
  • (%{x}, %{y}) is used to report new coordinates
  • <%{vx}, %{vy}> is used to report a new speed
  • [%{dx}, %{dy}] is used to report new delayed step

Together with the arena layout defined by walls1, it can start making sense. For instance, x=104 is the lowest valid position before entering the two-# "wall" of the upper platform, where the Gob-under-test is walking. Granted, this is arcane and confusing, and it would deserve a review fix even if it is only for me. Possibly even more confusing are the `s/%x.%x` codes reported between quotes afterwards. These are generated from ChiefInspector::report() call from the WalkerController.

  • lowercase s indicates that there has been a change of tile considered in doslopes(). hex values are the coordinates of the hotspot used to decide that.
  • uppercase S indicates that doslopes() reported we're on sloped ground. hex values are the speed defined during doslopes().

Even with that wrong report of sloped ground fixed, I still have my 'follower' capable of entering walls. It happens when we stop next to the wall, but do not cancel the 'step value still needed'. More investigation needed.

With some break getspeed and cond BREAKNO (x >> 8) < 105 && cdata[GOB_XSPEED] == -520 in an epic gdb session, I could trace precisely what happens, one GobController call after another, and how the speed, delayed steps and the like where further processed.

The issue turned to be linked to the 'maxmove computation', an engine feature added in 2020 that scans the animation commands ahead of think() calls, so that we don't ask doslopes() to check for something different than what we'll actually do, else the vertical move and the horizontal move won't match and we'll end up out of the slope. The code that decides what to do when we detect a move must be cancelled assumes that the motion debt stored in the 'delayed step' variable has already been validated by controllers. In case of failure, we're safe to move *at least there*.

That was true with the "school rush" engine, where it was helpful to ensure we actually get close enough to the walls for the testpoints to work as intended. It is no longer true if maxmove shortened some of it. How precisely this can be fixed still has needed a bit more time to be figured out.

PS: while looking for the 'inspector codes' in my 2020-notepad, I got my eyes caught by a character stating "this is contemporary to maxmove introduction"... The line below reads "hopefully, no need to tweak things (how many loops are allowed while processing animation instructions, btw) too much. All it takes is building an animation list with enough 'I_MOVETO(+1, *)' commands before it loops."

PPS: of course, fixing things here broke things there, but mostly because code was wrongly assuming that some things (like vertical position or FAIL counter)