Sunday, September 30, 2018

Another devkitpro update

Another laptop update ... The previous one started to act pretty weird regarding AC power. I survived a couple of week with the old (2007) laptop, enjoyed the music collection it has and the larger screen. This one is more similar to the faulty one with a 1350x768 screen, but it has SSD.

Well, it was also the opportunity to try dsgametools with the latest devkitpro. And it was pretty disturbing. At first, you now have to install a package manager first before you install what you need. It could have been done with a custom apt-get setup but they opted for pacman with a binary package you have to pre-install from their github. Not really the way I believe open source should work ...


Pour bien démarrer le développement homebrew, que ce soit sur NDS ou GBA, une seule addresse: devkitpro.org. La procédure d'installation a pas mal bougé depuis la dernière fois, par contre. L'équipe de Wintermute est passé à un dépôt pacman (pour "PACket MANager", hein. Toute ressemblance avec une pizza au citron n'est que clin d'oeil de geek), avec la nécessité de télécharger leur version binaire de pacman (pourtant un outil fréquent dans les distributions linux) avant de continuer.

Les utilisateurs windows auront peut-être une expérience plus proche de leurs habitudes avec l'installateur .exe ... Un peu déboussolant pour un vieux briscard de sourceforge, mais j'ai fini par en venir à bout et mon laptop tournant un ubuntu bionic LTS 2018 est maintenant prêt à me râler dessus vu que les réglages par défaut de g++ 8 n'ont pas grand-chose à voir avec ce à quoi mon code était habitué.

I'll keep the new error messages and improved warnings of g++ 8.1.0 for another post. Here, I missed some headers or other stuff to rebuild everything

  • errno definition for dswifi ... that is likely an old one. manually installing sgIP_errno.h apparently fixed things. I'd prefer to get the sources of current dswifi to ensure everything is fine. Hope I'll find them.
  • load_bin,load_bin_size as well as bootstub_bin, bootstub_bin_size (for nds_loader_arm9, used in runME and self-upgrade features) are missing. Iirc, they should be built from .bin files ... They are still in libppp9/data ... I'll have to find out why they were not processed.
  • there's a weird warning about __sync_synchronize() which is invoked when I have a static object. I have a workaround, but I really wonder why they introduced that in newlib...

Friday, September 28, 2018

/r/NDSHacks

During my HTML Quest to find people interested in NDS development nowadays, I stumbled upon a discord channel named /r/NDSHacks. I'm not much into Discord, but since I made an account this summer to follow the gosh-sad-panda-put-Nectarine-offline discussions, I did my best to enter #homebrew_development ...

With some luck it might make my tutorial more meaningful and get some beta-testers more easily. We'll see.

Tuesday, September 11, 2018

Truly Testable Level Editor

There was one annoying thing with the scripts while adding 1-up, and  that was I had to add the new rules in every level. This is the kind of things that should be written only once, and as far as the script parser is concerned, it could be written once, in the "rules.gam" file. The problem is, this file would be ignored by the level editor, and thus is not used so far.

And unfortunately, there are some design flaws in the way LEDS' own parser is written, making any change in that part pretty tricky to do right. It would be nice to refactor that a bit, but such a refactory with no easy way to test things would be errors-prone. My next move must be towards testable LEDs logic.


And I realised my first idea for the tests, with modified readers and all, was over designed.

Instead, it would be interesting to have two simple programs that run checks on parsed levels (eg. what are the coordinates of game object #42 , or how to display special block#3) or that do simple changes and then write back the result. Test cases are then simple shell perl scripts combining calls to those tools and to standard file comparison tools.

What I still need to figure out is how I'll extract specific information (e.g. what gob another gob links to) out of the full data. And how to compare the extracted data.

Friday, September 07, 2018

Retro-game mechanics explain: golden!

I just went into a super video (link below) about how collision code can be exploited in Super Mario World to beat the game faster. I'm not much into speedrunning myself, but I love whatever can teach us how those old games where built and what is the logic behind their engine. And tool-assisted speedrunning quickly gets quite deep into those subjects. So let's go.

C'est du tout bon. Une vraie pépite. Une fois encore, "retro-game mechanics explained" confirme que même quand on a pas l'intention de pratiquer le speed-run intensif, les découvertes des speedrunners sont une mine d'or pour qui s'intéresse aux techniques de programmation utilisées par les anciens de l'ère 8/16-bit. Et cette fois, c'est Super Mario world qui s'y colle.

When a tile is activated, it is deleted, and a sprite version of the block will displayed in its place. When that sprite returns to its initial position, it is removed, and another tile is set in its place (usually a brown block).
Well, that was an expected one. And as he explains, it was already used in SMB3 (and probably also back in SMB1). It is something I'd like to put into my own game engine as well, although the closest I have so far is simply the "mapanim" to replace tiles of a specific location on the map along an animation triggered by a collision.

Si vous avez déjà un peu cogité la manière dont les consoles nintendo construisaient leurs images à base de mosaïques de "tiles" et des "sprites" par-dessus, vous aurez aussi deviné que pour faire sursauter un bloc-question quand on le touche, il faut effacer le bloc de décor (à base de tiles, donc) et le remplacer par un graphisme librement positionnable (un sprite) le temps de son sursaut, puis remettre à nouveau des tiles pour le bloc transformé. C'est sympa d'en avoir une confirmation depuis l'analyse du code (et oui, j'ajouterai ça dans ma todoux-liste un de ces quatres).

