Friday, June 28, 2013

Incoming week-end ?

I cannot tell yet whether there will be significant development this week-end. I had a sort of marking-hangover last week-end and I barely managed to fix some bounding boxes and import some "school2.cmd" level to support the 20-year-old map I want to revive.

Ça ne donne pas l'air de bouger fort, hein, ces dernières semaines. En fait, j'avais commencé à ajouter les gommes sauteuses le week-end dernier pour me rendre compte qu'il y a toujours un sale défaut dans l'éditeur de niveau qui a tendance à bousiller la liste des monstres à chaque sauvegarde. Si je parviens malgré les valises que je traîne sous mes paupières à faire un peu de développement sur le projet Bilou ce week-end, ce sera donc très certainement dans 'LEDS' pour modifier la structure de données qui maintient le "gobscript" en mémoire ...

I'd have loved to include the pendat over the week, but I still have a dormant bug in LEDS which has been left pending since Easter's fixes. Now I need it fixed or I'll have to beam level scripts to the PC and back after every save to fix duplicate "gob% := state% (x,y)" statements.

Meanwhile, I had a nice idea for editing such blocks of text in a convenient fashion ... which led to an alternate design for processing the .cmd files that could solve the issue with monsters duplication. So if something happens in the svn tonight, it's likely to take place here.

Tuesday, June 25, 2013

7 ans!

Ça fait maintenant 7 ans que new Super Mario Bros a été annoncé, donc également 7 ans que j'ai attaqué réellement le développement sur DS. On est clairement à un autre rythme que pendant la période "PPP Team" de '93 à '97, mais je suis tenté de penser que le jeu vidéo s'est maintenant relativement affranchi des impressionnantes innovations techniques, même si -- comme pour le cinéma -- il restera des équipes pour qui la surenchère à coup de texelshading sera fondamentale. Une histoire originale et un gameplay réussi, c'est intemporel.

Donc je continue.

Et où en est-on ? À quelques lignes de code d'une démo d'un niveau dessiné en '93 qui prendrait enfin vie... avec des gommes qui rebondissent, des encriers qui crachent de l'encre, etc. À un outil qui devient confortable pour faire le type d'animation dont je rêvais avant de voir dans les démos de Rayman 1 tout le potentiel incroyable qu'il recelait. À une branche de la possibilité d'avoir des structures mobiles dans le niveau. Mais toujours du mauvais côté de la barrière polygonale de la 3D. Il faudra tôt ou tard que je la franchisse, si je veux que Bilou puisse construire des ponts en poussant des bouquins.

Il y a 6 ans, j'imaginais reprendre presque tels quels les niveaux et les monstres envisagés quand on étais ados. Maintenant, ils passent dans le grand chaudron du gameplay puis sont décantés au form-fits-function pour offrir quelque-chose de vraiment intéressant qui se démarque des "n-ième platformer où il faut récolter les items cachés".


Friday, June 21, 2013

Le stylet ultime ^_^

Je dois bien l'admettre, quand mon frère m'a prêté sa DS lite il y a déjà plus de 4 ans, j'étais resté à mon ancien stylet de DS "phat", bien plus fin dans la main, pour faire mon pixel art. Puis au moment où je passais moi-même à une DS lite (aux couleurs nettement mieux rendues) ma fée m'a trouvé un stylet ultime qui depuis ne me quitte plus ...

Le stylet à pointe transparente.

Je croyais l'avoir définitivement perdu, mais ce matin, je l'ai retrouvu ^_^

Howdy! I recovered the Ultimate Stylus of Pixel Art ... the one my Fairy gave me, with the transparent tip so that you accurately see where you're pointing. I'm really glad it wasn't lost.

Btw, I've got a student who just purchased a devkit for the original PSX. Anybody has links to share about homebrewing on that console ? http://code.google.com/p/psxsdk/, maybe ? (via)

