Repository navigation
Conversation
The port's own legend entries and the "Mark room" prompt are extra UI: a first playthrough should show the disc's map screen, and both were added by this port, not by the disc. They are now behind "Extra map UI" (`map_extra_ui`), off by default, on the F1 Tracker page beside the other map and pickup settings, in the settings search index, the console (`mapui [0|1]`) and the docs. The setting gates exactly the two things it names: the legend entries (the text, the row-count fit and the injected legend textures, which are not even asked for while it is off) and the prompt with the pointer click that marks a room. Pickup dots and their kinds, the saved marks, the cyan drawing of them and the physical X button are untouched. The legend's cache key carries the setting, so switching it with the map screen open rebuilds the text at once: off, the pane is back to the disc's own dimensions, pivot and text (measured identical before and after a switch), and the hint pane is back at the frame's own transform with no prompt and no click target, so nothing invisible marks a room. The key is in the settings table with two sample values, which the settings round-trip self-test applies and reads back; a wiring test checks the default, the gates, the cache bit and that nothing else reads the setting.
Select the nearest larger aligned mip when a thumbnail or capped DDS tail would otherwise have an invalid compressed base level. Preserve the original mip payload and uncompressed selection behavior, with memory/file regression coverage.
F1 > Video > Compatibility gains a Renderer list on Windows, saved as graphics_backend (auto, d3d12, vulkan, d3d11; default auto) and read at window creation, so a change applies on the next start. Auto asks Aurora for nothing in particular, keeping its own order (Direct3D 12, then Vulkan, then Direct3D 11); an explicit choice is tried first and falls back to that order when the driver can't start it, without touching the setting. MP_GRAPHICS_BACKEND=auto|d3d12|vulkan|d3d11 overrides the setting for one run (per-run MP_* precedence, like MP_OPENGLES); an unknown value warns and starts on the saved selection. platform/include/port_graphics_backend.h owns the selection-to-Aurora mapping, that override and the status the page draws, so main.cpp and the page cannot describe a request one way and start it another: the page names the API actually in use, reports "X could not start; using Y" when what this run asked for (override included) fell back, and keeps the restart notice on the saved selection. While an override is set it names the API it asks for and says a change to the setting starts applying once the variable is unset. The startup log names the API asked for and the one in use. Android's OpenGL ES choice (opengles / MP_OPENGLES) is unchanged, and the setting is ignored off Windows. Refs #135: the OpenGL side of it is out of scope.
…d label Two corrections to the merged map layout, both from the phone screenshot: * The legend's fit cache and the hint pane's placement cache were keyed on the pane pointer alone. A released pane can come back at the same address when the map screen is closed and reopened (the pause screen's pools hand out what was just freed), which would keep the disc's geometry from being re-taken, skip the fit on the reopened frame and leave the mark prompt unplaced. Both caches are now cleared when the frame is dropped and when it is loaded, before anything reads them (PortForgetMapPanes). * The disc's legend pane slides out by a fixed 15 units and its own text ends 20.1 units left of the screen in that state. "Marked room" was wide enough (285 text px against the disc's 198) that its last glyphs (`oom`) stayed on screen at the left edge. The row is now "Marked": 233 px, so the whole block ends at -20.19 against the disc's -20.14, i.e. at least as far off screen as the disc's own legend at every key state. tests/port_map_extra_ui_wiring.py gains the lifecycle guard (both resets, one call on load before the legend pane is read, one on the drop) and the label width guard.
NCollisionRayCast::Capsule 0xbf6f10 as pure geometry, with the source's own two callees: CMath::SolveQuadratic 0x699b0 (disc = b*b + (a * -4) * c, the linear arm below 0x34000000, the two roots ordered by the sign of a) and NCollisionRayCast::InfiniteCylinder 0xbf73c0, plus the inline frecpe + two frecps reciprocal at 0xbf70c4-0xbf70e8 and the bare FPRecipEstimate that sequence starts from. The kernel is axis-selective (per-axis crosses and span tests), so it runs in Remastered (Y up) axes: the public entry converts its port-frame inputs once, before any arithmetic, and converts nothing back because its outputs are scalars. The output follows the source rather than a cleaned-up geometry: tOut is output-only (a miss leaves it untouched, the perpendicular branch reports max(tNear, 0) while ignoring the ray length, and the general branch's tiny-clipped arm returns inside without storing), the capsule epsilon is 1e-5 rather than the quadratic's FLT_EPSILON, the clipped cylinder parameter is stored into the running best before the length test so that a cap hit must beat it, the cap-inside arm is gated on a positive best (so length == 0 reports nothing from inside a cap), and the span, tangency and cap tests are all strict. CMath::SqrtF 0x65f70 moves from the file-local copy in platform/port_collision_failsafe.cpp to PortRemasteredGroundContacts::Source::SqrtF so the discriminant root and the sphere tests share one instruction-identical implementation (`!(v > 0)` guard, fmax floor 0x0554ad2e, two frsqrts refinements); the failsafe's outputs are unchanged. Geometry only: no caller, no contact list, no traversal, no materials and no gameplay wiring - the library is not linked into the game binary - so this changes no gameplay and makes no parity claim. Its rows are pinned bit-for-bit against the game's own AArch64 instruction bodies run under qemu-aarch64: a 421-row selection of them plus an 87-row bare-`frecpe` table, both regenerated deterministically by the private lane's implementation/regenerate_fixture.sh. NaN and overflow inputs are outside the header's finite contract. CMakeLists.txt docs/REMASTERED_MOVEMENT.md platform/include/port_remastered_capsule_ray.h platform/include/port_remastered_ground_contacts.h platform/port_collision_failsafe.cpp platform/port_remastered_capsule_ray.cpp platform/port_remastered_ground_contacts.cpp tests/fixtures/port_remastered_capsule_ray_isa.h tests/port_remastered_capsule_ray.cpp
…nventory Issue #136 (a Missile Launcher that left missiles at 0) could not be traced from the log: it said a pickup was found and nothing more. The log now follows each item and check: - The session queues what it did (Session::TakeLogLine, like the chat lines) and the client writes it: the handshake with the slot, the checks the server has and the seed options that change grants; each ReceivedItems with its start and the expected index; each item with what it grants, or that a replay only counts it, or that it is unknown; gaps and the resend they ask for; rewinds. - Checks: queued to send, queued while offline, or recorded already and so not sent again (no item comes back for those). - Each grant the game takes, with the inventory's amount/capacity before and after. - Each reconcile of a loaded game (save, resume, save state): what it records, and whether it is in step, rewound, or adopted; checks from another seed being dropped. - Inbound DeathLink. - Randomizer seeds: each pickup the game grants itself, with the inventory after, in the main log (randomizer_checks.log is unchanged). docs/ARCHIPELAGO.md lists the lines; port_apclient_tests checks the session's for the #136 sequence (connect, rewind, resend, a gap, the Missile Launcher, an unknown item). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0115KR2ieF3JKrNSz4nRDC9H
0.28.0 was bumped but never tagged; the release now ships the 93 commits that landed after it, so its date and summary say so.
The checklist named Linux, Windows and Android only, so the 0.28.0 release started without the Flatpak and macOS builds every release ships.
) CPlayer applies the aim twice a tick: ProcessInput with the tick's travel, then Update to revalidate it with none left. The second AimState::Update (and PointerAim::MoveLagged) reset the turn the first had recorded to be drawn after the tick, so frames between ticks drew no stick turn at all and the view turned in tick-sized steps (60 Hz) while the rest of the scene was drawn at the display rate. PortMouse::TickLag keeps what the tick's first applied update recorded; the next tick starts fresh, so a released stick still draws no turn. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0115KR2ieF3JKrNSz4nRDC9H
… (issue #141) The retail grounded mover resets the flag at the top of every tick; the Remastered ground mover (052d325) never touched it, so a value set by a retail tick (the dynamic-contact fallback, a tick with the retained state invalid) stayed set. While orbiting, ComputeDash only writes the velocity when the flag is clear, so locking on kept the player moving at the velocity they had until the lock was released. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AFkbc95d4u2mzXdwQLeqBM
UpdateMouseAim runs twice a tick (CPlayer::ProcessInput, then Update), and both applied the pointer dead box's edge turn for a whole tick period, so the view turned at twice pointer_aim_turn. The frames between ticks preview one tick's edge turn, so the drawn view fell behind and jumped by the other half at every tick. Only the tick's first applied update turns now (TickLag is renamed TickAim and answers First()). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0115KR2ieF3JKrNSz4nRDC9H
With point and click menus on, a one-finger drag on the map was turned into a pan, so touch had no way to pitch the map and two fingers did the same thing as one. A one-finger drag now rotates (yaw and pitch) as a left mouse drag does, and two fingers pan; twist and pinch are unchanged. The hold that stops the map drifting back to its area now follows two-finger pans instead of any touch drag. Fixes #142 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WUhWhzqwXbK889n1gJtk8B
Adds "Swap map finger gestures" (map_swap_fingers, MP_MAP_SWAP_FINGERS, off by default). On, one finger pans the map and a two-finger drag rotates it; twist and pinch are unchanged and the mouse is never affected. Players used to one-finger pan, from the old behaviour or the Android overlay, had no way to keep it. The setting is greyed out when Point and click menus is off.
ApplyStorageClamp writes MP_STORAGE_CLAMP into the process environment when the setting is On or Off, and the settings dump then listed it under `port: env:` exactly like a variable the user set (issue #143's log read as a user override). The line now ends in "(from storage_clamp)" when the port set it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HeRsRdi17Y8F6kQG3V8LHe
…ings Issue #143's self-test failed every case with black pixels, which can't tell "draws don't land" from "the copy or readback is broken": every case goes draw -> EFB copy -> readback, the background is black and the logged alpha was a constant. Self-test: - `readback`: a texture written from the CPU, copied straight back (no GX). - `efb-clear-copy`: the EFB cleared by a copy and copied again (no draw). - The readback buffer starts filled with A5 bytes, so a block no copy reached says so instead of reading as black. - Real alpha in FAIL lines, and a `diagnosis:` line naming the stage the failures point at (readback, EFB copy or draws). - The caller re-sends the game's copy clear colour afterwards. Driver workarounds (Video > Compatibility, collapsed, `driver_workarounds`): checkboxes for d3d11_use_discard_view off, d3d11_disable_cpu_buffers, d3d11_use_unmonitored_fence, and Dawn validation back on. They reach Aurora through new AuroraConfig fields and apply at device creation; MP_DAWN_ENABLE / MP_DAWN_DISABLE still apply after them, and either list can now turn off one of Aurora's default toggles. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HeRsRdi17Y8F6kQG3V8LHe
- A start with workarounds ticked leaves driver_workarounds_starting in the user folder until frame 60. A start that finds it unticks them all, logs it and says so in the section; the marker stays until a start reaches the game, so repeated crashes keep them off. - MP_DRIVER_WORKAROUNDS=0 (or a list of names) replaces the boxes for one run and leaves the setting and the marker alone. - Driver error checks last one start: unticked as they are applied, with a log line and a note in the section while they're on. - The application's d3d11_* toggles only reach a D3D11 device; elsewhere the log lists them as ignored. - GPU self-test: a clean pass logs a diagnosis saying it doesn't cover presenting the frame. Docs note the count is now x/12. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HeRsRdi17Y8F6kQG3V8LHe
The keyboard and mouse drive player 1, so a controller left plugged in shook for them (issue #138). CRumbleGenerator now stops the motors while the keyboard or mouse is the last input used (PortPrompts::KeyboardActive), and the voices keep running so going back to the pad resumes the effect. The last-input watch is now installed even without prompt icons. Controls > Options gains a Rumble checkbox bound to the game's own rumble option, so it matches the pause menu and is saved with the game options. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MiQ1bpvnDUcDjm3sXqignS
Hold rumble off on keyboard/mouse; add a Rumble toggle to F1 Controls
The game's Visor Opacity is one value for every visor, and it never reaches the energy readout. Four new port settings (Video > Display in F1, console `hudopacity`, persisted as hud_opacity_xray/thermal/scan/energy) take a percent or "visor": - X-Ray, Thermal and Scan: that visor's HUD uses its own opacity, and so do the visor and beam menus and the minimap while that visor is on. Default "visor", as the game does. - Energy: the bar, tanks, digits and low-energy warning. Default 100, as the game draws them; "visor" makes them follow the HUD they are on. Original experience ignores all four. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FRESLeS2QtkW2zgnvGfsZy
Match the independently audited inverse-exposure policy for the exact held-charge child and material. Preserve other effects and LDR behavior; effects stage 28 requires a re-import.
Leave the non-PC visor and beam menu code as decompiled, reject a non-numeric hudopacity value instead of reading it as 0, and say in the tooltip and docs that the ENERGY label and its frame follow the visor's HUD. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FRESLeS2QtkW2zgnvGfsZy
configure.py, its dtk-template helpers in tools/, config/, orig/, sha1/, buildstrings/, scripts/ and docs/decomp/ only serve the original decompilation's matching ninja build. CMake, CI, packaging and the port tools never read them, so they cluttered the top level next to the port's own files. They now live together under decomp/; src/ and include/ stay where they are because the port compiles them. The build still runs from the repository root as `python3 decomp/configure.py`: configure.py points tools_dir and the config paths at decomp/, the config.yml files name decomp/orig and decomp/config, decompctx.py and rename_symbol.py find the root from their new depth, and .gitignore/.gitattributes and the docs and comments that named the old paths follow. The generated build.ninja differs from before only in those paths. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0175uBWzi5S5io3pmcnbvymd
X-Ray, Thermal and Scan HUDs can each have their own opacity instead of the game's single Visor Opacity, and the energy readout gets its own (default 100, as the game draws it). Original experience ignores all four.
tools/README.md lists every script with one line on what it does and whether the build, CTest, CI or packaging runs it, and documents the manual checks CTest cannot run (they need retail data, retrotool or a running game) and how to build their helper tools. anuv_slots.py answered a one-off question about the ANUV converter's texcoord slots; it needs an external anuv.py and a material index that nothing in the repository produces, so it goes. card_inspect.py, disc_diff.py and upstream_screen.py stay as hand-run debugging and merge aids. test_game_options_persistence.py is a rig regression check, not a tool, so it moves to tests/game_options_persistence_rig.py (its repo-root lookup is unchanged at that depth). It needs a disc and Xvfb, so it is not registered in CTest, nor are the three port_remastered_*_check.py scripts, which need extracted Remastered data and retrotool. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0175uBWzi5S5io3pmcnbvymd
added 29 commits
October 19, 2026 13:22
Remastered rotates vectors with ROTV outside a component too (POFS, ADV1) and normalises the particle velocity with PNCV. Both were dropped. ROTV by REUL (axis order 0), EXTR or CMRT becomes the port-only PRTV: the vector, the rotation kind, REUL's three angles. It evaluates the rotation first, then the vector, as CVERotateVector 0x2a0328 does, in Remastered's space (EXTR and CMRT are conjugated into it). PNCV becomes the port-only PNCV, which runs the particle velocity through one FRSQRTE step refined by FRSQRTS (CVENormalizedCompensatedVelocity 0x2a2800) and gives (0, 1, 0) below 2^-46. Retail effects are unchanged; kEffects is not bumped.
… vars Converted Remastered effects use a ninth real particle variable (ADV9, read by PAP9) and four per-particle ints (IAV0..3, read by PAP0..3), neither of which retail has. The converter now writes them, with each one's constant-after-start flag, as a port-only PPVR table in SortVars order (dependency, then stream order). Spawn evaluates every entry; later frames skip the constant ones (a CNST, or an IRND of constants), so an IAV drawn from IRND is drawn once per particle on the one RNG, as Remastered does. The ints live as raw bits after ADV1..9 in CAdvancedValues. Generators with only ADV1..8 keep the old PADO output and evaluation unchanged, and ImportStage::kEffects is not bumped.
ELPS GetValue 0x2ca73c guards both normalizes with CVector3f::IsNormalizable 0x6b030: all lanes finite and (x*x+y*y)+z*z >= 1e-28 (0x10fd87b5), not the 1.42e-14 skMinNormalizableMagSqd the port assumed. Normalize/AsNormalized are the same FRSQRTE-refined math NormalizeInPlace already has. Boundary test added.
# Conflicts: # CMakeLists.txt
Remastered's KEYF (CREKeyframeInput, ctor 0x296490, GetValue 0x29661c) reads its keys at an input element's value, optionally wrapping from a loop start once past the last key, and blends linearly between neighbours. The converter now turns a version-2 KEYF into the port-only PKYF (loop byte, N, A, B, R, keys, input); another version or a key count that differs from N stays dropped with a reason. The element keeps the original's operation order (D = 1 / R once, separate products and sums, saturating truncation). Retail effects unchanged.
Remastered's SLTW (GetLifetimeTweenElement 0x26a640 on time slot 0) blends two reals by the system tween, clamp(system frame / system lifetime, 0, 1), which the context keeps at ctx+0x60 (UpdateLifetimeTweenValues 0x24ef38). The port had no system tween, so CParticleGlobals now carries one, set next to every SetEmitterTime from the generator's frame and PSLT (lifetime 0 reads as 1, the infinite 0x7FFFFF/0x1000000 gives 0). The converter turns SLTW(a, b) into the port-only PSLW; the element is a + tween * (b - a) in the original's order. Retail effects unchanged. The reciprocal is a plain 1 / lifetime, within an ulp of the original's estimate.
A CRLN whose octave slot is set is Remastered's CVECurlNoise_PerlinOctave3d (slice 0), which the importer dropped. It now becomes the port-only PCRO (P, frequency, octave count, gain, A) inside the same PCRM/BSIS wrapper as PCRN. CurlNoiseOctave3d sums gain^k * noise(p * freq * 2^k) in the original's order, with the central-difference step added before the frequency multiplies the coordinate. The VECF is released by the octave class, so it is not carried. Slices 1-3 stay dropped: their layout is undecoded and no effect needs them. ImportStage::kEffects is not bumped.
# Conflicts: # platform/port_remastered_effect.cpp
The system tween divided by the lifetime with a plain 1/x, which differs from Remastered in the last bit for many lifetimes (13, 15, 19, 21, ...). UpdateLifetimeTweenValues 0x24ef38 takes FRECPE of the lifetime, refines it with two FRECPS/fmul steps, multiplies by the frame, and clamps with two fcsel (0 - t >= 0 gives 0, t - 1 >= 0 gives 1). PortSetSystemTime now runs the same steps through PortParticleMath::InverseScale, which is that sequence.
kEffects 61: the emitter, vector, var and misc element drops convert now.
CCEConstant holds four real elements, so each channel needs its own CNST; the bare floats failed the retail reader and four effects kept the disc's PART. The child-not-written log now says why the reader refused it.
A composite whose children are all CNST was given bit 0, so RAND, SPAF, TPVF, MPXV, SINE, PULS, CHAN ... were latched at spawn. Only the decoded shapes (CNST, IRND, KPIN, plain ADD_/SUB_/DIVD) are claimed constant; anything else is re-evaluated every frame (Finish 0x24f9a4, IsConstantAfterStart 0x250b18).
The converter's validator now rejects a duplicate variable slot like the factory does. On a bad slot or a duplicate the factory still reads the item's element so the rest of the property stays in step, then drops the table, and it logs the fall back to the retail ADV path.
A written ADV/IAV reading a dropped variable would see zero where Remastered sees its value. The readers are dropped too, transitively, with a reason, and a retail ADV1..8 property is taken back out of the part.
…ler on a variable count Compile1d resolves a CRCV 0 count from MAXP only for the SEMR position child, and Init's shuffler needs a constant count.
…pace PNCV and PRTV give Remastered's numbers; a mod vector under MPCB runs in retail's space, so BSIS wraps them there. PRTV's vector is always evaluated in Remastered's space. ROTV by INTR or RLCL is dropped with a reason.
A swoosh evaluates no per-particle variables, so a read of PAP0..3 or PAP9 and a property of ADV9 or IAVn have nothing behind them; drop them with a reason.
A PKYF with a zero or implausible key count evaluates to 0 instead of indexing key 0, and a PCRC whose children fail to read is refused like the cone elements.
A child generator that updates or renders inside a system left its own tween in xPortSystemTween for the parent's next PSLW; Remastered keeps it per system.
Each test now runs every converted part through SplitRetailEffect, so a layout the reader rejects fails the test. The factory's PKYF key-count cap now matches the validator (1..0x100000); every other port-only id was compared and reads the validator's layout.
…ime cap UpdateLifetimeTweenValues (0x24ef38) compares the lifetime with exactly 0x1000000 (the no-PSLT value and the PSLT clamp); the port zeroed anything from 0x7FFFFF up. The per-system context is built per CElementGen::Update (0x2460e0) and InitialUpdate (0x245e44) from this+0x2c/+0x180, which the port's SParticleSystem save/restore reproduces for children and render.
…R and TMTR COLR is evaluated per particle in UpdateExistingParticles 0x24a26c with that particle's own var block; TMTR is read from the same block by FillInParticleTextureTransformMaterialData 0x2acfc0/0x2ad058. The port's UV-site and COLR bindings already match; no behaviour change.
…pped effects convert The shipped files give ELPS's last argument as a GetBool (CNST and a byte), but the parser signature took a raw byte, so the parse fell back to a four-argument guess whose euler swallowed the speed and the converter dropped the emitter (E7638EFC, twice). Signature back to VVVRe, the converter unwraps the CNST, and the test writes the flag the way the files do.
ELPS now reads the surface-only flag the way the files give it, so the E7638EFC emitters convert; the stage must re-run to pick that up.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No description provided.