Monday, January 25, 2016

Leakage: 0%

Un script pour décoder les traces d'allocation de mémoire combinées à un mécanisme qui remplace malloc/free par une progression linéaire dans l'espace-mémoire et j'ai pu éliminer la totalité des fuites de mémoire dans le parsing des scripts.

Deuxième étape, une inspection systématique des listes d'animations maintenues par l'Engine lui-même histoire de s'assurer qu'il ne reste aucune référence à un objet libéré (c'est désormais facile, puisque chaque objet libéré se voit écrasé par des 0xfefefefe et que sa mémoire n'est pas ré-attribuée lors du test). Me voici armé pour tester les enchaînement de niveaux, mais jusqu'à un certain point seulement: au bout de quelques itérations, la mémoire disponible pour le test aura été complètement utilisée. Allez, je devrais pouvoir passer aux chargements-suite-à-une-fin-de-niveaux dans le courant de la semaine, et étudier les "script reset" expérimentaux supposés accélérer les chargements de niveaux.

I reached stable parsing, where no trailing objects exist when the GameScript is destroyed. The next test I implemented pointed out an object that was "recycled" but that was still referenced by the Engine's global state. So I am now ready to investigate what happens during levels transitions, both regular and "just reload" experiments. More testing to follow.

Sunday, January 17, 2016

When the player sees the challenge

http://imgur.com/a/215BY
The good thing with Mario Maker is that it makes it easy to illustrate how Mario games are built and where are the high-quality level design element. One point for which I had hard time to understand Kirby Kid's discourse was about coins placement. He typically objects that rings in Sonic are less interesting than coins in Mario
because some rings can't be obtained in one play through, the encouraging function of the rings becomes diminished. [...] Instead of saying "you can do it, keep trying," they say, "too bad you missed. better luck next time."
I ultimately got the true meaning of that thought while watching "Super Mario Fixer, episode 2" and see how KirbyKid played it, how he pushed effort into getting all the coins doing only one jump per platform, without going backwards. An exercise that may sound futile at first but that he describes as "recognize the challenge for themselves and let the player go for them".


Vous vous souvenez de Kirby Kid et de ses précieux commentaires sur le gameplay de ce qui devait devenir School Rush ? Un que je n'ai toujours pas pris en compte -- parce que je n'en comprenais pas la pleine mesure -- c'est la nature "généreuse et dense" des bonus sur les niveaux de School Rush. Forcément, il y en a assez bien, entre autres parce que c'est un enfant de 4 ans qui a choisi où les placer.

Où est le problème donc ? Eh bien, voyez-vous, si les bonus sont bien positionnés, ils peuvent proposer des façons alternatives de parcourir le niveau, par exemple "d'une traite, sans faire demi-tour ni s'arrêter mais en prenant tout de même tous les bonus". Ça représente une sorte de challenge supplémentaire que le joueur aguerri peut se fixer lui-même une fois que "finir le niveau" n'est plus un challenge intéressant pour lui. Mais pour se faire, il faudra veiller à ce que chaque pièce soit placée avec précision, pour qu'il n'y ait pas besoin de plusieurs passages pour être sûr de les ramasser toutes.

That's all about having some room for extra challenge for those who have full mastery of Super Mario. At that level, a single coin placement can make the difference. A single misplaced coin and the challenge is just impossible, and thus actually doesn't exist at all.

I want to have two ways of playing School Rush. One is power-play, rushing through. The other is exploring and experimenting. I can use some bonuses to tease the explorer and reward her. I can use some other bonuses to tease the rusher and reward him. Because I don't have a single coin, but multiple bonuses. I can have e.g. the "A" letters show the path for rush-and-be-amazing, and other letters for exploring places that require to do multiple pass over the level.

Sunday, January 10, 2016

100 bytes for a level

