mirror of
https://github.com/jkdansereau/goldeneye-pc-port.git
synced 2026-10-23 15:52:42 +02:00
v0.6.0: issue fixes and v0.7.0 roll-ins before release
Issue fixes: - D643 (#145 b/c, #149): Original control style is a strict virtual N64 pad (native buttons in stage/menu/watch/tank, per-seat Input.N64Pad remap, Input.N64StickFeel) - D605 + D645 (#145 a): Invert look reaches the original styles; in Original it is the game's own Look up/down, synced with the F10 row - D606 (#144): optional always-visible mission timer - D644 (#146): IA4 decodes as the RDP's I3:A1 (file-select crest drew on a dark square) - D581 (#151): open on the real primary monitor (SDL display 0) and centre every window placement in the display's global bounds; the earlier "display at (0,0)" rule still picked the left monitor - D580 (#153, #156): runtime check of the propDef type/end-of-list fix - D646: F10 dropdown popup stays inside the settings box Frame rate (#148): - D578: Auto follows the display up to 240 (4 presents per sim tick, the renderer's ceiling); 165 and 240 in the Frame rate cap row Rolled in from release/v0.7.0: - D632: release the GL context + window before exit (BSOD 0x119) - D626: Windows per-monitor DPI awareness - D639: exclusive fullscreen uses the desktop mode - verify.sh: a frame-size mismatch fails; demobatch/flicker_scan harness Release gate, licenses, docs: - NOTICE: macOS packaging and pi/ coverage; runtime libraries bundled in the downloads (SDL2/SDL3, zlib, MinGW runtime, macOS GCC runtime) - macOS package ships SDL3, GCC COPYING.RUNTIME and the GPL-3.0 text (tools_pc/dist/GPL-3.0.txt) - README: release date 2026-10-13, "Why play this port", Legal (Apple, runtime licenses), frame-rate text; site date tokens - tools_pc/release_gate.sh: one-command win/deck/mac release checks - release notes, findings D578/D580/D581/D643-D646 + index, porting notes
This commit is contained in:
1 parent
884b8fb8a5
commit
162691a3f5
29 files changed
+3000
-105
No files matched your search
+67
-14
@@ -15,8 +15,9 @@ This release is v0.5.0 plus frame rates above 60, an experimental macOS port
|
||||
and a batch of fixes.
|
||||
|
||||
- **Frame rates above 60.** The *Frame rate cap* (`Video.FpsCap`) now offers
|
||||
**90, 120 and 144**, and an **Auto** setting that matches your display's
|
||||
refresh rate (up to 144). Auto is the default on fresh installs; an existing
|
||||
**90, 120, 144, 165 and 240**, and an **Auto** setting that matches your
|
||||
display's refresh rate (up to 240; 165 and 240 have had less testing than
|
||||
90 to 144). Auto is the default on fresh installs; an existing
|
||||
`ge007.ini` keeps its value. Extra frames are blended between game ticks, so
|
||||
the game still simulates at its original rate. It costs one game tick of
|
||||
extra latency, does nothing on a 60 Hz display, and the *Original N64*
|
||||
@@ -32,7 +33,14 @@ and a batch of fixes.
|
||||
native-widescreen picture on front-end screens, and the first launch opening
|
||||
on the wrong monitor (#151).
|
||||
- **Crash fixes:** the Linux end-credits crash (#152) and a crash when a guard
|
||||
spawned wearing a hat (#153).
|
||||
spawned wearing a hat (#153, #156).
|
||||
- **Original controls are now original (#145, #149).** With *Control style*
|
||||
set to Original, a gamepad behaves as an N64 controller: South is A, West is
|
||||
B, and the game decides what each button does, so B opens a door or reloads
|
||||
(not both), A changes weapon, and B goes back in menus. *Invert look* works in
|
||||
every original style and stays in sync with the watch's *Look up/down*.
|
||||
- **Mission timer (#144)**, the file-select crest drawing on a dark square
|
||||
(#146), and a fix for a Windows exit crash.
|
||||
|
||||
Checked on PC at 120 Hz with VSync on a G-Sync display, and on the Steam Deck
|
||||
at 90 fps.
|
||||
@@ -43,12 +51,13 @@ at 90 fps.
|
||||
|
||||
### Frame rate
|
||||
|
||||
- **Auto, 90, 120 and 144** join 30 and 60 in the F10 *Frame rate cap* row.
|
||||
- **Auto, 90, 120, 144, 165 and 240** join 30 and 60 in the F10 *Frame rate cap* row.
|
||||
Above 60 the port draws extra frames between game ticks by blending the
|
||||
previous and current frame. It only applies when the display is faster than
|
||||
60 Hz; on a 60 Hz display nothing changes.
|
||||
- A fresh install uses **Auto**, so a 90 Hz screen runs at 90 and a 144 Hz
|
||||
monitor at 144. The *Original N64* display mode sets the cap to 60.
|
||||
- A fresh install uses **Auto**, so a 90 Hz screen runs at 90, a 144 Hz
|
||||
monitor at 144 and a 240 Hz monitor at 240 (faster displays hold at 240).
|
||||
The *Original N64* display mode sets the cap to 60.
|
||||
- A fast camera turn, or a large change in what is on screen, falls back to a
|
||||
full frame instead of a blend.
|
||||
- The Steam Deck at 90 is good on most levels (see the known issues).
|
||||
@@ -59,12 +68,32 @@ at 90 fps.
|
||||
automatically by CMake). The 64-bit address fixes this needed (textures,
|
||||
input, animation and multiplayer crashes) are in the shared code and have no
|
||||
effect at base 0 on Windows and Linux.
|
||||
- Build from source with Homebrew's GNU `gcc`; there is no download. The
|
||||
x86_64 macOS code (Julio C. Rocha's #88) is in the tree but was never
|
||||
- There is a `macos-arm64` download, or build from source with Homebrew's
|
||||
GNU `gcc`. The x86_64 macOS code (Julio C. Rocha's #88) is in the tree but was never
|
||||
verified on Intel hardware.
|
||||
|
||||
### Controls
|
||||
|
||||
- **Original control style = an N64 controller** (D643). Every press sends the
|
||||
real N64 button in levels, menus, the watch and the tank; no port-only
|
||||
actions are added. Default layout: South = A, West = B, right trigger = Z,
|
||||
left trigger / RB = R, LB = L, right stick = C-buttons, D-pad and Start as
|
||||
is. Each N64 button can be moved per player in `ge007.ini`
|
||||
(`Input.N64Pad.A = a`, `Input.N64Pad2.B = x`, ...). `Input.N64StickFeel = 1`
|
||||
(default in Original) gives the N64's linear stick range; 0 keeps the
|
||||
previous feel. The Modern style and keyboard/mouse are unchanged.
|
||||
- **Invert look** reaches every original style (D605) and, in Original, is the
|
||||
same setting as the watch's *Look up/down* (D645).
|
||||
|
||||
### Settings and diagnostics
|
||||
|
||||
- **Mission timer** (D606, #144): an optional always-visible timer, off by
|
||||
default (F10).
|
||||
- F10 dropdowns stay inside the settings box instead of covering the help
|
||||
text (D646).
|
||||
- Windows: per-monitor DPI awareness, so fullscreen on a scaled 4K display is
|
||||
native resolution (D626); exclusive fullscreen uses the desktop mode (D639).
|
||||
|
||||
- The old front-end **Velocity** pointer mode is removed; the retired ini keys
|
||||
are ignored quietly (D574).
|
||||
- The Linux/macOS **update check** no longer fails on a response over 64 KB
|
||||
@@ -120,6 +149,17 @@ the folder:
|
||||
xattr -dr com.apple.quarantine goldeneye-pc-port-<version>-macos-arm64
|
||||
```
|
||||
|
||||
Or download it in Terminal, which skips the block entirely (`curl` does not
|
||||
mark the file for Gatekeeper):
|
||||
|
||||
```
|
||||
curl -L https://github.com/jkdansereau/goldeneye-pc-port/releases/download/v<version>/goldeneye-pc-port-<version>-macos-arm64.tar.gz | tar xz
|
||||
```
|
||||
|
||||
Without Terminal: after the first launch is blocked, open System Settings →
|
||||
Privacy & Security and click **Open Anyway** next to the `ge007.aarch64`
|
||||
message.
|
||||
|
||||
I have only run it on one M3 Mac, so please report what Mac and macOS you
|
||||
tried and what happened.
|
||||
|
||||
@@ -139,6 +179,7 @@ Full steps, controls and troubleshooting are in the bundled `README.md`.
|
||||
| Jungle can feel slightly less smooth at 90 fps than other levels | Feel | Frame cap 60 |
|
||||
| Cradle's turret explosions may draw wrongly | Cosmetic; reported, not yet investigated | None |
|
||||
| After alt-tabbing away, the picture can look hazy until it settles; fast repeated alt-tab is wonkier and takes longer | Cosmetic, Windows | It recovers on its own; alt-tab again or give it a couple of seconds |
|
||||
| macOS: the end credits may crash | Ending only, macOS only | Your progress is already saved by then |
|
||||
| macOS (Apple Silicon) is experimental, tested on one Mac, and not notarized | Platform | Run `xattr -dr com.apple.quarantine <folder>` before the first launch; reports welcome |
|
||||
| PAL and JP ROMs aren't supported in release packages | NTSC-U only | Use an NTSC-U ROM. Both regions convert, build and boot from source; packaging is still open |
|
||||
| Changing aspect ratio inside a level can briefly glitch the gun/hand model, rarely | Cosmetic, one-off | Change the ratio from the front-end PC Options, or accept it |
|
||||
@@ -164,9 +205,19 @@ workarounds, is the
|
||||
cleared (D579 follow-up).
|
||||
- **#151** the first launch opens on the primary monitor (D581).
|
||||
- **#152** Linux end-credits crash: a garbage text pointer is rejected (D582).
|
||||
- **#153** hat-spawn crash: the object record type was read from the wrong byte
|
||||
on a little-endian machine (D580).
|
||||
- **#153, #156** hat-spawn crash: the object record type and the end-of-list
|
||||
marker were read from the wrong byte on a little-endian machine, so the search
|
||||
could run past the end of the list (D580).
|
||||
- **#108** SFX / text-bank pointer guards (D573).
|
||||
- **#145 / #149** Original control style as an N64 controller (D643); Invert
|
||||
look in the original styles (D605, D645).
|
||||
- **#144** mission timer (D606).
|
||||
- **#146** file-select crest on a dark square: 4-bit IA textures now decode as
|
||||
the N64 does (D644).
|
||||
- **Exit crash (BSOD 0x119) on some Windows GPUs:** the GL context and window
|
||||
are released before the process exits (D632).
|
||||
- Windows DPI awareness (D626), exclusive fullscreen mode (D639), F10 dropdown
|
||||
placement (D646).
|
||||
- Velocity removal, update-check fix, `ge007.log`, HUD-150 sliders, preset
|
||||
scope (D574-D577, D440).
|
||||
- **macOS arm64:** one address-model header, the 64-bit pointer fixes and a
|
||||
@@ -174,10 +225,12 @@ workarounds, is the
|
||||
|
||||
## Verification
|
||||
|
||||
I did not re-run the three-platform reference-frame gate for this release; the
|
||||
last full run was for v0.5.0. For v0.6.0 I checked the frame-rate path and the
|
||||
fixes above by playing on Windows and the Steam Deck, and the macOS build with
|
||||
a 21-level sweep and live sessions.
|
||||
Windows: the 21-level reference-frame sweep passes (Streets sits at the edge of
|
||||
its tolerance and flips run to run with or without this release's changes).
|
||||
Steam Deck and macOS: all 21 levels load, play and quit without a crash (their
|
||||
reference frames are not yet comparable, so no pixel check). The fixes above
|
||||
were also checked by playing on Windows, with the original controls compared
|
||||
against the issue reports rather than an N64 pad.
|
||||
|
||||
## Thanks
|
||||
|
||||
|
||||
@@ -205,3 +205,11 @@ tools_pc/sweep-captures/**/*.ppm
|
||||
# referenced by no project tooling. Never commit.
|
||||
/Python/
|
||||
docs/media/*.mp4
|
||||
|
||||
# mod-loader tool build artifacts
|
||||
/tools_pc/modpatch/modpatch.exe
|
||||
|
||||
# demo harness data (tools_pc/demobatch.sh): personal recordings + run output
|
||||
demos/
|
||||
demobatch/
|
||||
levelbench/
|
||||
@@ -10,6 +10,9 @@ repository:
|
||||
* the port layer port/
|
||||
* the PC build system CMakeLists.txt, cmake/, build-pc.sh
|
||||
* PC-port tooling tools_pc/
|
||||
* macOS packaging scripts/package-macos.sh, scripts/gen_macho_syms.py,
|
||||
scripts/strip_weak_pragmas.py
|
||||
* agent-development tooling pi/ (not part of the game build)
|
||||
* documentation docs/, README.md
|
||||
|
||||
Third-party code vendored into the port layer keeps its own license:
|
||||
@@ -18,6 +21,17 @@ Third-party code vendored into the port layer keeps its own license:
|
||||
libultraship / sm64-port lineage; see port/fast3d/LICENSE.txt.
|
||||
* port/fast3d/glad/ - generated OpenGL loader (glad), public domain / MIT.
|
||||
|
||||
Runtime libraries bundled in the downloads (not in this repository) keep
|
||||
their own licenses; each license text ships in the download's licenses/ folder:
|
||||
|
||||
* SDL2 (all platforms) and SDL3 (macOS, loaded by sdl2-compat) - zlib license.
|
||||
* zlib - zlib license.
|
||||
* Windows: the MinGW-w64 runtime (libwinpthread, libgcc, libstdc++).
|
||||
* macOS: the GCC runtime (libgcc_s, libstdc++) - GPL-3.0 with the GCC
|
||||
Runtime Library Exception 3.1. The GPL-3.0 text the macOS package ships
|
||||
is tools_pc/dist/GPL-3.0.txt (the Homebrew gcc keg carries only the
|
||||
exception text and GPL-2.0).
|
||||
|
||||
Vendored build tooling keeps its own license (none of it is compiled or linked
|
||||
into the PC port; it belongs to the N64 decompilation build):
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||

|
||||
|
||||
**GoldenEye 007 (Nintendo 64, 1997) as a native PC game for Windows, Linux
|
||||
and Steam Deck.** Native widescreen, 60 fps or higher (90/120/144 on fast
|
||||
and Steam Deck.** Native widescreen, 60 fps or higher (up to 240 on fast
|
||||
displays), 2–4 player split-screen, mouse and keyboard or a modern controller, rebindable controls
|
||||
and an in-game options menu. No
|
||||
emulator, and not a binary recompilation: the game's own
|
||||
@@ -17,7 +17,7 @@ for your PC and running from your own ROM.
|
||||
**[Download the latest release](https://github.com/jkdansereau/goldeneye-pc-port/releases)**,
|
||||
then follow the [Quick start](#quick-start).
|
||||
|
||||
[Quick start](#quick-start) · [Controls](#controls) · [Features](#features) · [Settings and files](#settings-and-files) · [Troubleshooting](#troubleshooting) · [Roadmap](#roadmap) · [Building](#building-from-source) · [Legal](#legal)
|
||||
[Quick start](#quick-start) · [Why this port](#why-play-this-port) · [Controls](#controls) · [Features](#features) · [Settings and files](#settings-and-files) · [Troubleshooting](#troubleshooting) · [Roadmap](#roadmap) · [Building](#building-from-source) · [Legal](#legal)
|
||||
|
||||
> [!IMPORTANT]
|
||||
> **You must supply your own GoldenEye 007 ROM.** This repository and its
|
||||
@@ -49,10 +49,10 @@ project is as much a study of that process as it is a port. See
|
||||
|
||||
## News
|
||||
|
||||
- **RELEASE-DATE** — **v0.6.0** ([release notes](https://github.com/jkdansereau/goldeneye-pc-port/releases/tag/v0.6.0)):
|
||||
- **Frame rates above 60.** `Video.FpsCap` now offers 90, 120 and 144, and
|
||||
an **Auto** setting (the new default on fresh installs) that matches your
|
||||
display's refresh rate. Frames between game ticks are blended from the
|
||||
- **2026-10-13** — **v0.6.0** ([release notes](https://github.com/jkdansereau/goldeneye-pc-port/releases/tag/v0.6.0)):
|
||||
- **Frame rates above 60.** `Video.FpsCap` now offers 90, 120, 144, 165 and
|
||||
240, and an **Auto** setting (the new default on fresh installs) that
|
||||
matches your display's refresh rate, up to 240. Frames between game ticks are blended from the
|
||||
previous and current frame; the game still simulates at its original
|
||||
rate. It costs one game tick of extra latency, does nothing on a 60 Hz
|
||||
display, and the *Original N64* preset caps at 60. This is a rework of
|
||||
@@ -66,9 +66,14 @@ project is as much a study of that process as it is a port. See
|
||||
for the one-line unblock. Test reports are very welcome.
|
||||
- **Fixes:** geometry vanishing when you press against a wall (#150),
|
||||
the first launch opening on the wrong monitor (#151), a Linux crash in the
|
||||
end credits (#152), a crash when a guard spawns with a hat (#153), a
|
||||
end credits (#152), a crash when a guard spawns with a hat (#153, #156), a
|
||||
sound/text pointer check that rejected valid pointers (#108), a Cradle
|
||||
catwalk shadow flicker, and stale watch pixels in native widescreen.
|
||||
- **Original controls (#145, #149):** with *Control style* set to Original,
|
||||
a gamepad now behaves as an N64 controller (South = A, West = B, the game
|
||||
decides what each does), and *Invert look* works in every original style.
|
||||
Also an optional mission timer (#144), the file-select crest no longer
|
||||
drawing on a dark square (#146), and a Windows exit-crash fix.
|
||||
- **Smaller changes:** the legacy front-end Velocity pointer mode is gone,
|
||||
the Linux/macOS update check no longer fails on a large response, every
|
||||
run is also logged to `ge007.log` next to the ini, and F10 slider labels
|
||||
@@ -201,6 +206,22 @@ one pad pick a 1.x style. Saves made with earlier builds may hold 1.2 (the port
|
||||
forced style back into the save), so when you switch to the Original control style, pick
|
||||
your style on the watch once.
|
||||
|
||||
In the Original control style the pad is a strict virtual N64 controller in
|
||||
every context (stage, menus, watch, tank): each press sends the native N64
|
||||
button and the game decides what it means (so *B* uses a door or reloads, as
|
||||
on the console). Default layout, remappable per player in the ini
|
||||
(`Input.N64Pad.<Button>`, `Input.N64Pad2..4` for players 2-4; up to two pad
|
||||
buttons each) and with `Input.N64StickFeel` (1 = linear +/-80 stick with a
|
||||
small dead centre, 0 = the Ext deadzone):
|
||||
|
||||
| Pad | N64 | Pad | N64 |
|
||||
|---|---|---|---|
|
||||
| South (A) | A | Right stick | C buttons |
|
||||
| West (X) | B | Left stick | Analog stick |
|
||||
| Right trigger | Z | D-pad | D-pad |
|
||||
| Left trigger / RB | R | Start | Start |
|
||||
| LB | L | Back | PC options overlay |
|
||||
|
||||
**Rebinding:** *Key Bindings* in the options overlay rebinds the
|
||||
keyboard and mouse (one list: movement, then actions). Controller buttons are
|
||||
rebindable per controller under *Controller* (pick a player first): set *Layout preset* to
|
||||
@@ -225,6 +246,47 @@ left/right adjusts the selected value, **A** opens a page or toggles a row,
|
||||
again) closes. With a keyboard, use `F10`, the arrow keys, Enter and
|
||||
Backspace.
|
||||
|
||||
## Why play this port
|
||||
|
||||
The questions people ask most about playing GoldenEye today, and how this
|
||||
port answers them:
|
||||
|
||||
- **Real 60 fps, not a speed hack.** The game's logic counts time in 60 Hz
|
||||
ticks. On the N64 it only drew a frame every 2–3 ticks (20–30 fps); here it
|
||||
draws every tick, so you get 60 fps with the game, physics and AI running
|
||||
at the N64's own speed. There is no CPU overclock or frame-skip patch involved,
|
||||
and the few spots in the game that behaved differently at one tick per frame
|
||||
are fixed to match the N64. Above 60 (90 to 240, or Auto), extra
|
||||
frames are blended between ticks for smoothness, at the cost of one tick of
|
||||
latency.
|
||||
- **Aiming that doesn't feel floaty.** *Aim style* picks the behavior.
|
||||
**Centered** pins the crosshair in the middle, and the mouse or the right
|
||||
stick turns the camera directly, like a modern shooter. **Original** keeps
|
||||
the N64's crosshair that moves a little before the camera follows. The stick
|
||||
has separate horizontal and vertical sensitivity, separate deadzones for each
|
||||
stick, look smoothing and a trigger threshold. The mouse has raw input, an aim
|
||||
curve, smoothing, and separate turn and aim-mode sensitivities.
|
||||
- **Dual analog and mouse and keyboard, built in.** No emulator remapping. The
|
||||
right stick looks, the triggers fire and aim, and every button is rebindable,
|
||||
for the keyboard, the mouse and each controller. Rare's Xbox control styles are
|
||||
presets. If you want the N64 exactly, the **Original** control style turns the
|
||||
pad into a strict virtual N64 controller.
|
||||
- **The whole game.** All 20 solo missions and the credits, with every
|
||||
difficulty, 007 mode and the cheats, plus 2–4 player split-screen on every
|
||||
multiplayer map. Your emulator saves (1964, Project64) load directly.
|
||||
- **Faithful by construction.** It is the game's own reconstructed source
|
||||
compiled natively, not an emulator and not a remake, so levels, guards and
|
||||
weapons behave the way the N64 cartridge does. The **Original N64** display
|
||||
preset switches every visual improvement off at once.
|
||||
- **Modern display options:** native widescreen at any aspect ratio, high
|
||||
resolutions, MSAA, adjustable FOV and draw distance, and HUD scaling.
|
||||
- **Windows, Linux, Steam Deck and (experimental) macOS** from one download
|
||||
page, with an in-game options menu on F10 or the pad's Select button.
|
||||
|
||||
What it doesn't do (yet): bots, online play, new or enlarged multiplayer maps,
|
||||
mod support, and PAL/JP ROMs are not part of this release. See the
|
||||
[Roadmap](#roadmap).
|
||||
|
||||
## Features
|
||||
|
||||
- **2–4 player split-screen multiplayer** on every MP map, with the N64's own
|
||||
@@ -238,8 +300,8 @@ Backspace.
|
||||
and *Aspect ratio → Original* shows the exact 4:3 frame.
|
||||
- **A steady 60 fps**, with VSync and a frame cap (30 or 60), including on
|
||||
low-end GPUs.
|
||||
- **Frame rates above 60:** 90, 120 or 144 fps, or **Auto** to match your
|
||||
display. Extra frames are blended between game ticks (the game still runs at
|
||||
- **Frame rates above 60:** 90, 120, 144, 165 or 240 fps, or **Auto** to match
|
||||
your display (up to 240). Extra frames are blended between game ticks (the game still runs at
|
||||
its original rate), at the cost of one tick of latency. It has no effect on
|
||||
a 60 Hz display, and the *Original N64* preset keeps 60. See
|
||||
[framerate and pacing](docs/framerate-and-pacing.md).
|
||||
@@ -355,7 +417,7 @@ MasterVolume = 100
|
||||
```
|
||||
|
||||
`Fullscreen` 1 is borderless fullscreen; `FpsCap` -1 is Auto (match the display,
|
||||
up to 144), 0 is uncapped, otherwise a fps cap (30, 60, or 90/120/144 for frame
|
||||
up to 240), 0 is uncapped, otherwise a fps cap (30, 60, or 90 to 240 for frame
|
||||
interpolation on a faster display; an existing ini keeps its value); `TextureFilter` is 0 nearest,
|
||||
1 bilinear, 2 the N64 3-point filter, 3 trilinear; `FovScale` and `DrawDistance` are
|
||||
percentages of the original (the options overlay shows the field of view in
|
||||
@@ -555,6 +617,17 @@ it comes from a download. Unpack it, then run `xattr -dr com.apple.quarantine <f
|
||||
xattr -dr com.apple.quarantine goldeneye-pc-port-<version>-macos-arm64
|
||||
```
|
||||
|
||||
**Or skip the quarantine step** by downloading in Terminal instead of a browser
|
||||
(`curl` does not mark the file, so Gatekeeper never checks it):
|
||||
|
||||
```sh
|
||||
curl -L https://github.com/jkdansereau/goldeneye-pc-port/releases/download/v<version>/goldeneye-pc-port-<version>-macos-arm64.tar.gz | tar xz
|
||||
```
|
||||
|
||||
**Without Terminal:** after macOS blocks the first launch, open System Settings →
|
||||
Privacy & Security, scroll down to the message about `ge007.aarch64` and click
|
||||
**Open Anyway** (macOS 15 removed the old right-click → Open bypass).
|
||||
|
||||
Then set up `data/` with your ROM as in the package README and run
|
||||
`./ge007.aarch64` from that folder in Terminal. If you launch it first, the process
|
||||
stalls before the window appears (it never reached `main` in my test; that is
|
||||
@@ -726,7 +799,7 @@ models used, and the harnesses that drove them:
|
||||
This is a non-commercial fan preservation/research project, in the same
|
||||
category as the many other N64 decompilation and native-port repositories on
|
||||
GitHub. It is **not affiliated with, endorsed by, or sponsored by** Nintendo,
|
||||
Rare, Microsoft, Valve, MGM, Danjaq, EON Productions, or any rights holder in
|
||||
Rare, Microsoft, Valve, Apple, MGM, Danjaq, EON Productions, or any rights holder in
|
||||
GoldenEye or James Bond. "GoldenEye 007", "007", "James Bond" and related
|
||||
marks belong to their respective owners; they are used here only to say which
|
||||
game this port runs. No official logos, box art, or marketing assets are used.
|
||||
@@ -734,8 +807,10 @@ game this port runs. No official logos, box art, or marketing assets are used.
|
||||
### What the download contains
|
||||
|
||||
- **The engine:** the `port/` layer plus the compiled decompilation.
|
||||
- **Runtime libraries** under permissive licenses (SDL2, zlib, the MinGW
|
||||
runtime); their licenses travel in the download.
|
||||
- **Runtime libraries:** SDL2 (and SDL3 on macOS) and zlib under the zlib
|
||||
license, the MinGW runtime on Windows, and the GCC C++ runtime on macOS
|
||||
(GPL-3.0 with the GCC Runtime Library Exception); their licenses travel in
|
||||
the download.
|
||||
- **The one-time asset tool**, which reads your ROM on your machine.
|
||||
|
||||
It contains **no ROM and no game assets of any kind**. Any build, yours or a
|
||||
@@ -776,4 +851,4 @@ is covered by [`NOTICE`](NOTICE), not by that license.
|
||||
|
||||
---
|
||||
|
||||
*Last updated 2026-10-10 — v0.6.0 · <a href="https://github.com/jkdansereau/goldeneye-pc-port">GitHub</a> · <a href="https://github.com/jkdansereau/goldeneye-pc-port/releases">Releases</a>*
|
||||
*Last updated 2026-10-13 — v0.6.0 · <a href="https://github.com/jkdansereau/goldeneye-pc-port">GitHub</a> · <a href="https://github.com/jkdansereau/goldeneye-pc-port/releases">Releases</a>*
|
||||
+2
-2
@@ -155,7 +155,7 @@ below).
|
||||
| Dam: grate decals vanish behind a sliding edge, wall lighting shifts with view; guard tower window/doorway view of the outside glitches | closed 2026-10-09: faithful | D583 | Present with every present exact (`allexact`) and in v0.5.0; maintainer checked 1964 (GEPD): the exact same behaviour. Original-game portal/culling artefacts, not port bugs. Candidate for the opt-in fixes row below. |
|
||||
| Opt-in fixes for original-game rendering bugs (QoL) | idea, post-1.0 (maintainer 2026-10-09) | D583 | Maintainer: a nice QoL feature would be optionally fixing artefacts the original game has, e.g. the Dam wall grates cut by a sliding portal edge and the guard-tower window/doorway outside view (D583 row above), and overlapping 2D transparent textures on Cradle sorting wrongly (also in the original). Must stay off by default (fidelity is the default) and live in `port/` or an explicit option; game-logic changes would need Rule-2 sign-off. |
|
||||
| Exclusive fullscreen picks the wrong resolution with NVIDIA "OpenGL present method = Prefer native" | open (v0.7.0, maintainer 2026-10-10) | D583 | Seen 2026-10-09 while bisecting D583; borderless is fine. Probably exclusive mode switches to the ini Width/Height instead of keeping the desktop mode. Not investigated. |
|
||||
| Deck golden captures are 1280x800 when interpolation is on | open (v0.7.0, maintainer 2026-10-10) | D578, D283 | With Auto FpsCap on the Deck's 90 Hz screen, GE_PCDUMP writes 1280x800 frames against the 640x480 Deck goldens, so every frame fails to compare (and the sweep prints PASS with "stems don't match", which is not a pass). `verify.sh` now pins `Video.DeckPresetApplied = 1` (D283 preset). Fix: dump at the window size, or re-capture the Deck goldens once. |
|
||||
| Deck golden captures are 1280x800 when interpolation is on | open (v0.7.0, maintainer 2026-10-10) | D578, D283 | With Auto FpsCap on the Deck's 90 Hz screen, GE_PCDUMP writes 1280x800 frames against the 640x480 Deck goldens, so every frame fails to compare (and the sweep prints PASS with "stems don't match", which is not a pass). **2026-10-11:** the false PASS is fixed: `framediff.py` exits 3 on a size mismatch and `verify.sh` reports it as REGRESSION ("not compared"); the capture-size fix itself is still owed. `verify.sh` now pins `Video.DeckPresetApplied = 1` (D283 preset). Fix: dump at the window size, or re-capture the Deck goldens once. |
|
||||
| Crash on save slots 1/3/4, plus a host BSOD (fltmgr.sys) | decided | #94 | Needs a repro. Two maintainer retest requests (v0.3.0, v0.4.0) got no reply. Likely D299/D301 (save-slot crash, fixed) and D344 (BSOD on exit, fixed). **Decided 2026-10-01: close as stale when the release ships**, pointing at D299/D301/D344; no GitHub activity before then. |
|
||||
| Authored mip chains not sampled for explicit-LOD textures | post-1.0 | D323 | Distant textures use driver mips instead of Rare's hand-authored chain (heaviest on Statue/Aztec). Parked as a fidelity enhancement. **Decided 2026-10-01:** after 1.0, together with the LOD-fraction approximation as one "real RDP LOD" project. |
|
||||
| Approximations in the software RSP | post-1.0 | `gfx_pc.cpp` `G_CCMUX_LOD_FRACTION`; D323 | The LOD-fraction CC input is an eyeballed approximation, bundled with D323 as one "real RDP LOD" project after 1.0 (2026-09-30 triage). **WrapFix settled 2026-10-02:** the maintainer checked the Depot building ceilings against 1964 with the default `Video.WrapFix = 0` and they look right, so the default stays off (the opt-in knob remains; RC3/D167). |
|
||||
@@ -195,7 +195,7 @@ A single campaign pass with these on the checklist would clear most of them.
|
||||
| Steam Deck preset skipped when first launched from Desktop Mode | verify | D283 | Fixed on `release/v0.4.1`: the Deck is detected by DMI hardware id (Valve Jupiter/Galileo; `STEAMOS` as a second signal) and the preset applies once per ini, only while Fullscreen/Maximized are untouched. Only a Windows build and a Linux `-fsyntax-only` check ran. **Owed: one run on a real Deck** (Desktop-Mode-created ini, then Game Mode; expect fullscreen 1280x800 and the log line `video: Steam Deck (DMI Valve ...)`). The Known-issues row goes when this ships. |
|
||||
| Windows 7 | open | #98 | **Re-checked 2026-10-02: likely feasible around 1.0.** The exe imports no Windows 8+ functions and the bundled SDL2 2.32.10 still supports Windows 7. The real blocker is the first-run converter `ge007-convert.exe` (PyInstaller, Python 3.9+): the native C converter (#6 Part B) removes it, or a separate Python 3.8 build. Work: one Windows 7 VM test, then manual per-release checks (CI cannot test it). Supersedes the earlier 'won't-fix' call. |
|
||||
| Alt-tab haze + fast alt-tab recovery | open (v0.7.0, maintainer 2026-10-10) | D578 | Maintainer 2026-10-10 (v0.6.0 build, Windows 120 Hz): alt-tabbing leaves a hazy picture; fast repeated alt-tab is wonky and recovers only after a while or more alt-tabbing. Not new in v0.6.0 (was an owed check). Known issue in the v0.6.0 notes. QoL fix: clean focus-loss/regain path (context/FBO state, present queue reset). |
|
||||
| Windows display scaling: the game is not DPI-aware on scaled displays | open (v0.7.0, maintainer 2026-10-10) | — | Seen 2026-10-07 during the v0.5.0 4K captures: on a 3840x2160 display at 125% Windows gives the game a 3072x1728 surface and stretches it, so the picture is softer than native. `gfx_sdl2.cpp` sets `SDL_HINT_WINDOWS_DPI_AWARENESS` only when `allow_hidpi` is off; setting it (`permonitorv2`) on the HiDPI path too renders at native resolution. Workaround today: environment variable `SDL_WINDOWS_DPI_AWARENESS=permonitorv2`. |
|
||||
| Windows display scaling: the game is not DPI-aware on scaled displays | fixed 2026-10-10 (D626); maintainer by-eye on a scaled display owed | D626 | Seen 2026-10-07 during the v0.5.0 4K captures: on a 3840x2160 display at 125% Windows gives the game a 3072x1728 surface and stretches it, so the picture is softer than native. `gfx_sdl2.cpp` sets `SDL_HINT_WINDOWS_DPI_AWARENESS` only when `allow_hidpi` is off; setting it (`permonitorv2`) on the HiDPI path too renders at native resolution. Workaround today: environment variable `SDL_WINDOWS_DPI_AWARENESS=permonitorv2`. |
|
||||
|
||||
## 5. Modern-remaster layer (port features beyond the N64)
|
||||
|
||||
|
||||
@@ -55,7 +55,7 @@
|
||||
<footer class="site-footer">
|
||||
<div class="footer-stamp">
|
||||
<a href="https://github.com/jkdansereau">maintained by jkdansereau</a>
|
||||
· last updated: 2026-10-10 · v0.6.0
|
||||
· last updated: 2026-10-13 · v0.6.0
|
||||
· <a href="https://github.com/jkdansereau/goldeneye-pc-port">GitHub</a>
|
||||
· <a href="https://github.com/jkdansereau/goldeneye-pc-port/releases">releases</a>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
# Release gate driver
|
||||
|
||||
`tools_pc/release_gate.sh` runs the automatable pre-release checks and prints one
|
||||
PASS / FAIL / SKIPPED table. It wraps `verify.sh` (pixel gate) and `levelbench.sh`
|
||||
(scripted run, clean-quit and crash-log detection); every run ends through
|
||||
`GE_QUITFRAME`, nothing is hard-killed.
|
||||
|
||||
```sh
|
||||
tools_pc/release_gate.sh --dry-run all # print what would run
|
||||
tools_pc/release_gate.sh win # sweep vs win goldens + fire smoke + exit path
|
||||
tools_pc/release_gate.sh deck <host> # 21 levels via deckrunner (shortcut must be up)
|
||||
GE_MAC_DIR=<remote game dir> tools_pc/release_gate.sh mac <host>
|
||||
```
|
||||
|
||||
Options: `--secs N` (per-level run length, default 30), `--out DIR`. Hosts and
|
||||
paths come only from arguments or env (`GE_WIN_DIR`, `GE_DECK_HOST`,
|
||||
`GE_MAC_HOST`, `GE_MAC_DIR`, `GE_MAC_EXE`, `GE_MAC_BUILD_CMD`,
|
||||
`GE_GATE_SSH_OPTS`). The Deck pixel compare is reported SKIPPED (640x480
|
||||
goldens vs 1280x800 dumps); Mac has no goldens, so it is crash-free only. The
|
||||
fire smoke is `GE_INPUTSCRIPT` `MLB1`/`MLB0` (D597, the D595 regression).
|
||||
Remote job, result and script files are deleted after the summaries are pulled.
|
||||
@@ -325,6 +325,8 @@ D579,FIXED,Issue #150: hugging a wall makes geometry vanish (N64 native widescre
|
||||
D583,FIXED,120 fps + VSync on G-Sync/VRR:,"FIXED on PC 2026-10-09 (G-Sync OLED, maintainer by eye); Deck 90 good on most levels (per-level pass 2026-10-10); open:"
|
||||
D584,FIXED,Cradle catwalk shadow flicker at every frame rate (regression from D579).,FIXED 2026-10-10 (maintainer Deck:
|
||||
D614,OPEN,Deck Bunker fight texture-cache thrash:,"OPEN: measured, fix not shipped (2026-10-10)"
|
||||
D605,FIXED,"Issue #145 (v0.6.0): F10 ""Invert look"" never reached the look stick in the HONEY role (game styles 1.1/1.3 -- left stick looks, right stick is the digital move…",FIXED 2026-10-09 (v0.6.0 line; port-only:
|
||||
D606,FIXED,Issue #144 (v0.6.0 triage):,"FIXED 2026-10-09 (v0.6.0 line; port-only in port/src/optionsoverlay.c, src/game zero-diff; off by default so goldens stay byte-identical; smoke-verified:"
|
||||
D407,CLOSED,Tanks cannot be boarded/exited on PC —,CLOSED (2026-09-28 bookkeeping:
|
||||
D408,FIXED,Game.SkipIntro skips the post-mission failure dossier:,FIXED 2026-09-30 (SkipIntro hook gated to first boot; live dossier check owed).
|
||||
D415,FIXED,"Shooting a light fixture does not ""kill"" its flickering light (Bunker, Caverns):",FIXED (2026-09-29 bookkeeping:
|
||||
@@ -439,6 +441,27 @@ D534,FIXED,Pre-release review of the 2026-10-05 local batch —,FIXED 2026-10-05
|
||||
D540,FIXED,Fog rework (Deck playtest):,FIXED 2026-10-05 (port:
|
||||
D450,FIXED,"Unplugging and replugging a controller was not picked up (user, GameSir-G7 Pro; 2026-09-29).","FIXED — port-only: the render thread's gfx_sdl_handle_events drains the same SDL queue as video.c's host pump but had no case for SDL_CONTROLLERDEVICEADDED/REMOVED, so it usually swallowed them and i…"
|
||||
D436,FIXED,"Persistent (hipfire) crosshair, opt-in:",FIXED (shipped 2026-09-29; rule-2 sign-off:
|
||||
D617,OPEN,"The HD pack's ge007.tdb is the emulator's full per-draw key list, not the pack's PNG list —",OPEN — plan written (docs/dev/TEXPACK-RICE-DRAW-PLAN.md); whole-texture recipe confirmed correct by 9 tdb hits with exact ;WxH agreement; PNG-filename and brute-force param searches are dead ends
|
||||
D618,?,"The community HD pack's non-CI keys are per-draw rects partly unreachable from N64 tile state (440x1 strips), and a rect key cannot land in the whole-texture p…",STOPPED 2026-10-06 — tdb ;WxH are per-draw rects (440x1 ×301 over tmem's 128-byte pitch = recompile-side); ~100 byte-domain variants score 0 hits; content-join does not discriminate; rect-keyed repla…
|
||||
D619,?,"The community HD pack's remaining 1790 PNGs are blocked by four independent causes (recompile-side rect keys, wrong hash domain, rect-vs-whole-texture format,…",WRITTEN UP 2026-10-06 —
|
||||
D620,OPEN,The HD pack's non-CI keys hash the pack's own re-encoded content —,MEASURED 2026-10-07 —
|
||||
D621,PARTIAL,The PD dabs-mod texture-pack notes mine:,PLAN LANDED 2026-10-07 —
|
||||
D622,?,B0 sprite-bank confirmation —,"PASS 2026-10-07 — two human-driven GE_TEXNUMDUMP+GE_TEXDUMP runs (front end 377 unique texnums / 521 dumps, -level_09 combat 321 / 316; evidence scratch/b0/):"
|
||||
D623,OPEN,Phase B1–B3 measured:,MEASURED 2026-10-07 —
|
||||
D624,OPEN,B4 content scorer built + tuned:,"BUILT 2026-10-07 — 5-stage pipeline (geometric prefilter → content rank → GT-tuned auto-accept → vision judge → human queue) per the 2026-10-07 decision (commits 512e6d57, 95d38637)."
|
||||
D625,?,"texpack A5: downscale-to-GL-max instead of the D615 vanilla fallback, plus first-use decode-stall metering —","BUILT 2026-10-10, not yet A/B-verified —"
|
||||
D626,FIXED,Windows DPI awareness was never declared:,FIXED 2026-10-10 (gfx_sdl2.cpp:
|
||||
D627,FIXED,DexDrive / GameFAQs .n64 saves import directly:,"FIXED 2026-10-10 (eepimport.c + eep_convert.py dexdrive-to-pc); byte-identical to the reference extraction, malformed inputs rejected untouched; by-eye mission select owed"
|
||||
D628,RESOLVED,"The exact-CRC CI half of the GoldenEye-007-HD join is sound (256 texnums, independently re-verified), but its structural half was scored against GE_TEXDUMP cap…","CI HALF ACCEPTED 2026-10-10 (256/256 (crc,fmt,siz) agreement with the rice census, 256 distinct texnums, palette-variant collision resolved by ROM palcrc, builder resolves all 256 sources); structura…"
|
||||
D632,OPEN,Exit BSOD 0x119 recurred (2026-10-10) despite D344; the first minidump shows it fires in the kernel's process-exit GPU cleanup (DXGPROCESS::Destroy -> DXGCONTE…,FIX BUILT 2026-10-10 (fix/d632-exit-teardown):
|
||||
D636,FIXED,Non-CI pack textures ARE fingerprint-keyed:,FIXED 2026-10-10 (feat/texpack-fp):
|
||||
D637,?,Fingerprint fallback in the loader:,BUILT 2026-10-10 (feat/texpack-fp); C CRC == Python on 80 ROM glyphs; not run-verified (game runs on hold)
|
||||
D638,FIXED,HD-pack ammo icon cut off:,FIXED 2026-10-10 (chore/v070-agentic-batch):
|
||||
D639,OPEN,Exclusive fullscreen picked the wrong resolution:,FIX BUILT 2026-10-10 (chore/v070-agentic-batch):
|
||||
D643,FIXED,Issues #145 (b)(c) / #149:,FIXED 2026-10-12 (port-only; GE_PADMAPTEST PASS; maintainer pad pass vs the #145/#149 reports; 1:1 N64-pad stick check owed when an adapter is available)
|
||||
D644,FIXED,File-select royal crest drew on an opaque dark square (#146):,"FIXED 2026-10-12 (import_texture_ia4; 1964-confirmed symptom, maintainer-verified; Win sweep unchanged)"
|
||||
D645,FIXED,"Original 1.1: F10 ""Invert look"" did not invert vertical look (#145 a):",FIXED 2026-10-12 (F10 row and watch row synced in Original; maintainer-verified)
|
||||
D646,FIXED,"F10 dropdown popup drew over the hint/tooltip panel (popup bounded by the screen edge, not the settings box) —",FIXED 2026-10-12 (ddGeom bounded to the box; maintainer-verified)
|
||||
D607,OPEN,Address model unified into one header:,DONE 2026-10-07 (macos branch; Windows identity gate:
|
||||
D608,FIXED,D34 ANIM_DATA_x re-basing guard in assets/animationtable_data.h was __x86_64__-only:,"FIXED 2026-10-07 (macos branch; mechanical guard widening, arm64 link clean)"
|
||||
D609,FIXED,macOS arm64 SIGKILL at exec:,"FIXED 2026-10-07 (macos branch; probe-confirmed; arm64 keeps the 4 GiB pagezero, floor 11.0)"
|
||||
|
||||
|
+1165
-1
File diff suppressed because it is too large.
Load diff
@@ -30,9 +30,10 @@ lighter values for draw distance, LOD distance and anti-aliasing into
|
||||
|
||||
### Above 60 fps
|
||||
|
||||
`Video.FpsCap` (F10, *Frame rate cap*) accepts Auto, 30, 60, 90, 120 and 144.
|
||||
`Video.FpsCap` (F10, *Frame rate cap*) accepts Auto, 30, 60, 90, 120, 144, 165 and 240.
|
||||
**Auto** is the default on fresh installs (an existing `ge007.ini` keeps its
|
||||
value) and matches the display's refresh rate, up to 144; on a 60 Hz display it
|
||||
value) and matches the display's refresh rate, up to 240 (the renderer draws at most four
|
||||
frames per game tick); on a 60 Hz display it
|
||||
is plain 60.
|
||||
|
||||
- **The game still simulates at its original 60 Hz (NTSC) tick.** The extra
|
||||
|
||||
+3
-3
@@ -9,7 +9,7 @@ description: >-
|
||||
# and the WebPage JSON-LD dates). Keep these equal to the last real content
|
||||
# change of THIS page, not to the last release.
|
||||
date: '2026-10-05'
|
||||
modified_time: '2026-10-10'
|
||||
modified_time: '2026-10-13'
|
||||
locale: en_US
|
||||
# The masthead is this page's title, so its first content h2 must NOT get the
|
||||
# amber page-title look (double header). See the .flat-title rule in
|
||||
@@ -32,9 +32,9 @@ Rare "Indy" engine family, one hardware generation apart.
|
||||
|
||||
<div class="cblock" markdown="1">
|
||||
|
||||
### v0.6.0 (RELEASE-DATE) — frame rates above 60, experimental macOS, fixes.
|
||||
### v0.6.0 (2026-10-13) — frame rates above 60, experimental macOS, fixes.
|
||||
|
||||
- **Frame rates above 60:** the frame cap offers 90, 120 and 144, and **Auto**
|
||||
- **Frame rates above 60:** the frame cap offers 90, 120, 144, 165 and 240, and **Auto**
|
||||
(the default on fresh installs) matches your display. Frames between game
|
||||
ticks are blended; the game still simulates at its original rate. It costs
|
||||
one game tick of latency and does nothing on a 60 Hz display. A rework of
|
||||
|
||||
@@ -1985,3 +1985,28 @@ On the Deck (Game Mode: stderr is dropped, so instruments must go to ge007.log v
|
||||
## D41. A long frame time on one render thread is not draw cost until you line presents up against draws (D583)
|
||||
|
||||
Before optimising a "slow" interpolated frame, check what its time contains: with one worker that both draws and presents, a frame's measured draw includes nothing but work, yet the presents queued behind it go out late by up to the whole draw, and the symptom (uneven 8/14 ms gap pairs at full present rate) looks exactly like an over-budget pass. Profile on-CPU time (`perf` works on SteamOS, `perf_event_paranoid` 2 is enough for your own process) and rule out the GPU clock (QAM Manual GPU Clock) before touching batching; then log each draw's start/end next to each present's due/late time (`GE_INTERPLOG=<n>`). On the Deck in Game Mode, gamescope only shows windows Steam launched: a harness started over ssh runs hidden and throttled, so launch through a Steam shortcut (`tools_pc/deckrunner.sh`).
|
||||
## D42.. Combiner alpha only reaches the framebuffer through the blender: no FORCE_BL, no blend (D414)
|
||||
|
||||
fast3d's `use_alpha` (blender inputs CLR_MEM/1MA) is a heuristic for "this is an alpha-blended draw". On the RDP the blender only acts on interior pixels when `FORCE_BL` is set (or via coverage/`CVG_X_ALPHA`); a render mode with XLU-style inputs but no `FORCE_BL` is opaque regardless of combiner alpha. Vanilla GE never noticed because its combiner alpha there is `SHADE` alpha (255). A mod's DL with cycle-2 alpha `(0,0,0,0)` drew fully transparent = black. GL blend is now gated on `FORCE_BL | CVG_X_ALPHA`.
|
||||
|
||||
**Tell:** rasterized with correct depth, immune to vertex-shade boosting, correct raw texels, black output. **Bisect fast:** output raw `texVal0/texVal1` in the fragment shader (bypasses combiner + blend); if that is right, the fault is combiner or blend state, not textures.
|
||||
|
||||
## D43. A capture taken through the feature under test is not ground truth (D628)
|
||||
|
||||
`GE_TEXDUMP` dumps what the GL upload receives, which is the *texture pack's*
|
||||
image whenever a pack supplies that texnum - so dumps captured with
|
||||
`Video.TexturePack` set cannot validate a pack join: the hold-out compares the
|
||||
pack PNG with a dump of itself and scores a perfect 1.000. 52 of 346 dumps in
|
||||
one such set were pack-substituted, and the false "pack and ROM textures are
|
||||
the same size" premise read off them then gated the matcher down to nothing
|
||||
(real packs are 4x-128x upscales). Two habits avoid the whole class: capture
|
||||
ground truth with the feature OFF (and record in the dump dir which switches
|
||||
were set), and before trusting a precision number, check that the hold-out's
|
||||
dims/format still look like the *source* data rather than the replacement.
|
||||
A 100 % score on a derived metric is a circularity smell, not a result.
|
||||
|
||||
**"Faithful" input mode = emit native bits and add nothing (D643).** A port that routes pad buttons to PC *actions* (dedicated use/reload calls, crouch, gadget cycle) cannot also claim to be the N64 pad: the game's own handlers (e.g. B = use-else-reload) only run on the native bit, so a button bound to two actions double-fires, and a fixed menu lockout reinterprets faces. For a strict mode, give the published bind table a native-button layer, zero every port-action mask in it (so side paths that read the action bitset, like a dedicated use/reload hook, go inert without being touched), and make the pure mapper `orig`-flagged and context-free; prove it with the same boot self-test (per-source table across stage/menu/tank). Also stop role-swapping sticks to match the game's selected style: a virtual pad keeps left = analog, right = C.
|
||||
|
||||
## D44. RDP IA4 is I3:A1; a decode tweak justified by "hardware layout" needs a texel dump (D644)
|
||||
|
||||
IA4 = 3-bit intensity + 1-bit alpha (nibble = I<<1 | A). An I2:A2 decode (D266) turned the A=0 nibbles 2/4/6 into semi-opaque black and drew the file-select crest on a dark square. When a texture's transparent texels render opaque, histogram the raw nibbles/bytes (`GE_TEXRAW` + `GE_D526` for the draw's fmt/oml/comb) before touching blender or alpha-compare code: the importer's alpha expansion is the cheapest suspect.
|
||||
@@ -51,8 +51,6 @@ uintptr_t gfxFramebuffer;
|
||||
#define SCALE_4_8(VAL_) ((VAL_)*0x11)
|
||||
#define SCALE_8_4(VAL_) ((VAL_) / 0x11)
|
||||
#define SCALE_3_8(VAL_) ((VAL_)*0x24)
|
||||
/* D266: RDP IA4 is I2:A2 (2-bit intensity, 2-bit alpha), not the I3:A1 the
|
||||
* PD-derived importer assumed. */
|
||||
#define SCALE_2_8(VAL_) ((VAL_)*0x55)
|
||||
#define SCALE_8_3(VAL_) ((VAL_) / 0x24)
|
||||
|
||||
@@ -959,16 +957,17 @@ static void import_texture_ia4(int tile, const LoadedTexture& loaded_texture, bo
|
||||
for (uint32_t i = 0; i < size_bytes * 2; i++, dest += 4) {
|
||||
const uint8_t byte = addr[i / 2];
|
||||
const uint8_t part = (byte >> (4 - (i % 2) * 4)) & 0xf;
|
||||
/* D266: I2:A2 -- the hardware IA4 layout. The old I3:A1 read made the
|
||||
* A2=1 canopy pixels fully opaque and the A2=2 edge dither invisible
|
||||
* (D236/D265 tree "wall"). */
|
||||
const uint8_t intensity = part >> 2;
|
||||
const uint8_t alpha = part & 3;
|
||||
const uint8_t c = SCALE_2_8(intensity);
|
||||
/* D644: RDP IA4 is I3:A1 (3-bit intensity, 1-bit alpha). D266's
|
||||
* I2:A2 read was unverified; it turned the even (A=0) nibbles 2/4/6 into
|
||||
* alpha 170/0 (and I2 as dark) and drew the file-select crest's transparent
|
||||
* texels as a dark square (#146). */
|
||||
const uint8_t intensity = part >> 1;
|
||||
const uint8_t alpha = part & 1;
|
||||
const uint8_t c = (uint8_t)((intensity * 255) / 7);
|
||||
dest[0] = c;
|
||||
dest[1] = c;
|
||||
dest[2] = c;
|
||||
dest[3] = SCALE_2_8(alpha);
|
||||
dest[3] = alpha ? 255 : 0;
|
||||
}
|
||||
|
||||
const uint32_t width = rdp.texture_tile[tile].line_size_bytes * 2;
|
||||
|
||||
@@ -18,6 +18,8 @@ void gfx_sdl_make_context_current(void);
|
||||
void gfx_sdl_release_context(void);
|
||||
/* D344: finish queued GL work and unbind the context (render thread, quit). */
|
||||
void gfx_sdl_park_for_exit(void);
|
||||
/* D632: delete the GL context + window (host thread, exit, render parked). */
|
||||
void gfx_sdl_teardown_for_exit(void);
|
||||
|
||||
/* Refresh the cached drawable size after a window resize (called by the
|
||||
* host thread's event pump). */
|
||||
|
||||
+63
-19
@@ -61,8 +61,24 @@ static void gfx_sdl_set_fullscreen_flag(int32_t mode) {
|
||||
}
|
||||
}
|
||||
|
||||
/* D639: set once a resolution is picked explicitly (gfx_sdl_set_closest_resolution). */
|
||||
static bool exclusive_mode_chosen = false;
|
||||
|
||||
static void set_fullscreen(bool on, bool call_callback) {
|
||||
fullscreen_state = on;
|
||||
/* D639: exclusive fullscreen switches the monitor to the window's display
|
||||
* mode; with none set, SDL derives it from the current WINDOW size, so a
|
||||
* 1440x1080 window asked the monitor for ~1440x1080 instead of its desktop
|
||||
* resolution (seen with NVIDIA "OpenGL present method = Prefer native").
|
||||
* Unless a resolution was picked explicitly, use the desktop mode of the
|
||||
* display the window is on. */
|
||||
if (on && fullscreen_flag == SDL_WINDOW_FULLSCREEN && !exclusive_mode_chosen) {
|
||||
SDL_DisplayMode dm;
|
||||
const int disp = SDL_GetWindowDisplayIndex(wnd);
|
||||
if (SDL_GetDesktopDisplayMode(disp >= 0 ? disp : 0, &dm) == 0 && dm.w > 0 && dm.h > 0) {
|
||||
SDL_SetWindowDisplayMode(wnd, &dm);
|
||||
}
|
||||
}
|
||||
SDL_SetWindowFullscreen(wnd, on ? fullscreen_flag : 0);
|
||||
if (call_callback && on_fullscreen_changed_callback) {
|
||||
on_fullscreen_changed_callback(on);
|
||||
@@ -86,17 +102,13 @@ static void gfx_sdl_get_active_window_refresh_rate(uint32_t* refresh_rate) {
|
||||
*refresh_rate = mode.refresh_rate;
|
||||
}
|
||||
|
||||
// D581 (#151): SDL display 0 is not the OS primary on multi-monitor setups. The
|
||||
// primary display is the one whose bounds contain the desktop origin (0,0)
|
||||
// (X11/Xwayland/Windows convention); fall back to display 0.
|
||||
// D581 (#151): SDL lists the OS primary display first (X11 RandR primary,
|
||||
// Windows MONITORINFOF_PRIMARY, macOS main display), so display 0 IS the
|
||||
// primary. The original bug was centring with display 0's size as absolute
|
||||
// coordinates, ignoring its position; callers now use its global bounds.
|
||||
// Not "the display containing (0,0)": on X11 the primary can sit right of a
|
||||
// secondary at the origin (the #151 layout; verified on KDE X11, 2026-10-13).
|
||||
static int primary_display_index() {
|
||||
const int n = SDL_GetNumVideoDisplays();
|
||||
for (int i = 0; i < n; ++i) {
|
||||
SDL_Rect r;
|
||||
if (SDL_GetDisplayBounds(i, &r) == 0 && r.x <= 0 && r.y <= 0 && r.x + r.w > 0 && r.y + r.h > 0) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -108,12 +120,16 @@ static void gfx_sdl_init(const struct GfxWindowInitSettings *set) {
|
||||
if (!set->allow_hidpi) {
|
||||
// HiDPI control, if available
|
||||
SDL_SetHint(SDL_HINT_VIDEO_HIGHDPI_DISABLED, "1");
|
||||
#if defined(PLATFORM_WIN32) && defined(SDL_HINT_WINDOWS_DPI_AWARENESS)
|
||||
// if HiDPI is disabled, declare ourselves DPI aware to get 1:1 window size on Windows
|
||||
SDL_SetHint(SDL_HINT_WINDOWS_DPI_AWARENESS, "permonitor");
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
#if defined(_WIN32) && defined(SDL_HINT_WINDOWS_DPI_AWARENESS) /* PD used PLATFORM_WIN32, never defined here */
|
||||
// Always declare per-monitor DPI awareness on Windows so the window and
|
||||
// fullscreen surfaces are 1:1 with physical pixels. Without it, a scaled
|
||||
// display (e.g. 3840x2160 at 125%) hands the game a 3072x1728 surface and
|
||||
// stretches it (soft picture) -- this used to be set only when HiDPI was
|
||||
// disabled. Width/Height in the ini are physical pixels either way.
|
||||
SDL_SetHint(SDL_HINT_WINDOWS_DPI_AWARENESS, "permonitorv2");
|
||||
#endif
|
||||
|
||||
// D566: the port never takes text input, so never ask for an on-screen
|
||||
// keyboard (Steam Deck Game Mode popped one at every launch). Literal
|
||||
@@ -328,16 +344,24 @@ static void get_centered_positions_native(int32_t width, int32_t height, int32_t
|
||||
const int disp_idx = SDL_GetWindowDisplayIndex(wnd);
|
||||
SDL_DisplayMode mode = {};
|
||||
SDL_GetDesktopDisplayMode(disp_idx, &mode);
|
||||
*posX = mode.w / 2 - width / 2;
|
||||
*posY = mode.h / 2 - height / 2;
|
||||
SDL_Rect b = { 0, 0, 0, 0 }; /* D581: the display's desktop offset */
|
||||
if (disp_idx < 0 || SDL_GetDisplayBounds(disp_idx, &b) != 0) { b.x = 0; b.y = 0; }
|
||||
*posX = b.x + mode.w / 2 - width / 2;
|
||||
*posY = b.y + mode.h / 2 - height / 2;
|
||||
}
|
||||
|
||||
static void gfx_sdl_get_centered_positions(int32_t width, int32_t height, int32_t *posX, int32_t *posY) {
|
||||
const int disp_idx = SDL_GetWindowDisplayIndex(wnd);
|
||||
/* D581: centre on the window's display in global coordinates (its bounds
|
||||
* carry the desktop offset; the mode size alone lands on the leftmost). */
|
||||
SDL_Rect b = { 0, 0, 0, 0 };
|
||||
if (disp_idx < 0 || SDL_GetDisplayBounds(disp_idx, &b) != 0 || b.w <= 0 || b.h <= 0) {
|
||||
SDL_DisplayMode mode = {};
|
||||
SDL_GetCurrentDisplayMode(disp_idx, &mode);
|
||||
*posX = mode.w / 2 - width / 2;
|
||||
*posY = mode.h / 2 - height / 2;
|
||||
SDL_GetCurrentDisplayMode(disp_idx < 0 ? 0 : disp_idx, &mode);
|
||||
b.x = 0; b.y = 0; b.w = mode.w; b.h = mode.h;
|
||||
}
|
||||
*posX = b.x + b.w / 2 - width / 2;
|
||||
*posY = b.y + b.h / 2 - height / 2;
|
||||
}
|
||||
|
||||
static void gfx_sdl_set_closest_resolution(int32_t width, int32_t height, bool should_center) {
|
||||
@@ -345,6 +369,7 @@ static void gfx_sdl_set_closest_resolution(int32_t width, int32_t height, bool s
|
||||
const int disp_idx = SDL_GetWindowDisplayIndex(wnd);
|
||||
SDL_DisplayMode closest = {};
|
||||
if (SDL_GetClosestDisplayMode(disp_idx, &mode, &closest)) {
|
||||
exclusive_mode_chosen = true; /* D639: an explicit pick wins over the desktop mode */
|
||||
SDL_SetWindowDisplayMode(wnd, &closest);
|
||||
SDL_SetWindowSize(wnd, closest.w, closest.h);
|
||||
if (should_center) {
|
||||
@@ -457,6 +482,25 @@ extern "C" void gfx_sdl_park_for_exit(void) {
|
||||
}
|
||||
}
|
||||
|
||||
/* D632: host thread, at exit, after the render thread has parked (context
|
||||
* unbound everywhere, GPU drained by gfx_sdl_park_for_exit). Delete the GL
|
||||
* context and the window while the process is still alive, so the driver
|
||||
* retires its hardware queues through the normal path. Left alone, they are
|
||||
* destroyed by the kernel inside process teardown (DXGPROCESS::Destroy ->
|
||||
* DXGCONTEXT::DestroyContext -> VidSchTerminateHwQueue), which is exactly
|
||||
* where the 0x119 bugcheck of 2026-10-10 fired (minidump, HAGS on). */
|
||||
extern "C" void gfx_sdl_teardown_for_exit(void) {
|
||||
if (ctx) {
|
||||
SDL_GL_MakeCurrent(NULL, NULL); // host never holds it after init; harmless
|
||||
SDL_GL_DeleteContext(ctx);
|
||||
ctx = NULL;
|
||||
}
|
||||
if (wnd) {
|
||||
SDL_DestroyWindow(wnd);
|
||||
wnd = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/* Release the GL context from the calling (host main) thread. WGL only allows
|
||||
* a context to be current on one thread at a time, and while another thread
|
||||
* still holds it, wglMakeContextCurrent fails with "The requested resource is
|
||||
|
||||
@@ -103,6 +103,10 @@ int videoIsFullscreen(void);
|
||||
* on a clean exit). No-op if the window isn't up. */
|
||||
void videoSaveWindowState(void);
|
||||
|
||||
/* D632: delete the GL context + window at exit (only if the render thread
|
||||
* parked). Called from the atexit handler after configSave. */
|
||||
void videoTeardownForExit(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
+182
-11
@@ -465,7 +465,33 @@ static const struct { const char *key; const char *label; const char *def; } kPa
|
||||
[PA_START] = { "Start", "Start / pause", "start" },
|
||||
};
|
||||
|
||||
typedef struct { u32 mask[PA_COUNT]; } PadBinds;
|
||||
/* D643 (#145 b/c, #149): Original control scheme = a STRICT virtual N64
|
||||
* controller. Each native N64 button is bound to up to two pad sources
|
||||
* (Input.N64Pad[N].<Button>), and a press emits exactly that bit in every
|
||||
* context (stage, menu, watch, tank) -- no port action (dedicated use/reload,
|
||||
* crouch, gadget cycle, prev-weapon) is injected; the game's own code decides
|
||||
* what A/B/Z/... mean (B = "use if usable else reload" lives in lv.c).
|
||||
* Defaults follow the Nintendo Switch Online N64 convention. The right stick
|
||||
* stays digital C-buttons (fixed, in addition to any C source bound here). */
|
||||
enum { NB_A, NB_B, NB_Z, NB_R, NB_L, NB_CU, NB_CD, NB_CL, NB_CR, NB_START,
|
||||
NB_DU, NB_DD, NB_DL, NB_DR, NB_COUNT };
|
||||
static const struct { const char *key; const char *def; unsigned bit; } kN64Btn[NB_COUNT] = {
|
||||
[NB_A] = { "A", "a", GE_CONT_A },
|
||||
[NB_B] = { "B", "x", GE_CONT_B },
|
||||
[NB_Z] = { "Z", "righttrigger", GE_CONT_G },
|
||||
[NB_R] = { "R", "lefttrigger,rightshoulder", GE_CONT_R },
|
||||
[NB_L] = { "L", "leftshoulder", GE_CONT_L },
|
||||
[NB_CU] = { "CUp", "none", GE_CONT_E },
|
||||
[NB_CD] = { "CDown", "none", GE_CONT_D },
|
||||
[NB_CL] = { "CLeft", "none", GE_CONT_C },
|
||||
[NB_CR] = { "CRight", "none", GE_CONT_F },
|
||||
[NB_START] = { "Start", "start", GE_CONT_START },
|
||||
[NB_DU] = { "DUp", "dpup", GE_CONT_UP },
|
||||
[NB_DD] = { "DDown", "dpdown", GE_CONT_DOWN },
|
||||
[NB_DL] = { "DLeft", "dpleft", GE_CONT_LEFT },
|
||||
[NB_DR] = { "DRight", "dpright", GE_CONT_RIGHT },
|
||||
};
|
||||
typedef struct { u32 mask[PA_COUNT]; u32 n64[NB_COUNT]; } PadBinds;
|
||||
typedef struct { PadBinds seat[MAX_PADS]; } PadTable;
|
||||
|
||||
static int padPreset[MAX_PADS] = {0}; /* D518: per-seat (Input.PadPreset[2/3/4]) */
|
||||
@@ -475,8 +501,22 @@ static int padPreset[MAX_PADS] = {0}; /* D518: per-seat (Input.PadPre
|
||||
* (D498: declared here so the stick-style helpers below can read it.) */
|
||||
static int naturalPitchMode = 1; /* Input.NaturalPitch */
|
||||
static int controlScheme = 0; /* D513 Input.ControlScheme: 0 Modern (port forces the style), 1 OG (the game's own selection is honoured) */
|
||||
/* D513 accessor (#145): watchsettings.c gates D564's pin of the game's own
|
||||
* "Look up/down" value on this: in the Modern scheme the port's Invert
|
||||
* look rows own the inversion; in Original the game's watch setting is
|
||||
* the live control (the game honours it in bondview2's stick-pitch path),
|
||||
* so it must stay user-settable there. */
|
||||
int inputControlScheme(void) { return controlScheme; }
|
||||
/* D645: Original scheme -- F10 "Invert look" mirrors the game's own
|
||||
* "Look up/down" (watchsettings.c keeps the two in sync both ways). */
|
||||
static int padLookInvertY = 0; /* Input.PadLookInvertY: 1 = invert the look stick's Y (right stick in SOLITARE roles, left stick in HONEY roles -- #145) */
|
||||
int inputPadLookInvert(void) { return padLookInvertY; }
|
||||
void inputSetPadLookInvert(int v) { padLookInvertY = v ? 1 : 0; }
|
||||
static char padStr[MAX_PADS][PA_COUNT][32]; /* Input.Pad[N].<Action> */
|
||||
static char padStrKey[MAX_PADS][PA_COUNT][40];
|
||||
static char n64Str[MAX_PADS][NB_COUNT][32]; /* D643 Input.N64Pad[N].<Button> */
|
||||
static char n64StrKey[MAX_PADS][NB_COUNT][40];
|
||||
static int n64StickFeel = 1; /* D643 Input.N64StickFeel (Original only): 1 accurate, 0 the Modern deadzone/rescale */
|
||||
static PadTable padTabBuf[2];
|
||||
static void *padTabPub; /* published PadTable*; pointer-sized/aligned for SDL_Atomic{Get,Set}Ptr (D599) */
|
||||
static unsigned padTabSig = 0;
|
||||
@@ -511,6 +551,21 @@ static void padBuildBinds(PadBinds *b, int seat, int custom, int *bad)
|
||||
for (int a = 0; a < PA_COUNT; a++)
|
||||
b->mask[a] = padParseMask(custom ? padStr[seat][a] : kPadAct[a].def, bad);
|
||||
if (!b->mask[PA_START]) b->mask[PA_START] = PSM(PS_START); /* lockout guard: keep a pause path */
|
||||
for (int n = 0; n < NB_COUNT; n++) b->n64[n] = 0;
|
||||
}
|
||||
|
||||
/* D643: the Original table. Port actions are zeroed (nothing may inject
|
||||
* use/reload/crouch/gadget/weapon-cycle); n64[] carries the buttons. Guards:
|
||||
* Start unbound -> default; A and B both unbound -> both default (menu lockout). */
|
||||
static void padBuildN64(PadBinds *b, int seat, int *bad)
|
||||
{
|
||||
for (int a = 0; a < PA_COUNT; a++) b->mask[a] = 0;
|
||||
for (int n = 0; n < NB_COUNT; n++) b->n64[n] = padParseMask(n64Str[seat][n], bad);
|
||||
if (!b->n64[NB_START]) b->n64[NB_START] = padParseMask(kN64Btn[NB_START].def, NULL);
|
||||
if (!b->n64[NB_A] && !b->n64[NB_B]) {
|
||||
b->n64[NB_A] = padParseMask(kN64Btn[NB_A].def, NULL);
|
||||
b->n64[NB_B] = padParseMask(kN64Btn[NB_B].def, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
/* D498: preset 3 is Custom; 4 = 1.4 Elektra is a fixed mapping, not custom.
|
||||
@@ -559,6 +614,11 @@ static unsigned padComputeSig(void)
|
||||
{
|
||||
int anyCustom = 0;
|
||||
unsigned h = 2166136261u;
|
||||
h = (h ^ (unsigned)controlScheme) * 16777619u; /* D643 */
|
||||
if (controlScheme)
|
||||
for (int s = 0; s < MAX_PADS; s++)
|
||||
for (int n = 0; n < NB_COUNT; n++)
|
||||
for (const char *p = n64Str[s][n]; *p; p++) h = (h ^ (unsigned char)*p) * 16777619u;
|
||||
for (int s = 0; s < MAX_PADS; s++) {
|
||||
h ^= (unsigned)padPreset[s] * 16777619u;
|
||||
if (padPresetCustom(s)) anyCustom = 1;
|
||||
@@ -584,7 +644,10 @@ static const PadTable *padTabGet(void)
|
||||
PadTable *cur = (PadTable *)SDL_AtomicGetPtr((void **)&padTabPub);
|
||||
PadTable *spare = (cur == &padTabBuf[0]) ? &padTabBuf[1] : &padTabBuf[0];
|
||||
int bad = 0;
|
||||
for (int s = 0; s < MAX_PADS; s++) padBuildBinds(&spare->seat[s], s, padPresetCustom(s), &bad);
|
||||
for (int s = 0; s < MAX_PADS; s++) {
|
||||
padBuildBinds(&spare->seat[s], s, padPresetCustom(s), &bad);
|
||||
if (controlScheme) padBuildN64(&spare->seat[s], s, &bad); /* D643 */
|
||||
}
|
||||
SDL_AtomicSetPtr((void **)&padTabPub, spare);
|
||||
padTabSig = sig;
|
||||
padTabValid = 1;
|
||||
@@ -618,7 +681,7 @@ static u32 padActHeld(const PadBinds *b, u32 raw)
|
||||
return h;
|
||||
}
|
||||
|
||||
typedef struct { int facePlayable; int slotNative; int southpaw; int tank; } PadMapCtx;
|
||||
typedef struct { int facePlayable; int slotNative; int southpaw; int tank; int orig; } PadMapCtx; /* orig: D643 */
|
||||
typedef struct { unsigned button; int crouch; int gadgetEdge; u32 actHeld; } PadMapOut;
|
||||
|
||||
/* The pure mapping: raw source bits + bindings + previous action state ->
|
||||
@@ -629,6 +692,15 @@ static void padMapPure(const PadBinds *b, u32 raw, u32 actPrev, unsigned buttonI
|
||||
const PadMapCtx *c, PadMapOut *o)
|
||||
{
|
||||
unsigned button = buttonIn;
|
||||
if (c->orig) { /* D643: strict virtual N64 pad; same in every context, no port actions, no southpaw swap */
|
||||
for (int n = 0; n < NB_COUNT; n++)
|
||||
if (raw & b->n64[n]) button |= kN64Btn[n].bit;
|
||||
o->button = button;
|
||||
o->crouch = 0;
|
||||
o->gadgetEdge = 0;
|
||||
o->actHeld = 0;
|
||||
return;
|
||||
}
|
||||
u32 held = padActHeld(b, raw);
|
||||
if (raw & b->mask[PA_FIRE] & PS_TRIGGERS) button |= GE_CONT_G;
|
||||
if (raw & b->mask[PA_AIM] & PS_TRIGGERS) button |= GE_CONT_R;
|
||||
@@ -1047,7 +1119,7 @@ static int padLookSensY = 100; /* right-stick look: vertical (pit
|
||||
static int padSouthpaw = 0; /* 1 = swap fire (G) / grenade (R) trigger actions */
|
||||
static int padLookSmooth = 0; /* right-stick look low-pass strength, 0-10 (0 = off, 10 = max) */
|
||||
static int padTriggerPct = 25; /* trigger press point, % of travel (~30*256) */
|
||||
static int padLookInvertY = 0; /* 1 = invert right-stick (look) Y */
|
||||
/* padLookInvertY: declared with the D513 accessors above (D645). */
|
||||
static int inputDefaultsRev = 0; /* D546: 1 once the deadzone default migration ran */
|
||||
/* Per-pad look-smoothing EMA state (Wave A item 5), one X/Y pair per pad. Reset
|
||||
* on hot-unplug (inputRescanPads) so a smoothed value can't stick after the
|
||||
@@ -1761,6 +1833,27 @@ void inputUpdate(void)
|
||||
mouseDY += dy;
|
||||
}
|
||||
|
||||
/* D643 Input.N64StickFeel=1 (Original): the stick as a bare N64 pot. Linear
|
||||
* 32767 -> STICK_MAX (80, the N64 range), a small centre deadzone and nothing
|
||||
* else (no deadzone rescale, look sensitivity or smoothing). The ~7.6% centre
|
||||
* (2500/32767) is an assumption of typical N64 stick dead-centre jitter --
|
||||
* confirm against 1964 / the N64 manual. */
|
||||
#define N64_STICK_DZ 2500
|
||||
static int scaleAxis(int v, int dz);
|
||||
static int scaleAxisN64(int v)
|
||||
{
|
||||
if (v > -N64_STICK_DZ && v < N64_STICK_DZ) return 0;
|
||||
int out = (int)((long)v * STICK_MAX / 32767);
|
||||
if (out > STICK_MAX) out = STICK_MAX;
|
||||
if (out < -STICK_MAX) out = -STICK_MAX;
|
||||
return out;
|
||||
}
|
||||
/* Modern/legacy deadzone scaling, or the accurate N64 response in Original. */
|
||||
static int padStickAxis(int v, int dz)
|
||||
{
|
||||
return (controlScheme && n64StickFeel) ? scaleAxisN64(v) : scaleAxis(v, dz);
|
||||
}
|
||||
|
||||
static int scaleAxis(int v, int dz)
|
||||
{
|
||||
if (dz < 0) dz = 0;
|
||||
@@ -2481,7 +2574,7 @@ static unsigned inputComputePadSlot(int idx, signed char *stick_x, signed char *
|
||||
* combined edges prevent a held input from repeatedly using a door.
|
||||
* In menus/watch/tank the pad keeps native accept/cancel buttons. */
|
||||
SDL_GameController *slot0Pad = slotPad(0);
|
||||
u32 slot0Held = slot0Pad ? padActHeld(&padTabGet()->seat[padSeatForSlot(0)], padReadRaw(slot0Pad)) : 0;
|
||||
u32 slot0Held = (slot0Pad && !controlScheme) ? padActHeld(&padTabGet()->seat[padSeatForSlot(0)], padReadRaw(slot0Pad)) : 0;
|
||||
int padUse = (slot0Held & (1u << PA_USE)) != 0; /* D469: bound actions (Jinx 1.1: A / X) */
|
||||
int padReload = (slot0Held & (1u << PA_RELOAD)) != 0;
|
||||
int useNow = scriptIsActive(0) ? scriptSlots[0].use : (actHeld(ks, IA_CANCEL) || padUse);
|
||||
@@ -2880,11 +2973,11 @@ static unsigned inputComputePadSlot(int idx, signed char *stick_x, signed char *
|
||||
if (!padMenuMode && stickSwap(idx)) { int tx = lx, ty = ly; lx = rx; ly = ry; rx = tx; ry = ty; } /* D518 */
|
||||
|
||||
if (padMenuMode) {
|
||||
int px = scaleAxis(lx, padDeadzoneL);
|
||||
int py = -scaleAxis(ly, padDeadzoneL); /* SDL up = negative -> N64 up = positive */
|
||||
int px = padStickAxis(lx, padDeadzoneL); /* D643: accurate N64 response in Original */
|
||||
int py = -padStickAxis(ly, padDeadzoneL); /* SDL up = negative -> N64 up = positive */
|
||||
if (px) sx = px;
|
||||
if (py) sy = py;
|
||||
} else if (styleIsSolitare(idx)) { /* D498, D513, D518 */
|
||||
} else if (!controlScheme && styleIsSolitare(idx)) { /* D498, D513, D518; D643: Original never swaps roles (left stick = analog stick, right = C) */
|
||||
/* D194/D238: SOLITARE swaps stick roles -- left stick becomes
|
||||
* digital-step movement (same analog-for-movement tradeoff as
|
||||
* the keyboard remap above), right stick becomes continuous
|
||||
@@ -2934,13 +3027,23 @@ static unsigned inputComputePadSlot(int idx, signed char *stick_x, signed char *
|
||||
if (rys) sy = rys;
|
||||
}
|
||||
} else {
|
||||
int px = scaleAxis(lx, padDeadzoneL);
|
||||
int py = -scaleAxis(ly, padDeadzoneL); /* SDL up = negative -> N64 up = positive */
|
||||
int px = padStickAxis(lx, padDeadzoneL); /* D643: accurate N64 response in Original */
|
||||
int py = -padStickAxis(ly, padDeadzoneL); /* SDL up = negative -> N64 up = positive */
|
||||
/* #145: in the HONEY role the LEFT stick is the look stick (1.1/
|
||||
* 1.3 -- and, after the D498/D518 physical swap for 1.3 Frost,
|
||||
* this channel carries the physical right stick, the 1.3 look
|
||||
* stick). Inverting it, not the right stick (which is the digital
|
||||
* movement C-button source in this role), is what makes F10's
|
||||
* "Invert look" affect in-game vertical aim under the game's own
|
||||
* original styles. The SOLITARE branch above already inverts its
|
||||
* (look) right-stick channel. */
|
||||
/* D645: not in Original -- there the game's own "Look up/down"
|
||||
* (mirrored from this row by watchsettings.c) inverts natively. */
|
||||
if (padLookInvertY && !controlScheme) py = -py;
|
||||
if (px) sx = px;
|
||||
if (py) sy = py;
|
||||
if (idx == 0 && (px || py)) s_aimDevMouse = 0; /* D337: pad aim active */
|
||||
|
||||
if (padLookInvertY) ry = -ry;
|
||||
if (rx > RSTICK_THRESHOLD) button |= GE_CONT_F;
|
||||
if (rx < -RSTICK_THRESHOLD) button |= GE_CONT_C;
|
||||
if (ry > RSTICK_THRESHOLD) button |= GE_CONT_D;
|
||||
@@ -2958,6 +3061,7 @@ static unsigned inputComputePadSlot(int idx, signed char *stick_x, signed char *
|
||||
pc.facePlayable = (idx == 0 || inputMpSplit()) && inputCanUseGameplayActions(padMenuMode);
|
||||
pc.slotNative = (idx > 0 || inputMpPadsOwnP1());
|
||||
pc.southpaw = padSouthpaw;
|
||||
pc.orig = controlScheme; /* D643 */
|
||||
pc.tank = !pc.facePlayable && idx == 0 && !padMenuMode && g_PlayerIsInTank == 1 &&
|
||||
slotPlayer() && slotPlayer()->outside_watch_menu && !slotPlayer()->pause_state;
|
||||
const PadBinds *pb = &padTabGet()->seat[padSeatForSlot(idx)];
|
||||
@@ -4004,6 +4108,64 @@ static void padMapSelfTest(void)
|
||||
sysLogPrintf(LOG_INFO, "GE_PADMAPTEST: binding API round trip (%d) clear keeps slot (%d) [%s]", c10, c11, padStr[1][PA_FIRE]);
|
||||
fail += !(c10 && c11);
|
||||
|
||||
/* D643: Original = strict virtual N64 pad. Per-source table (identical in
|
||||
* stage / menu / tank), then remap, per-seat independence, guards. */
|
||||
{
|
||||
static const unsigned exp[PS_COUNT] = {
|
||||
[PS_A] = GE_CONT_A, [PS_B] = 0, [PS_X] = GE_CONT_B, [PS_Y] = 0,
|
||||
[PS_LB] = GE_CONT_L, [PS_RB] = GE_CONT_R, [PS_LS] = 0, [PS_RS] = 0,
|
||||
[PS_START] = GE_CONT_START, [PS_DUP] = GE_CONT_UP, [PS_DDOWN] = GE_CONT_DOWN,
|
||||
[PS_DLEFT] = GE_CONT_LEFT, [PS_DRIGHT] = GE_CONT_RIGHT,
|
||||
[PS_LT] = GE_CONT_R, [PS_RT] = GE_CONT_G };
|
||||
char savedN[MAX_PADS][NB_COUNT][32];
|
||||
int savedCs = controlScheme, savedSouth = padSouthpaw, o1 = 1, o2 = 1, o3 = 1;
|
||||
memcpy(savedN, n64Str, sizeof(savedN));
|
||||
controlScheme = 1;
|
||||
padSouthpaw = 1; /* must be ignored in Original */
|
||||
for (int s2 = 0; s2 < MAX_PADS; s2++) {
|
||||
padPreset[s2] = 0;
|
||||
for (int n = 0; n < NB_COUNT; n++) snprintf(n64Str[s2][n], sizeof(n64Str[0][0]), "%s", kN64Btn[n].def);
|
||||
}
|
||||
padTabValid = 0;
|
||||
const PadTable *ot = padTabGet();
|
||||
for (int src = 0; src < PS_COUNT; src++)
|
||||
for (int cx = 0; cx < 3; cx++) { /* playable / menu / tank */
|
||||
PadMapCtx c = { cx == 0, 1, 1, cx == 2, 1 };
|
||||
padMapPure(&ot->seat[0], PSM(src), 0, 0, &c, &o);
|
||||
int ok = o.button == exp[src] && !o.crouch && !o.gadgetEdge &&
|
||||
padActHeld(&ot->seat[0], PSM(src)) == 0;
|
||||
o1 &= ok;
|
||||
sysLogPrintf(ok ? LOG_INFO : LOG_ERROR, "GE_PADMAPTEST: original %-13s %-8s -> %04x (want %04x)",
|
||||
kPadSrc[src].name, cx == 0 ? "playable" : cx == 1 ? "menu" : "tank", o.button, exp[src]);
|
||||
}
|
||||
{ PadMapCtx c = { 1, 0, 0, 0, 1 }; padMapPure(&ot->seat[0], PSM(PS_RT) | PSM(PS_A), 0, 0, &c, &o); o1 &= o.button == (GE_CONT_G | GE_CONT_A); }
|
||||
/* remap: seat 1 B := y,b and CUp := b; seat 0 untouched */
|
||||
snprintf(n64Str[1][NB_B], sizeof(n64Str[0][0]), "y,b");
|
||||
snprintf(n64Str[1][NB_CU], sizeof(n64Str[0][0]), "rightstick");
|
||||
/* guards: seat 2 Start none; seat 3 A and B both none */
|
||||
snprintf(n64Str[2][NB_START], sizeof(n64Str[0][0]), "none");
|
||||
snprintf(n64Str[3][NB_A], sizeof(n64Str[0][0]), "none");
|
||||
snprintf(n64Str[3][NB_B], sizeof(n64Str[0][0]), "none");
|
||||
ot = padTabGet();
|
||||
PadMapCtx oc = { 1, 0, 0, 0, 1 };
|
||||
padMapPure(&ot->seat[1], PSM(PS_Y), 0, 0, &oc, &o); o2 &= o.button == GE_CONT_B;
|
||||
padMapPure(&ot->seat[1], PSM(PS_B), 0, 0, &oc, &o); o2 &= o.button == GE_CONT_B;
|
||||
padMapPure(&ot->seat[1], PSM(PS_X), 0, 0, &oc, &o); o2 &= o.button == 0;
|
||||
padMapPure(&ot->seat[1], PSM(PS_RS), 0, 0, &oc, &o); o2 &= o.button == GE_CONT_E;
|
||||
padMapPure(&ot->seat[0], PSM(PS_X), 0, 0, &oc, &o); o2 &= o.button == GE_CONT_B;
|
||||
padMapPure(&ot->seat[0], PSM(PS_Y), 0, 0, &oc, &o); o2 &= o.button == 0;
|
||||
padMapPure(&ot->seat[2], PSM(PS_START), 0, 0, &oc, &o); o3 &= o.button == GE_CONT_START;
|
||||
padMapPure(&ot->seat[3], PSM(PS_A), 0, 0, &oc, &o); o3 &= o.button == GE_CONT_A;
|
||||
padMapPure(&ot->seat[3], PSM(PS_X), 0, 0, &oc, &o); o3 &= o.button == GE_CONT_B;
|
||||
int st = scaleAxisN64(2499) == 0 && scaleAxisN64(32767) == STICK_MAX && scaleAxisN64(-32768) == -STICK_MAX &&
|
||||
scaleAxisN64(16384) == 40 && padStickAxis(5000, 7500) == scaleAxisN64(5000);
|
||||
sysLogPrintf(LOG_INFO, "GE_PADMAPTEST: original table (%d) remap/seat-independence (%d) unbound-fallback (%d) stick (%d)", o1, o2, o3, st);
|
||||
fail += !(o1 && o2 && o3 && st);
|
||||
controlScheme = savedCs;
|
||||
padSouthpaw = savedSouth;
|
||||
memcpy(n64Str, savedN, sizeof(savedN));
|
||||
}
|
||||
|
||||
memcpy(padStr, saved, sizeof(saved));
|
||||
memcpy(padPreset, savedPreset, sizeof(savedPreset));
|
||||
padTabValid = 0;
|
||||
@@ -4025,6 +4187,15 @@ PD_CONSTRUCTOR static void inputConfigInit(void)
|
||||
snprintf(padStr[seat][a], sizeof(padStr[seat][a]), "%s", kPadAct[a].def);
|
||||
configRegisterString(padStrKey[seat][a], padStr[seat][a], sizeof(padStr[seat][a]));
|
||||
}
|
||||
/* D643 (#145/#149): Original-scheme N64 button map, per seat (seat 1 = Input.N64Pad.<B>, seats 2-4 = Input.N64Pad2/3/4.<B>). */
|
||||
for (int seat = 0; seat < MAX_PADS; seat++)
|
||||
for (int n = 0; n < NB_COUNT; n++) {
|
||||
if (seat == 0) snprintf(n64StrKey[seat][n], sizeof(n64StrKey[seat][n]), "Input.N64Pad.%s", kN64Btn[n].key);
|
||||
else snprintf(n64StrKey[seat][n], sizeof(n64StrKey[seat][n]), "Input.N64Pad%d.%s", seat + 1, kN64Btn[n].key);
|
||||
snprintf(n64Str[seat][n], sizeof(n64Str[seat][n]), "%s", kN64Btn[n].def);
|
||||
configRegisterString(n64StrKey[seat][n], n64Str[seat][n], sizeof(n64Str[seat][n]));
|
||||
}
|
||||
configRegisterInt("Input.N64StickFeel", &n64StickFeel, 0, 1);
|
||||
configRegisterInt("Input.MPMode", &mpInputMode, 0, 2); /* D416: 0 Auto, 1 PadsOnly, 2 KbmP1 */
|
||||
configRegisterInt("Input.MouseEnabled", &mouseEnabled, 0, 1);
|
||||
configRegisterInt("Input.CrosshairCursor", &cfgCrosshairCursor, 0, 1); /* D555 */
|
||||
|
||||
@@ -88,6 +88,10 @@ static void portAtExit(void)
|
||||
inputRumbleStopAll(); /* D493: no pad left rumbling after exit */
|
||||
videoSaveWindowState();
|
||||
configSave();
|
||||
/* D632: release the GPU context while the process is alive (needs the
|
||||
* window state saved first; before any relaunch so the GPU init of the
|
||||
* new instance never overlaps this teardown). */
|
||||
videoTeardownForExit();
|
||||
/* D443: Restart game -- runs only on the orderly D344 exit path, after the
|
||||
* ini is written, so the new instance reads the saved settings. */
|
||||
if (videoRestartRequested()) {
|
||||
|
||||
+68
-13
@@ -53,6 +53,7 @@ extern MENU current_menu;
|
||||
#include "video.h"
|
||||
#include "input.h"
|
||||
#include "front.h" /* selected_folder_num (D356 stage probe) */
|
||||
#include "lv.h" /* lvlGetCurrentMultiPlayerSec (D606 mission timer) */
|
||||
#include "bondtypes.h" /* sImageTableEntry (D555 crosshair pointer: typed field reads) */
|
||||
#include "file.h" /* save_data (D356 reset probe: second-file isolation) */
|
||||
#include "optionsoverlay.h"
|
||||
@@ -144,10 +145,11 @@ static const char *const kGameplayView[] = { "Original", "Extended", NULL }; /
|
||||
* have it, but it's dropped from the menu: with VSync on (the default) it's
|
||||
* indistinguishable from 60, and with VSync off it just burns GPU time
|
||||
* re-presenting the same simulated frame -- confusing for no real benefit. */
|
||||
/* D578: 90/120/144 draw interpolated frames between the 60 Hz game frames
|
||||
/* D578: 90/120/144/165/240 draw interpolated frames between the 60 Hz game frames
|
||||
* (port/fast3d/gfx_pc.cpp); the sim rate is unchanged. */
|
||||
/* -1 = Auto: match the display refresh (60..144; the default). */
|
||||
static const int kFpsCapSeq[] = { -1, 30, 60, 90, 120, 144 };
|
||||
/* -1 = Auto: match the display refresh (up to 240; the default). 240 is the
|
||||
* renderer's ceiling: at most IP_SLOTS/2 = 4 presents per 60 Hz tick (libultra.c). */
|
||||
static const int kFpsCapSeq[] = { -1, 30, 60, 90, 120, 144, 165, 240 };
|
||||
#define FPSCAP_N ((int)(sizeof(kFpsCapSeq) / sizeof(kFpsCapSeq[0])))
|
||||
|
||||
/* Windowed-mode resolution presets. Filtered at init to those that fit the
|
||||
@@ -383,10 +385,13 @@ static struct Row rows[] = {
|
||||
{ .key="__HdrGame", .label="GAME", .kind=ROW_HEADER },
|
||||
/* Aim control (hold vs toggle the aim button) is a per-file watch row
|
||||
* like Auto-aim; the port honours the game's toggle. The game's own
|
||||
* "Look up/down" (Bond.Look) row was removed (D564): the port's mouse and
|
||||
* centred-pad look write the view directly and never consult it, so the
|
||||
* Mouse / Controller "Invert look" rows are the only inversion controls;
|
||||
* watchsettings.c pins the game's value to its factory default. */
|
||||
* "Look up/down" (Bond.Look) row was removed (D564): in the Modern
|
||||
* scheme the port's mouse and centred-pad look write the view directly
|
||||
* and never consult it, so the Mouse / Controller "Invert look" rows
|
||||
* are the only inversion controls there (watchsettings.c pins the
|
||||
* game's value to its factory default). #145: in the Original scheme
|
||||
* the pin is lifted and the in-game watch row is live again -- the
|
||||
* rows above still apply on top (pad look stick, #145 fix). */
|
||||
{ .key="Bond.AimControl", .label="Aim control", .kind=ROW_TOGGLE,
|
||||
.names=kHold, .found=1, .uiMax=1, .cfgMax=1, .saveScoped=1 },
|
||||
{ .key="Bond.AutoAim", .label="Auto-aim", .kind=ROW_TOGGLE,
|
||||
@@ -437,6 +442,9 @@ static struct Row rows[] = {
|
||||
* patched, read or written. Applies live (rowSetCommit). */
|
||||
{ .key="Game.AllUnlocked", .label="All unlocked",
|
||||
.kind=ROW_TOGGLE, .step=1, .names=kOnOff },
|
||||
/* D606 (#144): PD-style always-visible mission timer; drawn top-right
|
||||
* (under the FPS readout when both are on) while a stage is active. */
|
||||
{ .key="Game.MissionTimer", .label="Mission timer", .kind=ROW_TOGGLE, .step=1, .names=kOnOff },
|
||||
SEP(G4),
|
||||
/* D551: opt-in update check; the action row appears only once a newer release is known. */
|
||||
{ .key="Game.CheckUpdates", .label="Check for updates", .kind=ROW_TOGGLE, .step=1, .names=kOnOff },
|
||||
@@ -791,9 +799,20 @@ int optionsBindingCaptureTick(void)
|
||||
static int s_showFps = 0;
|
||||
static char s_fpsText[16] = "";
|
||||
|
||||
/* D606 (#144, v0.6.0): optional PD-style always-visible mission timer.
|
||||
* Reads the game's own mission clock (lvlGetCurrentMultiPlayerSec: the
|
||||
* D_80048394 accumulator scaled to region ticks -- the same source the
|
||||
* watch TIME page and the AI_IFMissionTime conditions use), so it is the
|
||||
* faithful N64 mission time, not a wall clock. Port-only: a text row like
|
||||
* the D213 FPS readout; off by default, nothing appended when off, so
|
||||
* goldens are untouched. */
|
||||
static int s_missionTimer = 0;
|
||||
static char s_missionTimeText[24] = "";
|
||||
|
||||
PD_CONSTRUCTOR static void overlayConfigInit(void)
|
||||
{
|
||||
configRegisterInt("Video.DisplayFPS", &s_showFps, 0, 1);
|
||||
configRegisterInt("Game.MissionTimer", &s_missionTimer, 0, 1);
|
||||
}
|
||||
|
||||
/* Sampled once per emitted frame; recomputes the string every ~0.5 s. */
|
||||
@@ -820,6 +839,27 @@ static void fpsTick(void)
|
||||
}
|
||||
}
|
||||
|
||||
/* D606: refreshed once per emitted frame, like the FPS window above. The
|
||||
* clock is stage-paced (lvl.c advances it during a run), so menu frames
|
||||
* simply carry no text and the row disappears outside a mission. */
|
||||
static void missionTimerTick(void)
|
||||
{
|
||||
if (!s_missionTimer ||
|
||||
(current_menu != MENU_RUN_STAGE && current_menu != MENU_INVALID)) {
|
||||
s_missionTimeText[0] = 0;
|
||||
return;
|
||||
}
|
||||
int s = (int)lvlGetCurrentMultiPlayerSec();
|
||||
if (s < 0) s = 0;
|
||||
if (s >= 3600) {
|
||||
snprintf(s_missionTimeText, sizeof(s_missionTimeText), "TIME %d:%02d:%02d",
|
||||
s / 3600, (s / 60) % 60, s % 60);
|
||||
} else {
|
||||
snprintf(s_missionTimeText, sizeof(s_missionTimeText), "TIME %d:%02d",
|
||||
s / 60, s % 60);
|
||||
}
|
||||
}
|
||||
|
||||
/* Layout (game 2D pixel space = viGetX() x viGetY(), ~320x240). Shared by the
|
||||
* emit path and the mouse hit-testing in optionsOverlayHandleInput().
|
||||
* PD-style dialog (menu PD parity R4): sized to its content and centred, one
|
||||
@@ -1945,14 +1985,17 @@ static int ddGeom(s32 *x0, s32 *y0, s32 *x1, s32 *y1, int *vis)
|
||||
s32 h = v * OV_LINE + 2;
|
||||
*x1 = o.valueR + 3;
|
||||
*x0 = *x1 - w;
|
||||
s32 H = ovH();
|
||||
/* D646: the popup stays inside the settings box -- it used to open
|
||||
* down over the hint/tooltip panel below the box (its limit was the
|
||||
* screen edge, not o.bottom). */
|
||||
s32 lo = o.titleB + 1, hi = o.bottom - 1;
|
||||
*y0 = rowY + OV_LINE;
|
||||
if (*y0 + h > H - 4) *y0 = rowY - h; /* no room below: open upward */
|
||||
if (*y0 + h > hi) *y0 = rowY - h; /* no room below: open upward */
|
||||
/* D568: no room above either (a long list on a mid-page row): keep the whole
|
||||
* popup on screen, flush with the bottom margin. A negative top used to
|
||||
* popup inside the box, flush with its bottom. A negative top used to
|
||||
* draw the labels above the screen while the fill wrapped down past the
|
||||
* bottom edge (an empty, misplaced box). */
|
||||
if (*y0 < 4) *y0 = (H - 4 - h > 4) ? H - 4 - h : 4;
|
||||
if (*y0 < lo) *y0 = (hi - h > lo) ? hi - h : lo;
|
||||
*y1 = *y0 + h;
|
||||
*vis = v;
|
||||
return 1;
|
||||
@@ -2081,6 +2124,7 @@ static const struct { const char *key; double def; } kResetDefaults[] = {
|
||||
/* GAMEPLAY ini rows (port/src/video.c initializers) */
|
||||
{ "Game.SkipIntro", 0 }, /* = 0 */
|
||||
{ "Game.CheckUpdates", 0 }, /* = 0 (off, D551) */
|
||||
{ "Game.MissionTimer", 0 }, /* = 0 (off, D606) */
|
||||
{ "Game.NoHitFlash", 0 }, /* = 0 */
|
||||
{ "Game.AllUnlocked", 0 }, /* = 0 -- D257's "default ON" note was stale */
|
||||
{ "Game.HudScale", 100 }, /* = 100 (D226) */
|
||||
@@ -3403,6 +3447,7 @@ static Gfx *overlayEmitLocked(void)
|
||||
}
|
||||
|
||||
fpsTick();
|
||||
missionTimerTick();
|
||||
|
||||
{ /* D558 test hook: GE_OPTIONSOVERLAY_ATFRAME=<n> re-applies the open request at emit n (read once). */
|
||||
static int s_atFrame = -2, s_emitN = 0;
|
||||
@@ -3421,10 +3466,12 @@ static Gfx *overlayEmitLocked(void)
|
||||
}
|
||||
|
||||
if (!s_open) {
|
||||
if (!s_showFps || !s_fpsText[0]) {
|
||||
if ((!s_showFps || !s_fpsText[0]) && !s_missionTimeText[0]) {
|
||||
return NULL; /* nothing appended -> golden dumps byte-identical */
|
||||
}
|
||||
/* D213: FPS-only mini DL (top-right), panel closed. */
|
||||
/* D213: FPS-only mini DL (top-right), panel closed. D606: the
|
||||
* mission timer shares the top-right corner, below the FPS readout
|
||||
* when both are visible. */
|
||||
const s32 fw = ovW();
|
||||
const s32 fh = ovH();
|
||||
gfx_set_overlay_scale(ovScale()); /* D510 */
|
||||
@@ -3434,7 +3481,14 @@ static Gfx *overlayEmitLocked(void)
|
||||
gDPSetTexturePersp(fgdl++, G_TP_NONE);
|
||||
gDPSetScissor(fgdl++, G_SC_NON_INTERLACE, 0, 0, viGetX(), viGetY());
|
||||
fgdl = microcode_constructor(fgdl);
|
||||
int ty = 6;
|
||||
if (s_showFps && s_fpsText[0]) {
|
||||
fgdl = drawTextR(fgdl, fw - 6, 6, s_fpsText, 0x40ff60ff);
|
||||
ty = 22;
|
||||
}
|
||||
if (s_missionTimeText[0]) {
|
||||
fgdl = drawTextR(fgdl, fw - 6, ty, s_missionTimeText, 0x40ff60ff);
|
||||
}
|
||||
gDPPipeSync(fgdl++);
|
||||
gSPEndDisplayList(fgdl++);
|
||||
return s_buf;
|
||||
@@ -3817,6 +3871,7 @@ static const struct { const char *key, *help; } kRowHelp[] = {
|
||||
{ "Game.SkipIntro", "Skips the intro and opens the file select screen." },
|
||||
{ "Game.AllUnlocked", "Unlocks all levels, 007 mode and every cheat." },
|
||||
{ "Game.CheckUpdates", "Notifies you when a new version is available." },
|
||||
{ "Game.MissionTimer", "Shows the mission elapsed time on screen (top-right, under the FPS readout)." },
|
||||
};
|
||||
|
||||
|
||||
|
||||
+32
-5
@@ -60,7 +60,7 @@ static int initDone = 0;
|
||||
* at modern resolutions without requiring 4x anti-aliasing.
|
||||
*/
|
||||
static int cfgVSync = 1; /* swap interval: 0 = off, 1 = on */
|
||||
static int cfgFpsCap = -1; /* frame cap in fps; -1 = Auto (match display refresh, 60..144), 0 = uncapped (vsync); menu: Auto/30/60, and 90/120/144 = D578 interpolation */
|
||||
static int cfgFpsCap = -1; /* frame cap in fps; -1 = Auto (match display refresh, up to 4x the VI rate), 0 = uncapped (vsync); menu: Auto/30/60, and 90/120/144/165/240 = D578 interpolation */
|
||||
static int cfgMSAA = 2; /* 1/2/4/8/16 samples; 2x default is lighter on low-end GPUs */
|
||||
static int cfgTexFilter = 1; /* 0 = nearest, 1 = bilinear (default), 2 = N64 3-point + trilinear, 3 = trilinear (Trilinear option, playtest 2026-10-03) */
|
||||
static int cfgFixMipTex = 1; /* RC2: clip mip-contaminated texture uploads to base height */
|
||||
@@ -1033,13 +1033,15 @@ static int s_dumpSlot = -1; /* D578: GE_PCDUMP_PRESENTS names each present */
|
||||
|
||||
/* Auto (Video.FpsCap = -1): match the display. Refresh unknown, or no faster
|
||||
* than the sim (60 Hz panel, 59.94 reported as 59/60) -> the VI rate, i.e. the
|
||||
* plain non-interpolated path; otherwise the refresh clamped to 144. */
|
||||
* plain non-interpolated path; otherwise the refresh itself, up to the
|
||||
* renderer's ceiling of 4 presents per sim tick (IP_SLOTS / 2 in libultra.c:
|
||||
* the ring holds this frame + one still waiting), i.e. 240 NTSC / 200 PAL. */
|
||||
int videoAutoFpsCap(void)
|
||||
{
|
||||
const int vi = (osTvType == 0) ? 50 : 60;
|
||||
const int refresh = gfx_sdl_get_refresh_rate();
|
||||
if (refresh <= vi + 5) return vi;
|
||||
return refresh > 144 ? 144 : refresh;
|
||||
return refresh > 4 * vi ? 4 * vi : refresh;
|
||||
}
|
||||
|
||||
static void videoApplyFpsCap(void)
|
||||
@@ -1277,6 +1279,24 @@ static void videoRenderPark(void)
|
||||
}
|
||||
}
|
||||
|
||||
/* D632: host thread, from the atexit handler (after the window state and
|
||||
* config are saved). Tears the GL context + window down only when the render
|
||||
* thread is parked: it then holds no context and never touches GL again. On a
|
||||
* park timeout the render thread may still be inside the driver, so deleting
|
||||
* its context would be worse than leaving it to the kernel. */
|
||||
void videoTeardownForExit(void)
|
||||
{
|
||||
if (!initDone) {
|
||||
return;
|
||||
}
|
||||
if (!SDL_AtomicGet(&s_parked)) {
|
||||
sysLogPrintf(LOG_WARNING, "video: render not parked; GL teardown skipped");
|
||||
return;
|
||||
}
|
||||
gfx_sdl_teardown_for_exit();
|
||||
sysLogPrintf(LOG_INFO, "video: GL context and window destroyed");
|
||||
}
|
||||
|
||||
/* Host thread: exit once the render thread is out of the GL driver. */
|
||||
static void videoHostExitIfRequested(void)
|
||||
{
|
||||
@@ -1284,8 +1304,15 @@ static void videoHostExitIfRequested(void)
|
||||
return;
|
||||
}
|
||||
Uint32 start = SDL_GetTicks();
|
||||
while (!SDL_AtomicGet(&s_parked) && SDL_AtomicGet(&s_inFrame) &&
|
||||
SDL_GetTicks() - start < QUIT_WAIT_MS) {
|
||||
/* D632: wait for the park itself, not just for "outside a frame". A
|
||||
* quit landing between frames (window close, 2026-10-10: "render idle
|
||||
* after 0 ms") used to exit at once, before the render thread had
|
||||
* unbound its context, so the D632 teardown was skipped and the GL
|
||||
* context fell to the kernel's process-exit cleanup -- the bugcheck
|
||||
* path. Once s_quitReq is set the next frame start parks instead of
|
||||
* drawing (see the ordering note above), so this takes about a frame;
|
||||
* QUIT_WAIT_MS stays the bound if the render thread is stuck. */
|
||||
while (!SDL_AtomicGet(&s_parked) && SDL_GetTicks() - start < QUIT_WAIT_MS) {
|
||||
SDL_Delay(2);
|
||||
}
|
||||
sysLogPrintf(LOG_INFO, "video: exiting (render %s after %u ms)",
|
||||
|
||||
@@ -27,6 +27,9 @@ extern u16 musicTrack1GetVolume(void);
|
||||
extern u16 get_mTrack2Vol(void);
|
||||
extern void fileBuildWriteNewSave(u32 folder);
|
||||
extern void set_cur_player_look_vertical_inverted(u32 value);
|
||||
extern int inputControlScheme(void); /* D513: 0 Modern, 1 Original */
|
||||
extern int inputPadLookInvert(void); /* D645 */
|
||||
extern void inputSetPadLookInvert(int v);
|
||||
extern void cur_player_set_aim_control(u32 value);
|
||||
extern void cur_player_set_lookahead(u32 value);
|
||||
extern void cur_player_set_sight_onscreen_control(u32 value);
|
||||
@@ -623,13 +626,35 @@ void watchSettingsGameTick(void)
|
||||
* during a level/menu transition. Each command's folder is re-checked
|
||||
* against selected_folder_num at apply time, so a stale cross-file edit
|
||||
* is still dropped, never misapplied. */
|
||||
/* D564: the game's own "Look up/down" option is not exposed (the port's
|
||||
* Mouse / Controller "Invert look" rows are the inversion controls). Pin it
|
||||
* to its factory value (0, as a blank profile has it) so a value saved by an
|
||||
* older build or set via the in-game watch menu cannot silently flip the
|
||||
* pad stick on the game's pitch path. RAM only; one int compare per tick. */
|
||||
if (stageActive() && g_CurrentPlayer && get_cur_player_look_vertical_inverted() != 0)
|
||||
/* D564: the game's own "Look up/down" option is not exposed in the
|
||||
* Modern scheme (the port's Mouse / Controller "Invert look" rows are
|
||||
* the inversion controls there -- the port writes the view directly on
|
||||
* those paths). Pin it to its factory value (0, as a blank profile has
|
||||
* it) so a value saved by an older build or set via the in-game watch
|
||||
* menu cannot silently flip the pad stick on the game's pitch path.
|
||||
* #145 (v0.6.0): in the Original scheme the pin is LIFTED -- the game
|
||||
* honours the setting in bondview2's stick-pitch path
|
||||
* (moveData.invertPitch), so the in-game watch "Look up/down" row is
|
||||
* the player's live control again, exactly as on the N64. RAM only;
|
||||
* one int compare per tick. */
|
||||
if (stageActive() && g_CurrentPlayer && inputControlScheme() == 0 &&
|
||||
get_cur_player_look_vertical_inverted() != 0)
|
||||
set_cur_player_look_vertical_inverted(0);
|
||||
/* D645 (#145): in Original the F10 "Invert look" row and the watch
|
||||
* "Look up/down" row are one setting. Whichever changed since the last
|
||||
* tick wins; the game then applies it natively (C-buttons and the
|
||||
* aim-mode stick), as on the N64. */
|
||||
static int lastRow = -1, lastGame = -1;
|
||||
if (!stageActive()) lastRow = -1; /* re-sync on each stage entry */
|
||||
if (stageActive() && g_CurrentPlayer && inputControlScheme() == 1) {
|
||||
int row = inputPadLookInvert(), game = get_cur_player_look_vertical_inverted() ? 1 : 0;
|
||||
if (lastRow < 0) { /* stage entry: the F10 row (edited in the front end) wins */
|
||||
if (row != game) set_cur_player_look_vertical_inverted(row);
|
||||
} else if (row != lastRow && row != game) set_cur_player_look_vertical_inverted(row);
|
||||
else if (game != lastGame && game != row) inputSetPadLookInvert(game);
|
||||
lastRow = inputPadLookInvert();
|
||||
lastGame = get_cur_player_look_vertical_inverted() ? 1 : 0;
|
||||
}
|
||||
|
||||
if (!stageActive() || !g_CurrentPlayer) {
|
||||
wsStageWasActive = 0;
|
||||
|
||||
@@ -172,6 +172,18 @@ cp LICENSE "$OUT/licenses/LICENSE-port-MIT.txt"
|
||||
for d in /opt/homebrew/opt/sdl2/LICENSE.txt /opt/homebrew/opt/sdl2-compat/LICENSE.txt /opt/homebrew/opt/sdl2/share/licenses/*/LICENSE.txt; do
|
||||
if [ -f "$d" ]; then cp "$d" "$OUT/licenses/SDL2.txt"; break; fi
|
||||
done
|
||||
# sdl2-compat loads the bundled libSDL3 (zlib); carry its text too.
|
||||
for d in /opt/homebrew/opt/sdl3/LICENSE.txt /opt/homebrew/opt/sdl3/share/licenses/*/LICENSE.txt; do
|
||||
if [ -f "$d" ]; then cp "$d" "$OUT/licenses/SDL3.txt"; break; fi
|
||||
done
|
||||
# The bundled libgcc_s / libstdc++ are GPL-3.0 with the GCC Runtime Library
|
||||
# Exception. The Homebrew gcc keg carries COPYING.RUNTIME but only GPL-2.0 as
|
||||
# COPYING, so the GPL-3.0 text ships from the tree.
|
||||
cp tools_pc/dist/GPL-3.0.txt "$OUT/licenses/gcc-COPYING3.txt"
|
||||
if [ -f /opt/homebrew/opt/gcc/COPYING.RUNTIME ]; then
|
||||
cp /opt/homebrew/opt/gcc/COPYING.RUNTIME "$OUT/licenses/gcc-COPYING.RUNTIME.txt"
|
||||
fi
|
||||
ls "$OUT/licenses"
|
||||
|
||||
# --- save converter + asset-prep tool (same assembly as bundle-linux.sh) ---
|
||||
mkdir -p "$OUT/tools"
|
||||
|
||||
@@ -0,0 +1,185 @@
|
||||
#!/usr/bin/env bash
|
||||
# demobatch.sh -- batch-replay maintainer-recorded pad demos through
|
||||
# tools_pc/levelbench.sh --replay, collect frames / flicker flags / pacing
|
||||
# stats, and write one report per batch. Sequential only (parallel runs
|
||||
# invalidate frame pacing).
|
||||
#
|
||||
# demobatch.sh [options] DEMODIR [ENV=VAL ...]
|
||||
# --dir GAMEDIR game dir holding data/ (default: cwd)
|
||||
# --exe PATH game binary (default: levelbench's autodetect)
|
||||
# --out ROOT results root (default: <GAMEDIR>/demobatch/<YYYYMMDD-HHMMSS>)
|
||||
# --spot f1,f2 dump just these game frames (GE_PCDUMP lo-hi:step with
|
||||
# step = gcd of the spacing; extra frames are deleted)
|
||||
# --flicker lo-hi dump every frame in the window, run flicker_scan.py
|
||||
# (--spot and --flicker are mutually exclusive: one dump
|
||||
# spec per run)
|
||||
# --only a,b only these demo names (file name without .demo)
|
||||
# --dry-run print the commands, launch nothing
|
||||
# demobatch.sh --compare ROOT_A ROOT_B
|
||||
# side-by-side report rows with deltas for numeric columns
|
||||
#
|
||||
# Demo harness (maintainer workflow). Record once, per level:
|
||||
# tools_pc/levelbench.sh --dir <game dir> --secs N --record demos/<name>.demo <level>
|
||||
# You play the level with the pad; input is saved to the .demo and a
|
||||
# <name>.demo.meta (level, secs, seed, binary md5, date) is written beside it.
|
||||
# Demos live in <game dir>/demos/ -- personal input recordings, gitignored,
|
||||
# never committed. Then `demobatch.sh --dir <game dir> <game dir>/demos`
|
||||
# replays each with the recorded level/secs/seed (GE_RSEED pinned) and writes
|
||||
# <ROOT>/report.txt. Replays drift slightly (D117): compare runs within one
|
||||
# session (--compare), never as pixel gates. Extra ENV=VAL args go to every run.
|
||||
set -u
|
||||
HERE=$(cd "$(dirname "$0")" && pwd)
|
||||
absdir() { (cd "$1" 2>/dev/null && pwd); }
|
||||
|
||||
compare() {
|
||||
local A=$1/report.txt B=$2/report.txt
|
||||
if [ ! -f "$A" ] || [ ! -f "$B" ]; then echo "missing report.txt in $1 or $2" >&2; exit 2; fi
|
||||
awk '
|
||||
function d(a, b) {
|
||||
if (a ~ /^-?[0-9.]+$/ && b ~ /^-?[0-9.]+$/) return sprintf("%+.2f", b - a)
|
||||
return "-"
|
||||
}
|
||||
FNR == NR { if ($1 != "name" && NF >= 9) { a[$1] = $0; order[++n] = $1 } next }
|
||||
$1 != "name" && NF >= 9 { b[$1] = $0 }
|
||||
END {
|
||||
f = "%-16s %-5s | %-5s %-5s | %-4s %-4s %-6s | %-8s %-8s %-7s | %-6s %-6s %-6s | %-5s %-5s %-6s\n"
|
||||
printf f, "name", "level", "exitA", "exitB", "wdA", "wdB", "dWd", "pres/sA", "pres/sB", "dPres", "unevA", "unevB", "dUnev", "slowA", "slowB", "dSlow"
|
||||
for (i = 1; i <= n; i++) {
|
||||
k = order[i]
|
||||
split(a[k], x, " ")
|
||||
if (!(k in b)) { printf "%-16s (only in A)\n", k; continue }
|
||||
split(b[k], y, " ")
|
||||
printf f, k, x[2], x[3], y[3], x[5], y[5], d(x[5], y[5]), x[6], y[6], d(x[6], y[6]), x[7], y[7], d(x[7], y[7]), x[8], y[8], d(x[8], y[8])
|
||||
if (x[9] != y[9]) printf "%-16s flicker flags: %s -> %s (%s)\n", "", x[9], y[9], d(x[9], y[9])
|
||||
}
|
||||
for (k in b) if (!(k in a)) printf "%-16s (only in B)\n", k
|
||||
}' "$A" "$B"
|
||||
}
|
||||
|
||||
if [ "${1:-}" = --compare ]; then
|
||||
[ $# -eq 3 ] || { echo "usage: $0 --compare ROOT_A ROOT_B" >&2; exit 2; }
|
||||
compare "$2" "$3"; exit 0
|
||||
fi
|
||||
|
||||
GAME=$PWD; EXE=; OUT=; SPOT=; FLK=; ONLY=; DRY=0
|
||||
while [ $# -gt 0 ]; do
|
||||
case $1 in
|
||||
--dir) GAME=$2; shift 2;;
|
||||
--exe) EXE=$2; shift 2;;
|
||||
--out) OUT=$2; shift 2;;
|
||||
--spot) SPOT=$2; shift 2;;
|
||||
--flicker) FLK=$2; shift 2;;
|
||||
--only) ONLY=$2; shift 2;;
|
||||
--dry-run) DRY=1; shift;;
|
||||
-h|--help) sed -n 2,26p "$0"; exit 0;;
|
||||
*) break;;
|
||||
esac
|
||||
done
|
||||
[ $# -ge 1 ] || { echo "usage: $0 [options] DEMODIR [ENV=VAL ...]" >&2; exit 2; }
|
||||
DEMODIR=$1; shift
|
||||
if [ -n "$SPOT" ] && [ -n "$FLK" ]; then echo "--spot and --flicker are mutually exclusive" >&2; exit 2; fi
|
||||
[ -d "$DEMODIR" ] || { echo "no demo dir: $DEMODIR" >&2; exit 2; }
|
||||
GAME=$(absdir "$GAME") || { echo "bad --dir" >&2; exit 2; }
|
||||
DEMODIR=$(absdir "$DEMODIR")
|
||||
[ -n "$OUT" ] || OUT="$GAME/demobatch/$(date +%Y%m%d-%H%M%S)"
|
||||
case $OUT in /*|[A-Za-z]:*) ;; *) OUT=$PWD/$OUT;; esac
|
||||
|
||||
# Dump spec: spot frames -> lo-hi:step (step = gcd of gaps); flicker -> lo-hi.
|
||||
DUMP=; SPOTLIST=
|
||||
if [ -n "$SPOT" ]; then
|
||||
SPOTLIST=$(echo "$SPOT" | tr ',' '\n' | sort -n -u | tr '\n' ' ')
|
||||
read -r -a FR <<< "$SPOTLIST"
|
||||
lo=${FR[0]}; hi=${FR[$((${#FR[@]} - 1))]}; g=0
|
||||
for ((i = 1; i < ${#FR[@]}; i++)); do
|
||||
a=$((FR[i] - FR[i-1])); b=$g
|
||||
while [ "$b" -ne 0 ]; do t=$((a % b)); a=$b; b=$t; done
|
||||
g=$a
|
||||
done
|
||||
DUMP="$lo-$hi"
|
||||
[ "$g" -gt 1 ] && DUMP="$lo-$hi:$g"
|
||||
elif [ -n "$FLK" ]; then
|
||||
DUMP=$FLK
|
||||
fi
|
||||
|
||||
LB=("$HERE/levelbench.sh" --dir "$GAME")
|
||||
[ -n "$EXE" ] && LB+=(--exe "$EXE")
|
||||
PY=
|
||||
for c in python3 python py; do
|
||||
if command -v "$c" > /dev/null 2>&1 && "$c" -c 'import sys' > /dev/null 2>&1; then PY=$c; break; fi
|
||||
done
|
||||
[ -n "$PY" ] || PY=python3
|
||||
[ "$DRY" = 1 ] || mkdir -p "$OUT"
|
||||
|
||||
metaget() { sed -n "s/^$2=//p" "$1" | head -1 | tr -d '\r'; }
|
||||
|
||||
NAMES=()
|
||||
for demo in "$DEMODIR"/*.demo; do
|
||||
[ -f "$demo" ] || continue
|
||||
n=$(basename "$demo" .demo)
|
||||
if [ -n "$ONLY" ]; then case ",$ONLY," in *",$n,"*) ;; *) continue;; esac; fi
|
||||
if [ ! -f "$demo.meta" ]; then echo "skip $n: no $n.demo.meta" >&2; continue; fi
|
||||
NAMES+=("$n")
|
||||
lvl=$(metaget "$demo.meta" level); secs=$(metaget "$demo.meta" secs); seed=$(metaget "$demo.meta" seed)
|
||||
CMD=("${LB[@]}" --secs "$secs" --replay "$demo" --out "$OUT/$n")
|
||||
[ -n "$DUMP" ] && CMD+=(--dump "$DUMP")
|
||||
CMD+=("$lvl" "GE_RSEED=${seed:-0123456789abcdef}" "$@")
|
||||
if [ "$DRY" = 1 ]; then
|
||||
echo "[$n] ${CMD[*]}"
|
||||
[ -n "$SPOTLIST" ] && echo "[$n] then keep only frames: $SPOTLIST"
|
||||
[ -n "$FLK" ] && echo "[$n] then: $PY -I $HERE/flicker_scan.py $OUT/$n/ppm --csv $OUT/$n/flicker.csv"
|
||||
continue
|
||||
fi
|
||||
echo "=== $n (level $lvl, ${secs}s)"
|
||||
"${CMD[@]}" > "$OUT/$n.console" 2>&1
|
||||
tail -3 "$OUT/$n.console"
|
||||
if [ -n "$SPOTLIST" ] && [ -d "$OUT/$n/ppm" ]; then
|
||||
for f in "$OUT/$n"/ppm/frame_*.ppm; do
|
||||
[ -e "$f" ] || continue
|
||||
num=$((10#$(basename "$f" .ppm | sed 's/frame_//')))
|
||||
case " $SPOTLIST " in *" $num "*) ;; *) rm -f "$f";; esac
|
||||
done
|
||||
fi
|
||||
if [ -n "$FLK" ] && [ -d "$OUT/$n/ppm" ]; then
|
||||
"$PY" -I "$HERE/flicker_scan.py" "$OUT/$n/ppm" --csv "$OUT/$n/flicker.csv" > "$OUT/$n/flicker.txt" 2>&1
|
||||
tail -1 "$OUT/$n/flicker.txt"
|
||||
fi
|
||||
done
|
||||
if [ "$DRY" = 1 ]; then echo "(dry run: no report written; root would be $OUT)"; exit 0; fi
|
||||
|
||||
# ---- report ----------------------------------------------------------------
|
||||
# presents/s: mean over "D578 interp: X presents/s, ..." lines.
|
||||
# uneven%: from "D578 cadence: gaps <7:a 7-9.5:b 9.5-12:c 12-20:d >20:e | ...",
|
||||
# bucket counts summed over all lines; uneven = share outside the modal bucket.
|
||||
R="$OUT/report.txt"
|
||||
fmt="%-16s %-5s %-5s %-5s %-9s %-10s %-8s %-5s %-7s\n"
|
||||
{
|
||||
printf "$fmt" name level exit crash watchdog presents/s uneven% slow flicker
|
||||
for n in "${NAMES[@]}"; do
|
||||
d="$OUT/$n"; L="$d/ge007.log"; [ -f "$L" ] || L="$d/stdout.txt"
|
||||
lvl=$(metaget "$DEMODIR/$n.demo.meta" level)
|
||||
ex=n; crash=n; wd=0; slow=0; stats="- -"
|
||||
if [ -f "$L" ]; then
|
||||
grep -q -E 'quit requested|video: exiting' "$L" && ex=y
|
||||
wd=$(grep -c 'no frame rendered' "$L")
|
||||
slow=$(grep -c 'D583 SLOW' "$L")
|
||||
stats=$(awk '
|
||||
/D578 interp:/ { for (i = 2; i <= NF; i++) if ($i ~ /^presents\/s/) { p += $(i-1); pn++ } }
|
||||
/D578 cadence:/ {
|
||||
for (i = 1; i <= NF; i++) if ($i == "gaps") {
|
||||
for (j = 1; j <= 5; j++) { s = $(i + j); sub(/^.*:/, "", s); g[j] += s + 0 }
|
||||
break
|
||||
}
|
||||
}
|
||||
END {
|
||||
tot = 0; mx = 0
|
||||
for (j = 1; j <= 5; j++) { tot += g[j]; if (g[j] > mx) mx = g[j] }
|
||||
printf "%s %s", (pn ? sprintf("%.1f", p / pn) : "-"), (tot ? sprintf("%.1f", 100 * (tot - mx) / tot) : "-")
|
||||
}' "$L")
|
||||
fi
|
||||
[ -f "$d/ge007.crash.log" ] && crash=y
|
||||
fl=-; [ -f "$d/flicker.txt" ] && fl=$(grep -c '^FLICKER' "$d/flicker.txt")
|
||||
printf "$fmt" "$n" "${lvl:--}" "$ex" "$crash" "$wd" "${stats% *}" "${stats#* }" "$slow" "$fl"
|
||||
done
|
||||
} > "$R"
|
||||
cat "$R"
|
||||
echo "report: $R"
|
||||
Vendored
+674
@@ -0,0 +1,674 @@
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The GNU General Public License is a free, copyleft license for
|
||||
software and other kinds of works.
|
||||
|
||||
The licenses for most software and other practical works are designed
|
||||
to take away your freedom to share and change the works. By contrast,
|
||||
the GNU General Public License is intended to guarantee your freedom to
|
||||
share and change all versions of a program--to make sure it remains free
|
||||
software for all its users. We, the Free Software Foundation, use the
|
||||
GNU General Public License for most of our software; it applies also to
|
||||
any other work released this way by its authors. You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
them if you wish), that you receive source code or can get it if you
|
||||
want it, that you can change the software or use pieces of it in new
|
||||
free programs, and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to prevent others from denying you
|
||||
these rights or asking you to surrender the rights. Therefore, you have
|
||||
certain responsibilities if you distribute copies of the software, or if
|
||||
you modify it: responsibilities to respect the freedom of others.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must pass on to the recipients the same
|
||||
freedoms that you received. You must make sure that they, too, receive
|
||||
or can get the source code. And you must show them these terms so they
|
||||
know their rights.
|
||||
|
||||
Developers that use the GNU GPL protect your rights with two steps:
|
||||
(1) assert copyright on the software, and (2) offer you this License
|
||||
giving you legal permission to copy, distribute and/or modify it.
|
||||
|
||||
For the developers' and authors' protection, the GPL clearly explains
|
||||
that there is no warranty for this free software. For both users' and
|
||||
authors' sake, the GPL requires that modified versions be marked as
|
||||
changed, so that their problems will not be attributed erroneously to
|
||||
authors of previous versions.
|
||||
|
||||
Some devices are designed to deny users access to install or run
|
||||
modified versions of the software inside them, although the manufacturer
|
||||
can do so. This is fundamentally incompatible with the aim of
|
||||
protecting users' freedom to change the software. The systematic
|
||||
pattern of such abuse occurs in the area of products for individuals to
|
||||
use, which is precisely where it is most unacceptable. Therefore, we
|
||||
have designed this version of the GPL to prohibit the practice for those
|
||||
products. If such problems arise substantially in other domains, we
|
||||
stand ready to extend this provision to those domains in future versions
|
||||
of the GPL, as needed to protect the freedom of users.
|
||||
|
||||
Finally, every program is threatened constantly by software patents.
|
||||
States should not allow patents to restrict development and use of
|
||||
software on general-purpose computers, but in those that do, we wish to
|
||||
avoid the special danger that patents applied to a free program could
|
||||
make it effectively proprietary. To prevent this, the GPL assures that
|
||||
patents cannot be used to render the program non-free.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
TERMS AND CONDITIONS
|
||||
|
||||
0. Definitions.
|
||||
|
||||
"This License" refers to version 3 of the GNU General Public License.
|
||||
|
||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||
works, such as semiconductor masks.
|
||||
|
||||
"The Program" refers to any copyrightable work licensed under this
|
||||
License. Each licensee is addressed as "you". "Licensees" and
|
||||
"recipients" may be individuals or organizations.
|
||||
|
||||
To "modify" a work means to copy from or adapt all or part of the work
|
||||
in a fashion requiring copyright permission, other than the making of an
|
||||
exact copy. The resulting work is called a "modified version" of the
|
||||
earlier work or a work "based on" the earlier work.
|
||||
|
||||
A "covered work" means either the unmodified Program or a work based
|
||||
on the Program.
|
||||
|
||||
To "propagate" a work means to do anything with it that, without
|
||||
permission, would make you directly or secondarily liable for
|
||||
infringement under applicable copyright law, except executing it on a
|
||||
computer or modifying a private copy. Propagation includes copying,
|
||||
distribution (with or without modification), making available to the
|
||||
public, and in some countries other activities as well.
|
||||
|
||||
To "convey" a work means any kind of propagation that enables other
|
||||
parties to make or receive copies. Mere interaction with a user through
|
||||
a computer network, with no transfer of a copy, is not conveying.
|
||||
|
||||
An interactive user interface displays "Appropriate Legal Notices"
|
||||
to the extent that it includes a convenient and prominently visible
|
||||
feature that (1) displays an appropriate copyright notice, and (2)
|
||||
tells the user that there is no warranty for the work (except to the
|
||||
extent that warranties are provided), that licensees may convey the
|
||||
work under this License, and how to view a copy of this License. If
|
||||
the interface presents a list of user commands or options, such as a
|
||||
menu, a prominent item in the list meets this criterion.
|
||||
|
||||
1. Source Code.
|
||||
|
||||
The "source code" for a work means the preferred form of the work
|
||||
for making modifications to it. "Object code" means any non-source
|
||||
form of a work.
|
||||
|
||||
A "Standard Interface" means an interface that either is an official
|
||||
standard defined by a recognized standards body, or, in the case of
|
||||
interfaces specified for a particular programming language, one that
|
||||
is widely used among developers working in that language.
|
||||
|
||||
The "System Libraries" of an executable work include anything, other
|
||||
than the work as a whole, that (a) is included in the normal form of
|
||||
packaging a Major Component, but which is not part of that Major
|
||||
Component, and (b) serves only to enable use of the work with that
|
||||
Major Component, or to implement a Standard Interface for which an
|
||||
implementation is available to the public in source code form. A
|
||||
"Major Component", in this context, means a major essential component
|
||||
(kernel, window system, and so on) of the specific operating system
|
||||
(if any) on which the executable work runs, or a compiler used to
|
||||
produce the work, or an object code interpreter used to run it.
|
||||
|
||||
The "Corresponding Source" for a work in object code form means all
|
||||
the source code needed to generate, install, and (for an executable
|
||||
work) run the object code and to modify the work, including scripts to
|
||||
control those activities. However, it does not include the work's
|
||||
System Libraries, or general-purpose tools or generally available free
|
||||
programs which are used unmodified in performing those activities but
|
||||
which are not part of the work. For example, Corresponding Source
|
||||
includes interface definition files associated with source files for
|
||||
the work, and the source code for shared libraries and dynamically
|
||||
linked subprograms that the work is specifically designed to require,
|
||||
such as by intimate data communication or control flow between those
|
||||
subprograms and other parts of the work.
|
||||
|
||||
The Corresponding Source need not include anything that users
|
||||
can regenerate automatically from other parts of the Corresponding
|
||||
Source.
|
||||
|
||||
The Corresponding Source for a work in source code form is that
|
||||
same work.
|
||||
|
||||
2. Basic Permissions.
|
||||
|
||||
All rights granted under this License are granted for the term of
|
||||
copyright on the Program, and are irrevocable provided the stated
|
||||
conditions are met. This License explicitly affirms your unlimited
|
||||
permission to run the unmodified Program. The output from running a
|
||||
covered work is covered by this License only if the output, given its
|
||||
content, constitutes a covered work. This License acknowledges your
|
||||
rights of fair use or other equivalent, as provided by copyright law.
|
||||
|
||||
You may make, run and propagate covered works that you do not
|
||||
convey, without conditions so long as your license otherwise remains
|
||||
in force. You may convey covered works to others for the sole purpose
|
||||
of having them make modifications exclusively for you, or provide you
|
||||
with facilities for running those works, provided that you comply with
|
||||
the terms of this License in conveying all material for which you do
|
||||
not control copyright. Those thus making or running the covered works
|
||||
for you must do so exclusively on your behalf, under your direction
|
||||
and control, on terms that prohibit them from making any copies of
|
||||
your copyrighted material outside their relationship with you.
|
||||
|
||||
Conveying under any other circumstances is permitted solely under
|
||||
the conditions stated below. Sublicensing is not allowed; section 10
|
||||
makes it unnecessary.
|
||||
|
||||
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||
|
||||
No covered work shall be deemed part of an effective technological
|
||||
measure under any applicable law fulfilling obligations under article
|
||||
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||
similar laws prohibiting or restricting circumvention of such
|
||||
measures.
|
||||
|
||||
When you convey a covered work, you waive any legal power to forbid
|
||||
circumvention of technological measures to the extent such circumvention
|
||||
is effected by exercising rights under this License with respect to
|
||||
the covered work, and you disclaim any intention to limit operation or
|
||||
modification of the work as a means of enforcing, against the work's
|
||||
users, your or third parties' legal rights to forbid circumvention of
|
||||
technological measures.
|
||||
|
||||
4. Conveying Verbatim Copies.
|
||||
|
||||
You may convey verbatim copies of the Program's source code as you
|
||||
receive it, in any medium, provided that you conspicuously and
|
||||
appropriately publish on each copy an appropriate copyright notice;
|
||||
keep intact all notices stating that this License and any
|
||||
non-permissive terms added in accord with section 7 apply to the code;
|
||||
keep intact all notices of the absence of any warranty; and give all
|
||||
recipients a copy of this License along with the Program.
|
||||
|
||||
You may charge any price or no price for each copy that you convey,
|
||||
and you may offer support or warranty protection for a fee.
|
||||
|
||||
5. Conveying Modified Source Versions.
|
||||
|
||||
You may convey a work based on the Program, or the modifications to
|
||||
produce it from the Program, in the form of source code under the
|
||||
terms of section 4, provided that you also meet all of these conditions:
|
||||
|
||||
a) The work must carry prominent notices stating that you modified
|
||||
it, and giving a relevant date.
|
||||
|
||||
b) The work must carry prominent notices stating that it is
|
||||
released under this License and any conditions added under section
|
||||
7. This requirement modifies the requirement in section 4 to
|
||||
"keep intact all notices".
|
||||
|
||||
c) You must license the entire work, as a whole, under this
|
||||
License to anyone who comes into possession of a copy. This
|
||||
License will therefore apply, along with any applicable section 7
|
||||
additional terms, to the whole of the work, and all its parts,
|
||||
regardless of how they are packaged. This License gives no
|
||||
permission to license the work in any other way, but it does not
|
||||
invalidate such permission if you have separately received it.
|
||||
|
||||
d) If the work has interactive user interfaces, each must display
|
||||
Appropriate Legal Notices; however, if the Program has interactive
|
||||
interfaces that do not display Appropriate Legal Notices, your
|
||||
work need not make them do so.
|
||||
|
||||
A compilation of a covered work with other separate and independent
|
||||
works, which are not by their nature extensions of the covered work,
|
||||
and which are not combined with it such as to form a larger program,
|
||||
in or on a volume of a storage or distribution medium, is called an
|
||||
"aggregate" if the compilation and its resulting copyright are not
|
||||
used to limit the access or legal rights of the compilation's users
|
||||
beyond what the individual works permit. Inclusion of a covered work
|
||||
in an aggregate does not cause this License to apply to the other
|
||||
parts of the aggregate.
|
||||
|
||||
6. Conveying Non-Source Forms.
|
||||
|
||||
You may convey a covered work in object code form under the terms
|
||||
of sections 4 and 5, provided that you also convey the
|
||||
machine-readable Corresponding Source under the terms of this License,
|
||||
in one of these ways:
|
||||
|
||||
a) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by the
|
||||
Corresponding Source fixed on a durable physical medium
|
||||
customarily used for software interchange.
|
||||
|
||||
b) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by a
|
||||
written offer, valid for at least three years and valid for as
|
||||
long as you offer spare parts or customer support for that product
|
||||
model, to give anyone who possesses the object code either (1) a
|
||||
copy of the Corresponding Source for all the software in the
|
||||
product that is covered by this License, on a durable physical
|
||||
medium customarily used for software interchange, for a price no
|
||||
more than your reasonable cost of physically performing this
|
||||
conveying of source, or (2) access to copy the
|
||||
Corresponding Source from a network server at no charge.
|
||||
|
||||
c) Convey individual copies of the object code with a copy of the
|
||||
written offer to provide the Corresponding Source. This
|
||||
alternative is allowed only occasionally and noncommercially, and
|
||||
only if you received the object code with such an offer, in accord
|
||||
with subsection 6b.
|
||||
|
||||
d) Convey the object code by offering access from a designated
|
||||
place (gratis or for a charge), and offer equivalent access to the
|
||||
Corresponding Source in the same way through the same place at no
|
||||
further charge. You need not require recipients to copy the
|
||||
Corresponding Source along with the object code. If the place to
|
||||
copy the object code is a network server, the Corresponding Source
|
||||
may be on a different server (operated by you or a third party)
|
||||
that supports equivalent copying facilities, provided you maintain
|
||||
clear directions next to the object code saying where to find the
|
||||
Corresponding Source. Regardless of what server hosts the
|
||||
Corresponding Source, you remain obligated to ensure that it is
|
||||
available for as long as needed to satisfy these requirements.
|
||||
|
||||
e) Convey the object code using peer-to-peer transmission, provided
|
||||
you inform other peers where the object code and Corresponding
|
||||
Source of the work are being offered to the general public at no
|
||||
charge under subsection 6d.
|
||||
|
||||
A separable portion of the object code, whose source code is excluded
|
||||
from the Corresponding Source as a System Library, need not be
|
||||
included in conveying the object code work.
|
||||
|
||||
A "User Product" is either (1) a "consumer product", which means any
|
||||
tangible personal property which is normally used for personal, family,
|
||||
or household purposes, or (2) anything designed or sold for incorporation
|
||||
into a dwelling. In determining whether a product is a consumer product,
|
||||
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||
product received by a particular user, "normally used" refers to a
|
||||
typical or common use of that class of product, regardless of the status
|
||||
of the particular user or of the way in which the particular user
|
||||
actually uses, or expects or is expected to use, the product. A product
|
||||
is a consumer product regardless of whether the product has substantial
|
||||
commercial, industrial or non-consumer uses, unless such uses represent
|
||||
the only significant mode of use of the product.
|
||||
|
||||
"Installation Information" for a User Product means any methods,
|
||||
procedures, authorization keys, or other information required to install
|
||||
and execute modified versions of a covered work in that User Product from
|
||||
a modified version of its Corresponding Source. The information must
|
||||
suffice to ensure that the continued functioning of the modified object
|
||||
code is in no case prevented or interfered with solely because
|
||||
modification has been made.
|
||||
|
||||
If you convey an object code work under this section in, or with, or
|
||||
specifically for use in, a User Product, and the conveying occurs as
|
||||
part of a transaction in which the right of possession and use of the
|
||||
User Product is transferred to the recipient in perpetuity or for a
|
||||
fixed term (regardless of how the transaction is characterized), the
|
||||
Corresponding Source conveyed under this section must be accompanied
|
||||
by the Installation Information. But this requirement does not apply
|
||||
if neither you nor any third party retains the ability to install
|
||||
modified object code on the User Product (for example, the work has
|
||||
been installed in ROM).
|
||||
|
||||
The requirement to provide Installation Information does not include a
|
||||
requirement to continue to provide support service, warranty, or updates
|
||||
for a work that has been modified or installed by the recipient, or for
|
||||
the User Product in which it has been modified or installed. Access to a
|
||||
network may be denied when the modification itself materially and
|
||||
adversely affects the operation of the network or violates the rules and
|
||||
protocols for communication across the network.
|
||||
|
||||
Corresponding Source conveyed, and Installation Information provided,
|
||||
in accord with this section must be in a format that is publicly
|
||||
documented (and with an implementation available to the public in
|
||||
source code form), and must require no special password or key for
|
||||
unpacking, reading or copying.
|
||||
|
||||
7. Additional Terms.
|
||||
|
||||
"Additional permissions" are terms that supplement the terms of this
|
||||
License by making exceptions from one or more of its conditions.
|
||||
Additional permissions that are applicable to the entire Program shall
|
||||
be treated as though they were included in this License, to the extent
|
||||
that they are valid under applicable law. If additional permissions
|
||||
apply only to part of the Program, that part may be used separately
|
||||
under those permissions, but the entire Program remains governed by
|
||||
this License without regard to the additional permissions.
|
||||
|
||||
When you convey a copy of a covered work, you may at your option
|
||||
remove any additional permissions from that copy, or from any part of
|
||||
it. (Additional permissions may be written to require their own
|
||||
removal in certain cases when you modify the work.) You may place
|
||||
additional permissions on material, added by you to a covered work,
|
||||
for which you have or can give appropriate copyright permission.
|
||||
|
||||
Notwithstanding any other provision of this License, for material you
|
||||
add to a covered work, you may (if authorized by the copyright holders of
|
||||
that material) supplement the terms of this License with terms:
|
||||
|
||||
a) Disclaiming warranty or limiting liability differently from the
|
||||
terms of sections 15 and 16 of this License; or
|
||||
|
||||
b) Requiring preservation of specified reasonable legal notices or
|
||||
author attributions in that material or in the Appropriate Legal
|
||||
Notices displayed by works containing it; or
|
||||
|
||||
c) Prohibiting misrepresentation of the origin of that material, or
|
||||
requiring that modified versions of such material be marked in
|
||||
reasonable ways as different from the original version; or
|
||||
|
||||
d) Limiting the use for publicity purposes of names of licensors or
|
||||
authors of the material; or
|
||||
|
||||
e) Declining to grant rights under trademark law for use of some
|
||||
trade names, trademarks, or service marks; or
|
||||
|
||||
f) Requiring indemnification of licensors and authors of that
|
||||
material by anyone who conveys the material (or modified versions of
|
||||
it) with contractual assumptions of liability to the recipient, for
|
||||
any liability that these contractual assumptions directly impose on
|
||||
those licensors and authors.
|
||||
|
||||
All other non-permissive additional terms are considered "further
|
||||
restrictions" within the meaning of section 10. If the Program as you
|
||||
received it, or any part of it, contains a notice stating that it is
|
||||
governed by this License along with a term that is a further
|
||||
restriction, you may remove that term. If a license document contains
|
||||
a further restriction but permits relicensing or conveying under this
|
||||
License, you may add to a covered work material governed by the terms
|
||||
of that license document, provided that the further restriction does
|
||||
not survive such relicensing or conveying.
|
||||
|
||||
If you add terms to a covered work in accord with this section, you
|
||||
must place, in the relevant source files, a statement of the
|
||||
additional terms that apply to those files, or a notice indicating
|
||||
where to find the applicable terms.
|
||||
|
||||
Additional terms, permissive or non-permissive, may be stated in the
|
||||
form of a separately written license, or stated as exceptions;
|
||||
the above requirements apply either way.
|
||||
|
||||
8. Termination.
|
||||
|
||||
You may not propagate or modify a covered work except as expressly
|
||||
provided under this License. Any attempt otherwise to propagate or
|
||||
modify it is void, and will automatically terminate your rights under
|
||||
this License (including any patent licenses granted under the third
|
||||
paragraph of section 11).
|
||||
|
||||
However, if you cease all violation of this License, then your
|
||||
license from a particular copyright holder is reinstated (a)
|
||||
provisionally, unless and until the copyright holder explicitly and
|
||||
finally terminates your license, and (b) permanently, if the copyright
|
||||
holder fails to notify you of the violation by some reasonable means
|
||||
prior to 60 days after the cessation.
|
||||
|
||||
Moreover, your license from a particular copyright holder is
|
||||
reinstated permanently if the copyright holder notifies you of the
|
||||
violation by some reasonable means, this is the first time you have
|
||||
received notice of violation of this License (for any work) from that
|
||||
copyright holder, and you cure the violation prior to 30 days after
|
||||
your receipt of the notice.
|
||||
|
||||
Termination of your rights under this section does not terminate the
|
||||
licenses of parties who have received copies or rights from you under
|
||||
this License. If your rights have been terminated and not permanently
|
||||
reinstated, you do not qualify to receive new licenses for the same
|
||||
material under section 10.
|
||||
|
||||
9. Acceptance Not Required for Having Copies.
|
||||
|
||||
You are not required to accept this License in order to receive or
|
||||
run a copy of the Program. Ancillary propagation of a covered work
|
||||
occurring solely as a consequence of using peer-to-peer transmission
|
||||
to receive a copy likewise does not require acceptance. However,
|
||||
nothing other than this License grants you permission to propagate or
|
||||
modify any covered work. These actions infringe copyright if you do
|
||||
not accept this License. Therefore, by modifying or propagating a
|
||||
covered work, you indicate your acceptance of this License to do so.
|
||||
|
||||
10. Automatic Licensing of Downstream Recipients.
|
||||
|
||||
Each time you convey a covered work, the recipient automatically
|
||||
receives a license from the original licensors, to run, modify and
|
||||
propagate that work, subject to this License. You are not responsible
|
||||
for enforcing compliance by third parties with this License.
|
||||
|
||||
An "entity transaction" is a transaction transferring control of an
|
||||
organization, or substantially all assets of one, or subdividing an
|
||||
organization, or merging organizations. If propagation of a covered
|
||||
work results from an entity transaction, each party to that
|
||||
transaction who receives a copy of the work also receives whatever
|
||||
licenses to the work the party's predecessor in interest had or could
|
||||
give under the previous paragraph, plus a right to possession of the
|
||||
Corresponding Source of the work from the predecessor in interest, if
|
||||
the predecessor has it or can get it with reasonable efforts.
|
||||
|
||||
You may not impose any further restrictions on the exercise of the
|
||||
rights granted or affirmed under this License. For example, you may
|
||||
not impose a license fee, royalty, or other charge for exercise of
|
||||
rights granted under this License, and you may not initiate litigation
|
||||
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||
any patent claim is infringed by making, using, selling, offering for
|
||||
sale, or importing the Program or any portion of it.
|
||||
|
||||
11. Patents.
|
||||
|
||||
A "contributor" is a copyright holder who authorizes use under this
|
||||
License of the Program or a work on which the Program is based. The
|
||||
work thus licensed is called the contributor's "contributor version".
|
||||
|
||||
A contributor's "essential patent claims" are all patent claims
|
||||
owned or controlled by the contributor, whether already acquired or
|
||||
hereafter acquired, that would be infringed by some manner, permitted
|
||||
by this License, of making, using, or selling its contributor version,
|
||||
but do not include claims that would be infringed only as a
|
||||
consequence of further modification of the contributor version. For
|
||||
purposes of this definition, "control" includes the right to grant
|
||||
patent sublicenses in a manner consistent with the requirements of
|
||||
this License.
|
||||
|
||||
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||
patent license under the contributor's essential patent claims, to
|
||||
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||
propagate the contents of its contributor version.
|
||||
|
||||
In the following three paragraphs, a "patent license" is any express
|
||||
agreement or commitment, however denominated, not to enforce a patent
|
||||
(such as an express permission to practice a patent or covenant not to
|
||||
sue for patent infringement). To "grant" such a patent license to a
|
||||
party means to make such an agreement or commitment not to enforce a
|
||||
patent against the party.
|
||||
|
||||
If you convey a covered work, knowingly relying on a patent license,
|
||||
and the Corresponding Source of the work is not available for anyone
|
||||
to copy, free of charge and under the terms of this License, through a
|
||||
publicly available network server or other readily accessible means,
|
||||
then you must either (1) cause the Corresponding Source to be so
|
||||
available, or (2) arrange to deprive yourself of the benefit of the
|
||||
patent license for this particular work, or (3) arrange, in a manner
|
||||
consistent with the requirements of this License, to extend the patent
|
||||
license to downstream recipients. "Knowingly relying" means you have
|
||||
actual knowledge that, but for the patent license, your conveying the
|
||||
covered work in a country, or your recipient's use of the covered work
|
||||
in a country, would infringe one or more identifiable patents in that
|
||||
country that you have reason to believe are valid.
|
||||
|
||||
If, pursuant to or in connection with a single transaction or
|
||||
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||
covered work, and grant a patent license to some of the parties
|
||||
receiving the covered work authorizing them to use, propagate, modify
|
||||
or convey a specific copy of the covered work, then the patent license
|
||||
you grant is automatically extended to all recipients of the covered
|
||||
work and works based on it.
|
||||
|
||||
A patent license is "discriminatory" if it does not include within
|
||||
the scope of its coverage, prohibits the exercise of, or is
|
||||
conditioned on the non-exercise of one or more of the rights that are
|
||||
specifically granted under this License. You may not convey a covered
|
||||
work if you are a party to an arrangement with a third party that is
|
||||
in the business of distributing software, under which you make payment
|
||||
to the third party based on the extent of your activity of conveying
|
||||
the work, and under which the third party grants, to any of the
|
||||
parties who would receive the covered work from you, a discriminatory
|
||||
patent license (a) in connection with copies of the covered work
|
||||
conveyed by you (or copies made from those copies), or (b) primarily
|
||||
for and in connection with specific products or compilations that
|
||||
contain the covered work, unless you entered into that arrangement,
|
||||
or that patent license was granted, prior to 28 March 2007.
|
||||
|
||||
Nothing in this License shall be construed as excluding or limiting
|
||||
any implied license or other defenses to infringement that may
|
||||
otherwise be available to you under applicable patent law.
|
||||
|
||||
12. No Surrender of Others' Freedom.
|
||||
|
||||
If conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot convey a
|
||||
covered work so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you may
|
||||
not convey it at all. For example, if you agree to terms that obligate you
|
||||
to collect a royalty for further conveying from those to whom you convey
|
||||
the Program, the only way you could satisfy both those terms and this
|
||||
License would be to refrain entirely from conveying the Program.
|
||||
|
||||
13. Use with the GNU Affero General Public License.
|
||||
|
||||
Notwithstanding any other provision of this License, you have
|
||||
permission to link or combine any covered work with a work licensed
|
||||
under version 3 of the GNU Affero General Public License into a single
|
||||
combined work, and to convey the resulting work. The terms of this
|
||||
License will continue to apply to the part which is the covered work,
|
||||
but the special requirements of the GNU Affero General Public License,
|
||||
section 13, concerning interaction through a network will apply to the
|
||||
combination as such.
|
||||
|
||||
14. Revised Versions of this License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions of
|
||||
the GNU General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Program specifies that a certain numbered version of the GNU General
|
||||
Public License "or any later version" applies to it, you have the
|
||||
option of following the terms and conditions either of that numbered
|
||||
version or of any later version published by the Free Software
|
||||
Foundation. If the Program does not specify a version number of the
|
||||
GNU General Public License, you may choose any version ever published
|
||||
by the Free Software Foundation.
|
||||
|
||||
If the Program specifies that a proxy can decide which future
|
||||
versions of the GNU General Public License can be used, that proxy's
|
||||
public statement of acceptance of a version permanently authorizes you
|
||||
to choose that version for the Program.
|
||||
|
||||
Later license versions may give you additional or different
|
||||
permissions. However, no additional obligations are imposed on any
|
||||
author or copyright holder as a result of your choosing to follow a
|
||||
later version.
|
||||
|
||||
15. Disclaimer of Warranty.
|
||||
|
||||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. Limitation of Liability.
|
||||
|
||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||
SUCH DAMAGES.
|
||||
|
||||
17. Interpretation of Sections 15 and 16.
|
||||
|
||||
If the disclaimer of warranty and limitation of liability provided
|
||||
above cannot be given local legal effect according to their terms,
|
||||
reviewing courts shall apply local law that most closely approximates
|
||||
an absolute waiver of all civil liability in connection with the
|
||||
Program, unless a warranty or assumption of liability accompanies a
|
||||
copy of the Program in return for a fee.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
state the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program does terminal interaction, make it output a short
|
||||
notice like this when it starts in an interactive mode:
|
||||
|
||||
<program> Copyright (C) <year> <name of author>
|
||||
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, your program's commands
|
||||
might be different; for a GUI interface, you would use an "about box".
|
||||
|
||||
You should also get your employer (if you work as a programmer) or school,
|
||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||
For more information on this, and how to apply and follow the GNU GPL, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
The GNU General Public License does not permit incorporating your program
|
||||
into proprietary programs. If your program is a subroutine library, you
|
||||
may consider it more useful to permit linking proprietary applications with
|
||||
the library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License. But first, please read
|
||||
<http://www.gnu.org/philosophy/why-not-lgpl.html>.
|
||||
@@ -0,0 +1,114 @@
|
||||
#!/usr/bin/env python3
|
||||
"""flicker_scan.py - find A/B/A single-frame flicker in GE_PCDUMP captures.
|
||||
|
||||
flicker_scan.py DIR [--scale 4] [--thresh T] [--csv out.csv]
|
||||
|
||||
Loads DIR/frame_NNNNNN.ppm in frame order (gaps skipped: only frames that
|
||||
exist are compared), block-average downscales by --scale, and flags frame n
|
||||
when diff(n, n-1) > T and diff(n, n-2) < T/4 -- i.e. n differs from its
|
||||
predecessor but the frame before that looks like n again (or n-1 was the
|
||||
odd one out). diff = mean abs channel difference (0-255). Default T = 12.
|
||||
Pure stdlib; uses numpy if present. Always exits 0.
|
||||
"""
|
||||
import argparse, os, re, sys
|
||||
|
||||
try:
|
||||
import numpy as np
|
||||
except ImportError:
|
||||
np = None
|
||||
|
||||
|
||||
def read_ppm(path):
|
||||
with open(path, 'rb') as f:
|
||||
data = f.read()
|
||||
toks, pos = [], 0
|
||||
while len(toks) < 4:
|
||||
while data[pos:pos + 1].isspace():
|
||||
pos += 1
|
||||
if data[pos:pos + 1] == b'#':
|
||||
while data[pos:pos + 1] != b'\n':
|
||||
pos += 1
|
||||
continue
|
||||
s = pos
|
||||
while not data[pos:pos + 1].isspace():
|
||||
pos += 1
|
||||
toks.append(data[s:pos])
|
||||
pos += 1
|
||||
if toks[0] != b'P6' or int(toks[3]) != 255:
|
||||
raise ValueError('not an 8-bit P6 ppm')
|
||||
return int(toks[1]), int(toks[2]), data[pos:]
|
||||
|
||||
|
||||
def downscale(w, h, px, k):
|
||||
"""Block-average to (w//k, h//k); returns a flat sequence of ints."""
|
||||
ow, oh = w // k, h // k
|
||||
if np is not None:
|
||||
a = np.frombuffer(px, dtype=np.uint8, count=w * h * 3).reshape(h, w, 3)
|
||||
a = a[:oh * k, :ow * k].astype(np.float32)
|
||||
return a.reshape(oh, k, ow, k, 3).mean(axis=(1, 3)).ravel()
|
||||
out = []
|
||||
n = k * k
|
||||
for by in range(oh):
|
||||
rows = [by * k + dy for dy in range(k)]
|
||||
for bx in range(ow):
|
||||
for c in range(3):
|
||||
t = 0
|
||||
for r in rows:
|
||||
base = (r * w + bx * k) * 3 + c
|
||||
for dx in range(k):
|
||||
t += px[base + dx * 3]
|
||||
out.append(t / n)
|
||||
return out
|
||||
|
||||
|
||||
def diff(a, b):
|
||||
if np is not None:
|
||||
return float(np.abs(a - b).mean())
|
||||
return sum(abs(x - y) for x, y in zip(a, b)) / max(len(a), 1)
|
||||
|
||||
|
||||
def main():
|
||||
ap = argparse.ArgumentParser(description=__doc__.split('\n')[0])
|
||||
ap.add_argument('dir')
|
||||
ap.add_argument('--scale', type=int, default=4)
|
||||
ap.add_argument('--thresh', type=float, default=12.0)
|
||||
ap.add_argument('--csv')
|
||||
a = ap.parse_args()
|
||||
files = []
|
||||
for n in os.listdir(a.dir):
|
||||
m = re.fullmatch(r'frame_(\d+)\.ppm', n)
|
||||
if m:
|
||||
files.append((int(m.group(1)), n))
|
||||
files.sort()
|
||||
t = a.thresh
|
||||
rows, flagged = [], []
|
||||
prev = [] # last two (frame, thumb)
|
||||
for fn, name in files:
|
||||
try:
|
||||
w, h, px = read_ppm(os.path.join(a.dir, name))
|
||||
th = downscale(w, h, px, max(a.scale, 1))
|
||||
except Exception as e:
|
||||
print('skip %s: %s' % (name, e), file=sys.stderr)
|
||||
continue
|
||||
d1 = diff(th, prev[-1][1]) if len(prev) >= 1 else None
|
||||
d2 = diff(th, prev[-2][1]) if len(prev) >= 2 else None
|
||||
flag = d1 is not None and d2 is not None and d1 > t and d2 < t / 4
|
||||
rows.append((fn, d1, d2, flag))
|
||||
if flag:
|
||||
flagged.append(fn)
|
||||
print('FLICKER frame %d (odd frame: %d): diff(n-1)=%.2f diff(n-2)=%.2f'
|
||||
% (fn, prev[-1][0], d1, d2))
|
||||
prev = (prev + [(fn, th)])[-2:]
|
||||
if a.csv:
|
||||
with open(a.csv, 'w') as f:
|
||||
f.write('frame,diff_prev,diff_prev2,flag\n')
|
||||
for fn, d1, d2, fl in rows:
|
||||
f.write('%d,%s,%s,%d\n' % (fn, '' if d1 is None else '%.3f' % d1,
|
||||
'' if d2 is None else '%.3f' % d2, int(fl)))
|
||||
print('scanned %d frames, %d flagged (thresh %.1f, scale %d)' %
|
||||
(len(rows), len(flagged), t, a.scale))
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(main())
|
||||
@@ -48,7 +48,8 @@ USAGE
|
||||
Frames are matched by filename stem (frame_000200). .ppm (P6) and .png
|
||||
are both accepted on either side. Exit status: 0 = all frames within
|
||||
threshold, 1 = at least one frame/region over threshold, 2 = usage / IO
|
||||
error (e.g. a golden frame with no candidate).
|
||||
error (e.g. a golden frame with no candidate), 3 = a candidate frame's size
|
||||
differs from its golden (never a pass: the frames were not compared).
|
||||
|
||||
Regenerate the golden set (after a deliberate visual change):
|
||||
GE_PCDUMP="640-1120:240" ./build-pc/ge007.x86_64.exe -level_09
|
||||
@@ -419,7 +420,7 @@ def main(argv):
|
||||
print("FAIL %s: size %dx%d vs golden %dx%d"
|
||||
% (stem, cw, ch, gw, gh))
|
||||
results.append({"frame": stem, "status": "size"})
|
||||
worst = 2
|
||||
worst = max(worst, 3)
|
||||
continue
|
||||
mask = build_mask_bitmap(gw, gh, masks)
|
||||
|
||||
@@ -470,6 +471,7 @@ def main(argv):
|
||||
print("\n%d/%d frames within threshold%s"
|
||||
% (len(results) - n_fail, len(results),
|
||||
"" if worst == 0 else " -- REGRESSION" if worst == 1
|
||||
else " -- SIZE MISMATCH" if worst == 3
|
||||
else " -- ERROR"))
|
||||
return worst
|
||||
|
||||
|
||||
+25
-2
@@ -13,6 +13,10 @@
|
||||
# --record FILE human plays (no script); pad input saved to FILE
|
||||
# --replay FILE replay a --record file instead of a script
|
||||
# (both pin GE_RSEED so guards make the same choices)
|
||||
# (also writes FILE.meta: level, secs, seed, binary_md5,
|
||||
# recorded, script=none -- consumed by demobatch.sh)
|
||||
# --dump SPEC pass GE_PCDUMP=SPEC ("lo-hi[:step]"); the ./ppm frames
|
||||
# are moved to <out>/ppm/ after the run
|
||||
# --gamescope wrap in nested `gamescope -r 90 -w 1280 -h 800 -f`
|
||||
# (Deck desktop mode: approximates Game Mode pacing)
|
||||
#
|
||||
@@ -22,7 +26,7 @@
|
||||
# play), then 1.2 s Z holds every 4 s, so guards hear shots and start their
|
||||
# patterns. Z is fire in every control style and harmless in cutscenes.
|
||||
set -u
|
||||
SECS=45; SCRIPT=fight; REC=; REP=; EXE=; DIR=$PWD; OUT=; GSCOPE=0
|
||||
SECS=45; SCRIPT=fight; REC=; REP=; EXE=; DIR=$PWD; OUT=; GSCOPE=0; DUMP=
|
||||
while [ $# -gt 0 ]; do
|
||||
case $1 in
|
||||
--secs) SECS=$2; shift 2;;
|
||||
@@ -32,8 +36,9 @@ while [ $# -gt 0 ]; do
|
||||
--out) OUT=$2; shift 2;;
|
||||
--record) REC=$2; SCRIPT=none; shift 2;;
|
||||
--replay) REP=$2; SCRIPT=none; shift 2;;
|
||||
--dump) DUMP=$2; shift 2;;
|
||||
--gamescope) GSCOPE=1; shift;;
|
||||
-h|--help) sed -n 2,22p "$0"; exit 0;;
|
||||
-h|--help) sed -n 2,26p "$0"; exit 0;;
|
||||
*) break;;
|
||||
esac
|
||||
done
|
||||
@@ -93,6 +98,20 @@ IENV=(GE_INPUTSCRIPT_MS=1 "GE_INPUTSCRIPT=$INPUT")
|
||||
[ "$SCRIPT" = none ] && IENV=("GE_RSEED=${GE_RSEED:-0123456789abcdef}")
|
||||
[ -n "$REC" ] && IENV+=("GE_INPUTRECORD=$REC")
|
||||
[ -n "$REP" ] && IENV+=("GE_INPUTREPLAY=$REP")
|
||||
if [ -n "$REC" ]; then
|
||||
{
|
||||
echo "level=$LVL"
|
||||
echo "secs=$SECS"
|
||||
echo "seed=${GE_RSEED:-0123456789abcdef}"
|
||||
echo "binary_md5=$(md5sum "$EXE" | cut -c1-8)"
|
||||
echo "recorded=$(date +%Y-%m-%dT%H:%M:%S)"
|
||||
echo "script=none"
|
||||
} > "$REC.meta"
|
||||
fi
|
||||
if [ -n "$DUMP" ]; then
|
||||
IENV+=("GE_PCDUMP=$DUMP")
|
||||
mkdir -p ppm; rm -f ppm/frame_*.ppm
|
||||
fi
|
||||
echo "input env: ${IENV[*]}" >> "$OUT/run.txt"
|
||||
env GE_QUITFRAME=$((SECS * 60)) "${IENV[@]}" "$@" \
|
||||
timeout $((SECS + 90)) "${CMD[@]}" > "$OUT/stdout.txt" 2>&1
|
||||
@@ -105,6 +124,10 @@ LOG=$(find . -maxdepth 3 -name ge007.log -newer "$STAMP" 2>/dev/null | head -1)
|
||||
[ -n "$LOG" ] && cp "$LOG" "$OUT/ge007.log"
|
||||
for f in ge007.crash.log; do [ -f "$f" ] && [ "$f" -nt "$STAMP" ] && cp "$f" "$OUT/"; done
|
||||
rm -f "$STAMP"
|
||||
if [ -n "$DUMP" ]; then
|
||||
mkdir -p "$OUT/ppm"
|
||||
for f in ppm/frame_*.ppm; do [ -e "$f" ] && mv "$f" "$OUT/ppm/"; done
|
||||
fi
|
||||
|
||||
L="$OUT/ge007.log"; [ -f "$L" ] || L="$OUT/stdout.txt"
|
||||
{
|
||||
|
||||
@@ -0,0 +1,166 @@
|
||||
#!/usr/bin/env bash
|
||||
# release_gate.sh -- run the automatable release checks per platform and print
|
||||
# one PASS/FAIL/SKIPPED table. Reuses verify.sh (pixel gate) and levelbench.sh
|
||||
# (scripted run + clean-quit + crash-log detection); invents no new harness.
|
||||
#
|
||||
# release_gate.sh [--dry-run] [--secs N] [--out DIR] <win|deck HOST|mac HOST|all>
|
||||
#
|
||||
# win verify.sh sweep --platform win (pixel gate vs goldens/<lvl>/win)
|
||||
# + scripted-fire smoke on -level_09 (GE_INPUTSCRIPT MLB1/MLB0,
|
||||
# the D595/D597 regression) + exit-path check (rc 0 through
|
||||
# GE_QUITFRAME, "quit requested" log line, no ge007.crash.log)
|
||||
# deck HOST queue 21 levelbench jobs through deckrunner (Steam shortcut must
|
||||
# be running on the Deck), crash-free + clean-quit + fps summary.
|
||||
# Pixel compare is SKIPPED (deck goldens 640x480 vs 1280x800 dumps).
|
||||
# mac HOST over ssh: optional build, 21-level crash-free sweep + fire smoke.
|
||||
# No goldens exist -> crash-free only.
|
||||
# all every platform that is configured (HOSTs from env), else SKIPPED.
|
||||
#
|
||||
# Environment (no defaults are machine-specific):
|
||||
# GE_WIN_DIR game dir for the Windows runs (default: <repo>/build-pc, has data/)
|
||||
# GE_DECK_HOST / GE_MAC_HOST ssh targets for `all` (or pass as argument)
|
||||
# GE_MAC_DIR remote game dir on the Mac (binary + data/); required for mac
|
||||
# GE_MAC_EXE remote binary name inside GE_MAC_DIR (default: ge007)
|
||||
# GE_MAC_BUILD_CMD optional command run in GE_MAC_DIR before the sweep
|
||||
# GE_GATE_SSH_OPTS extra ssh/scp options
|
||||
# Every run ends through GE_QUITFRAME (levelbench sets it); nothing is hard-killed.
|
||||
# Remote temp/result files are removed after results are pulled.
|
||||
set -u
|
||||
ROOT=$(cd "$(dirname "$0")/.." && pwd)
|
||||
DRY=0; SECS=30; OUT=; MODE=; HOST=
|
||||
while [ $# -gt 0 ]; do
|
||||
case $1 in
|
||||
--dry-run) DRY=1; shift;;
|
||||
--secs) SECS=$2; shift 2;;
|
||||
--out) OUT=$2; shift 2;;
|
||||
-h|--help) sed -n 2,26p "$0"; exit 0;;
|
||||
win|all) MODE=$1; shift;;
|
||||
deck|mac) MODE=$1; HOST=${2:-}; shift; [ $# -gt 0 ] && shift;;
|
||||
*) echo "unknown argument: $1" >&2; exit 2;;
|
||||
esac
|
||||
done
|
||||
[ -n "$MODE" ] || { sed -n 2,6p "$0" >&2; exit 2; }
|
||||
[ -n "$OUT" ] || OUT="$ROOT/scratch/release-gate-$(date +%Y%m%d-%H%M%S)"
|
||||
LEVELS="33 34 35 36 09 20 26 43 27 22 24 29 30 25 37 23 39 41 28 32 54"
|
||||
FIRE='1500:START;6000:MLB1;14000:MLB0'
|
||||
SSHO=${GE_GATE_SSH_OPTS:-}
|
||||
ROWS=(); FAILS=0
|
||||
row() { [ $DRY = 1 ] && [ "$3" = PASS ] && set -- "$1" "$2" DRY "$4"; ROWS+=("$1|$2|$3|$4"); [ "$3" = FAIL ] && FAILS=$((FAILS + 1)); }
|
||||
run() { if [ $DRY = 1 ]; then echo "+ $*"; return 0; fi; "$@"; }
|
||||
if [ $DRY = 1 ]; then OUT=$(mktemp -d); trap 'rm -rf "$OUT"' EXIT; else mkdir -p "$OUT"; fi
|
||||
|
||||
# judge_summary FILE -> "PASS" or "FAIL:<why>" from a levelbench summary.txt
|
||||
judge_summary() {
|
||||
local f=$1
|
||||
[ -s "$f" ] || { echo "FAIL:no summary"; return; }
|
||||
grep -q '^exit=0$' "$f" || { echo "FAIL:rc $(grep -m1 '^exit=' "$f" | cut -d= -f2)"; return; }
|
||||
grep -q 'CRASH LOG PRESENT' "$f" && { echo "FAIL:crash log"; return; }
|
||||
grep -q 'no clean quit line' "$f" && { echo "FAIL:no clean quit"; return; }
|
||||
echo PASS
|
||||
}
|
||||
|
||||
gate_win() {
|
||||
local d=${GE_WIN_DIR:-$ROOT/build-pc} v rc
|
||||
echo "== win: pixel sweep"
|
||||
if [ $DRY = 1 ]; then
|
||||
echo "+ $ROOT/tools_pc/verify.sh sweep --platform win"; rc=0
|
||||
else
|
||||
"$ROOT/tools_pc/verify.sh" sweep --platform win | tee "$OUT/win-sweep.txt"; rc=${PIPESTATUS[0]}
|
||||
fi
|
||||
if [ $rc = 0 ]; then row win "verify.sh sweep (21 lvls vs win goldens)" PASS ""
|
||||
else row win "verify.sh sweep (21 lvls vs win goldens)" FAIL "rc $rc, see win-sweep.txt"; fi
|
||||
echo "== win: scripted-fire smoke + exit path (level 09)"
|
||||
run "$ROOT/tools_pc/levelbench.sh" --dir "$d" --secs "$SECS" --script "raw:$FIRE" \
|
||||
--out "$OUT/win-fire" 09
|
||||
if [ $DRY = 1 ]; then v=PASS; else v=$(judge_summary "$OUT/win-fire/summary.txt"); fi
|
||||
if [ "$v" = PASS ]; then
|
||||
row win "fire smoke -level_09 (MLB1/MLB0)" PASS ""
|
||||
row win "exit path (rc 0 via GE_QUITFRAME, no crash log)" PASS ""
|
||||
else
|
||||
row win "fire smoke / exit path -level_09" FAIL "${v#FAIL:}"
|
||||
fi
|
||||
}
|
||||
|
||||
gate_mac() {
|
||||
local h=${HOST:-${GE_MAC_HOST:-}} dir=${GE_MAC_DIR:-} exe=${GE_MAC_EXE:-ge007} l v bad=0 badl=
|
||||
if [ -z "$h" ] || [ -z "$dir" ]; then
|
||||
row mac "all checks" SKIPPED "no host (arg/GE_MAC_HOST) or GE_MAC_DIR"; return
|
||||
fi
|
||||
# shellcheck disable=SC2086 # SSHO is an option list
|
||||
run scp $SSHO "$ROOT/tools_pc/levelbench.sh" "$h:$dir/levelbench.sh"
|
||||
# shellcheck disable=SC2086
|
||||
[ -n "${GE_MAC_BUILD_CMD:-}" ] && run ssh $SSHO "$h" "cd '$dir' && $GE_MAC_BUILD_CMD"
|
||||
for l in $LEVELS; do
|
||||
# shellcheck disable=SC2086
|
||||
run ssh $SSHO "$h" "cd '$dir' && bash levelbench.sh --dir . --exe './$exe' --secs $SECS --script 'raw:$FIRE' --out gate-$l $l >/dev/null 2>&1; cat gate-$l/summary.txt; rm -rf gate-$l" \
|
||||
> "$OUT/mac-$l.txt" 2>&1
|
||||
[ $DRY = 1 ] && continue
|
||||
v=$(judge_summary "$OUT/mac-$l.txt")
|
||||
[ "$v" = PASS ] || { bad=$((bad + 1)); badl="$badl $l(${v#FAIL:})"; }
|
||||
done
|
||||
# shellcheck disable=SC2086
|
||||
run ssh $SSHO "$h" "cd '$dir' && rm -rf levelbench.sh gate-* ppm ge007.crash.log"
|
||||
if [ $bad = 0 ]; then row mac "21-level crash-free + fire smoke" PASS ""
|
||||
else row mac "21-level crash-free + fire smoke" FAIL "$bad bad:$badl"; fi
|
||||
row mac "pixel compare" SKIPPED "no mac goldens exist"
|
||||
}
|
||||
|
||||
gate_deck() {
|
||||
local h=${HOST:-${GE_DECK_HOST:-}} l n=0 v bad=0 badl= name t pre last max
|
||||
if [ -z "$h" ]; then row deck "all checks" SKIPPED "no host (arg/GE_DECK_HOST)"; return; fi
|
||||
echo "== deck: checking for duplicate deckrunner instances"
|
||||
if [ $DRY = 1 ]; then echo "+ ssh $h pgrep -af deckrunner.sh"; else
|
||||
# shellcheck disable=SC2086
|
||||
ssh $SSHO "$h" 'pgrep -af deckrunner.sh' || true
|
||||
echo "(if more than one deckrunner.sh is listed, stop the extras before continuing)"
|
||||
fi
|
||||
# shellcheck disable=SC2086
|
||||
run scp $SSHO "$ROOT/tools_pc/levelbench.sh" "$h:levelbench.sh"
|
||||
pre="gate$(date +%H%M%S)"
|
||||
for l in $LEVELS; do
|
||||
n=$((n + 1)); name=$(printf '%s-%03d-%s' "$pre" "$n" "$l")
|
||||
# shellcheck disable=SC2086
|
||||
run ssh $SSHO "$h" "mkdir -p ~/lb/queue && echo '--secs $SECS --script fight $l' > ~/lb/queue/$name.job"
|
||||
done
|
||||
# the runner is serial; wait for the last job's .finished marker
|
||||
last=$(printf '%s-%03d-%s' "$pre" 21 54); t=0; max=$((21 * (SECS + 60)))
|
||||
if [ $DRY = 1 ]; then echo "+ poll: ssh $h test -e ~/lb/$last.finished (timeout ${max}s)"; else
|
||||
# shellcheck disable=SC2086
|
||||
until ssh $SSHO "$h" "test -e ~/lb/$last.finished" 2>/dev/null; do
|
||||
sleep 10; t=$((t + 10))
|
||||
[ $t -ge $max ] && { echo "deck: timed out waiting (is the deckrunner shortcut running?)" >&2; break; }
|
||||
done
|
||||
fi
|
||||
n=0
|
||||
for l in $LEVELS; do
|
||||
n=$((n + 1)); name=$(printf '%s-%03d-%s' "$pre" "$n" "$l")
|
||||
# shellcheck disable=SC2086
|
||||
run ssh $SSHO "$h" "cat ~/lb/$name/summary.txt" > "$OUT/deck-$l.txt" 2>&1
|
||||
[ $DRY = 1 ] && continue
|
||||
v=$(judge_summary "$OUT/deck-$l.txt")
|
||||
[ "$v" = PASS ] || { bad=$((bad + 1)); badl="$badl $l(${v#FAIL:})"; }
|
||||
done
|
||||
# shellcheck disable=SC2086
|
||||
run ssh $SSHO "$h" "rm -rf ~/lb/$pre-* ~/lb/done/$pre-* ~/levelbench.sh"
|
||||
if [ $bad = 0 ]; then row deck "21-level crash-free + clean quit" PASS ""
|
||||
else row deck "21-level crash-free + clean quit" FAIL "$bad bad:$badl"; fi
|
||||
row deck "levelbench fps summary" INFO "D578 cadence / D583 SLOW lines in deck-<lvl>.txt"
|
||||
row deck "pixel compare" SKIPPED "deck goldens 640x480 vs 1280x800 dumps (known v0.7.0 item)"
|
||||
}
|
||||
|
||||
case $MODE in
|
||||
win) gate_win;;
|
||||
deck) gate_deck;;
|
||||
mac) gate_mac;;
|
||||
all) gate_win; gate_deck; gate_mac;;
|
||||
esac
|
||||
|
||||
echo
|
||||
echo "=== RELEASE GATE ($([ $DRY = 1 ] && echo dry-run || echo "$OUT")) ==="
|
||||
printf '%-5s %-52s %-8s %s\n' PLAT CHECK RESULT NOTE
|
||||
for r in "${ROWS[@]}"; do
|
||||
IFS='|' read -r a b c d <<<"$r"
|
||||
printf '%-5s %-52s %-8s %s\n' "$a" "$b" "$c" "$d"
|
||||
done
|
||||
if [ $FAILS = 0 ]; then echo "OVERALL: no failures"; else echo "OVERALL: $FAILS FAIL"; fi
|
||||
[ $FAILS = 0 ]
|
||||
+5
-1
@@ -412,9 +412,13 @@ except Exception:
|
||||
# golden's frame stems don't line up with this run's --dump window
|
||||
# (goldens are captured at GOLDEN_DUMP; a
|
||||
# sweep using a different stride has nothing to compare against
|
||||
# rather than a real regression). Only exit 1 is a verdict fail.
|
||||
# rather than a real regression). exit 3 = a frame was captured at a
|
||||
# different size than its golden (e.g. the Deck dumping 1280x800
|
||||
# against 640x480 goldens): nothing was compared, so it is a fail,
|
||||
# never a pass. Exits 1 and 3 are verdict fails.
|
||||
case "$fexit" in
|
||||
1) status=REGRESSION; note="framediff vs $gdir" ;;
|
||||
3) status=REGRESSION; note="frame size differs from golden $gdir (not compared)" ;;
|
||||
2) note="golden frame stems don't match --dump window ($dump) for $gdir" ;;
|
||||
esac
|
||||
local tolpct; tolpct=$(golden_tolpct_for "$name")
|
||||
|
||||
Reference in new issue
Block a user