Builds a browser-playable .html/.js/.wasm/.data via `./build-web.sh` (no
new CMake top-level target — Emscripten is detected at configure time and
takes the existing kobodl through a web-flavored pipeline).
Key pieces:
- CMakeLists.txt: route SDL3 through `--use-port=sdl3`; substitute
`--use-port=libpng` for the missing sdl3_image port (Emscripten 5.0.7
ships only sdl3 + sdl3_ttf). pthread (atomics+bulk-memory) lifted to
kobo_deps so every TU agrees with wasm-ld's --shared-memory. Link with
-sASYNCIFY (avoids rewriting kobo's blocking main loop around
emscripten_set_main_loop), -sALLOW_MEMORY_GROWTH/-sMAXIMUM_MEMORY=1gb,
-sPTHREAD_POOL_SIZE=4, -lidbfs.js, and --preload-file data@/data.
- graphics/img_compat.{h,c}: libpng-backed IMG_Load shim, used in
sprite.c / gfxengine.cpp / filters.c via #include "img_compat.h".
Native builds passthrough to <SDL3_image/SDL_image.h> unchanged.
- web/shell.html: HTML wrapper that mounts /home as IDBFS for persistent
scores/config. Startup gated on addRunDependency until syncfs(true)
finishes, then /home/scores is mkdir'd post-load (the game's addPlayer
doesn't auto-create the score dir; see TODO in score.cpp).
beforeunload + visibilitychange flushes /home back to IndexedDB.
- build-web.sh: emcmake wrapper + a static server that sets the
COOP/COEP headers SharedArrayBuffer/pthreads require.
Source fixes needed to build under Emscripten's libc/libcxx:
- Renamed enemy_kind `pipe2` -> `pipe2k` (collided with libc pipe2(2)
always declared in Emscripten's <unistd.h>).
- graphics/toolkit.cpp: added missing <stdio.h> for snprintf (Emscripten's
libcxx headers don't pull it in transitively).
- states.cpp: gated the "Quit Kobo Deluxe" main-menu button on
!__EMSCRIPTEN__; exit() on the web leaves a dead canvas.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
When KOBO_BENCH=1 is set in the environment, the game becomes a
hands-off benchmark:
* Skips the intro/menu sequence and pushes st_game directly with
scene=0, skill=CLASSIC, invulnerable ship, always_fire = constant
auto-fire, countdown = 0 so "GET READY" auto-pops in 700ms.
* Seeds pubrand and gamerand from KOBO_BENCH_SEED (default 12345)
so the map, stress spawn pattern, and enemy AI are identical
run-to-run.
* Defaults KOBO_STRESS=1500 unless overridden, so the move pass has
a steady workload, and KOBO_PROF=1 so the profiler is on.
* Counts frames in run_game; after KOBO_BENCH_FRAMES (default 1800)
it dumps prof to stderr and exits cleanly.
Usage from repo root (the cwd matters — relative GFX paths only
resolve from here):
KOBO_BENCH=1 ./build/kobodl 2>bench-logs/phaseN.log
A single command produces a comparable profiler dump at every phase
of the DOD refactor — no menus to navigate, no keyboard input,
identical workload.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Adds a tiny zone-based profiler (prof.{h,cpp}) enabled via KOBO_PROF=1.
Instruments run_game with zones for myship.move, enemies.move (split into
realize and move_pass), hit_structure, and hit_bolt. Adds KOBO_STRESS=N
to keep the enemy pool topped up with rocks for stable per-frame timing.
Why: the entity/render pipeline refactor in BENCH.md needs a comparable
baseline before any layout changes. Without a stress workload the natural
gameplay enemy count is too noisy to compare across commits.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
The audio engine queues sound-control commands (PATCH, PAN, PLAY,
STOP, etc.) into an sfifo for the audio thread to consume from inside
_run_commands(). The FIFO was sized at 128 commands, which was fine
under SDL 1.2's push-callback model: the driver fired callbacks at a
predictable cadence that drained the FIFO promptly.
SDL 3's pull-stream model on macOS calls our audio callback ~25-30 ms
apart with 4096-byte (1024-sample) chunks. Under sustained
multi-channel activity (e.g. holding fire in Classic mode, where
xkobo_shot generates several commands per shot at ~33 Hz), the FIFO
fills faster than the audio thread drains it and overflows. Each
overflowed command is a silently dropped sound event — audibly
manifest as unevenly-spaced shot sounds.
Bumping to 1024 gives the FIFO plenty of headroom (the drain rate at
~250 Hz handles thousands of commands/sec; the FIFO only needs to
absorb burst arrivals between drain ticks).
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Brings the rendering, audio, input, and joystick layers from SDL 1.2
era APIs to native SDL 3, removes the ~2300-line glSDL OpenGL shim
that is now obsolete, and strips all the dead SDL 1.2 video-surface
state machinery (page flipping, hardware-surface flags, depth/driver
selection).
Major changes:
- Build system: find_package(SDL3 CONFIG) + SDL3::SDL3 /
SDL3_image::SDL3_image. sdl12-compat is no longer needed.
- Renderer: SDL_CreateWindow + SDL_CreateRenderer + a streaming
SDL_Texture replaces SDL_SetVideoMode + SDL_Flip. The CPU-side
back-buffer (softbuf) is now always ARGB8888 and uploaded once per
frame. SDL_SetRenderLogicalPresentation handles letterbox scaling.
- glSDL shim deleted (graphics/glSDL.{c,h}, ~2300 lines).
- Surface API: SDL_FreeSurface→SDL_DestroySurface, SDL_FillRect→
SDL_FillSurfaceRect, SDL_DisplayFormat→SDL_ConvertSurface(ARGB8888),
SDL_CreateRGBSurface→SDL_CreateSurface, SDL_SetColorKey/SetAlpha→
SDL_SetSurfaceColorKey/AlphaMod, surface->format access via
SDL_GetPixelFormatDetails.
- Events: SDL_KEYDOWN→SDL_EVENT_KEY_DOWN etc., ev.key.keysym.sym→
ev.key.key, ev.button.x/y for mouse buttons, SDL_VIDEOEXPOSE→
SDL_EVENT_WINDOW_EXPOSED, SDL_ACTIVEEVENT→SDL_EVENT_WINDOW_FOCUS_LOST.
- Keyboard: SDLKey→SDL_Keycode, SDLK_KP1..9→SDLK_KP_1..9, lowercase
SDLK_p/y/n→uppercase, KMOD_META→SDL_KMOD_GUI.
- Mouse grab: SDL_WM_GrabInput→SDL_SetWindowMouseGrab (via new
gfxengine_t::window_handle() accessor).
- Text input: SDL_EnableUNICODE→SDL_StartTextInput/SDL_StopTextInput
with SDL_EVENT_TEXT_INPUT forwarded to gsm.press(-1, ch).
- Joystick: SDL_NumJoysticks/JoystickOpen→SDL_GetJoysticks (instance
IDs)/SDL_OpenJoystick/SDL_CloseJoystick.
- Audio: SDL_OpenAudio + push-callback model→SDL_OpenAudioDeviceStream
+ SDL_AudioStreamCallback (pull). The engine's existing
_audio_callback is kept; an adapter (sdl3_stream_callback) runs it
on a scratch buffer and pushes the result via SDL_PutAudioStreamData.
- AUDIO_S16SYS→SDL_AUDIO_S16, SDL_FreeWAV→SDL_free.
Cleanup:
- Removed gfxengine_t fields _doublebuf, _pages, _shadow, _depth,
_driver, xflags, broken_rgba8, backpage/frontpage and the per-page
dirty-rect arrays (collapsed to 1-D).
- Removed prefs fields videodriver, depth, doublebuf, shadow, pages,
broken_rgba8. Existing config files are still readable (silently
absorbed as obsolete keys).
- Removed the Display Depth / Buffering Mode / Software Shadow Buffer
/ Display Buffer Pages / Broken RGBA8 entries from the options
menu.
- Removed the doublebuffer()-guarded second pass of the loading-
animation nibble draw in dashboard.cpp.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
After the SDL 3 renderer migration, the engine has exactly one back
buffer (a CPU-side ARGB8888 SDL_Surface) and one presentation path
(upload to streaming SDL_Texture, RenderPresent). All the SDL 1.2
hardware-surface / page-flipping / depth / driver knobs were dead.
Removed from gfxengine_t:
- _doublebuf, doublebuffer() setter+getter
- _pages, pages() setter
- _shadow, shadow() setter+getter
- _depth + the depth argument processing in mode()
- _driver, driver() setter, gfx_drivers_t enum, GFX_DRIVER_SDL2D
- xflags + the extraflags parameter to open()
- broken_rgba8 (the OpenGL RGBA→RGBA4 workaround field)
- MAX_PAGES + backpage/frontpage + the per-page dirtyrects[]/
dirtytable[]/dirtywtable[] arrays. Collapsed to 1-D dirtytable.
- __invalidate(int page, ...) → __invalidate(rect, window).
- The per-page invalidate() switch on _pages.
- vsync() now hot-applies via SDL_SetRenderVSync (no hide/show cycle).
- mode() keeps the int-bits arg for API compat but ignores it.
Removed from prefs_t:
- videodriver, depth, doublebuf, shadow, pages, broken_rgba8.
Their config-file keys are now read as obsolete fields (silently
consumed, never re-written) so existing config files load cleanly
without breaking on unknown keys.
Removed from the options menu:
- Display Depth, Display Buffering Mode, Software Shadow Buffer,
Display Buffer Pages, Broken RGBA8.
Removed from the escape-hammering safe-video-mode reset path the
fields that no longer exist (doublebuf, pages, shadow, depth,
videodriver).
dashboard.cpp: dropped the doublebuffer()-guarded second pass of the
loading-animation nibble draw; SDL 3's single back buffer doesn't
need it.
Build clean, binary still runs.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- logger.c:103 log_write_raw() missing return in unreachable
assert(0) branch; clang flagged the missing return statement.
- logger.c:191 SDL_GetTicks() now returns Uint64 in SDL 3; widen the
printf format from %d to %llu and bump the local buffer size.
Build is clean. Remaining warnings (strncat-size in a_wave.c,
invalid-source-encoding in a_midifile.c) are pre-2007 code-style
issues unrelated to SDL.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Phase 7-8 of the migration. Replaces the per-subsystem stubs added in
the earlier "builds clean" commit with proper SDL 3 implementations.
- gfxengine: add SDL_Window* accessor window_handle() so callers that
need to talk to SDL 3's window APIs (grab, text input) can reach it.
- states.cpp mouse grab: SDL_WM_GrabInput → SDL_SetWindowMouseGrab.
Drops the local SDL_GRAB_* / SDL_WM_GrabInput stub macros.
- states.cpp text input: SDL_EnableUNICODE → SDL_StartTextInput /
SDL_StopTextInput. kobo.cpp event loop now handles
SDL_EVENT_TEXT_INPUT (in both quit_requested() and the main game
loop) and forwards each byte of the UTF-8 string as a unicode value
to gsm.press(-1, ch) so the high-score name-entry path keeps
working without any state-side changes.
- kobo.cpp joystick: full re-port of init_js / close_js using
SDL_GetJoysticks (returns SDL_JoystickID array), SDL_OpenJoystick,
SDL_CloseJoystick. options.cpp joystick enumeration already used
the new API from the previous commit.
- sound/audio.c: replace the silent stub with a real SDL 3 backend
built on SDL_OpenAudioDeviceStream + an SDL_AudioStreamCallback.
The engine's existing _audio_callback (Sint16 stereo fill-buffer)
is kept; a small adapter (sdl3_stream_callback) runs it on a
scratch buffer and pushes the result via SDL_PutAudioStreamData.
audio_lock/unlock now call SDL_LockAudioStream / SDL_UnlockAudioStream.
After this commit the binary opens an audio device successfully and
the joystick path resolves real SDL 3 calls. Renderer + audio +
input are now all on SDL 3 with no compat shims.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Step 1+2 of the SDL 3 migration. The 2300-line graphics/glSDL.{c,h}
OpenGL-on-top-of-SDL-1.2 shim is obsolete under SDL 3 (SDL_Renderer
does what glSDL did natively), and its #define overrides of SDL
surface APIs collided directly with SDL 3's SDL_oldnames.h.
- Add graphics/sdl_compat.h: a one-line umbrella that just pulls in
<SDL3/SDL.h>.
- Replace every #include "glSDL.h" with #include "sdl_compat.h" (15
files across the tree).
- Delete graphics/glSDL.c, graphics/glSDL.h.
- Drop glSDL.c from graphics/CMakeLists.txt.
- Remove GFX_DRIVER_GLSDL from the gfx_drivers_t enum and from all
switches/conditions in gfxengine.cpp.
- Drop the glSDL_VSync() call site (will be re-introduced as
SDL_SetRenderVSync once gfxengine is ported to SDL_Renderer).
- Drop the two glSDL_Invalidate() calls in window.cpp (texture
re-upload hints with no equivalent in a pure-surface compositor).
- options.cpp: remove the now-empty "OpenGL/glSDL" menu item; force
videodriver to GFX_DRIVER_SDL2D.
- prefs.cpp: collapse legacy GLSDL setting onto SDL2D on load.
After this commit the macro-collision wall is gone (renamed_-token
errors drop from many to zero). The build still fails on the next
layer of work: SDL 1.2 surface APIs that no longer exist
(SDL_FreeSurface, SDL_SetAlpha, SDL_CreateRGBSurface, SDL_DisplayFormat,
SDL_PixelFormat struct member access, SDL audio callback API, etc.).
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
This is the first commit of the SDL 1.2 -> SDL 3 migration. It lands
the build-system change and the include-path rewrites; the code does
NOT compile against SDL 3 yet, by design. Stays on this branch until
the migration is finished.
- CMakeLists.txt: find_package(SDL3 CONFIG) + SDL3::SDL3 /
SDL3_image::SDL3_image. Drops the legacy FindSDL machinery.
- glSDL.h, sprite.c, glSDL.c, filters.c, gfxengine.cpp, audio.c:
rewrite #include "SDL*.h" -> <SDL3/...> / <SDL3_image/...>.
Known wall: graphics/glSDL.{c,h} collides with SDL 3's surface API
renames (SDL_FreeSurface, SDL_FillRect, SDL_SetColorKey,
SDL_SetClipRect, SDL_bool). The next phase deletes the entire glSDL
shim and rewrites graphics/ against SDL_Renderer / SDL_Texture.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Replace configure.in + 7 Makefile.am files with a CMake build:
- Top-level CMakeLists.txt with feature detection (SDL 1.2,
SDL_image, OpenGL, optional OSS/ALSA), platform-specific install
layouts (macOS .app bundle, Windows simple bundle, Unix), and a
shared kobo_deps INTERFACE target for flags and link libraries.
- Sub-CMakeLists for graphics/, sound/, eel/ static libraries.
- aconfig.h.cmake.in and Info.plist.cmake.in templates (minimal:
only the HAVE_* macros actually referenced by the code).
Remove all autotools machinery: configure, configure.in, aclocal.m4,
Makefile.am files, generated Makefile.in files, install-sh, missing,
depcomp, compile, mkinstalldirs, config.guess, config.sub, the
buildpkg.sh Solaris helper, and the cfg-* configure shortcuts.
Update .gitignore for CMake out-of-tree builds, and point
run-kobodl.sh at build/kobodl (overridable via KOBODL_BIN).
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- filemap.cpp: fix inverted ternary in getkey() that dereferenced ref
when ref==NULL; remove dead strncpy in recurse_get(); log a warning
when addpath() silently truncates keys longer than 8 chars.
- screen.cpp: replace lone strcpy with memcpy.
- enemies.h: brace nested if/else blocks to silence dangling-else
warnings (no behavior change).
- myship.cpp: replace three 8-case direction switches (movement and
twice in shot_single) with dir_dx[]/dir_dy[] lookup tables;
extract free_bolt() helper to deduplicate five copies of the
boltst/free_obj/NULL pattern.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Three source patches the modern clang/C++ frontend requires:
- graphics/window.cpp: strchr on const char* now returns const char*
- filemap.cpp, prefs.cpp: C++11 forbids no-space concatenation of a
string literal with an adjacent macro identifier
Autotools state regenerated with autoreconf -fi against current
autoconf/automake so configure can be run on macOS 26 / Apple Silicon.
Added run-kobodl.sh to launch the binary from the source tree (the
compiled-in data paths assume a .app bundle), and a .gitignore for
build artifacts.
Out-of-tree dependency: SDL_image 1.2 must be built with
--disable-imageio. Apple's ImageIO backend in IMG_ImageIO.m yields
SDL_Surfaces with all-zero pixel data on current macOS, leaving every
sprite invisible; libpng decodes correctly.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Import existing source tree; original VCS history is no longer available.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>