Monday, April 27, 2009

Coding Funky Funghi

J'avais envie de mettre côte à côte "comment je me représente le comportement de Funky Funghi" et "comment je réalise ce comportement avec mes outils actuels.

state12 :anim15 {
using stopper
}

state13 :anim6 {
using gravity(24,1536);
testpoint off (16,32)
}

state12->state13 on done [t] (v4 0 = 200 * 500 + ~ :1)
state13->state12 on fail [t] (v4 1 + 4 % :4 0 :1)
The gamescript for funky funghi is a good example of what i called "more sophisticated monster behaviour" in my earlier post about how to write the game logic. Eventually, such behaviours will be edited *on the DS* with a mostly-graphical tool, but the logic is already there: Funghi has two states (jumping and on the ground) to which i can attach different animations and "core behaviours" such as 'affected by gravity' or 'just stay in place'. It also has internal variables (loaded with v4 and stored back with :4, for instance) that both manipulates its internal speed (:1 sets vertical speed, for instance) and can be used as monster-specific state.
I reused the UML state machine model where each state transition can have a predicate that indicates whether the transition can occur (between square brackets) and an action that alters the object before it enters its new state.

Voilà donc à la fois un "mockup" futuriste de l'éditeur de monstres sur DS et le genre de script que je bricole en attendant pour quand-même avoir des monstres qui se balladent dans mes niveaux. Histoire que vous suiviez :
  • Funghi a deux états: au sol (12) ou en saut (13). Ca me permet notamment d'avoir une animation spécifique à l'état "au sol" et synchroniser la "déformation" du sprite avec la collision au sol (chose dont le RSD Game-Maker était incapable).
  • Comme tous les objets que j'ai jusqu'à présent, Funky Funghi respecte la convention que v0 est la vitesse horizontale et v1 la vitesse verticale. A priori, ces valeurs sont gérées par le contrôleur et utilisées par la classe SimpleGob pour mettre les coordonnées à jour. Elles sont aussi accessibles dans la machine d'état (:1 signifie "écrire la valeur dans v1)
  • Chaque "gob" possède 12 compteurs sous son entier contrôle. Funghi utilise le premier d'entre-eux (v4) pour compter ses rebonds. "v4 1 + 4 % :4" est une expression post-fixée (pensez 'calculatrice HP' qui doit se lire "prend v4, ajoute 1, calcule le reste de la division par 4 et stocke le résultat dans v4 à nouveau". Ce calcul est effectué chaque fois que Funghi entre en contact avec le sol.
The above script is a bit complicated because i decided to rule out flow control when i 'designed' the language (something i learnt in my thesis), yet i need here that Funky Funghi jumps higher every 4 bops. Plus, it's a bit ugly because i used post-fixed arithmetic (2 + 3 is thus written 2 3 +, as on a HP calculator). Hopefully, the existence of predicates allow me to compensate the lack of conditional expressions in actions by multiple transitions, each with a predicate and a separate action. See the code below, it's actually clearer (sort of) with similar efficiency.

A la fin de l'animation "déformation", la nouvelle vitesse verticale est définie. "500 ~ :1" donnerait à chaque fois la même implusion (~ est utilisé ici pour prendre l'opposé d'un nombre, donc v1 reçoit -500 ... eh non, je n'ai pas implémenté les constantes négatives. Sue me).
Pas d'expressions conditionnelles genre "si(test, réussi, échec)" comme on en trouverait dans Excel ici (ou test?réussi:échec pour les programmeurs); c'est une restriction délibérée, et sans doute la plus contraignante de mon évaluateur d'expressions jusqu'ici. Or, je dois décider de fixer la vitesse après rebond à 700 ou 500 selon qu'on est au 4eme saut ou pas. Ma première approche était de tenir compte du fait que "v4 0 =" teste la valeur du compteur de rebonds et laisse 0 ou 1 sur la pile selon le résultat, et de jouer sur la multiplication absorbante : "t*200+500" vaut 700 quand t=1 et 500 quand t=0. D'où "v4 0 = 200 * 500 + ~ :1".
En fait, il y a plus simple puisqu'à défaut d'expressions conditionnelles, j'ai séparé prédicats et actions. Rien ne m'empèche donc de réécrire la machine d'état sous la forme suivante:
state12->state13 on done [v4 0 =] (700 ~ :1)
state12->state13 on done [t] (500 ~ :1)
state13->state12 on fail [t] (v4 1 + 4 % :4 0 :1)
Alors oui, bien sûr, c'est horrible à relire. L'idée n'est évidemment pas d'obliger l'utilisateur final de GEDS à écrire des expressions de ce genre-là, mais plutôt de se donner un "langage intermédiaire" (un bytecode, quoi) comme résultat d'un éditeur de monstres plus graphique.

