Tuesday, December 29, 2009

Xmas Checkpoint

Je ne vais pas vous faire le coup du changement de palette pour avoir une forêt aux feuilles toutes blanches de neige, ni affubler Bilou d'un gros bonnet rouge et blanc ... Mais joyeux noël et d'avance une bonne année 2010 quand-même.

A défaut de "release", voici l'état actuel de mes trois applications:

  • le sprite editor, dont j'ai un brin amélioré l'édition de palettes.
  • le level editor, avec son interface revue et corrigée.
  • le moteur de jeu, avec support des pentes. J'ai aussi essayé d'un peu ajuster les collisions avec les ennemis. Vous pouvez donc vous amuser *aussi* à écraser les appleman.
I'm afraid I'm not going to show you Bilou with a red, floppy hat in snowy woods this time. I'm afraid you won't even consider as a "release" the snapshot of my three major DS tools available from the link above. Okay, the Sprite Editor has (slightly) improved palette editor, featuring readable RGB and HSV values. Okay the game engine better supports slopes and appleman has an improved behaviour.
But before I go for a "real" release (that is, an extended level that really showcases slopes), I'd like to improve my level editor so that it could handle .cmd files -- and especially graphically represent and edit monsters.

Je voudrais parvenir assez prochainement à vous faire une "nouvelle release" (comprenez, greendemo avec un 3eme stage) qui fasse profiter pleinenement de l'ajout des pentes. Mais pour ça, il faut que je progresse encore un peu sur l'éditeur de niveau, histoire de lui permettre de gérer les monstres présents dans le niveau. Grosso-modo, il s'agit de permettre graphiquement l'édition des lignes du genre "gob4 :state8 (300,240)" qui positionne un nouveau monstre et définit son état initial. La bonne nouvelle, c'est que puisque j'ai "extrait" les comportements dans des fichiers .cmd séparés lors de la dernière release, celà devrait réduire le nombre de lignes à traiter. L'inconvénient, c'est que je n'ai pas de liaison entre "state8" et une image représentant, p.ex. Funky Funghi à moins de me retaper le parsing de ces fichiers séparés (pour identifier les images associées aux animations associées aux états :P)

There's just a silly little thing to figure out for this: how am I going to know what graphic should show up as "state8" in a gob-description-line without importing the whole GameObject.cpp file into the level editor ...

Tuesday, December 22, 2009

Tracking Memory Bugs

malloc et free sont des outils formidables, mais quand on s'est un peu embrouillé dans son code, ça devient vite une plaie. Je m'explique: le système d'allocation dynamique ne fonctionne pas "magiquement": il conserve autour des blocs libres et occupés des informations : taille de la zone, prochaine zone libre, etc. Elles sont évidemment importantes lorsqu'une zone libre est allouée ou qu'une zone occupée est libérée, mais elles servent aussi lors d'opérations de "compaction" (pensez au defrag du disque windows, vous n'êtes pas loin). Si on dépasse l'espace alloué pour une zone A, on peut facilement aller écraser les informations de contrôle d'une zone B. Du coup, on perd le lien entre la cause de l'erreur (le code qui gère A) et ses conséquences (erreur lors de la libération ou de l'allocation de B).

Dynamic memory allocation is a key aspect of programming that both open new horizons and puzzle newbie programmers. Unfortunately, it can also become a real nightmare for the seasoned programmer who has overestimated his skills, even just temporarily. malloc and free are not built from magic out of the void*, of course. They are algorithms that use small memory headers to maintain internal information about which block is free, how big is what, etc. This information is used when allocating/freeing blocks, of course, but also in the process of coercing small, contiguous free blocks into a larger one to avoid excessive memory fragmentation.
The problem is, that if code responsible for block A trashes information about block B (e.g. due to a buffer overflow bug), the error remains latent until block B is used again, and by then, you completely lost the knowledge that A is the faulty guy.

Ca fait un moment que je traine un bug de ce genre dans runme, sans trop savoir si c'est mon code qui est en faute ou celui de NTXM, le module player que j'y ai intégré. Bug plus ou moins facile à reproduire d'une révision à l'autre, mais que je sais être lié à une erreur de gestion de mémoire grâce au "crash code address" indiqué sur l'écran "guru meditation". Ces dernières semaines, j'ai pas mal avancé dans la gestion des erreurs et j'ai maintenant une fonction die() qui peut reprendre la main en cas de guru meditation ou d'exception C++ non traitée. Elle peut à présent tenter de retourner au moonshell, mais aussi afficher le contenu de n'importe quelle zone de mémoire de la DS. De quoi étudier la situation de manière un peu plus méthodique.

I know that I've got such a bug in runme (hence the lack of releases and the extensive use of gedsdemo). It's reappearing now and then and then hides again for a few releases. I've already proceed to a few code sanitization steps, but it doesn't look like i've been effective. With my recent findings (__cxa_terminate) and the introduction of a die() loop that replace main() after a guru meditation has been notified, I've given myself a memory inspection tool that let me follow "valid blocks" (starting the list from entries in __malloc_av_, the entry point of free lists) up to a faulty block that precedes the one where malloc/free() triggered an exception due to a wrong size.

Now, the goal is to trace back the owner of this memory block ... to be continued.
Un papier ... un bic ... pas mal de patience et la DS branchée sur chargeur ... C'est parti.

Thursday, December 17, 2009

512K! Time for Diet Code!

Quand j'ai lu l'article de Cearn sur la taille des binaires C++ pour NDS, je dois admettre que j'avais un léger sourire en coin. C'est bien connu: C++ a besoin d'un "run-time", cet ensemble de code quasiment inévitable qui permet au langage de tourner parce qu'il fournit, p.ex. la gestion des exceptions, etc. Par contre, quand j'ai vu hier lors de la dernière mise à jour de runme que l'exécutable avait gonflé jusqu'à 512K, j'ai cessé de sourire.

Sur la balance du code DS, pour moi, 512K, c'est "Va courir!"

J'ai ressorti la page de Cearn (l'auteur de TONC, ma première référence en programmation GBA) et tenté d'améliorer sa technique pour savoir d'où cette augmentation subite provenait. Bon, déjà, je compilais toujours sans aucune optimisation (-O0, pour faciliter le debugging de ces dernières semaines). On redescend vers les 420K. Ouf. Je continue, passant de la sortie de nm au désassembleur pour trouver qui appelle les fonctions que je n'ai pas écrites moi-même, genre d_print_comp ou _dtoa_r ... A force de creuser, j'ai fini par identifier deux fonctions à la base d'une bonne part de l'overhead. Tirant avantage du mécanisme de liaison des programmes aux bibliothèques, j'ai remplacé ces fonctions par du "code creux", court-circuitant ainsi un bon 40K de run-time support:

 /** these are heavy guys from the lib i want to strip out. **/
extern "C" char* _dtoa_r(_reent*, double, int, int, int*, int*, char**) {
die(__FILE__, __LINE__);
}

extern "C" char* __cxa_demangle(const char* mangled_name,
char* output_buffer, size_t* length,
int* status) {
if (status) *status = -2;
return 0;
}


Et très sympathiquement, Cearn lui-même s'est montré plutôt intéressé par mes résultats.

Nice! How did you find these? That is to say, how did you find out these are the ones at the top of it and can safely be removed?

There are also two others that I found out about recently: __cxa_guard_acquire() and __cxa_guard_release(). They were introduced when I tried to create a local const
array using data from a global constant instance that used templates. I'm assuming these call __cxa_demangle() at some point, but I can't be sure.

Voici donc ma réponse, "comment je m'y suis pris":

$sylvain> nm -S --demangle runme/arm9/runme.arm9.elf | sort -k 2 | grep '^[0-9a-f]\+ [0-9a-f]\+ [^B] ' --color=always | less -R

is how I find the "heavy hitters". I know that __cxa_* is run-time support. For _dtoa_r, I was suspicious because the disassembled code featured many calls to builtin_(insert arithmetic function name here)* things, while i'm not doing any FPU things internally. I tried replacing all the *printf with *iprintf and *scanf with *scanf and then I realised that at some point, both function actually called the same internal function that contains the full logic for floating points as well (maybe it's due to vsniprintf and that iprintf would be just fine).

