Showing posts with label coding. Show all posts
Showing posts with label coding. Show all posts

Friday, August 08, 2025

ROX'64

This is a piece of history. A screenshot from ROX64, a small lunar lander for C64 (without rotation) where you'd have to shoot at meteors once you've landed before the moon you've landed on starts getting moonquake due to excessive amount of shocks from meteors.

Ce petit screenshot n'a l'air de rien mais il a pour moi la saveur du Chaînon Manquant... cette part d'histoire qu'il faut que je vous raconte: c'est le jeu qui m'a fait passer d'un projet de quizz géographique en texte pur à un projet mêmant PETSCII, sprites et même quelques effets son au SID. Oh, ne vous emballez pas: le jeu que vous voyez là est un titre tout à fait officiel dont je n'ai pas touché un seul byte, mais il était écrit en BASIC, et j'ai donc pu l'imprimer, l'analyser et -- au final -- le bidouiller pour remplacer le module lunaire par un Caliméro.

One interesting aspect of the game was that it was 100% BASIC, meaning you could get and print the listing, study it ... and in my case, modify it so you'd play as Calimero throwing apples to avoid getting squashed. It wasn't much, and I bet nobody would have played it more than I did while proof-testing the thing, but it was the first time I was going beyond a quizz game and actually made something with all chips of the C64. If you think about it, replace the starry sky by some massive mountain, make the asteroid look red and you've got a boss-sort encounter that would have actually fit the theme of the (yet to come Calimero) game's first zone.

Oh and the (original) game was by Jeff Minter, by the way .. the one person who wrote "Revenge of the Mutant Camels"
 

Friday, May 23, 2025

the NIOS quest

I had learnt of Microblaze by my FPGA colleagues, of course. It felt a bit weird at first that you would use fully programmable silicium to remake a CPU because I was working on a design where there was a CPU onboard already. And yes, if I want my gates in the FPGA do what they have to (e.g. convert the proper video signal based on width and height), they need to be filled with proper values, and in many cases, a compact CPU fetching instructions from memory is more practical for the job than turning that into state machines that turn into FIFOs and LUTs. I still had to learn that not all of them are microblaze. There are actually so many soft cores!

