Add physical window size overrides

This commit is contained in:
gamer147
2026-07-28 22:10:01 -04:00
parent eb9f11c251
commit 8f84f0b083
9 changed files with 179 additions and 12 deletions

View File

@@ -201,7 +201,7 @@ The meaningful consumer catalog is:
| Setting group | Native Himegari effect | Classification |
|---|---|---|
| `CREATEOBJECT`, `DRAWMODE` | `CreateObject=2` selects only the retained D3D/object backend; `DrawMode=1` selects its matching draw/text path. Startup verifies that bit `1 << DrawMode` exists in the backend mask. | Active renderer selection |
| `SCREENX`, `SCREENY` | Replaces the generic `640x480` fallback with the `800x600` logical canvas before window/surface creation. The port now parses and applies these dimensions to its logical canvas and default windowed client size. | Active core profile; implemented |
| `SCREENX`, `SCREENY` | Replaces the generic `640x480` fallback with the `800x600` logical canvas before window/surface creation. The port parses and applies these dimensions to its logical canvas and default windowed client size; optional host boot arguments can vary only the physical client. | Active core profile; implemented |
| `FONT`, `ENABLEANTIFONT`, `ANTIFONTVERSION` | Seeds MS Mincho, enables `message:UseAntiFont`, selects the grayscale glyph-outline path, and records antialias version 3. | Active text profile |
| `DEPENDMOVIESOUND` | Supplies the default movie-audio dependency/routing policy when movie opcode flags do not force another route. | Active media policy |
| `FULLSCREENBIT`, `ALWAYSBACKUPSURFACE` | These are read only inside the legacy `CreateObject & 1` DirectDraw branch of `engine_initialize_graphics_from_settings@0x406480`. Himegari selects only bit 2, so its 32-bit/zero values do not drive the active renderer. | Valid compatibility settings, inert for this profile |
@@ -226,7 +226,9 @@ This is intentionally a native-behavior catalog, not a mandate to reproduce ever
`Sys4AssetCatalog` now parses the ordered trailer once, retaining unknown pairs for diagnostics, and projects
`SCREENX`/`SCREENY` into one validated engine-owned logical canvas. Godot uses that value for its content-scale
base, default windowed client, backbuffer/compositor bounds, primary surface, movie/layout fallbacks, and input
coordinates. AGE's independent `640x480` defaults apply for missing or invalid dimensions. The port still
coordinates. `--window-width`/`--window-height` may vary that client independently while the logical canvas
and all AGE coordinates remain unchanged. AGE's independent `640x480` defaults apply for missing or invalid
dimensions. The port still
preserves requested Mincho/Gothic faces, save version 3.10, and movie/audio behavior through their existing
paths; those keys have not yet been migrated to the parsed registry.

View File

@@ -3545,6 +3545,30 @@ The Godot C# project builds with zero warnings, and the threaded headless selfte
windowed startup directly requests the same validated dimensions. Physical window overrides remain the
second slice described above.
### Physical-window override result (implemented 2026-07-28)
The second slice adds `--window-width <pixels>` and `--window-height <pixels>` as presentation-only
Godot user arguments. Each omitted dimension independently defaults to the selected SYS4INI logical
canvas; specifying only width or height therefore changes only that physical client dimension. Repeated
options use the last value. Present values must be invariant-culture integers in `1..16384`; missing,
nonnumeric, nonpositive, and oversized values stop startup with a parameter-specific diagnostic.
`WindowLaunchOptions` is resolved immediately after the logical canvas and before any presentation
allocation. In ordinary windowed mode its result sets `Window.Size`; fullscreen, maximized, or minimized
launch modes retain their display-owned physical size. In every mode `ContentScaleSize`, backbuffers,
primary/offscreen surfaces, ADV layouts, and VM/input coordinates remain tied to the unchanged logical
canvas under Godot's `Viewport`/`Keep` aspect policy. The startup log distinguishes the logical canvas,
requested physical window, request source, actual backend size, and current window mode. The local
launchers remain intentionally narrow conveniences; pass these user arguments directly after Godot's
`--` separator.
Eleven focused cases cover defaults, both overrides, independent partial overrides, duplicates, missing
values, malformed/nonpositive values, and the upper bound. The complete engine suite passes 454/454 and
the Godot project builds with zero warnings. Headless selftests pass both the default `800x600` request
and an overridden `1200x700` request while continuing to report `logical-canvas=800x600`; invalid zero
width exits during startup with the expected diagnostic. Headless's minimized `64x64` backend remains
non-authoritative for visible client geometry.
## ADV requested-face selection implemented (2026-07-28)
Opcode `0x1a5` is no longer a presentation no-op. The VM retains its requested face in

View File

@@ -27,7 +27,7 @@ or replaced before claiming portable exports.
| Area | Current dependency | Runtime impact | Portability status / future action |
|---|---|---|---|
| SYS4INI per-game startup profile | `Sys4AssetCatalog` parses and retains the bounded ordered startup trailer; `Sys4LogicalCanvas` applies `SCREENX`/`SCREENY` with AGE's independent `640x480` fallbacks. Native SYS4INI also carries text, ADV input/skip, save ABI/path, audio, legacy renderer, and Windows registration settings | Godot now uses the selected canvas for content scaling, backbuffers/compositor bounds, primary surfaces, layout/input fallbacks, and the matching default windowed client. The port still does not source `CancelMesSkipOnClick`, `CoexistMesSkip`, cursor/redraw policy, or wheel action ids from the trailer | Add later boot parameters that vary only the physical window while Godot scales/letterboxes the unchanged canvas. Apply further semantic keys explicitly, translate save roots through the host, and classify DirectDraw/fullscreen-bit and registration/key settings as native compatibility metadata. Canonical inventory and consumers: `sys4-format-notes.md` and `engine-re.md` |
| SYS4INI per-game startup profile | `Sys4AssetCatalog` parses and retains the bounded ordered startup trailer; `Sys4LogicalCanvas` applies `SCREENX`/`SCREENY` with AGE's independent `640x480` fallbacks. Native SYS4INI also carries text, ADV input/skip, save ABI/path, audio, legacy renderer, and Windows registration settings | Godot uses the selected canvas for content scaling, backbuffers/compositor bounds, primary surfaces, layout/input fallbacks, and the default windowed client. `--window-width`/`--window-height` vary only the physical client while preserving the canvas and letterbox policy. The port still does not source `CancelMesSkipOnClick`, `CoexistMesSkip`, cursor/redraw policy, or wheel action ids from the trailer | Validate the existing presentation policy on Linux/macOS window managers. Apply further semantic keys explicitly, translate save roots through the host, and classify DirectDraw/fullscreen-bit and registration/key settings as native compatibility metadata. Canonical inventory and consumers: `sys4-format-notes.md` and `engine-re.md` |
| Retained graphics presentation | Backend-neutral `GfxState`; accepted default Godot `Sprite2D` GPU stage plus the retained software pixel oracle, using runtime `ImageTexture`, canvas transforms/materials, and no native graphics API | GPU backend caches static/color-key variants, updates dynamic surfaces, handles retained range transitions, and falls back whole-frame for the legacy host screen-transition path | Godot owns D3D/Vulkan/Metal/OpenGL selection; validate shader/blend/filter behavior per target rather than adding a platform renderer |
| AGE movie decode (`0x236` scene movies; `0x20f` modal LOGO/OP/ED) | `FfmpegMovieDecoder` is the sole factory over the project-owned `native/age_movie_ffmpeg` ABI | Windows-x64 passes the complete 213-payload installed video/audio corpus gate plus audible LOGO/OP/CHAPTER playback | Add target-specific native builds and export packaging |
| Movie integration | Each surface owns a unique playback-instance id; `MovieRuntime` owns `IMovieDecoder` from an injected factory; video-only streams use monotonic pacing while audio-bearing streams use the Godot output clock | Concurrent/restarted uses of one asset have independent frame/audio/completion/teardown state; managed code is no longer Windows-annotated, while only the win-x64 native bundle exists today | Add Linux/macOS native builds and smoke gates |