I then got clued from the library content that "dtoa" is likely "double-to-ascii", and decided to replace it with a "runtime error report" function. So far, it didn't affected the functionality of the code.

The story for __cxa_demangle was more complicated. Initially, the function i was suspicious about was d_print_comp. Again, i tried disassembling and tracing back "who calls that", but it turned out that noone actually called it (that is, it is a virtual function of some sort, called only through a pointer and the content is statically defined). then i scanned lib*.a for a hit on d_print_comp (DS/dka-r21/arm-eabi/lib/libsupc++.a, if you ask), who revealed the symbol was present from cp-demangle.o, where d_print_comp is a "static" (internal) symbol, and __cxa_demangle is the only "external" symbol. I further googled for information on __cxa_demangle, and found http://idlebox.net/2008/0901-stacktrace-demangled/cxa_demangle.htt, where I found the error codes, full function prototype, etc. I gave "status=-2" a try with a dummy exception, and all of sudden, it reports 16iScriptException to be caught, while the code shrunk by ~100K. Bingo.

Similarly, i identified __cxa_terminate() which i succesfully replaced using std::set_terminate(my_terminator) who is responsible from handling uncaught exceptions. I don't feel like just abort()ing a DS program, so now when I got that, I fall back to a "press A to return to moonshell, B to download software upgrade" menu.

Yet, I'm not hot about killing __cxa_guard_acquire() and __cxa_guard_release(). They are required to ensure you got a lock (guard) on the static initialisation. They're defined in guard.o, and from what I see of nm output on libsupc++.a, they rely on throw, unwind, class-type-info, etc., but not __cxa_demangle directly. Even if they would, i did not _remove_ __cxa_demangle here, just replaced it with a "oh, sorry. I cannot demangle that for you. How about showing it raw to the user ?"


Tuesday, December 15, 2009

Slopes, Cont'd.

Initially, I thought handling of slopes would be well-documented and tutored on the Web. The reality is that it is still a poorly-covered subject, that often puzzle hobby game programmers. I remember of discussions where developers (i.e. Gregg) claimed "think twice before adding slopes to your game. If you can go without them, do it without them". And I wasn't quite satisfied with the implementation proposed by Tony Pa in "Tile Based Game Tutorial" series, either. Tony listed a number of slopes that are not allowed (i.e. anything that isn't a 45° straight line) and arrangement of slopes on the map that lead to bugs or glitches.

I intended to overcome these limitations in my own implementation, and I'm in the process of cross-checking to which extent I got it working. I'm still sticking with 45° items right now, despite adding other shapes should merely be a matter of pushing more data[] in the engine and extend iWorld::groundheight() so that it looks for those new shapes.


