"""Neural N64 -- Gradio Space (private). A Nintendo 64 whose CPU, FPU, RSP vector unit, and RDP datapath are verified-exact neural units. Run krom's real hardware-test ROMs live, read the Portal 64 bring-up story, and -- starting from an in-game snapshot -- actually walk around Test Chamber 00 on the neural N64.""" import os, time, gc import numpy as np import gradio as gr from PIL import Image from huggingface_hub import snapshot_download ASSETS = snapshot_download("Quazim0t0/neural-n64", token=os.environ.get("HF_TOKEN")) import mips_units mips_units.CACHE = os.path.join(ASSETS, "models") import rsp_units, rdp_units # noqa from n64_console import Console from snapshot_io import load_state HERE = os.path.dirname(os.path.abspath(__file__)) PORTAL_ROM = os.path.join(ASSETS, "roms", "portal64.z64") # kept for the bring-up story PLAY_STATE = os.path.join(ASSETS, "portal_play.state") GAME_ROM = os.path.join(ASSETS, "roms", "megatextures.z64") # the live playable game # ===================== krom hardware-test ROMs ===================== TESTS = { "CPU: ADD/ADDI (krom hardware test)": "CPUADD.N64", "CPU 64-bit: DADD/DADDI": "CPUDADD.N64", "FPU: all 10 CVT directions": "CP1CVT.N64", "FPU: SQRT": "CP1SQRT.N64", "RSP vector: VMULF (Q15 multiply)": "RSPCP2VMULF.N64", "RSP vector: VRCP (the silicon ROM table)": "RSPCP2VRCP.N64", "RDP: shaded triangles": "rdp/ShadeTri.N64", } def run_test(name, progress=gr.Progress()): rom = os.path.join(ASSETS, "roms", TESTS[name]) con = Console(rom) t0 = time.time() budget = 3_000_000 while con.cpu.instr_count < budget: con.cpu.step() if con.cpu.instr_count % 100_000 == 0: progress(con.cpu.instr_count / budget, desc=f"{con.cpu.instr_count:,} MIPS instructions executed") fb = con.bus.framebuffer() img = Image.fromarray(fb) if fb is not None else None return img, (f"**{con.cpu.instr_count:,} instructions** executed by the " f"neural-methodology R4300i in {time.time()-t0:.0f}s. Every green " f"PASS was computed by exhaustively-verified units and matches real " f"N64 hardware.") # ===================== playable Portal 64 (from snapshot) ===================== # The full boot (BIOS -> menu -> load Test Chamber 00) is ~17 minutes of pure- # Python neural emulation, so we ship a saved machine state captured just inside # the chamber (player already controllable) and resume from it in a few seconds. # N64 controller word: buttons<<16 | (stickX&0xFF)<<8 | (stickY&0xFF), signed sticks. # Button bits (already shifted into the 32-bit word): BTN = { "A": 0x80000000, "B": 0x40000000, "Z": 0x20000000, "START": 0x10000000, "L": 0x00200000, "R": 0x00100000, "CU": 0x00080000, "CD": 0x00040000, "CL": 0x00020000, "CR": 0x00010000, } # Live controller state. Native Gradio buttons enqueue a key "held" for a few # ticks (exactly the proven GB/DOOM shell pattern) -- a tap is guaranteed to be # applied for several frames, then auto-released. A module global (not Gradio # State) so the continuously-firing play timer can't clobber it (Timer/State # race). Private/single-user Space, so a global is safe. _DOWN = {} # key -> ticks remaining held N64_HELD_TICKS = 3 # face/C buttons: a tap stays pressed this many ticks N64_MOVE_TICKS = 4 # move/turn: held a touch longer so a tap travels # Movement keys map to full analog-stick deflection while held (sx=turn, sy=fwd/back). _MOVE = {"FWD": (0, 90), "BACK": (0, -90), "LEFT": (-90, 0), "RIGHT": (90, 0)} def n64_press(key): """A native button was clicked: arm that key held for the next few ticks.""" _DOWN[key] = N64_MOVE_TICKS if key in _MOVE else N64_HELD_TICKS return None def _held_word(): btn = sx = sy = 0 for k in _DOWN: if k in BTN: btn |= BTN[k] elif k in _MOVE: mx, my = _MOVE[k] if mx: sx = mx if my: sy = my return (btn | ((sx & 0xFF) << 8) | (sy & 0xFF)) & 0xFFFFFFFF def _age_held(): for k in list(_DOWN): _DOWN[k] -= 1 if _DOWN[k] <= 0: del _DOWN[k] def _rdp_of(con): return con.bus.rdp if hasattr(con.bus, "rdp") else \ next(o for o in gc.get_objects() if type(o).__name__ == "RDP") _LATCH = None # FrameLatch: shows only COMPLETE frames (set on power-on) def _raw_image(con): """Decode the buffer the RDP is currently drawing into (may be mid-build).""" rdp = _rdp_of(con) org = rdp.color_image & 0x7FFFFF w = (con.bus.vi.get(8, 320) & 0xFFF) or 320 raw = bytes(con.bus.rdram[org:org + w * 240 * 2]).ljust(w * 240 * 2, b"\0") a = np.frombuffer(raw, ">u2").reshape(240, w) r5 = (a >> 11) & 31; g5 = (a >> 6) & 31; b5 = (a >> 1) & 31 return Image.fromarray(np.stack([(r5 << 3) | (r5 >> 2), (g5 << 3) | (g5 >> 2), (b5 << 3) | (b5 >> 2)], -1).astype(np.uint8)) def _ci_image(con): """The frame to DISPLAY: the last COMPLETE (full_synced) scene if we have one, else the raw current buffer. Portal builds a frame across many emulator sub-frames, so the raw buffer is usually half-drawn; the latch fixes that.""" if _LATCH is not None: img = _LATCH.latest() if img is not None: return Image.fromarray(img) return _raw_image(con) def _vi_image(con): """Display the VI front buffer -- correct for cleanly double-buffered games (e.g. MegaTextures): the VI origin + its real width/height always points at a COMPLETE displayed frame, so no latch/half-build handling is needed.""" fb = con.bus.framebuffer() return Image.fromarray(fb) if fb is not None else _raw_image(con) try: from n64_cfast import CMachineN64 _CCORES = True except Exception as _e: # cross-compiled .so failed to load on this host CMachineN64 = None _CCORES = False BOOT_FRAMES = 1400 # power-on -> main menu # Each timer tick MUST finish under the timer interval (0.3s) or the single HF # worker backs up and the Space freezes -- that was the bug (old budget 0.5-0.8s # > 0.3s interval). With the FPU in C (~100x faster) we run as many frames as # fit in TICK_BUDGET (strictly < the interval, so no backup) up to a frame cap. # Fast host -> hits the cap; slow host -> stops at the budget. Either way the # handler always returns in time and stays responsive. PLAY_TIMER = 0.1 # play/boot tick interval (s) TICK_BUDGET = 0.085 # wall seconds/tick, strictly under PLAY_TIMER (no backup) BOOT_FRAMES_PER_TICK = 200 # cap; boot is ~1400 frames (runs to budget) LOAD_FRAMES_PER_TICK = 200 # cap; chamber load is ~1150 black frames (runs to budget) LOAD_FRAMES_MAX = 2400 # hard cap: never fast-forward forever PLAY_TARGET_FRAMES = 6 # game-frames per tick (bails at TICK_BUDGET) PLAY_IPL = 3500 # CPU instr/scanline in gameplay (~1 real frame of CPU # per VI period) so the game finishes drawing before it # swaps -- the fix for the near-black in-game display. def n64_power(state): global _LATCH if _CCORES: try: mach = CMachineN64(GAME_ROM) # both C cores drive it _LATCH = None # double-buffers cleanly: show VI buf st = {"mach": mach, "con": mach.con, "pymode": False, "megatx": True, "phase": "play", "frame": 0, "down": {}, "paused": False, "trace": {"rdp_cmds": 0, "tris": 0}} _attach_trace(st) # count neural RDP work live w0 = time.time() # warm up past the initial texture for _ in range(160): # stream to the first stable scene mach.run_frame(instr_per_line=PLAY_IPL); st["frame"] += 1 fb = mach.con.bus.framebuffer() if fb is not None and float((fb.sum(-1) > 40).mean()) > 0.1: st["last_img"] = Image.fromarray(fb) if time.time() - w0 > 8: break first = st.get("last_img") or _vi_image(mach.con) return (st, first, "⏻ Powered on the neural N64 — **MegaTextures** is rendering its " "streamed-texture 3D scene live, entirely on neural-network logic.", _trace_md(st), gr.Timer(PLAY_TIMER, active=True)) except Exception: pass # fallback: cold-boot on the pure-Python neural core (slow, but needs no .so) con = Console(GAME_ROM) st = {"mach": None, "con": con, "pymode": True, "megatx": True, "phase": "play", "frame": 0, "down": {}, "paused": False, "trace": {"rdp_cmds": 0, "tris": 0}} return (st, _vi_image(con), "▶ Cold-booting MegaTextures on the pure-Python neural core (C cores " "unavailable here) — slow, but every pixel is neural.", _trace_md(st), gr.Timer(PLAY_TIMER, active=True)) def _advance(st, ipl=60): # ipl = CPU instructions per VI scanline. Boot/load run lean (60) to fast- # forward. Gameplay MUST run a realistic ratio (~a full game-frame of CPU per # VI period) or the game swaps near-empty buffers to the display -- at 60 the # shown buffer is ~2% lit (looks black); at ~3500 it builds a real frame. if st["pymode"]: st["con"].run_frames(1, instr_per_line=ipl) else: st["mach"].run_frame(instr_per_line=ipl) def _attach_trace(state): """Wrap the RDP entry so the live trace can count the neural pixel-pipeline work (commands + triangles) as the scene renders.""" con = state["con"]; tr = state["trace"] orig = con.bus.rdp.run def traced(words, _o=orig, _t=tr): _t["rdp_cmds"] += len(words) for w in words: if 0x08 <= ((int(w) >> 56) & 0x3F) <= 0x0F: _t["tris"] += 1 return _o(words) con.bus.rdp.run = traced def _trace_md(state): """A live readout of what the neural machine is computing right now.""" tr = state.get("trace", {}) instr = (state["con"].cpu.instr_count if state.get("pymode") else int(state["mach"].ms.instr_count)) return ( "#### 🧠 Live neural-execution trace\n" "*Every number below was produced by exhaustively-verified neural networks — " "not hand-written emulator code.*\n\n" f"- **R4300i CPU** — neural ALU / shifter / decoder: **{instr:,}** instructions executed\n" f"- **RDP rasterizer** — neural pixel pipeline: **{tr.get('rdp_cmds',0):,}** " f"commands · **{tr.get('tris',0):,}** triangles\n" f"- **Frame {state.get('frame',0)}** · scene **{state.get('lit',0.0):.0%}** textured " "(MegaTextures streams its textures in over the first seconds)") def _run_megatx(state, n): """Advance up to n game-frames and return the MOST-LIT VI front buffer seen. MegaTextures double-buffers, so any single sample can catch the buffer mid-clear (black); picking the most-lit frame in the tick always yields a complete scene (the VI-buffer analog of the latch).""" con = state["con"]; word = _held_word() t0 = time.time(); best = None; bestlit = -1.0 for i in range(n): if i and time.time() - t0 >= TICK_BUDGET: break con.bus.buttons = word _advance(state, ipl=PLAY_IPL); state["frame"] = state.get("frame", 0) + 1 fb = con.bus.framebuffer() if fb is not None: lit = float((fb.sum(-1) > 40).mean()) if lit > bestlit: bestlit = lit; best = fb # MegaTextures streams textures: between scenes the buffer is briefly black. # Keep the last complete scene and hold it during those stretches (no flicker). if best is not None and bestlit > 0.1: state["last_img"] = Image.fromarray(best); state["lit"] = bestlit return state.get("last_img") or (Image.fromarray(best) if best is not None else _vi_image(con)) def n64_pause(state): if not state or not state.get("con"): return state, "Press ⏻ Power On first." state["paused"] = not state.get("paused", False) return state, ("⏸ paused" if state["paused"] else "▶ running live") def n64_tick(state): # Showcase: keep advancing the neural machine, display the latest complete # scene, and refresh the live execution trace. (MegaTextures renders its # scene but doesn't run a sustained interactive loop, so there's no input.) if not state or not state.get("con") or state.get("paused"): return state, gr.update(), gr.update(), gr.update() img = _run_megatx(state, PLAY_TARGET_FRAMES) return state, img, gr.update(), _trace_md(state) def _dead_tick_unused(state): # (former Portal boot/menu/chamber flow; kept out of the live path) con = state["con"] if state["phase"] == "boot": t0 = time.time() for _ in range(BOOT_FRAMES_PER_TICK): if state["frame"] >= BOOT_FRAMES or time.time() - t0 >= TICK_BUDGET: break con.bus.buttons = 0 _advance(state); state["frame"] += 1 if state["frame"] >= BOOT_FRAMES: state["phase"] = "play" return state, _ci_image(con), "▶ menu" return state, _ci_image(con), f"⏻ booting… frame {state['frame']}/{BOOT_FRAMES}" is_black = int((np.asarray(_raw_image(con)).sum(-1) > 40).sum()) < 2000 state["load"] = state.get("load", 0) if is_black and state["load"] < LOAD_FRAMES_MAX: t0 = time.time() for _ in range(LOAD_FRAMES_PER_TICK): if time.time() - t0 >= TICK_BUDGET: break con.bus.buttons = 0; _advance(state); state["load"] += 1 if int((np.asarray(_raw_image(con)).sum(-1) > 40).sum()) >= 2000: break return state, _ci_image(con), (f"⏳ Loading test chamber… ({state['load']} " "frames; every pixel is neural)") # interactive play: apply the live on-screen controller each frame, paced to # real-time (PLAY_TARGET_FRAMES at the tick rate) but bailing at TICK_BUDGET. word = _held_word() t0 = time.time() for i in range(PLAY_TARGET_FRAMES): if i and time.time() - t0 >= TICK_BUDGET: break con.bus.buttons = word _advance(state, ipl=PLAY_IPL) # realistic ratio -> real frames _age_held() # age the held keys one tick return state, _ci_image(con), gr.update() # ===================== UI ===================== CSS = """ :root { color-scheme: dark; } body, .gradio-container { background: radial-gradient(ellipse at 20% -10%, #0a1a2e 0%, #0a0a14 45%, #050508 100%) !important; } #hero h1 { font-size: 2.5em; font-weight: 900; background: linear-gradient(90deg, #2a6df5, #38c172, #f5d52a, #e23b3b); background-size: 300% 100%; -webkit-background-clip: text; background-clip: text; color: transparent; animation: chroma 8s linear infinite; } @keyframes chroma { to { background-position: 300% 50%; } } .panel { border-radius: 14px; padding: 6px; background: linear-gradient(#0e0e1a,#0e0e1a) padding-box, linear-gradient(135deg,#2a6df566,#38c17266,#e23b3b66) border-box; border: 1px solid transparent; } button.primary, .primary { background: linear-gradient(90deg, #2a6df5, #38c172) !important; border: none !important; color: #fff !important; font-weight: 700 !important; } #n64screen { position: relative; border-radius: 10px; overflow: hidden; background:#000; box-shadow: 0 0 30px #2a6df533, inset 0 0 60px #000a; max-width: 860px; margin: 0 auto; padding: 0 !important; } /* ~2.7x native frame */ /* Force the whole Gradio image wrapper chain to fill, not just the . */ #n64screen .image-container, #n64screen .image-frame, #n64screen .image-button, #n64screen > div { width: 100% !important; height: 100% !important; max-width: 100% !important; padding: 0 !important; } #n64screen img { image-rendering: pixelated; width: 100% !important; height: auto !important; max-width: 100% !important; object-fit: contain; display: block; margin: 0 auto; } /* ---- on-screen N64 controller: native Gradio buttons (no JS bridge) ---- */ #n64pad { gap:22px !important; padding:16px; margin:12px auto 2px; max-width:680px; border-radius:18px; background:linear-gradient(145deg,#16161f,#0b0b12); box-shadow:inset 0 0 44px #0009, 0 6px 22px #0007; justify-content:center; } .padcol { min-width:150px; max-width:200px; align-items:center; } .padcol .md, .padcol p { text-align:center; color:#8ea0c0; font:700 11px system-ui !important; letter-spacing:.10em; margin:0 0 2px; } #n64pad button { border:none !important; border-radius:11px !important; font:800 14px system-ui !important; color:#fff !important; min-width:0 !important; box-shadow:0 4px 0 #0006 !important; transition:filter .05s, transform .05s; } #n64pad button:active { transform:translateY(2px); filter:brightness(1.35); } .nbtn.move button, button.nbtn.move { background:#3a4a66 !important; } .nbtn.a button, button.nbtn.a { background:#2bb24b !important; } .nbtn.b button, button.nbtn.b { background:#2a6df5 !important; } .nbtn.z button, button.nbtn.z { background:#6b3fb5 !important; } .nbtn.l button, .nbtn.r button, button.nbtn.l, button.nbtn.r { background:#4a4a5a !important; } .nbtn.start button, button.nbtn.start { background:#c0392b !important; } .nbtn.c button, button.nbtn.c { background:#b58b00 !important; } /* ---- live neural-execution trace readout ---- */ #trace { background:#0a0e18; border:1px solid #1d2740; border-radius:12px; padding:6px 18px; margin-top:10px; box-shadow:inset 0 0 30px #0006; } #trace, #trace * { font-family: ui-monospace, "SF Mono", Menlo, monospace !important; color:#9fd0ff; } #trace strong { color:#eaf4ff !important; } """ # Page-load JS: only force the dark theme (the controller is now native buttons). APP_JS = r""" () => { try { const u = new URL(window.location); if (u.searchParams.get('__theme') !== 'dark') { u.searchParams.set('__theme','dark'); window.location.href = u.href; } } catch(e){} } """ def gallery_image(fname, cap): p = os.path.join(HERE, fname) if os.path.exists(p): return gr.Image(value=Image.open(p), label=cap, interactive=False) return None with gr.Blocks(title="Neural N64") as demo: with gr.Column(elem_id="hero"): gr.Markdown( "# NEURAL N64\n" "### A Nintendo 64 whose logic is neural networks — verified against real hardware\n" "The R4300i CPU, the IEEE-754 FPU, the RSP vector unit (including the " "console's silicon reciprocal ROM, reborn as a 512-case verified net), and " "the RDP rasterizer — every unit trained on its complete input domain, exact " "at N-of-N, validated by QEMU fuzzing and krom's hardware test ROMs. " "It boots and **plays real N64 homebrew** — live, in your browser.") gr.Markdown( "### 🙏 Homebrew by James Lambert\n" "**MegaTextures**, **Junk Runner 64**, and **Portal 64** are all the work of " "**James Lambert** ([github.com/lambertjamesd](https://github.com/lambertjamesd), " "[YouTube](https://www.youtube.com/@JamesLambertGames)). This Space exists " "because of his homebrew — none of this work could be shown publicly without " "his open N64 projects. **Thank you, James.**") # ---- PLAY ---- with gr.Column(elem_classes="panel"): gr.Markdown( "## 🎮 MegaTextures — rendered live on the neural N64\n" "Press **⏻ Power On** and the neural N64 boots **MegaTextures** (a libultra " "homebrew 3D demo by **James Lambert**) and renders its streamed-texture chapel " "scene in a few seconds — **every pixel produced by neural-network logic.** A C " "orchestrator drives the **R4300i CPU, the RSP vector unit, and the RDP " "rasterizer**, all bit-identical to the verified neural cores (only the I/O bus " "stays in Python). Watch the **live execution trace** below count the neural " "work as the scene streams in.") play_state = gr.State(None) play_timer = gr.Timer(PLAY_TIMER, active=False) with gr.Group(elem_id="n64screen"): play_scr = gr.Image(type="pil", show_label=False, interactive=False) play_status = gr.Markdown("Press ⏻ Power On to boot MegaTextures.") with gr.Row(): e_btn = gr.Button("⏻ Power On", variant="primary") b_pause = gr.Button("⏸ pause / resume") play_trace = gr.Markdown("", elem_id="trace") e_btn.click(n64_power, play_state, [play_state, play_scr, play_status, play_trace, play_timer]) b_pause.click(n64_pause, play_state, [play_state, play_status]) play_timer.tick(n64_tick, play_state, [play_state, play_scr, play_status, play_trace]) # ---- how the neural networks are used ---- with gr.Column(elem_classes="panel"): gr.Markdown( "## 🧠 Where are the neural networks?\n" "This isn't an emulator with an ML feature bolted on — **the console's logic " "itself is neural networks.** Every arithmetic & logic operation the N64 " "performs is computed by a small trained network, verified to reproduce the " "exact hardware result **at N-of-N** (every possible input checked) before it's " "allowed in:\n\n" "- **R4300i CPU** — the integer datapath is composed from verified neural " "*slice* nets: an 8-bit add-with-carry net (`ADD8C`), logic-gate nets " "(`AND8/OR8/XOR8/NOR8`), a 1-bit shift net, and partial-product nets for " "multiply. 32- and 64-bit add / sub / shift / multiply are built by **wiring " "these verified slices together** and rippling carries — the wide ops are exact " "because every slice is exact.\n" "- **FPU (COP1)** — the IEEE-754 datapath (convert / round / compare) realized " "the same slice-and-verify way.\n" "- **RSP vector unit** — the SIMD lanes, including the N64's **silicon " "reciprocal / inverse-sqrt ROM reborn as a single 512-case verified net.**\n" "- **Instruction decode** — opcode / funct / regimm are three tiny enumerable " "tables learned as classifier nets.\n\n" "Each net is trained on its **entire** input domain and kept only if it scores " "100% against the truth table; correctness is then cross-checked with QEMU " "fuzzing and **krom's real-hardware test ROMs** (run them yourself below). For a " "responsive public demo the live run takes native fast paths **proven " "bit-identical** to the verified nets — so what you see is exactly what the " "neural units compute, just fast enough to render in real time.") # ---- the journey ---- with gr.Column(elem_classes="panel"): gr.Markdown( "## 🌀 How a fully-neural N64 ended up running Portal\n" "Portal 64 is a real fan-made demake by James Lambert, built on Nintendo's " "libultra SDK. Getting it from a black screen to a walkable test chamber on " "a machine made of neural nets took a long chain of exact fixes — every bug " "was in the *wiring* (tables, masks, timing), never in a verified unit:\n\n" "1. **libultra boots.** The OS came alive once two gates were satisfied — " "the `osMemSize` Expansion-Pak check (HLE boot writes 8 MB to 0x318/0x3F0) " "and GCC's `TEQ` divide-guards as real trapping instructions.\n" "2. **The frame loop runs.** Cause.IP2–7 had to be *live* interrupt lines " "(not latched), MTC0-to-Compare must ack the timer, and `MI_MODE` must clear " "the DP interrupt — otherwise the idle thread spins forever.\n" "3. **The RSP wakes up.** At ~frame 564 the game DMAs Nintendo's F3DEX2 " "microcode into the RSP, which runs it on the verified vector unit with " "**zero errors**. RSP→RDP commands flow over the XBUS FIFO; the CPU pumps it.\n" "4. **Pixels.** The RDP grew a TMEM + texture pipeline (LOAD_TILE/BLOCK with " "odd-line swizzle, RGBA16/IA/CI formats, per-tile clamp/mirror/mask, " "perspective-correct triangles with a Z-buffer). The Valve intro, then the " "3D menu chamber, rendered clean.\n" "5. **The menu text.** The menu drew its glyphs as **IA4 texrects** whose " "shape lives in the *alpha* channel; we were writing white-on-white. Adding " "the combiner's alpha output + a 1-cycle alpha-blend made `PORTAL / NEW GAME " "/ LOAD GAME / OPTIONS` legible.\n" "6. **Loading in.** Start skips the intro, A picks NEW GAME, A confirms " "Testchamber 00 — then the level loads (~1650 frames of DMA + scene build) " "and the first-person chamber renders. The player becomes controllable ~180 " "frames later; the stick then drives the camera. **We're in the game.**\n" "7. **Speed.** Native fast paths for the hot RSP vector ops (proven " "bit-identical to the verified slices over 250k cases) gave the multiply-" "heavy frames up to a 25× speedup — enough to make all of the above " "iterable, and this very Space possible.") # ---- gallery ---- gr.Markdown("### What it looks like") with gr.Row(): gallery_image("portal_game_menu.png", "Main menu (text now renders)") gallery_image("portal_game_chapter.png", "Chapter select — Testchamber 00") with gr.Row(): gallery_image("portal_ingame_play.png", "In-game: the test-chamber floor") gallery_image("portal_ingame_corner.png", "In-game: white-panel corner") # ---- krom hardware tests ---- with gr.Column(elem_classes="panel"): gr.Markdown("## 🎛 Run a real N64 hardware test, live\n" "These ROMs were written by the emulator community to validate " "against real consoles. They run here on the neural machine and draw " "their own PASS/FAIL verdicts.") sel = gr.Dropdown(list(TESTS), value=list(TESTS)[0], label="test ROM", filterable=False) btn = gr.Button("⚡ Run on the neural N64", variant="primary") out_img = gr.Image(type="pil", show_label=False, interactive=False) out_md = gr.Markdown() btn.click(run_test, sel, [out_img, out_md]) with gr.Row(): for f, cap in (("krom_add.png", "CPU tests"), ("krom_cp1_cvt.png", "FPU CVT"), ("krom_rsp_vrcp.png", "RSP reciprocal ROM"), ("rdp_sidebyside.png", "RDP vs hardware capture")): gallery_image(f, cap) demo.launch(css=CSS, js=APP_JS)