| """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 |
| 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") |
| PLAY_STATE = os.path.join(ASSETS, "portal_play.state") |
| GAME_ROM = os.path.join(ASSETS, "roms", "megatextures.z64") |
|
|
| |
| 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.") |
|
|
| |
| |
| |
| |
| |
| |
| BTN = { |
| "A": 0x80000000, "B": 0x40000000, "Z": 0x20000000, |
| "START": 0x10000000, "L": 0x00200000, "R": 0x00100000, |
| "CU": 0x00080000, "CD": 0x00040000, "CL": 0x00020000, "CR": 0x00010000, |
| } |
| |
| |
| |
| |
| |
| _DOWN = {} |
| N64_HELD_TICKS = 3 |
| N64_MOVE_TICKS = 4 |
| |
| _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 |
|
|
| 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: |
| CMachineN64 = None |
| _CCORES = False |
|
|
| BOOT_FRAMES = 1400 |
| |
| |
| |
| |
| |
| |
| PLAY_TIMER = 0.1 |
| TICK_BUDGET = 0.085 |
| BOOT_FRAMES_PER_TICK = 200 |
| LOAD_FRAMES_PER_TICK = 200 |
| LOAD_FRAMES_MAX = 2400 |
| PLAY_TARGET_FRAMES = 6 |
| PLAY_IPL = 3500 |
| |
| |
|
|
| def n64_power(state): |
| global _LATCH |
| if _CCORES: |
| try: |
| mach = CMachineN64(GAME_ROM) |
| _LATCH = None |
| 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) |
| w0 = time.time() |
| for _ in range(160): |
| 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 |
| |
| 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): |
| |
| |
| |
| |
| 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 |
| |
| |
| 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): |
| |
| |
| |
| 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): |
| 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)") |
| |
| |
| 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) |
| _age_held() |
| return state, _ci_image(con), gr.update() |
|
|
| |
| 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 <img>. */ |
| #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; } |
| """ |
|
|
| |
| 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.**") |
|
|
| |
| 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]) |
|
|
| |
| 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.") |
|
|
| |
| 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.") |
|
|
| |
| 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") |
|
|
| |
| 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) |
|
|