SMB3, un des premiers jeux de plate-forme à introduire des pentes, va doucement sur ses 20 ans. On pourrait s'attendre, du coup, à ce que la gestion de terrains pentus soit un sujet largement traité dans les tutoriels, mais c'est loin d'être le cas. Quand j'ai attaqué la question, même le tuto de Tony PA "Tile Based Game Tutorials" ne m'avait pas satisfait. Il ne prenait en compte que des pentes à 45°, mais en plus, certaines combinaisons conduisaient à des bugs ou des "glitches" désagréables. Pour construire mon petit algorithme, je me suis donc appuyé sur un tableau "groundheights[]" pouvant être étendu avec n'importe quelle forme de terrain : marches, dunes, pentes de degrés divers, etc. Notre seule limitation sera le nombre de bits disponibles pour indiquer de quelle pente il s'agit. Et pour trouver l'algorithme correct, j'ai appliqué les fonctions élémentaires que je m'étais données (ground_height, next_tile, etc.) sur chacun des cas qui peuvent être rencontrés et que j'ai maintenant reproduit dans un recoin du niveau 1 de la greenzone. C'est presque fonctionnel, à l'exception du cas "e" (la pointe) où Bilou se "coince" et ne peut être débloqué que par un saut (l'appleman, lui, a déjà trouvé la parade et "saute par-dessus" l'obstacle !)
The reason why I want slopes is level design : they allow a regular "walker" ennemy to find itself in a better tactical position (above you -- "in your functional blind box", would say Kirby Kid), as anyone who played Commander Keen knows. Until a "sloped level" is properly built, I altered the part of my level 1 that features appleman with all possible slope combinations. Walking right-to-left, you'll encounter (g), then (h) and (e) near the wall. So far, only (h) causes coding trouble: Bilou just stop at the top of that "spike" and refuses to move any further. The Applemans happily work around this situation by either turning back (if Bilou is not in sight) or jumping over the spike to attack Bilou. Cases (f) and (h) are automatically covered by the cando() tests that complement hotspots in my engine.

