Fix streaming completion timing on CD-ROM reads and CLIO input DMA

Wing Commander III - Heart of the Tiger (USA, Disc 1) could stall during
its credits or opening movies because streaming completion became visible
at the wrong boundaries.

Publish successful MEI READ_DATA status only after the final requested
byte has left the data FIFO. Reporting success as soon as one sector is
buffered prematurely ends Portfolio's read-ahead command, even while data
from the request remains unread.

Raise CLIO input-DMA completion and reload the next buffer when the final
sample is consumed, rather than waiting for another DSP read. Wing
Commander III can otherwise observe the end address and detach an audio
attachment whose completion has not fired. Once the instrument stops,
there is no further read to generate completion and playback waits
forever.

During investigation, stock-2x playback advanced through the previously
stuck credits and opening movie sequence to the interactive hangar. This
addresses the observed streaming stalls, not an exhaustive compatibility
claim for every gameplay or disc-transition path.

Also default CD-ROM speed to the stock 2x rate in both option definitions
and the missing-option fallback. This affects all titles using the
default; explicit unbounded and other saved speed selections remain
available and unchanged.
This commit is contained in:
Antonio SJ Musumeci authored and trapexit committed 2026-10-22 21:45:34 -05:00
1 parent 684926fe20
commit 39bf0700c1
4 files changed
+48 -40

No files matched your search

+5 -3
View File
@@ -859,8 +859,10 @@ static
void
cdrom_data_publish_read_status_if_ready(cdrom_device_t *cd_)
{
/* MEI DIPIR consumes read data only when data is ready with the status. */
if(!g_CDROM_STATE.data.read_status_pending || (cd_->data_len == 0))
// Portfolio keeps read-ahead active until the transfer, not a sector, completes.
if(!g_CDROM_STATE.data.read_status_pending ||
(cd_->blocks_requested != 0) ||
(cd_->data_len != 0))
return;
g_CDROM_STATE.data.read_status_pending = false;
@@ -1150,7 +1152,6 @@ cdrom_data_load_next_if_ready(cdrom_device_t *cd_)
cd_->blocks_requested--;
cd_->poll |= POLDT;
g_CDROM_STATE.data.sectors_queued++;
cdrom_data_publish_read_status_if_ready(cd_);
if(audio_burst)
g_CDROM_STATE.data.next_ready_cycle = cycles;
@@ -4363,6 +4364,7 @@ opera_cdrom_fifo_get_data(cdrom_device_t *cd_)
cd_->blocks_requested = 0;
cd_->data_len = 0;
cd_->data_idx = 0;
cdrom_data_publish_read_status_if_ready(cd_);
}
}
}
+34 -34
View File
@@ -40,6 +40,7 @@
#include "boolean.h"
#include "prng32.h"
#include <assert.h>
#include <string.h>
#define DECREMENT 0x1
@@ -1098,50 +1099,49 @@ dma_channel_enabled(uint32_t const channel_)
return (CLIO.regs[0x304] & (1 << channel_));
}
static
void
_clio_fifo_ei_complete(uint16_t channel_)
{
clio_fifo_t *fifo = &CLIO.fifo_i[channel_];
assert(fifo->idx >= fifo->start.len);
fifo->idx = 0;
opera_clio_fiq_generate((1U << (channel_ + 16)),0);
// Reload is enabled by the channel request bit (WO1994010641A1, p. 49).
if((fifo->next.addr != 0) && dma_channel_enabled(channel_))
fifo->start = fifo->next;
else
fifo->start.addr = 0;
}
uint16_t
opera_clio_fifo_ei(uint16_t channel_)
{
uint16_t val_;
uint16_t val;
clio_fifo_t *fifo;
if(CLIO.fifo_i[channel_].start.addr == 0)
assert(channel_ < (sizeof(CLIO.fifo_i) / sizeof(CLIO.fifo_i[0])));
fifo = &CLIO.fifo_i[channel_];
// A restored state can still have completion waiting on the next DSP read.
if((fifo->start.addr != 0) && (fifo->idx >= fifo->start.len))
_clio_fifo_ei_complete(channel_);
if(fifo->start.addr == 0)
return 0;
if((CLIO.fifo_i[channel_].start.len - CLIO.fifo_i[channel_].idx) > 0)
{
val_ = opera_clio_fifo_ei_read(channel_);
CLIO.fifo_i[channel_].idx += sizeof(uint16_t);
}
else
{
CLIO.fifo_i[channel_].idx = 0;
opera_clio_fiq_generate(1<<(channel_+16),0);
val = opera_clio_fifo_ei_read(channel_);
fifo->idx += sizeof(uint16_t);
/* reload enabled see patent WO09410641A1, 49.16 */
if((CLIO.fifo_i[channel_].next.addr != 0) && dma_channel_enabled(channel_))
{
CLIO.fifo_i[channel_].start.addr = CLIO.fifo_i[channel_].next.addr;
CLIO.fifo_i[channel_].start.len = CLIO.fifo_i[channel_].next.len;
// Complete before the CPU can observe the end address and detach the sample.
if(fifo->idx >= fifo->start.len)
_clio_fifo_ei_complete(channel_);
val_ = opera_clio_fifo_ei_read(channel_);
CLIO.fifo_i[channel_].idx += sizeof(uint16_t);
}
else
{
CLIO.fifo_i[channel_].start.addr = 0;
val_ = 0;
}
}
return val_;
// JMK SEZ: What is this? It was commented out along with this whole "else"
// block, but I had to bring this else block back from the dead
// in order to initialize val appropriately.
// opera_clio_fiq_generate(1<<(channel_+16),0);
return val;
}
void
opera_clio_fifo_eo(uint16_t channel_,
uint16_t val_)
+2 -2
View File
@@ -162,7 +162,7 @@ static struct retro_core_option_v2_definition option_defs_us_v2[] =
{ "32x", "32x - 4800KB/s" },
{ NULL, NULL },
},
"unbounded"
"2x"
},
{
"opera_region",
@@ -471,7 +471,7 @@ static struct retro_core_option_definition option_defs_us[] =
{ "32x", "32x - 4800KB/s" },
{ NULL, NULL },
},
"unbounded"
"2x"
},
{
"opera_region",
+7 -1
View File
@@ -493,7 +493,13 @@ opera_lr_opts_get_cd_speed(opera_lr_opts_t *opts_)
{
const char *val = getval("cd_speed");
if((val == NULL) || !strcmp(val,"unbounded"))
if(val == NULL)
{
opts_->cd_speed = 2;
return;
}
if(!strcmp(val,"unbounded"))
{
opts_->cd_speed = 0;
return;