- tools_pc/bundle-linux.sh: package a Linux build (exe + README + licenses +
prepare-assets) as a .tar.gz. Mirrors bundle-win.sh's ROM / size guards and
prepare-assets assembly. No shared libs bundled — the generated README tells
the user to install SDL2 / zlib / libGL from their distro.
- tools_pc/dist/README.md.in: made OS-neutral via @PLATFORM@ / @EXE@ / @DEPS@ /
@LICENSE_EXTRA@ tokens; refreshed the known-issues list (dropped the stale
"ladders don't work" line — D177 fixed M-36; added black sky + missing HUD
weapon icon).
- .github/workflows/ci.yml: linux-build now packages + uploads the Linux
bundle; the release job attaches both platforms to the draft pre-release.
- .github/release-notes.md: rewritten for the two-platform NTSC-U alpha with
the prepare-assets step and current known issues.
Alpha scope: NTSC-U only, one-time prepare-assets step (Part A launcher and
Part B native converter from release-dropin-rom-plan.md remain follow-ups).
Claude-Session: https://claude.ai/code/session_01V4pK3pT83YnPsBp3Ey4RP2
gbi.h's gDP* DL macros use _SHIFTL/_SHIFTR without defining them; game TUs
pull them in via <ultra64.h>. This is the first port/src TU to build a
display list, and on GCC/ld the missing macro became an undefined symbol
(MinGW's include chain happens to provide it). Define the two pure macros
locally, verbatim from PR/mbi.h -- including mbi.h itself drags in N64 OS
headers that shadow libc.
- Fix invisible text: drawText passed the measured text w/h into textRender's
width/height args, which are the on-screen CLIP rect (not the text size), so
every glyph failed the clip test. Pass the full 2D viewport like bondview2.c's
debug-text path.
- Mouse UI: hover-to-highlight; left-click in the value column toggles/cycles;
drag sliders; red X close box (clicking a row's label only focuses it, so
selecting can't flip a toggle). inputSuspendForOverlay() forces the OS cursor
free + visible while the overlay owns the mouse.
- New rows: Fullscreen (live) and Resolution (windowed presets filtered to the
desktop, live resize + recenter). SDL window ops are posted from the
scheduler thread and drained host-side in videoPumpEvents.
- Layout recomputed for the wide BankGothic font: right-aligned values, wider
row pitch, all 13 rows fit the ~240-unit 2D space; nothing clips.
Port-layer only. Links clean; runtime-verified on a real display (front end +
-level_09): layout, hover/click/drag, live VSync/filter/fullscreen/resolution.
The M-37 scratch note (D176a-sky-rootcause.md) was never committed. Re-derive
the complete format from src/game/sky.c and include/PR/gbi.h:
- skyRenderTri/skyRenderFull emit a verbatim N64 RDP triangle command
(2-word header + 3 edge-slope pairs + 16-word shade block + 16-word
texture block) chopped into gImmp1(G_RDPHALF_1/_CONT) pairs, terminated
by G_RDPHALF_2. GE's RSP ucode DMAs it straight to the RDP.
- sub_GAME_7F097388 already yields complete screen-space verts; the
projection math is endian-clean and is not the defect.
Two implementation paths documented with a recommendation (Path B: an
#ifdef PORT emit swap in sky.c, first; Path A: an RDP-tri decoder in
gfx_pc.cpp, as the long-term answer). Word layout cross-checked against
gbi.h packing conventions.
sync_framerate_with_timer() runs inline on the scheduler thread
(osSpTaskStartGo -> gfx_run -> swap_buffers_begin), so a low frame cap
blocks VI-retrace delivery and drags every game thread down to the cap
rate rather than just dropping presented frames. There is no N64 analogue
(RSP/RDP/VI are separate silicon; the CPU never waits on them).
Clamp 0 < FpsCap < 30 to 0 (uncapped) with a warning, at both:
- videoInit, so a bad ge007.ini value (e.g. dinged to 10 via the F10
options overlay) is normalised and persists sane on next configSave();
- gfx_sdl_set_target_fps, the fast3d chokepoint, which also covers the
overlay's live set path.
Caps >= 30 are unchanged. Default (FpsCap = 0) is byte-identical to
golden (-level_09 framediff 3/3). The real fix — moving frame pacing off
the scheduler thread — is documented as owed in findings.md D186.
- findings.md / GRAPHICS-BACKLOG.md: -level_22 (Statue, night) is a clean
no-input headless repro of the D176(a) black sky — bare boot, frame ~360,
upper half pure black. Best verification target for the RDPHALF sky-tri
decoder. Note -level_36/-43 intro cams face the terrain (not a sky test
without GE_INPUTSCRIPT); -level_29 top-of-frame is dim, not black.
- GRAPHICS-BACKLOG "D74 wrap-block": struck through — the dead-code / OOB
in-vertex-loop wrap block was reworked and hoisted to a per-texunit
pre-wrap window behind Video.WrapFix at M-30 (RC3/D167). No longer latent.
building.md listed only d43 + d69; d88 (per-level Usetup*Z stage setup,
appended to the pccg dir) was omitted, so a source build per these
instructions produced a game that crashed on first level load.
The non-Debug branch unconditionally appended -Og, which (last -O wins)
overrode RelWithDebInfo's -O2 for every TU. -Og is a ~10% frame-time
regression on the software RSP/rasteriser hot path and is not what a
release/playtest build wants. Restrict -Og to explicit Debug builds.
Note: does NOT fix the ~1 fps issue under investigation — the software
renderer is ~950 ms/frame at both -Og and -O2 on this box; this is just
the correct default.
portHeartbeatCheck fired on any >3s frame gap and re-fired every 2s,
spamming ge007.crash.log with thread dumps during gdb pauses, level
loads, and SIGTERM teardown on a slow/software-GL setup. Widen to 8s and
stop after 3 reports. A genuine hang is permanent so it still gets caught.
CMakeLists.txt: CMAKE_{C,CXX}_COMPILER_LAUNCHER=ccache when found (no-op
otherwise); game sources are frozen so branch-switch rebuilds get near-free.
build-pc.sh: BUILD_DIR=${BUILD_DIR:-build-pc} so parallel agents/worktrees
can use separate build dirs.
- gfx_opengl_start_draw_to_framebuffer: fb_id 0 now binds GL framebuffer 0
(the window) instead of framebuffers[0].fbo, a generated attachment-less
FBO. Harmless on Windows (verified: -level_09 renders, mean RGB ~80);
strict GL (Mesa) treats the incomplete FBO as an error and drops draws.
- GE_PCDUMP / F12 screenshots move to a pre-swap hook (gfx_pre_swap_hook,
called from gfx_sdl_swap_buffers_begin before SDL_GL_SwapWindow). Reading
the back buffer AFTER the swap is undefined on buffer-exchange drivers
(Mesa/WSLg) -> black capture. Also: read GL_BACK explicitly, glFinish
first, GL_PACK_ALIGNMENT=1 (639*3 is not 4-aligned -> row shear), and log
any glReadPixels error.
Windows -level_09 verified: renders, no crash, capture non-black.
The no-PIE default base 0x400000 puts .data/.bss around 0x00500000-
0x00600000, colliding with the address space fast3d's seg_addr() decodes.
A game-built display list embeds &symbol (osVirtualToPhysical identity
shim); seg_addr sees w1 < 0x800000, treats it as a K0-physical DRAM
offset and remaps to 0x80xxxxxx (zeroed DRAM mirror) -> 'unknown GBI
opcode 0x00 at 0x80575d40' twice per frame -> DL walk aborts.
Move the image to 0x20000000 -- the gap between segmented addrs
(<0x10000000), the cart map (0x10000000), the D131 module range
([0x40000000,0x70000000)) and DRAM (>=0x70000000). seg_addr passes
0x20xxxxxx through untouched. Still <2GiB so -mcmodel=small relocations
are fine (0x140000000 needed mcmodel=large). ELF analogue of the Windows
0x140000000 base. D146 opcode-0x00 errors: 2/frame -> 0.
The N64 <stdlib.h> stub declares neither. MinGW's other CRT headers leak
them; a strict host GCC (Linux/macOS, -std=gnu11) does not, so getenv()'s
64-bit char* return is assumed int and truncated -> configGetFrameDump
(GE_PCDUMP path) dereferenced a low-32 pointer and segfaulted. Add K&R
decls: pc_protos.h (D38 mechanism -> game TUs via <ultra64.h>) and the
port/shim/stdlib.h non-Windows branch (-> port TUs that #include
<stdlib.h>). K&R form stays compatible with the real prototypes; malloc/
calloc/free are already covered elsewhere so not repeated.
First runtime bring-up on Linux (Ubuntu, gcc-14). -level_09 now renders
past frame 1200 crash-free with a clean shutdown, matching Windows.
- port/src/romdata.c: no-PIE loads the image at 0x400000, so the D34
'clear low 32 bits of a symbol address' gives 0 -> false 'image base not
4GiB-aligned' abort. The ANIM_DATA_* lvalues are only ever address-taken
(never dereferenced), so fall back to a fixed 4GiB-aligned sentinel
(0x100000000) for g_pc_animdata_base.
- port/src/main.c: the 'image must load at 0x140000000' assertion is
PE-specific (.refptr base relocations); ELF no-PIE resolves dram_syms.s
absolute symbols literally regardless of load address. Guard it to
PLATFORM_WINDOWS.
- port/src/libultra.c: mmap game-thread stacks with MAP_32BIT so they sit
in the low 2GiB. The decomp truncates stack-buffer pointers to u32 (e.g.
in texLoad); glibc pthread stacks live above 4GiB and
those truncations corrupt. One fix covers the whole idiom class. Windows
pthread stacks are already low.
- src/game/image.c: belt-and-braces #ifdef PORT -- align compbuffer at
uintptr_t width (identical result on N64).
- CMakeLists.txt: drop the -Ttext-segment experiment (needs -mcmodel=large;
breaks R_X86_64_32 relocs) -- comment only.
Windows: build 240/241, -level_09 crash-free past frame 1440.
crashDumpThreads was defined only in the PLATFORM_WINDOWS block; the
kernel-heartbeat watchdog in port/src/libultra.c references it
unconditionally -> undefined reference at link on Linux. Add a
PLATFORM_LINUX implementation: log the live thread roster + the watchdog
thread's own backtrace (a full cross-thread stackwalk needs signal-based
peer unwinding -- deferred). Linux-only block; Windows unaffected.
The decomp's src/sched.h (N64 OSSc* scheduler) is on the include path and
shadows the system header. libstdc++'s <thread>/<mutex> gthr headers in the
fast3d C++ TUs #include <sched.h> and need the real one (cpu_set_t,
sched_yield) -- with -std=gnu++20 libstdc++ defines _GNU_SOURCE so
<pthread.h> also references cpu_set_t. New shim: C++ -> generated
hostsched.h (absolute path, hoststring pattern); C -> #include_next so
src/sched.h still wins as before. Windows build verified green (240/241).
<stddef.h> is compiler-internal (lib/gcc/<triple>/<ver>/include), not in
bin/../include on a native Linux GCC, so the MinGW-layout guess produced
/usr/include/stddef.h which does not exist -> fatal error in every fast3d
C++ TU. Ask the compiler with -print-file-name=include/stddef.h and fall
back to the old path. MinGW resolves to its own lib/gcc internal copy;
Windows build verified green (240/241).
- CMakeLists: CMAKE_C_EXTENSIONS/CXX_EXTENSIONS ON. Strict -std=c11/c++20
define __STRICT_ANSI__, which disables glibc _DEFAULT_SOURCE and hides
clock_gettime(CLOCK_REALTIME) etc. that port/src/libultra.c needs.
-std=gnu11/gnu++20 is GCC's default and what MinGW already used.
- port/src/{pccg,pcmodels,romdata}.c: local 'extern int snprintf(...,
unsigned long long maxsize, ...)' decls conflict with glibc's size_t one
(same bug already fixed in pc_protos.h in M-39); spell it size_t.
- libultra.c: drop the _GNU_SOURCE define (pulls cpu_set_t refs into
<pthread.h> without <sched.h>); the D60 POSIX stack-bounds diagnostic
now just prints n/a rather than needing pthread_getattr_np.
Windows: full rebuild 240/241, -level_09 crash-free past frame 1740.
- pc_protos.h: the vestigial 'long long time()' prototype conflicts with
glibc's time_t time(time_t*); no game code calls time(), guard it to
_WIN32 (MinGW shadows <time.h> with the N64 stub; POSIX gets the real one).
- n64stubs.c: don't redefine bcopy/bzero on POSIX -- glibc exports them
(via <strings.h>, size_t sizes); the local int-signature definitions
conflict. _WIN32-only now.
- libultra.c: #define _GNU_SOURCE on POSIX for clock_gettime(CLOCK_*) and
pthread_getattr_np; guard the D60 VirtualQuery committed-memory probe and
GetCurrentThreadStackLimits behind PLATFORM_WINDOWS with a
pthread_getattr_np-based stack-bounds fallback for the diagnostic path.
Windows build verified green.
glibc's <strings.h> (pulled in transitively, e.g. via _GNU_SOURCE in
port/src/crash.c) declares these with size_t sizes; the N64 prototypes
here use int, which GCC 14 rejects as conflicting types. On
PORT && !_WIN32 include <strings.h> and drop the local decls. Windows
(PORT+_WIN32) and the N64 build (no PORT) keep the original prototypes
unchanged; Windows build verified green (239/239).
GCC 14 hard-errors enum(CHRFLAG)->pointer as 'incompatible type for
argument' with no controlling warning flag (not downgraded by
-fpermissive / -Wno-error=int-conversion). Line 157 of the same WIP-decomp
function already casts the identical (Model*)var_v0->chrflags expression;
match it on line 162. Codegen-identical.
SDL2's headers (SDL_stdinc.h, SDL_hidapi.h) and glibc's <bits/wchar2.h>
reference wchar_t. In C it is a library typedef owned by the compiler's
shadowed <stddef.h>, which our shim replaces with builtin-derived types
on Linux/macOS. Add wchar_t from __WCHAR_TYPE__, guarded by _WCHAR_T so a
real <stddef.h> reached another way stays a no-op. C-on-Linux/macOS
branch only; MinGW and C++ paths untouched.
The vestigial u8* param2/param3 receive an AMMOTYPE enum and a u32 at
both callsites. GCC 14 treats enum->pointer as a hard 'incompatible type
for argument' error that -fpermissive does not downgrade (unlike the
int<->pointer permerrors). Guard the definition with #ifdef PORT to match
the callsites' actual types; params stay unused so codegen is unchanged
and the N64 source path is literally untouched.
GCC 14 turned int<->pointer (known prototype), incompatible-pointer arg
types, and implicit int/function into hard errors. The WIP decomp still hits
these (append_text_picked_up in propobj.c passes AMMOTYPE/u32 to vestigial
u8* params). -fpermissive downgrades them to warnings — the C analogue of
the existing -Wno-error= lines, and does not touch game code. Non-MinGW C only.
- src/bondtypes.h's `#define inherits struct` makes the base an unnamed
tagged member and reads its fields directly. mingw32 GCC enables this via
implicit -fms-extensions; native GCC does not -> "'ObjectRecord' has no
member named 'type'". Add -fplan9-extensions (C only) for non-MinGW.
- Pin the CI job to ubuntu-24.04 + gcc-14 so the first Linux bring-up chases
real portability gaps, not old-GCC pedantry (dev toolchain is GCC 16).
Next GCC-on-Linux breaks after the stddef fix (linux-build CI):
- port/shim/stdarg.h routed to the decomp include/stdarg.h, which defines
va_list from __builtin_va_list but NOT the __gnuc_va_list alias that
glibc's <stdio.h> references — 'unknown type name __gnuc_va_list' across
every TU that pulls <stdio.h>. Add a Linux/macOS branch that provides
va_list / __gnuc_va_list / va_* straight from compiler builtins and does
not drag <ultra64.h> into system-header parsing. Windows branch unchanged.
- pc_protos.h declared snprintf's size argument as 'unsigned long long' —
correct on MinGW (LLP64) but not glibc, where size_t is 'unsigned long':
'conflicting types for snprintf'. Spell it size_t (now visible via the
stddef fix); identical to ull on MinGW.
Windows: both changes are no-ops on MinGW (stdarg Windows branch byte-identical;
size_t == unsigned long long).
The first cut routed C TUs to hoststddef.h unconditionally. That regresses
MinGW: its <stddef.h> defines `errno (*_errno())`, which collides with
PR/os.h's `u8 errno;` field. And hoststddef.h's bin/../include/stddef.h path
does not exist on Linux anyway.
Split three ways: C++ -> hoststddef.h (unchanged); C on Windows -> the empty
N64 stub (unchanged, pre-existing behaviour); C on Linux/macOS -> size_t /
ptrdiff_t / NULL / offsetof from __SIZE_TYPE__ / __PTRDIFF_TYPE__ /
__builtin_offsetof (always predefined, no header, no collision).
Verified: Windows ntsc-final builds 240/240, -level_09 boots crash-free.
Two GCC-on-Linux build breaks that MinGW masked (surfaced by the new
linux-build CI job):
- port/shim/stddef.h routed C TUs to the empty N64 include/stddef.h stub.
MinGW's CRT headers leak size_t/ptrdiff_t transitively; glibc/GCC define
them only in the compiler's own <stddef.h>, so include/PR/ultratypes.h's
non-N64 path ("typedef ptrdiff_t ssize_t") failed with "unknown type name".
Route C TUs to hoststddef.h too (C++ already was). Additive — the active
N64 stub typedefs nothing.
- CMakeLists: dram_syms.s absolute symbols at 0x70000000 need a non-PIE image
on Linux exactly as they need --disable-dynamicbase on Windows. Add an
elseif(UNIX AND NOT APPLE) branch: -fno-pie / -no-pie.
Windows unaffected (host <stddef.h> is strictly more correct than the stub;
the MINGW linker branch is unchanged).
validate only CMake-configures on Ubuntu; it never compiles, so GCC-on-Linux
strictness gaps (implicit declarations, -Wint-conversion, missing includes,
MinGW-only builtins) stay invisible until someone builds on a real Linux box.
Add linux-build: system GCC, `cmake --build` for ntsc-final. Green here means
"compiles + links on Linux", not a runtime claim (the port has never run on
Linux). Not wired as a required check yet.
Pairs with the romdata POSIX mmap patch on this branch (per docs/dev/LINUX-PORT.md).
The cart-base map at 0x10000000 (so absolute ROM-asset symbols are live host
pointers) was Windows-only; the #else branch was a stub that logged
"not implemented" and fell to the degraded heap-copy path.
- Factor the post-map work (memcpy + pcmodels/pccg sidecar load + D55 RLE
header fixup) into romdataFinishCartMap(), shared by both paths.
- POSIX #else: anonymous mmap at CART_BASE with MAP_FIXED_NOREPLACE where
available; on any failure munmap + fall through to the identical heap copy.
- romdataDestroy() munmaps on POSIX (length tracked in mappedLen).
Verified on Windows: builds 242/242, -level_09 boots crash-free (identical
behaviour with/without the refactor). Linux compile + runtime verification
owed — see docs/dev/LINUX-PORT.md. No behaviour change on Windows.
building.md listed only d43 + d69; d88 (per-level Usetup*Z stage setup,
appended to the pccg dir) was omitted, so a source build per these
instructions produced a game that crashed on first level load.