View File

@@ -686,9 +686,10 @@ specific data for diagnostics; semantic settings are applied through explicit cr
legacy renderer/registration switches remain classified rather than blindly emulated. The first bounded
application slice is complete: `SCREENX`/`SCREENY` select the validated logical canvas, all presentation
allocations and primary bounds consume it, and it is the default windowed size. Other settings remain on
their existing paths while gameplay is the priority. A second slice will add
independent physical `--window-width`/`--window-height` overrides without changing VM coordinates or
surface geometry. The executable task plan and gates live in `phase-a-slice-plan.md`. This experiment was
their existing paths while gameplay is the priority. The second slice is also complete:
independent physical `--window-width`/`--window-height` overrides change only the windowed client while
the logical canvas, VM coordinates, and surface geometry remain fixed. The executable task plan and gates
live in `phase-a-slice-plan.md`. This experiment was
diagnostic only; no Kamidori support or `0x1be` semantics were implemented.
### Other engine versions (SYS3 / SYS5) — one app, not many

View File

@@ -318,6 +318,11 @@ use the last `input-wait` event in `timeline.jsonl` as the authoritative manual
- `--scene <NAME>` — override the default `SYSTEM4` root with a direct diagnostic scene, e.g. `--scene SC0240` (executes 29 nested subroutines).
- `--selftest` — headless; runs a **synthesized** scene through the thread/suspend/`CallDeferred` plumbing and asserts it matches a live headless run (full handling; no vm0/frozen golden). Exits.
- `--window-width <pixels>` / `--window-height <pixels>` — override only the physical windowed client
dimensions; each omitted dimension defaults independently to the SYS4INI logical canvas. Godot keeps
that logical canvas and aspect-preserving letterboxing, so these options do not change VM coordinates,
AGE surfaces, layout, or backbuffer allocation. Values must be integers in `1..16384`; duplicate
options use the last value.
- `--seed 0xADDR=VAL` (repeatable) — seed initial global state, e.g. `--seed 0xa57=1` unlocks Lily's form-A voiced dialogue.
- `--boot` — direct-scene diagnostic only: with `--scene <non-SYSTEM4>`, run the old
`INITCONFIG/INIT2/INIT` state prefix before that isolated scene. The normal SYSTEM4-rooted launch neither

View File

@@ -33,6 +33,7 @@
<Compile Include="..\..\godot\PerformanceFrameLog.cs" Link="PerformanceFrameLog.cs" />
<Compile Include="..\..\godot\MovieSurfaceRegistry.cs" Link="MovieSurfaceRegistry.cs" />
<Compile Include="..\..\godot\RiffWaveSanitizer.cs" Link="RiffWaveSanitizer.cs" />
<Compile Include="..\..\godot\WindowLaunchOptions.cs" Link="WindowLaunchOptions.cs" />
<Compile Include="..\..\tools\movie-corpus-gate\MovieCorpusGate.cs" Link="MovieCorpusGate.cs" />
</ItemGroup>

View File