In intel/altera technosystem, it seems that NIOS is the reference softcore. And readers, it is sure tricky to install. I finally got nios2eds/bin/gnu/*/bin/nios2-elf-addr2line and friends installed on my linux VM after I manually started QuartusSetup-23.1std.0.991-linux.run and manually checked the md5sum because the Intel Quartus Prime Installer was stuck in the "checking file integrity step" forever. Now that it is done, I should be able to close the installer, but it insists that it has a background task to complete. I don't see anything but the qinst processes ... I guess I'll have to kill them manually as well.

So much about not sending humans do machines' job.


Thu Apr 18 16:10:08 2024 INFO: Do download ...
Thu Apr 18 16:10:08 2024 INFO: Path: /tmp/intelFPGA_standard_23.1std
Thu Apr 18 16:10:08 2024 INFO: Total disk space: 21,83 GB
Thu Apr 18 16:10:08 2024 INFO: Free disk space: 4,44 GB
Thu Apr 18 16:10:08 2024 INFO: File: QuartusSetup-23.1std.0.991-linux.run resume position: 0

Thu Apr 18 16:10:08 2024 INFO: GET QuartusSetup-23.1std.0.991-linux.run
Thu Apr 18 16:11:30 2024 INFO: Downloaded /tmp/intelFPGA_standard_23.1std/QuartusSetup-23.1std.0.991-linux.run, 2671252465 bytes (status: 200)
Thu Apr 18 16:11:31 2024 INFO: File: max10-23.1std.0.991.qdz resume position: 0

Thu Apr 18 16:11:31 2024 INFO: GET max10-23.1std.0.991.qdz
Thu Apr 18 16:11:41 2024 INFO: Downloaded /tmp/intelFPGA_standard_23.1std/max10-23.1std.0.991.qdz, 300376601 bytes (status: 200)
Thu Apr 18 16:11:42 2024 INFO: Checking file integrity ...
Thu Apr 18 16:57:02 2024 INFO: Download stopped
Thu Apr 18 16:57:02 2024 INFO: Total download time: 46 mins 54 secs
A few tips about the machine code
  • uses the RISC-typical approach of OP dst, op1 [,op2] except for store instructions that follow STx data, target(offset)
  • movhi reg, 0xcafe ; addi reg, 0xbabe is the natural way to put 0xcafebabe in some register
  • arguments in function calls are put in r4, r5, r6, r7, ...
  • function calls can fit 1 32-bit word.
  • it is not unusual to see a bunch of ldbu reg, ...; stb reg, ... in place of a memcpy call (for small amount of data)

Thursday, April 24, 2025

"Why am I doing this?"

It's not unusual to ask yourself the question among indiedev. What struck me is that the question was raised this time by someone who's been interviewed by Pix'n'Love and makes games with his son: Dale Coop.

There are moments when I wonder, “Why am I doing all this?” It takes so much time, few people care, and it brings nothing in return. The majority of people don’t even know it exists. Should I just move on to something that makes more sense for my life and family? That’s where I am today 😔 -- Dale Coop

I know that feeling. I relate it to those time where you feel the urge to eat, but nothing you eat seems to quiet it. Because actually, I need to eat something different (sometimes more fruity, sometimes more crunchy, ...) Sometimes gamedev is satisfying, sometimes we need something else.

What tend to make me wonder "why am I ..." is the overall dsgametools approach. 20 years past the introduction of the NintendoDS, and despite my first game on the system is now about 15 years old, none of my relatives have really picked up the tools and start making their own PPP games with them. It is heartwarming to read veteran artist say "your tools are perfect and tailored to your needs", yet

  • basic *EDS tools apparently feel repulsive to friends and nephew and will be lacking to professionals
  • conversion tools are so tricky to use, you'd better consider them inexistant
  • I've got nice IRL chat sessions with my son about the game design overall, but pencil drawing is none of his hobbies so I'm left with all the job anyway
  • mixing script and C++ code in the engine doesn't feel that useful when I'm the only developer anyway. It makes many debugging sessions more complicated that they should because you can't really single-step the GobExpressions

I've got something running, but it is far from perfect and it gives me the feeling that I've made my own life overcomplicated. Nobody would use scripted state machines in a platformer. Or if you really want editable behaviour, you'd go for something like Fury of the Furries

Nobody ?

Well, I've encountered some github project to decompile the original Rayman title on PSX. I was looking for the code of that tricky-to-beat bandlands bug, and I got intrigued by some of the GO_SPEED, GO_LEFT, etc. you could find in the "top" function DO_MIT_COMMAND. No reference to that anywhere else in the codebase. I was also puzzled by the "skipToLabel();" calls. Digging through references, I end up with just one big "load" function filling all the commands together with other loads for the level... So they're not just some data part of the game engine.

I couldn't find anything like "Rayman Level Format" as I hoped, but I found Ray1Map ... the website that pretends to be a Rayman 1 level editor, and that's nearly as good as far as I'm concerned.

And there, we can view the commands for each individual enemy in the game. Some of them will be delivered to per-enemy handler (like LEFT % and RIGHT %), many will affect variables that those handlers can check and manipulate (especially enemy state), but there's also a whole set of flow control instructions for loops, branches, and even subroutine calls. And as you can see, the programs are already quite sophisticated. But that was used, and not only by that game (and its numerous ports), not only for other Rayman games (like the designer kit or the Junior series), but for dozens of unrelated platformers until the GBA era... or so does the Readme.md file suggests.

So well ... let's say that for 32-bit consoles, that approach makes sense, and recreating a game engine that deals with such abstract code at runtime isn't a bad idea. I just need to give myself better tools to address the debugging aspects... like presenting the whole thing as the debugger for a virtual machine that does not exists ?

Tuesday, April 15, 2025

gravity.flow ?

Je voulais faire tout autre chose, mais au moment de prendre le code en main, je me rends compte que j'ai une série de modifications qui n'ont pas encore eu droit à leur commit ... et que je ne sais plus trop à quoi elles correspondent.

Il s'agissait en réalité de deux modifications liées à l'eau: animer la surface (nager si bien avec un miroir en guise de surface, c'est un peu dommage ...) et faire en sorte qu'on puisse placer un jet d'eau dans une grotte. Parce que vous ne ça n'aura pas manqué de vous faire tiquer: il y a une sorte de rectangle moche au pied du geyser sur l'image ci-contre/dessus. C'est lié au fait que le geyser (tout comme la cascade) est obtenu en par une combinaison des deux couches de décor: l'une avec une animation de type "palette cycling" et l'autre avec une animation conventionnelle. Et donc pour mettre le geyser dans une grotte, il me faut deux nouveaux blocs "décor de geyser fusionné avec le décor de grotte".

After a fairly busy week and significantly busy Saturday, I wanted to try updating the Appleman behaviour, but I found my homebrew directory with different files uncommitted and changes involving binary files like maps. So I started by running the current code first on the emulator, and visit those maps to see whether there were any changes. And oh, of course. First change was animating the water line (see the mastodon video below). Second was an extra water jet in the second green map.

Quand je dis "je ne sais plus à quoi elles correspondent" (y compris une modification du code qui assure le palette cycling), comprenez "il a fallu que je fasse tourner le .nds pour m'en souvenir". Ce qui m'a donné envie d'en faire des petites captures (un jour après le ScreenshotSaturday ... pas de chance ^^"), et sur une de ces captures, on remarquait quand-même fort que le haut du geyser reste en permanence au même emplacement. Moi, j'aimerais qu'il monte et qu'il descende, pour ajouter un peu de dynamisme à la scène, et appeler à l'interaction.

See on that level editor, waterfalls and geysers are made of two layers of tiles combined. But that means you can't put a geyser over a dirt wall unless you've got tiles showing that in first place. Plus I'm using another palette slot for the dirt wall than the default one, meaning the cycling code had to be updated. That was ready for some screen-shotting

J'aurais peut-être pu utiliser le contrôleur "grid" de furblock, mais tôt ou tard, il faudra aussi que Bilou soit propulsé vers le haut par le geyser. Plan B: utiliser le même mécanisme de "flow" dans le contrôleur gravity que celui codé pour swim. Je profite donc que tout le setup wifi est là pour modifier le niveau et je définis un nouveau type de tile (les ^ mauves). ça donne des résultats rigolos avec Bilou, mais en réalité, ça ne marche pas: Bilou est bien ralenti quand il arrive dans le haut du geyser, il peut même remonter légèrement, mais au final, la gravité lui donne une vitesse suffisamment élevée (on peut aller jusqu'à 6px/frame) pour qu'il redescende, puisque le geyser le fait monter à une vitesse constante de 2px/frame. Bref, c'est bien pour des tapis roulants mais pas pour des geysers.

But then, the top of the geyser was very static ... I want it to wave up and down a bit. There are different ways I could do that in my engine, but only a few where not only the geyser top but also Bilou is lifted up when in contact with the stream. That implies a mechanism similar to the water flow designed last year, except this new special physics tile will define a property for the gravity controller instead. It will still be an "AIR" tile, though: we can fall through geyser, not swim them. (I hope to avoid nasty corner cases that way).

Unfortunately it didn't work the way I wanted. Lifting Bilou by 2 pixels (cyan lines on the diff) is fine as long as the vertical speed is lower than 2 pixels/frame downwards. It gives a nice boost when jumping and catches you when you're "stomping" the geyser, but while you're being stopped, your intended velocity keeps increasing, and at some point, you'll be fast enough to reach the ground again. Exactly as you start sprinting backwards on a conveyor belt.

So I ended up using the yellow code instead: directly change the polarity of the gravity based on the sign of the terminal velocity stored as a per-tile-type property. Not perfect, but close enough for now.

Mais en fait, il n'y a pas tant à changer que ça: plutôt que de déplacer Bilou, on va utiliser l'information stockée pour contraindre la vitesse maximale ... et tant qu'à faire, inverser la poussée si la vitesse maximale est négative. J'ai pris des vitesse identiques pour la gravité et le jet d'eau pour le "petit nuage" en haut du geyser, ce qui lui donne un mouvement symétrique alors que pour Bilou, la gravité lui permet d'acquérir une vitesse jusqu'à 3 fois plus grande, et il a donc tendance à s'enfoncer plus dans le jet d'eau. ça suffira pour le premier jet ;-)

edit: valeur maximale -2px/frame validée: il suffit de garder le bouton de saut enfoncé pour décoller bien au-dessus du geyser. Et si on tombe du sommet, on ne redescend pas trop bas.
 

Post by @PypeBros@mastodon.social
View on Mastodon

Sunday, July 21, 2024

map poked!

map.poke is done. Okay, it doesn't read "map.poke" in the level script but rather block (coordinates) = (hex) so that it mimmics the block (hex) { ... } definition. And that makes it specific to special blocks.

But there it is. After some branches untangling and more commits weaving, I could use it to add a secret exit in the trunk of the 'greent' room without having to go through the now-clumsy steps of patching the level on NDS while editing code on the PC.

Bon, je crois qu'il n'y a pas besoin que je vous dise que quelque-chose n'a pas marché, hein ? vous vous en doutez rien qu'en regardant l'image... En mars, j'avais évoqué la possibilité de reprendre les vieilles maps pour étoffer un peu "Dreamlands" (ou à tout le moins, proposer un peu plus de contenu pour la démo "3 rooms"). Et bonne nouvelle, j'avais fini par retrouver les maps en question. Je m'étais donc rajouter un peu de script qui dise "alors, le bloc de type 11, c'est une sortie secrète, et dans le niveau greent (le nouveau), si on la touche, tu charges le niveau greeny (l'ancien)". Sauf que pas moyen de tester, évidemment, parce que sur greent.map, il n'y a aucun bloc de type 11...

C'était donc l'occasion de se bouger un peu et de coder le mécanisme dénommé "map.poke", à savoir permettre au script d'aller modifier les propriétés d'un bloc du niveau sans rien demander à personne (et en particulier pas à l'éditeur de niveaux ;P). Et oui, la vie est telle qu'il m'aura fallu un bon mois pour que ces malheureuses lignes de code finissent compilées sur le PC qui avait aussi le fichier greeny.map ce matin. Tout ça pour se rendre compte qu'avant de pouvoir faire une "release" avec la vieille map n°1, je vais devoir lui faire un sérieux ravalement de façade pour l'adapter au nouveau tileset ^^"

Oh I presume you don't really recognize the big tree of my so old two-levels demo: since I re-structured the tileset, there are quite some editing to happen there before you can actually enjoy anything :P

Tuesday, July 16, 2024

How GEDS scrolling works

The scrolling routines of InfiniMap weren't as well documented as I hoped. Not even when I blogged that "yeah, it finally works". And while trying to document them, I noted some undesired difference between what the code does and what I claimed it does in the comments. So here it goes. First this is the full level map, stored as a 2D array of tiles. Within them, we define an area of world coordinates (xmin,ymin)-(xmax,ymax) that is guaranteed to be present in the video memory as well. That is the "invariant", the condition that was true on the first line of a scroll_*() call and that must be restored before we leave the function.

if (py < ymin + WH / 2)
do { up_scroll(); } while (py < ymin + WH / 2);
else if (py + WH / 2 > ymax)
do { down_scroll(); } while (py + WH / 2 > ymax);
if (px < xmin + WW / 2) left_scroll();
else if (px + WW / 2 > xmax) right_scroll();

WWxWH is the dimension of the validty area. We will never let xmax - xmin < WW happen. To decide whether scrolling routines should update the contents of video memory, the scrolling code compares the position of the camera (i.e. the center of the viewport) with the coordinates of the area. If it gets too close to one edge of the 'validity area', video memory contents will be updated and that edge will be moved, possibly dragging the opposite edge along (since we have fixed-dimension video memory)

void right_scroll() {
unsigned xoff = (xmax - xmin) / TL;
// compute target position in video memory
u16 *src = dataview + xoff;
xmax += SQ; // new validity window will end 64 pixels on the right.
if (xmax - xmin > 512) {
xmin += SQ;
xview = (xview + SQ / TL) & 63;
dataview += SQ/TL;
}
// copy from src to video memory
// so we fill the expanded area with valid tiles. 

Then, this is the video memory. The hardware reads it as a 2-dimensional wrapping array, that is if it cannot read more at the right, it goes back reading on the leftmost column. The (xview,yview) reference point corresponds to "where in the video memory is (xmin,ymin)" (that was the part that needed a patch).

Note that only a part of that valid area will show up on screen (highlighted here).

Note that whatever happens, xmin always shifts by 64 pixels. That's the horizontal copy granularity SQ (because it was meant to be square), and no, I never checked it led to better performance than 32. We must have ScreenWidth + 2*SQ < VramWidth, though.
 

Saturday, March 02, 2024

Let's have more musics!

If you have tried the last demo, you most certainly noted that there are only 2 soundtracks for 3 rooms. And moreover, that there is a different set of sound effects depending on the room you're in. This is a drawback of the current sound engine, where everything is in one data file: main in-game tune, side tunes for menu and such, single-pattern for level clear, mini-patterns for 1-UPs and notable pick-ups, samples to play them all and samples to be used as standalone sound effects.

Bon, un des gros morceaux de programmation qu'il me reste à faire sur le moteur de jeu, c'est de faire en sorte qu'on puisse plus librement passer d'une musique à l'autre. Pas charger une autre musique en changeant de monde, ça c'est déjà fait. Non. Je veux dire décider que "string tracking" n'est pas la musique idéale pour la green zone et que "4U2" donnera mieux. Ou "little flower on the grass". Pour l'instant, ça pose un soucis parce que les fichiers musique contiennent aussi la palette sonore pour les différents personnages. Je voudrais donc pouvoir faire avec les samples et autres patterns ce que j'ai fait avec les images : charger un deuxième jeu de sons par-dessus la musique destinée au monde. ça fait un moment que ça me trotte en tête.

Et cette semaine, j'ai pu passer un premier cap: importer les samples. ça a l'air de marcher, mais ne nous leurrons pas: vu le type de programmation auquel on a affaire, il faudra rajouter quelques tests automatiques sous scruteur de mémoire pour garantir qu'on n'a pas ajouté une bombe à retardement dans le jeu. Et il y aura une repasse pour mettre ça au net avec des classes une fois que j'aurai validé le concept, parce que pour l'instant, ça part dans tous les sens, aucune des classes du moteur de jeu n'ayant été prévues pour ça.

So among the objectives I sketched in my 2024 notebook, there was some ideas to overlay .xm files the way I overlay .spr files... Or rather the other way'round. Here the generic "bilou.xm" containing mostly sound effects would see its samples and patterns imported over the world-specific song.

#ndsdev status last night: I could allow 2 instances of NTXM::Song to exist together.
#ndsdev status tonight: I can import instruments from one NTXM::Song so that different environment can share some sound effects.
little step for #gamedev, giant leap for game #MadeWithMyEngine.

I admit it might sound weird to add the common part over the world-custom part. After all video does the opposite. The reason here is that odds of adding common sounds over the course of the development feels higher than odds of adding new samples to an existing song. It shouldn't be too hard to tell my brother that patterns 50-60 should be kept free for sound effects, same for samples 50+, no matter what song we're using than to remap all samples of the per-world-songs to other slots because we've got one extra ooch-that-is-electric sample added in the shared bilou.xm

But yeah, I still make sure that bilou.xm is only loaded once, when the game initializes, and we don't pay extra wait time for overlaying it from the SD card. Instead, samples and patterns from the shared song got "borrowed" from the shared song and marked as such so that they aren't freed when we swap songs. Did I mention I read a bit about RUST programming language earlier this week ;) ?

edit: now also imports mini-pattern ^_^

edit: tried new songs ... some things worked, but some things look broken

edit: ow. turns out when you encounter pattern break on a mini-pattern, only the channel with the break command is discarded ^^". Seems like it was only a matter of luck that SchoolRush did work fine, and possibly explain why playing / leaving it idle long enough started producing super-fast trash sounds. Now, you'd have hard time dumping the ROM with that ... not as "easy" as with a runaway audio channel on a GBA.

edit: fixed

Saturday, January 06, 2024

Raycasting ?

There's an awesome effect one can achieve once you have HDMA in a platformer: add depth to your background with "floors" like in DKC2 Lava lagoon. There will be complete occlusion of the 'far' background plane (wall) by the 'near' background plane (floor side), so having them scroll at different speed is 'just' a matter of updating X scrolling register at the right time. Having the floor positioned above or below the "seam line" between two walls is 'just' a matter of adjusting Y scrolling register earlier or later so that they 'skip' part of the far background art to show front background art.

The awesome part is that depth effect added with the horizontal planes, of course. That is about adjusting both X and Y scrolling registers and that will need maths to explain properly, using some pre-slanted texture as shown in the ripped contents of DKC2.

First immediate thought after I realised that it could be more than welcome in the pyramid level of Bilou's Dreamland was that it is actually an instance of the raycasting algorithm. Yeah, that infamous thing that turned ID software away from my beloved Commander Keen and sent them trashing the whole era of 2D platformers with first-person shooters. No wonder why, despite my affinty for maths and software optimization, I never ever felt tempted to code one myself. But hey, do it vertically instead of horizontally, and this is precisely what we need to decide whether to show floor, ceiling, wall or cut-through floor-to-ceiling structure.

So before trying to reconstruct the algorihtm that might have been used on SNES, let's see what it would cost us.
  • the background scene must be structured along a 2D grid. I'd say 64x64 pixels would be fine by me
  • For every scanline , we will have to step through the grid, one tile at a time, essentially checking whether one more "depth" step takes us farther than one "height" step in our case.
  • We need a direction vector associated with every scanline. That implies 192 square roots per frame, but hopefully, those computations are the same for every frame: they depend on the camera-to-screen distance and define angle used to trace through each pixel. They could be pre-computed even at compile time and stored in a look-up table.

So for every scanline Ys of the screen, (DDA) raycasting will give us a pair of (Zw, Yw) coordinates in the world that is shown on that scanline. Most likely, we don't want to compute the distance to Zw, Yw nor use that distance to adjust scrolling speed. Instead, Zw should directly be used to decide the scrolling speed. Well, unless we're on a horizontal surface, that is. Well, I can't help thinking this is overgeneral and overkill, despite übercool.

The trick in the SNES DKC2 implementation is that we have pre-rendered floors and ceilings. They already feature some depth-of-field effect. if you use them as-is. And being 440px wide, they're significantly larger than the screen (256 pixels iirc). Where does that 440 value come from ? Well, I guess this is screen_width + pattern_width, as the flat stripe shows a repeating pattern of 184 pixels. So whereever you are in the pattern, you can always have at least one full scanline ahead. The most distant line of the ceiling has only 96 pixels between two patterns, matching exactly the size of the tiled background wall. That means if the 'front' part is moving exactly at 1 pixel / frame, the tiled part should be moving at 0.52 pixel/frame so that Thales theorem is satisfied.

That size difference also tell us how far away the tiled parallax layer should be from the 184x32 parallax layer (and thus how deep the floor/ceiling objects are): they're as far from each other as the 184x32 layer is from the "camera"

  s16 xref = REG_BG1HOFS;
  s16 yref = (offset >> 2) % 192;
  s16 xamp = xref + xref / 2;
  s16 yamp = yref + yref / 2;
  int ytrigger = 224 - yamp;
  int btrigger = 192 - yref;
  int i, j;
  for (i = 0, j = 0; j < N; j++, i += 2) {
    if (j > ytrigger && j < ytrigger + 64 
|| j > ytrigger + 256) { data[i] = xamp; data[i+1] = yamp + 30; } else { data[i] = xref; data[i+1] = yref + (j >= btrigger ? 128 : 64); } }

That doesn't make the 3D-effect of DKC yet, but at least it gets me synchronous parallax with a single hardware layer.

Next step: find the zones where ceiling and floor should be shown. And there, trials and errors became too complicated to figure out. Hopefully, I found a way to analyze the problem with maths. Most of what's computed is derived from that "yref" value, which is normally the input from camera position. What I need to do is use that yref as horizontal axis and study how "triggers" that define top or bottom of areas evolve, cross and areas overlap appear or disappear. And once the (simple) maths were written, it took only half an hour to write the code to do it right.

With this graphics, that's the best I can do... let's see how it follows up once I have dedicated background.

Oh, and while I'm at it, there's a stunning online tool out there, to visit every map of every DKC SNES game: the DKC-atlas.




Sunday, May 28, 2023

special properties

While thinking at 'how to implement conveyer belts', I end up investigating some currently incomplete code in special blocks handling. They have correct implementation of their collision areas, but since the migration to the new tile types engine, they can no longer act as 'doors' because they all have the same cando() properties.

There's some code in the parser to change that, and some code in 'FlatWorld' to return BlockInfo::props when we encounter one ...

Wait ... BlockInfo::props is precisely what this last `siscanf()` updates ... so ... that means the code is not incomplete at all ? it just needs to be tested ?

Tuesday, May 02, 2023

A bit of shadow

Once upon a tweet, was a blog post about making dsgametools widget more readable and nice to use by adding a shadow to the default font. It was the youngest of the Todo family and its sibling would often make fun of it because it had the lowest possible priority for the project.

But then, on an idle day were the coder so tired he couldn't read a screen, though he had the Todo category open on his boox. "Well, why not" he thought, and he let our little Thumby Todo post jump over a blank page of his notebook.

It started sketching one letter, with its right and bottom empty lines of pixels, and split it in 3: Darkness, where light normally never comes; Highlights, where light might strike harder. And it turns out that if Highlights were to fall and the darkness picked up its power, then the letter would look all flat. And then the color that never changes, regardless of whether Highlights or Darkness is winning.

Bon, quelque part entre 2 scorpions, j'ai eu envie de creuser un peu cette histoire d'ombre pour les caractères dans les outils DS. Le mockup donne tellement mieux que mes captures d'écran ... en plus, ça se prête à merveille à un peu de programmation sur papier. Un petit schéma d'abord... puis quelques routines comme à l'époque du cours d'algo. Et puis j'ai fini par me rendre compte que chaque fois que je passais devant cette page-là dans mon livre-ordinateur cahier-agenda, ça réchauffait un peu mon coeur de codeur de penser qu'un jour, peut-être, j'aurais le temps d'essayer ça.

Very well, thought the coder, but I do not feel like hand-painting every 128 characters of the default font. Can we find a simple set of rules that, given a regular 1-bit-per-pixel font would deduce the 2-bit-per pixel font with highlights ? Yes, of course. And that's a perfect fit for bringing back paper coding.

Well, as usual, it then took a good deal of checks and debugging to find where that code should actually be invoked, what prevents it from working and so on. 

Et puis zut. On est dans un projet perso pour le plaisir, pas dans un projet-pro pour le client, donc au diables le Canban, les priorités, les milestones et tout ça. hg branch et voyons ce que ça donne... eh bien, ça donne bien. J'adore. Juste un hic: ça verrouille presque 40 couleurs de la palette utilisée par SEDS. Ou plutôt, dès qu'on charge un .spr qui utilise plus de 216 couleurs, l'effet est ruiné par un texte qui devient illisible. Parce que oui, j'utilise les palettes étendues de la NDS pour le moteur de jeu et pour presque toute l'interface graphique sauf pour le texte et ... pour le widget "palette" :-P

But there it is. Working. And even on emulator, the feel of it is just wonderful. And all it takes is some NES-glory palette swapping, where the text is moved from the 'interactive' palette with black shadow to the 'active' palette where 'shadow' is actually white and highlight has background color, as if the text had been pushed down by one pixel. Unfortunately, there's a price to pay: instead of 4 system colors, I need 40 of them (2 x 16-color palette + 4 on the 3rd palette). And so far, in SEDS, that means we can only use 216 colors instead of 250.

edit: but there is hope: SEDS is currently *not* using the multi-palettes mode (unlike LEDS) It likely wouldn't suffer from some extra system colors if it did. Converting AnimEDS sounds less appealing, because that has all its interactive windows on the 'main' screen rather than on sub-screen like older tools ^^"

Tuesday, April 18, 2023

std::unique_ptr<>

Okay, it shouldn't be that hard to have a variable that gives access to an object RAII-style while allowing late initialization: I should use std::unique_ptr

RAII is neat. Despite having an impossible-to-use-and-remember-name (don't be surprised if I use 'Rabi' instead ;), it means that you spend less time catching/rethrowing exceptions, less time checking return codes (because you used exceptions) and can think of your software as something as reliable as a building instead of some Jenga game. Want to have some code dealing with some file, but don't want to forget closing the file handle if anything goes wrong ? Simple.

  • have a RabiFile class that encapsulate your file handler
  • have the ctor of RabiFile do *everything* that's needed to be working on the file
  • have the destructor always going to a clean state.

As soon as you've written something like RabiFile music("forest.xm", RabiFile::RO), you're good to go

  • either your RabiFile exists and all its methods are now valid
  • or you failed to create it and an exception has been thrown, taking you out

So with that approach, you'll never write any music->open("underwater.xm") nor any music->close() anywhere. That makes writing of constructors a *bit* more complicated, I admit, especially those for objects that contains some RabiObjects: they have to catch exceptions and rollback any 'personal' resource acquisition they performed because nothing invokes destructor on objects that haven't been fully constructed. If your constructor fails with an exception, it's up to your constructor to ensure it leaves no mines behind.

But that usually don't happens a lot. It does happen when I stretch the RAII fashion to long-lived objects like GameLevel that has LevelMap and SpriteSet(s), or to GobState that has GobTransitions, though. But Rabi* is mostly for temporary things that need us to hold something while something else.

There's one common drawback to both, though. Because my objects have to be allocated on the stack to benefit automatic cleanup on failure, it is tricky to benefit from polymorphism. Say I should either create an instance that reads the script from a file or an instance that reads it from a buffer received over WiFi by runME ... I can't just replace BufferReader reader(...) at the head of my function by


loaderCode() {
   if (fromFile) {
       FileReader reader(whichFile);
   } else {
       BufferReader reader(whichBuffer);
   }
   DoStuffWith(reader); // no such reader, dude.
}

because there, the condition-dependant objects are no longer valid when I then want to use them. And migrating the DoStuffWith into the condition will soon turn unpleasant as well because it is not DRY. So instead, I can have


loaderCode() {
   std::unique_ptr<InputReader> reader;
   if (fromFile) { 
      reader = std::unique_ptr<FileReader>(new FileReader(...));
   } else {
      reader = std::unique_ptr<BufferReader>(new BufferReader(...));
   }
   DoStuffWith(*reader);
}

Granted, that will not allocate the object on the stack but at least it guarantees that the created object gets deleted whatever happens during DoStuffWit(*reader). Given the size of the NDS stack, it might not be a bad move.
Maybe I could have done it with auto_ptr instead. It seems it was the way to do it before C++11 came, declared auto_ptr obsolete and unique_ptr as being the way to do this instead.

A few things to remember when working with unique_ptr

  • you must use std::move(reader) if you intend to return the unique pointer as function value
  • you must use std::move(aswell) if you want to capture some unique pointer you've received as a member of a new uniquely pointed thing

  •  

Wednesday, January 12, 2022

Slopes Landed.

I think I got it working. There was one major flaw in my earlier design: stating that you can F_FALLTRHU a slope implied that you could no longer walk on it, as the walking controller tests for solid ground by checking whether it would be possible to fall down from the current location. Oh, not much. Just one pixel is enough to claim that you cannot walk anymore.

But the technique used for terrain collision detection -- cando() -- assumes that we only do a move if we can do it over all the tiles covered during the move. That means the slope tiles should both allow us to fall through them (until ground height, at least), and not allow to start falling through them. To get that solved, I had to split the flag, having one bit telling whether we can start falling (F_START_FALLING) and one telling whether we can keep falling. Walker controller tests one of these bits, gravity controller tests the other one.

Then I had another issue, not properly computing the ground distance to see whether the move we cando actually remained over the ground. Let Bouli explain that...

We were at old position (ox, oy) and will move our 'hot spot' (the one that is kept in contact with the ground on uneven grounds) to (hx,hy). In order to know whether we're find with slope-landing, we compare hy - oy with the 'ground distance', which is construct with an appropriate sum of tile.groundheight() calls.

But groundheight() gives us a value relative to the bottom of the corresponding tile. -8 means the whole tile is solid. -2 means the first 2 pixels of the tile are solid, the rest is air. 0 means the whole tile is air. That was quite quickly remembered and accounted for. The other part to take into account is that the start of the darkblue 'vertical motion vector' may be anywhere within the first tile. If we're in the 4th pixel of the tile and the last pixel of the tile is solid, then only hy - oy < 3 are uninterrupted moves.


Thursday, December 30, 2021

Color Cycling.

Being able to animate things just by changing entries in the color palette is one of the reasons why I'm loving old school programming that much. The amount of pixels you can affect with this technique with just a few memory writes has so much hex appeal! Yet, you wouldn't find anything animated that way in Apple Assault or in School Rush. Flowing ink could have used some, though.

Technically speaking, one key thing to fix to get it working with my setup is to ensure that the palette writes happen in a Vertical blank handler, and the other is that we bring the video memory holding the colors as freely-accessible video memory ( aka LCD for the Nintendo DS) while updating it, and then return it as extended palette bank.

So with all that coded, I could replace the lengthy 16-frames animation of the waterfall with just one static frame. And I have a comfortable homework to create as many tiles of flowing-wafer-slopes as I could need. I used a sort of "rainbow raster" to be able to draw those animations properly, which makes them look a bit strange in the level editor and in runMe, but I should be able to fix that by having the raster be on a secondary palette shot and a "static preview" on the main (and animated) slot.

Yeah, that's the major drawback: I don't see how to get it customizable from the level scripts by now, so I prefer keep it as some custom code running in Dreams.nds and leave runMe alone for now.

Là, je m'attaque à un morceau iconique du développement 16-bit: l'animation des palettes. Un truc que je connais depuis le BASIC mais que je n'ai encore jamais eu l'occasion de pratiquer sur DS (c'est dire si les petites animations sont à la traîne dans mes jeux DS :-P. Le principe ? les graphismes en mémoire vidéo ne contiennent pas directement des couleurs, mais des références à ces couleurs. Les pieds de Bilou seront faits avec la couleur n° 24 alors que sa main aura la couleur n° 79 et le blanc de ses yeux la couleur n°3, etc. Une partie de la mémoire vidéo est dédiée à retenir la correspondance entre ces numéros et les couleurs RGB à utiliser: c'est ce qu'on appelle la palette. Du coup, si vous changez une de ces valeurs, vous avez la possibilité de modifier simultanément une grande quantité de pixels à l'écran.

Une sous-catégorie de ces animations concernent les boucles de palettes. Vous n'ajoutez aucune nouvelle couleur, vous n'en retirez aucune non plus, vous vous contentez de les changer de place. du coup, un dégradé bien construit peut devenir un tapis roulant, une chandelle vacillante, une mare de lave ou une jolie cascade. (Mark Ferarri étant le champion de la discipline, cf. ci-contre).
 
If you look closely at the blue ramp between the two rightmost waterfalls on the gif above, you'll note that palette cycling is working there (backwards, but working). The waterfalls are still 'frozen' because I haven't applied my palette effect on the background layer yet. Oh, and if you wonder why half of the background is pink on those green zone captures, greenbg.spr is 32x32 and CommonMap expects a 64x32 map instead ... so half of it is 'blank'.

Mais pour que le chip graphique puisse pomper les valeurs de la palette assez vite pour tenir le rythme de l'écran, on doit régulièrement caser la palette dans une mémoire dédiée, avec un protocole d'accès un peu plus compliqué que BG_PALETTE[index] = rgbValue; C'était le cas avec le VGA, c'était le cas avec la SNES, c'est toujours le cas avec la Nintendo DS (plus simple à coder, mais pas forcément à expliquer).

Bref, ça marche, mais vu que ça ne peut se produire que pendant le 'temps mort' entre deux rafraîchissements d'écran, et vu qu'il y a cette histoire de va-et-vient entre la configuration où on sait écrire dans la palette (mode LCD) et celle où on sait l'utiliser pour afficher les couleurs (mode EXTENDED_PALETTE), je vais juste garder ça comme un 'petit extra' de Dreams.nds et vous aurez le fond des cascades statiques quand je ferai une démo IRL avec runMe. Vendu ?

edit: pour que ça tourne rond, j'avais eu besoin de me faire une petite map de où-est-quelle-couleur dans le code de la démo. Je vais reprendre ça ici aussi, parce que je me repose la même question deux ans plus tard et que twitter, hein ... on verra bien où il en est dans 2 ans.


Saturday, December 18, 2021

Ludricous Speed

 There's another bug with my modified version of libntxm. It has been around since at least early School Rush prototypes and possibly even Apple Assault. If you let the player spin for too long, it suddenly starts to play things at ridiculous speed that makes the song barely recognizable.

I had the Dreams demo running with ARM7 debugger plugged in for another reason (undefined instruction) and it started speeding the song up so I dropped all my kitchen stuff and jumped on the CTRL+C to be able to dig that later. And later is now.

One symptom of the issue seems to be that the tick_ms variable is now way larger than the MsPerTick() property of the Song.

Every time the playTimeHandler() is invoked, it recomputes how many milliseconds elapsed since last call, and here I get the value of 1. It won't get out of the issue because there's nowhere we could read tick_ms = 0. Instead, the amount of time expected is substracted from the time elapsed so far, so that 'extra milliseconds' are deduced from the next 'tick'.But watching that tick_ms evolve, it is properly decreasing. It has not just been gone wild. It got corrupted from something else.

It doesn't tell me what goes wrong in the soft, unfortunately, but at least, it gives me a way to make sure players won't experience it. If I cap the tick_ms to 2*MsPerTick(), I shouldn't have issues.

Note how buggy tick_ms is close to last_ms ...

Note how unlikely it is that the player has been running for 43509 seconds. I wasn't even running it for one hour! And when it eventually occured again, lastms was 43494543, or something alike (decimal, not hexa).

Seeing some more code featuring 'compute over 100' now makes much more sense. 100 times higher than 43 million, we're quite at MAXINT for an unsigned. Plus, if you've got an unsigned, comparing -t to something might very well produce something that is compiler-dependent. Likely, there might be a switch to produce a warning for that.

Il y a un truc qui me chagrinait, avec School Rush: au bout d'un certain temps la musique partait en vrille. Le problème se posait uniquement au bout d'un temps assez long, du coup, je n'avais jamais eu l'occasion de prendre le temps de chercher le problème. Mais hier, j'avais un débuggeur ARM7 prêt à fonctionner, un émulateur branché dessus avec le code qui 'là-normalement-ça-devrait-marcher' pour un autre soucis et j'étais en cuisine ... donc ils ont tourné pendant longtemps sans intervention ... assez longtemps pour que le problème de School Rush se produise. J'ai bondi sur mon ordi pour interrompre tout ça et débugguer enfin cette histoire.

Au premier tour, tout ce que j'ai pu dire c'est que "bin c'est parce que tout d'un coup, on découvre qu'on est en retard de plus de 40 millions de microsecondes pour passer au 'tic' de musique suivant". J'avais espéré qu'il s'agissait d'une déviation ou quelque-chose de ce genre, j'avais mis quelques 'break if...' dans ddd mais quand le problème s'était finalement re-reproduit (bonne journée, ça), on était passé de quelques millisecondes de retard à plus de 43 millions, comme ça, sans-transition-à-vous-les-studios. J'en venais à me dire que 'tant pis, je vais juste mettre un patch qui limite artificiellement ce délai' puis je tombe sur l'implémentation de la lecture du temps à partir du hardware qui cache une division par 100 ... et 100 * 43 million, on approche du plus grand entier 32 bits. Là, ça pue le bug de conversions arithmétique dû à une soustraction mal calibrée... ça je pourrai corriger pour de bon.

Tuesday, December 14, 2021

PlaySFX vs. PlayInst

 

Je refais un peu d'explication-BD parce que là, c'est un brin compliqué. Le mécanisme que j'avais ajouté pour faire des effets-par-pattern, je l'ai poussé un peu trop loin.

Au départ, c'était bien vu: un ensemble de flags permettant de dire si on doit suivre la "partition" normale du module ou continuer à avancer dans la piste en cours jusqu'à trouver un effet 'pattern break'. Mais il y a un cas que je n'avais pas en tête à ce moment-là: les effets 'normaux' fait d'un p'tit sample tout seul. Eh oui.

Dans ce cas-là, la piste sur laquelle on met le sample n'a normalement pas de contenu. Depuis le nouveau code, elle est ignorée puisqu'au-dessus du nombre de pistes définies dans le fichier .xm. La seule exception à cette règle, c'est pour les pistes qu'on a attachées à un morceau de piste d'effet.

Disclaimer first: many of the things I'm now doing with libntxm are far beyond what it was designed to do, that is running a soundtracker on the NDS. When thigns go wild because I stop a song and replace it with another before the Player class on the ARM7 get any chance to run its every-millisecond handler routine, I'm worse than a TAS-speedrunner producing superhuman inputs on the software. Right ? Same when I feed the Player with two patterns simultaneously and going 'yeah, play pattern 9 on channels 1-12 while at the same time playing pattern 27 on channels 14-16, will you ?'. None of this was part of the original plan, and that's perfectly natural. It's up to me to make it work if I want it to work.

Ça, ça marche bien pour le moment 'lecture de la nouvelle cellule (note) dans la ligne'. Mais si on veut qu'un p'tit son puisse être joué, il faut qu'on continue à faire des calculs d'enveloppe, la gestion des registres de volume. C'était déjà un peu à ça que servait le flag channel_active. Il reprend du service.

Bon, là je me suis limité à un p'tit test tout bas, parce que ma fée est pas ultra-fan de StrTrk et que c'est l'heure de la p'tite série qu'elle aime bien à la télé.

So a few weeks ago, I fixed a crash in the ARM7 code with 'oh, I know: I'll give NTXM player a NULL song first, and then delete-and-create another one'. Not a bad idea in itself, but I missed a few things that the player has to do even when I change the song, like progressively muting the channels when there are no more notes to do. Obviously, I shouldn't prevent it from doing so even though we activated the NULL song.

I also finally implemented an if-block for the GobScript (it turned out quite simple thing to do) and tried to use that so that we'd use pattern-effects only when playing a tune with such patterns, and play plain-samples-effects otherwise. Nice try, but it turned out that no sample was playing at all. That was because of my previous changes again: when playing such lone samples, we're perfectly right to do so on channels that are not present in the song. (Actually, we should better do so). But the code from SchoolRush would only start playing a sample if there was a pattern Cell to back it up. Either a regular one, or one from a pattern-effect. I had to identify the tasks where that test was mandatory (e.g. when processing effects or advancing to the next row) and tasks where it shouldn't be used (process volume envelopes, start the sample, turn down the volume when done playing the sample, etc.)

Reste à espérer qu'avec les corrections de 'quelle musique jouer quand' et 'okay, on a plus de partitions, mais continue quand-même à baisser le volume', ça suffise pour que les musiques de la prochaine démo soit un peu plus sous contrôle.

Ah oui. Et au cas où, ces choses en forme de tentacules (Bob) sont des statues-registres.

Saturday, April 10, 2021

spr.more in Level Editor

 

ça n'aura pas été simple de recommencer à avoir des représentations des monstres correctes dans le Level Editor. J'aurais probablement dû utiliser mon "autoexec.nds" pour le coup, mais j'ai procrastiné ça en insistant que 'nan, mais j'y suis presque, là'. J'aurais aussi eu plus simple si j'avais eu un affichage de la VRAM 'brute' dans mon émulateur, mais je n'ai pas encore ça non plus. Ou éventuellement un 'print' qui se mette à jour d'office.

Là, j'ai pu corriger pour que spr.more ne perturbe plus le chargement du 1er spriteset, mais je dois encore chipoter pour le 2eme. Je pensais que c'était lié aux arguments de la commande, mais en réalité, ils n'ont pas d'impact si on ne joue pas les animations.

Mais les dernières ruses que j'ai dû déployer pour que ça fonctionne (notamment un champ additionnel 'meds+%d') me mettent un peu mal à l'aise. J'ai l'impression que c'est le signe que le mécanisme spr.more n'est pas au point et demande de trop connaître son comportement pour pouvoir s'en servir.

edit: près d'une semaine plus tard, je finis par mettre le doigt sur le dernier problème. Une commande destinée à introduire une animation pour 'SimpleGob' dont j'ai changé la syntaxe dernièrement sans penser à changer la syntaxe aussi dans le parseur de l'éditeur de niveaux.

Donc, l'éditeur de niveau est prêt (mais pas encore uploadé), l'éditeur d'animations est prêt aussi (allez, je les uploade tous les deux sur le cube), les fichiers .spr sont corrigés (sur la DS aussi, je crois) ... On dirait que je vais depouvoir commencer à faire des maps pour Dreamland ?_?

La bonne nouvelle c'est qu'au passage, j'ai doté cmdck de nouvelles fonctions pour me montrer le contenu des tables régissant ces vignettes sans devoir faire appel à l'émulateur (?thumbs, ?page, ?states)

Saturday, April 03, 2021

Where are my thumbs ?

I'm trying to add support for spr.more in LevelEditor (so that we keep seeing monsters positioning while editing DreamLand maps ;) but it doesn't work well. After some debugging, I realize that the values for the 'animation number' stored in the first word of the 'TINY' section make no sense. Animation #17000 ? with 48 animations per sheet ?

A simple test with ./sprdo confirms that even if I just load-and-save a file that has meaningful thumbs numbers, we end up with bogus numbers in the saved file.

Still wondering what thumbs I'm talking about ? They are still 32x32 images produced while saving animations in AnimEDS that are then used in LEDS for monsters edition.

Watching the memory backing my vector through execution, I finally found a suspicious set of `free()` statements at the end of the 'extra/unknown data blocks to set of MEDS animations' function.

The history of that function is that it abstracted something from `spr.load` script statement so that `spr.more`can exist. When it is done with them, the GameScript used to dispose of remaining really-unknown data blocks. Such as thumbnails. They won't be needed while we play the game and they eat up significant amount of memory.

Then, there was the code of SpriteEditor, the FileModel, to be precise. It had been updated so that it could invoke the same sort of 'FromExtras' function, but since we're not interested in MEDS animations while editing sprites, template magic makes this whole function unused. And then is the new tool, that tries and use code meant to be for GameScript but in a load-modify-write setup. That one ends up with freed pointers in its array and then try to save back those opaque data in the file. This is where my 17000 comes from.

The perplexing thing is that AnimEditor itself doesn't seem to read back the TINY thumbnails, but still when I load a file from OtherFileWindow, I get no thumbs on my top screen. Not until I edit something and move to another page, that is.

It took me time to check everything, and it finally revealed that, yes, makeThumb() are properly invoked even for OtherFileWindow, as soon as we call MetaWindow::data_reloaded() to signal things were updated. But the setup ThumbsWindow::restore() installed to show and use the TileTable showing the thumbnails got screwed up by OtherFileWindow's default layers settings. forcing a call to THW::restore() within data_reloaded finally fixed the issue. So that means bilou.spr on lime DS is likely fine and I can download it to resume development of LevelEditor's support for spr.do ^_^ (erhm. Next time. It's quarter-to-midnight and I've got meetings tomorrow ^^")

edit: unfortunately, after retrieving bilou.spr and school.spr that had been edited by AnimEditor on the NDS, they still contain rubbish TINY sections... I'll have to work on a medsdo tool to study that...