Émulateur fidèle de la console portable Entex Adventure Vision (1982), la seule console à affichage LED à miroir rotatif jamais commercialisée.
| Composant | Spécification |
|---|---|
| CPU | Intel 8048 @ 733 KHz (11 MHz ÷ 15) |
| RAM interne | 64 octets IRAM |
| RAM externe | 4 × 256 octets XRAM (banques via P1.0-P1.1) |
| ROM BIOS | 1 Ko (interne 8048) |
| ROM cartouche | 4 Ko max (adressage via P2.0-P2.3) |
| Affichage | 150 × 40 pixels LED rouges, miroir rotatif 15 fps |
| Son | COP411L @ ~54,4 KHz, DAC 2 bits, 13 effets + 16 notes |
| Entrées | Croix directionnelle + 4 boutons (matrice P1.3-P1.7) |
- Savestate COP411L complet : phase_acc, phase_inc, cur_step, step_samples_left, segments — reprise audio bit-exacte
- Mode scan mid-frame (F3) : capture VRAM en temps réel pendant l'exécution CPU, reproduisant le balayage progressif du miroir physique
- Timing configurable : T1_PULSE_START/END paramétrable dans
advision.inipour ajustement sur mesures hardware
- 3 profils audio (F4 pour cycler) : Raw (brut), Speaker (passe-bas ~4kHz + soft clip tanh), Headphone (~8kHz, sans distorsion)
- Filtre passe-bas configurable par profil avec saturation douce simulant le petit haut-parleur
- Gamma LED configurable dans
advision.ini(0.2–3.0, défaut 1.0) - Phosphor decay configurable (0.0–1.0, défaut 0.45)
- Effet scanlines (F9) : assombrissement alterné des lignes LED
- Integer scaling (F6) : mise à l'échelle en multiples entiers stricts
- Overlay statistiques (touche `) : FPS mesuré, cycles CPU, pixels allumés
- Suite de tests intégrée (
--test) : 19 tests couvrant CPU (MOV, ADD, carry, JMP, DJNZ, DAA, PSW, ISR), timer/prescaler, interruptions (DIS TCNTI, latch overflow, Figure 11 Note 1), décodage LED, COP411L (init, tone, noise), persistance phosphore, round-trip savestate - Mode headless enrichi :
--frames N,--input UDLR1234,--dump(ASCII art VRAM) - Dump VRAM ASCII : visualisation texte du framebuffer pour debug et tests automatisés
- Rewind (F8), enregistrement WAV (F2), capture d'écran BMP (F12)
- Drag & drop ROM, fichier
advision.ini, CLI étendue - Portabilité MSVC, indices de contrôle par jeu dans le menu
| Jeu | Année | Genre | Contrôles |
|---|---|---|---|
| Defender | 1982 | Shoot horizontal | Z:tir X:poussée A:bombe |
| Super Cobra | 1982 | Shoot horizontal | Z:tir X:bombe |
| Space Force | 1982 | Tir fixe | Z:tir |
| Turtles | 1982 | Labyrinthe | Flèches Z:boîte |
| Table Tennis | 2020 | Sport/Pong (homebrew, Ben Larson) | ↑↓:raquette Z:service |
# Standard (SDL2)
gcc -O2 -DUSE_SDL -o advision adventure_vision.c -lSDL2 -lm
# Complet (ROMs + jaquettes intégrées)
gcc -O2 -DUSE_SDL -DEMBED_ROMS -DEMBED_COVERS -o advision adventure_vision.c -lSDL2 -lm
# Headless (tests, automatisation)
gcc -O2 -o advision adventure_vision.c -lm
# MSVC (Windows)
cl /O2 /DUSE_SDL adventure_vision.c SDL2.lib SDL2main.lib./advision # Sélecteur de jeux
./advision bios.rom game.rom # Chargement direct
./advision --fullscreen --volume 8 # Avec options
./advision --test # Suite de tests
./advision --frames 120 --dump bios.rom game.rom # Headless + dump| Touche | Action |
|---|---|
| ↑ ↓ ← → | Croix directionnelle |
| Z / X / A / S | Boutons 1-4 |
| P | Pause |
| R | Reset |
| +/- | Volume |
| ` | Overlay statistiques (FPS/cycles/pixels) |
| F2 | Enregistrement WAV on/off |
| F3 | Mode scan mid-frame on/off |
| F4 | Cycler profil audio (Raw→Speaker→Headphone) |
| F5 / F7 | Sauvegarder / Charger état |
| F6 | Integer scaling on/off |
| F8 | Rewind (retour en arrière) |
| F9 | Scanlines on/off |
| F11 | Plein écran |
| F12 | Capture d'écran BMP |
| Échap | Menu / Quitter |
| Double-clic | Basculer plein écran |
[advision]
volume=7
fullscreen=0
scale=0
audio_profile=1 # 0=Raw 1=Speaker 2=Headphone
gamma=1.00 # LED gamma (0.2-3.0)
phosphor=0.45 # Persistance POV (0.0-1.0)
scanlines=0
integer_scale=0
# Timing (avancé — ajuster si mesures hardware disponibles)
t1_pulse_start=200
t1_pulse_end=400$ ./advision --test
=== Adventure Vision Self-Test Suite ===
State saved.
State loaded.
13 passed, 0 failed (13 total)
Tests couverts : MOV A, ADD carry, JMP, DJNZ loop, DAA (BCD), timer prescaler/overflow, COP411L init/tone/noise, persistance phosphore, round-trip savestate complet.
- Thread safety : tout accès à
av->snd(save/load state, rewind push/pop) est maintenant sousSDL_LockAudioDevice; macrosAUDIO_LOCK/AUDIO_UNLOCKpour cohérence - WAV ring buffer :
audio_cbécrit dans un tampon annulaire (8192 samples), vidé côté thread principal — plus aucune E/S disque dans le thread audio - Préservation de l'état persistant : le retour au menu ne perd plus
rewind_buf,wav, volume, gamma, config ; plus de fuite mémoire ni de fichier WAV orphelin - load_file robuste : avertissement explicite si ROM tronquée, échec sur lecture partielle
- CLI sûre :
atoiremplacé parstrtolavec validation complète - Pas de deadlock : les verrous internes redondants dans
load_statesont supprimés (l'appelant verrouille)
- Retour capture XRAM directe : le pipeline des registres LED (v15.3) introduisait un décalage horizontal systématique. La capture XRAM en fin de trame est rétablie comme méthode d'affichage principale. Le code des registres LED est conservé pour référence mais désactivé.
- Scanlines dans le framebuffer : l'effet scanlines est maintenant appliqué directement dans le tampon image (plus propre, pas de discordance de coordonnées SDL).
- Table de décodage LED corrigée : les bits P2.5-P2.7 étaient interprétés dans le mauvais ordre depuis v15.3, causant 3/5 registres LED mappés vers le mauvais index et un registre manquant (sel=3 non décodé). Le BIOS utilise P2=0x20→0xA0 correspondant à sel=1→5, le mapping correct est simplement
sel-1. Corrige l'affichage écrasé/déformé. - Sécurité null :
av_led_latch()vérifie le pointeur NULL pour compatibilité avec les self-tests. - Test de régression : nouveau test vérifiant les 5 valeurs P2 du BIOS + 2 valeurs invalides.
- DIS TCNTI efface l'IRQ en attente : l'instruction
DIS TCNTI(0x35) remet à zéro la bascule d'interruption timer. Doc : "A pending interrupt request is cleared." - Séparation enable externe/timer : la bascule d'overflow timer est conditionnée par
tcnti_en && !in_irq, sans vérifierirq_en(enable externe). Note 1 Figure 11 : "Overflow FF will NOT store any overflow" pendant une ISR. Timer Flag (JTF) est toujours activé.
- PSW bit 3 = 1 : bit 3 forcé à 1 lors de toute lecture. Doc : "Bit 3 of the PSW is unused and is always set to one."
- PC bit 11 = 0 pendant ISR :
JMP/CALLen ISR ne peuvent plus activer le bit 11 (memory bank). Doc : "During servicing of an interrupt, PC bit 11 is held at zero."
- Volume/profil audio : les touches +/−/F4 utilisent maintenant
AUDIO_LOCKpour synchroniser l'accès àsnd_volumeetaudio_profileavec le fil audio SDL. - WAV flush thread-safe : nouvelle fonction
wav_flush_safe()utilisant le pattern copie-sous-verrou : snapshot du ring buffer sousAUDIO_LOCK, écriture disque hors verrou. Élimine la condition de compétition sans risquer de coupures audio. - Suppression de
volatile:snd_volumeetring_wrne dépendent plus devolatile(insuffisant pour la synchronisation inter-fils), remplacés par une protection systématique viaAUDIO_LOCK.
- WAV little-endian : fonctions
wav_le16()/wav_le32()pour écriture explicite LE dans l'en-tête et la finalisation WAV. Corrige les fichiers invalides sur architecture big-endian. - Savestate steps : sérialisation champ-par-champ de
SndStep(float freq, bool noise, int dur_ms, float volume) au lieu defwritede structure brute. Élimine les problèmes de padding/alignement entre compilateurs. SAVE_VER → 19. - Fichier temp portable : le self-test utilise
av_test_tmp.savau lieu de/tmp/av_test.sav.
load_file()nettoie le buffer :memset(0xFF)avant lecture, évite les données résiduelles si un ROM plus court est chargé après un plus long.- Config T1 : validation différée : la vérification
t1_pulse_start < t1_pulse_endest maintenant effectuée après lecture complète du fichier INI (évite une fausse alerte si les valeurs apparaissent dans un ordre quelconque).
<strings.h>inclus pourstrcasecmpsur POSIX.isfiniteMSVC : alias vers_finitepour compatibilité MSVC.- Zéro warning
-Wshadowconfirmé.
- 8 nouveaux tests (12–19) : PSW bit 3, DIS TCNTI, timer overflow latch (irq_en=0 et ISR), JMP en ISR, décodage LED complet. Total : 19 tests.
Note : le pipeline d'affichage LED a été désactivé en v15.4 (décalage horizontal systématique). La capture XRAM directe est utilisée. Le code LED est conservé pour référence.
- Registres LED matériels : émulation des 5 registres LED 8 bits du hardware réel. Chaque registre contrôle 8 LEDs (40 au total). L'écriture se fait comme effet de bord de la lecture XRAM (MOVX A,@Rr), exactement comme sur le vrai hardware
- Décodage adresse P2.5-P2.7 : sélection des registres LED par les bits 5-7 du port P2 :
100→reg0 (LEDs 1-8),010→reg1 (9-16),110→reg2 (17-24),001→reg3 (25-32),101→reg4 (33-40) - Strobe P2.4 : front montant de P2.4 = latch des registres LED vers la colonne d'affichage courante. Synchronisation colonne-par-colonne identique au BIOS réel
- Compteur de colonnes : remis à zéro sur le front montant T1 (sync miroir), puis incrémenté par chaque strobe P2.4 — timing cycle-exact
- Mode hybride : si le BIOS utilise les registres LED (P2.4 détecté), ils sont prioritaires. Sinon fallback vers lecture directe XRAM (compatibilité homebrews)
- Délai post-EI : l'instruction EI (0x05) impose un délai d'1 instruction avant que les IRQ soient acceptées (comportement hardware MCS-48 documenté)
- Dispatch IRQ : vérification du compteur
ei_delayavant dispatch — corrige un race condition potentiel entre EI et timer overflow
- P2 tracking :
prev_p2sauvegardé pour détection de fronts (P2.4 strobe, protocole son) - État transient : les registres LED et le compteur de colonnes sont réinitialisés à chaque début de trame
- Restauration savestate :
prev_p2synchronisé avec P2 CPU après chargement
- Horloge CPU : 737280 → 733333 Hz (11 MHz ÷ 15 exact, doc §1.0) — 0.54% plus précis
- Cycles/frame : 49152 → 48889 (division corrigée)
- Timer prescaler : réinitialisé sur
STRT T,STRT CNT,STOP TCNTetMOV T,A(MCS-48 manual + MAME)
- Registre de contrôle : bits 0/3 inversés — bit 0 = fast/slow, bit 3 = loop (doc §6.1-6.2)
- Fréquences des tons : remplacées par les fréquences nominales mesurées sur le hardware (doc §6.2, table Freq Nominal) au lieu du tempérament égal
- Durées des segments : seg1 et seg2 ont des durées distinctes (doc §6.2 : fast=0 → 117ms/240ms, fast=1 → 46ms/104ms)
- Protocole son : machine d'état 4 états, accepte toutes les valeurs de commande y compris $00/$C0 (routine BIOS $03A9)
- Scan miroir sync-aware : les colonnes sont capturées dans une fenêtre de ~2550 cycles après la fin du pulse T1 (front montant), au lieu d'être réparties linéairement sur toute la trame
- Mid-frame scan par défaut : activé par défaut pour plus de précision
- Savestate OOB (critique) :
cur_step,step_count,segmentvalidés après chargement — un.savmalveillant ne peut plus provoquer d'accès hors bornes danssteps[16] - Savestate NaN/Inf :
cur_freq,cur_vol,seg1_vol,seg2_volet toutes les fréquences/volumes des steps rejetés si non finis - Savestate portabilité :
sizeof(bool)etsizeof(int)remplacés par types à largeur fixe (uint8_t,int32_t). Steps sérialisés champ-par-champ depuis SAVE_VER 19 - Garde OOB runtime :
cop411_sample()vérifiecur_step < MAX_SND_STEPSmême en fonctionnement normal (défense en profondeur) - CLI
--volume: n'est plus écrasé silencieusement paradvision.ini(appliqué aprèsconfig_load) - Integer scaling : le flag F6 fonctionne réellement — calcul en pixels natifs, letterbox centré, restauration du mode logique
- Texture statique : suivi du renderer pour invalidation si le contexte GPU change ; fuite mémoire éliminée
- LUT gamma :
powf()éliminé de la boucle chaude render (table 256 entrées, recalculée uniquement si gamma change) - WAV batch writes :
wav_flushécrit par segments contigus au lieu de sample-par-sample ; détection d'overflow ring buffer - T1 pulse :
t1_pulse_start >= t1_pulse_endrejeté → plus de boucle infinie BIOS - Config INI :
gammaetphosphorrejettentNaN/Infviaisfinite()
Émulateur mono-fichier C (~3650 lignes), zéro dépendance externe hors SDL2.
| Module | Lignes | Description |
|---|---|---|
| Intel 8048 | ~450 | CPU cycle-exact, 105 opcodes, timer/IRQ |
| COP411L | ~300 | Son comportemental, LFSR 15 bits, 3 profils audio |
| Display | ~100 | Rendu LED POV, gamma, scanlines, phosphor configurable |
| Rewind | ~80 | Buffer circulaire 120 snapshots |
| Save/Load | ~120 | Savestate complet (CPU + COP411L playback) |
| Self-test | ~210 | 19 tests unitaires intégrés |
| Menu | ~500 | Sélecteur, jaquettes, infos, contrôles par jeu |
| Config | ~80 | INI persistant avec timing avancé |