Compare commits
10 Commits
e2fe660c75
...
71ec6e55ed
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
71ec6e55ed | ||
|
|
4db2f416cf | ||
|
|
a4f029c0f8 | ||
|
|
17f31dc728 | ||
|
|
5f6e647114 | ||
|
|
21c1317b05 | ||
|
|
067a11440e | ||
|
|
9e59e98efa | ||
|
|
728399cb8b | ||
|
|
81d063da37 |
18
.editorconfig
Normal file
18
.editorconfig
Normal file
@@ -0,0 +1,18 @@
|
||||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[*.{c,cpp,cs,h,py,ps1,sh}]
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
|
||||
[*.{csproj,json,jsonl,props,targets,toml,tscn,tres,xml,yaml,yml}]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
|
||||
[*.{bat,cmd}]
|
||||
end_of_line = crlf
|
||||
69
.gitattributes
vendored
69
.gitattributes
vendored
@@ -1,5 +1,72 @@
|
||||
* text=auto
|
||||
.editorconfig text eol=lf
|
||||
.gitattributes text eol=lf
|
||||
.gdignore text eol=lf
|
||||
.gitignore text eol=lf
|
||||
|
||||
*.bat text eol=crlf
|
||||
*.cmd text eol=crlf
|
||||
|
||||
*.c text eol=lf
|
||||
*.cfg text eol=lf
|
||||
*.cs text eol=lf
|
||||
*.csproj text eol=lf
|
||||
*.config text eol=lf
|
||||
*.cpp text eol=lf
|
||||
*.gd text eol=lf
|
||||
*.godot text eol=lf
|
||||
*.h text eol=lf
|
||||
*.json text eol=lf
|
||||
*.jsonl text eol=lf
|
||||
*.md text eol=lf
|
||||
*.props text eol=lf
|
||||
*.targets text eol=lf
|
||||
*.ps1 text eol=lf
|
||||
*.py text eol=lf
|
||||
*.sh text eol=lf
|
||||
*.sln text eol=lf
|
||||
*.toml text eol=lf
|
||||
*.tscn text eol=lf
|
||||
*.tres text eol=lf
|
||||
*.uid text eol=lf
|
||||
*.xml text eol=lf
|
||||
*.yaml text eol=lf
|
||||
*.yml text eol=lf
|
||||
|
||||
*.7z binary
|
||||
*.a binary
|
||||
*.agf binary
|
||||
*.avi binary
|
||||
*.bin binary
|
||||
*.bmp binary
|
||||
*.cur binary
|
||||
*.dll binary
|
||||
*.dylib binary
|
||||
*.exe binary
|
||||
*.gif binary
|
||||
*.ico binary
|
||||
*.jpeg binary
|
||||
*.jpg binary
|
||||
*.lib binary
|
||||
*.mp3 binary
|
||||
*.mp4 binary
|
||||
*.mpeg binary
|
||||
*.mpg binary
|
||||
*.ogg binary
|
||||
*.otf binary
|
||||
*.pdb binary
|
||||
*.png binary
|
||||
*.rar binary
|
||||
*.so binary
|
||||
*.ttf binary
|
||||
*.wav binary
|
||||
*.woff binary
|
||||
*.woff2 binary
|
||||
*.zip binary
|
||||
|
||||
# Generated references are ordinary canonical-LF text. Their whitespace
|
||||
# exceptions preserve generator-owned formatting without disabling EOL policy.
|
||||
docs/opcode-reference.md whitespace=trailing-space,space-before-tab,cr-at-eol
|
||||
docs/global-reference.md whitespace=trailing-space,space-before-tab,cr-at-eol
|
||||
docs/engine-ctx-reference.md whitespace=trailing-space,space-before-tab,cr-at-eol
|
||||
tools/age_opcodes_himegari.py whitespace=trailing-space,space-before-tab,cr-at-eol
|
||||
*.sh text eol=lf
|
||||
|
||||
61
.gitea/workflows/core-validation.yml
Normal file
61
.gitea/workflows/core-validation.yml
Normal file
@@ -0,0 +1,61 @@
|
||||
name: Core validation
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: core-${{ gitea.workflow }}-${{ gitea.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
core:
|
||||
name: Linux core gate
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 15
|
||||
env:
|
||||
DOTNET_CLI_TELEMETRY_OPTOUT: "1"
|
||||
DOTNET_NOLOGO: "true"
|
||||
NUGET_XMLDOC_MODE: skip
|
||||
PYTHONUTF8: "1"
|
||||
steps:
|
||||
- name: Check out repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Set up Python 3.11
|
||||
uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: "3.11"
|
||||
architecture: x64
|
||||
|
||||
- name: Set up .NET SDK
|
||||
uses: actions/setup-dotnet@v4
|
||||
with:
|
||||
global-json-file: global.json
|
||||
|
||||
- name: Report toolchain
|
||||
run: |
|
||||
python --version
|
||||
dotnet --version
|
||||
|
||||
- name: Run asset-independent validation
|
||||
run: python -X utf8 tools/validate.py --level core
|
||||
|
||||
- name: Upload failure logs
|
||||
if: failure()
|
||||
uses: christopherhx/gitea-upload-artifact@v4
|
||||
with:
|
||||
name: core-validation-${{ gitea.run_id }}-${{ gitea.run_attempt }}
|
||||
path: build/validation/**
|
||||
if-no-files-found: ignore
|
||||
retention-days: 7
|
||||
3
bin/.gitignore
vendored
Normal file
3
bin/.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
*
|
||||
!.gitignore
|
||||
!README.md
|
||||
15
bin/README.md
Normal file
15
bin/README.md
Normal file
@@ -0,0 +1,15 @@
|
||||
# Local third-party tools
|
||||
|
||||
This directory is reserved for optional, machine-local binaries. Its contents are ignored by Git;
|
||||
only this README and `.gitignore` are repository content.
|
||||
|
||||
The installed-data archive parity test can use `BinExtractALF.exe` here together with its matching
|
||||
`LzssCpp.dll`. These files are not required by the runtime or the asset-independent test suite, and
|
||||
their provenance and redistribution terms have not been verified. Do not commit them. The historical
|
||||
local copies audited on 2026-08-03 had these SHA-256 values:
|
||||
|
||||
- `BinExtractALF.exe`: `46167CDF3DA1F02733CE1E7F0DA99D155E934EDE0987CA79C8927F48FEB25FE2`
|
||||
- `LzssCpp.dll`: `596B9DDB07CA5C29CEE609E846F1512654EDEC56310A90D6C2A6951BE1E39FE1`
|
||||
|
||||
`pe-sieve32.exe` is no longer used. The failed IAT reconstruction experiment is retained in
|
||||
`docs/engine-re.md`; the Frida import-map workflow replaced it.
|
||||
@@ -22,13 +22,17 @@ S:\Game Hacking\Eushully\Himegari\ ← workspace root (three siblings)
|
||||
│ └── *.exe (uninstallers), SAS0099.OGG … other shipped files.
|
||||
│
|
||||
├── extracted/ ← DERIVED (game-side) — extracted ALF contents,
|
||||
│ │ ~3.9 GB, regenerable via age-reimpl/bin/BinExtractALF.
|
||||
│ │ ~3.9 GB, regenerable with a local archive extractor.
|
||||
│ └── DATA1/ … DATA5/ DATA1 = 481 .BIN scripts (the corpus we parse)
|
||||
│ + AGF/BMP/WAV in the others.
|
||||
│
|
||||
└── age-reimpl/ ← OUR WORK (everything we made lives here)
|
||||
│
|
||||
├── README.md navigation-only repository front door; canonical facts stay in docs/
|
||||
├── global.json pins the validated .NET 8 SDK feature band
|
||||
├── .editorconfig / .gitattributes UTF-8, indentation, text/EOL, and binary-file policy;
|
||||
│ tracked source and generated references use canonical LF
|
||||
├── .gitea/workflows/core-validation.yml read-only Ubuntu source-only core gate for the private Gitea remote
|
||||
├── run-godot.ps1 / run-godot.cmd tracked development launcher + Windows wrapper;
|
||||
│ resolves Godot/game-root from parameters, environment,
|
||||
│ PATH, and the conventional sibling install
|
||||
@@ -56,10 +60,9 @@ S:\Game Hacking\Eushully\Himegari\ ← workspace root (three siblings)
|
||||
│ ├── movie-corpus-gate/ C# full-corpus FFmpeg decode/lifecycle acceptance tool
|
||||
│ └── probe_*.py format reverse-engineering probes (historical)
|
||||
│
|
||||
├── bin/ 3rd-party binaries we use (not ours, not the game's)
|
||||
│ ├── BinExtractALF.exe ALF archive extractor → produces extracted/
|
||||
│ ├── LzssCpp.dll its LZSS codec dependency
|
||||
│ └── pe-sieve32.exe (hollowsprocess) unpacked-PE dumper — engine-RE escalation (see docs/engine-re.md)
|
||||
├── bin/ ignored home for optional machine-local tools
|
||||
│ ├── README.md / .gitignore tracked policy; binaries never enter Git
|
||||
│ └── BinExtractALF.exe + LzssCpp.dll optional local archive oracle (unverified provenance)
|
||||
│
|
||||
├── vm-map/ VM / reverse-engineering reference artifacts
|
||||
│ ├── opcodes.toml ★ CANONICAL opcode reference (hand-edited: ABI + semantics
|
||||
@@ -118,9 +121,9 @@ S:\Game Hacking\Eushully\Himegari\ ← workspace root (three siblings)
|
||||
│ ├── Age.Engine/Persistence/ native S3SD/S4SD + S3RT codecs, shared payload/ReadTextDB,
|
||||
│ layout-3 numbered state/history/gfx, DAT/STH pair + BMP codec,
|
||||
│ and profile-owned state
|
||||
│ ├── Age.Engine.Frontend/ platform-neutral frontend diagnostics/options project: page
|
||||
│ locator, timeline/trace/step-limit diagnostics, VM/window launch
|
||||
│ options, and performance-frame logging
|
||||
│ ├── Age.Engine.Frontend/ platform-neutral frontend support: diagnostics/options, page
|
||||
│ location, performance logging, movie runtime/FFmpeg decoding,
|
||||
│ audio alignment, surface registry, and WAV sanitization
|
||||
│ └── Age.Engine.Text.Windows/ optional exact Windows GDI gray-4 reference backend; kept
|
||||
│ outside the platform-neutral VM/runtime core
|
||||
├── native/ authored native runtime boundaries
|
||||
@@ -154,8 +157,13 @@ full-width text entry.
|
||||
Both `engine/AgeEngine.sln` and `godot/Himegari.sln` include the project; Godot and `Age.Engine.Tests` consume it
|
||||
through project references rather than compiling duplicate linked sources. The first owned cluster contains
|
||||
`PageLocatorState`, `GodotTimelineLog`, `GodotTraceSink`, `StepLimitDiagnosticFormatter`, `GodotVmOptions`,
|
||||
`PerformanceFrameLog`, and `WindowLaunchOptions`. Movie runtime/decoder helpers remain under `godot/` until their
|
||||
separate ownership slices.
|
||||
`PerformanceFrameLog`, and `WindowLaunchOptions`. It also owns the internal movie decoder boundary/runtime,
|
||||
audio-timeline alignment, movie-surface registry, and RIFF/WAVE sanitizer; only the `Himegari` and
|
||||
`Age.Engine.Tests` assemblies receive explicit friend access to that internal API. FFmpeg native interop and the
|
||||
asynchronous/paced decoder also live here; the `Age.MovieCorpusGate` tool receives friend access to the native
|
||||
frame-source seam and references this project rather than compiling source links. Reusable movie-corpus discovery,
|
||||
decode-gate, and report contracts live here as well; `tools/movie-corpus-gate/Program.cs` remains only the
|
||||
command-line entry point and output writer.
|
||||
|
||||
`godot/GodotAdvHost.cs` retains cross-domain host coordination. Its partial-class companion
|
||||
`godot/GodotAdvHost.AdvText.cs` owns live/retained ADV text, surface glyph rasterization and caching,
|
||||
@@ -276,7 +284,8 @@ have no repository output tree and write their automatic maps below `user://diag
|
||||
and under `extracted/DATA1/`, the game-folder copy (patch v1.03) wins. `paths.scripts()` resolves
|
||||
this automatically (overrides win).
|
||||
- **`build/` and `extracted/` are disposable.** `build/` regenerates via `tools/extract_phase2.py`
|
||||
(or `sys4load.py`); `extracted/` regenerates via `bin/BinExtractALF.exe` on the `.ALF` files.
|
||||
(or `sys4load.py`); `extracted/` regenerates from the `.ALF` files with a separately obtained local
|
||||
extractor. `bin/README.md` records the optional local convention; no extractor binary is tracked.
|
||||
Safe to delete and rebuild; do not hand-edit.
|
||||
- **Opcode knowledge is edited ONLY in `vm-map/opcodes.toml`** (ABI + semantics + provenance +
|
||||
`depends_on`). Run `tools/opcodes_build.py --build` to regenerate the shim (`tools/age_opcodes_himegari.py`),
|
||||
|
||||
@@ -275,7 +275,7 @@ scene-local numeric addressing mode.
|
||||
LZSS-or-raw header section, 4/8/truecolor source pixels, LZSS-or-raw pixel section, bottom-up row/stride
|
||||
conversion, and optional `ACIF` LZSS alpha plane. Port only the algorithm and attribution into
|
||||
platform-neutral .NET code; do not carry GARbro's WPF/GameRes dependencies. Kelebek's extractor and the
|
||||
on-disk `BinExtractALF.exe` are validation references; the Kelebek repository exposes no clear license,
|
||||
separately obtained local `BinExtractALF.exe` is a validation reference; the Kelebek repository exposes no clear license,
|
||||
so its code should not be copied without clarification. The focused `LzssDecoder` is shared with
|
||||
`Sys4AssetCatalog`; raw and compressed information/pixel/ACIF sections use the same bounded primitive.
|
||||
4. **Runtime consumers (packed-id correction complete 2026-07-21).** Script loading, textures, voice,
|
||||
@@ -461,7 +461,8 @@ VFS-B passes its bounded gates in `Sys4AssetStoreTests`: the installed AAI expan
|
||||
`0x118` (expanded size at `0x110`, packed size at `0x114`) to one `APPEND01.ALF` archive and 81 80-byte
|
||||
records. All records carry selector 1 and literal `$1$` names. The full directory has stable SHA-256
|
||||
`23F0C104A45C099CEFB7D333362716EDE6F20B9EC53E4C3705A8E3A87063708E` over its ordered record fields.
|
||||
The integration gate runs `BinExtractALF.exe` into a disposable directory, compares all 81 names, validates
|
||||
The private installed-data integration gate can run a machine-local `bin/BinExtractALF.exe` into a disposable
|
||||
directory, compares all 81 names, validates
|
||||
every range and size, and byte-compares all 81 payloads. `Sys4ScriptProvider` resolves a real append script
|
||||
through `0x01xxxxxx`; direct base-name lookup deliberately does not see append records.
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ Related: `docs/scjump-progression.md` (the SCJUMP decoder that hit this wall), `
|
||||
→ `run_analysis`.
|
||||
- **Load sanity check (AGF-decoder landmark):** at VA `0x474f23`, `CMP word ptr [ESI + 0x4], 0x4d42`
|
||||
(the `BM`/BMP-magic check) confirms the image is correctly based + decoded.
|
||||
- IAT reconstruction — **tried, DOESN'T WORK on this binary (2026-07-09):** `bin/pe-sieve32.exe /pid
|
||||
- IAT reconstruction — **tried, DOESN'T WORK on this binary (2026-07-09):** a local PE-sieve 0.4.1.1 `/pid
|
||||
<PID> /imp 3 /dmode 3 /dir build/pe-sieve` (run from **PowerShell**, not Git Bash — it mangles
|
||||
`/flags`) ran fine but the game is packed with a **zeroed IAT** resolved via `GetProcAddress` at load,
|
||||
so there is no conventional import table to rebuild. Of 363 "imports" it emitted, only ~17 are genuine
|
||||
@@ -46,7 +46,8 @@ Related: `docs/scjump-progression.md` (the SCJUMP decoder that hit this wall), `
|
||||
30×, `in_main:0`, non-terminated). ⇒ **do not graft pe-sieve output** — grafting the noise would inject
|
||||
wrong import names. The game's hot APIs (`ReadFile`/`CreateFileA`/`timeGetTime`/d3d9 device methods) are
|
||||
`GetProcAddress`-resolved into private pointer tables, invisible to a static IAT scan. Report/dump left
|
||||
at `build/pe-sieve/process_<pid>/` (disposable).
|
||||
at `build/pe-sieve/process_<pid>/` (disposable). The executable was removed from the repository and purged
|
||||
from reachable history on 2026-08-03; it is obsolete and is not needed to reproduce the accepted workflow.
|
||||
|
||||
**→ The Frida import-map approach — ✅ DONE 2026-07-09 (replaced pe-sieve).** Named the
|
||||
dynamically-resolved APIs at their call sites via the LIVE process. `tools/frida/map_imports.py`
|
||||
|
||||
@@ -52,10 +52,10 @@ claiming portable exports.
|
||||
| ADV font discovery/raster fidelity | `Age.Engine.Text` remains OS-neutral. On compatible ACP-932 Windows, `auto` selects the separate exact-to-request `Age.Engine.Text.Windows` GDI gray-4 backend. Otherwise Godot TextServer supplies Unicode masks and metrics from ordered system-font substitutions in `godot/config/himegari-text-rendering.json`; this policy is explicitly not GDI pixel-exact. Immediate `0x204`/`0x205`, live ADV, retained History, and the wait atlas all enter ordinary RGBA surfaces/retained handles | Three Mincho/Gothic regular/bold samples byte-match an independent Unicode GDI oracle. A live `SC0000@0x869` trace proves that the `AGE Patch.exe`/`jprun.dll` wrapper overrides authored Mincho to MS Gothic: 13 captured masks match Gothic byte-for-byte and Mincho 0/13. This is wrapper behavior, not the true-native default. Forced exact and forced portable self-tests cover immediate, live, and History rasterization, bounded caching, complete-line materialization, retained ordering, alpha/tint/fade, transforms, clipping, capture, HIDEWIN suspension, partial erase/republication, reset, and teardown. Gameplay `Label` pools and overlay projection are gone | Optionally expose realized-face overrides through future launch profiles when wrapper compatibility is desired; independently produce and smoke-test Linux/macOS exports, confirm installed family availability/fallback selection there, and keep the GDI project out of non-Windows deliverables |
|
||||
| Filesystem semantics | Several filename and containment comparisons use `OrdinalIgnoreCase`; installed assets are conventionally uppercase | Needs validation on case-sensitive filesystems; may hide casing or containment mistakes | Add Linux/macOS tests with mixed-case synthetic roots and use filesystem-appropriate containment rules |
|
||||
| Save/profile/settings storage | `Sys4PersistencePaths` models AGE's independent `SAVEPATH` and `REGFILEPATH` resolutions. Godot replaces Himegari's related profile directory with `user://`, yielding `user://SAVE` for native S3SD/S4SD/S3RT files and thumbnails plus `user://SYS4REG.INI` for the BOM-less CP932 options file; the preserving writer changes only its nine `[sound]` keys | Save payloads and engine options are isolated together without changing either native format. Native/drop-in resolution remains available through `USEAPPDATAFOLDER` plus both SYS4INI paths. A single-root override is rejected when `SAVEPATH` is not beneath `REGFILEPATH`, preventing cross-profile guesses | Expose explicit profile/native selection through the future launcher and allow independent overrides for profiles whose two native paths are unrelated. Validate CP932 availability, replace/flush, case, permissions, and interrupted-write behavior on each export target |
|
||||
| Game-install, runtime metadata, and repository discovery | `GameRootSelection` accepts `--game-root`, then probes the executable directory and current working directory for `SYS4INI.BIN`; on Unix the frontend prefers inherited shell `PWD` because Godot may change the process directory during project startup. Godot injects the selected root into its catalog and loose-first ALF store. `Himegari.csproj` embeds generated `build/opcodes.json`, and the frontend loads that assembly resource rather than `Paths.OpcodesJson`. `Paths.cs` remains a development/test locator; its page-map use is compiled only for `TOOLS` builds | Installed game data and packaged VM metadata no longer depend on the workspace sibling layout. The Linux export passed an explicit absolute game root outside the repository and read the installed archives. Exported automatic page maps use `user://diagnostics/page-maps`; editor/development runs preserve `build/page-map-<SCENE>.jsonl` | Let the future profile launcher pass `--game-root`, and run executable-directory/CWD plus case/permission smoke gates on Linux and macOS |
|
||||
| Archive parity oracle | One integration test launches `bin/BinExtractALF.exe` | Windows-only test helper, not a shipped runtime dependency | Skip/replace on non-Windows CI; runtime ALF/AAI readers do not depend on it |
|
||||
| Native RE tools | Frida/Ghidra helpers target the original `AGE.EXE`; supporting utilities include Windows executables and Windows command conventions | Development/research only | Keep separate from export requirements; document platform prerequisites per tool |
|
||||
| Python workflow | Operating guide uses Windows `py -3.11` invocation | Developer workflow only | Add equivalent `python3` instructions if non-Windows development becomes active |
|
||||
| Game-install, runtime metadata, and repository discovery | `GameRootSelection` accepts `--game-root`, then probes the executable directory and current working directory for `SYS4INI.BIN`; on Unix the frontend prefers inherited shell `PWD` because Godot may change the process directory during project startup. Godot injects the selected root into its catalog and loose-first ALF store. `Himegari.csproj` embeds generated `build/opcodes.json`, and the frontend loads that assembly resource rather than `Paths.OpcodesJson`. Development/test `Paths.cs` finds the repository by tracked `global.json`, solution, and opcode-map markers rather than a checkout-directory name | Installed game data and packaged VM metadata no longer depend on the workspace sibling layout. The Linux export passed an explicit absolute game root outside the repository and read the installed archives; a source-only core proof also passes from an arbitrary repository directory name. Exported automatic page maps use `user://diagnostics/page-maps`; editor/development runs preserve `build/page-map-<SCENE>.jsonl` | Let the future profile launcher pass `--game-root`, and run executable-directory/CWD plus case/permission smoke gates on Linux and macOS |
|
||||
| Archive parity oracle | One integration test can launch a machine-local `bin/BinExtractALF.exe`; the binary and matching DLL are ignored and have unverified redistribution terms | Windows-only installed-data helper, not a shipped runtime dependency | Keep it in an explicit private workspace gate; runtime ALF/AAI readers do not depend on it |
|
||||
| Native RE tools | Frida/Ghidra helpers target the original `AGE.EXE`; PE-sieve was tried and replaced by the Frida import-map workflow | Development/research only; no native RE executable is tracked | Keep separate from export requirements; document platform prerequisites per tool |
|
||||
| Python workflow and core CI | The operating guide uses Windows `py -3.11`; `validate.py` launches child tools through `sys.executable` and has POSIX process-group cleanup plus `ps`-based leak auditing | The 590 engine cases are explicitly split into 502 source-only core cases and 88 `Workspace` installed-data/native-oracle cases; both selections pass with zero skips. The opcode-bootstrap unit injects synthetic observations while production bootstrap retains real-corpus scanning. `.gitea/workflows/core-validation.yml` uses the target server's demonstrated `ubuntu-latest`, checkout/setup-dotnet v4, setup-python v6, and Gitea artifact action baseline. A standalone arbitrary-name, corpus-free repository passes the complete core driver | Confirm the first real Linux run after the rewritten history is pushed. Retain Windows-native GDI parity and private installed-game/runtime checks as separate trusted gates |
|
||||
|
||||
No authored runtime code currently calls native DirectSound, Direct3D, Vulkan, Metal, or OpenGL. Mentions of
|
||||
those APIs in `docs/engine-re.md` describe the original AGE implementation. Both the software-oracle display
|
||||
|
||||
@@ -834,6 +834,27 @@ do not mix mechanical moves with semantic changes.
|
||||
test source links without changing public types, namespaces, behavior, or export configuration. Both solution
|
||||
builds and runtime validation remain green.
|
||||
|
||||
The second bounded build-graph slice moved the unchanged internal movie decoder boundary/runtime, audio
|
||||
alignment policy, movie-surface registry, and RIFF/WAVE sanitizer from `godot/` into `Age.Engine.Frontend`.
|
||||
Five more direct test source links are gone. Explicit friend access is limited to `Himegari` and
|
||||
`Age.Engine.Tests`, so the move preserves the intentionally internal API instead of widening it; FFmpeg remains
|
||||
in Godot while implementing the moved boundary through that friend access. Both solution builds and runtime
|
||||
validation remain green.
|
||||
|
||||
The third bounded build-graph slice moved the unchanged FFmpeg native interop and asynchronous/paced decoder
|
||||
from `godot/` into `Age.Engine.Frontend`. The two remaining movie test source links and the corpus tool's native
|
||||
source link were replaced by project references. Friend access now also names only the `Age.MovieCorpusGate`
|
||||
assembly required by the native frame-source seam; native ABI resolution, decoder pacing, seek/audio behavior,
|
||||
failure handling, and teardown remain unchanged. Engine, Godot, and corpus-gate builds plus runtime validation
|
||||
remain green.
|
||||
|
||||
The fourth bounded build-graph slice moved the unchanged reusable movie-corpus discovery, decode-gate, and
|
||||
report implementation from `tools/movie-corpus-gate/` into `Age.Engine.Frontend`. The tool retains only its
|
||||
command-line entry point and consumes the internal gate through its existing friend/project boundary; tests use
|
||||
the same production implementation through their project reference. The final cross-project production `.cs`
|
||||
source link is gone, completing the planned build-graph ownership cleanup. Engine, Godot, and corpus-gate
|
||||
builds plus runtime validation remain green.
|
||||
|
||||
5. **Close repository-policy and storage gaps.** Add an explicit project license, third-party notices and
|
||||
checksums/provenance for committed binaries, consistent editor/build policy, a pinned .NET SDK, and a
|
||||
CI entry point built on the same validation driver. Remove the empty workspace-root `.git` directory and
|
||||
@@ -841,6 +862,104 @@ do not mix mechanical moves with semantic changes.
|
||||
only after active work is committed. Configure an off-machine remote or equivalent backup before relying
|
||||
on local history as the sole recovery path.
|
||||
|
||||
**Read-only audit (2026-08-03):**
|
||||
|
||||
- **License/provenance:** the repository has no project `LICENSE`, `NOTICE`, third-party notice, or artifact
|
||||
manifest. The three committed binaries are unsigned and have no dedicated artifact records; audit hashes are
|
||||
`BinExtractALF.exe` 0.8
|
||||
(`46167cdf3da1f02733ce1e7f0da99d155e934ede0987ca79c8927f48feb25fe2`), metadata-free `LzssCpp.dll`
|
||||
(`596b9ddb07ca5c29cee609e846f1512654edec56310a90d6c2a6951be1e39fe1`), and PE-sieve 0.4.1.1
|
||||
(`ee684b34b37af24d1c1e9ca80ceeee6878cc80bb24c0c3793840c9acf905bd59`). PE-sieve's upstream is
|
||||
[BSD-2-Clause](https://github.com/hasherezade/pe-sieve/blob/master/LICENSE), but that license is not carried
|
||||
here; the extractor/DLL provenance and redistribution terms remain unverified. The two committed Kelebek
|
||||
source snapshots and transcribed opcode data come from the
|
||||
[upstream repository](https://github.com/Kelebek1/Eushully-Decompiler), where no license file was found, so
|
||||
their redistribution status also remains unresolved. FFmpeg is the good model: Windows/Linux manifests pin
|
||||
provider, release, URL, version, variant, and SHA-256, while build/export scripts stage its LGPL license.
|
||||
NuGet/Godot dependencies are versioned in project files but have no consolidated notice inventory.
|
||||
- **Editor/build/CI policy:** there is no `.editorconfig`, `global.json`, `Directory.Build.*`, package lock,
|
||||
Python environment manifest, or CI configuration. `.gitattributes` only fixes LF for shell scripts and
|
||||
whitespace exceptions for generated references. Projects target `net8.0`; the Godot SDK is pinned to 4.7.0
|
||||
and NuGet versions are explicit. The locally validated tools are .NET SDK 8.0.408 and Python 3.11.4, but only
|
||||
the Python 3.11 invocation is documented. `tools/validate.py --level core` is already the asset-independent
|
||||
CI entry point; workspace/runtime/full levels deliberately require the private game corpus and/or Godot.
|
||||
- **Repository/storage:** `age-reimpl/` is the sole real worktree. The workspace parent contains an empty
|
||||
`.git` directory, and the repository has an empty `.godot/logs` nest plus an accidental ignored
|
||||
`godot/godot/build` nest (3.7 MB). All inspected ignored outputs total 6.43 GiB across 12,190 files: 4.38 GB
|
||||
under `build/`, 1.29 GB under `godot/.godot`, 1.12 GB under historical `godot/build`, and the remainder in
|
||||
.NET/tool caches. The real `.git` holds 6,100 loose objects (71 MiB), no packs and no reported garbage; defer
|
||||
compaction until backup and cleanup decisions are complete.
|
||||
- **Recovery:** `main` is the only ref and has no remote, upstream, additional worktree, or repository/parent
|
||||
bundle. An unrelated off-machine backup cannot be disproved from repository state, but none is configured or
|
||||
evidenced here. Do not publish, delete ignored data, remove the empty parent `.git`, or run Git compaction
|
||||
until the user chooses the corresponding legal, retention, and backup policy.
|
||||
|
||||
**Bounded execution order:** (1) add tracked SDK/editor/line-ending policy without reformatting existing files;
|
||||
(2) add an asset-independent CI workflow that invokes the existing core validation driver; (3) obtain the
|
||||
user's project-license choice and resolve, replace, or exclude every unverified third-party artifact before
|
||||
adding the project license/notice/manifest set; (4) configure a user-selected private/off-machine backup or
|
||||
remote; (5) after explicit retention approval, remove only the verified empty/obsolete ignored nests and any
|
||||
selected regenerable caches; (6) compact the real repository only after a clean committed state and verified
|
||||
backup. Public distribution remains blocked on item 3; destructive cleanup and compaction remain blocked on
|
||||
item 4 plus explicit target approval.
|
||||
|
||||
**Policy baseline (2026-08-03):** item 1 is complete. `global.json` pins the validated .NET 8.0.408 feature
|
||||
band to stable patch releases; `.editorconfig` establishes UTF-8, final-newline, trailing-whitespace, and
|
||||
indentation defaults; and `.gitattributes` classifies repository text, Windows command wrappers, and binary
|
||||
assets. Generator-owned references that already commit CRLF are explicit byte-preserving exceptions with
|
||||
textual diffs, so rebuilding them does not create whole-file normalization churn. No existing source or
|
||||
project file was reformatted or renormalized. Both solution builds and the asset-independent core validation
|
||||
gate remain green under the tracked policy. Item 2, a CI workflow calling that same core gate, is the next
|
||||
safe slice.
|
||||
|
||||
**Linux CI preparation (2026-08-03):** the eight legacy text blobs that prevented a clean Linux checkout or
|
||||
generated-reference comparison are now deliberately normalized to canonical LF: the engine solution, three
|
||||
engine project files, three generated Markdown references, and the generated Himegari opcode module. The
|
||||
generated files are once again ordinary text under the repository EOL policy; their existing whitespace
|
||||
exceptions do not disable normalization. Regeneration through all three owning tools is clean, Windows core
|
||||
validation remains green, and a Linux Git comparison reports no unstaged EOL drift. The first hosted CI job
|
||||
can therefore target Linux rather than carrying forward a Windows-runner dependency.
|
||||
|
||||
**Initial Linux core workflow (2026-08-03, superseded below):** item 2 was first implemented as
|
||||
`.github/workflows/core-validation.yml`. One read-only `ubuntu-24.04` job provisions Python 3.11 and the SDK selected by `global.json`, reports both resolved
|
||||
versions, and invokes the existing asset-independent core driver. Pull requests, `main` pushes, and manual
|
||||
dispatch share the job; branch-local concurrency cancels superseded runs, and seven-day validation-log upload
|
||||
occurs only after failure. The workflow has no cache, secrets, private corpus, Godot runtime, packaging, or
|
||||
deployment access. All four official actions are pinned to full immutable release SHAs. `actionlint` accepts
|
||||
the workflow and the underlying Python 3.11 core gate remains green locally. Because no remote is configured,
|
||||
the tracked workflow is dormant and its first hosted Linux execution remains a publication-time confirmation,
|
||||
not a condition hidden by this commit.
|
||||
|
||||
**Private-repository preflight and binary purge (2026-08-03):** a clean source-only checkout disproved the
|
||||
earlier asset-independent claim: after opcode generation, 501/590 engine cases pass while 89 installed-corpus
|
||||
or native-oracle cases fail, and `test_opcodes.py` still scans the external script corpus. The target Gitea
|
||||
server demonstrates a working `ubuntu-latest` runner with checkout/setup-dotnet v4 and Gitea-specific artifact
|
||||
upload. CI must explicitly separate those workspace cases and inject a synthetic opcode fixture before Actions
|
||||
is enabled; missing private data will not be converted into green skips.
|
||||
|
||||
The unverified `bin/BinExtractALF.exe`, `bin/LzssCpp.dll`, and obsolete `bin/pe-sieve32.exe` are removed from
|
||||
tracking and purged from every reachable commit before the first remote is added. The rewritten tip is
|
||||
`a4f029c`; `git fsck --full --strict` is clean, the three paths have no reachable object or path history, and
|
||||
compaction leaves one 3.00 MiB pack with no loose or garbage objects. The complete pre-rewrite recovery bundle
|
||||
is `../age-reimpl-pre-binary-purge-20260803.bundle` (8,824,649 bytes; SHA-256
|
||||
`E157D7D2404DD2FC6E19FB4B65D08FB9784F415DAB1A2CFF6850121A5F305E5E`) and was verified again after the rewrite.
|
||||
`bin/` now tracks policy only and ignores optional machine-local extractor files; PE-sieve remains documented
|
||||
solely as a failed historical experiment. This resolves the committed-binary portion of item 3, but
|
||||
Kelebek-derived source/data and the user-owned project license choice remain unresolved for wider distribution.
|
||||
|
||||
**Hermetic Gitea core workflow (2026-08-03):** the correction is complete. The 590 engine cases are now
|
||||
explicitly partitioned into 502 source-only cases and 88 `Workspace` installed-data/native-oracle cases;
|
||||
both selections pass with zero skips in the populated workspace. The one accidentally coupled synthetic
|
||||
asset-store test now builds its catalog fixture instead of being excluded. `test_opcodes.py` injects 248
|
||||
synthetic observations derived from the canonical observed-opcode set, while production `--bootstrap` still
|
||||
scans the real corpus by default. Development/test repository discovery uses tracked marker files rather than
|
||||
the checkout directory name.
|
||||
|
||||
The workflow moved to `.gitea/workflows/core-validation.yml` and matches the same-server baseline:
|
||||
`ubuntu-latest`, checkout/setup-dotnet v4, setup-python v6, and Gitea-specific artifact upload. A standalone
|
||||
source-only repository under an arbitrary name passes the complete core driver without sibling game/extracted
|
||||
data. The first actual Linux runner result remains pending until the user adds and pushes the selected remote.
|
||||
|
||||
**Not cleanup targets:** generated `build/` output, the two intentional solution files, historical
|
||||
`docs/superpowers/` plans/specifications, and fidelity-specific complexity that is directly covered by the
|
||||
native ABI. Reorganization is successful when ownership and reproduction become clearer, not when the raw
|
||||
@@ -1187,10 +1306,9 @@ layer's rendering diverges from ADV; save layout.
|
||||
---
|
||||
|
||||
## 8. Immediate next step
|
||||
Begin step 4 of the **codebase consolidation** maintenance slice: make the build graph express source ownership
|
||||
without changing behavior. Move the platform-neutral movie abstractions and runtime support—decoder interfaces,
|
||||
movie/audio runtime state, movie-surface registry, and RIFF sanitization—from `godot/` into
|
||||
`Age.Engine.Frontend` next. Replace their test source links while preserving the intentionally internal API through
|
||||
explicit friend-assembly access; leave FFmpeg interop/decoder ownership for the following slice.
|
||||
Continue step 5 of the **codebase consolidation** maintenance slice by adding and pushing the user-selected
|
||||
private Gitea remote, then confirm the first hosted Linux core result. After that, make the user-owned project-
|
||||
license and Kelebek-derived-material decisions required before wider distribution. Do not infer a license choice
|
||||
or configure/publish a remote without the user's explicit direction.
|
||||
Concrete playthrough blockers may still preempt this bounded maintenance work; the consolidation effort does
|
||||
not replace Phase B gameplay validation or the open cross-platform gates.
|
||||
|
||||
@@ -26,7 +26,7 @@ whenever a tool's inputs/outputs change.**
|
||||
|
||||
| Tool | Purpose | Run | Reads → Writes |
|
||||
|---|---|---|---|
|
||||
| `validate.py` | Layered project validation front door. `core` regenerates/runtime-checks opcode metadata, lints canonical registries, runs pure Python tooling tests and all .NET engine tests, checks generated opcode references, and runs `git diff --check`. `workspace` adds corpus-derived global generation, the real-data Python suites, full SYS4 decode, and Python RECOVER. `runtime` adds the Godot C# build and forced-portable threaded self-test. `full` combines all phases and adds the booted faithful-wait C# scene sweep. Selected prerequisites are strict: an unavailable game/corpus/Godot requirement fails before execution instead of becoming a green skip. Each gate has a timeout and UTF-8 log under `build/validation/validate-<timestamp>/`; Godot receives an isolated validation-owned user-data/log root there so it cannot read or modify the developer's saves/settings. The final table reports results/durations and a before/after Godot-process leak audit. | `validate.py` (defaults to `--level full`) · `--level core|workspace|runtime|full` · `--godot <console>` · `--game-root <install>` · `--verbose` · `--fail-fast` | sources + selected toolchain/game/corpus prerequisites → console summary + ⚙ `build/validation/validate-*/<gate>.log` |
|
||||
| `validate.py` | Layered project validation front door. `core` regenerates/runtime-checks opcode metadata, lints canonical registries, runs pure Python tooling tests and the 502 .NET cases without the `Workspace` trait, checks generated opcode references, and runs `git diff --check`. `workspace` adds the 88 explicitly traited installed-data/native-oracle cases, corpus-derived global generation, real-data Python suites, full SYS4 decode, and Python RECOVER. `runtime` adds the Godot C# build and forced-portable threaded self-test. `full` combines all phases and adds the booted faithful-wait C# scene sweep. Selected prerequisites are strict: an unavailable game/corpus/Godot requirement fails before execution instead of becoming a green skip. Each gate has a timeout and UTF-8 log under `build/validation/validate-<timestamp>/`; Godot receives an isolated validation-owned user-data/log root there so it cannot read or modify the developer's saves/settings. The final table reports results/durations and a before/after Godot-process leak audit. | `validate.py` (defaults to `--level full`) · `--level core|workspace|runtime|full` · `--godot <console>` · `--game-root <install>` · `--verbose` · `--fail-fast` | sources + selected toolchain/game/corpus prerequisites → console summary + ⚙ `build/validation/validate-*/<gate>.log` |
|
||||
| `test_validate.py` | Pure tests for launcher-equivalent explicit/environment/PATH/conventional resolution precedence, invalid-explicit hard failure, level composition, and final gate ordering. | `test_validate.py` | temporary files only |
|
||||
|
||||
Levels are cumulative around `core`: `workspace` means core+workspace-corpus, `runtime` means core+Godot,
|
||||
@@ -35,6 +35,23 @@ by a failed preflight; rebuild each through its owning tool in this reference. R
|
||||
`--godot`, then `AGE_GODOT_CONSOLE`, then `godot4`/`godot`/`godot-mono` on `PATH`. Game-root resolution uses
|
||||
`--game-root`, then `AGE_GAME_ROOT`, then the conventional sibling install and always requires `SYS4INI.BIN`.
|
||||
|
||||
The hosted wrapper is `.gitea/workflows/core-validation.yml`. It matches the target server's demonstrated
|
||||
`ubuntu-latest`, `actions/checkout@v4`, `actions/setup-dotnet@v4`, and
|
||||
`christopherhx/gitea-upload-artifact@v4` baseline, adds Python 3.11, and runs only `--level core`. A standalone
|
||||
source-only repository under an arbitrary directory name passes that exact driver without sibling game or
|
||||
extracted data. Repository discovery uses tracked marker files rather than requiring the checkout directory to
|
||||
be named `age-reimpl`. The workflow has no secrets, private corpus, Godot runtime, cache, packaging, or deployment
|
||||
access; failure logs are retained for seven days. Its first actual Linux/Gitea execution remains pending until
|
||||
the rewritten repository is pushed.
|
||||
|
||||
## Optional local binary tools
|
||||
|
||||
`bin/` is an ignored machine-local tool directory, not a source or artifact distribution directory. A local
|
||||
`BinExtractALF.exe` plus its matching `LzssCpp.dll` may be used by the installed-data archive parity test and to
|
||||
regenerate `../extracted/`; neither is a runtime dependency. Their known local hashes and unverified provenance
|
||||
are recorded in `bin/README.md`. PE-sieve is obsolete and is not retained; its historical experiment remains in
|
||||
`docs/engine-re.md`.
|
||||
|
||||
## Container parse / disassemble
|
||||
|
||||
| Tool | Purpose | Run | Reads → Writes |
|
||||
@@ -51,7 +68,7 @@ All opcode knowledge (ABI, semantics, provenance, `depends_on`) is hand-edited *
|
||||
|---|---|---|---|
|
||||
| `opcodes_build.py` | Generator + linter for the opcode reference. `--bootstrap` appends entries observed in the Himegari corpus; `--bootstrap-age` appends every missing opcode from the pristine 548-entry AGE catalog as an unobserved compatibility stub. Catalog stubs retain Kelebek's ABI label/argument count so other AGE scripts decode past them, but remain `noop_headless=false`: the VM currently traces/skips them, while coverage continues to report them as unresolved rather than semantically safe. | `--build` · `--lint` · `--bootstrap` · `--bootstrap-age` | `vm-map/opcodes.toml` → ⚙ `tools/age_opcodes_himegari.py`, ⚙ `build/opcodes.json`, ⚙ `docs/opcode-reference.md`, ⚙ `build/opcode-coverage.md` |
|
||||
| `opcodes_model.py` | In-memory model + loader + linter (dangling-ref / confidence-ceiling / vocabulary / dependents). | *Imported by `opcodes_build.py`.* | `vm-map/opcodes.toml` → — |
|
||||
| `test_opcodes.py` | Unit tests for the opcode tooling. | `test_opcodes.py` | — |
|
||||
| `test_opcodes.py` | Unit tests for the opcode tooling. Bootstrap coverage injects 248 synthetic observations derived from the canonical observed-opcode set; it does not read the private script corpus. Production `--bootstrap` still scans the real corpus by default. | `test_opcodes.py` | `vm-map/opcodes.toml` → temporary files only |
|
||||
| `opcode_context.py` | Read-only evidence gatherer for classifying unnamed opcodes (frequency, argc, operand-type signature, neighbours, disasm snippets, Kelebek comment). | `--top 20` · `opcode_context.py 0x1f4 0x71 …` | corpus → stdout |
|
||||
| `validate_opcode_table.py` | Definitive decode-coverage validator (replicates Kelebek's `data_array_end` code/data split). | `validate_opcode_table.py` | corpus → stdout |
|
||||
| `validate_opcode_table_naive.py` | Naïve variant of the above (baseline comparison). | `validate_opcode_table_naive.py` | corpus → stdout |
|
||||
|
||||
5
engine/Age.Engine.Frontend/Properties/AssemblyInfo.cs
Normal file
5
engine/Age.Engine.Frontend/Properties/AssemblyInfo.cs
Normal file
@@ -0,0 +1,5 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
[assembly: InternalsVisibleTo("Age.Engine.Tests")]
|
||||
[assembly: InternalsVisibleTo("Age.MovieCorpusGate")]
|
||||
[assembly: InternalsVisibleTo("Himegari")]
|
||||
@@ -156,6 +156,7 @@ public class AdvTextHistoryTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Workspace")]
|
||||
public void RealSc0000FirstPagePopulatesTheRetainedBacklogBeforeItsWait()
|
||||
{
|
||||
var scripts = Sys4ScriptProvider.Load(Table);
|
||||
|
||||
@@ -259,6 +259,7 @@ public class AdvTextOpsTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Workspace")]
|
||||
public void RealMamesResearchDescriptionUsesImmediateRetainedTextPath()
|
||||
{
|
||||
var table = OpcodeTableJson.Load(Paths.OpcodesJson);
|
||||
@@ -310,6 +311,7 @@ public class AdvTextOpsTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Workspace")]
|
||||
public void System4BootstrapReplaysAllResetCursorAndBoundsConfigurations()
|
||||
{
|
||||
var table = OpcodeTableJson.Load(Paths.OpcodesJson);
|
||||
|
||||
@@ -24,14 +24,6 @@
|
||||
<ProjectReference Include="..\Age.Engine\Age.Engine.csproj" />
|
||||
<ProjectReference Include="..\Age.Engine.Frontend\Age.Engine.Frontend.csproj" />
|
||||
<ProjectReference Include="..\Age.Engine.Text.Windows\Age.Engine.Text.Windows.csproj" />
|
||||
<Compile Include="..\..\godot\IMovieDecoder.cs" Link="IMovieDecoder.cs" />
|
||||
<Compile Include="..\..\godot\MovieRuntime.cs" Link="MovieRuntime.cs" />
|
||||
<Compile Include="..\..\godot\MovieAudioTimeline.cs" Link="MovieAudioTimeline.cs" />
|
||||
<Compile Include="..\..\godot\FfmpegMovieNative.cs" Link="FfmpegMovieNative.cs" />
|
||||
<Compile Include="..\..\godot\FfmpegMovieDecoder.cs" Link="FfmpegMovieDecoder.cs" />
|
||||
<Compile Include="..\..\godot\MovieSurfaceRegistry.cs" Link="MovieSurfaceRegistry.cs" />
|
||||
<Compile Include="..\..\godot\RiffWaveSanitizer.cs" Link="RiffWaveSanitizer.cs" />
|
||||
<Compile Include="..\..\tools\movie-corpus-gate\MovieCorpusGate.cs" Link="MovieCorpusGate.cs" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -71,6 +71,7 @@ public class AgfDecoderTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Workspace")]
|
||||
public void InstalledSo001HasExpectedAlphaBearingDimensions()
|
||||
{
|
||||
var catalog = Sys4AssetCatalog.Load(Paths.Sys4Ini);
|
||||
@@ -87,6 +88,7 @@ public class AgfDecoderTests
|
||||
[InlineData(0x32db, "SO007.AGF")]
|
||||
[InlineData(0x32dc, "SO008A.AGF")]
|
||||
[InlineData(0x32dd, "SO007A.AGF")]
|
||||
[Trait("Category", "Workspace")]
|
||||
public void InstalledFieldMapSheetsResolveAndDecodeByRawCatalogIndex(int rawId, string name)
|
||||
{
|
||||
var resources = ResourceMap.Load();
|
||||
|
||||
@@ -160,6 +160,7 @@ public class AudioMixerOpcodeTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Workspace")]
|
||||
public void NativePersistencePathsComeFromIndependentSys4IniProfileValues()
|
||||
{
|
||||
Sys4AssetCatalog catalog = Sys4AssetCatalog.Load(Paths.Sys4Ini);
|
||||
@@ -177,6 +178,7 @@ public class AudioMixerOpcodeTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Workspace")]
|
||||
public void ProfileOverrideRedirectsSaveAndSettingsAsOneNativeLayout()
|
||||
{
|
||||
Sys4AssetCatalog catalog = Sys4AssetCatalog.Load(Paths.Sys4Ini);
|
||||
|
||||
@@ -3,6 +3,7 @@ using Age.Engine.Sys4;
|
||||
using Age.Engine.Vm;
|
||||
using Xunit;
|
||||
|
||||
[Trait("Category", "Workspace")]
|
||||
public class CallScriptIntegrationTests
|
||||
{
|
||||
private sealed class NullHost : IHost
|
||||
|
||||
@@ -97,6 +97,7 @@ public class CoroutineHostModelTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Workspace")]
|
||||
public void Sc0000EntryRunsSetupAndFillsDistinctTextureSlots()
|
||||
{
|
||||
var script = Sys4Loader.Load(Paths.Scripts()["SC0000.BIN"], Table);
|
||||
|
||||
@@ -6,6 +6,7 @@ using Xunit;
|
||||
public class CurDecoderTests
|
||||
{
|
||||
[Fact]
|
||||
[Trait("Category", "Workspace")]
|
||||
public void HimegariCursor_DecodesPixelsAndHotspot()
|
||||
{
|
||||
var resources = ResourceMap.Load();
|
||||
@@ -24,6 +25,7 @@ public class CurDecoderTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Workspace")]
|
||||
public void HimegariPanCursor_DecodesFourBitPixelsAndHotspot()
|
||||
{
|
||||
var resources = ResourceMap.Load();
|
||||
|
||||
@@ -22,6 +22,7 @@ public class FrameYieldTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Workspace")]
|
||||
public void FrameYield_CalledOncePerStep()
|
||||
{
|
||||
var table = OpcodeTableJson.Load(Paths.OpcodesJson);
|
||||
|
||||
@@ -61,6 +61,7 @@ public class GameSessionTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Workspace")]
|
||||
public void SC0000ViaSessionMatchesSingleRun()
|
||||
{
|
||||
var script = Sys4Loader.Load(Paths.Scripts()["SC0000.BIN"], Table);
|
||||
@@ -77,6 +78,7 @@ public class GameSessionTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Workspace")]
|
||||
public void BootingSkinitPopulatesDataTableGlobals()
|
||||
{
|
||||
// Running the SKINIT data script through the session populates the real skill table into the
|
||||
@@ -103,6 +105,7 @@ public class GameSessionTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Workspace")]
|
||||
public void BootedStateSurvivesSnapshot()
|
||||
{
|
||||
var s = new GameSession();
|
||||
@@ -115,6 +118,7 @@ public class GameSessionTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Workspace")]
|
||||
public void SeedingFormFlagChangesBehavior()
|
||||
{
|
||||
// Lily's lines are gated on form flags G[0xa57/8/9]; unseeded => all skipped (0 voices on her lines).
|
||||
|
||||
@@ -62,6 +62,7 @@ public class HistoryDataOpsTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Workspace")]
|
||||
public void LoaderRetainsRealHistoryFooterArraysForRuntimeCopy()
|
||||
{
|
||||
var history = Sys4Loader.Load(Paths.Scripts()["HISTORY.BIN"], Table);
|
||||
|
||||
@@ -179,6 +179,7 @@ public class HistoryInteractionOpsTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Workspace")]
|
||||
public void RealHistoryWheelUpNavigatesToOlderRetainedRows()
|
||||
{
|
||||
var scripts = Sys4ScriptProvider.Load(Table);
|
||||
@@ -196,6 +197,7 @@ public class HistoryInteractionOpsTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Workspace")]
|
||||
public void RealHistoryRendersMultipleRowsAfterSeveralSc0000Messages()
|
||||
{
|
||||
var scripts = Sys4ScriptProvider.Load(Table);
|
||||
@@ -282,6 +284,7 @@ public class HistoryInteractionOpsTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Workspace")]
|
||||
public void RealSc0000HistoryButtonRendersAndClosesWithoutAdvancingThePageWait()
|
||||
{
|
||||
var scripts = Sys4ScriptProvider.Load(Table);
|
||||
@@ -323,6 +326,7 @@ public class HistoryInteractionOpsTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Workspace")]
|
||||
public void RealHistoryVoicedRowDispatchesItsRetainedVoicePair()
|
||||
{
|
||||
var scripts = Sys4ScriptProvider.Load(Table);
|
||||
|
||||
@@ -109,6 +109,7 @@ public class HistoryPresentationOpsTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Workspace")]
|
||||
public void RealHistoryScriptBuildsVisibleRowsFromARealSc0000Page()
|
||||
{
|
||||
var scripts = Sys4ScriptProvider.Load(Table);
|
||||
|
||||
@@ -255,6 +255,7 @@ public class HotspotInputTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Workspace")]
|
||||
public void Sc0000AdvChromeBootstrap_VisitsAllFiveVisibleButtonRegistrations()
|
||||
{
|
||||
var table = OpcodeTableJson.Load(Paths.OpcodesJson);
|
||||
@@ -274,6 +275,7 @@ public class HotspotInputTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Workspace")]
|
||||
public void Sc0000FirstWait_DispatchesRealHistoryHoverCallback()
|
||||
{
|
||||
var table = OpcodeTableJson.Load(Paths.OpcodesJson);
|
||||
@@ -436,6 +438,7 @@ public class HotspotInputTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Workspace")]
|
||||
public void Sc0000MessageSkipButton_EnablesPersistentServiceState()
|
||||
{
|
||||
var table = OpcodeTableJson.Load(Paths.OpcodesJson);
|
||||
@@ -471,6 +474,7 @@ public class HotspotInputTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Workspace")]
|
||||
public void Sc0000HideWindowButton_RunsRealHidewinAndReturnsToAdvWait()
|
||||
{
|
||||
var table = OpcodeTableJson.Load(Paths.OpcodesJson);
|
||||
@@ -497,6 +501,7 @@ public class HotspotInputTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Workspace")]
|
||||
public void Sc0000ActionSeven_EntersRealMenuAndRestoresParentAdvHotspots()
|
||||
{
|
||||
var table = OpcodeTableJson.Load(Paths.OpcodesJson);
|
||||
|
||||
@@ -48,6 +48,7 @@ public class InputBindingTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Workspace")]
|
||||
public void System4BootstrapReplaysAllSixteenInputConfigurationCalls()
|
||||
{
|
||||
var scripts = Sys4ScriptProvider.Load(Table);
|
||||
@@ -72,6 +73,7 @@ public class InputBindingTests
|
||||
[InlineData(0x08)] // retained native Backspace default
|
||||
[InlineData(0x11)] // SYSTEM4 LeftCtrl binding
|
||||
[InlineData(0x43)] // SYSTEM4 C binding
|
||||
[Trait("Category", "Workspace")]
|
||||
public void LogicalActionSixDrivesHeldAdvFastForward(int virtualKey)
|
||||
{
|
||||
var script = ScriptAssembler.Assemble(Table, "ADV_FAST_FORWARD", new List<(int, Operand[])>
|
||||
@@ -99,6 +101,7 @@ public class InputBindingTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Workspace")]
|
||||
public void HeldLogicalActionSixSurvivesPersistentSkipLifecycleSuspension()
|
||||
{
|
||||
var script = ScriptAssembler.Assemble(Table, "HELD_SKIP_LIFECYCLE", new List<(int, Operand[])>
|
||||
@@ -122,6 +125,7 @@ public class InputBindingTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Workspace")]
|
||||
public void ReleasingPhysicalFastForwardDoesNotClearPersistentSkip()
|
||||
{
|
||||
var script = ScriptAssembler.Assemble(Table, "ADV_PERSISTENT_AND_HELD_SKIP", new List<(int, Operand[])>
|
||||
|
||||
@@ -87,6 +87,7 @@ public class IntegerQueueOpsTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Workspace")]
|
||||
public void RegisteredRealMvseek_ExpandsMovementCostsThroughTheSystem4ServiceAbi()
|
||||
{
|
||||
var table = OpcodeTableJson.Load(Paths.OpcodesJson);
|
||||
@@ -111,6 +112,7 @@ public class IntegerQueueOpsTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Workspace")]
|
||||
public void RealMvseek_ExpandsMovementCostsBeyondTheOrigin()
|
||||
{
|
||||
var vm = RealSearchVm("MVSEEK.BIN");
|
||||
@@ -126,6 +128,7 @@ public class IntegerQueueOpsTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Workspace")]
|
||||
public void RealAtseek_ExpandsAttackDistancesBeyondTheOrigin()
|
||||
{
|
||||
var vm = RealSearchVm("ATSEEK.BIN");
|
||||
|
||||
@@ -554,6 +554,7 @@ public class MovieOpcodeTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Workspace")]
|
||||
public void Sc0000ResumesImmediatelyAfterMovieOpcodeAtBytecodeOffset13d1()
|
||||
{
|
||||
var table = OpcodeTableJson.Load(Paths.OpcodesJson);
|
||||
@@ -756,6 +757,7 @@ public class MovieOpcodeTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Workspace")]
|
||||
public void Sc0000MoviePayloadReadsFromArchiveVfsAndIsMpegProgramStream()
|
||||
{
|
||||
var catalog = Sys4AssetCatalog.Load(Paths.Sys4Ini);
|
||||
@@ -771,6 +773,7 @@ public class MovieOpcodeTests
|
||||
[Theory]
|
||||
[InlineData(0x335f, "LOGO.AGF")]
|
||||
[InlineData(0x3364, "OP.AGF")]
|
||||
[Trait("Category", "Workspace")]
|
||||
public void ModalMoviePayloadResolvesFromUniversalPackedCatalog(int resourceId, string expectedName)
|
||||
{
|
||||
var catalog = Sys4AssetCatalog.Load(Paths.Sys4Ini);
|
||||
@@ -783,6 +786,7 @@ public class MovieOpcodeTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Workspace")]
|
||||
public void DebugTestMovieDecodesToExactGreenMaskDimensions()
|
||||
{
|
||||
if (!OperatingSystem.IsWindows()) return;
|
||||
@@ -807,6 +811,7 @@ public class MovieOpcodeTests
|
||||
[Theory]
|
||||
[InlineData(0x335f, "LOGO.AGF")]
|
||||
[InlineData(0x3364, "OP.AGF")]
|
||||
[Trait("Category", "Workspace")]
|
||||
public void ModalMovieOpeningFramesAreOpaqueAndHaveContinuousCadence(
|
||||
int resourceId, string expectedName)
|
||||
{
|
||||
@@ -841,6 +846,7 @@ public class MovieOpcodeTests
|
||||
[InlineData(0x2b94, "MVB238.AGF", 280, 352, 866)]
|
||||
[InlineData(0x2bc2, "MVB908.AGF", 400, 400, 333)]
|
||||
[InlineData(0x33, "CHAPTER.AGF", 800, 600, 12016)]
|
||||
[Trait("Category", "Workspace")]
|
||||
public void FfmpegShimDecodesRepresentativeVfsMovie(int resourceId, string expectedName,
|
||||
int expectedWidth, int expectedHeight,
|
||||
long expectedStopTimeMs)
|
||||
@@ -879,6 +885,7 @@ public class MovieOpcodeTests
|
||||
[InlineData(0x33, "CHAPTER.AGF")]
|
||||
[InlineData(0x2bf1, "MVS001.AGF")]
|
||||
[InlineData(0x335f, "LOGO.AGF")]
|
||||
[Trait("Category", "Workspace")]
|
||||
public void FfmpegShimDecodesRepresentativeMpegAudio(int resourceId, string expectedName)
|
||||
{
|
||||
if (!OperatingSystem.IsWindows()) return;
|
||||
@@ -912,6 +919,7 @@ public class MovieOpcodeTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Workspace")]
|
||||
public void FfmpegShimSeeksBothVideoAndAudioNearRequestedPosition()
|
||||
{
|
||||
if (!OperatingSystem.IsWindows()) return;
|
||||
@@ -943,6 +951,7 @@ public class MovieOpcodeTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Workspace")]
|
||||
public void FfmpegShimRejectsTruncatedMovieWithBoundedDiagnostic()
|
||||
{
|
||||
if (!OperatingSystem.IsWindows()) return;
|
||||
@@ -956,6 +965,7 @@ public class MovieOpcodeTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Workspace")]
|
||||
public void FfmpegShimSupportsRepeatedOpenAndClose()
|
||||
{
|
||||
if (!OperatingSystem.IsWindows()) return;
|
||||
@@ -974,6 +984,7 @@ public class MovieOpcodeTests
|
||||
[Theory]
|
||||
[InlineData(0x2be3, 280, 500, 450)]
|
||||
[InlineData(0x2bc2, 400, 333, 300)]
|
||||
[Trait("Category", "Workspace")]
|
||||
public void FfmpegPacedDecoderKeepsRealMovieAliveThroughItsStopTime(
|
||||
int resourceId, int expectedWidth, long expectedStopTimeMs, long minimumElapsedMs)
|
||||
{
|
||||
|
||||
@@ -4,6 +4,7 @@ using Age.Engine.Sys4;
|
||||
using Age.Engine.Vm;
|
||||
using Xunit;
|
||||
|
||||
[Trait("Category", "Workspace")]
|
||||
public class NaturalBootIntegrationTests
|
||||
{
|
||||
private sealed class ReachedSc0000Exception : Exception { }
|
||||
|
||||
@@ -64,6 +64,7 @@ public class ReadTextDatabaseTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Workspace")]
|
||||
public void InstalledNativeRtDatRoundTripsItsLogicalRecordsWhenPresent()
|
||||
{
|
||||
string path = Path.Combine(
|
||||
|
||||
@@ -9,6 +9,7 @@ public class RecoverTests
|
||||
private static long G(VirtualMachine vm, int k) => vm.Globals.TryGetValue(k, out var v) ? v : 0;
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Workspace")]
|
||||
public void RecoverUnitTestPasses()
|
||||
{
|
||||
var table = OpcodeTableJson.Load(Paths.OpcodesJson);
|
||||
|
||||
@@ -48,6 +48,7 @@ public class RiffWaveSanitizerTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Workspace")]
|
||||
public void InstalledSc0000GlowSoundDropsCp932InfoBlockWithoutChangingPcmData()
|
||||
{
|
||||
var resources = ResourceMap.Load();
|
||||
@@ -63,6 +64,7 @@ public class RiffWaveSanitizerTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Workspace")]
|
||||
public void InstalledFirstBossSoundStopsAtFirstDeclaredRiff()
|
||||
{
|
||||
var resources = ResourceMap.Load();
|
||||
|
||||
@@ -85,6 +85,7 @@ public class RootReloadTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Workspace")]
|
||||
public void HimegariRawScriptZeroIsSystem4()
|
||||
{
|
||||
var table = OpcodeTableJson.Load(Paths.OpcodesJson);
|
||||
|
||||
@@ -5,6 +5,7 @@ using Age.Engine.Persistence;
|
||||
using Age.Engine.Sys4;
|
||||
using Age.Engine.Vm;
|
||||
|
||||
[Trait("Category", "Workspace")]
|
||||
public class SaveUiIntegrationTests
|
||||
{
|
||||
private sealed class MenuReadyException : Exception;
|
||||
|
||||
@@ -265,6 +265,7 @@ public class SharedProfileTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Workspace")]
|
||||
public void InstalledSharedProfileUnlocksKnownCgAndHSceneResourcesWhenPresent()
|
||||
{
|
||||
string root = Path.Combine(
|
||||
|
||||
@@ -8,6 +8,7 @@ using Xunit;
|
||||
public class Sys4AssetStoreTests
|
||||
{
|
||||
[Fact]
|
||||
[Trait("Category", "Workspace")]
|
||||
public void InstalledAppendCatalogHasNativePackSelectionAndStableDirectory()
|
||||
{
|
||||
var catalog = Sys4AssetCatalog.Load(Paths.Sys4Ini);
|
||||
@@ -40,6 +41,7 @@ public class Sys4AssetStoreTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Workspace")]
|
||||
public void CompletePackedAssetEnumerationPreservesBaseThenAppendOrder()
|
||||
{
|
||||
var catalog = Sys4AssetCatalog.Load(Paths.Sys4Ini);
|
||||
@@ -57,6 +59,7 @@ public class Sys4AssetStoreTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Workspace")]
|
||||
public void CompleteAppendDirectoryAndPayloadsMatchBinExtractAlf()
|
||||
{
|
||||
string temp = Path.Combine(Path.GetTempPath(), "age-vfsb-oracle-" + Guid.NewGuid().ToString("N"));
|
||||
@@ -105,6 +108,7 @@ public class Sys4AssetStoreTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Workspace")]
|
||||
public void RuntimeCatalogMatchesDiagnosticCatalogAndSceneViews()
|
||||
{
|
||||
var catalog = Sys4AssetCatalog.Load(Paths.Sys4Ini);
|
||||
@@ -154,6 +158,7 @@ public class Sys4AssetStoreTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Workspace")]
|
||||
public void EveryCatalogRangeFitsAndRepresentativePayloadsMatchExtractedData()
|
||||
{
|
||||
var catalog = Sys4AssetCatalog.Load(Paths.Sys4Ini);
|
||||
@@ -186,6 +191,7 @@ public class Sys4AssetStoreTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Workspace")]
|
||||
public void Sc0000RoomAndTitleAudioPayloadsReadDirectlyFromTheirNativeAddressSpaces()
|
||||
{
|
||||
var catalog = Sys4AssetCatalog.Load(Paths.Sys4Ini);
|
||||
@@ -222,6 +228,7 @@ public class Sys4AssetStoreTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Workspace")]
|
||||
public void Sc0010LowOperandsAreAlreadyUniversalPackedIds()
|
||||
{
|
||||
var resources = ResourceMap.Load();
|
||||
@@ -236,6 +243,7 @@ public class Sys4AssetStoreTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Workspace")]
|
||||
public void TypedResourceResolutionPreservesAppendPackSelector()
|
||||
{
|
||||
var catalog = Sys4AssetCatalog.Load(Paths.Sys4Ini);
|
||||
@@ -249,6 +257,7 @@ public class Sys4AssetStoreTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Workspace")]
|
||||
public void AllInstalledLooseScriptOverridesShadowArchiveCopies()
|
||||
{
|
||||
var catalog = Sys4AssetCatalog.Load(Paths.Sys4Ini);
|
||||
@@ -281,7 +290,7 @@ public class Sys4AssetStoreTests
|
||||
try
|
||||
{
|
||||
File.WriteAllBytes(Path.Combine(archives, "DATA1.ALF"), new byte[] { 9, 8, 1, 2, 3, 7 });
|
||||
var catalog = Sys4AssetCatalog.Load(Paths.Sys4Ini);
|
||||
var catalog = Sys4AssetCatalog.Parse(Sys4StartupSettingsTests.BuildCatalog(includeTrailer: true));
|
||||
var entry = new AssetEntry("TEST.BIN", "DATA1.ALF", 2, 3);
|
||||
var store = new Sys4AssetStore(catalog, archives, loose);
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ using Xunit;
|
||||
public class Sys4LoaderTests
|
||||
{
|
||||
[Fact]
|
||||
[Trait("Category", "Workspace")]
|
||||
public void ParsesMenuBinLikeSys4load()
|
||||
{
|
||||
var table = OpcodeTableJson.Load(Paths.OpcodesJson);
|
||||
|
||||
@@ -2,6 +2,7 @@ using Age.Engine.Sys4;
|
||||
using Age.Engine.Diagnostics;
|
||||
using Xunit;
|
||||
|
||||
[Trait("Category", "Workspace")]
|
||||
public class Sys4ScriptProviderTests
|
||||
{
|
||||
[Fact]
|
||||
|
||||
@@ -5,6 +5,7 @@ using Age.Engine.Sys4;
|
||||
public class Sys4StartupSettingsTests
|
||||
{
|
||||
[Fact]
|
||||
[Trait("Category", "Workspace")]
|
||||
public void InstalledCatalogExposesOrderedStartupSettingsAndLogicalCanvas()
|
||||
{
|
||||
var catalog = Sys4AssetCatalog.Load(Paths.Sys4Ini);
|
||||
@@ -100,7 +101,7 @@ public class Sys4StartupSettingsTests
|
||||
params (string Key, string Value)[] pairs)
|
||||
=> WrapCatalog(BuildExpanded(pairs));
|
||||
|
||||
private static byte[] BuildCatalog(bool includeTrailer)
|
||||
internal static byte[] BuildCatalog(bool includeTrailer)
|
||||
=> WrapCatalog(BuildExpanded(Array.Empty<(string, string)>(), includeTrailer));
|
||||
|
||||
private static byte[] BuildExpanded(
|
||||
|
||||
@@ -24,6 +24,7 @@ public class TextureOpsTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Workspace")]
|
||||
public void SC0000FiresTextureOpsWithAssignedFullScreenSlot()
|
||||
{
|
||||
var table = OpcodeTableJson.Load(Paths.OpcodesJson);
|
||||
|
||||
@@ -19,10 +19,16 @@ public static class Paths
|
||||
private static string FindRepo()
|
||||
{
|
||||
var d = new DirectoryInfo(AppContext.BaseDirectory);
|
||||
while (d != null && d.Name != "age-reimpl") d = d.Parent;
|
||||
if (d == null) throw new DirectoryNotFoundException(
|
||||
"age-reimpl root not found above " + AppContext.BaseDirectory);
|
||||
while (d != null)
|
||||
{
|
||||
if (File.Exists(Path.Combine(d.FullName, "global.json"))
|
||||
&& File.Exists(Path.Combine(d.FullName, "engine", "AgeEngine.sln"))
|
||||
&& File.Exists(Path.Combine(d.FullName, "vm-map", "opcodes.toml")))
|
||||
return d.FullName;
|
||||
d = d.Parent;
|
||||
}
|
||||
throw new DirectoryNotFoundException(
|
||||
"repository root not found above " + AppContext.BaseDirectory);
|
||||
}
|
||||
|
||||
/// name(UPPER).BIN -> path; game-dir loose overrides shadow extracted/DATA1 (mirrors paths.scripts()).
|
||||
|
||||
7
global.json
Normal file
7
global.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"sdk": {
|
||||
"version": "8.0.408",
|
||||
"rollForward": "latestPatch",
|
||||
"allowPrerelease": false
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,6 @@
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\engine\Age.Engine\Age.Engine.csproj" />
|
||||
<Compile Include="..\..\godot\FfmpegMovieNative.cs" Link="FfmpegMovieNative.cs" />
|
||||
<ProjectReference Include="..\..\engine\Age.Engine.Frontend\Age.Engine.Frontend.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
@@ -100,8 +100,9 @@ def skeleton_toml(op: int, label: str, argc: int, argtypes_for_op: dict,
|
||||
f"observed_types = [{obs}]"]
|
||||
return "\n".join(lines) + "\n"
|
||||
|
||||
def bootstrap(toml_path: Path) -> None:
|
||||
used, argtypes = scan_corpus()
|
||||
def bootstrap(toml_path: Path, *, corpus_scan=None) -> None:
|
||||
"""Append observed skeletons from a real scan or an injected synthetic scan fixture."""
|
||||
used, argtypes = corpus_scan if corpus_scan is not None else scan_corpus()
|
||||
present = set(M.load(toml_path).opcodes) if toml_path.exists() else set()
|
||||
blocks = []
|
||||
for op in sorted(used):
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Standalone tests for the opcode reference tooling. Run: py -3.11 -X utf8 tools/test_opcodes.py"""
|
||||
import os, sys, tempfile
|
||||
from collections import Counter
|
||||
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||
import opcodes_model as M
|
||||
|
||||
@@ -120,14 +121,21 @@ def test_bootstrap():
|
||||
import opcodes_build as B
|
||||
from age_opcodes import OPCODES
|
||||
from pathlib import Path
|
||||
canonical = M.load(Path(__file__).resolve().parents[1] / "vm-map" / "opcodes.toml")
|
||||
observed = sorted(op for op, entry in canonical.opcodes.items() if entry.observed_in_himegari)
|
||||
synthetic_scan = (
|
||||
Counter({op: 1 for op in observed}),
|
||||
{op: {i: {0} for i in range(OPCODES[op][1])} for op in observed},
|
||||
)
|
||||
fd, p = tempfile.mkstemp(suffix=".toml"); os.close(fd); os.remove(p)
|
||||
tp = Path(p)
|
||||
B.bootstrap(tp) # first run: meta + all skeletons
|
||||
B.bootstrap(tp, corpus_scan=synthetic_scan) # first run: meta + synthetic observed skeletons
|
||||
m = M.load(tp)
|
||||
check(len(m.opcodes) >= 240, f"bootstrap seeded ~248 opcodes (got {len(m.opcodes)})")
|
||||
check(len(m.opcodes) == len(observed),
|
||||
f"bootstrap seeded all {len(observed)} synthetic observations (got {len(m.opcodes)})")
|
||||
check(0x90 in m.opcodes and m.opcodes[0x90].argc == 7, "0x90 seeded with argc 7")
|
||||
n1 = len(m.opcodes)
|
||||
B.bootstrap(tp) # idempotent: appends nothing new
|
||||
B.bootstrap(tp, corpus_scan=synthetic_scan) # idempotent: appends nothing new
|
||||
check(len(M.load(tp).opcodes) == n1, "second bootstrap adds no duplicates")
|
||||
e, w = M.lint(m)
|
||||
check(e == [], f"bootstrapped file lints clean (errors: {e[:3]})")
|
||||
|
||||
@@ -75,13 +75,21 @@ class GatePlanTests(unittest.TestCase):
|
||||
self.assertNotIn("sys4-corpus-validate", keys)
|
||||
self.assertNotIn("godot-selftest", keys)
|
||||
self.assertNotIn("age-cli-sweep", keys)
|
||||
engine = next(gate for gate in validate.build_gate_plan("core") if gate.key == "engine-tests")
|
||||
self.assertIn("Category!=Workspace", engine.command)
|
||||
|
||||
def test_workspace_extends_core(self) -> None:
|
||||
core = {gate.key for gate in validate.build_gate_plan("core")}
|
||||
workspace = {gate.key for gate in validate.build_gate_plan("workspace")}
|
||||
self.assertLess(core, workspace)
|
||||
self.assertIn("sys4-corpus-validate", workspace)
|
||||
self.assertIn("engine-workspace-tests", workspace)
|
||||
self.assertNotIn("godot-selftest", workspace)
|
||||
installed = next(
|
||||
gate for gate in validate.build_gate_plan("workspace")
|
||||
if gate.key == "engine-workspace-tests"
|
||||
)
|
||||
self.assertIn("Category=Workspace", installed.command)
|
||||
|
||||
def test_runtime_requires_resolved_paths(self) -> None:
|
||||
with self.assertRaisesRegex(ValueError, "resolved Godot"):
|
||||
|
||||
@@ -152,16 +152,27 @@ def build_gate_plan(
|
||||
gates.append(
|
||||
Gate(
|
||||
"engine-tests",
|
||||
".NET engine tests",
|
||||
".NET engine core tests",
|
||||
(
|
||||
"dotnet", "test", "engine/AgeEngine.sln", "--nologo", "--verbosity", "minimal",
|
||||
"-p:UseSharedCompilation=false",
|
||||
"--filter", "Category!=Workspace", "-p:UseSharedCompilation=false",
|
||||
),
|
||||
300,
|
||||
)
|
||||
)
|
||||
|
||||
if "workspace" in phases:
|
||||
gates.append(
|
||||
Gate(
|
||||
"engine-workspace-tests",
|
||||
".NET installed-data and native-oracle tests",
|
||||
(
|
||||
"dotnet", "test", "engine/AgeEngine.sln", "--nologo", "--verbosity", "minimal",
|
||||
"--filter", "Category=Workspace", "-p:UseSharedCompilation=false",
|
||||
),
|
||||
300,
|
||||
)
|
||||
)
|
||||
gates.append(
|
||||
Gate("globals-build", "Global registry build", _python_tool("globals_build.py", "--build"))
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user