Il m'aura fallu me promener dans le code désassemblé de Super Mario Bros 1 pour finir par y croire. Oui, sur cette cartouche de 40KB, les niveaux sont extrêmement compacts. Oui, la technique pour en faire le rendu est à la base de ce que j'avais observé avec l'éditeur pour NSMB: bien que le hardware travaille avec des "tiles", le niveau est décrit par une série de commandes (un byte de coordonnées, un byte indiquant le type de commande et éventuellement une taille). On aura ainsi des "un tuyau de 3 blocs de haut" puis "une rangée de 5 briques" et "un escalier de hauteur 3". Voire même "un trou dans le sol de largeur 4". Pas de "patterns extensibles" derrière ces codes, mais directement du code assembleur!
Les buissons, nuages, le sol sont eux décrits sur une autre couche qui est utilisée en premier lieu pour remplir la zone mémoire sur laquelle le niveau sera ensuite dessiné.

Thanks fly to Daniel Turner who teased my interest for how SMB1 encoded its levels. The cartridge was incredibly small (40KB) and indeed encoded levels as a list of "painters" operation, each giving a coordinate on the current screen, a type and possibly a size. Painters are simply dedicated assembly routine, and thus they could have something as complex as "add a staircase of 9 steps".

What I beleived to be an original technique in NSMB was thus a good old recipe. Would it be useful to shrunk those 64K levels of Bilou : School Rush ? Well, hardly, because compared to SMB1 I have much more variety in the graphics of the level, and I used those "alternate tiles" to make books, folders and pencils looks as unique as I could and avoid giving the feeling of a world built by copy-pasting. That's a bit sad, because SMB approach definitely makes editing the level much easier.

L'équipe RD4 aura profité au maximum du fait que le jeu ne fait pas marche arrière: le niveau est encodé écran par écran, et les coordonnées des objets sont données à l'intérieur de l'écran en cours. Un marqueur est ajouté aux objets qui se trouvent sur un nouvel écran par rapport à leur prédécesseur. (I AM ERROR) Il faudra attendre que SMB3 embarque une RAM additionnelle dans sa cartouche pour que le jeu puisse se souvenir des blocs déjà cassés et des pièces collectées, rendant ainsi possible une navigation plus libre dans le niveau (/I AM ERROR).

