82e733c1a6ecc7586cc211598a8bdab5616707c0
100
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
82e733c1a6 |
Replicants reckon from when an update was sent
Simulation::ReadUpdateRecord threw away the sender's timestamp and
stamped lastUpdate with its own arrival time. The line carried the
original authors' own note: "HACK - should be based upon
message->timeStamp".
The dead reckoner extrapolates a replicant over
(lastPerformance - lastUpdate), so starting that clock at ARRIVAL rather
than at SEND leaves every remote vehicle exactly one network latency
behind where it should be. On the 1 ms LAN inside an arcade that is
nothing. Over Steam Datagram Relay it is 50-150 ms of positional lag on
every other player - a constant bias, not jitter, and the information
needed to remove it was already in the packet.
The timestamp cannot be used as it stands: both machines run
QueryPerformanceCounter since their own boot, so the two clocks share no
epoch. The offset is estimated per peer instead. Each record gives
sample = ourNow - theirStamp = trueOffset + oneWayLatency
and latency is never negative, so the smallest sample seen is the
closest to the truth. A rolling minimum over 128 samples follows crystal
drift and re-adapts when a route gets slower, rather than being pinned
forever by one lucky packet; a shorter path is believed immediately.
Applied with two clamps: never ahead of our own clock, and never further
back than 500 ms. Past that the packet is stale or the estimate is
wrong, and throwing a vehicle half a second forward does more damage
than the lag being corrected.
Entity::UpdateMessageHandler is the only point on the receive path that
knows whose update this is - records carry a timestamp but not an owner -
so it publishes the sender around the loop, and only for entities
somebody else owns. Offsets are forgotten in CreateMission: the hosts in
the next race are not the hosts in the last one and a HostID gets reused.
RP412NETCLOCK=0 restores the arrival-time behaviour, documented in
environ.ini, so a test machine can compare the two without a rebuild.
The estimate is logged per host when it first settles and whenever it
moves more than 50 ms, which is what a three-machine session should be
read against.
WHAT IS AND IS NOT VERIFIED. A full single-player race runs unchanged -
the path is never entered without replicants, which is the regression
risk that reaches everybody. The behaviour this exists for needs real
latency between real machines and is therefore untested: a two-instance
loopback race would only have exercised the zero-latency case, where the
correction is a no-op by construction. Expect remote vehicles to sit
further forward than before, and watch for overshoot when somebody
changes direction sharply - that is the tradeoff this makes, and the
clamp above is what bounds it.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
||
|
|
68f5780efa |
The cockpit clock counts the console's clock
A race ends when the console says so, but the countdown on the map display was computed from the engine clock and its own idea of when the race started - QueryPerformanceCounter from Application::gameStarted, against the console's GetTickCount from gRunStartTick. Two clocks, two epochs, two threads. They agreed to within a frame in the ordinary case, which is why nobody noticed. They do not agree at all when RP412MISSIONSECONDS is set: the override shortens the CONSOLE's length and leaves the egg's alone, so a 25-second test race displayed a clock counting down from 5:00 and was stopped with 4:35 still showing. gMissionClockHook (APPMGR.h, alongside the gPerFrameHook it mirrors) lets the console answer for the countdown when it is marshalling. NULL, or a console that has no answer yet, falls back to exactly the old computation - which is what the arcade -net pods, lobby members and mission review all take, none of them running a console locally. A member's clock is anchored by the console's RunMission arriving over the wire anyway, so it starts within one latency of correct and only drifts at the rate the two crystals differ. Two things come out of it beyond the clock itself. The camera directors switch behaviour at "30 seconds left" (DIRECTOR.cpp, RPDIRECT.cpp) and were reading the same free-running number, so the dramatic end-of-race camera and the actual buzzer were on different clocks too; they now share one. And the countdown holds at 00:00 instead of going negative - the console polls at 250 ms, so zero always arrives slightly before the stop is dispatched. The hook is guarded on gWatchedApp == application. Nothing ever uninstalls it, so a player who hosts a race and then joins somebody else's lobby still has it wired up, and in that race the console is a bystander holding the previous mission's gLengthMs and gRunStartTick. Verified by running a 25-second race with the menu still set to 5:00 and photographing the map display: 00:17, 00:01, then 00:00 held while "time expired - stopping mission" went to the log. Captures use PrintWindow rather than CopyFromScreen - the first attempt grabbed the desktop sitting in front of the Map window, which is somebody's screen contents written to disk, and those files were deleted. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
4f34684b16 |
environ.ini is written on first run, not shipped
Packing one into every zip meant a tester who unzipped a new build over their folder got their configuration replaced. bindings.txt has never had that problem, because the exe carries the template and writes the file only when it is absent. environ.ini now works the same way, so a new build can land on an existing folder and every setting survives. The 245-line template moves out of pack-dist.ps1 and into RPL4ENVIRON.cpp as the exe's own literal, which also means the exe alone can produce a working install. It was lifted mechanically rather than retyped, and the file it writes is line-for-line identical to the one we have been shipping - only the line endings changed, from a mongrel 243 LF plus one stray CRLF that PowerShell's Set-Content left on the end, to the uniform LF the game already writes bindings.txt with. It cannot simply become optional. Without environ.ini, L4GAUGE is unset - which disables the gauge renderer and takes every MFD with it - and L4MFDSPLIT is unset, which is the packed-window arcade layout rather than the glass cockpit. The shipped values ARE the desktop game; the built-in getenv fallbacks are the 1995 pod. So the game writes the file rather than tolerating its absence. The cost of a file that is never overwritten is that a tester carrying one across many builds stops being offered new options. Nothing breaks - an option added later defaults to "behave as before" - but it goes unnoticed, and "the podium does not work" is a confusing bug report when the real answer is that their environ.ini predates RP412PODIUM. So the load names every template key the player's file has never mentioned, and says they are at built-in defaults and that deleting the file brings the documented one back. A stale seven-line file lists all 40. The file is read, never rewritten. The mention test is deliberately generous - a key counts as known if it appears in any form, commented or not - because the failure it guards against is worse than a missed notice: environ.ini is applied line by line, so a second copy of a key appearing later in the file would silently override the player's own. The version line also moves to the top of WinMain. It used to print after the environment was loaded, so the first thing in rpl4.log was a message about environ.ini rather than which build wrote it. Verified: the written file matches the old shipped one line for line; an edited file with a hand-added comment survives another run untouched; a seven-line file from an older build boots and names all 40 options it has never heard of; and a full mission on a self-written file brings up the glass cockpit at 125% with the virtual RIO active and nothing alarming in the log. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
72bb3b394f |
The controls map becomes the handbook
It stopped being a controls map somewhere around the display arrangement and the joystick wizard, and a page called CONTROLS.html is the wrong place to look for what a file in the game folder does. So: docs/rp412-controls.html is now docs/rp412-handbook.html and ships as HANDBOOK.html, titled to match. The new section answers the question the page could not: what is in the game folder and which of it is yours. Four files are - environ.ini, bindings.txt, pilot.cfg, mfd_layout.cfg - and only the first ships, so a fresh unzip has none of the others and deleting one simply starts that part over. A second table covers the shipped engine data, which nobody should edit but everybody eventually wonders about: which INI the gauge canvas comes from, why there are audio mixer tables for hardware that has not existed since 1995, and that JOYSTICK.INI is the legacy path rather than anything the new joystick support reads. The callout carries the two that actually catch people, both of which have caught us during this work: environ.ini is applied OVER the environment, so a variable set in a shell loses to an uncommented line in the file; and bindings.txt is never overwritten once it exists, which is what protects a player's edits and also why an update's new defaults do not appear until it is deleted. CONTROLS.txt keeps its name. It is the controls half in plain text for Notepad, which is still exactly what it is, and the README now says so rather than describing the two as the same thing. Verified by rendering the packed HANDBOOK.html headless: both tables and the callout sit in the page's own components, the footer names the new file, and the version stamp still flows through - the shipped copy reads 4.12.96 from the build it was packed with. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
a1d2de591c |
The patch number is the commit count
A hand-maintained version says what somebody remembered to type. Pinning it to the repository means a binary always names the commit it came from, so a log from a test machine settles which changes are in it. stamp-version.ps1 runs as RP_L4's pre-build step and writes the generated RP_L4\rpl4build.h: #define RP412_VERSION "4.12.96" #define RP412_VERSION_LONG "4.12.96 (a1b2c3d)" The hash beside the number names the commit exactly; a trailing '+' means the tree had uncommitted changes to TRACKED files when it was built, which is the state a puzzling bug report usually comes from. Untracked files do not count - one scratch document in the tree would otherwise mark every build dirty and the marker would stop meaning anything. Generated rather than committed, and gitignored, because a hardcoded number cannot work: the commit that records "4.12.96" is itself commit 96, so the file is stale the moment it lands. The header is rewritten only when the stamp changes, so ordinary rebuilds do not drag RPL4.CPP through a recompile. pack-dist.ps1 reads that header instead of asking git again - a commit between building and packing would otherwise have the zip claiming a version the binary inside it does not report - and warns when the build it is packing came from a modified tree. The README banner, the zip name and the shipped CONTROLS.html all take the same number. Numbering stays ordered: 95 commits so far, so 4.12.95 follows 4.12.7 and every future build sorts after it. Only the "4.12" line is set by hand, at the top of the script. Two things the wiring turned up: Windows PowerShell turns a native command's stderr into ErrorRecords, so with $ErrorActionPreference = 'Stop' git's routine "LF will be replaced by CRLF" warning threw straight past the dirty check and stamped a modified tree as clean. Every git call now goes through cmd, which keeps stderr out of PowerShell's error stream entirely. The script ended on "git diff --quiet", which exits 1 to mean "there are changes" - as a pre-build step that failed the build on exactly the tree a developer builds in. It exits 0 explicitly now. Verified: deleting the header and building recreates it; a second build reports "(unchanged)" and leaves the timestamp alone; a build on a modified tree succeeds and stamps 4.12.95 (c1729e4+); and the packed game logs "Red Planet 4.12.95 (c1729e4+)" on its first line while README.txt and CONTROLS.html in the same package both read 4.12.95. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
c1729e40c7 |
The callsign and loadout outlive the session
The loadout has always survived a race - gPersistSelection is why the setup screen reopens the way you left it - but only for as long as the process lived. Closing the game was a reset, and the callsign is the one thing on that screen a player types rather than picks, so it was the one they had to type again every launch. pilot.cfg beside bindings.txt now holds both, KEY=VALUE like environ.ini, one line per group. BT411 solved this first, in fe_last.ini, and its own comment says why RP412 never grew the file: BT411 relaunches the process between missions and would otherwise forget the loadout mid-evening, while RP412 stays in one process. That made the gap invisible from inside a session and total across two. Same idea, two differences worth naming: BT411 saves only on a launch - it returns before SavePersisted when the player quits. That loses a callsign typed by somebody who then changed their mind, which is exactly the moment this feature exists for, so this writes on the way out however the menu is left: launching, stepping into a lobby, or EXIT GAME. BT411 takes the stored name as-is. A callsign here is quoted into frontend.egg, joined into a comma-separated list for the results screen, and published as Steam lobby member data, so a comma alone would split one pilot into two on the score sheet. SanitizeCallsign drops what could end a token early and is applied to what is typed as well as to what is read, so the file cannot hold what the game will not accept. Every index is range-checked on the way in, against the group's real size rather than a constant - the track list is the one that moves, since football and the death race carry different maps, so it answers for whichever scenario is selected. The track is re-checked after the whole file is read as well, because the file is parsed in the order it happens to be written and the scenario may arrive second. Written unconditionally rather than only on a change: it is a few hundred bytes, and writing every time means a value hand-edited out of range comes back corrected instead of being quietly re-rejected on every launch forever. Verified by round trip. A callsign typed and then abandoned via EXIT GAME is in the file and back in the box next launch. A file carrying Ba"d,Na#me loads as BadName; an empty one falls back to Pilot. A full loadout round-trips value for value; vehicle=999 and color=-3 come back 0 with the rest untouched; and track=9 under football falls back to 0 both when the scenario is read first and when it is read second, which is the case the second check exists for. One correction to my own test rig on the way: cross-process SetWindowText on an EDIT updates the cached caption, which an external GetWindowText then reads back happily, while leaving the control's own buffer alone - so the harness looked right and the game correctly saw the old name. WM_SETTEXT is marshalled properly and shows the truth. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
f20547cb25 |
Controls page covers the joystick setup
CONTROLS.md gained this in the port; the page that ships beside it did not, and the page is the one people actually look at. A "Bring your own stick" section before Rebinding: what DirectInput is and why it needs telling what its axes are for, joyconfig.bat as the answer, and the four beats of running it. The callout carries the reason the wizard reads direction rather than asking you to know it, since that is the part that looks like a quirk until it is explained. Then what it writes beside the grammar that produced it, and a table for the two rules the pod's shape asks for - the signed Pedals axis working the pedal pair, and a real lever owning the throttle channel. The reference tables were left incomplete by the port and are now whole: Pedals joins the axis list, the joy rows join the grammar block, and DirectInput's own axis names get a row of their own. Written in the page's existing components - glance, callout, two-col, tbl-scroll - rather than new ones. The two <kbd> elements I reached for first are not styled anywhere on this page and would have rendered as browser defaults, so they are <code> like every other inline literal here. No version change: 4.12.7 is republished with the page in it. Verified by rendering the packed CONTROLS.html headless at 1280 wide and reading the section back - heading, lede, the four-panel strip, callout, both code blocks, both tables, and the reference rows all sit in the page's own idiom, and the tag balance is unchanged. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
52da65bc0d |
Release 4.12.7
Flight sticks, HOTAS throttles, twist grips and rudder pedals, none of which the game could see before: they arrive through DirectInput rather than XInput, and PadRIO only read XInput. joyconfig.bat is the setup: the wizard asks you to move each control in turn and derives the sign convention from the direction of the move, then writes the joystick rows of bindings.txt between marker lines, leaving anything you have edited yourself alone. Confirmed on the Logitech Extreme 3D: a full pass wrote all four axes, six buttons and the hat, with X and the throttle lever inverted to match the pod's convention and Y left alone - and the deadzone on the twist grip was then hand-tuned from 0.08 to 0.18 in the file, which is the workflow the marker section exists for. Version strings bumped in RPL4.CPP, pack-dist.ps1 and the controls page that ships in the zip. Built clean, packed, zipped (1004 entries, nothing loose at the root) and smoke-tested from the dist: boots reporting 4.12.7, virtual RIO up, and no DirectInput enumeration at all on a default bindings.txt - the joystick layer only opens when the profile asks for it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>v4.12.7 |
||
|
|
91420b5cb2 |
Flight sticks, HOTAS and pedals, with a setup wizard
Ported from BT411, which needed the same thing for its glass cockpit. PadRIO reads XInput, which covers Xbox-class pads and nothing else. A flight stick, a HOTAS throttle, a twist grip, rudder pedals or a wheel arrive through DirectInput instead, and until now the game could not see any of them - the only generic-joystick path left was the 1995 single- device DIJoystick behind L4CONTROLS=DIJOYSTICK, which is untouched here. L4JOY is the reader: up to four devices as normalized state blocks, hot- plug re-enumeration on the same ~3 s cadence PadRIO uses to look for a pad, and a device lost mid-race zeroed rather than left holding whatever was pressed when it went. XInput-class devices are excluded by VID/PID against the RawInput paths carrying the "IG_" marker - without that an Xbox pad arrives through both APIs and every button counts twice. bindings.txt gains four rows in the grammar it already had, using its own vocabulary (deadzone/rate) rather than BT411's: joydev <slot> [product-name substring] joyaxis <src> axis <axis> [invert] [deadzone <d>] [rate <n>] joybutton <n> button <addr> [toggle] joyhat <n> <up|down|left|right> button <addr> Slots resolve to a live device every poll, by name substring or ordinal, so unplugging and replugging does not rewrite anyone's file. Two things the pod's shape forced that BT411 solved differently: Pedals - a signed composite axis that decomposes into the pod's two pedals, positive right and negative left. The pod has a pedal each side; a twist grip or rudder bar is one signed control, and pressing one or the other but never both is exactly what it wants to say. It is a channel name like any other, so a pad stick can drive the turn too. A joyaxis on Throttle with no rate is a real lever and OWNS the channel - full travel maps onto the 0..1 the pod runs on, instead of nudging the accumulator that a spring-centred pad stick has to use. RP412JOYCONFIG=1 (joyconfig.bat) runs the capture wizard before the console screen: it asks the player to move each control, and derives the sign convention from the DIRECTION of the move. That is the point of it - a stick that reads positive pushed right and one that reads negative are equally common, and no amount of documentation gets a player to work out which they own. It writes only its own section, between marker lines, so hand-edited keyboard and pad rows survive re-running it. The wizard also prints every axis at rest before it starts. A driver that refuses the +-32767 range we ask for reports its own, and an axis then sits hard over instead of near zero; seeing "X +1.00" on an untouched stick is the difference between a five-minute fix and a bug report that says it configured itself. Each capture reports the move it saw for the same reason. Verified on the Logitech Extreme 3D on this machine. Enumeration finds it and excludes the Xbox pad, which still arrives separately through XInput. Every row shape parses - 7 axes, 2 buttons, 4 hat directions - and three deliberately malformed rows (a bad axis name, button 99, a "sideways" hat) are each rejected by line number rather than silently dropped. The wizard lists the device with its axes at rest reading X +0.00 Y -0.01 RZ -0.04 SL0 +1.00, waits on the first prompt without self-triggering, and with a hand on the stick captures X to steering, Y to pitch, RZ to the pedals and SL0 to the throttle, inverting the ones that read backwards. Running the captures through to a written file needs a hand on the stick, so that part is the machine's to confirm, not this build's. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
fd61316a40 |
Release 4.12.6
Seven commits since 4.12.5, all about where things sit on screen. RP412MFDLAYOUT remembers window placement across the menu-race-menu loop, in mfd_layout.cfg beside bindings.txt: the game window, the exploded view's display panes, and the plasma glass. Append ,noframe to a line to take that window's title bar and border off - a cockpit filling a monitor edge to edge at a rect you chose, where -fit could only do it by taking the whole screen. That needed a way out of a window with no title bar, so the setup screen carries an EXIT GAME button, bottom left and diagonally opposite LAUNCH. The Steam host/join buttons dim and say STEAM NOT RUNNING rather than disappearing - two buttons quietly missing reads as a broken build. And the Winners Circle camera is framed off the award stand rather than off whoever is standing on it. It had been averaging the filled spots, which moved the shot with the head count: eight finishers put the eye 24 units closer to the stand and aimed it at the middle of the tiers instead of at the winner. The framing constants are untouched, so the shot everybody gets now is the one that was dialled in. Version strings bumped in RPL4.CPP, pack-dist.ps1 and the controls page that ships in the zip. Built clean, packed, and smoke-tested from the dist: boots to the console screen reporting 4.12.6, virtual RIO up, Steam transport up, nothing alarming in the log. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>v4.12.6 |
||
|
|
dce8818273 |
The plasma glass is placeable too
It is a draggable top-level window whenever L4PLASMA=SCREEN, which makes it the last one still being placed fresh every launch. It joins mfd_layout.cfg under "Plasma Display", the caption it already carries - the same key-is-the-title rule the display panes follow. Position only, like the panes: the glass is 128x32 at L4PLASMASCALE, so its size is a setting rather than something to drag. It registers and loads at the point it creates its window rather than leaving that to SVGA16. The glass comes from the gauge renderer and the panes from the video mode, and nothing guarantees which is built first; loading in both places means whichever runs second simply re-applies a placement the first already has. Its window procedure picks up the same WM_EXITSIZEMOVE save the panes have, so a drag writes the file straight away, and the destructor forgets the window before destroying it. Verified by round trip in the exploded view: dragged to 640,880, the file took "Plasma Display=640,880,528,167" alongside the panes and the game window, and a fresh launch in load mode put it physically back at 640,880. The three load lines in the log - one window, then two, then eight - are the ordering doing its job. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
a1f9c0e3c0 |
Winners Circle framed off the stand, not off who is on it
The camera was built from the spots that actually got filled: the first filled one for the front, the average of the filled ones for the centre. So it moved with the finishing order and the head count, and would move between machines if a remote player's vehicle was not there to place. Measured on Wiseguy's Wake, where win1 sits at (1199.84, 3, 2.92) and the eight spots run back to z~38 on the high tier: one finisher eye 1199.84,15,-33.08 aim z 2.92 eight finishers eye 1199.82,18.44,-9.03 aim z 26.97 Twenty-four units closer to the stand and looking at the middle of the tiers instead of at the winner - a different photograph of the same podium depending on how many people showed up. The stand is fixed furniture on every map, so the shot comes off the geometry now. The eight dropzones are read once, up front, before anybody is placed; win1 anchors the framing and the axis from the back rows out through win1 gives the facing, so a map that mounts its stand at another angle is still photographed from the front rather than relying on the old (0,0,-1) fallback. Placement then runs as its own pass and is the only thing that cares who finished - the log reports "N placed on M spots" precisely so a changing N beside unchanged camera numbers is visible. The framing constants are untouched and so is the shot they produce: the new numbers for a single finisher are eye 1199.77,15,-33.08 aim 1199.84,5,2.92, which is the old single-finisher shot to within 0.07 in x. That is the case the standoff/height/aim defaults were dialled in against, so the approved photograph is what everybody gets now instead of what one person got. Verified by running a race to the podium: the eight spot positions log as expected, the camera numbers match the calculation, and the frame is the same one as before - rank 1 centred with its callsign, 2 and 3 flanking on the low tier, 4-8 across the high tier behind. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
bfd5fa163e |
EXIT GAME on the console screen
,noframe takes the title bar away, and with it the only way out of the game. The console screen now offers its own, bottom left: half width and diagonally opposite LAUNCH GAME, because it is the one button on that screen you cannot undo and it should not sit next to the one everybody is aiming for. It goes through the same door as closing the window - fe.closed, so RPL4FrontEnd_Run returns False and the race loop breaks - rather than opening a second shutdown path. Two things had to move for it to make sense. The saved placement now loads in RPL4.CPP, right after the main window is shown, instead of only when SVGA16 builds the cockpit. That was not until a mission started, so the console screen came up at the default rect with its title bar still on and the window only jumped to the saved placement once a race began - which, for a flag whose whole purpose is to take the title bar off, meant it did nothing on the screen you land on. RPL4.CPP now owns the main window's registration outright and SVGA16's branches only reload; the reload after CockpitShellProc goes on still matters, since its WM_SIZE is what re-fits the canvas. That in turn made the exploded view's position-only registration incoherent - the startup load had already applied the size - so the game window is simply position and size everywhere now. The earlier reasoning that its exploded size IS the -res render size does not hold: the back buffer stretches to the window in either view, exactly as it does for the cockpit. WM_EXITSIZEMOVE moves from the cockpit subclass to RPL4.CPP's own WndProc, which the subclass chains to anyway. In its old home it only existed once a cockpit had been built, so dragging the window on the console screen - the obvious moment to put it where you want it - saved nothing. There is also a save on the way out of WinMain, for a session that never started a race and so never ran SVGA16's teardown save. Verified: on a bare-framed window the console screen comes up at the saved 1280x760 with client == window rect, EXIT GAME ends the process with code 0, and a screenshot shows it clear of the column content. A console-only session dragged to 333,222 900x640 wrote that on the drag, kept it through the exit, and came back to exactly it on relaunch - without a race anywhere in the round trip. The noframe and cockpit round trips still pass unchanged. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
a52fec80a9 |
mfd_layout.cfg: ,noframe takes a window's title bar off
Append it to any line - "RPL4=240,120,1000,620,noframe" - and that window comes up with no caption and no border. For the game window that is a cockpit filling a monitor edge to edge at a rect you chose, which -fit could only do by taking the whole screen; for an exploded pane it is a display photographed without chrome. Per line rather than global, so the shell can go bare while the panes keep their captions, or the other way round. The flag is an instruction rather than something measured off the window, so Save carries it back out - otherwise the first finished drag would rewrite the file and quietly drop it. Windows are always built framed and Load only ever strips, so deleting the flag is all it takes to get the frame back; there is no un-strip path to get wrong. A bare window's rect IS its client rect, so the client area is what survives: a window that had a size in the file keeps it as the client, and a position-only pane keeps whatever client it had. That also makes the round trip stable - once bare, what Save records is already the client, so load-save-load does not creep. WS_SYSMENU stays on. It draws nothing without a caption, but without it DefWindowProc will not honour Alt+F4, and a window with no title bar and no way to close it is a trap. Nothing else can be dragged either, hence the note in the file header and environ.ini: place it first, add the flag after. Verified in both views. Cockpit: the same 240,120 1000x620 line with and without the flag, CAPTION|THICKFRAME and a 984x581 client becoming POPUP with a 1000x620 one, and a screenshot showing the displays hard against all four edges where the framed shot had them inside a letterbox. Save mode with the flag set, nudged with WM_EXITSIZEMOVE, rewrote the line with ",noframe" intact. Exploded: Map bare at 777,333 with its 500x640 client preserved while the shell beside it kept its caption. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
40b00ddde1 |
The game window remembers where you put it too
RP412MFDLAYOUT already kept the exploded view's display panes where they
were dragged. The main window is the one people move most, and it was
still being placed fresh every launch, so it joins them.
MFDSplitView_LoadLayout/SaveLayout become RPWindowLayout_Load/Save, with
Register/Forget taking any HWND rather than the module reaching into a
pane registry. Same file, same format, one more line in it.
What comes back depends on the window, so Register takes it as a flag:
display panes position only, as before. A pane's size follows its
content and its button banks, so an old size from a
different build must not distort it.
the game window position and size in the cockpit view. Nothing
derives that size - the cockpit fits itself to
whatever client area it is given - so a window sized
to suit a monitor should come back that way, and
half-restoring it would be the strange behaviour. In
the exploded view its size IS the render resolution
-res asked for, so there only the position returns.
Registered after the CockpitShellProc subclass is installed, on purpose:
the restore's WM_SIZE then runs LayoutCockpit again and the canvas
re-fits the restored client area. -fit does not register at all - it
owns the whole monitor, so there is no placement of the player's to
keep.
CockpitShellProc gained the WM_EXITSIZEMOVE hook the panes already had,
so dragging or resizing the shell writes the file immediately rather
than waiting for teardown.
Two hazards the panes were small enough to get away with and the game
window is not:
- Save reads rcNormalPosition rather than GetWindowRect. A minimised
window reports a nonsense rect and a maximised one reports the
screen; since the file is rewritten whole, either would have
replaced a good line with a useless one. rcNormalPosition is the
restored placement whatever state the window is in.
- Load drops any placement that intersects none of the monitors
currently plugged in. Restoring the game window onto a display that
is no longer there would leave nothing to drag back.
Verified by round trip in both views. Cockpit: dragged and resized to
240,120 1000x620, the file took it, a fresh launch in load mode came up
exactly there with a 984x581 client - and a screenshot confirms the
canvas re-fit it, displays at the corners and the map centred at the
bottom, nothing spilling. Exploded: the shell came back at 60,60 still
1280x720 from -res while Map came back at 777,333, which is the
size-flag split doing its job.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
||
|
|
16ce4dfbea |
Exploded view remembers where you drag its windows
Ported from BT411's BT_GLASS_LAYOUT (29c502d).
The exploded view's panes are draggable desktop windows, but the
arrangement is recomputed on every launch, so dragging one somewhere
useful never survived the menu-race-menu loop.
RP412MFDLAYOUT persists it to mfd_layout.cfg beside bindings.txt:
off / 0 / unset computed arrangement only, no file (default)
load / restore restore saved positions at startup, never write
save / adjust restore, then rewrite on each finished drag
(WM_EXITSIZEMOVE) and on teardown
One "<title>=x,y,w,h" line per pane. Position is restored and the size
read and discarded: a pane's size follows its content and its button
banks, so letting an old size back in would misshape it after any
geometry change - and this port has changed that geometry twice already.
Load runs after the computed arrangement rather than instead of it, so a
pane the file does not mention simply keeps its computed spot. Only the
exploded view registers: the composited cockpit's panes are chrome-less
children with nothing to drag, so they have no position worth keeping.
RP412 needs no equivalent of BT411's "restored" flag. Its re-snap is
LayoutCockpit on WM_SIZE, which only runs in cockpit mode, so nothing
comes back later to overwrite a hand-placed window.
Verified by round trip: dragged Map to 777,333 in save mode, the file
took all six panes, and a fresh launch in load mode put it physically
back at 777,333. The harness also resized the window while moving it,
which incidentally proved the saved size really is ignored - the pane
came back correctly sized from a cfg that recorded 136x39.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
||
|
|
2a23ec0923 |
Say when the Steam buttons are off rather than removing them
With RP412STEAM=1 but no Steam client, the lobby buttons were not drawn at all - the menu simply had two fewer buttons than last time, with the only explanation a line near the top of rpl4.log that scrolls past during a normal startup. It reads as a broken build, and it cost someone a puzzled look today. The buttons are now laid out whenever environ.ini asks for Steam, and greyed out when the wire did not come up, with STEAM NOT RUNNING above them. Clicks on a greyed button do nothing - a dead control that still fires would be worse than the silence it replaces. Two conditions rather than one, so RPL4Lobby_Configured joins Available: configured is the ini switch, available is whether the transport got a FakeIP. Configured keeps the #ifdef in the lobby module, so a build without the Steam SDK still shows no buttons at all rather than a pair that can never work. The notice does not fit inside a button - they are about sixteen characters wide and "HOST STEAM GAME - STEAM NOT RUNNING" is more than twice that, so appending it clipped mid-word. It goes on its own line above the pair instead. Verified both ways, with the Steam client up and with it unavailable: bright and clickable, dim and inert. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
1efd5137d6 |
Release 4.12.5
Version string, zip name and README, plus the new environ.ini options - the podium and the mission-length override. Everything else in that file documents itself, so these should not be the exception. This one restores the Winners Circle: the race fades out and fades back in on the award platform, finishers in finishing order with their callsigns on the plates, held for a few seconds before the results. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>v4.12.5 |
||
|
|
b97dcce3a2 |
Pilot callsigns on the Winners Circle plates
The plates beside each spot came out blank. They ask for textures called player1..player8, which are not files - the renderer draws each pilot's callsign into a texture at run time - so the load failed and left them untextured. Nothing bound the two together. SortAndReloadNameBitmaps already builds those textures indexed by finishing place, which is exactly how the plates are numbered, so the plate beside each spot wants mNameTextures[place]. Binding them is the whole fix, but it takes two steps rather than one. The plates have to survive mesh consolidation first. Static geometry is merged with D3DXConcatenateMeshes and its draw ops deduped by material - and eight failed texture loads leave eight identical untextured ops, so all eight plates collapse into one that could only ever carry a single name. Each plate now gets a distinct 1x1 marker texture as it loads, which keeps it a subset of its own. The marker is never seen. Then the binding runs against the consolidated mesh, not the objects the plates were loaded from - by podium time those have been merged away and are no longer drawn, which is why re-pointing them changed nothing. Verified on a race: 8 plates found in the consolidated mesh, 1 bound, and the winner's plate reads their callsign under their vehicle. One bound of eight is right for a single-pod race - the rest of the places have nobody in them. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
f31c8401c7 |
No gunsight on the Winners Circle
The reticle was still drawn over the podium. It goes out in the 2D pass, after everything the presentation turns off, so it survived. The race is over and nothing is being aimed at. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
e1a3ef7cf1 |
Put the pilot's own vehicle on the Winners Circle
Your own vehicle is built insideEntity - a cockpit and no hull, because you are sitting in it and never see it. That is fine for a race and wrong for a podium: from the presentation camera your spot on the stand was the one that was empty, and on a single-pod race that is the whole picture. The renderer now gives the viewpoint entity an exterior before the shot. Disconnected_Eye is the engine's own switch for this case, documented as being there "so higher level renderers can fix the eye in one spot and watch the viewpoint entity drive around", and it is what makes NotifyOfNewInterestingEntity choose outsideEntity. The exterior is added alongside what is already there rather than by tearing the entity down and rebuilding it. Teardown-and-rebuild is the path the interest manager uses constantly for scenery dropping out of range, so it looked safe, but it is not safe for the viewpoint entity: that one is never uninteresting, the eye renderable goes down with it, and doing it mid-mission stops the scene rendering at all - the screen went black from the moment the podium came up and never came back. Adding the renderables directly does the same job with nothing removed. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
858fb7fb42 |
Fade into the Winners Circle instead of cutting to it
The podium arrived as a hard cut: the race was still on screen one frame and the stand was there the next. The race has its own fade-to-black already, and it was being suppressed to keep the fade from blacking out the podium behind it - which threw away the transition along with the problem. Now the two are sequenced. StopMission lets the race fade out as it always did and posts the podium to itself for when that fade has landed on black; the handler stands the finishers up behind the black and ramps back in. The fade-in is the end-of-mission fade run backwards - the same multiply on the fog colour and both fog distances, from nothing up to what the Winners Circle asked for. Timings: 0.7s of fade-out and black, then a 0.45s fade in. Both come out of the 11 second hold, leaving about ten seconds of podium. RP412PODIUMFADEIN sets the ramp. Verified by measuring frame brightness across the transition. The race falls away and the screen reaches black, then the stand comes up - and with the ramp stretched to 3s to make it resolvable at a half-second sampling interval, it climbs 71.8, 77, 78.7, 79.7, 80.5 rather than stepping, so it is a real fade and not a cut arriving late. The camera also comes down and tilts up across the tiers, which is how a podium wants to be shot. It is a balance in both directions: drop it further or tilt harder and the sky takes the top half while the winner's spot slides off the bottom of the frame; tilt down instead and the shot turns into a floor plan. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
28df53aa31 |
Frame the Winners Circle for the shape it was built for
The stand was composed to be looked at from a 4:3 pod monitor. On a 16:9 canvas its platform runs out at the sides and the shot fills up with sky and void, so the podium is now pillarboxed: the scene renders into a centred 4:3 viewport with the surround left black. The projection has to use the cropped shape too, or the scene comes out squashed into the narrower viewport instead of cropped by it. RP412PODIUMASPECT overrides the ratio, 0 turns it off. The camera also came in closer, from 33 units rather than 45, and now aims slightly below the group rather than above it. That tilt is what buys back the sky above the grandstand - aiming above the group tips the camera up instead and walks the winner's spot off the bottom of the frame, which is the one position that has to be in shot. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
461dcfb6b9 |
Clear the cockpit glass for the Winners Circle
The six secondary displays sit over the viewscreen like the pod's bezels and have nothing to say once the race is over. Worse, the radar sits dead centre along the bottom edge - directly on top of the winner's spot, so the one position that matters was the one you could not see. They are hidden when the podium comes up, which uncovers the canvas the 3D is already being drawn on. No matching show: the mission is over by then, and the next race builds a fresh cockpit. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
9389ec2003 |
Winners Circle: the award platform at the end of a race
The pod hall stood the finishers on a numbered platform when the race ended. All of it shipped in this repo and none of it ever ran here: the stand geometry, the eight ranked dropzones win1-win8 in every one of the 11 maps, and the sequence that places the racers on them. The sequence lived on RPL4PlaybackApplication - the mission-review build - behind a spool file, so the app the pods actually race has never called it. RPL4Application now has its own StopMission handler that ranks the finishers, drops each onto their spot, freezes them, re-sorts the name plates into finishing order and frames a camera on the stand. It fires once: StopMission arrives twice, from the console at the buzzer and again from the player when the ending fade expires, and only the first is the end of the race. Ranking works in football as well as a race. CalcFootballRanking ranks only the RunnerPlayers group, which would have placed the runners and stopped - but nothing calls it. What runs is Player::CalcRanking, every frame, over every scoring player by score. Three pieces of the original had been stubbed out in the D3D9 port and are restored: SetViewAngle was an empty function, so the 45 degrees the sequence asks for did nothing. It now rebuilds the projection the way DPLReadINIPage does and pushes it, and sets viewRatio, which nothing had written since the DPL body was commented out. winnersCircleFogStyle was an empty case. The stand sits far off the track in open ground where the track's own fog leaves it dark; this is the blue-violet the original used, with the fog pushed back to 100/1050 and the clip plane pulled to 1100. The end-of-mission fade had to be told to stand down. It multiplies the fog colour and both fog distances toward zero every frame - correct when a race just ends, fatal to anything shown afterwards. That fade is what made the podium a black screen, and it took a while to find because every frame was being built and presented correctly the whole time. The presentation camera overrides D3DTS_VIEW between the eye renderable writing it and ExecuteImplementation reading it back for the draw calls, so no CameraShip is needed. It builds with LookAt LH, not RH: the projection is LH, and RH aims the camera the opposite way - ask to look down at the stand and you get the sky behind you. The engine's own eye renderable is right to use RH, because its forward and up come out of the entity matrix already in that convention. The mission is held open 11 seconds rather than 3. That fade timer is the only thing keeping the simulation and the renderer alive once the race is over, and it has no upper bound on the ending path. Switches, all off-by-default behaviour aside: RP412PODIUM=0 skips it, RP412PODIUMCAM=0 keeps the cockpit view, RP412PODIUMSTANDOFF/HEIGHT/AIM frame the shot, RP412MISSIONSECONDS overrides the menu game length (the shortest it offers is 3:00, a long wait when what you are testing is the buzzer), and RP412RENDERDIAG=1 reports what a frame is made of. Verified end to end on Wiseguy's Wake: the stand, its tiers, the blue 2 and 3, the red 4 through 8 and all eight name bays, held steady for the full 11 seconds and then handing off to the results screen. Known gaps: the name plates are blank, because the player1-8 textures are runtime name bitmaps that do not resolve as files in this port, and your own vehicle has no exterior model - you see the others, not yourself. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
3a24de03e6 |
Release 4.12.4
Version string, zip name and README. Cut as its own release rather than replacing 4.12.3's asset again: the three test machines need to be able to tell builds apart from what the log and the README say, and a binary that reports 4.12.3 while carrying the lobby work defeats exactly that. This one is about the lobby. Both rooms show the host's mission setup, the football team sheet names each player's VTV, and a lobby holds eight players rather than four. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>v4.12.4 |
||
|
|
e4025c2aec |
Lobbies hold eight, and the room lays itself out to fit them
The lobby capped at four, which is half a grid. The pod hall raced eight and the egg builder already carries the owner plus maxExtraPilots, so eight members fit with room to spare. CreateLobby now asks Steam for eight and the roster arrays follow. Checked what eight has to travel through before raising it: the go roster is 43 bytes a member against an 800 byte buffer, the score sheet 41 against 600, and the pods list 21 against 512, so all three hold eight with margin. The member side parses the go string with a cursor rather than a fixed array, and RegisterRoster walks the count it is given, so neither needed touching. The layout is the part that could not just be renumbered. Rows were a fixed sixteenth of the window, and eight of those ran off the bottom of every window we support - the buttons are laid out upward from the bottom edge while the roster runs down from the top, so the two meet in the middle. The room now measures the band between the title and the topmost button and sizes its rows to it, counted in half rows as setup lines + gap + eight rows + gap + hint so the row height falls out of the space actually available. It never grows past the old sixteenth, so a two player lobby does not get circus sized rows, and when the band cannot give eight rows the room the font needs, the block is drawn in a font that does fit rather than letting rows overlap. The minimum row is the font cell plus two, not the cell plus an eighth. That sounds like a detail and is not: at 1920x1080 the generous version missed by one pixel and paid for it by dropping the conditions line out of every football lobby, which is the one thing in the room nobody can see for themselves. Verified live on hosted lobbies at 1904x1041, and modelled across every window size from 3440x1440 down to 800x480 in race and football, host and member. Race keeps both setup lines everywhere; football, which carries four stacked buttons, keeps both down to 1080 and drops to one below that. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
43cf086ea4 |
Show the host mission setup in both lobbies, and the VTV in football
The room told you who was in it and nothing about what you were about to fly into. Only the owner's menu decides the mission, so nobody else could see the track or the conditions until the race had already started. The owner now publishes its picks as lobby data (track, time of day, weather, game length) and the room draws them under the title: the track on its own line, the conditions under it. Published as display names rather than catalog keys, because the map list differs between race and football and resolving it at the source saves the room from knowing which table a key came from. Football also names the VTV now. The team sheet was team and position only, but the vehicle still decides how someone plays the position, and it was already travelling as member data, just never drawn. Both roster rows now run keys back through the catalogs, so nobody reads bttlbrg where Battle Barge belongs, and the rows widened from the middle half to two thirds to carry three fields. The setup lines are only taken if they fit. Buttons are laid out upward from the bottom edge while the roster runs down from the top, so on a short window the two extra lines would have pushed the roster into MY TEAM. The room measures the gap first and takes two lines, one, or none. Checked against every window size from 3440x1440 down to 640x400 in both scenarios. Verified live on a hosted lobby. Race shows the track, the conditions line, and Battle Barge / Red. Football shows the same setup with Battle Barge, Blue / Aqua, Crusher, tracking the MY TEAM and MY POSITION buttons as they cycle. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
abd720dd6e |
Ship the controls map as a page in the dist
The published artifact source now lives in docs/, and pack-dist emits it as CONTROLS.html beside the plain-text CONTROLS.txt so players get the diagrams without the repo. The artifact source is a fragment - the publisher supplies the document shell - so it is wrapped here with a doctype, charset and viewport. Without those a browser opens it in quirks mode and renders the typography as mojibake. Written without a BOM so the charset declaration is the only thing speaking. The map itself gains a section for the mouse: the button banks reach under the glass, and the six displays are the players to size and the radar to place. Verified the five radar-placement diagrams against the layout they describe - box 16:9, radar 16.7x39.3% of it, centred flush to the bottom edge, flush in either bottom corner, and 30/30 vertically when halfway up a side. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
0c696c9952 |
Release 4.12.3
Version string, zip name and README. This one is about the screen: the cockpit scales both ways and re-fits on resize keeping 16:9, -fit runs it borderless over the monitor at a matched render size, the six secondary displays are the players to scale and the radar to place, and the button banks reach under the glass. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>v4.12.3 |
||
|
|
ea9491d2d5 |
Add the cockpit glass-sizing declarations missed by the last commit
L4VB16.h carries GlassSize and CockpitGlassSizes, which L4VB16.cpp uses
in both the constructor and LayoutCockpit -
|
||
|
|
8dc6605a07 |
Cockpit: buttons under the glass, -fit, and player display layout
Button banks The exploded diagnostic view was still display-only - it predates the button work - so it now builds the same banks as the cockpit, with the pod arrangement laid out from the panes measured sizes rather than a hardcoded 640/480 grid (the banks make each window bigger than its glass, and the bottom row hung off the work area otherwise). The map side columns were spread height/6 from the top, but the maps own legend grid is not sixths: measured off the bitmap it starts 13 rows down with six 102-tall cells on a 105 pitch. Every button sat high of its label, worst at the bottom. Each buttons top and bottom now come off that grid separately and are subtracted - scaling a height directly would let rounding drift them back out of step on a resized cockpit. Depth 100 to 240: against the 480 glass the two banks meet in the middle bar the strips, so practically the whole display is a press target. This mattered most in the cockpit, where the panes are small enough that the halfway clamp governs - at 100 the MFDs had a 110px dead band straight through the middle of the glass. -fit (also spelled -windowed-fullscreen) Borderless over the whole monitor, with the render size chosen to match. The cockpit presents the 3D into a viewscreen that fills its canvas, so the right -res is that canvas at the scale the cockpit will settle on; computing it with identical arithmetic makes the stretch a copy. On the 3440x1440 panel that is 133% and -res 2553 1436, against 125% for the windowed path that pays for the taskbar. The pick runs after the whole command line, so an explicit -res wins from either side of -fit. Capped at 3840x2160. Cockpit mode only - mode 0 has to stay playable on real pod hardware and mode 2 is a dev view - so those get the resolution and keep their windows. Display layout, in environ.ini L4MFDSCALE sizes all five MFDs, L4MFDSCALE_UL and friends override any one of them, L4RADARSCALE the radar, and L4RADARPOS puts the radar bottom centre, in either bottom corner, or halfway up either side. Scaling is applied in canvas units before the canvas is fitted to the window, so a number means the same thing on every monitor. Sizing each display separately let the clamps become exact rather than one conservative rule for all five: what limits a display is its actual neighbour. Which neighbour that is depends on the radar, so the clamps follow it - on the bottom edge it clears the one MFD above its column, but centred on a side it has one above AND below and grows from the middle both ways, so it must clear the taller twice over. Clamping shrinks uniformly; these are photographs of real instruments and a one-axis clamp would squash them. Verified on the ultrawide: all five radar positions, per-display and group scaling with the clamps biting, -fit with and without an explicit -res, and the button geometry measured back off the screen. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
6b43971d2d |
Cockpit scales both ways and re-fits on resize, aspect locked
The canvas was capped at 100%, so a bigger monitor got a 1920x1080 cockpit in the corner of the screen and maximising did nothing - the layout only ran once at startup. Now the fit is one uniform scale with no ceiling, recomputed whenever the window changes size (maximise, restore, drag), and the canvas is centred in whatever client area it gets. Uniform scale is what locks the aspect: a wider-than-16:9 desktop letterboxes with even black bars instead of stretching the cockpit. Panes gained Resize() so the glass and its button banks re-scale with the canvas; the pixel buffers keep their native source resolution. Scaling up is free quality on the MFDs - their glass is a downscale of a native 640x480 channel until about 200%. Verified on the 3440x1392 ultrawide: opens at 125% (2400x1350) instead of 100%, maximises to 126% centred with even bars, a 1884x661 window fits 61% letterboxed left/right, 1084x961 fits 56% letterboxed top/ bottom, and a 2584x1461 client scales up to 134%. The 3D still renders at -res and D3D stretches it to the canvas, so raise -res to match a large screen for 1:1 pixels; start-windowed.bat and the README say so. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
b6045f3c94 |
Glass cockpit: radar columns reach under the map too
The amber Secondary/Screen columns get the same treatment as the red MFD banks, turned on its side: each button reaches 100px in behind the map with a 10px indicator clearing the edge, so the lamp reads as a slim column and the radar picture is the click target. The shared buttonDepth/indicatorStrip constants now drive both banks, and the columns are clamped so they can never meet behind a narrow map. The map pane narrows from 404 to 344 for a 324-wide glass, handing the difference back to the viewscreen. Verified live: clicking 60px inside the radar from either edge presses the column button behind it, with feedback in that side's strip (720 and 1350 pixels). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
0bec0f9640 |
Glass cockpit: MFD buttons reach under the display
The red banks were thin strips sitting outside the glass, so the click target was only as tall as the strip. They are now 100px buttons that extend BEHIND the MFD picture with just a 10px indicator clearing the edge: the lamp still reads as a slim strip along the bezel, but the region of the display above or below it is what you press. Paint order flipped to match - buttons first, glass over them. The banks are clamped so they can never meet in the middle on a scaled-down cockpit, and the panes lost 40px of height each (260 vs 300 for a 240-tall glass), which hands that space back to the viewscreen. Verified live: clicking 50px inside the picture presses the button behind it and the press shows in the indicator strip (770 pixels changed, all of them within the strip). Amber map columns are untouched for now. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
8fb4b72f7a |
Pad Start and Back ship unbound
They were mapped to the config buttons (0x37/0x36), which the 9 and 0 keys already reach from the Upper Right MFD bank. Freeing them gives players two pad buttons to assign; bindings.txt carries the mapping as commented example lines so the way to re-enable it is in the file. Docs and the dist README follow. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
00cb87907c |
Controls map: docs/CONTROLS.md, shipped with the dist
Written from the default bindings.txt so it matches what the game actually writes. Covers the Xbox pad (ASCII diagram plus a table of every button and its RIO address), the keyboard - numpad flight cluster diagram, the Shift/Ctrl/Alt throttle and reverse modifiers, Alt+Q abort - and the panel button banks, showing how the number and letter rows map onto the five MFD clusters exactly as printed on the pod board, with the G/B gap keys and the F-key Secondary/Screen columns. Also notes the keypads being deliberately unbound, the RGB lamp mirror, and how to rebind with worked examples. pack-dist copies it to the dist as CONTROLS.txt, flattened to ASCII so it reads correctly in Notepad while the markdown source keeps its typography. README links it. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
53228686b4 |
pack-dist: release zips nest everything under an RP412 folder
Unpacking the zip anywhere now yields one self-contained RP412\ directory instead of scattering ~1000 files into the extraction folder. The -Zip step stages a copy under the temp dir as RP412\ and compresses that folder. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
1bd6dd83e2 |
MFD rendering: HALFTONE downscale, plus the exploded diagnostic view
Two pieces of MFD work that had been sitting in the tree (already in every build and dist since). The compact cockpit glass shows the full 640x480 gauge canvas at about half size, and COLORONCOLOR did that by dropping every other row and column - which shredded the 1-bit vector strokes and small text. HALFTONE area-averages instead (with the brush origin set, as MSDN requires) so the downscaled MFDs stay legible. L4MFDSPLIT=2 adds an exploded diagnostic view: every display in its own full-size desktop window at native resolution, decoded exactly as the pod VDB split them from the single gauge canvas, with no cockpit compositing and no downscale. It makes an individual MFD readable and screenshottable at full resolution for comparison against the emulator per-channel reference windows. L4MFDSPLIT=1 keeps the composited glass cockpit and stays the default. Also: .gitignore now covers the packaged RedPlanet-*.zip releases, which live on the Gitea release page rather than in the tree. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
1ba3bde36a |
pack-dist: ship steam_appid.txt so a fresh dist is test-ready
The packer wipes and rebuilds dist\, which silently discarded the hand-made steam_appid.txt every repack - and without it SteamAPI_Init fails and the game quietly falls back to TCP, which is the confusing symptom testers hit. The dist now ships it (480, Spacewar) alongside the RP412STEAM=1 environ.ini, so copying the folder to a machine with Steam running is the whole setup. Test doc updated to match. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
4b25f89e6f |
Football: every player picks their own team and position
Closes the gap left by the football commit. Members publish their picks as lobby member data (tm/ps) alongside their loadout, and the owner publishes the scenario (sc) so members know when the picks matter. The lobby room turns into a team sheet: each member row shows their team and position, and MY TEAM / MY POSITION buttons cycle the local pick and republish it live, so everyone watches the sides fill out before the host launches. The egg builder honors explicit picks and only fills gaps: - pilots who chose a team get it; the rest are spread across the host team and one other so the sides stay balanced - a team with no volunteer runner promotes its first UNPICKED member, never overriding someone who chose crusher or blocker - if two pilots on a team both claim runner, the first keeps it and the second lines up - colors stay derived: runner in the team runner color, everyone else in the team color Verified: a three-pilot egg puts the host on crusher in team color with an unpicked teammate promoted to runner in runner color, the other side gets its own runner, and the teams/pilots blocks group correctly; the lobby room cycles picks and repaints the roster; both scenarios still pass their single-player regressions. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
3194d3f973 |
Martian Football: the second scenario is playable
The setup menu gains a SCENARIO group. Picking Football relayouts the menu live: the track list swaps to the football-legal set (drops Paingod Passage and the race build of Freezemoon Freeway, adds the football build headmf - RPConfig per-scenario invalid lists), the COLOR and BADGE columns become TEAM and POSITION, and the title reads MARTIAN FOOTBALL. The egg builder was restructured around one flat pilot table so both scenarios share it. Football emits scenario=football, per-pilot team= and position= entries, and the [teams] / [team::X] / [pilots::X] / [teambitmap] blocks in RPFootballMission layout (team name bitmaps generated by the same GDI plasma renderer as pilot names). Colors are derived rather than chosen, as the arcade did it: the runner wears the team runner color, crushers and blockers the team color. Multi-pod games alternate pilots across two teams and give each team exactly one runner, honoring the host own position pick. Verified end to end: Football launches from the menu, the engine loads the football mission (the cockpit shows the RUNNER - GO FOR POINTS panel), the console marshals it to a scored finish, and the Death Race path still passes its regression unchanged. Known gap: lobby members cannot pick their own team or position yet - the host pick seeds a deterministic assignment. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
0b84b32486 |
README: corrections from review
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
0cf5a23d2e |
Release 4.12.2: version bump
Carries the worldwide lobby-search filter for cross-region internet testing (the only change since 4.12.1 besides the README rewrite). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>v4.12.2 |
||
|
|
13424cce50 |
README: the Steamification story; lobby search goes worldwide
README.md rewritten around the lineage: Red Planet 4.10 on the Tesla 1 pod platform, 4.11 the Win32 port, 4.12 the Steamification of 4.11 - with the arcade-to-consumer replacement table, current status (v4.12.1 verified on three machines), playing/building/docs sections brought up to date. And ahead of internet-wide beta testing: lobby search now applies the worldwide distance filter - Steam defaults to roughly same-region results, which would have hidden lobbies from cross-region testers even though joining them works fine. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
bfdbfe6353 |
Release 4.12.1: version banner and packaging name
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>v4.12.1 |
||
|
|
f1604213ad |
Cockpit: radar trimmed to 1.35x (1.5x was a touch too large)
324x432 glass on the 1080p canvas, per playtest feedback. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
d6f4f16a24 |
Cockpit: radar 50 percent larger
The map/radar glass grows from 240x320 to 360x480 on the 1920x1080 canvas (user request - the radar reads best big). The flanking Secondary/Screen columns, bottom-center placement, and canvas scaling all follow from the measured size automatically. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
29f8572d5c |
environ.ini is self-documenting: every option shipped with comments
The environ.ini reader now skips comments (# or ;), blank lines, and anything that is not KEY=VALUE, so the shipped file documents the whole configuration surface: the core settings as-shipped (controls, renderer, gauge canvas, plasma, single-window cockpit, frame rate, Steam networking), the optional toggles (keyboard lighting, stick flip, AA, particles, plasma scale/position, fixed seed), LAN hosting without Steam, the developer/testing switches (RP412DEVKEYS, L4CONSOLELEN, the Steam self-test), and the arcade multi-monitor heritage variables. Stale L4MFDSCALE reference dropped from the README. Verified: the game boots on the commented file with values applied (controls line honored, Steam transport up from the in-file switch). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
0ca6b5b01f |
RGB keyboard lamp mirror: vRIO Dynamic Lighting port, live in-game
The polish-backlog item, implemented from vRIO KeyboardLampMirror: game-commanded RIO lamp states paint per-key RGB keyboards through Windows Dynamic Lighting (WinRT LampArray). Keys bound to lamp addresses in the active bindings profile glow with the panel palette (red banks, yellow Secondary/Screen columns), flash modes use the exact L4MFDVIEW formula so keyboard and on-screen buttons blink in step, unbound keys are blacked out so the board reads as the button field, and zone-lit keyboards fall back to a board-wide mirror of the strongest lamp. Advantage over vRIO: Dynamic Lighting grants LEDs to the FOREGROUND app - which is the game - so no Windows settings dance. Isolation: L4KEYLIGHT.cpp compiles /std:c++17 + DEFAULT packing + conformance (per-file vcxproj settings; the engine /Zp1 would break the WinRT ABI) with a scalars-only interface, and all WinRT work runs on a private worker thread (watcher, claiming, 100ms paint loop). On by default with a bindings map present; RP412KEYLIGHT=0 opts out; missing Dynamic Lighting logs once and stays dormant. Verified live on the dev laptop: claimed its 24-zone keyboard (board-wide mirror) during a race; race cycling with per-race start/stop of the mirror thread stays green. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |