Saturday, September 30, 2023

Un p'tit coup de marteau ...

Mes dernières sessions avec AnimEDS s'étaient toutes soldées par des "guru meditation". Peu de travail perdu, heureusement, le problème se produisant généralement soit juste au début, soit juste après une sauvegarde. L'impact sur ma motivation à continuer à animer mes p'tits persos en prenait quand-même chaque fois un coup: j'aurais pu effectivement perdre un travail précieux.

Alors j'ai noté de faire du debugging de tout ça. Il y a un bail. D'abord refaire une version précise de l'animateur (rH2021) et garder le .nds et son .elf à côté des fichiers re-générés à tout bout de champ quand je bricole du homebrew, histoire que quand le problème se présente console en main, je puisse effectivement utiliser la valeur du registre PC pour retrouver un numéro de ligne dans le code. C'est le cas depuis Juin.

I can't help wondering whether this is worth translating. It's another epic (?) showdown between me and my code to see who's got the StrongARM. But well, it has been bugging me since January, crashing the animation editor almost every time I used it. I'm just lucky I never lost anything important but motivation to work on some cute things over the evening. The "guru meditation" screens I got during the first half of the year were almost useless: the AnimEDS build on my 'lime' DS was so old I did not have the matching .ELF file for my debugger anymore. Believe it or not, the RealLife (tm) has turned so intense that I even had to write down an agenda check list with "rebuild ; keep .elf apart ; upload .nds to lime" to actually get it done.

Pas de chance: c'est un de ces bugs où l'adresse ne dit pas grand-chose parce qu'on est a suivi des pointeurs de fonctions qui ne voulaient rien dire. Mais! Bonne nouvelle, avec les nouvelles animations pour l'Appleman, le bug est plus facile à reproduire: il suffit d'essayer de copier une frame d'animation juste après avoir chargé la première animation du fichier dans l'éditeur.

Alors j'ai ressorti mon émulateur et mon débuggeur et là, bonne nouvelle, ça foire aussi dans l'émulateur. Différemment, mais ça foire, donc c'est débuggable. Et là, j'ai galéré pendant des heures ... Des structures qui ne veulent rien dire, des vptr complètement à l'ouest ... Il faut dire que pour encoder la ligne du temps utilisée par l'éditeur, j'ai pris une std::list de std::pair de classe dérivées. Bref, on se perd sous les couches de templates, de membres dépendant de l'implémentation et d'optimisation du compilateur qui prend un malin plaisir à rendre inaccessible les variables dont on aurait besoin.

With that done, I wasn't much more lucky. It's one of those bugs where you try doing a virtual function call on something that isn't truly an object and thus end up in the middle of nowhere, especially where memory contents doesn't match any valid opcode. Hopefully, while trying to get the crash to write down registers values, I realised that the bug was actually easy to reproduce (just open one animation and try to copy the frame before selecting any frame) and a bit later, that it also happened with the same file in my emulator. At least I could save the evening where I navigate DS memory to reconstruct objects on paper this time.

J'allais jeter le gant puis un soir j'ai noté dans mon calepin "fait du débugging indirect en surveillant les constructeurs". Bah oui: la variable aura beau avoir été optimisée, il faut bien qu'il soit construit à un moment où à un autre, le TIFrame qui explique pourquoi j'ai du n'importe quoi dans les registres au moment de copier cette première frame.

Sauf que ... non. Les "TIFrames" sont construites vides, puis au fur et à mesure que l'AnimationParser traite la liste de commandes destinées à GEDS -- le moteur de jeu -- il complète les coordonnées, les numéros d'images, etc. J'étais sur le point de laisser tomber (traduire: réécrire tout avec ma propre classe 'liste' plus propice au debugging) quand je réalise que je peux "figer" l'afficher d'une ou de TIFrame dont je capture l'adresse à la construction, et suivre leur évolution jusqu'au bug (l'animation en question ne contient en réalité qu'une frame et une commande de contrôle). Et là, surprise, tout va très bien (Mme la Marquise :)

 It did not save navigating memory altogether though. The way gdb handles std::list and std::pair combined to the amount of variables that were actually "optimized away" when they would be critical to have around still turned that debugging session into some guru meditation. I first thought that it was because of some incoherent animation instructions into the animation itself, but inspecting how the animationParser processed them shown everything should be fine. Yet, if I tried to see what frame was triggering the bug, all I'd get was garbage non-sense. I was about to replace all that std::* by some pype::* when I realised that I could just break on constructors of the contained TimeItems to see what the list contained.

That did not work either, unfortunately, because when TimeItems are added to the list, they are "blank" items that will be modified by the yet-to-come UPDATE animation commands. What did help, was creating some static watches at addresses discovered in a constructor-breakpoints so that I could look at the state of my list just before the offending call. That and realising that the list was just fine, thanks, but that I was using an iterator that might not have been updated since the previous list had been trashed and replaced by the new one :P

Une seule explication restante: c'est l'itérateur qui devient invalide au bout d'un moment. Je sors mon cahier A4 histoire de me faire une map UML de tous les bouts de code impliqués et c'est bien ça: quand on choisit une animation à éditer, la liste est vidée, mais l'itérateur reste inchangé, ce qui est invalide.

Ah oui. Vous vous demandez "pourquoi le marteau" ... et vous n'avez pas Thor. C'est à cause de cette blague d'ingénieur où un consultant rentre une facture de $100000 pour une intervention et ça rouspète chez le client parce que "vous avez juste donné un coup de marteau!". Et le consultant de reconnaître qu'il a fait une erreur et de préparer la facture suivante

  • 1 hammer hit: $10
  • knowing where to hit $99990

(bon, c'était vraiment une grosse machine très chère qu'il fallait dépanner). Bin c'est un peu la sensation que ça me fait sur ce bug-ci. Sauf que je ne vais pas recevoir des cents et des mille et que je ne devrai pas les débourser non plus :P

 

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.

The game indicates how long I spent on the level, and it was easily up to 45 minutes! 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.

edit: actual first contact with the game was back in 2016, before it caught attention of Henk and Chris. It was pitched by "8bit horse" and already caught my attention with a fun and interesting character design of "8-year-old Thor who gets Mjolnir as birthday gift"

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.