Oh, and Nocash, the guys who wrote the 'DS&GBA tek' reference I'm using almost daily (when brewing) also built up PSX SPeX :)

Thursday, June 20, 2013

DpadController::timeout

Back in 2010, I had introduced a tweak to the DPAD reading so that you wouldn't bounce again and again by just holding the JUMP button down. That seemed efficient enough. Whenever a button (DPAD excluded) was pressed, a timer was started, running for 30 frames, and as long as this timer hadn't reached zero, the transitions of your state machine would read the button as "pressed". That allowed players to do special bounces when bopping on applemen wihtout requiring 1/60sec. precision. That's called "input buffering" anywhere else in gamedev documents.

Comparé à un clavier ou à un joystick de PC, la saisie des mouvement du joueur sur le DPAD de la DS est une vraie partie de plaisir. Pensez un peu: un emplacement mémoire qui contient directement l'état de chaque bouton de votre console !
Celà dit, s'en servir pour alimenter la logique d'un jeu de plate-formes comme Bilou demande un tout petit peu de subtilité quand-même. Selon les situations, la question n'est pas uniquement "est-ce que le bouton de saut est enfoncé" mais plutôt "depuis combien de temps le bouton de saut a-t'il été enfoncé ?"

Shortening the "time window" (::delay) to 10 frames actually reduced the maximum height for jumps! After rising for 10 frames, the game logic is indeed notified that the key was released, and switch to "end-of-jump". Annoying. I already had the feeling that the "action" button may need to be held and thus be "timemask-free", but the issue is actually deeper. It means whether you use the timer mechanism or not depends on the state you're currently in.

Eh, c'est que rebondir sur la tête d'une tortue pour aller plus haut, c'est bien rigolo, mais s'il faut enfoncer le bouton précisément dans le 60ème de seconde où la collision a lieu, ça devient franchement trop exigeant. En revanche, voir son personnage faire un deuxième saut spontanément en arrivant au sol, c'est perturbant. J'ai donc depuis un moment déjà un mécanisme qui décompte les "images de jeu" depuis que le bouton (croix directionelle exclue) a été enfoncé, de sorte que la logique du jeu regarde surtout si ce décompte est arrivé ou non à zéro. Mais pour que Bilou puisse s'accrocher aussi longtemps qu'on le veut aux éponges, il faut en plus que je puisse définir, état par état, quel(s) bouton(s) ont le droit à un "décompte infini".

While moving up in a jump, you want to check simply whether the button is held or not, and keep pushing up as long as the button is pressed. When going down, however, the timer mechanism is applied so that you only get your special bounce if you hadn't pressed the button too much in advance. Similarly, the "action" button uses the timer when you're falling down, but as soon as you switch to the "grabbing spongebop" state, it just checks whether you're still holding it down. Same would go for mario-like running, for instance.

Tuesday, June 18, 2013

Kung-f00: surviving in the kernel

There are two level of programming in an environment like the Linux Kernel, imho: survival and release. Survival programming is what you will do when you need to prototype or a proof-of-concept. Release programming (or refactoring) takes place when you want your code to be accepted in the kernel as a patch or another driver. Here are some ancient, half-forgotten programming techniques that could help you survive.
Initialization

If you have been raised in OOP, you may need to un-learn a few things to survive in the kernel. Especially your habits on constructors. Every time you can define an initial value at compile time rather than at run-time, you save yourself from the need to check your initialisation has indeed happened, and you avoid the risk that some mistake you’ve made could prevent the system from booting altogether. You don’t necessarily need to pollute the top-level name space for that:

{
   static int reported=0;
   do_swap_page(p);
   if (!reported && my_page(p)) {
       printk("my page got swapped.\n");
       reported=1;
   }
}

The static reported variable will have block-limited scope (so you could have as many such block as you need, and always use the name ‘reported’), but program-wide lifetime (and thus its value will persist across function calls). I believe this saves us from many __init functions.

just useless ...