The next one is a bit more unexpected.

Which tile is activated during sprite/tile collision is determined by a point that is a mix (blue) between the the sprite's position (green) and its clipping box (red). If that point is not within the tile that was activated [...] there will be block duplication.
Of course, that duplication is the whole point in SMW - Level End Glitches video by RG Mech EX:  the location where to spawn the bopping block and set the new brown block won't match the original question block location, which will remain unchanged. Interrestingly, this is partly because sprites that are located inside a solid tile are ejected outwards so that they don't get stuck. I always thought that would be mario-specific, but actually no: it applies to all objects.

Un peu plus inattendu: en plus de leur zone active -- la hitbox, en rouge sur la carapace -- qui doit rester en-dehors des zones solides du jeu, les objets ont un point unique qui sert à déterminer quel bloc a été touché. Si on cogne un bloc-question avec une carapace lancée vers le haut, c'est d'abord le carré rouge qui va renseigner qu'il y a collision puis le point bleu servira à trouver avec quoi il y a collision. Chose intéressante, tous les objets subissent le traitement "repoussé par les murs" qui autorise Mario à contourner un bloc lors d'un saut plutôt que de s'y cogner méchamment comme une Giana sister.

En revanche, le fait d'avoir mis le point-test en dehors de la zone de collision m'intrigue. Quelle est la raison ? ou est-ce juste un bug ? et cette possibilité d'avoir changé de position entre les deux opérations du test de collision (boîte et point) trahit-elle une optimisation du genre "on ne teste les blocs-question qu'une frame sur 4" ?

A few wonders ...
- is there a good reason for that blue hot spot not being with the red box (other than saving computation cycles) ? It just sounds like a bug to my ears, since the red box is what triggers the collision.
- is that "ejected first but still triggering the initial block" linked to some lower rate for the collision code compared to the motion code ?


And did you know ?

In order to reduce the number of distinct objects, some power-up blocks have different contents depending on their X coordinate on screen.
The limit on the number of objects you can have in a game engine is a old opponent. I know him a bit too well myself. But still ... Thinking of editing your level and having to shift that key pick-up one block to the left or one to the right so that it actually contains a key feels just mind-blowing. Naturally, it might not have been a big deal for Miyamoto's team who already knew player needs wide enough areas to move their avatar around ... and possibly went for "aha! guess which of those 4 ?-blocks hold a key and which are mere coins ;-)". But still. That's pretty unexpected.

Mais il reste le plus croustillant. Le truc que explique qu'un bloc supposé contenir une clé peut tout d'un coup donner des ailes à yoshi. Visiblement, je ne suis pas le seul à avoir choisi trop peu d'information par bloc dans mon format de niveau, et pour pouvoir représenter tous les power-ups et bonus possibles dans Super Mario World, l'équipe de Myamoto a choisi d'utiliser la position du bloc au sein du niveau pour choisir quel objet serait offert au joueur. Pas la position absolue, hein, mais le fait qu'il soit sur un bloc pair, multiple de 4, impair, etc.

C'est à la fois génial et complètement déroutant. Dans un commander keen, je ne me serais jamais attendu à un truc du genre "si le bonus est au 2eme étage du building, alors c'est une glace à 2000 points. S'il est au 1er c'est un donuts à 1000 points et au 3eme un nounours à 5000". Mais ici, dans un contexte où les blocs-question sont souvents présentés alignés comme les gobelets d'un jeu de hasard, le truc prend tout son sens. Il reste à considérer le "numéro" stocké dans le niveau non plus comme un identifiant d'objet à créer mais plutôt comme l'identifiant du générateur d'objets correspondant. Au moins, ils ont évité les contraintes du genre "un niveau peut offrir soit les ailes, soit une clé, soit un ballon, mais jamais une combinaison de ceux-ci."

