| Commit message (Collapse) | Author | Age |
| |
|
|
|
|
|
|
|
|
|
|
| |
gbos grew a /bin mount and a pile of userland (help, ls -l, df, mv, tail,
tee, sleep, xxd, poweroff), so the header suggests the ones that show the
machine is real rather than a canned animation: help, ls /bin, ls -l, df.
Also drops the stray path from the wget example - it takes a host only.
Checked each in the browser against the deployed ROM: `ls /bin` lists the
builtins, `help` prints the program table, `df` reports "blocks 5/128 used
(1K of 32K), inodes 2/32 used", `ls -l` shows the /bin directory entry.
Demo still assembles in ~22s with 4/4 leases, ircd chat and httpd 200 /.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
The page spent ~20s assembling itself with nothing to show for it but four
Game Boys sitting on banners, which reads as broken rather than busy. There's
now a boot banner with a spinner naming the current step ("GB2 is 10.0.0.4 -
joining #gb on 10.0.0.2... step 3/4"), and each machine's card pulses while
it's still being set up. When it finishes, the banner turns green and says so.
The controls are disabled for the duration - not just cosmetics: plugging in a
Game Boy mid-scenario takes the address the next step is about to be told to
connect to.
Two bugs found by testing the lock rather than assuming it: the DHCP-lease
handler re-enabled "ping across" directly instead of going through refresh(),
and refresh() itself had a leftover second assignment to the same button that
overwrote the demo-aware one. Both now go through refresh(), which is the only
thing that decides whether a control is live.
Verified: all four controls stay off across every step and come back together
at the end, exactly one card pulses at a time, and the banner reaches "demo
ready" with no exceptions.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
An empty switch is a blank page. The lab now stands up a four-machine demo by
itself: one Game Boy runs ircd, one runs httpd, and two more use them - an irc
client that joins #gb and says hello, and a web client that wgets the server
and pings it. Cards carry a role badge so the shape is readable at a glance.
Nothing is faked: every step is a command typed into a real shell over the
link port, which is why you can take it over afterwards and keep typing. New
controls: "restart demo" and "empty the switch"; "+ plug in a Game Boy" still
works the same.
Booting four machines at real speed would be a minute of watching banners, so
each one is fast-forwarded (6 frames per tick) until it has a lease, then
drops back to real time for the part worth watching. Only one is ever boosted
at a time - the scenario is sequential - which keeps the total inside the
~12x-realtime budget a single thread has. Whole thing is ready in ~22s.
deploy.sh now also refreshes the gitignored web/roms/gbos.gb dev copy. That
copy had silently gone stale by two gbos releases, and a local demo run kept
reporting "ircd: not found" for a program that was plainly in the ROM - the
page was fine, the ROM behind it was from before ircd existed.
Verified against the live site: 4/4 leases, roles .2-.5, 60fps each, ircd logs
"<gbchat> hello from the other Game Boy", httpd logs "200 /", wget prints the
HTML and ping gets 4/4, no exceptions.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
gbos picked up an IRC server (and httpd before it), which is a much better
advert for the lab than `ls`: one Game Boy runs the daemon, another connects
over the virtual switch. Verified on the live site end to end - GB0 `ircd &`,
GB1 `irc 10.0.0.2 gb1`, `/join #gb`, and the message lands:
`<gb1> hello from sl0p.foo`. The header now suggests exactly that, plus the
httpd/wget pair.
Two display fixes fell out of running it:
- irc(1) is a colour client, so the console mirror was printing raw CSI
sequences as "[44m[37m..." - the decoder dropped the ESC byte but kept
the rest. It now tracks ESC / CSI state and discards the whole sequence.
The LCD still renders the colours; only the plain-text mirror strips them.
- the LCD is 40 columns, so full-width output (irc's status bar) arrives
space-padded. The mirror isn't column-aligned, so it squeezes runs of
whitespace and drops lines that were nothing but padding.
The ROM itself isn't vendored here (it's a build artifact of ~/dev/gbos that
deploy.sh ships), so this commit is the page copy and the decoder.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
zig defaults the wasm stack to 16MB, and that lands in the module's *initial*
memory, so every instance allocated 16.25MB before executing an instruction.
With several Game Boys on one page that was the dominant cost: 123MB for six.
The machine is a static and nothing here recurses deeply, so 256KB is plenty.
Per instance: 20.5MB -> 4.8MB of linear memory (six: 123MB -> 29MB). Note the
spelling - zig cc rejects -Wl,--stack-size and -zstack-size, and wants
-Wl,-z,stack-size=N.
With memory no longer the constraint, the page cap goes 6 -> 8 on measurement
rather than guesswork. Emulation-only, one thread: 12.5x realtime at 1, 3.1x
at 4, 1.5x at 8, and exactly 1.0x at 12 - i.e. ~12 is where a thread runs out
of road with nothing left for rendering. 8 holds a verified 60fps each in a
real browser with all eight leased.
Golden frames are still pixel-exact on cgb-acid2, dmg-acid2, cpu_instrs@600
and pky@400 with its .sav, and two Game Boys still lease and ping 4/4.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Typing commands is the main thing you do in the lab, so selecting a machine
now focuses its input instead of making you click twice. Clicking anywhere on
a card selects it; the input takes the caret.
The catch is that the console swallows the keyboard - 'z' and 'x' are letters
before they are A and B - so there are two ways back to the joypad: esc, or
clicking the Game Boy's screen (which is where you'd reach for it anyway).
Both are spelled out in the header and the input's placeholder.
Switching machines also releases any button still held on the one you left,
which previously could stick down across a focus change.
Also lands the page swap: the network lab is the landing page and the
single-Game-Boy player moved to /play.html, with /net.html 301'd in nginx.
Verified in headless Chrome: focus follows add and click, the screen and esc
both blur back, SELECT still opens the on-screen keyboard afterwards
(20 -> 491 lit pixels), and typing 'uptime' into a card still answers.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
/net.html plugs Game Boys into a switch that leases them addresses and routes
between them: add one, add another, and they ping each other. Unplug either
and its address goes back to the pool for the next one.
web/gbhub.js is tools/gbhub (gbos) ported to the browser - SLIP framing, DHCP
OFFER/ACK, unicast routing, broadcast flooding and ICMP echo for the gateway.
It deliberately has no uplink: NAT needs a TUN device and a tab has no raw
sockets, so packets addressed off-net are dropped and logged as "no route"
rather than pretending.
web/gbcore.js wraps one wasm instantiation as a GameBoy object. Several Game
Boys are several instantiations of the same compiled Module - the core keeps
its machine in a static, so each gets its own linear memory and is isolated
for free. Measured 12.3x realtime per instance, so the page caps at 6.
Each card gets a text input, because unframed link bytes are console I/O: it
lands on the shell's stdin like tools/gbtype, so you can run `ping 10.0.0.3`
without the on-screen keyboard.
Verified headless (node, two Game Boys through the hub: 2/2 leases, ping
4/4 received) and in headless Chrome against the live site: 4 Game Boys at
60fps each, leases .2-.5, typing into a card's console pings another, unplug
frees the address and a replug reclaims it, no exceptions.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
A browser has no file descriptors, so the wasm build's link port was inert.
Give it a pair of ring buffers instead: the guest's transmitted bytes pile up
in one, the host pushes received bytes into the other, and the external-clock
RX path pops from it (staying pending when empty, exactly like a quiet link).
gbw_serial_drain / gbw_serial_push hand them to JS through a fixed staging
buffer, so nothing allocates per frame.
That is enough to be a real link. SLIP-framed bytes are IP packets; unframed
bytes are console I/O - the same split tools/gbhub makes natively, which is
what lets a page type into the link port and reach the shell's stdin.
One machine per wasm instance, so the queues are plain statics: several Game
Boys on a page are several instantiations of the one compiled module, each
with its own linear memory.
The native link is untouched: the fd paths sit in the #else and, preprocessed
for a native target, gb.c comes out byte-identical to the previous commit
(verified with gcc -E). blargg cpu_instrs still passes and the golden-frame
tests are still pixel-exact.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
The page fetched nothing on its own, so a visitor met an empty canvas and a
file picker. It now loads roms/gbos.gb on arrival - gbos being a Unix-ish
microkernel for the GBC, which shows off rather more of the emulator than a
title screen - with a note pointing at SELECT for its on-screen keyboard and
warning that its networking rides a link cable a browser tab doesn't have.
Dropping your own ROM still replaces it (and hides the blurb).
web/deploy.sh builds the wasm, stages index + wasm + the demo ROM, rsyncs to
/opt/sl0p/gbc and curls the three URLs back. The ROM is a build artifact of
~/dev/gbos and deliberately not vendored here; if it's missing the page falls
back to "drop in a ROM" on the 404, so the deploy still works.
Also fixes a save-path bug that would have bitten the first 128KB cart:
String.fromCharCode(...sram) throws RangeError on a spread that big, killing
the autosave. Chunked, and the whole thing is wrapped for quota errors.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
`make wasm` builds the core to WebAssembly (~44KB stripped) and web/ is a
front-end for it: drag-drop a ROM, pixelated canvas at any zoom, keyboard
mapped like the native build, battery saves in localStorage.
Built with zig cc, which carries its own wasm backend, linker and
wasi-libc - so no emscripten dependency, just zig on PATH. Only the
portable core goes in; render/kitty/control/main are terminal- and
POSIX-bound and stay out. src/wasm.c is the browser's whole host layer:
no terminal, no sockets, no files, and no frame pacing (the page's
requestAnimationFrame owns that). The machine is static, the RGB888 ->
RGBA conversion for putImageData happens in wasm, and nothing allocates
per frame.
The entire host interface is four WASI fd_* imports, pulled in by the
cart info banner's fprintf; the page shims them to console.log in ~20
lines. Cartridge RAM *is* the save file here, so it's exported for the
page to persist per cart title (every 5s and on unload).
Because both builds now advance on the same frame boundary, the port is
checked byte-for-byte against the native binary rather than by eyeballing
a canvas - tools/wasm_check.mjs runs N frames in node and diffs the
framebuffer against a --shot PPM:
cgb-acid2 @60, dmg-acid2 @60, cpu_instrs @600, mem_timing-2 @700,
pky.gbc @400 with an imported .sav, cgb-acid2 @120 through the full
CGB BIOS boot animation -> all pixel-identical
Also smoke-tested in headless chromium over CDP: core instantiates, ROM
loads through the file input, canvas animates (7 distinct frames over
5.6s), keyboard reaches the joypad, 60fps, and a 32KB save lands in
localStorage.
No audio: the APU is still stubbed.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Groundwork for building the core for targets that have no files, no
sockets and no terminal (a browser, next commit). Three changes, no
behaviour change for the native binary:
- cart_load() splits into cart_init_owned() (everything past the file
read) plus cart_load_mem(), which takes a ROM image already in memory.
The path version keeps owning the .sav wiring; a memory load attaches
no battery file and leaves save_path empty, which cart_save() now
treats as "nowhere to write".
- gb_load_bootrom_mem() does the same for boot ROMs, so the power-on
animation works wherever the image comes from.
- gb_run_frame() was declared in gb.h but never existed; main.c had a
private run_frame() instead. Implement it in gb.c and point main.c at
it, so every host advances the machine on one identical boundary.
The link-socket layer (--serial-sock) is the only genuinely unportable
part of the core, so under __wasm__ it becomes no-op stubs: the link is
simply always down and the call sites stay untouched. The file-backed
loaders compile out there too, which keeps the libc surface tiny.
Verified: blargg cpu_instrs (11/11), instr_timing, mem_timing,
mem_timing-2 all still Passed; --shot output unchanged.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Sixel is a 1987 printer format wearing a terminal costume: 256 colors per
image, no alpha, no compositing, one flat picture. --kitty [scale] takes
the other road on kitty/ghostty/WezTerm/Konsole:
- the framebuffer ships as raw RGB888, so no per-frame palette build and
no coarser quantization when a CGB game blows past 256 colors
- the HUD (title, render rate, frameskip, turbo, live button readout) is
a *separate* alpha-blended image on z-index 1, composited over the game
by the terminal instead of being burned into the picture; 'h' toggles
it, --no-hud starts without it
- consecutive identical images are dropped, so a pause menu or text box
stops the traffic dead
- pixels go over at 1x and the terminal scales them into a cell box, so
--kitty 6 costs the same bytes as --kitty 1 (padded to whole cells
first, so the zoom stays exact)
Two transfers: shm hands over the name of a POSIX shared-memory object
(~60 bytes/frame) when we can tell the terminal is local, b64 inlines the
bytes for ssh and tmux (which might be attached from another machine).
--kitty-shm / --kitty-b64 force either. tmux gets DCS passthrough wrapping.
--chrome and --palette work here just like in sixel mode.
Nothing about a graphics stream is human-readable, so tools/kitty_decode.py
reassembles a capture back into images - chunked payloads, tmux wrapping
and all - and can assert a frame is pixel-identical to a --shot:
tools/kitty_decode.py cap.bin -o frames --check shot.ppm
-> OK: image 1 matches shot.ppm exactly (160x144)
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
| |
Built with -std=c11 + _POSIX_C_SOURCE, glibc's signal() gives System V
semantics: the disposition resets to SIG_DFL the moment the handler runs.
So a second SIGTERM - a supervisor retrying, `timeout` signalling the
process group, an impatient second Ctrl-C - killed us mid-shutdown, before
term_restore() put the terminal back and before the render backend cleaned
up after itself. Wrap sigaction() in install_sig() and route SIGINT,
SIGTERM and the serial-link SIGPIPE ignore through it.
Verified: staged tree builds clean (-Wall -Wextra, no warnings); added
lines are already clang-format clean (hook bypassed only because the
split commit leaves main.c partially staged).
|
| |
|
|
|
|
| |
Add a BIOS subsection covering usage (Pillow dep, SL0P_TEXT/SL0P_FONT
knobs, the preview PNG), how the in-place $0607 blob patch works, and
why the forked binary stays out of git while the generator ships.
|
| |
|
|
|
|
|
|
|
|
|
|
|
| |
The stock CGB boot ROM draws its GAME BOY wordmark from a 192-byte blob
at $0607 (routine $03F0 copies it to VRAM tiles $08-$37: 48 tiles, 4
bytes each, every byte a row doubled vertically). This re-encodes that
blob with a bold-italic SL0PBOY bitmap of the same size and patches it
in place over a local bios/gbc_bios.bin -> bios/sl0pboy_bios.bin. No
code moves; the cart's own Nintendo logo ($104) is untouched so the
integrity check still passes and the boot hands off normally.
bios/ stays gitignored (copyrighted boot ROM); run this to produce the
fork locally. Verified end to end: logo drop + color + chime -> gbos.
|
| |
|
|
|
|
|
|
|
| |
git-clang-format scopes formatting to the lines each commit touches -
a full-file reformat would destroy the deliberately dense hand
formatting (opcode tables, multi-statement helpers). The hook re-adds
formatted files (git-clang-format --staged fixes the worktree, not the
index) and refuses partially-staged C files rather than mixing staged
and unstaged changes. Enable once: git config core.hooksPath hooks
|
| |
|
|
|
|
| |
The Architecture table listed the core sources as bare filenames
(types.h, cart.c, ...), but they live under src/. Prefix them with
src/ so the paths actually point at the files.
|
| |
|
|
|
|
|
|
|
|
|
| |
Decimates the LCD N x N before the half-block encode so the monitor pane
takes 160/N cols x 72/N rows. Display-only: capture, recording and sixel
still read the full-res framebuffer.
Decimation is max-luma pick, not box-average: terminal fonts are 1px
bright strokes on dark background, and averaging dims them into grey
mush (the 'blurry font' regression). Keeping the brightest source pixel
of each block preserves stroke brightness and hue.
|
| |
|
|
|
|
|
|
| |
The control socket was only opened on the windowed path, so a --headless
GB couldn't be inspected. A scriptable, invisible GB is exactly the one
you most want to peek into - reading kernel WRAM over the debug socket is
how the TCP field-offset corruption got pinned down. Open it in the
headless loop too and poll it each frame; close it on exit.
|
| |
|
|
|
|
|
| |
The clean-exit opcode used to print nothing, so any guest crash that
walked into a stray $ED byte was indistinguishable from a silent
socket death (one of the kill vectors in the link-drop investigation).
Now it identifies itself on stderr with pc/af/bc/de/hl/sp.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Root-cause work for the 'idle link-socket drop' (see gbos
docs/link-drop-investigation.md): when the hub restarts or the bridge
dies, the link fd went dead forever - and before SIGPIPE was ignored,
the next console-echo write() would kill the whole emulator silently.
Now, in --serial-sock mode, EOF on read or EPIPE/ECONNRESET/ENOTCONN/
EBADF on either direction marks the link dead, closes the fd, and
retries connect() every 500ms from the serial poll path (the guest
polls constantly, so no extra plumbing). RX stays pending while down
(serial_no_eof semantics), so the guest just sees a quiet link that
comes back. Logs 'link socket lost / reconnected' either way.
Verified: SIGKILL the hub, restart it - every GB reconnects within ~1s
and keeps its lease.
|
| |
|
|
|
|
|
|
| |
Runtime chatter (link reconnect notices, input debug, cart warnings) was
landing mid-sixel-stream and corrupting the picture. Redirect fd 2 to
/dev/null for the duration of the display loop; restored on exit so the
final stats line still prints. Startup errors before the display begins
are unaffected.
|
| |
|
|
|
|
|
|
|
|
|
|
|
| |
The serial fds are serviced by cpu_step in every mode, so wiring them to a
connected unix socket lets networking run alongside a live display (--sixel /
terminal) - unlike --headless, which needs stdio for the serial. The emulator
connects to PATH (retrying ~1s so a launcher can listen first) and uses it
non-blocking with no EOF-exit; it overrides the headless stdio serial.
Pairs with gbos tools/netboot, which listens on PATH, brings up a TUN+NAT, and
bridges the socket <-> TUN. In windowed mode the OSK is the console and the
link port is pure network - cleanly separated. Verified: host pings the GB 3/3
over the socket link.
|
| | |
|
| | |
|
| | |
|
| | |
|
| |
|
|
|
|
|
|
|
|
|
|
| |
- Rewrite argument handling: the ROM is now the final argument and options
precede it (parsed in argv[1..argc-2] so optional-value flags never swallow
the ROM). Add grouped --help/-h, unknown-option errors, and clear 'missing
value'/'no ROM' diagnostics.
- Add --chrome, --palette/--green, and --bios flags; set the display/capture
options before the --shot and recording paths so screenshots and recordings
honor them. Make --shot dump the composited capture frame.
- gbctl: pass the ROM last when spawning the emulator.
- gitignore the (copyrighted) bios/ directory; document all new flags.
|
| |
|
|
|
|
|
| |
The video recorder now writes render_capture_frame() instead of the raw
LCD framebuffer, so captures match what's shown: chrome frames record at
their larger dimensions (written into the .gbv header) and the LCD palette
is baked in. Button presses also show up lit in recordings.
|
| |
|
|
|
|
|
|
|
|
|
|
|
| |
- --chrome: draw a DMG-style 'sl0pboy' shell around the LCD in sixel mode,
with a bitmap-font silk-screen (logo, labels), speaker grille, power LED,
and buttons that light up while held.
- LCD palette recolor (render_set_palette): remap the LCD to a 4-shade
palette by luminance for the classic monochrome look ('dmg'/'green' pea
green, 'pocket'/'gray'); applies to every render path.
- Refactor the sixel encoder into generic build_palette_buf/encode_sixel_buf
operating on any RGB888 buffer, shared by the bare LCD and the chrome canvas.
- render_capture_size/frame: produce the exact displayed frame (recolor +
chrome) as an RGB buffer, for screenshots and video recording.
|
| |
|
|
|
|
|
|
|
| |
Add optional boot ROM emulation. When a boot ROM image is loaded, gb_reset
starts execution at PC=0 with power-on register state instead of the
documented post-boot state; the ROM overlays low memory (with the CGB
0x0100-0x01FF header hole showing the cartridge through) until the game
writes 0xFF50 to hand off. gb_load_bootrom() accepts 256-byte (DMG) or
2304-byte (CGB) images.
|
| |
|
|
|
|
|
|
| |
--keys "udlrabse." plays scripted joypad taps (each char = one button
held 10 frames then released 10, so the guest sees a clean edge); pairs
with --headless --shot to drive on-screen input and capture the result.
When --keys is active, serial-RX EOF stays pending instead of sending
$04, so a shell blocked on read waits for OSK input instead of exiting.
|
| |
|
|
|
|
|
| |
Lets scripted runs feed stdin to the serial console AND capture the
resulting screen, e.g. printf 'ls\nexit\n' | gbc rom.gb --headless --shot 0 out.ppm
Previously --shot ran frames with no stdin, so anything waiting on input
(a shell) only showed its first prompt.
|
| |
|
|
|
|
|
|
| |
The unused SM83 opcode $ED now sets gb->poweroff, which breaks the headless
loop so the normal cart_free()->cart_save() path runs. Lets a guest OS shut the
emulator down cleanly (and flush its .sav) instead of relying on an external
timeout/SIGTERM, which could SIGKILL before the save. run_frame() also stops on
poweroff so exit is immediate.
|
| |
|
|
|
|
|
|
| |
When stdin is a terminal, --headless now disables canonical mode + local echo
so keystrokes reach the guest immediately and only the guest echoes (no doubled
chars). Piped/redirected stdin is untouched (scripted input still works). Keep
ISIG so Ctrl-C quits; restore termios on exit. VMIN=1 so a no-data non-blocking
read is EAGAIN, not a 0-byte read the serial layer would misread as EOF.
|
| |
|
|
|
|
| |
read()==0 completes the external-clock transfer with SB=0x04 (EOT), so a gbos
program's readc() sees EOF instead of polling forever. Enables cat/wc-style
tools and lets the shell exit on Ctrl-D / pipe end.
|
| |
|
|
|
|
| |
- external-clock transfers (SC=$80) deliver a stdin byte into SB without
echoing to stdout, and stay pending when no input is available -> programs
can poll for input cleanly. internal-clock (SC=$81) stays pure TX.
|
| |
|
|
|
|
|
|
| |
- --headless: no rendering/TUI; serial TX -> stdout, RX <- stdin (nonblocking)
- generalize serial: serial_out_fd/serial_in_fd on the GB; internal-clock
transfer now shifts in a real RX byte (from stdin) instead of always 0xFF
- paced to native speed by default, --uncapped for turbo; pipes/scripts cleanly
- README: document headless serial console
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Two socket features for building reproducible showcase clips:
- record start <path> [everyN] / record stop: append the RGB888 framebuffer of
each produced frame to a flat 'GBCV' capture file (downsample with everyN).
tools/gbgif.py turns it into a GIF (or PNG frames). Capture is decoupled from
wall-clock/turbo/frameskip, so timing is always correct.
- input play <path> [reset] / input stop: drive the joypad from a synthesized
TAS-style movie (text: '<frames> [buttons...]' per line), frame-locked so
replay is deterministic. 'reset' does an exact power-on (preserve cart
ROM+SRAM, zero all other state) so a movie replays byte-identically -- verified
by hashing two runs. tools/gbmovie.py is a Python builder for movies.
|
| |
|
|
| |
overworld image
|
| |
|
|
|
| |
In --sixel mode the emulator drew a status line (title/skip/[f]ast/q:quit) under
the image; drop it so sixel output is nothing but the game graphics.
|
| |
|
|
|
| |
New INPUT section reads hJoyHeld (0xFFB4) and draws a d-pad cross + A/B circles
+ SEL/START pills; held buttons glow bright green, idle ones dim grey.
|
| |
|
|
|
|
| |
gbctl hud now scans tmux for panes started with gbhud.py and kills them before
spawning, so there's never more than one HUD (fixes the double-spawn flicker).
Added 'gbctl hud --kill' to just tear the HUD down.
|
| |
|
|
|
|
|
|
|
| |
A client that connects and closes before reading (e.g. the HUD's liveness
probe) made the greeting write() raise SIGPIPE, terminating the whole emulator
- this was behind much of the 'it keeps dying' this session. Also: gbhud now
re-resolves the live instance from the registry on every reconnect (skipping
stale sockets by test-connecting), so the HUD follows respawns, and its box
rendering is ANSI-width-correct.
|
| |
|
|
|
|
|
| |
Reads WRAM over the control socket, resolves addresses from pokeyellow.sym, and
pretty-prints game state (location, player pos/facing, movement+collision debug,
player name/money/badges, party nicknames/levels/HP bars) refreshing ~5Hz.
'gbctl hud' spawns it in a tmux split bound to the live instance.
|
| |
|
|
|
|
|
|
|
| |
The APU was a black hole: reads returned 0xFF, writes were dropped. That hangs
any game that fades music out by decrementing the NR50/rAUDVOL volume register
until it reads 0 (Pokemon's StopMusic spin-wait) - the write never stuck, the
read never hit 0, infinite loop. Store the 48 sound registers and return them
with the standard hardware read-back OR-masks (NR50 mask 0x00 = exact readback).
No synthesis, just correct register behavior.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
| |
Replace the input-only FIFO's limitations with a Unix-domain socket control
channel (--sock) that is a superset of the button vocabulary plus emulator
introspection: read/write bus memory, get/set CPU context, single-step, PC
breakpoints, value-change watchpoints, run/pause. Being a socket it replies to
each command and broadcasts async 'event stop ...' lines; a stored last-stop
record (stopinfo) lets per-request clients recover a missed event.
Add gbctl: a stdlib-python CLI that spawns a tmux pane running the emulator on
a ROM, auto-resolves the socket, and drives it with terse verbs (cpu/read/write/
reg/step/break/watch/continue/pause/press/hold/screen/stop). The FIFO stays as
legacy input-only.
|
| |
|
|
|
|
|
| |
Emulation always runs at native rate (correct game speed); --frameskip N draws
only 1 of every N+1 frames so a slow terminal no longer bottlenecks the loop.
Adds --fps cap, --uncapped/--turbo, live [ ] and f keys, HUD readout, and an
exit summary of emulated-vs-drawn fps. Replaces the confusing --render-fps.
|
| |
|
|
|
|
|
| |
Adds --fifo [path] opening a named pipe that accepts button commands from any
process. Supports momentary taps (a, start, name:N), deterministic hold/release
(+name / -name), and 'release'. Merges with keyboard input; held state carries
true key-up semantics the terminal can't provide. GBC_INPUT_DEBUG traces state.
|
| |
|
|
|
|
|
| |
Only cells that changed since the previous frame are redrawn, using cursor
positioning to skip unchanged runs. Static screens write nothing after the
first frame (~108x reduction on acid2). SIGWINCH forces a full repaint.
Verified pixel-exact against the framebuffer (11520/11520 cells, 0 mismatch).
|