Do you need a reference to some kernel object X? Save yourself the problem of figuring out whether that object will be ready when your __init function would be called. Use a NULL value as initialisation and ensure that you properly use lazy initialization
   if (!backing_device) backing_device=find_swap_device(0);
Downside is that you’ll have to repeat this line in every access point of your code. That’s unacceptable at the scope of a corporate business logic, but perfectly maintainable at the size of a prototype.

Do you need N bytes of memory to be available and can’t afford using kmalloc because your lazy initialization could take place at a moment where it could require memory that isn’t available right now (yup, even the kernel could be low on memory) ? Well, just be plain 8-bit style and go
   byte mySpace[N];
that will be set in the bss segment of your kernel, wiped with zeroes by the loader, and don’t take extra space on the vmlinux kernel image.

Probe System Call

Ideally, when you want a feature to be optional in the Linux Kernel, you wrap it in a module that can be loaded and unloaded. The price to pay for that is a more sophisticated template to follow and significant attention to memory management to ensure whatever your module doesn’t let dangling pointers when it’s gone. Core kernel functionalities typically get enabled/disabled through system control variable in /proc, just like support for the “magic SysRq key”. That’s the good way to go for a release. During prototyping, we can be old-school and define a new system call that we’ll use for invoking late initialisation in contexts where the initialisation, and otherwise enable communication from our user-world.

The /proc filesystem requires a significant amount of support data structure to work properly, yet if using /proc/kernel/myprobe is fine and if a mere int is enough for your communication needs, it comes down to
  • locate the appropriate place to add a entry in kern_table[], with .procname=”myprobe”;
  • mimmic sysrq_sysctl_handler, and add your own static int to receive the value decoded by proc_dointvec;
  • use a clone of the sysrq_sysctl_handler() function: let proc_dointvec deal with the buffer that comes from user-space, and just launch your code logic if the user is writing to your control variable.
Dynamic Allocation within a Table

You’re used to linked list, hash tables, trees, stacks and other dynamic data structures but realise that allocating cells wouldn’t be a wise idea in the kernel ? Congrats. Kernel developer put significant effort in crafting the slab and kernel object caches to reduce allocation costs, but at a prototype scale, it isn’t clear it’s worth learning how to master it. The “16-bit style” solution to this, given that you have a static table of N elements and that “N shall be enough for everybody and we guarantee there will be at least N slots available” is to embed a linked list of free slots within your table.

You don’t even need pointers for that: simply one field in your table slot that can hold an integer index to the next free slot.
int free_slot=-2;
int allocate_slot() {
   int al;
   if (free_slot==-1) return -1; // all slots used
   if (free_slot==-2) { // lazy init: chain all entries
      for (int i=0;i<N-1;i++) table[i].next=i+1;
      table[N-1].next=-1;
      free_slot=0;
   }
   al=free_slot; free_slot=table[al].next;
   return al;
}
Now you can focus on the real problem of guaranteeing that this table won’t suffer concurrent allocation issues :P

Monday, June 17, 2013

Spongebop Rodeo