J'aurai donc quand-même mis un an entre "l'idée sur papier" et le protoype qui tient la route pour la définition des contrôleurs (bon, j'ai fait d'autres trucs entre-temps, évidemment). Et l'intégration des "test de possibilité de déplacements" dans le contrôleur (reléguant les testpoints au rang de sophistication optionnelle) n'est pas la moindre des décisions.

Sunday, April 26, 2009

Petit à petit ...

Le gros du "refactoring" est fait. J'ai bazardé le plus gros de ma gestion de collision basée sur les "testpoints" en faveur d'un test de propriétés sur la zone occupée par le sprite. Et progressivement, je mets à jour les règles définissant le comportement des différents sprites pour en tenir compte.

Une petite modif' d'une classe GobController par-ci, une commande 'xs = 0' dans les transitions entre états par-là ... Ca prend forme. Et j'en ai profité pour rajouter un funky funghi, histoire de voir ce qu'il donne animé.

Most of the refactoring effort on world collisions management is done. I'm now (slowly) updating the state machine rules to take new controllers into account (these use 3-state logic : EVENT, FAIL, NOPE rather than just boolean logic) and fixing bugs due to inconsistencies here and there. Initially,

Comment ?? Vous ne savez pas qui est Funky Funghi ??

Ah oui, c'est juste, je ne l'ai pas encore bloggé ici. C'est la version revue et corrigée de Boppin' Toadstool, qui apparaît dans la bande dessinée et que j'ai pixelisé il y a quelques mois en Suisse. Au départ, je voulais me le garder pour vous faire la surprise, une sorte de petite animation exécutable sur DS où on aurait eu les sprites rejouant le scénario de la BD.