@@ -0,0 +1,64 @@
using Age.Engine.Sys4;
public class WindowLaunchOptionsTests
{
private static readonly Sys4LogicalCanvas LogicalCanvas = new(1024, 576);
[Fact]
public void DefaultsPhysicalWindowToLogicalCanvas()
{
var options = WindowLaunchOptions.Resolve([], LogicalCanvas);
Assert.Equal(new WindowLaunchOptions(1024, 576, false, false), options);
Assert.False(options.IsOverridden);
}
[Fact]
public void WidthAndHeightOverrideOnlyPhysicalDimensions()
{
var options = WindowLaunchOptions.Resolve(
["--scene", "SC0000", "--window-width", "1600", "--window-height", "900"],
LogicalCanvas);
Assert.Equal(new WindowLaunchOptions(1600, 900, true, true), options);
Assert.Equal(new Sys4LogicalCanvas(1024, 576), LogicalCanvas);
}
[Theory]
[InlineData("--window-width", "1280", 1280, 576, true, false)]
[InlineData("--window-height", "720", 1024, 720, false, true)]
public void PartialOverrideKeepsOtherLogicalDefault(
string argument, string value, int width, int height,
bool widthOverridden, bool heightOverridden)
{
var options = WindowLaunchOptions.Resolve([argument, value], LogicalCanvas);
Assert.Equal(new WindowLaunchOptions(
width, height, widthOverridden, heightOverridden), options);
}
[Fact]
public void DuplicateDimensionUsesLastValue()
{
var options = WindowLaunchOptions.Resolve(
["--window-width", "1200", "--window-width", "1400"], LogicalCanvas);
Assert.Equal(1400, options.Width);
Assert.Equal(576, options.Height);
}
[Theory]
[InlineData("--window-width")]
[InlineData("--window-width", "0")]
[InlineData("--window-height", "-1")]
[InlineData("--window-width", "not-a-number")]
[InlineData("--window-height", "16385")]
[InlineData("--window-width", "--window-height", "720")]
public void InvalidOrMissingDimensionIsRejected(params string[] arguments)
{
var error = Assert.Throws<ArgumentException>(
() => WindowLaunchOptions.Resolve(arguments, LogicalCanvas));
Assert.Contains(arguments[0], error.Message);
}
}

View File

@@ -22,6 +22,7 @@ public partial class Main : Godot.Control
private const int NativeBoldGlyphSpacing = 1;
private int _screenWidth = Sys4LogicalCanvas.DefaultWidth;
private int _screenHeight = Sys4LogicalCanvas.DefaultHeight;
private WindowLaunchOptions _windowOptions;
private TextureRect _screenView = null!; // shows the composited screen backbuffer
private Image _screen = null!; // SYS4INI-sized immediate-mode canvas
private ImageTexture _screenTex = null!;
@@ -108,10 +109,21 @@ public partial class Main : Godot.Control
public override void _Ready()
{
var userArgs = OS.GetCmdlineUserArgs();
// Resolve the selected game's logical canvas before any presentation allocation. The same catalog
// instance is reused for scripts and assets later in startup.
var catalog = Sys4AssetCatalog.Load(Paths.Sys4Ini);
var logicalCanvas = catalog.LogicalCanvas;
try
{
_windowOptions = WindowLaunchOptions.Resolve(userArgs, logicalCanvas);
}
catch (System.ArgumentException error)
{
GD.PushError($"[startup] {error.Message}");
GetTree().Quit(2);
return;
}
_screenWidth = logicalCanvas.Width;
_screenHeight = logicalCanvas.Height;
_screenPixels = new byte[logicalCanvas.RgbaByteCount];
@@ -120,9 +132,11 @@ public partial class Main : Godot.Control
rootWindow.ContentScaleAspect = Window.ContentScaleAspectEnum.Keep;
rootWindow.ContentScaleSize = new Vector2I(_screenWidth, _screenHeight);
if (rootWindow.Mode == Window.ModeEnum.Windowed)
rootWindow.Size = new Vector2I(_screenWidth, _screenHeight);
rootWindow.Size = new Vector2I(_windowOptions.Width, _windowOptions.Height);
GD.Print($"[profile] SYS4INI logical canvas={_screenWidth}x{_screenHeight} " +
$"window={rootWindow.Size.X}x{rootWindow.Size.Y}");
$"requested window={_windowOptions.Width}x{_windowOptions.Height} " +
$"source={(_windowOptions.IsOverridden ? "boot-arguments" : "logical-canvas")} " +
$"actual={rootWindow.Size.X}x{rootWindow.Size.Y} mode={rootWindow.Mode}");
// One logical canvas that draw-texture blits into, shown behind the dialogue.
_screen = Image.CreateEmpty(_screenWidth, _screenHeight, false, Image.Format.Rgba8);
@@ -205,7 +219,6 @@ public partial class Main : Godot.Control
}
_audioOutputLatencySeconds = AudioServer.GetOutputLatency();
var userArgs = OS.GetCmdlineUserArgs();
_selftest = System.Array.IndexOf(userArgs, "--selftest") >= 0;
bool boot = System.Array.IndexOf(userArgs, "--boot") >= 0; // diagnostic prefix for direct-scene runs
bool nativeDebugMenu = System.Array.IndexOf(userArgs, "--native-debug-menu") >= 0;
@@ -2158,7 +2171,10 @@ public partial class Main : Godot.Control
&& _screen.GetHeight() == _screenHeight
&& _screenPixels.Length == checked(_screenWidth * _screenHeight * 4)
&& rootWindow.ContentScaleSize
== new Vector2I(_screenWidth, _screenHeight);
== new Vector2I(_screenWidth, _screenHeight)
&& _windowOptions == WindowLaunchOptions.Resolve(
OS.GetCmdlineUserArgs(),
new Sys4LogicalCanvas(_screenWidth, _screenHeight));
textEffectSmoke.QueueFree();
ok &= launcherOk && sleepMinimumOk && inputTranslationOk && cp932WavMetadataOk
&& bgmReplacementCancelsFade && textEffectModesOk && fontCalibrationOk
@@ -2167,13 +2183,15 @@ public partial class Main : Godot.Control
$"debug launcher catalog/UI smoke ({debugEntries.Count} packed scripts); " +
$"sleep-min=1ms; native-key-translation=ok; cp932-wav-info=ok; " +
$"bgm-fade-replacement=ok; text-effect-modes=ok; font-calibration=ok; " +
$"logical-canvas={_screenWidth}x{_screenHeight}");
$"logical-canvas={_screenWidth}x{_screenHeight}; " +
$"window-request={_windowOptions.Width}x{_windowOptions.Height}");
else GD.Print($"SELFTEST FAIL: threaded={actual.Count} vs headless={expected.Count}; " +
$"debug-launcher={launcherOk}; sleep-min={sleepMinimumOk}; " +
$"native-key-translation={inputTranslationOk}; cp932-wav-info={cp932WavMetadataOk}; " +
$"bgm-fade-replacement={bgmReplacementCancelsFade}; " +
$"text-effect-modes={textEffectModesOk}; font-calibration={fontCalibrationOk}; " +
$"logical-canvas={logicalCanvasOk}({_screenWidth}x{_screenHeight})");
$"logical-canvas={logicalCanvasOk}({_screenWidth}x{_screenHeight}); " +
$"window-request={_windowOptions.Width}x{_windowOptions.Height}");
GetTree().Quit(ok ? 0 : 1);
}