I now have a (partly-)working prototype where Bilou can grab a Sponge bop and stay hooked while swinging, but I have to hard-code the offset between Bilou and the Sponge as arguments of the copycoords controller. Not so elegant. Plus, if I also allow to grab on Bladors (granted, that's a silly idea, but it helped for debugging :), those offsets are no longer correct and we're rather grabbing some point in the air over Blador's top-right corner >_<

Bien. Bilou peut maintenant s'accrocher aux éponges en balance. Il y a encore des soucis non résolus avec l'animation prévue à cet effet (d'où l'absence de démo jusqu'ici) et un désagrément mineur: il m'a fallu ajuster à la main et préciser dans les paramètres du comportement "accroché à X" la position relative de Bilou et de l'éponge. Au moment d'ajouter le comportement "assis dans un encrier", ça me démange un peu.

I initially had plans for making those "grabs" act on an are, and it would make sense to actually state "copycoords(a.bottom=b.top ; a.center=b.center)", but I've just said that accessing GobAreas within a controller isn't practical.
Expressions handling a collision have access to some extra-context variables (wc-wf) which were just involved in that "repel" behaviour that made inkjet "solid".


  • xcontext[0] - wc -- collision flags
  • xcontext[1] - wd -- unused (0)
  • xcontext[2] - we -- X-axis center-to-center distance
  • xcontext[3] - wf -- Y-axis center-to-center distance
  • xcontext[4] -- not in GobScript - X-axis area overlap
  • xcontext[5] -- not in GobScript - Y-axis area overlap
 The idea would be to use some GobScript to compute/force the desired location, possibly record it in some GOB variables and have the CopyCoords controller simply enforce that offset from the GOB variables.

Il y aurait bien des solutions techniques pour automatiser ces coordonnées relatives en "alignement vertical, centrage horizontal", à la façon des éditeurs de diagramme ... il y aurait même un "chemin de moindre résistance" pour construire ça dans le contexte actuel. Mais soyons honnête: ce n'est *pas* un élément nécessaire pour le programme. J'ai un seul objet auquel Bilou puisse s'accrocher de la sorte (l'éponge) et je dois de toutes façon utiliser une autre animation pour Bilou-dans-l'encrier (donc, autre état et autres paramètres). Ce serait donc de la généralisation intempestive et prématurée! Caramba! Ça le ferait pas!

I could thus extend the "interaction opcodes":
  • A[p]: attach [with path] evaluating object to context object (works in hit and found)

  • D: detach evaluating object (works in any transition)
  • R[xy]: repels context object away from the evaluating object (works in hit and found) [only along x/y axis]
  • L[xy]: line up the evaluating object with context object (mirrored repel)
  • C[xy]: center the evaluating object with context object (wish)
  • Now, let's be honest. That's a "wish", not a "todo". I only have one grabbable ennemy so far, and only Bilou grabs it. "hard-coded" offset are just fine in that context, and alternative are "premature generalization". I thought about all this because I'll also need a "copycoords" when Bilou sits in an inkjet, but that will be another animation and another state, so other copycoords offset is just fine.

    Saturday, June 15, 2013

    NSMB Editor !

    The fact that I've just beaten the game without using a Mega Mushroom against final Bowser on *deline's request shouldn't let me forget that new Super Mario Bros on Nintendo DS is now 5... omg! 7 years old... Much enough to allow level editors to have been created. And I'm really glad that NSMBe works fine on my linux, since it's a mono-friendly Csharp program ^_^. Much easier than running Lunar on Wine.

    Cette fois j'ai battu Bowser sans utiliser de Mega Mushroom (interdiction édictée par *deline). Et je réalise tout à coup que "new" Super Mario Bros a ... 7 ans. Bien assez pour que les éditeurs de niveaux aient fleuri, et j'ai même réussi a faire un peu tourner NSMBe un tout petit peu sous Linux. J'avoue que je n'ai pas l'intention de créer mes propres niveaux à NSMB. C'est un exercice qui ne m'a jamais vraiment attiré. Par contre, je suis curieux de voir ce que je peux apprendre sur la structure du jeu.

    As you guess, I'm not that interested into crafting alternate levels for nsmb, although thanks to my ds linkers, I could even play the modified ROM on the Real Thing. But I'm deeply curious to discover how the thing was built. As I had expected, many of the special platforms you can walk on are "sprites" (either made of OAM or using 3D hardware ? I still couldn't tell). Obviously, not all level may use all the sprites, but what is interesting is that they have been organised in swappable "banks". You can't have e.g. giant Bowser and Nessie starred in the same level because they both belong to bank 5. But you're free to chose wheter you complement them with Hammer Bros. or Chomp Chomp, with mushrooms or tornados or moving platforms, etc. Spawn points, door/pipe entrance are also managed as "sprites".

    Comme on pouvait s'y attendre, un grand nombre de plate-formes sont en réalité des "sprites" (soit avec des OAM, soit avec la couche 3D. Difficile à dire), même celles qui ne sont pas forcément mobiles, mais dont le comportement est suffisamment différent du sol. Les portes, les début de niveaux, les entrées/sorties par des tuyaux, sont eux aussi gérés avec des sprites.

    Les sprites sont organisés en "bancs" que l'on peut utiliser ou non dans le niveau, certains bancs en excluant d'autres (pas de bowser et de nessie ensemble: ils sont tous les deux pour le banc n°5, par exemple). Par contre, on pourra les compléter avec des Hammer Bros ou des chomp chomps, comme on préfère.

    Coins and blocks belong to the "ground" layer. Even item-providing blocks. I trust that to be made of good old tiles, as there can be only one layer of 3D objects and that sprites already use it... yet it remains to be proved. One reason that make me hesitate is that you edit it in terms of "objects" and not merely by laying out tiles on a grid. Each object can be individually selected, moved along, dropped, resized, etc. It is very efficient, compared to what LEDS currently offer, so I'm not surprised. It leads to funny things such as many different patterns of 'coins' so that you could have vertical, horizontal or dithered coins arrangement with a single 'object'.

    Les pièces d'or et les bloc-question font partie de la couche "sol". Idem pour les blocs-à-power-up. Ils sont sans aucun doute fait à partir de bons vieux "tiles". Chose assez inhabituelle, en revanche: on les édite et tant "qu'objets", pas juste en plaçant des pavés sur une grande grille. Chaque objet peut être sélectionné, déplacé, redimensionné individuellement, ce qui s'avère très efficace pour la conception rapide de niveau comparé à ce que LEDS propose. Du coup, on retrouve des éléments assez curieux comme des "pointillés de pièces d'or" qui permettent d'avoir un "damier de pièces d'or en diagonale" avec un seul objet.

    Tout ça n'a pas l'air d'être juste un choix de fonctionnement de l'éditeur: les niveaux eux-même contiennent ce genre d'objets: si on clique sur un "bloc" du dessus d'une plate-forme, on sélectionne directement l'entièreté du sommet de la plate-forme. Je présume que c'est une technique pour réduire l'espace sur la cartouche: le contenu en mémoire du niveau sera reconstitué en appliquant des commandes du type "remplir (10,10)-(20,20) avec du sol" puis "remplir (10,9)-(20,9) avec de l'herbe", etc. J'y crois d'autant plus fort quand je vois des motifs "effaceurs" qui n'auraient aucun sens dans un système utilisant des textures et des polygones.

    And that doesn't seem to be a mere side-effect of the editor: the level has those objects, truly. If you click on the top edge of a platform that's already part of the design, you select the whole top. I can only imagine that this helps storing levels using less memory. Given that there's a lot of empty space and repetitions in NSMB maps, storing the level as "paint commands" (fill x1,y1 - x2,y2 with object #12) where object12=[start at tile 42, width=3, height=4] is much more compact than storing the whole map, and allows easier edition cycles. That would also explain the presence of "erasors" patterns that wouldn't make any sense if the level was 3D-rendered (afaik). When the level is loaded, the painting list is streamed from the ROM card (unlike previous Nintendo console, the DS doesn't have direct access to game ROM through its bus, iirc), a large chunk of RAM is allocated, and the commands are applied to know what to map on-screen. This in-RAM map would then be dynamically updated as coins are taken, blocks are used and bricks are destroyed... it all make sense.

    Alternatively, you could just 'unpack' part of the level by applying a clip mask on the "paint commands", and have a level-sized bitmap for "interactive tiles".  Both coins, bricks and blocks are binary (original/used) so you'd mix that information with a paint command and still be able to retrieve the start of the level as the player left it although it wasn't permanently available as a tile map in RAM.