The algorithm I presented a few weeks ago is still mostly unmodified, but it had a major defect: it only moves you along a slope. The fact is that it should also inform the caller whether 1) we are on normal ground (and thus will fall if we can) 2) or on a slope (and thus don't fall) or 3) if we just couldn't move because there's something in our path. A bit more critical thinking is needed here to lay things down properly.

Encore un peu de patience, donc, et je vous livrerai l'algo revu et corrigé. La principale difficulté est qu'il avait été pensé uniquement pour suivre une pente existante, mais que j'ai aussi besoin de savoir si on est toujours bien sur la pente ou si les tests de chute habituels peuvent à nouveau être appliqués.

Sunday, December 13, 2009

Go Create !

Finalement, la DS ressemble terriblement à ces micro-ordinateurs 8bit de mon enfance, où un jeu était écrit en 1/2 journée, sans se prendre la tête. Les graphismes symboliques n'ont rien perdu de leur charme, ni les petits sons blip-blops de leur éclat.

Alors les gars, "go create!"

Laissez donc tomber le repompage de sprites, les RPG online trop ambitieux et compagnie. Faites simple, mais faites marcher votre imagination, comme l'ami Jayenkai.

By many ways, the Nintendo DS looks very much like these 8-bit microcomputers of my childhood. Back then, you could write a game on a single wednesday afternoon (and use a whole week-end to make it work properly :) You don't need to be a top-artist if you embrace symbolic graphisms. You just need good taste and creativity. So go ahead ! forget your FPS and MMORPG. Think small, but CREATE. You don't need sprites ripped from some commercial production. Be more than a fan: be yourself.

Thursday, December 10, 2009

Enfin, des pentes qui marchent !

C'est encore un peu mystérieux, mais ça commence à fonctionner! Petite démo téléchargeable (non-jouable, par contre) de mon "cas d'étude pour le débugging". Je vais tagguer le CVS pour qu'on puisse retrouver les sources correspondantes. Je dis "mystérieux", parce qu'au départ, l'appleman semble "hésiter" à prendre les pentes, chose qui n'a pourtant pas été programmée comme telle. Je soupçonne un effet de bord des vitesses inférieures à 1pixel/frame, sans avoir pu creuser complètement. Howdy! It starts working (though still subtly mysterious). Rather than trying to shoot you a full-blown Youtube Video, i used byzanz-record and some javascript to provide you a nice "video" teaser (hover the picture, you'll see ;). On the other hand, you're welcome to grab the running .nds and try it on your favourite emulator or linker.

Descente (aux enfers ?)

J'ai beau avoir pris le temps de tester conceptuellement et méticuleusement le petit algorithme de gestion des pentes que je présentais la semaine dernière, je me bats toujours avec mon code pour l'intégrer au game engine.

Le plus intéressant dans ce combat, c'est sans doute que jusqu'ici, aucun des problèmes ne provient directement de l'algorithme ...

Détails du Standard
J'en parlais tout dernièrement: prendre pour acquis et "standard" un comportement que l'on a rencontré sur une plate-forme est une erreur facile à faire pour qui utilise le C ou le C++ dont la définition est pleine de "comportement indéfini" ou (pire?) "définis par l'implémentation".

Interaction avec l'ancien code
En particulier, le système qui contrôle le déplacement pas-à-pas et les test-points contrôlés par les animations peuvent conduire à des décisions contradictoire du système de contrôleurs qui appelle doslopes(), d'où l'introduction d'une commande "move x *" pour ne placer une contrainte que sur le déplacement horizontal et laisser le déplacement vertical libre.

Fournir les bons arguments
L'algorithme reçoit vitesse et position en pixels. Dans mon moteur de jeu, pourtant, certains éléments travaillent en 256emes de pixels (pour mieux gérer les vitesses) et d'autres directement en tiles. Le système de types de C++ étant ce qu'il est, on a vite fait de prendre l'un pour l'autre et d'avoir une implémentation incorrecte parce qu'elle essaie instantanément d'envoyer le joueur à l'autre bout du niveau au lieu de le faire monter d'un pixel.

Le code de base n'était pas prêt !
Comment ça, la pente gauche et la pente droite ont les même propriétés ? Et le bloc semi-perméable ne laisse pas passer les monstres ?

Je ne serais pas surpris que ce soit le genre d'erreurs que rencontrent couramment ceux qui programment "en entreprise" et doivent déployer telle ou telle nouvelle solution dans une base de code pré-existante assez large. J'en suis réduit à "sortir l'artillerie lourde" :

  • d'abord vérifier que le nouveau code ne pose pas de soucis au éléments plus anciens (est-ce que les 2 niveaux fonctionnent correctement si je n'y mets aucune pentes ?)
  • construire un test-case minimal (un seul appleman, placé directement au bord d'une pente)
  • suivi méthodique de l'exécution (ddd avec du code -O0, placer des breakpoints dans toutes les fonctions succeptibles de modifier l'état de l'appleman, inspecter le "stack trace" pour comprendre pourquoi ces modifications d'état se sont produites).
  • Corriger "à la volée" les calculs erronés ("set value ..." dans ddd) pour poursuivre le déroulement du programme le plus loin possible sans passer par un nouveau cycle de compilation (j'avais plus fait ça depuis Crazy Brix, tiens !)
  • Procéder par élimination. "Une fois l'impossible éliminé, ce qui reste -- même improbable -- est forcément vrai, mon cher Watson".
Encore un peu de patience, donc. Quand les causes seront parfaitement identifiées, il restera à chercher une manière élégante d'intégrer les nouvelles contraintes au moteur de jeu ...

Wednesday, December 09, 2009

-Wconversion

Ca doit faire bien 10 ans maintenant que je pratique quotidiennement le C (ou un de ses "dérivés"), et il y a des jours où je crois honnètement avoir fait le tour du langage. Pourtant, les pièges restent bien présent, en particulier compte tenu du fait que je cesse de le pratiquer dans un seul environnement. Usermode, kernel mode, programmation embarquée sous ARM, etc. A force d'avoir utilisé uniquement gcc-linux-ia32 pendant un paquet d'années, il y a des particularités que j'ai fini par prendre pour des vérités. Un exemple: ce code m'a valu bien des misères avec la gestion des pentes :
#include <stdio.h>
char n=-1;

int function() {
return n;
}

int main() {
printf("char -1 is %i\n",function());
return 0;
}
Just realised while looking at the actual value of some ARM register that my code cannot work properly if i try to add 0xff to a 32-bit value to decrement it. I swear i've been using a char though. And that was precisely my mistake. I overlooked that, unless ints, chars signedness isn't defined by the C (or C++) standard, but rather by the platform's implementation. And x86 and ARM implementations differ, for that matter. So the code above might display "-1 is -1" or "-1 is 255" depending on the target you compile it for, and even worse, it will compile without complaints (even with -Wall) unless you explictly request -Wconversion on the command line. Read this "scatter-gather thoughts" to learn more about it.

Dans ma tête, "un char = un byte signé = -128..127" et bien sûr, comme je l'ai appris il y a longtemps à mes dépens. Et bien sûr les "int", dont on ne sait jamais trop bien la taille, sont signés aussi. Eh ben il faudra que je relise plus attentivement le Kernighan&Ritchie, parce que je me suis retrouvé avec 255 sortant de function() ! En ajoutant (à la recommandation de Cyril) -Wconversion à mon code, j'obtiens enfin un avertissement du compilateur :
test.c:2: warning: negative integer implicitly converted to unsigned type
En fait, à relire ce très bon article sur "scatter-gather thoughts", je prends enfin pleinement conscience de mon erreur: un char peut être signé ou non-signé par défaut, en fonction de ce qui est préférable (le plus optimal) sur une architecture donnée. Sur x86, c'était donc char = int8_t et sous ARM (au moins avec le devkitpro) char = uint8_t.

see-also : signs from hell by Conarac

Wednesday, December 02, 2009

Handling of slopes.

Okay. It might be a little pathetic ... I've spent some time figuring out an algorithm for properly handling slopes in my platformer, but i didn't found the time to properly inject it into code, so I just speed-typed it in a draft post. One of the key ideas is that you won't try to adjust frame t's horizontal speed to the slope you have to cross, but instead defer the effect of climbing dy pixels to the next frame's horizontal speed. This allows a one-pass while all my previous attempt had to iterate until they find the position you'd actually take given the slope you have to climb.

  • dx : horizontal speed for this step.
  • tile(x) : returns the tile holding a given coord.
  • end_of_tile(x, dir) : last pixel of tile containing x ends when you go towards dir.
  • next_tile(x,dir) : first pixel of the tile after tile containing x when you go towards dir. next_tile(x, dir) == end_of_tile(x, dir) + (dir>0)?1:-1
  • ground_height(x,y) : in the tile under (x,y), the height of the ground at specific location (x,y). 0 means "this is a sky tile", there is no ground to stand here. -1 is the lowest pixel of the tile, at -7, you should be on the highest pixel of the tile, and at -8, you should actually not be on this tile, but on the lowest pixel on the one just ahead.
Since we don't know how the slope "continues" to the next horizontal tile, we have to "pause" at the end of each tile in order to compute the Y coordinate we'd have then. When dx < TILE_SIZE
new_x = x + dx
while (tile(new_x) != tile(x)) {
gh = ground_height(end_of_tile(x,dx),y);
if (gh!=0) y = end_of_tile(y,1) +gh;
x = next_tile(x,dx);
}
Now, we know what height we have when we enter tile(new_x). We just have to compute where we'll end in that tile and we've finally done.
if (ground_height(new_x, y) == 0) {
new_y = end_of_tile( next_tile(y,1), 1) + ground_height(new_x, next_tile(y,1));
} else {
new_y= end_of_tile( y, 1) + ground_height(new_x, y);
}
Quick Note : the 'x' and 'y' are copies of the object's hotspot position, and the result of the algorithm is the new_x, new_y position at which the GOB's hotspot should be moved. I still have to figure out where cando(new_x, new_y) should be applied to ensure walking slopes do not allow one to move through walls, etc. I also have to figure out how to integrate this properly with the OO model I have so far : what is under the responsibility of iWorld, iGobController and GameObject.


C'est par manque de temps que ce petit algorithme de gestion des pentes se retrouve sur ce blog plutôt que dans le code. Une des idées simplificatrices de base (par rapport aux essais précédents) est qu'il n'est pas nécessaire de traiter immédiatement l'effet de la pente sur la vitesse horizontale (je ne sais pas vous, mais moi, je vais généralement moins vite en montée qu'en descente). A la place, je peux utiliser l'élévation du sprite à l'instant t-1pour ajuster sa vitesse horizontale à l'instant t... Le résultat, c'est que le calcul de la position actuelle peut être effectué en une seule passe, se basant exclusivement sur la fonction ground_height() qui retourne pour chaque position horizontale dans un tile pentu la hauteur de la pente depuis la base du tile.

ancien post-scriptum: je ne vous ai pas oublié, mes lecteurs francophones (près de 50% d'entre-vous). Vous aurez peut-être compris que ce billet fait office de "pseudo-code retapé en vitesse et commenté". Le temps de loisir est distillé au compte-goutte ces temps-ci, entre les dents de *deline et les grands rangements de la maisonnette qui en a bien besoin.

Tuesday, December 01, 2009

The Measure of Mario - Coins, contd.

In reply to Kirby Kid's "Measure of Mario : Coins"

Afaik, the "dragon coins" of SMW were used to compute "how much in % you completed the game", and it was the first nintendo game to provide such "comparable" scoring system. That is, claiming in playground that you completed SMB3 with 9,999,999 points doesn't tell much. Claiming that you completed SMW at 103% brought you respect from your classmates.

Star coins in NSMB are also a money for buying game save, so they is a tight balance for the travelling gamer between taking the risk to collect one (at the cost of one life) or having to shut down the game without being able to save your progress because you haven't got enough star coins to do so and are running short of lives/time to beat the next fortress. Afaik, this additional mechanism was first introduced with "kong coins" in DKC2.

Kirby Kid avait passé en revue les pièces d'or de Mario. Elles servent essentiellement à encourager le joueur à sauter à des emplacements autrement plats, à explorer, à récompenser les chercheurs de secrets et les preneurs de risque. Selon moi, c'est à partir de Super Mario World que Nintendo introduit un comptage du "pourcentage de finalisation" du jeu, qui était plus facile à comparer que le score final en nombre de points. Ce qui comptait, c'était ici de rassembler les pièces-yoshi.

On retrouve quelque-chose de très similaire avec les pièces-étoile de NSMB. En plus du côté "je t'en bouche un coin" du jeu complété à 103%, les pièces étoiles permettent d'acheter des sauvegardes à n'importe quel moment. Pour celui qui ne joue pas à 2 mètres d'une prise électrique, celà rend les pièces-étoile particulièrement importantes, succeptibles de décider si oui ou non le jeu peut continuer. On voit le challenge de récupérer la pièce-étoile d'autant plus significatif.

Red coins were first seen in Yoshi's Island, but not with that "timely challenge". They're somehow a mixture between dragon coins and P-switches that divert you from your main mission (beat the level) with an extra challenge that you cannot read in advance. I think they add indeed a layer to the game, but the result is that 1UP get too cheap in the game. I somehow regret that NSMB doesn't have something like a "hard core mode" where such "red coins challenge" would be the only way to get a 1UP.

SMB3 and the following have indeed much more ways for you to get "a 1UP for free", such as hitting a yoshi-containing ?-block when riding yoshi, finding a starman in an area where there is enough koopas, etc. Compared to the Great Giana Sisters where collecting 100 diamonds is virtually the only way for you to get a 1-UP, i barely find myself "hunting for coins" in the later Mario games, where they are devaluated. In a fresh game, i'd be tempted to reinforce the role of those "suspended lives". I agree with the fact that such bonuses should "read" as the level-designer's favourite path rather than "the one path to go". And insisting on the fact that the player should collect them all (like in Rayman 2, iirc) is a painful way for "extending" the lifetime of a platformer.

Les autres pièces spéciales de NSMB, ce sont les pièces rouges. Je les ai vues la première
fois dans Yoshi's Island, mais dans ce cas, il ne s'agit pas d'un défi d'atteindre un certain nombre d'objectifs en un temps limité. C'est une façon de détourner le joueur de sa mission principale (finir le niveau) avec un challenge alternatif, mais plus difficile à lire, puisqu'elles ont presqu'exactement le look d'une pièce classique, avec juste un reflet rouge.

Pourtant, leur effet est réduit dans chacun des jeux parce que le jeu offre de nombreux autres moyens de gagner bien plus de vies d'un coup. On en manque normalement jamais. C'est une grande différence des Mario depuis SMB3 avec, p.ex. Giana Sisters -- où récupérer 100 diamants est presque la seule façon d'avoir une vie supplémentaire. Les vies, dans les Mario suivants, sont assez dévaluées.

La question de la dévaluation des 1-UP pose la question "comment récompenser le joueur". Ils étaient une approche facile dans un environnement où le joueur manque de 1-UP pour terminer le jeu. Des sauvegardes et des continues, c'est une autre façon de procéder. Les power-ups, encore une autre, ou débloquer l'accès à des niveaux supplémentaires (le monde caché de DKC2, et les oiseaux-bananes de DKC3). Aujourd'hui, on a des trophées (à obtenir à partir de coquillages "secrets") assez artificiels, qui personnellement ne me convainquent guère. Je leur préfère la façon dont Professeur Layton permet de découvrir l'histoire sous-jacente au jeu, explorable en-dehors du jeu de base.

I also note that, between "regular coins" (suspension) and dragon/kong coins (no suspension, but exploration required), there is an intermediate approach that doesn't appear often in Nintendo platformers but that i've seen e.g. in Prehistorik 2, that is suspended-but-unique "coins". You'll find letters E-X-T-R-A or B-O-N-U-S in a level, and each level has the 5 letters. They are suspended from one level to another, and you'll get a "bonus rush" when you managed to get them all, though getting a second O when you already have B-O- -U-S doesn't make a difference,but instead tease you. Seeing the 'U' when the last letter is a 'O' also hints the player that he's been missing some nice part of the level (like seeing that the first star coin you encounter is the rightmost one in NSMB).

Ultimately, coins pose the question of "how do we reward the player". 1-UP is an example, continues/saves another one. Power ups obviously another one, as well as enabling access to bonus/extra levels (e.g. Krem Koins enabling access to the Lost World in DKC2) or alternate ending (banana birds in DKC3). They are typically devaluated in modern games, and replaced by more "artificial" rewards such as trophees (secret seashells in ZMC opposed to secret seashells in Zelda : Link's Awakening). Note that Professor Layton also provided a nice idea of using "collectibles" as a way to tell the story at a pace that fits the player rather than forcing it to the player, interrupting the gameplay with tale telling or tutorial levels.

En-dehors du monde de Mario, j'aime beaucoup le système des lettres B-O-N-U-S de Titus (prehistorik 2), qui ont un côté "suspension" comme les pièces de Mario (e.g. on peut étaler la récolte sur plusieurs niveaux du jeu) mais aussi un côté "lettres KONG" ou "pièces dragon"  (il faut les récolter toutes pour que quelque-chose se passe). La différence entre les lettres KONG et les pièces dragon, c'est qu'elles sont uniques. Voir le 'N' après le 'K', c'est le signe qu'on a loupé un secret entre les deux, alors que toutes les pièces-dragon de SMW sont identiques. Avoir un nouveau 'U' alors qu'il ne nous manque qu'un 'N' provoque un titillement du joueur dans prehistorik 2: ça fait deux niveaux d'affilée que tu as manqué le 'N'. Si tu ne fais pas demi-tour pour mieux chercher, toutes les autres lettres B-O U-S que tu trouveras dans la suite du jeu ne te serviront à rien jusqu'à ce que tu trouves un 'N'.

"How I mastered the butt-stomping attack to break thin platforms and access my treasure rooms, by Funky Funghi" could be teared apart in 5 pieces, scattered around the forest, so that Bilou has to collect them to get a hint on the presence of another attack technique, more secrets, etc. "How Pencils soldats suddenly appeared all over the place" similarly scattered fills the backstory in a nice way if player is "clued" on what happens, but not boringly "told". Give Small World a play, you'll know what I mean.