Un monde de différence, donc, avec le stockage brutal d'un tableau de MxN éléments que j'utilise dans LEDS (avec des niveaux de 32 ou 64KB. Plus gros que l'entièreté du jeu SMB1, donc), mais qui me permet en contre-partie une souplesse totale pour que les livres ne soient pas tous exactement les même. Allez, apparemment, une simple compression .zip pourrait réduire les niveaux à une taille d'environ 8KB.

Tuesday, January 05, 2016

10 ans de DS

Voilà. Il y a 10 ans, je déballais ma première DS. Juste un an après, je démarrais le projet "dsgametools" sur sourceforge. Bonne année 2016 à tous. J'espère parvenir à finaliser "School Rush" cette année ^_^ Le prochain projet de Bilou "Infinite Pyramid" a déjà commencé à me faire cogiter.



Happy new Year, everyone. Know what ? This year, it's been exactly 10 years since I got my first Nintendo DS. I still hope I'll be able to complete and release School Rush this year so that I can start working more effectively on my next homebrew project: Bilou in the Infinite Pyramid.

Noted the pattern ? 

  • one year from gameplay sketch to game release
  • one year from engine test to gameplay sketch
  • we have most of the GFX done within the first year.

Wednesday, December 30, 2015

Je peux améliorer mon C++

Grande différence entre mon "nouveau" boulot (depuis Mars 2014) et mon ancien poste universitaire: ici, il y a des revues de code. Et mes collègues "Hergé et Jigé" ont un sacrément haut niveau en C++ comparé au mien. Alors autant profiter de mes deux semaines de "Super Papa Bros" pour essayer de remanier le code de mon moteur de jeu, le rendre plus fiable, plus lisible, et peut-être plus efficace.

J'avais introduit un mécanisme de gestion de mémoire inspiré du cours "compilateurs" : le "tank", avec un seul bloc de mémoire qui est découpé progressivement en sous-blocs qui auront tous la même durée de vie. L'ennui principal, c'est que ce "tank" n'a aucun moyen de retenir quels objets ont été créés ni d'appeler les destructeurs en fin de cycle. Du coup, tout objet "standard" présent dans les morceaux du tank sont une fuite de mémoire potentielle.


Parmi les "nouveaux trucs" appris cette année qui pourront m'être utiles, il y a la fonction "foreach", les fonctions template (et en particulier leur utilisation pour faire de la programmation assertive), les namespaces anonymes, et les structures-internes-pour-masquer-l'implémentation.


Let me collate a few C++ tricks I practiced this year and hope to use in my hobby tools/game engine to improve them.
If it make sense to have a function applied on all members of a collection, foreach can help:

- for (vector<Tire>::iterator it = wheels.begin(), e = wheels.end(); it != e; it++) {
- checkPressure(*it);
- }
+ for_each(wheels.begin(), wheels.end(), checkPressure);


Template function do exist. Template functions do not need their template argument to be specified when it can be inferred from function arguments. E.g.

template<typename T>
void assert(T a, T b, const std::string msg) {
  if (a != b) throw AssertException(msg);
}

can be invoked as
+ assert(myCar, TimeTravellingDelorean, "timed' out");
-assert<Car>(myCar, TimeTravelling ...);;

Template integers exist too. If you want something to behave completely differently depending on whether you're on a 32-bit or 64-bit system, you might consider the following function that can be invoked as getLibraryPath<sizeof(int)>():


template<int> path getLibraryPath();
template<> inline path getLibraryPath<4>() {
  return "/usr/lib32";
}
template<> path getLibraryPath<8>() {
  return "/usr/lib/x86_64-linux-gnu";
}

Note that only template declaration can fit within the class body. Specializations introduced with template<> must be out of the class block and have additional MyClass:: token.

And as we're talking about templates stack overflow's question on puzzling template error messages can help.

You don't need to declare your functions static to avoid interference with other translation units of the program. Simply put them in an anonymous namespace.

You don't need to explicitly track the "object setup sequence" with an init_level if you can do it with contents of the regular members of the objects,


Car::~Car() {
- switch(init_level) {
- case TIRES_MOUNTED: RecycleTires();
- case ENGINE_INSTALLED: RecycleEngine();

- // FIXME: what do you do for default: ?
- }

+ if (tires!=UNDEF) RecycleTires();
+ if (engine!=UNDEF) RecycleEngine();

PS: UNDEF could just be 0 for pointers to components.

You can have compact structure initialization with (optionally-)named fields but it must be *trivial*, e.g.

  • you may not swap the order of components;
  • you may not omit a field if there are other field after it
  • but you *can* omit items at the tail of the description

If you want a class/struct to look more like a first-class citizen, think about
  • copy constructor : Car(const Car &that) : engine(that.engine), tires(that.tires) {}
  • comparison operator : bool operator==(const Car &that) { return that.tires==tires && that.engine==engine; }
  • ostream-compatibility: this requires a additional std::ostream& operator<<(std::ostream& os, const Car& that) { os << "powered by " << engine << " on " << tires; return os; } function. Note that it is *not* a member of the Car struct/class and that it will need to be declared friend of the Class in case of a class.


Something thrown as throw new std::runtime_error(..) is caught by catch (...) { releaseResources(); throw; }, but not by catch(const exception& e). That latest one only catch stack-allocated exceptions, e.g. throw std::runtime_error(...);. Reading more on this I should.

I should remember that namespace ds = PPPTeam::LibGEDS is the way to say import PPPTeam.LibGEDS as ds. And that ostream & operator << (ostream &out, const Complex &c) is the way to tell how the class 'Complex' should be printed.

Oh, and I shouldn't use std::unique_ptr on stack-allocated object. ever. unique_ptr will eventually call free on the pointer it holds.

 

In case of doubt on performance, remember that Quick-Bench.com does exist.

Tuesday, December 22, 2015

Usage: 15% ; Leakage: 12%

I guess you all know what a "memory leak" is. Well, you might not have a lot of gameplay updates in the following weeks: I just enabled a sort of report that measures how much of the state created when parsing the title screen of School Rush. 12%. I've got some code-cleanup to go through.

Bon, bin les premiers résultats de l'analyseur mémoire intégré à mon système de test-de-code-DS-sur-x86 sont précis, mais aussi assez effrayant: 12% de la mémoire allouée lors de l'initialisation de l'écran-titre ne sont pas rendus à la fin du niveau. Dans un cas pareil, la mémoire de la DS (4Mo) finit systématiquement par être saturée après un certain temps d'utilisation du jeu, ce qui ce traduira fort probablement par un crash.

Allez, joyeuses fêtes à vous aussi. (Et, euh, non, n'espérez pas trop une version "spéciale Noël" de School Rush avec de la neige sur les bancs et des bonnets à ponpon sur les gommes sauteuses, du coup)

PS: I suspect this could be linked to the migration of most state-machine data structure into the "tank" memory allocator: because the tank doesn't know objects themselves, and that it's quite unlikely that it invokes their destructors when "flushing" the tank. I'll have to make sure that structures that were pushed into the tank are flat enough or that their sub-components are allocated in the tank too.

Monday, December 21, 2015

Rayman Designer

Quelque-part en 1997, Ubisoft sort "Rayman Gold", avec un éditeur de niveau. Curieusement, je passe mon tour. Pas trop envie d'acheter une 2eme fois le jeu que j'ai toujours pour pouvoir avoir l'éditeur de niveau ? Pas trop envie de me contenter de faire des maps dans un univers qui n'est pas le mien et d'en être réduit aux seuls ennemis déjà existant dans le jeu ? J'avoue que je ne saurais plus trop dire.

Years before Mario Maker was Rayman Designer. I never owned it, and it never quite appealed me to play custom maps, although I loved very much the original game. It's a bit hard to say why, but I think it could be reduced to two things.

First, the core gameplay is different, and the player is asked to collect every ting (read "golden coin" if it was a Mario game) to reveal the exit of the level. Okay, I love it when you have something more to do than reaching the exit, but being forced to grab every whatever to complete the level always felt annoying. I already found it lame near 2001 when I played Rayman II and discovered that without collecting all the tings of the games I couldn't access the last levels. But here you have to collect them all *level per level*. Oh, okay, you can select levels freely, but still. And the "colored tings that open gates" felt artificial, even more abstract than typical "keys and locks" and much less integrated than Commander Keen's coloured keys.


Pas de cages dans cet épisode, mais des "tings" de différentes couleurs, pouvant ouvrir des barrières quand on a récolté tous les tings d'une couleur donnée. Du coup, le level design ressemble assez fort à un mélange entre pacman et rayman. Un aspect qui m'a assez refroidi du côté de 2007 quand j'ai découvert tout ça sur wiki.

The second thing .. what would player's level look like. I did some game making myself between 1995 and 1997, and I know people who got granted the super-powers of creating levels tends to build impossible missions that they only can beat (sometimes cheating like being the only ones to know that lava is not quite lava in the 3rd pool and that the player needs to dive there to get access to the end of the level). Checking youtube videos of "Rayman by his fans" proved that this was mostly not the case, but instead aspiring level designers forgot to hint on where the tings are to be found, leaving the player to explore the map again and again everytime some magic happened to make things appear or disappear, and ultimately rely on some flying potion to collect the last tings somewhere in the top of the map where regular platforming just can't let you get them.

Mais j'étais curieux l'autre jour de voir un peu à quoi pouvait ressembler les niveaux intégrés dans l'édition "Rayman Forever" -- niveaux de fans repris sur galette. Sont-ils du même tonneau que les missions impossibles de Mario Maker ?


Pas exactement. Pas ceux que j'ai vu, en tout cas. Par contre, il y a usage abusif de la "potion magique qui te donne des aileuus", présente presque systématiquement, et contrairement aux designers de Ubisoft, qui prenaient soin de guider le joueur vers la suite du niveau une fois qu'on a ramassé le "ting-qui-fait-apparaître-un-pont-quelque-part", on risque ici de devoir ré-explorer tout le niveau chaque fois qu'on entend la petite fanfare qui annonce que quelque-chose a changé. Bof pour moi, donc.