View File

@@ -0,0 +1,52 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using Age.Engine.Sys4;
/// <summary>
/// Presentation-only window dimensions resolved from Godot user arguments. These never redefine the
/// SYS4 logical canvas, VM coordinates, or AGE surface dimensions.
/// </summary>
public readonly record struct WindowLaunchOptions(
int Width, int Height, bool WidthOverridden, bool HeightOverridden)
{
public const int MaximumDimension = Sys4LogicalCanvas.MaximumDimension;
public bool IsOverridden => WidthOverridden || HeightOverridden;
public static WindowLaunchOptions Resolve(
IReadOnlyList<string> arguments, Sys4LogicalCanvas logicalCanvas)
{
ArgumentNullException.ThrowIfNull(arguments);
int width = logicalCanvas.Width;
int height = logicalCanvas.Height;
bool widthOverridden = false;
bool heightOverridden = false;
for (int index = 0; index < arguments.Count; index++)
{
string argument = arguments[index];
if (argument is not ("--window-width" or "--window-height")) continue;
if (index + 1 >= arguments.Count)
throw new ArgumentException($"{argument} requires a pixel value");
string raw = arguments[++index];
if (!int.TryParse(raw, NumberStyles.Integer, CultureInfo.InvariantCulture, out int value)
|| value <= 0 || value > MaximumDimension)
throw new ArgumentException(
$"{argument} must be an integer from 1 through {MaximumDimension}; got '{raw}'");
if (argument == "--window-width")
{
width = value;
widthOverridden = true;
}
else
{
height = value;
heightOverridden = true;
}
}
return new WindowLaunchOptions(width, height, widthOverridden, heightOverridden);
}
}