Add physical window size overrides
This commit is contained in:
@@ -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.
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 |
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
64
engine/Age.Engine.Tests/WindowLaunchOptionsTests.cs
Normal file
64
engine/Age.Engine.Tests/WindowLaunchOptionsTests.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
52
godot/WindowLaunchOptions.cs
Normal file
52
godot/WindowLaunchOptions.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user