diff --git a/recompiler/include/code_generator.h b/recompiler/include/code_generator.h index c8a18682..ca138830 100644 --- a/recompiler/include/code_generator.h +++ b/recompiler/include/code_generator.h @@ -90,6 +90,16 @@ struct CodeGenConfig { // FOV (identity at 4:3). Overlay-resident; see config_loader.h. Empty=default. std::set ws_backdrop_x_sites; + // Widescreen pure-2D background tile-loop widen ([widescreen.bg2d]). Three + // instruction addresses in a 2D game's per-layer BG renderer (MMX6's + // FUN_800270d0): the column-count load, the start tile-column mask, and the + // start screen-x shift, rewritten through the gpu.c psx_ws_mmx6_bg_* helpers + // so the loop draws the 16:9 reveal columns on both sides (identity at 4:3 / + // 512 hi-res). 0 = unset. Main-EXE addresses; verified by opcode at gen time. + uint32_t ws_bg2d_count_site = 0; + uint32_t ws_bg2d_startcol_site = 0; + uint32_t ws_bg2d_startx_site = 0; + // Persistent game-option init-store hooks ([persist_options] in // game_options.toml). Each entry is the PC of a boot-init sb/sh that writes a // config global's DEFAULT value; the store is rewritten to route the value diff --git a/recompiler/src/code_generator.cpp b/recompiler/src/code_generator.cpp index 08ab724b..6011d9a8 100644 --- a/recompiler/src/code_generator.cpp +++ b/recompiler/src/code_generator.cpp @@ -821,6 +821,48 @@ std::string CodeGenerator::translate_instruction(uint32_t addr, uint32_t instr) // overlay variant: addr is different code here — fall through to vanilla. } + // Widescreen pure-2D background tile-loop widen ([widescreen.bg2d]). MMX6's + // per-layer BG renderer draws `count` 16px tile columns from a start column / + // start screen-x derived from the camera scroll. Rewrite those three values + // (via the gpu.c psx_ws_mmx6_bg_* helpers — identity at 4:3 / 512 hi-res) so + // the loop draws the 16:9 reveal columns on both sides. Each site's opcode is + // verified; a mismatch is a loud build error (main-EXE addresses). + if (config_.ws_bg2d_count_site && addr == config_.ws_bg2d_count_site) { + if (opcode == 0x09 || opcode == 0x0D) { // addiu / ori (li rt,imm) + uint32_t rt = get_rt(instr); + uint16_t imm = get_imm16_u(instr); + return fmt::format("{} = (uint32_t)psx_ws_mmx6_bg_cols({});{}", + reg_name(rt), (int)imm, comment); + } else if (!config_.overlay_mode) { + fmt::print(stderr, "ERROR: [widescreen.bg2d] count_site 0x{:08X} is not " + "addiu/ori (opcode 0x{:02X})\n", addr, opcode); + std::exit(1); + } + } + if (config_.ws_bg2d_startcol_site && addr == config_.ws_bg2d_startcol_site) { + if (opcode == 0x0C) { // andi rt,rs,imm (start tile-column mask) + uint32_t rs = get_rs(instr), rt = get_rt(instr); + uint16_t imm = get_imm16_u(instr); + return fmt::format("{} = (uint32_t)psx_ws_mmx6_bg_startcol((int)({} & 0x{:X}u));{}", + reg_name(rt), reg_name(rs), imm, comment); + } else if (!config_.overlay_mode) { + fmt::print(stderr, "ERROR: [widescreen.bg2d] startcol_site 0x{:08X} is not " + "andi (opcode 0x{:02X})\n", addr, opcode); + std::exit(1); + } + } + if (config_.ws_bg2d_startx_site && addr == config_.ws_bg2d_startx_site) { + if (opcode == 0x00 && (instr & 0x3F) == 0x03) { // sra rd,rt,sa (start screen-x) + uint32_t rt = get_rt(instr), rd = get_rd(instr), sh = get_shamt(instr); + return fmt::format("{} = (uint32_t)psx_ws_mmx6_bg_startx((int32_t){} >> {});{}", + reg_name(rd), reg_name(rt), sh, comment); + } else if (!config_.overlay_mode) { + fmt::print(stderr, "ERROR: [widescreen.bg2d] startx_site 0x{:08X} is not " + "sra (instr 0x{:08X})\n", addr, instr); + std::exit(1); + } + } + // Persistent game-option init store ([persist_options] in game_options.toml). // The site is the boot-init sb/sh that writes a config global's DEFAULT value // (Tomba MESSAGE / SOUND / VIBRATION / ADJUST SCREEN). Route the stored value @@ -2018,6 +2060,9 @@ std::string CodeGenerator::generate_file( ss << "extern int psx_ws_x_margin(void); /* widescreen cull-margin term (gpu.c) */\n"; ss << "extern int psx_ws_cull_sltiu(uint32_t sx, uint32_t imm); /* ws auto screen-x cull (gpu.c) */\n"; ss << "extern int psx_ws_backdrop_x(int x); /* widescreen backdrop screenX squash (gpu.c) */\n"; + ss << "extern int psx_ws_mmx6_bg_cols(int base); /* ws 2D bg tile-loop widen: col count (gpu.c) */\n"; + ss << "extern int psx_ws_mmx6_bg_startcol(int col); /* ws 2D bg tile-loop widen: start tile col (gpu.c) */\n"; + ss << "extern int psx_ws_mmx6_bg_startx(int x); /* ws 2D bg tile-loop widen: start screen-x (gpu.c) */\n"; ss << "extern int psx_game_option_store(uint32_t addr, int val); /* persisted OPTION restore-at-init (game_options.c) */\n"; ss << "extern uint32_t psx_ws_backdrop_value(uint32_t orig, int is_end, int window_cols); /* ws backdrop preload (gpu.c) */\n"; ss << "extern void gte_ws_set_suppress(int on); /* widescreen far-backdrop un-squash (gte.cpp) */\n\n"; diff --git a/recompiler/src/config_loader.cpp b/recompiler/src/config_loader.cpp index 55ca7d45..fe699ada 100644 --- a/recompiler/src/config_loader.cpp +++ b/recompiler/src/config_loader.cpp @@ -572,6 +572,7 @@ GameConfig load_game_config(const fs::path& config_path_in) { std::vector ws_sprite_tag_funcs; uint32_t ws_sprite_anchor_addr = 0; bool ws_hud_sprt_squash = false; + bool ws_full_2d = false; if (cfg.contains("widescreen")) { const toml::value& ws = toml::find(cfg, "widescreen"); if (ws.contains("sprite_tag_funcs")) { @@ -591,6 +592,8 @@ GameConfig load_game_config(const fs::path& config_path_in) { config_path.string())); if (ws.contains("hud_sprt_squash")) ws_hud_sprt_squash = toml::find(ws, "hud_sprt_squash"); + if (ws.contains("full_2d")) + ws_full_2d = toml::find(ws, "full_2d"); } // Optional [widescreen.cull] block — world-space draw-cull widening. @@ -616,6 +619,24 @@ GameConfig load_game_config(const fs::path& config_path_in) { } } + // Optional [widescreen.bg2d] block — pure-2D background tile-loop widen. + uint32_t ws_bg2d_count_site = 0, ws_bg2d_startcol_site = 0, ws_bg2d_startx_site = 0; + if (cfg.contains("widescreen")) { + const toml::value& ws = toml::find(cfg, "widescreen"); + if (ws.contains("bg2d")) { + const toml::value& bg = toml::find(ws, "bg2d"); + auto load1 = [&](const char* key) -> uint32_t { + return bg.contains(key) + ? parse_hex(toml::find(bg, key), + fmt::format("widescreen.bg2d.{}", key)) + : 0u; + }; + ws_bg2d_count_site = load1("count_site"); + ws_bg2d_startcol_site = load1("startcol_site"); + ws_bg2d_startx_site = load1("startx_site"); + } + } + // Optional [widescreen.backdrop] block — parallax 2D backdrop screenX squash // (x_sites) + far-backdrop GTE un-squash (unsquash_funcs). std::vector ws_backdrop_x_sites; @@ -665,6 +686,10 @@ GameConfig load_game_config(const fs::path& config_path_in) { /*ws_backdrop_unsquash_funcs*/ ws_backdrop_unsquash_funcs, /*ws_auto_screen_x_cull*/ ws_auto_screen_x_cull, /*ws_auto_backdrop_preload*/ ws_auto_backdrop_preload, + /*ws_full_2d*/ ws_full_2d, + /*ws_bg2d_count_site*/ ws_bg2d_count_site, + /*ws_bg2d_startcol_site*/ ws_bg2d_startcol_site, + /*ws_bg2d_startx_site*/ ws_bg2d_startx_site, }; } diff --git a/recompiler/src/config_loader.h b/recompiler/src/config_loader.h index dbfe6fe0..2a798c1a 100644 --- a/recompiler/src/config_loader.h +++ b/recompiler/src/config_loader.h @@ -358,6 +358,30 @@ struct GameConfig { // are overlay-resident, so the overlay compile must see this (--ws-config). // 0 at 4:3 (native-wide inactive) => byte-identical. Off by default; regen. bool ws_auto_backdrop_preload = false; + + // [widescreen] full_2d — pure-2D sprite game (e.g. Mega Man X6) that never + // emits the sprite_tag hook the 3D detector relies on. When true, every + // in-game frame is treated as "gameplay" so native-wide engages and the 2D + // scene is presented widescreen (the framework's normal full-2D screens + // pillarbox 4:3, which is wrong for a game that IS 2D end-to-end). Runtime- + // only (read at startup; no codegen impact) — no regen required. The wider + // field of view itself is supplied by [widescreen.cull]/engine hooks; this + // flag only opts the title into the 2D widescreen present path. Off by + // default; the env var PSX_WS_FORCE_2D=1 forces it on for testing. + bool ws_full_2d = false; + + // [widescreen.bg2d] — pure-2D background tile-loop widen (e.g. MMX6's + // FUN_800270d0). Three instruction addresses in the per-layer BG renderer + // whose column count and loop start are rewritten so the loop draws the + // 16:9 reveal columns on both sides of the 320 view (see gpu.c + // psx_ws_mmx6_bg_* helpers — identity at 4:3 / 512 hi-res mode). Regen-class. + // count_site: the `li rt,21` column-count load (addiu/ori). + // startcol_site: the `andi rt,rs,0x3f` start tile-column mask. + // startx_site: the `sra rd,rt,sa` start screen-x. + // 0 = unset (feature off). Verified by opcode at gen time. + uint32_t ws_bg2d_count_site = 0; + uint32_t ws_bg2d_startcol_site = 0; + uint32_t ws_bg2d_startx_site = 0; }; // UserSettings — the launcher-written, user-editable override layer. diff --git a/recompiler/src/main_psx.cpp b/recompiler/src/main_psx.cpp index ff57ea53..0d677ae4 100644 --- a/recompiler/src/main_psx.cpp +++ b/recompiler/src/main_psx.cpp @@ -99,6 +99,8 @@ int main(int argc, char** argv) { bool ws_auto_screen_x_cull = false; // [widescreen.cull] auto_screen_x std::set persist_init_sites; // [persist_options] init-store hooks (game_options.toml) bool ws_auto_backdrop_preload = false; // [widescreen.cull] auto_backdrop + uint32_t ws_bg2d_count_site = 0, ws_bg2d_startcol_site = 0, + ws_bg2d_startx_site = 0; // [widescreen.bg2d] std::filesystem::path out_dir = "generated"; if (!config_path.empty()) { @@ -116,6 +118,9 @@ int main(int argc, char** argv) { ws_backdrop_unsquash.insert(cfg.ws_backdrop_unsquash_funcs.begin(), cfg.ws_backdrop_unsquash_funcs.end()); ws_auto_screen_x_cull = ws_auto_screen_x_cull || cfg.ws_auto_screen_x_cull; ws_auto_backdrop_preload = ws_auto_backdrop_preload || cfg.ws_auto_backdrop_preload; + if (cfg.ws_bg2d_count_site) ws_bg2d_count_site = cfg.ws_bg2d_count_site; + if (cfg.ws_bg2d_startcol_site) ws_bg2d_startcol_site = cfg.ws_bg2d_startcol_site; + if (cfg.ws_bg2d_startx_site) ws_bg2d_startx_site = cfg.ws_bg2d_startx_site; // [persist_options] init-store hook sites live in a dedicated // game_options.toml next to game.toml (the game's own native OPTION // settings, kept separate from game.toml/settings.toml). Best-effort: @@ -685,6 +690,12 @@ int main(int argc, char** argv) { codegen_config.ws_auto_screen_x_cull = ws_auto_screen_x_cull; codegen_config.persist_init_store_sites = persist_init_sites; codegen_config.ws_auto_backdrop_preload = ws_auto_backdrop_preload; + codegen_config.ws_bg2d_count_site = ws_bg2d_count_site; + codegen_config.ws_bg2d_startcol_site = ws_bg2d_startcol_site; + codegen_config.ws_bg2d_startx_site = ws_bg2d_startx_site; + if (ws_bg2d_count_site || ws_bg2d_startcol_site || ws_bg2d_startx_site) + fmt::print(" ws_bg2d 2D-background widen = ON (count=0x{:08X} startcol=0x{:08X} startx=0x{:08X})\n", + ws_bg2d_count_site, ws_bg2d_startcol_site, ws_bg2d_startx_site); if (ws_auto_screen_x_cull) fmt::print(" ws_auto_screen_x_cull = ON (render-funnel FOV widening)\n"); if (!persist_init_sites.empty()) diff --git a/runtime/include/gpu.h b/runtime/include/gpu.h index 68fedcad..9093c95d 100644 --- a/runtime/include/gpu.h +++ b/runtime/include/gpu.h @@ -95,6 +95,15 @@ void gpu_set_screen_kind(int kind); * (render the wider FOV into a wider frame, present 1:1; GTE not squashed). */ void gpu_ws_configure(int aspect_num, int aspect_den, uint32_t sprite_anchor_addr, int hud_sprt_squash, int mode); +/* [widescreen] full_2d: opt a pure-2D sprite game into the widescreen present + * path (treat every in-game frame as gameplay, since it never tags 3D prims). */ +void gpu_ws_set_full_2d(int on); +/* [widescreen.bg2d] MMX6 background tile-loop widen — hooked at the renderer's + * column-count / start-tile-col / start-screen-x instructions. Identity at 4:3 + * and in the engine's 512 hi-res mode. */ +int psx_ws_mmx6_bg_cols(int base); +int psx_ws_mmx6_bg_startcol(int col); +int psx_ws_mmx6_bg_startx(int x); struct CPUState; void psx_ws_sprite_tag(struct CPUState* cpu); diff --git a/runtime/src/debug_server.c b/runtime/src/debug_server.c index bf4240d2..c65857c7 100644 --- a/runtime/src/debug_server.c +++ b/runtime/src/debug_server.c @@ -6204,6 +6204,47 @@ static void handle_wide_shot(int id, const char *json) id, path, W, H); } +/* wide_full: dump the ENTIRE active wide compositor surface (both double-buffer + * y-bands, full width incl. both reveal margins) to a PNG. Unlike wide_shot + * (which crops to the displayed band), this shows where the over-draw actually + * lands across the whole surface. SW backend only (diagnostic). base_x defaults + * to 0 (the common vertical-double-buffer origin). */ +static void handle_wide_full(int id, const char *json) +{ + extern int sw_wide_dump_full(uint32_t *out, int cap_pixels, int *ow, int *oh, int base_x); + int base_x = json_get_int(json, "base_x", 0); + char path[512]; + if (!json_get_str(json, "path", path, sizeof(path))) + strncpy(path, "psx_wide_full.png", sizeof(path) - 1); + path[sizeof(path) - 1] = '\0'; + + int cap = 1024 * 1024 * 4; /* up to 4 Mpix (e.g. 426*512*S^2) */ + uint32_t *buf = (uint32_t *)malloc((size_t)cap * sizeof(uint32_t)); + if (!buf) { send_err(id, "alloc failed"); return; } + int W = 0, H = 0; + int n = sw_wide_dump_full(buf, cap, &W, &H, base_x); + if (n <= 0) { free(buf); send_err(id, "no wide surface (SW backend? native-wide engaged?)"); return; } + + uint8_t *rgb = (uint8_t *)malloc((size_t)W * H * 3); + if (!rgb) { free(buf); send_err(id, "alloc failed"); return; } + for (int y = 0; y < H; y++) + for (int x = 0; x < W; x++) { + uint32_t px = buf[(size_t)y * W + x]; + uint8_t *p = rgb + ((size_t)y * W + x) * 3; + p[0] = (uint8_t)((px >> 16) & 0xFF); + p[1] = (uint8_t)((px >> 8) & 0xFF); + p[2] = (uint8_t)(px & 0xFF); + } + free(buf); + FILE *f = fopen(path, "wb"); + if (!f) { free(rgb); send_err(id, "cannot open file"); return; } + int ok = png_write_rgb(f, rgb, (uint32_t)W, (uint32_t)H); + free(rgb); fclose(f); + if (!ok) { send_err(id, "png encode failed"); return; } + send_fmt("{\"id\":%d,\"ok\":true,\"path\":\"%s\",\"width\":%d,\"height\":%d}", + id, path, W, H); +} + static void handle_vram_peek(int id, const char *json) { int x = json_get_int(json, "x", 0); @@ -9254,6 +9295,7 @@ static const CmdEntry s_commands[] = { { "get_snapshots", handle_get_snapshots }, { "screenshot", handle_screenshot_file }, { "screenshot_file", handle_screenshot_file }, /* alias */ + { "wide_full", handle_wide_full }, { "wide_shot", handle_wide_shot }, { "gpu_opcodes", handle_gpu_opcodes }, { "gpu_ring_stats", handle_gpu_ring_stats }, diff --git a/runtime/src/gpu.c b/runtime/src/gpu.c index df8b6ff6..fcabd1e3 100644 --- a/runtime/src/gpu.c +++ b/runtime/src/gpu.c @@ -103,16 +103,19 @@ static int32_t ws_disp_w(void); * within the last couple of frames => the actor render funnel is running => * gameplay. A menu/title/save screen draws no tagged prims. The 2-frame window * makes this frame-stable regardless of per-prim ordering within a frame. */ +/* Full-2D widescreen ([widescreen] full_2d). A pure 2D sprite game (e.g. MMX6) + * never emits the sprite-tag hook the 3D gameplay detector keys on, so every + * in-game frame would be misclassified as a "full-2D menu" and pillarboxed 4:3. + * When this is set, treat every in-game frame as gameplay so native-wide engages + * and the 2D scene presents widescreen. Set once from game.toml at widescreen- + * engage (gpu_ws_set_full_2d); PSX_WS_FORCE_2D=1 forces it on for testing. */ +static int ws_full_2d = 0; +void gpu_ws_set_full_2d(int on) { ws_full_2d = on ? 1 : 0; } + static int ws_game_mode(void) { - /* EXPERIMENT (PSX_WS_FORCE_2D=1): force "gameplay" so native-wide engages on - * an all-2D sprite game (MMX6) that never emits the sprite-tag hook. This is - * the 2D "overscan reveal" path — the game over-draws past the 320 viewport - * (clipped by the draw area), and native-wide's draw_offset shift + draw-area - * widen + wide-mirror + widened present reveal it. Env-gated so it's regen- - * free and off for tagged 3D games. */ - static int force = -1; - if (force < 0) { const char *e = getenv("PSX_WS_FORCE_2D"); force = (e && e[0] == '1') ? 1 : 0; } - if (force) return 1; + static int env = -1; + if (env < 0) { const char *e = getenv("PSX_WS_FORCE_2D"); env = (e && e[0] == '1') ? 1 : 0; } + if (ws_full_2d || env) return 1; return (uint32_t)s_frame_count - ws_last_tag_stamp <= 2; } @@ -191,6 +194,41 @@ int psx_ws_x_margin(void) { return (160 * (ws_xden - ws_xnum) + ws_xnum / 2) / ws_xnum; } +/* ---- MMX6 2D background tile-loop widen ([widescreen.bg2d]) --------------- + * Mega Man X6 is a pure-2D sprite engine that renders only a 4:3 (320px) field + * of view — there is no overscan to "reveal." Its per-layer background renderer + * (FUN_800270d0) draws `count` 16px tile columns × 16 rows from a start tile + * column / start screen-x derived from the camera scroll. To produce a TRUE + * wider FOV we widen that loop so it draws extra columns on BOTH sides of the + * 320 view, filling the 16:9 reveal margins with real adjacent stage. The three + * helpers below are hooked at the renderer's count / start-col / start-x + * instructions by the recompiler (gen-time). They are IDENTITY unless native- + * wide is engaged on a ~320 screen, so 4:3 (and the engine's own 512 hi-res + * mode, which already draws 33 columns) stay byte-identical. + * + * LEFT = per-side reveal in whole 16px tile columns = ceil(nw_offset / 16). The + * loop start moves LEFT columns earlier (tile col -LEFT in the 64-col ring, + * screen-x -LEFT*16) and the count grows by 2*LEFT, so existing columns keep + * their screen positions (stay aligned with objects/player) while new columns + * are prepended left and appended right. The 64-column tile ring (mask 0x3f) is + * far wider than the visible window, so the prepended columns fetch the correct + * already-streamed adjacent tiles. */ +static int ws_mmx6_left_cols(void) { + if (!ws_native_wide_active()) return 0; + /* Only the ~320 gameplay mode; the engine's 512 hi-res mode (title) draws + * its own 33 columns and centres itself — never double-shift it. */ + if (ws_disp_w() > 384) return 0; + int off = ws_nw_offset(); /* per-side reveal in screen px */ + if (off <= 0) return 0; + return (off + 15) / 16; /* ceil to whole tile columns */ +} +/* Column count: base (21) + the columns revealed on both sides. */ +int psx_ws_mmx6_bg_cols(int base) { return base + 2 * ws_mmx6_left_cols(); } +/* Start tile column: LEFT earlier, wrapped in the 64-column ring (mask 0x3f). */ +int psx_ws_mmx6_bg_startcol(int col) { return (col - ws_mmx6_left_cols()) & 0x3f; } +/* Start screen-x: LEFT*16 further left (more negative). */ +int psx_ws_mmx6_bg_startx(int x) { return x - ws_mmx6_left_cols() * 16; } + /* Shared render-funnel screen-X cull widening ([widescreen.cull] auto_screen_x), * called identically by the gcc emit, the sljit JIT, and the interpreter so every * overlay execution path widens the same way. sx = the GTE screen-X as loaded by diff --git a/runtime/src/gpu_sw_renderer.c b/runtime/src/gpu_sw_renderer.c index 7630743f..a0aea758 100644 --- a/runtime/src/gpu_sw_renderer.c +++ b/runtime/src/gpu_sw_renderer.c @@ -1610,3 +1610,22 @@ int sw_render_wide_display(uint32_t *out_pixels, int out_pitch, int base_x, } int sw_wide_width(void) { return g_wide_w; } + +/* Diagnostic: dump the ENTIRE active wide surface for base_x (full g_wide_w*scale + * × VRAM_HEIGHT*scale — BOTH double-buffer y-bands, all margins) to ARGB. Lets a + * probe see exactly where the over-draw lands, independent of the per-frame band + * the present selects. *ow/*oh receive the surface dims. Returns pixels written, + * or 0 if no surface for base_x / it won't fit in cap_pixels. */ +int sw_wide_dump_full(uint32_t *out, int cap_pixels, int *ow, int *oh, int base_x) { + uint16_t *surf = NULL; + for (int i = 0; i < WIDE_MAX_SURF; i++) + if (g_wide_surf[i] && g_wide_base[i] == base_x) { surf = g_wide_surf[i]; break; } + if (!surf || g_wide_w <= 0) return 0; + int s = g_scale; + int W = g_wide_w * s, H = VRAM_HEIGHT * s; + if ((long long)W * H > cap_pixels) return 0; + for (int i = 0; i < W * H; i++) out[i] = rgb555_to_argb(surf[i]); + if (ow) *ow = W; + if (oh) *oh = H; + return W * H; +} diff --git a/runtime/src/main.cpp b/runtime/src/main.cpp index e7137f63..77b8b604 100644 --- a/runtime/src/main.cpp +++ b/runtime/src/main.cpp @@ -1915,6 +1915,10 @@ int main(int argc, char** argv) { g_fmv_skip_end_total = gc.runtime.video_fmv_skip_end_total; g_ws_anchor_addr = gc.ws_sprite_anchor_addr; g_ws_hud_sprt = gc.ws_hud_sprt_squash; + /* [widescreen] full_2d — opt a pure-2D sprite game (MMX6) into the + * widescreen present path. Applied to the GPU layer up front so the + * ws engage at game entry classifies every frame as gameplay. */ + gpu_ws_set_full_2d(gc.ws_full_2d ? 1 : 0); /* Register the [widescreen.backdrop] store PCs so the dirty-RAM * interpreter applies the backdrop screenX squash on the interp * path (overlay backdrop handlers run interpreted when no cache