Wednesday, September 05, 2018

gimme a (control-)break!

Out of TheOffice unexpectedly. TheOffice runs Windows systems and recently decided that VPN solutions should be replaced by a remote desktop gateway. Oh yeah. Back then I had made a quick try with xfreerdp. It was quite ugly to setup (especially because xfreerdp **wants** you to provide your password on the command line) but it did worked.

Couldn't get it to work again in my sick-on-Monday mode. So I tried to find something better with remmina. After re-installing the software from vendor repositories (rather than distro repositories, which I typically prefer) to get access to the gateway-support plugin I managed again to connect. It wasn't nice (256 colors by default), it wasn't fast, but it did the trick. But unfortunately, i clicked "connect" rather than "save and connect" and my setup got lost.

Unfortunately, I couldn't set it up right again.

So let's try xfreerdp again

let $WUSER be my 'THEOFFICE\\brosp' domain/user name à la windows
let $WACHINE be the name of my machine at the office w-brosp-hb.example.org
let $GATEWAY be the name of the gateway like thegateway.example.org
and let's say get_a_password is a shell function that will just read one line of text and return it without showing it on screen ...

then

xfreerdp /u:$WUSER /p:$(get_a_password) /v:$WACHINE /g:$GATEWAY

did the trick.

It worked, it is nicer (i'd say I got true colors) but just scrolling to the output of the console is a pain. Give me a SSH login **please**. My firth thought of "yeah, I know, I'm going to run a SSH server at home (hopefully I can get my public IP address quite easily nowadays, although it won't be the same everyday) and I'll setup a reverse tunnel from work. The firewall/NAT on the ISP box might not like that as much as I do, though. I'll have to tweak it to give my laptop a fixed IP, etc. That won't be for today, I'm afraid.


Thursday, August 30, 2018

1-ups... At last.

Finally, something good does happen when you collect letters in School Rush. Nothing fancy, and not even anything original, I'm afraid: you just get an extra life. Yet, considering the difficulty of the -final level, this might be welcome.

It required some new tools, though. Lives, hit points and collectibles are managed through counters in the game engine. counters can normally get their value defined only when you parse the level script. Then, you would only increment or decrement the counter as game events occur. Then, you can define one or more actions that happen when the counter reaches zero.


Enfin! Ça vaut enfin la peine de faire la collecte des lettres dans "School Rush". Rien que du très conventionnel, j'en ai peur (on prend une vie supplémentaire), mais bon, vu la difficulté du dernier niveau, quelques vies supplémentaires ne seront sans doute pas de refus. Mais cela ne s'est pas fait sans de nouveaux outils.

When hit points counter gets to zero, for instance, a jingle and a death animation are played. We can even force a domino effect that decreases the "lives" counter then. But that mostly works because I load level" action also allow an expression to be defined, and because you're then allowed to reset the hit points value when reloading the level.

To provide 1-ups, I had to introduce a new elementary action dubbed "setcounters" that offers that freedom out of the level-loading machinery.


Les vies, les points de vie, les objets à récolter, tout cela est géré à travers des compteurs dans mon moteur de jeu. Normalement, on ne sait definir la valeur d'un compteur qu'au chargement du niveau. Durant le niveau, en revanche, on ne sait qu'augmenter ou diminuer la valeur petit à petit.

Le gros intérêt de ces compteurs, c'est qu'on peut forcer l'exécution d'une action lorsqu'ils arrivent à zéro. Comme redémarrer le niveau, changer la musique ou, faire apparaître un nouvel objet. Mais idéalement, ici, il faut surtout augmenter un autre compteur (les vies) et reprogrammer pe compteur arrivé à échéance (le nombre de lettres avant la prochaine vie). Bref, ce sera le rôle de l'action "setcounters", fraîchement ajoutée à la panoplie du parfait programmeur de GobScript.

Tuesday, August 07, 2018

Air Control

Bon, voyons un peu ces histoires de contrôle aérien, maintenant. Je sais qu'il y a des effets que je veux éviter et d'autres auxquels je tiens. Et ce à quoi je tiens par-dessus tout, c'est que le joueur ait la sensation qu'il tombe lorsque la gravité reprend le dessus. Pas question donc que le déplacement horizontal puisse devenir plus rapide que le déplacement vertical si ce n'était pas le cas au moment de commencer à sauter.

It's time to have a closer look at the mid-air player control. There are thing I want to have and others I want to avoid. And among them, my priority is that the player should feel Bilou is falling once the gravity pulls him back. No way the horizontal speed could grow higher than vertical speed, unless it was already the case at the jump impulse. If there is no wind, and if we can fall down for long enough, it shouldn't even be possible: air resistance is the same in all directions!

En fait, en l'absence de vent, et si on peut tomber suffisamment longtemps, ça ne devrait même tout simplement pas être possible: la friction de l'air est la même dans tous les sens. La seule chose qui peut faire qu'on se déplace plus vite horizontalement qu'on ne peut tomber, c'est le fait de planer. Et ça, j'ai déjà un power-up pour le gérer.

Maintenant, soyons honnètes: je viens de me repasser des vidéos de Super Meat Boy et de N+ jusqu'à plus-de-vaisselle-à-essuyer ce week-end et je n'ai jamais pu mettre en évidence ce "point d'inflexion" ou la vitesse horizontale accélèrerait plus vite que la vitesse verticale. Par contre, la gravité est tellement basse qu'on peut difficilement dire qu'on a l'impression de tomber.

The second key property is that we must be able to tell how far we can go with one jump. If Bilou is moving at top speed on the ground, there is no reason we seen in accelerate just because he jumped. So if it took us exactly 1 second to cover N blocks while walking, then it must also take us 1 second to clear a N-blocks hole, and we can only do it if the jump makes us stay in the air for at least a second.

Deuxième élément: la portée du saut doit être prévisible. Si Bilou se déplace à sa vitesse maximale au sol (soit en courant, soit en marchant), il n'y a aucune raison qu'il se mette à accélérer une fois en l'air. Si on met exactement 1 seconde à franchir N blocs en marchant, alors on prendra 1 seconde à franchir ces N blocs en sautant pendant la marche et on saura les franchir si et seulement si le saut dure au moins une seconde.

L'exception à ce principe, c'est le saut depuis l'arrêt. Ici, on ne sait pas conserver l'énergie d'origine du personnage. Par contre, il me semble important que le joueur ne puisse pas atteindre la vitesse de course à partir d'un saut-à-l'arrêt.

There is one important case where this rule is deliberately broken, and this is the case where we were initially standing still, but start moving sideways mid-air. Here, there is no initial energy we could preserve. I feel like we should not let Bilou gain running speed mid-air from a standing jump.

Jusqu'ici, on va plutôt dans le sens de garder ce qui a déjà été développé. Une chose que je voudrais améliorer, par contre, c'est le tuning du saut à plus grande vitesse.

Le simple fait de relacher la direction "avant" lors d'un saut permet dans Bilou de retomber à une vitesse horizontale nulle. Par contre, si la vitesse retombe en-dessous de celle de la marche, il est impossible de remonter de nouveau à une vitesse plus élevée...
J'aimerais mieux pouvoir moduler la vitesse en relachant puis ré-enfonçant le pad. On pourrait du coup avoir n'importe quelle vitesse entre la marche et la course.

All of this so far confirms the current implementation. There is one thing, though, that I'd like to see improved: high-speed jump tuning . I mean, by just releasing the forward direction mid-air, we allow Bilou's horizontal speed to fall down to zero. And once your horizontal speed falls below top walking speed mid-air, you are never allowed to be fast again. I would love to be able to modulate speeabgpressing and releasing the d-pad instead. we could then be moving mid-air at any speed between walk and run.

Grosse difference par rapport au contrôle de Super Mario, donc, où si on veut si arrêter son saut, il faudra faire demi-tour avant d'avoir atteint la hauteur maximale.

Finally, there is a significant difference between current Bilou air control and the one seen in Mario games : Bilou can make a complete turn-back mid-air. you do not even have to plan such a move ahead: if you press backwards before you reached the top of your jump, you will land back safely on the platform you just left. In Mario, instead, it already takes you a lot of effort just to cut the horizontal distance in half. No wonder why they imported Yoshi's rodeo arrack in the "New Super Mario"

Mais pour être franc, je dois admettre que j'aurais du mal à me séparer du comportement actuel. En particulier parce que ça donne la possibilité d'annuler un saut si jamais je n'avais pas assez de vitesse. un confort que Mario n'offre qu'aux plus ratons d'entre-nous.