CasioCraft... le retour
Posté le 27/12/2024 18:59
Mon project
Bonjour à tous ! Cela fait litéralement 10 ans que je n'ai pas ouvert ce site ! C'est un plaisir de revenir !
La dernière fois j'étais au lycée et participais au concours des 10 ans du site avec CasioCraft, un petit terraria like.
Je suis passionné par la programmation sur systèmes à ressources limités et récemment je me suis repris d'intérêt pour ma Casio 35+ et je compte reprendre CasioCraft, toujours en basic vanilla, pas d'overclocking.
Dans la liste de ce que j'aimerai faire :
- affichage dense : 32x12 cases, 3x3 pixels par bloc avec 1 pixel d'écart entre les bloc
- au moins 16 blocs distincts
- toute combinaison de 3x3 pixels affichable pour un bloc
- "texture pack" modifiable
- système de visibilité : les cases qui n'ont aucun coté en contact avec de l'air sont cachées
- chargement d'écran "rapide" : si possible en moins de 10s (mon premier CasioCraft pouvait prendre entre 20 et 30s)
- système d'inventaire "avancé" : barre active, inventaire, craft, coffre, loot...
- monde "vaste" : je vise un minimum de 128x64 ou un système dynamique par chunk qui permettrait des mondes de forme arbitraire
- génération "intéressante" : j'aimerai expérimenté avec des bruits à plusieurs octaves, probablement avec une base de perlin modifié, mais il faut alors s'attendre à une génération initiale de la map de plusieurs minutes
Précision pour l'affichage, une fois un écran chargé, seul le joueur et les blocs cassés/posés seront mis à jour pour un rendu intéractif. Mais le joueur pourra recentrer l'écran sur lui à volonté et c'est ce chargement qui devrait durer une dizaine de secondes.
Je m'excuse par avance, cet article va être long et technique ! Il nécessite de bonne connaissance en binaire et en multiplication matricielle. J'espère qu'il sera intéressant à lire et potentiellement inspirant.
Pour le moment je me concentre sur le stockage et l'affichage. Pour stocker de vaste monde sans exploser la capacité limité de mémoire de la 35+ il parait évident que je vais devoir compresser la donnée. Pour afficher un écran rapidement et "jouer" dedans il est fort probable que j'utilise une autre représentation décompressée. Pour avoir un temps de chargement limité la transformation entre les 2 représentations doit être très efficace. Si vous avez des idées géniales je suis preneur !
Stockage
De mon côté j'explore la piste du "binary packing". Tous les nombres (entiers ou floatants non imaginaires) de la 35+ sont représentés sur 12 octets (96 bits). Je n'ai pas fait de recherche sur la représentation interne de ces nombres, mais empiriquement les entiers de 0 à ~2^33 (0b100100010000001011111000111111111 pour être exacte) sont considérés "non floatants" et supportent donc les opérations "strictement entières" comme MOD. Malgré cela, les entiers se comportent comme attendu jusqu'à 2^43 environ, passé ce point, les additions et soustractions perdent en précision et les derniers bits peuvent être tronqués. Etonnemment, d'autres opérations marchent correctement jusqu'à 2^47 environ. J'imagine que c'est un format propriaitaire avec 48 bits de mantisse et des bits de metadata pour des optimisations spécifiques à la casio. En tout cas ce n'est pas IEEE 754.
Malheureusement la 35+ n'a pas d'opérations binaires (and/or/xor/shift) sur les nombres, pour extraire le Nième bit d'un nombre X il faut donc utiliser des opérations algébriques :
- MOD(X int÷ 2^N, 2) : fonctionne jusqu'à 2^33 environ
- int(2 frac(X÷2^(N+1)) : fonctionne jusqu'à 2^47 environ
Si vous connaissez une méthode encore plus efficace pour packer et extraire des bits, je suis preneur ! 47 bits sur 96 ce n'est pas un très bon ratio.
Je pense qu'un bloc peut être représenté par 5 bits : 4 pour le type (16 bloc différents) et 1 pour la visibilité (pour éviter de revisiter ses voisins à chaque chargement). Une première approche serait de packer 6 ou 9 blocs dans un seul entier utilisant 30 ou 45 bits (compatible avec la première ou deuxième méthode de décodage respectivement).
Je peux ensuite stocker ses nombres dans une matrice (compressant les lignes ou les colonnes), ou dans une liste en utilisant la deuxième méthode : 128x64/9 ≈ 911 < 999. De façon consécutive ou désordonné par chunk (avec une autre table de metadata pour les retrouver). Une compression RLE (Run Length Encoding) pourrait grandement diviser la mémoire nécessaire, au prix d'une décompression beaucoup plus longue.
Decompression
J'avoue que cette partie me parait particulièrement complexe.
Pour ce qui suit, considérons que le blocs sont "simplement packés" et stockés dans une matrice compressée par ligne. Si un entier stock 6 blocs, 7 entiers doivent être récupérés pour former une ligne de 32 blocs (6x7 = 42, 10 blocs sont inutilement décodés). De même pour :
- 9 blocs par entier : 5 entiers à récupérer, 13 blocs décodés inutilement
- 8 blocs par entier : 5 entiers à récupérer, 8 blocs décodés inutilement
Ce n'est pas optimal pour le stockage, mais 8 blocs par entier semble un bon compromis. J'utiliserai donc cette valeur à partir de maintenant, mais rapelez vous que rien n'est définitif sur la façon dont la donnée est compressée et stockée.
Je ne me rapelé pas que le basic casio était aussi lent, simplement itérer sur chaque bloc d'un écran prend 7s !
For 1→J To 12 // Y
For 1→I To 5 // X
Mat[I, J]→C // entier compressé
For 1→K To 8 // sub X
Int(32Frac(C÷32^K))→D // bloc décompressé
Next
Next
C'est bien entendu sans compter le temps de traitement de chaque bloc, qui consiste certainement à :
- stocker sa valeur dans une matrice décompressée (ou un format optimisé pour l'affichage)
- tester/séparer sa visibilité de son type
- afficher le bloc en fonction de sa visibilité et de son type
Une méthode
potentiellement plus efficace (plus rapide, mais peut être plus dur à utiliser ensuite) consiste à utiliser les capacités vectorielles de la casio. Voici le code (une explication suit) :
Seq(1÷32^N,N,1,8,1)→List 1
Trn List→Mat(1)→Mat B
{60,8}→Dim Mat C // matrice décompressée, chaque cellule représente un bloc, au lieu d'être 12x32, elle contient les blocs inutiles 12x40 = 12x5x8 = 60x8
60→Dim List 1 // liste de tous les entiers compressés d'un écran
For 1→J To 12 // Y
For 1→I To 5 // X
Mat A[I, J]→List 1[12I+J-12]
Next
Next
// Décompression vectorielle !
Int(32Frac List→Mat(1)Mat B)→Mat C
Ce code prend seulement 2s ! Mais le résultat est plus difficilement exploitable, c'est une matrice de 60x8, pratique pour tester les collisions, casser/placer des blocs (avec un simple changement d'index 32x12→60x8), mais je ne vois pas de façon efficace pour faire l'affichage.
Rapide explication de cet algo avec une version simplifiée du problème, considérons que List 1 contiennent 3 nombres (A, B et C) et que l'on veut obtenir leur représentation binaire sur 3 bits. La matrice B contient l'inverse des puissances de 2 de 1 à 3 (chaque entier de List 1 contient 3 "groupes" de 1 bit).
List 1 = {A, B, C}
List→Mat(1)Mat B =
┌ ┐ ┌ ┐
│A│ │A/8, A/4, A/2│
│B│[1/8, 1/4, 1/2] = │B/8, B/4, B/2│
│C│ │C/8, C/4, C/2│
└ ┘ └ ┘
Il ne reste plus qu'à appliquer la formule Int(2Frac(X)) à chaque élément, ce que la casio permet de faire "d'un coup" :
List 1 = {1, 3, 6}
┌ ┐
│0, 0, 1│
Int(2Frac(List→Mat(1)Mat B)) = │0, 1, 1│
│1, 0, 1│
└ ┘
Dans l'algo de décompression List 1 contient tous les blocs à décompresser, Mat B les puissance de 32 de 1 à 8 (entier contient 8 groupes de 5 bits).
Affichage
Mon premier CasioCraft utilisait une commande Text pour chaque bloc, ce qui n'est pas efficace du tout. Une deuxième version (non publiée) utilisait un Text par ligne (en utilisant les String), ce qui est beaucoup plus efficace du moment que l'on peut construire les Strings rapidement (ce qui n'est pas chose facile suivant l'algo de décompression utilisé). Un désavantage de cette technique est que les blocs affichables sont restreints aux caractères affichable par Text. La palette est toute fois relativement étoffée mais loin de pouvoir représenter toutes les combinaisons de 3x3 pixels.
Une deuxième approche est le DrawStat. Encore faut-il trouver une façon efficace de dessiner 32x12 cases de 3x3 pixels où chaque case peut avoir un paterne différent. Je pense avoir trouvé une façon intéressante, mais difficile à exploiter. Elle utilise 5 listes :
- List 1 : coordonnées X des points "actifs"
- List 2 : coordonnées Y des points "actifs"
- List 3 : coordonnées complexes X+iY de tous les blocs visibles (non air et non cachés)
- List 4 : liste des "bitmap" de chaque bloc visible (un entier de 0 à 2^9-1)
- List 5 : liste temporaire
Voici un code pour initialiser les listes avec des paternes aléatoire, juste pour tester :
// je remplis seulement 10x12 blocs, il est peu probable que les 32x12 blocs soient tous visibles en même temps
0→K
For 0→J To 12
For 0→I To 10
K+1→K
4I+4Ji→List 3[K] // coordonnées X+Yi
RanInt#(0,511)→List 4[K] // random bitmap
Next
Next
Et le code pour dessiner :
S-Grph1 DrawOn,Scatter,List 1,List 2,Dot
BG-Pict 1
0→K
// itère les 3x3 pixels de chaque bloc
For 0→J To 2
For 0→I To 2
K+1→K
// extrait le Kième bit de chaque bitmap (0 ou 1) et multiplie leur position
Int(2Frac(List 4÷2^K))List 3→List 5
ReP List 5→List 1 // coordonnées X
ImP List 5→List 2 // coordonnées Y
ViewWindow -I,126-I,1,-J,62-J,1 // 127x63 pixels, origine décallée par I et J
DrawStat
StoPict 1
Next
Next
Ce code dessine une liste de blocs en 9 DrawStat en suivant une liste de bitmap. Pour toute itération (I, J) List 5 contient une liste de cooronnées :
- 0+0i si le bloc correspondant n'a pas de pixel dans sa bitmap à la position [I, J]
- X+Yi si non, où X et Y sont les coordonnées du bloc correspondant
Chaque DrawStat dessine donc tous les pixels [I, J] de tous les blocs qui ont ce pixel dans leur bitmap, et un pixel en (0, 0) pour tous les blocs qui n'ont pas le pixel [I, J] dans leur bitmap.
Cette méthode prend 10s pour afficher 120 blocs (et grandit linéairement avec le nombre de blocs à afficher). Ce qui n'est pas trop mal. Encore faudrait-il avoir un moyen efficace de décompresser le format de stockage dans ce format spécifique.
A noter que le format compressé proposé est suffisamment simple pour permettre les accessions nécessaires pour les tests de collisions et casser/poser des blocs. Décompressé "un écran" dans un format "à plat" n'est pas strictement nécessaire. La seule décompression nécessaire est vers un format optimisé pour l'affichage.
J'ai fait quelques tests avec le super DrawStat/Graph(X,Y) mais aucun résultats conluants.
Conclusion
En résumé beaucoup d'ambition, des techniques intéressantes (je trouve), mais pas énormément de résultats. L'utilisation des capacités vectorielles de la casio me semble essentielle pour parvenir à des temps de chargement raisonable. Encore désolé pour le pavé ! Si vous avez des pistes, des informations sur le format binaire, des idées pour le stockage/decompression/dessin, ou des remarques je serais très content de les lire !
Citer : Posté le 28/12/2024 09:42 | #
Que d'idées, wow ! J'ai un peu peur des blocs de 3x3, pour être honnête. Combien de texture peut-on vraiment communiquer en 9 pixels ? Surtout que 32x12 par écran pour un monde de 128x64 ça fait pile 4 écrans mis bout à bout—sûrement il serait acceptable de mettre des blocs plus gros (e.g. 5x5) ? Si la limite de 128×64 est choisie pour passer dans 999, je suggère 192×40 ou un autre arrangement dans ce style plus allongé. Si tu penses à la taille de Minecraft (des millions au moins x 384) on va bien moins loin verticalement qu'horizontalement.
Non c'est du BCD. De mémoire 12 chiffres visibles, 15 en interne. Les détails sont documentés quelque part...
On a and/or/xor and en mode base et si ma mémoire est bonne si tu les copies dans un programme pas en mode base ils marchent toujours. shift, je sais plus par contre. Dans tous les cas sur 12 chiffres t'as guère que 39 bits significatifs un truc comme ça, t'auras jamais les 96 (exposant + signe).
Pour la décompression, attention au fait que 1/32^n c'est représenté de façon approximative parce que c'est du BCD.
Malin, du reste ! J'aime beaucoup la décompression en parallèle. C'est pas souvent qu'on a besoin d'une opération matricielle qui augmente la taille des données (e.g. vecteur colonne * ligne) mais là ça marche très bien.
Pour l'affichage des blocs, tu pourrais peut-être éviter la génération des pixels en pré-stockant les blocs de ton jeu dans des listes (ou dans une matrice) et en affichant simplement la liste numéro « ID du bloc ». Après tout, toutes les combinaisons de bits pour les textures ne sont pas intéressantes : pas en 3x3 (no way je suis capable de différencier 512 blocs différents de 3x3) et encore moins à des tailles plus grandes.
Citer : Posté le 28/12/2024 15:30 | #
Quelques sources pour le format des nombres :
- https://bible.planet-casio.com/simlo/chm/v20/fx_legacy_numberformat.htm (issu de la rétro-ingénierie de CASIOWIN par SimLo)
- https://next.cahuteproject.org/topics/number-formats.html#fx-9860g-style-bcd (récap' avec quelques exemples en plus intégré à la doc de Cahute)
Mon blog ⋅ Mes autres projets
Citer : Posté le 28/12/2024 15:38 | #
Merci pour ce retour ! C'est amusant de retrouver des pseudos que je n'ai pas vu en 10 ans.
Résolution
Pour les blocs 3x3 je ne suis pas encore sûr, j'ai simplement repris ce que j'avais fait à l'époque. Dans le premier CasioCraft ce choix était lié à la selection limités de caractères affichables par Text. Je suis d'accord que la résolution est très faible, mais j'aime bien le style très simpliste (et nostalgique pour moi) et la taille des écrans que cela donne. Cela permet de créer des "constructions" plus grandes sur 1 seul écran, et minimise le besoin de changer fréquemment d'écran.
Exemple d'écran en 32x13 (12 ou 13 pour la hauteur, ça dépend de la taille de la barre d'inventaire) :
Si je reste sur cette résolution, je pense réutiliser les textures des blocs de base (du premier CasioCraft), ici sur la première ligne dans l'ordre :
[terre | pierre | sable | feuille | bois | planche | coffre | portail-complet | portail-vide | fer | autre minerai ?]
Sur la deuxième ligne une proposition de dalles, et sur les deux dernières plus de blocs décoratifs. Il y a 12 blocs de base (en comptant l'air), ce qui rentre largement sur 5 bits. Cette palette complète compte 49 blocs, ce que je peux faire rentrer sur 6 bits en changeant un peu comment la "visibilité" fonctionne.
Je suis curieux de voir à quoi pourrait ressembler un écran utilisant des blocs 4x4 ou 5x5, je ferai des tests de mon côté.
Stockage
128x64 est vraiment arbitraire, c'est plus pour donner un ordre de grandeur sur le volume plutot qu'une taille exacte. Je suis d'accord qu'il est certainement plus intéressant d'avoir des maps longues plutot que hautes (même si j'aimerai expérimenter un peu avec de la génération de cavernes). J'ai simplement choisi les puissances de 2 les plus proches des dimensions que je pense possible à stocker.
Merci pour l'information sur le BCD ! Je n'y avait pas pensé (et pas cherché correctement). Je vais expérimenter pour voir si je peux en tirer profit ou peut être trouver des opérations plus performantes. Je me demande notamment si un packing décimal plutot que binaire serait plus éfficace ?
Effectivement, je n'avais jamais regardé le mode de base, il y a bien and/or/xor mais copy/paste ne semble pas marcher dans ce mode. Si cela marche, c'est étonnant qu'ils ne figurent pas dans le catalogue du mode standard.
32^n perd en précision à partir de n=10, je n'ai pas trop exploré comment l'inverse est représenté, mais dans mes tests je n'ai jamais observé de perte de donnée jusqu'à n=9.
Affichage
Il est vrai que je n'ai pas essayé cette approchage de rendu séquentiel de tiles précalculées. Mais j'avoue que je pense qu'elle sera trop lente, en tout cas pour des écrans de 32x12, DrawStat semble avoir un overhead assez conséquent, le payer une centaine de fois me semble (sans avoir testé) trop lent. Ce que j'aime bien avec la technique que j'ai décrite c'est que tous les blocs sont dessinés "en parallèle" avec seulement 9 DrawStat. Mais ça reste une idée à creuser, surtout si je passe à une résolution plus élevée pour les blocs (et donc moins de blocs dans un écran).
Citer : Posté le 28/12/2024 15:46 | #
Maintenant que je le vois, le 3x3 est plus raisonnable que je le pensais. Hmm... je suis curieux maintenant de voir jusqu'où ça peut marcher !
C'est clairement pas prévu pour qu'on les saisisse, de mémoire faut les insérer via FA-124 ou direct dans un g1m.
Je crois que je n'avais pas bien compris ton code. Ce qui m'échappe c'est pourquoi tu as besoin des 9. Dans le code que tu cites K est incrément mais ne fait rien. Il est caché dans formule sur laquelle tu fais le DrawStat ?
Mon premier réflexe aurait été de faire un Super/Multi DrawStat avec des listes précalculées pour chaque bloc parce que comme tu traces des lignes tu as besoin de moins de points et tu peux équilibrer le nombre d'appels vs. la taille des listes assez librement.
Citer : Posté le 28/12/2024 19:06 | #
Merci Cakeisalie5 c'est super intéressant et bien documenté !
Oui, pardon Lephenixnoir, j'ai fait une erreur sur cette ligne :
Int(2Frac List 4)List 3→List 5
Correction (je vais également corriger dans le message original) :
Voici un exemple qui devrait être un peu plus explicite, disons que List 3 contient les positions (8, 0), (0, 4), (4, 4), (0, 8), (8, 8) dans des nombres complexes. Cela représente une liste de coordonnées de 4 blocs. Un premier DrawStat avec la partie réelle dans List 1 et la partie imaginaire dans List 2 on obtient les 4 points de le la première grille ci-dessous :
Dans la grille 1, le pixel en bas à gauche a comme coordonnées (0, 0).
Si on décalle l'origine d'un pixel vers le haut (viewwindow 0, -1) et que l'on refait le même DrawStat, alors on obtient le même paterne mais décallé d'un pixel vers le haut (grille 2). On pourrait répéter cela 9 fois pour couvrir les 9 pixels de chaque cases et obtenir des blocs pleins.
Pour varier un peu nous pouvons multiplier la List 3 par la liste { 1, 0, 1, 1, 0 } avant d'assigner les parties réelles et imaginaires à List 1 et List 2, la nouvelle liste de coordonnées est donc (8, 0), (0, 0), (4, 4), (0, 8), (0, 0) (nous avons "nullifier" le deuxième et dernier bloc). En décallant l'origine vers la droite (viewwindow -1, 0) 3 pixels sont dessinés sur les cases "en diagonal" tandis que pour les 2 blocs "nullifié" le pixel est dessiné sur la case (0, 0) (en rouge).
En multipliant par { 1, 0, 0, 1, 1 } et en décallant en haut à droite, on obtient la grille 3.
Cette méthode de dessin peut être systématisée en stockant la bitmap de chaque blocs visibles dans la List 4 et en utilisant l'extraction vectorielle de chaque bit. Pour l'exemple ci dessus, la List 4 contiendrait : { 1+2+4+8, 1+2, 1+2+4, 1+2+4+8, 1+2+8 }.
Bien sur, pour éviter que les pixels des blocs nullifiés soient visibles en bas à gauche, tous les blocs seront certainement décallé de 10 pixels sur un axe et le viewwindow décallé dans le sens opposé, les pixels nullifiés seront donc dessiné en dehors de l'écran.
J'espère que c'est plus clair comme ça !
Citer : Posté le 28/12/2024 21:49 | #
Maintenant que je le vois, le 3x3 est plus raisonnable que je le pensais. Hmm... je suis curieux maintenant de voir jusqu'où ça peut marcher !
Et pourtant d'après les archives tu as déjà test le jeu ya a peine 10 ans
https://www.planet-casio.com/Fr/programmes/tests_programmes.php?showid=2690#410
Citer : Posté le 28/12/2024 23:05 | #
Je n'ai plus le programme du premier CasioCraft et n'ai pas de cable sous la main pour le DL.
Mais de mémoire les blocs étaient espacés de 2 pixels et les écrans faisaient 25x10 (ou 25x11). Cela me permettait d'effacer un bloc en une seule instruction Text " ".
Comparaison de densité entre la V1 et ce que j'aimerai faire :
Citer : Posté le 30/12/2024 01:01 | #
J'ai fait un test rapide aujourd'hui avec Text et String, la vitesse est déconcertante.
Sans effort particulier j'ai décompression + affichage en 15s. J'adorerai réussir à faire marcher le DrawStat, mais je ne pense pas pouvoir le rendre assez rapide. Text est parfait pour les contraintes que j'ai : blocs de 3x3 espacés de 1 pixel (en même temps, ces contraintes viennent de l'utilisation de Text dans la V1 et la V1.1).
Le gros problème de Text c'est la palette extrémement limité qu'il permet d'afficher. Voilà tous les caractères 3x3 à peu près utilisable que j'ai trouvé :
Les 10 premiers me semblent pas mal comme blocs de base, les 4 suivants pourraient être utilisés comme blocs "orientables", pour de la déco potentiellement.
La deuxième ligne avec tous les petits traits je ne sais pas trop, j'en utiliserai surement un pour représenter les blocs cachés.
La troisième ligne me parait complexe à exploiter, les blocs n'ont aucune symétrie et n'ont pas de version miroire.
J'ai très envie d'utiliser le dernier bloc, mais il s'agit de 2 caractères ("ii") et non un seul. Je ne l'ajouterai que si je trouve un moyen de le gérer rapidement, ce serait dommage de ralentir le chargement de tout un écran juste pour la texture d'un bloc assez rare.
Enfin... avec une résolution si basse c'est peu être pas plus mal d'avoir peu de type de bloc (à voir à quel point le gameplay en patira), mais le DrawStat offre tout de même une liberté qui est de difficile à abandonner.
Je vais revoir du coté du Super/Multi DrawStat, mais des quelques tests que j'ai fait il me semble que Graph(X,Y) est plus rapide que DrawStat pour dessiner des lignes mais plus lent pour dessiner des points. Malheureusement vu la taille des blocs, je ne pense pas que les lignes fassent gagner énormément de temps. Mais, tout de même, vu la plus grande flexibilité de Graph(X,Y) en terme d'input, il est possible qu'il s'adapte plus facilement au format des données decompressées (ou compressées) sans transformations trop couteuses.
Petite question sur les listes, est ce qu'il y a une fonction (ou astuce) pour obtenir rapidement une subrange, un peu comme les fonctions StrLeft/StrRight/StrMid, ou je dois copier chaque élément dans une nouvelle liste ?
Citer : Posté le 30/12/2024 09:36 | #
Bon courage, tu es en territoire assez peu exploré à pousser les perfs comme ça !
Avec Seq():
Citer : Posté le 30/12/2024 10:32 | #
Why not use Text, then drawstat on top?
it would be more complex, but should give the best speed/quality
using Text
printing 16 strings of 32 characters, should only take at most 2sec
or maybe two sets of drawstats
one using Square for the 3x3 squares
and a second with Dot for everything else
I do like the idea of using the matrix for decompression
you can copy over the binary and/or/xor/shift using C.Basic
however in practice they are extremely slow
so not very useful
you might be able to copy the list to a matrix and operate on it in there some way
but I don't have any knowledge of being able to
Citer : Posté le 30/12/2024 17:14 | #
I'm considering this option: doing a "rough pass" with Text for all the basic blocs and a second with DrawStat for less frequent, more detailed blocks, like Minecraft "tile entities".
Drawing 16 strings is extremely quick, the expensive part is the decompression and building the strings.
I like the idea of having two sets of DrawStat, one with Square, and the other with Dot.
I will experiment with the following pipeline:
- get all the compressed integers with Seq
- decompress with a matrix multiplication
- build the list of blocks with a 3x3 outline (dirt and stone)
- build the list of blocks with a central dot (dirt and hidden blocks)
- build the list of all other non-air blocks (coordinates + bitmaps)
- DrawStat with Square the list dirt+stone
- DrawStat with Dot the list dirt+hidden
- then 9 DrawStat to draw all the bitmaps of all other blocks
The first 2 DrawStat should take care of most of the screen, leaving the more expensive "bitmap DrawStat" with fewer blocks to cover.
Citer : Posté le 02/01/2025 14:24 | #
Bon, j'ai beau essayer encore et encore, je ne trouve pas de méthode avec DrawStat qui descende sous les 20 secondes. Quel que soit le format et la décompression utilisés, construire les listes et l'affichage sont très lent.
Je vais concentrer mes efforts sur Text+Str.
Par contre les String semblent bien capricieux. De ce que j'observe, leur taille est capée à 255 caractères "simples" (encodable sur 1 octet), ce qui parait étonnant dans un système BCD où les listes et matrices sont limités à 999. Bien sûr ce nombre est plus faible si j'utilise des symboles plus complexes. Autre limitation que je n'avais jamais remarqué avant : Text n'affiche que les 32 premiers caractères d'un String. J'imagine que c'est une optimisation liée au fait que pour des caractères de 4 pixels de large, Text ne peut afficher que 32 caractères sur la largeur d'un écran. Text ne peut pas avoir une origine négative, mais cette coupure est tout de même visible si on choisit des symboles qui s'affichent sur moins de 4 pixels de large, par exemple le "i" minuscule ne prend que 2 pixels de large.
Je pense partir sur un système "hybride", avec Text pour tous les blocs de base et DrawStat pour des blocs spéciaux et plus détaillés (certainement les coffres, peut être les plantes de culture...). Malgré la vitesse de Text, décompresser et itérer 40x12 blocs reste très lent (plus de 15s)...
Je pense laisser tomber la décompression et directement utiliser la donnée compressée. Je suis aussi en train d'expérimenter à ajouter 1 chiffre BCD de metadata aux entiers compressés pour indiquer une "palette" :
- 0 : aucune palette (niveau 0) est appliquée, tous les blocs sont affichable mais je dois décompresser les 8 blocs
- de 1 à 4 : une palette de 3 blocs (niveau 1) est utilisée, ce qui permet d'itérer 2 par 2 les blocs
- de 5 à 9 : une palette de 1 bloc (niveau 2) est utilisée, ce qui permet de skip les 8 blocs d'un coup
Dans le cas où tous les "chunks" d'un écran utilisent une palette à 1 bloc, l'affichage se fait en 4s. Un écran classique mélant les 3 niveaux de palette devraient s'afficher, je l'espère en moins de 10s.
Sinon, à propos de Seq, ces expressions ne marchent pas :
- Seq(MOD(I, 2), I, 1, 8, 1)
- Seq(Mat A[I, 1], I, 1, 8, 1)
il semble que le parsing du premier argument s'arrête à la première virgule et non à la fin d'une expression. Je me demande si c'est voulu, ou si c'est un bug et si il affecte d'autres fonctions.