This also allowed me to introduce Funky Funghi, the remasterised poisonous, bopping toadstool into the woods. Funghi confirms that 32x32 sprites work well with the engine and that Gravity controller can apply not only to the hero (though it required a bit of tweaking ^^"). He's also the very first monster to use internal counters: much like the Mad Mushrooms of the shadowlands, Funky Funghi will do three small bops before he goes for a higher jump.

J'ai encore quelques petits bugs à corriger, mais le plus gros est déjà réglé. Début de semaine, figurez-vous que dans certaines circonstances indéfinies, Bilou s'envolait à vive allure, hors de tout contrôle jusqu'au moment où, sortant de la zone de jeu, il provoquait un magnifique "Guru Meditation" ... Comme vous voyez, on revient de loin ^_^

Mais là, il y a un rayon de soleil et je vais aller me faire une p'tite balade jusqu'à la bulle de collecte des verres.

edit: cliquez sur la photo pour regarder la vidéo youtube.

Tuesday, April 21, 2009

impure_data

Some bugs are definitely easier to track than others. And bugs that appear in standard libraries are definitely the trickiest you're likely to encounter, because you know little of what's inside the library, after all ^^"

This specific bug happened in _fread_r, the re-entrant version of fread, meaning that neither PC (current program location, within _fread_r) nor LR (register holding the last return, within fread) were of any help. If 10 years of C programming tought me one thing, it is that bugs are not in the standard libraries, but in how you invoke the library. Fread, strcpy and friends trust you to give them pointers where pointers are due and will not try to do anything fancy to ensure you're actually entitled to read/write to those memory locations. Once you got that wired in /dev/brain, you can start debugging.

Chers lecteurs francophones, ceci est un post technique sur la mise au point de programmes sur la console DS, comment interpréter le contenu de la pile, le désassemblage du code et tutti quanti. Si vous pensez avoir déjà les connaissances techniques (rudiments d'assembleur, registres, pile, adresses) mais que l'anglais vous bloque, laissez un commentaire et je traduirai...

As usual, my starting point is the guru meditation screen of the DS, but this time, registers are of little help: what really matters is the stack dump just below.

00000011 00000001
00000e0c 00000000
00000000 00000000
00000000 0b003bd6
00000001 00000011
00000000 020299c7
00000000 ffffffff
0b003c20 00000000
00000000 020362cf
0b003bd6 0200c721

2029894 : crash within _fread_r
_fread_r internal variables
_fread_r saving registers
20299c6 : call by fread
fread internal variables
fread saving registers
20362ce : call by FileRead::read
FileRead::read saving registers
200c720 call by XMtransport::load

Unfortunately such "stack unwinding" is more complicated on ARM cpus than on the x86 architecture, because parameters to function calls are typically kept in registers (r0..r7 at least) rather than pushed on the stack, and because there is nothing like the "base frame pointer". Knowing how many words on stack each function takes can only be deduced by disassembling the corresponding function with "arm-eabi-objdump -drl <file.arm9.elf>", e.g.

fread():
# address code disassembled
20299ac: b570 push {r4, r5, r6, lr}
20299ae: 1c16 adds r6, r2, #0
20299b0: 4a07 ldr r2, [pc, #28] (20299d0 <.text+0x296d0>)
20299b2: 1c0d adds r5, r1, #0
20299b4: b082 sub sp, #8
20299b6: 1c04 adds r4, r0, #0
20299b8: 1c21 adds r1, r4, #0
20299ba: 6810 ldr r0, [r2, #0]
20299bc: 9300 str r3, [sp, #0]
20299be: 1c2a adds r2, r5, #0
20299c0: 1c33 adds r3, r6, #0
20299c2: f7ff ff4f bl 2029864 <_fread_r>
20299c6: b002 add sp, #8
20299c8: bc70 pop {r4, r5, r6}
20299ca: bc02 pop {r1} ; retrieve LR
20299cc: 4708 bx r1 ; return
20299ce: 0000 lsls r0, r0, #0
20299d0: ebe8 0204 undefined
teaches us that fread saves 4 registers on the stack before it starts executing and that it needs 8 bytes of local storage for its own use. The order of the arguments in push commands is a bit confusing but it works as such: lr will be pushed first, then r6, then r5 and r4 will show at the top of the stack when the processor will be ready to execute next instruction at 20299ae. You shouldn't be confused by the fact that sub sp, #8 is "reserve 8 bytes for local variables": the stack grows downwards in virtually every CPU architecture.
Now, i have to admit that this is quite a tedious way to go, so you're more likely to just check the output of arm-eabi-objdump -h arm9/runme.arm9.elf :
Idx Name          Size      VMA       LMA       File off  Algn
0 .init 000002dc 02000000 02000000 00008000 2**4
CONTENTS, ALLOC, LOAD, READONLY, CODE
1 .text 00040520 02000300 02000300 00008300 2**6
CONTENTS, ALLOC, LOAD, READONLY, CODE
2 .fini 0000000c 02040820 02040820 00048820 2**2
CONTENTS, ALLOC, LOAD, READONLY, CODE
that tells us simple and basic that everything between address 02000300 and 02040820 is our code. If you spot any value within that range on your stack dump, it's very likely to be a value of lr that has been pushed and you can just call arm-eabi-addr2line to figure out where it is in your program. Note, however, that addr2line doesn't manage to extract function names out of the standard library component, which will just be refered to as "crtstuff:0" ... however, as soon as your code is reached, you usually know by reading line 95 of FileRead::read that the library function you're calling is actually fread...

But what actually puzzled me in this debug session was a suspicious data address that objdump resolved into "impure_data". It did not appeared on the program map (build/.map) and was co-located with _impure_ptr in lib_a-impure.o ... As usual, there are tons of bug reports that can be found by google about impure_data, but little clue. So i downloaded the sources of newlib (the libc used in the devkitpro project) and quite immediately located newlib/libc/reent/impure.c and

struct _reent __ATTRIBUTE_IMPURE_DATA__ _impure_data = _REENT_INIT(_impure_data);
struct _reent *__ATTRIBUTE_IMPURE_PTR__ _impure_ptr = &_impure_data;

Of course, i should have guessed that right from the start: "impure" is just a nickname for the "reentrant datastructure" of the C library in its newlib incarnation, following the precept that "A pure function is one with no side effects; an impure function is any other". Data that must be kept apart of functions so that e.g. you can call strtok within an interrupt handler even if strtok has been itself interrupted.

Btw, Peter Schraut wrote a nice blog entry about DS "guru mediation" and how to handle them.

Thursday, April 16, 2009

<algorithm>

Bon. J'aurais été en C, j'aurais ajouté "GOB* prev=0; GOB* next=0;" dans ma structure et on en aurait plus parlé. Mais je suis en C++. Mes sprites (Graphic OBjects) doivent être listés par "caste" (héro ou vilain) pour les tests de collisions et je cherchais désespérément une manière simple d'effacer un élément donné d'un vecteur C++ dans le desctructeur. Je dis bien "simple", donc

for (std::vector<x*>::iterator b=v.begin(), e=v.end(); b!=e ;b++)
if(*b==this) v.erase(b);
n'est pas vraiment un candidat (quoi que si c'est correct, c'est presque compact).

Eh bien je cherchais mal. De même que std::vector ne possède pas de méthode "sort()", ce qui correspondrait aux "fonctions built-in" sur les listes dans le langage PERL sont des algorithmes dans <algorithm> en C++. Il faudra donc écrire:
v.erase(std::remove(v.begin(), v.end(), this), v.end());
Sauf que "std::remove does not change the size of the container,it moves elements forward to fill gaps created and returns the new 'end' position.". Ah. Bin j'aimais autant écrire mon "@list = grep { $_ != $remove_me } @list", à ce prix-là, hein ...

Vous me voyez un peu commencer à décaler la moitié de mes sprites dans leur tableau chaque fois que l'un d'eux sort de l'écran, vous ? Passer à un hachage ? Il y a bien "std::set" qui ferait l'affaire, et c'est le genre de réaction que j'ai en PERL quand ma liste n'a finalement pas si besoin que ça d'être ordonnée et que j'en retire régulièrement des éléments.

Ceci dit, j'ai franchement l'impression que je vais faire l'impasse sur tout ce bazar, et utiliser la solution 'naturelle' sachant que je travaille en réalité avec des tableaux de taille ajustable et pas des listes liées:
v[this.pos]=v[v.size()-1];
v.erase(v.end()-1);
v[this.pos].setpos(this.pos);
Remarque, il y aurait une alternative plus "C++ienne": std::list, pour laquelle l'insertion et l'effacement sont en temps constant à condition d'avoir déjà un iterateur positionné correctement. En principe insert(v.end(), this) nous renvoie un tel itérateur, la question étant 'ai-je le droit de le conserver malgré le fait que ma liste va subir des modifications ?' ...
Selon les gurus, oui. Pour une liste, en tout cas, seuls les itérateurs pointant sur l'objet supprimé sont invalidés". Ce n'est pas le cas pour les vecteurs ni pour la plupart des séquences.

On aura donc au final :
class GameObject {
public:
 typedef std::list<GameObject*> GobList;
private:
 static GobList gobs[2];
 GobList::iterator self;
public:
 enum CAST { HERO, EVIL, DONTLIST };
 enum CAST cast;
};

GameObject::GameObject(CAST c) : self(0), cast(c) {
 if (c!=DONTLIST)
   self=gobs[c].insert(gobs[c].end(), this);
}

GameObject::~GameObject() {
 if (cast != DONTLIST) gobs[cast].erase(self);
}
Et je garde une liste de pointeurs: je n'ai pas envie de venir tout embrouiller avec les constructeurs de copies et autres ^^"
PS: Merci à Cyril pour la relecture et les conseils sur les templates.

Tuesday, April 14, 2009

(Might be) Back again ?

La prison, "retaillée" pour DS
Ok, mettons que je n'essaie pas de faire un programme qui convertisse un jeu Game Maker en un jeu DS tout fait, mais plutôt que je reprenne les graphismes du premier Badman pour m'amuser un peu à faire une "version anniversaire" sur DS, reprennant les meilleurs niveaux, etc.

Première observation, les graphismes étaient tellement simplistes qu'ils passent pratiquement tous à travers une conversion "20x20 -> 16x16" brutale dans The Gimp. Certains (la porte en bois et les caisses) auront besoin d'un petit relifting dans SEDS, d'autres (la grille de la prison) auront tout intérêt à être réimportés de leur version 20x20 et "recollés" sur le fond.

A few more "mapshots" of RSD game-maker games i did in '95. Here comes Badman, an overweight parody of Batman, in a super-zero style. The tools i've crafted so far don't allow straight conversion of any RSD game, but let's try and see how much work a specific port (for instance an "15th anniversary release" of Badman) would require. Let's go. Just convert 2000x2000 map into 1600x1600 map and see what happens...

Le manoir, "retaillé" également à l'exception de Badman
Deuxio, Badman ne fait pas 16x16 mais seules ses "oreilles" et sa cape dépassent du cadre. Au niveau du gameplay, ça ne devrait donc pas choquer de le voir se faufiler dans des passages un peu trop étroits. Les ennemis seront conservés en 20x20 aussi (et prout pour la surconsommation de VRAM), sinon ça ne donne pas


la version d'origine, sur PC
Tertio, si la version PC était limitée, il y a moyen de s'amuser en variant un peu le comportement des ennemis, cette fois-ci. Faire hanter les cuirasses de chevalier par les fantômes du manoir, par exemple. Ou passer l'horrible grille de la prison en parallaxe, etc.

well, a few background tiles will need to be reworked, of course, though in some cases, like the jail's grid, i can just re-import the pixels because they actually don't have per-block granularity.
Clearly, i should better keep
sprites 20x20 anyway (if i dedicate per-OAM vram rather than per-frame vram, it should work for every game, and it really doesn't care for Badman 1 which features some 40 frames for all animated objects throughout the game ;)


Retenus pour l'occasion : gettham city, le manoir, la prison et le japon.

et point de vue code:
  • extraire les animations des blocs vers du script "GEDS"
  • itou pour la position des monstres sur la map (avec un offset et ajustement des coordonnées quand-même)
  • un mbl2spr qui ajuste les sprites 20x20 sur du 32x32 (ou éventuellement 24x24 si vous êtes sages.

Saturday, April 11, 2009

Bubule Warrior

Si vous cherchez à savoir "Qui est Bilou", je crois que ceci est le plus ancien document que je peux vous ressortir. Presqu'une pièce de musée. Gribouillée par mon frère entre deux heures de cours alors qu'il venait tout juste de rencontrer Pierrick, Piet nous fait un petit topo des mouvements de "Bubule Warrior" qui cherchera à retrouver sa belle Raph aux talons pointus et aux cils courbés.

Funny enough, many people on the internet mix up my own identity with Bilou's identity. In the case you wonder "who is Bilou, anyway", this is the eldest piece of museum i can show you. Sketched by my brother while he just met Pierrick, this is where you get a few hints on moves that "Bubble Warrior" would have and why he'll be running through a strange world to save his sweetheart from unknown hazard. It shared most of the previous level design in my earlier Calimero platforming game.

Et avec ça, un bestiaire rudimentaire où l'on voit déjà Bubble Bat (que je suis occupé à transformer en "Berry Bat" après avoir abandonné l'"Apple Bat") et une "space chenille" qui préfigure "Big Caterpillar", le premier boss de Bilou.

La "little bomb" est un clin d'oeil au "Bob Omb" de Mario 3 (et fait probablement un peu référence aux mines marchantes de Commander Keen ?). La "tortue turbo" nous annonce la "turtle zone" et le "tictac bull fire" faisait office de premier boss.

Comme dirait l'autre, ça ne nous rajeunit pas.

Another interesting part was a small list of foes that would be found on the road. Most of them didn't made it through the filter of years, such as one-eyed snail, the turbo turtle and those fire-thrower-bubbles. You'll note the "batbull" that has now turned into the berrybat, but still keep the idea of "a ball with wings". Note, too, the "space caterpillar" that has been the root for World1 boss: BigCaterpillar.
"Little Bomb" is likely a lost tribute to SMB3's Bob Omb, and never showed in any level.

By those ancient times (around 1994), i was just the "pixeler" guy in charge to turn my brother's impressive (;) graphics into pixels into the digital world, usually through millimeter-sheets and DATA lines of BASIC listings.

Piet prévoyait visiblement une sortie sous Amstrad, puisqu'il bombardait Pierrick à la programmation et me reléguait au rôle subalterne de "pixeliseur" (comprenez, retraceur de ses dessins géniaux sur des feuilles millimétrées pour ensuite les encoder sous forme de "DATA 0,0,0,1,3,3,1,0,0 ..." dans un programme BASIC :P

Refactoring again...

Well, once again, i'm refactoring the code of my game engine, as suggested in this earlier post ... Though i already have to revise some of those proposals: iGobPubstate is not clean object-oriented approach and causes more trouble than solution for the rest of the stuffs.

Meanwhile, i've updated a former post about collision detection on slopes with a review of how a selection of popular side-scrolling platformers handle such slopes, both technically and gameplay-wise.

And while reading the Frequently Questionned Anwsers about C++, it reminds me that most of these late refactoring things doesn't come from the fact that i hesitate on how things should be done, but rather how work should be delegated to the different classes. I'm certainly not a C++ guru, and the whole SEDS/GEDS project is also an attempt to get more familiar with C++ (though i deliberately avoid templates and friends so far).


Well, at least i'm not throwing all my energy into Yet Another LinkedList/HashTable/AvlTree library :P

Friday, April 10, 2009

To the moon and back

My homebrew development kit has recently been upgraded to include a shiny DS lite, in order to enjoy the true colors of my pixels. That's for the neat side. I asked my brother to get me a R4 chip to fuel it, since i had quite a good experience with his (see former entries), and so he ordered me a R4-IIISDHC. Quite confusing device, i must say. On the web, everything looks like it is indeed an upgrade of the former R4, but documentation provided doesn't match the device's operation. It doesn't seem to be really using MoonShell at all (which my bross installed on the device, following former instruction), and it is said to be "a fake, scam, don't buy" on forums (as of june 2008)...

En plus de ma DS "phat" et sa Supercard, me voici équipé d'une DS Lite et sa R4-IIIsdhc, légèrement différente de la R4 "originale" de mon frère, et un rien intriguante (nombreux forums la dénonce comme "clone pirate" de la R4), mais en gros, je n'ai pas à m'en plaindre. Seul hic, ils ont fait l'impasse sur le "CD d'installation", pourtant annoncé sur le site du constructeur (www.r4iii.com, www.r4iiisdhc.com ou www.r4dsl.net selon celle que vous avez obtenue). Il faudra donc aller télécharger le "kernel" adapté et installer les fichiers à la racine de votre carte mémoire micro-SD, faute de quoi, vous aurez juste droit à un écran bigarré avec un petit logo "loading" animé en bas à droite de l'écran ...

I first thought that the authors of MoonShell didn't like that their product was embedded and defaced into a device's firmware, but i'm really starting to wonder whether that R4iii has anything to do with the earlier device :-/

Anyway, my software runs flawlessly on that beast. So far, i only regret it has no notion of directories and obviously recursively scan the media for some .nds, showing 4 of them per screen ... i feel like i'm gonna miss my supercard's menu, somehow.

At least, tweaking directories names and things alike could allow me to have files in a more "usable" order. On the other side, it made me realise that Moonshell is capable of something that i still have to implement in runme : soft reset to the firmware. And they do this quite simply via "reset.mse" (MoonShell Executable) pluging provided by the card vendor.

To quote "reset.txt" that comes along with the "R4iii upgrade" :

MoonShell execute plug-in

This plug-in is exclusive return to firmware function.

Reference URL that becomes base of reset function.
Most of reset.mse is a copy of LoveLite.

Lick + ds homebrew
http://licklick.wordpress.com/tag/lovelite/

For other homebrew developers.
'reset.mse' can be used from your self-made application.

Please make the C/D bank of VRAM accessible from ARM9.
example.
VRAM_C_CR = VRAM_ENABLE | _VRAM_CD_MAIN_BG_0x6000000;
VRAM_D_CR = VRAM_ENABLE | _VRAM_CD_MAIN_BG_0x6020000;

Please write the following values in 1024 bytes from 0x0603FC00. (16bit access is necessary.)
0x0603FC00 0x01
0x0603FC01 0x01
0x0603FC02 0x01
0x0603FC03 0x01
0x0603FC04 '?'
0x0603FC05 '?'
0x0603FC06 '?'
0x0603FC07 '?'
0x0603FC08 0x00
0x0603FC09 0x00
0x0603FC0A 0x00
0x0603FC0B 0x00
0x0603FC0C 0x00,0x00,0x00...
Please input the adaptor name (four characters) from 0x0603FC04 to 0x0603FC07.
Please fill zero from 0x0603FC0C to 0x0603FFFF.

Please start 'reset.mse'. (Please begin with usual NDS multi boot in a similar way.)


As a matter of fact, i did not even tried to implement this directly. I happen to have a slightly different memory setup in exec.cpp:LoadNDS_VRAM, which is the root of my devstation (and comes from GrizzlyAdams' work in DSChannels, remember?). All i want is a way from runme to the firmware without going through the power button. Moonshell does this ? fine. So just

if (keys & KEY_SELECT) {
  if ((keysHeld()&KEY_L)) {
printf("** trying to switch to moonshell **\n");
LoadNDS_VRAM("_DS_MSHL.NDS","/",0);
  }
  printf("checking for updates...");

does the trick ... at the expense of going through moonshell, i admit (which is a shortcut if you had just uploaded some MP3 to your system, i must add). This is somehow an ugly hack, true. I'm not proud of it in any way, but it's there and maybe at some point it will save the day. Who knows.

It could be equally sufficient if moonshell was able to run my tools, but for some reason, all i get is some "not support adapter type" error message when i try to. Only a few programs (without DLDI ?) seem to work with the version of moonshell that ended up on my system. Wait and see...

Monday, April 06, 2009

Quid de Qwak ?

Il y a quelques semaines, je craquais pour "Qwak", un petit jeu d'arcade/ plate-formes adorable et bien foutu. Sans être vraiment "bon" à ce genre de jeu, je n'ai jamais pu y résiter... Bubble Bobble et BombJack ont toujours eu ma préférence sur les Sega Rally, Street Fighter et autres R-Type sur les bornes d'arcade de ma jeunesse. Alors prout! Plutôt que de relancer une énième fois Arcane pour qu'elle me refile ses superbes graphismes pour en faire un jeu DS, j'ai décidé d'apporter mon soutien à un vieu de la vieille (pensez, la première version de Qwak tournait sur un micro 8 bits) en achetant cette étrangeté : une cartouche homebrew.

I got Qwak, a small, lovely arcade/platform game particularly well-crafted. It reminds me of the Lode-Runner / Bubble Bobble / Bombjack era, but with the level of polish of the Amiga era. I've always prefered them to Sega Rally, Street Fighter or R-Type. The original thing is that Qwak, a GBA game, is also a homebrew.

The goal of the game is to collect all the keys shown on single-screen levels. Some shields power-up allows for a single hit. Eggs to collect so that you can later throw them on monsters and fruits to collect to get more eggs at the end of the level.


Eh bien, après quelques heures de jeu, je suis définitivement séduit. Le principe est simple : ramassez toutes les clés du tableau pour ouvrir la sortie et poursuivre vers un tableau plus dur. Notre mignon canard peut ramasser des "fioles-bouclier" (dans les coins supérieurs de l'image) pour se protéger contre les monstres qui rôdent, lancer des oeufs sur les monstres en question pour s'en débarasser et ramasser des fruits qui seront transformés en oeufs supplémentaires à la fin du niveau. Le tout avec des graphismes et une bande son probablement repris tels quels de la version Amiga, Qwak GBA à tout ce qu'il faut.

The number one gameplay idea that I love in that game is the fact that bonus fruits are also walls for the monsters. So monsters start the level confined in small areas, but you set them free as you progress in collecting keys. Eating fruit is both good and bad.


My number one complain on the game is that hitpoints are hard to spot on the game. Rather than hearts or shields or armors, they are small grey bottles, with a small shield icon stamped on it. Given that, when you enter a new level, they may be the only chance you have to avoid instant death on contact with an enemy, I really wish they had more distinct shape so that I could spot them at glance, and maybe that they'd blink if I'm just a naked duck. The color of your armor, too, is quite hard to see on the GBA screen, and this carry the indication of your hitpoints.


J'avais été attiré par le gameplay un peu unique : les bonus font aussi office de murs pour les ennemis, qui sont donc au départ confinés dans une petite zone mais que vous libérez au fur et à mesure que vous progressez dans le niveau. Eh bien, je n'étais pas au bout de mes surprises. Ce jeu est un régal de petites recettes qui prolongent sa durée de vie d'une manière quasi impensable en cette époque du jeu jetable.

  • Si les mondes de Qwak sont toujours traversés dans le même ordre, en revanche, vous visitez les niveaux de chaque monde dans un ordre varié (si pas aléatoire).
  • Notre p'tit canard, une fois rôti, recommence le niveau, mais pas toujours du même endroit.
  • En plus des "bêtes bonus", 10 fleurs vous donnent une vie et un arc-en-ciel de diamants = gros points. Il y a aussi des fioles "power-up", malheureusement au look un peu trop difficile à prévoir pour que j'aie vraiment pu m'y retrouver.
  • Une fois le temps imparti écoulé, vous pouvez continuer le niveau, mais il se met à pleuvoir des boulets hérissés de pointes qui vous donneront du fil à retordre. Heureusement, le changement de musique avertit le joueur quelques secondes avant l'averse fatidique.
Mais ce qui fait de chaque partie de Qwak un moment unique, se sont ces évènement aléatoires qui sont annoncés lors de l'écran de bonus. La "pluie de bonus", est probablement ma préférée, quoi qu'il faille tout de même se méfier de certains "malus" qui s'y cache. Dans un autre genre "Attrape la clé volante" rappellera une séquence de Harry Potter et peut allonger de manière inquiétante le temps nécessaire pour finir le niveau. Quand à "Baddies get Revenge" et "Welcome the Happy Ghost", ils ne dureront heureusement pour moi que le temps d'une vie, parce que dans ces conditions, mon stock d'oeufs baisse dangereusement vite.

There is other very interesting design element in Qwak, that makes the game fresh more often than "lode runner" or "manic miner". Although the levels are pre-defined, there are several layers of randomness added:
  • the order in which levels are presented within a world;
  • the respawn location after the player got fried by a monster;
  • special rules may be added to the level with low probability, like a moving key, respawning baddies, an additional "ghost" monster or the bonus rain. Note that if you die during the level, the special rule terminates, be it good or bad.
Much like many other single-screen collect-them-all platformer, there is a time limit. But unlike e.g. Manic Miner, you don't arbitrarily die when the timer runs out. Instead, it starts raining spikeballs. If you have the required skills, you can still barely escape the level, and maybe even quickly collect a last key.


Voilà. Un jeu bien sympa, donc, que les plus chanceux pourront emporter avec eux sur GBA (Dépéchez-vous, il n'y a plus que quelques cartouches disponibles : c'était une édition limitée) et que les autres préfèreront peut-être en version multi-joueur sur PC avec des graphismes "HD-ready" (ce que personnellement je regrette, mais bon). Merci Jamie. Je t'envoie un Bilou par retour de hibou dès qu'il est prêt et je retourne bricoler des étagères dans ma cave.

PS: tout ceci a été bloggué dans les escaliers: j'ai des petits soucis avec ces adaptateurs "Ethernet sur courant porteur" qui amenaient ma connexion Internet jusque dans mon salon :P