commit 603d9cba2e900619a4483b204eac4184d582c8e1
parent 7207ae1c02403917ef9066ba08756055cc8f4d69
Author: Jean Schurger <jean@schurger.org>
Date: Wed, 4 Nov 2026 16:49:57 -0500
Couleurs TMS3556 : fond de zone par délimiteurs (d'après MAME)
Le port dsp-cpp du VDP TMS3556 utilisait un fond global au lieu du fond de zone
porté par des cellules délimiteur (code $20), et testait le mode alphanumérique
sur le mauvais registre. En vidéo inverse le premier plan doit devenir le fond de
zone, pas le fond global — d'où le noir sur noir des menus (Exeldrums) et les
couleurs fausses (Imagix). draw_line_text_common réécrite d'après MAME
tms3556.cpp (délimiteurs, zone_bg, zones masquées). Corrige Exeldrums (surbrillance
verte) et Imagix (titre correct) ; Capitaine Menkar, Tennis, Guppy, ExelBASIC
inchangés. Correctifs source regroupés dans patches/exl100.patch (appliqué par
git apply au lieu des sed).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QyJB9yqUAYm3wRQi4uytj9
Diffstat:
2 files changed, 112 insertions(+), 10 deletions(-)
diff --git a/Makefile b/Makefile
@@ -39,16 +39,11 @@ build: upstream cartridges web/index.html
web/index.html: $(wildcard roms/*) roms/cartridges/titles.json web/shell.html.in Makefile
cp web/shell.html.in upstream/web/shell.html.in
- # Correctif de portabilité Emscripten : mpeg_audio.cpp (musique Sega Model 3, hors sujet ici)
- # appelle abort() sans inclure <cstdlib>, que la libc++ d'Emscripten ne fournit pas en transitif.
- grep -q '<cstdlib>' upstream/src/sound/mpeg_audio.cpp || \
- sed -i 's|#include <cstring>|#include <cstring>\n#include <cstdlib>|' upstream/src/sound/mpeg_audio.cpp
- # Correctif son (parole trop rapide) : dans on_main_cycles la puce TMS5220 était avancée deux
- # fois — par speech_.tick(cycles CPU) ET par speech_.update() (qui l'avance déjà au bon rythme
- # 640 kHz). Le tick redondant la faisait tourner ~4,8x trop vite. On le retire ; io_ready reste
- # décompté par les ticks-horloge de update() (16 horloges-puce ≈ 25 µs, le vrai timing).
- grep -q 'exl100_patch_speech' upstream/src/drivers/computers/exelv.cpp || \
- sed -i 's|^ speech_.tick(cycles);| /* exl100_patch_speech: pilotée par update(), ce tick(cycles CPU) la faisait tourner ~4,8x trop vite */|' upstream/src/drivers/computers/exelv.cpp
+ # Correctifs source appliqués au clone amont (patches/exl100.patch) : portabilité Emscripten de
+ # mpeg_audio.cpp (abort sans <cstdlib>), son de la parole TMS5220 (double avance ~4,8x trop vite),
+ # et couleurs du TMS3556 (fond de zone par délimiteurs, d'après MAME : corrige le noir sur noir
+ # des menus comme Exeldrums et les attributs couleur). Voir patches/exl100.patch.
+ grep -q 'zone_bg' upstream/src/video/tms3556.cpp || git -C upstream apply $(CURDIR)/patches/exl100.patch
$(DOCKER) emcmake cmake -S /work/upstream -B /work/build-web $(CMFLAGS)
$(DOCKER) cmake --build /work/build-web -j --target dsp
mkdir -p web
diff --git a/patches/exl100.patch b/patches/exl100.patch
@@ -0,0 +1,107 @@
+diff --git a/src/drivers/computers/exelv.cpp b/src/drivers/computers/exelv.cpp
+index debcb94..69b25ef 100644
+--- a/src/drivers/computers/exelv.cpp
++++ b/src/drivers/computers/exelv.cpp
+@@ -519,7 +519,7 @@ void Exelv::on_main_cycles(int cycles) {
+ main_cycles_ += uint64_t(cycles);
+ if (tape_fast_ && tape_playing_ && tape_.loaded()) fast_load_hook();
+ if (tape_playing_ && tape_.loaded() && in_tape_read()) tape_.advance(cycles);
+- speech_.tick(cycles);
++ /* exl100_patch_speech: pilotée par update(), ce tick(cycles CPU) la faisait tourner ~4,8x trop vite */
+ const uint32_t cpu_clock = maincpu_.cpu_clock();
+ audio_accumulator_ += int64_t(cycles) * kSampleRate;
+ while (audio_accumulator_ >= cpu_clock) {
+diff --git a/src/sound/mpeg_audio.cpp b/src/sound/mpeg_audio.cpp
+index ab4d563..7f2f930 100644
+--- a/src/sound/mpeg_audio.cpp
++++ b/src/sound/mpeg_audio.cpp
+@@ -11,6 +11,7 @@
+ #include <cmath>
+ #include <cassert>
+ #include <cstring>
++#include <cstdlib>
+
+
+
+diff --git a/src/video/tms3556.cpp b/src/video/tms3556.cpp
+index 288aa7e..49487a3 100644
+--- a/src/video/tms3556.cpp
++++ b/src/video/tms3556.cpp
+@@ -142,7 +142,15 @@ void Tms3556::draw_line_text_common(uint32_t* ln) {
+ uint16_t patterntbl_base[4];
+ for (int i = 0; i < 4; i++) patterntbl_base[i] = address_regs_[i + 3];
+
+- const uint32_t border = rgb3(uint8_t(bg_color_));
++ // Attributs « série » du TMS3556 (d'après MAME tms3556.cpp) : la couleur de
++ // fond est portée par des cellules délimiteur (code caractère $20), pas par un
++ // fond global. En vidéo inverse le premier plan devient ce fond de zone ; c'est
++ // ce qui manquait (d'où le noir sur noir des menus, p. ex. Exeldrums).
++ const uint8_t margin_color = uint8_t((control_regs_[7] >> 5) & 0x7); // VDP_CM4
++ uint8_t zone_bg = margin_color;
++ bool zone_masked = (control_regs_[7] & 0x08) != 0;
++
++ const uint32_t border = rgb3(margin_color);
+ int xx = 0;
+ for (; xx < kLeftBorder; xx++) *ln++ = border;
+
+@@ -151,32 +159,39 @@ void Tms3556::draw_line_text_common(uint32_t* ln) {
+ for (int x = 0; x < 40; x++) {
+ const int name_hi = read_byte(uint16_t(nametbl_base + name_offset));
+ const int name_lo = read_byte(uint16_t(nametbl_base + name_offset + 1));
+- const int pattern_ix = ((name_hi >> 2) & 2) | ((name_hi >> 4) & 1);
+- const bool alphanumeric = (pattern_ix < 2) || ((pattern_ix == 3) && !(control_regs_[7] & 0x08));
+- uint8_t fg = uint8_t((name_hi >> 5) & 0x7);
++ uint8_t fg;
+ uint8_t bg;
+ int dbl_w = 0;
+ int dbl_h = 0;
++ uint8_t pattern;
++ if ((name_lo & 0x7f) == 0x20) {
++ // Délimiteur : fixe le fond de la zone qui suit, affiché dans sa couleur.
++ fg = uint8_t((name_hi >> 5) & 0x7);
++ if (zone_masked && (control_regs_[5] & 0x20)) fg = zone_bg;
++ zone_bg = uint8_t(name_hi & 0x7);
++ zone_masked = (name_hi & 0x08) != 0;
++ bg = zone_bg;
++ pattern = 0xff;
++ if (char_line_counter_ == 0) dbl_h_phase_[x] = 0;
++ } else {
++ const int pattern_ix = ((name_hi >> 2) & 2) | ((name_hi >> 4) & 1);
++ const bool alphanumeric = (pattern_ix < 2) || ((pattern_ix == 3) && !(control_regs_[5] & 0x08));
++ fg = uint8_t((name_hi >> 5) & 0x7);
+ if (alphanumeric) {
+ if (name_hi & 4) {
+ bg = fg;
+- fg = uint8_t(bg_color_);
++ fg = zone_bg;
+ } else {
+- bg = uint8_t(bg_color_);
++ bg = zone_bg;
+ }
+- // Software never pairs inverse video with double width: EXELTEL
+- // highlights menu entries with $1E and Capitaine Menkar draws
+- // $1F cells, both inverse with bit 1 set and no duplicated
+- // characters, which double width (left half / right half of
+- // consecutive cells) needs. Only honour bit 1 on normal video.
+- dbl_w = (name_hi & 0x4) ? 0 : (name_hi & 0x2);
++ dbl_w = name_hi & 0x2;
+ dbl_h = name_hi & 0x1;
+ } else {
+ bg = uint8_t(name_hi & 0x7);
++ zone_bg = bg; // le fond mosaïque est un attribut série
+ }
+ if ((name_lo & 0x80) && blink_) fg = bg;
+
+- uint8_t pattern;
+ if (!dbl_h) {
+ pattern = read_byte(uint16_t(patterntbl_base[pattern_ix] + (name_lo & 0x7f) +
+ 128 * char_line_counter_));
+@@ -190,6 +205,8 @@ void Tms3556::draw_line_text_common(uint32_t* ln) {
+ 128 * (char_line_counter_ >> 1)));
+ if (char_line_counter_ == 0) dbl_h_phase_[x] = !dbl_h_phase_[x];
+ }
++ if (zone_masked && (control_regs_[5] & 0x20)) pattern = 0; // zone masquée : espaces
++ }
+
+ if (!dbl_w) {
+ for (int bit = 0; bit < 8; bit++) {