From 0a8665a9c8005f3edf2786ebd85bac311f1cd0d5 Mon Sep 17 00:00:00 2001 From: EHotwagner Date: Sat, 10 Oct 2026 00:00:51 +0200 Subject: [PATCH 1/2] Prepare explicit public package-only portal presentation consumer --- docs/roadmaps/game-area-portals.md | 19 +++++++++ .../PresentationConsumer.fsproj | 22 ++++++++++ tests/release/Box2D.PackageConsumer/README.md | 26 ++++++++++++ .../verify-package-boundary.py | 33 +++++++++++++-- tests/release/test-box2d-package-consumer.sh | 40 ++++++++++++++++++- 5 files changed, 135 insertions(+), 5 deletions(-) create mode 100644 tests/release/Box2D.PackageConsumer/PresentationConsumer.fsproj diff --git a/docs/roadmaps/game-area-portals.md b/docs/roadmaps/game-area-portals.md index 75e55b46..1602f9b0 100644 --- a/docs/roadmaps/game-area-portals.md +++ b/docs/roadmaps/game-area-portals.md @@ -224,6 +224,25 @@ producer `0.17.0` acceptance and the retained resume04 failure/resume05 unrun reservation are unchanged; this source attempt does not retry those operations or transfer their custody. +### P3 public presentation consumer source preparation — 2026-10-09 + +The existing Box2D package-consumer runner has an explicit public `--presentation` +route and a separate exact-PackageReference project. It copies the delivered portal +presentation and actual command-line modules into a fresh outside-checkout receiver, +checks source/project inputs before restore and the expected seven-package rendering +closure before build, then requires unchanged locked restore and both entrypoint modes. +The original four-package physics consumer remains a separate no-presentation route. + +This is source preparation. Public acquisition, restore, compile and managed receiver +execution require a fresh independent admission and have not run for this receiver. +Thirteen local source controls passed with synthetic assets and a stubbed `dotnet`: +both valid routes reached restore, missing/linked source and foreign direct pins or +ProjectReferences refused before restore, and missing Render, foreign packages, +project dependency nodes and disabled assertions refused at the boundary. These +controls do not authenticate public payloads or establish installed acceptance. P3 remains +open for the actual public rendering closure, performance and fresh/retained product +adoption. Original resume04/resume05 effects, caches and custody are not reused. + A crate straddling an opening and colliding on both sides, or a rope/joint spanning areas, requires a separately designed and measured solver/constraint strategy. Body recreation does not preserve hidden contacts, warm-start state or exact solver rollback. diff --git a/tests/release/Box2D.PackageConsumer/PresentationConsumer.fsproj b/tests/release/Box2D.PackageConsumer/PresentationConsumer.fsproj new file mode 100644 index 00000000..a5f64391 --- /dev/null +++ b/tests/release/Box2D.PackageConsumer/PresentationConsumer.fsproj @@ -0,0 +1,22 @@ + + + Exe + net10.0 + false + true + false + true + true + true + + + + + + + + + + + + diff --git a/tests/release/Box2D.PackageConsumer/README.md b/tests/release/Box2D.PackageConsumer/README.md index f37a3059..b4608d6b 100644 --- a/tests/release/Box2D.PackageConsumer/README.md +++ b/tests/release/Box2D.PackageConsumer/README.md @@ -17,3 +17,29 @@ scene against package APIs, requiring one live traveller after transfer, transfo one step per local world per tick, one traversal, no repeated transfer and exact replay. SDK10.0.401 and runtime10.0.12 scope this result; output reports the actual OS/architecture/runtime. Candidate smoke cannot claim public installed support; neither smoke selects a product/template or establishes cross-platform determinism. + +## Explicit package-only portal presentation + +`../test-box2d-package-consumer.sh --public --presentation --output /absolute/new/directory` +selects the presentation consumer. It copies the real portal presentation and command-line +modules from `examples/Box2D.Portals`, together with the existing package-only portal scene, +into a fresh directory outside the checkout. The consumer has exact PackageReferences to +Game Physics.Box2D and Render `0.17.0` and FSharp.Core `10.1.302`; it uses no sibling projects, +repository imports or linked sources. Presentation selection requires the public feed. + +Before restore, the route rejects missing or linked inputs and validates the copied project +and exact direct pins. After restore, it requires exactly seven packages: Game Core, +Physics.Box2D and Render `0.17.0`, Box2D.NET `3.1.654`, FSharp.Core `10.1.302`, and +UI Scene and KeyboardInput `0.31.0`. Missing, foreign or project dependencies refuse +before compilation. The original no-presentation route keeps its four-package physics +boundary and falling-body/portal entrypoint. + +The presentation route generates a lock through public restore, checks unchanged locked +restore, and builds the copied source. It runs the actual default and `--presentation` +entrypoint modes under runtime `10.0.12`; an invalid argument must print usage and exit 2. +Presentation checks destination-only traversal snaps and ordinary interpolation over +five ticks and twelve frames. Generated locks, package inputs and invalid-argument output +remain in the new private directory. The invoking owner must separately admit acquisition +and execution, bound the process lifetime/resources, and retain failure and cleanup evidence. +Source preparation alone does not establish a successful public consumer, generated-product +adoption, retained upgrade/removal, performance, browser/GPU support or historical cleanup. diff --git a/tests/release/Box2D.PackageConsumer/verify-package-boundary.py b/tests/release/Box2D.PackageConsumer/verify-package-boundary.py index def94363..f012ab43 100644 --- a/tests/release/Box2D.PackageConsumer/verify-package-boundary.py +++ b/tests/release/Box2D.PackageConsumer/verify-package-boundary.py @@ -1,16 +1,37 @@ from pathlib import Path +import argparse import json import xml.etree.ElementTree as ET +if not __debug__: + raise SystemExit('package boundary assertions require Python optimization to be disabled') +parser = argparse.ArgumentParser(description='Verify the isolated Box2D package consumer boundary.') +parser.add_argument('--presentation', action='store_true') +parser.add_argument('--preflight', action='store_true', help='Check copied project/source inputs before restore.') +args = parser.parse_args() root = Path(__file__).resolve().parent project = ET.parse(root / 'Consumer.fsproj').getroot() for tag in ('ProjectReference', 'Reference', 'Import'): assert not project.findall('.//' + tag), f'consumer must contain no {tag}' for source in project.findall('.//Compile'): - path = (root / source.attrib['Include']).resolve() - assert path.parent == root and path.is_file(), 'consumer source must be copied and local' + path = root / source.attrib['Include'] + assert not path.is_symlink() and path.resolve().parent == root and path.is_file(), 'consumer source must be copied and local' + assert 'Link' not in source.attrib, 'consumer source must not be linked' +expected_references = {'FS.GG.Game.Physics.Box2D': '[0.17.0]', 'FSharp.Core': '[10.1.302]'} +if args.presentation: + expected_references['FS.GG.Game.Render'] = '[0.17.0]' +references = project.findall('.//PackageReference') +assert len(references) == len(expected_references), 'unexpected direct package reference count' +assert {node.attrib.get('Include'): node.attrib.get('Version') for node in references} == expected_references, 'direct package pins differ' +if args.presentation: + assert [node.attrib['Include'] for node in project.findall('.//Compile')] == ['PortalScene.fs', 'Presentation.fs', 'Program.fs'], 'presentation compile inputs differ' +if args.preflight: + print('package-only preflight passed: copied local sources and exact direct package pins') + raise SystemExit(0) assets = json.loads((root / 'obj/project.assets.json').read_text()) expected = {'FS.GG.Game.Physics.Box2D/0.17.0', 'FS.GG.Game.Core/0.17.0', 'Box2D.NET/3.1.654', 'FSharp.Core/10.1.302'} +if args.presentation: + expected |= {'FS.GG.Game.Render/0.17.0', 'FS.GG.UI.Scene/0.31.0', 'FS.GG.UI.KeyboardInput/0.31.0'} assert set(assets['libraries']) == expected, set(assets['libraries']) assert all(row['type'] == 'package' for row in assets['libraries'].values()), 'repository project dependency present' packages = Path(assets['project']['restore']['packagesPath']) @@ -21,4 +42,10 @@ def dependency_ids(package, version): return {node.attrib['id'] for node in tree.iter() if node.tag.rsplit('}', 1)[-1] == 'dependency'} assert dependency_ids('FS.GG.Game.Core', '0.17.0') == {'FSharp.Core'}, 'Core runtime boundary changed' assert dependency_ids('FS.GG.Game.Physics.Box2D', '0.17.0') == {'FSharp.Core', 'FS.GG.Game.Core', 'Box2D.NET'}, 'adapter runtime boundary changed' -print('package-only boundary passed: exact four-package closure, Core BCL/FSharp.Core, adapter has no rendering dependency') +if args.presentation: + assert dependency_ids('FS.GG.Game.Render', '0.17.0') == {'FSharp.Core', 'FS.GG.Game.Core', 'FS.GG.UI.Scene', 'FS.GG.UI.KeyboardInput'}, 'Render runtime boundary changed' + assert dependency_ids('FS.GG.UI.Scene', '0.31.0') == {'FSharp.Core'}, 'Scene runtime boundary changed' + assert dependency_ids('FS.GG.UI.KeyboardInput', '0.31.0') == {'FSharp.Core', 'FS.GG.UI.Scene'}, 'KeyboardInput runtime boundary changed' + print('package-only presentation boundary passed: exact seven-package closure, no window/GPU dependency') +else: + print('package-only boundary passed: exact four-package closure, Core BCL/FSharp.Core, adapter has no rendering dependency') diff --git a/tests/release/test-box2d-package-consumer.sh b/tests/release/test-box2d-package-consumer.sh index 0fa06f90..fcf4cdf1 100755 --- a/tests/release/test-box2d-package-consumer.sh +++ b/tests/release/test-box2d-package-consumer.sh @@ -4,22 +4,45 @@ root="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" packages="" mode="" output="" +presentation=false while (($#)); do case "$1" in --packages) packages="$(cd "$2" && pwd)"; mode=candidate; shift 2 ;; --public) mode=public; shift ;; --output) output="$2"; shift 2 ;; + --presentation) presentation=true; shift ;; *) echo "unknown argument: $1" >&2; exit 2 ;; esac done [[ "$mode" == candidate || "$mode" == public ]] || { echo 'select --packages DIR or --public' >&2; exit 2; } +if [[ "$presentation" == true ]]; then + [[ "$mode" == public && -z "$packages" ]] || { echo '--presentation requires --public without candidate packages' >&2; exit 2; } + for source in \ + "$root/tests/release/Box2D.PackageConsumer/PresentationConsumer.fsproj" \ + "$root/tests/release/Box2D.PackageConsumer/PortalScene.fs" \ + "$root/tests/release/Box2D.PackageConsumer/global.json" \ + "$root/tests/release/Box2D.PackageConsumer/verify-package-boundary.py" \ + "$root/examples/Box2D.Portals/Presentation.fs" \ + "$root/examples/Box2D.Portals/Program.fs"; do + [[ -f "$source" && ! -L "$source" ]] || { echo "missing or linked presentation input: $source" >&2; exit 2; } + done +fi if [[ -n "$output" ]]; then [[ "$output" == /* && ! -e "$output" ]] || { echo '--output must be a new absolute directory' >&2; exit 2; } mkdir -m 700 "$output" else output="$(mktemp -d "${RUNNER_TEMP:-${TMPDIR:-/tmp}}/box2d-package-consumer.XXXXXX")" fi -cp "$root/tests/release/Box2D.PackageConsumer/"{Consumer.fsproj,Program.fs,Scene.fs,PortalScene.fs,global.json,verify-package-boundary.py} "$output/" +boundary_args=() +if [[ "$presentation" == true ]]; then + cp "$root/tests/release/Box2D.PackageConsumer/PresentationConsumer.fsproj" "$output/Consumer.fsproj" + cp "$root/tests/release/Box2D.PackageConsumer/"{PortalScene.fs,global.json,verify-package-boundary.py} "$output/" + cp "$root/examples/Box2D.Portals/"{Presentation.fs,Program.fs} "$output/" + boundary_args=(--presentation) + export NUGET_HTTP_CACHE_PATH="$output/http-cache" +else + cp "$root/tests/release/Box2D.PackageConsumer/"{Consumer.fsproj,Program.fs,Scene.fs,PortalScene.fs,global.json,verify-package-boundary.py} "$output/" +fi python3 - "$output/NuGet.Config" "$mode" "$packages" <<'PY' import sys, xml.etree.ElementTree as ET path, mode, packages = sys.argv[1:] @@ -36,11 +59,24 @@ ET.ElementTree(root).write(path, encoding='unicode', xml_declaration=True) PY printf 'Box2D package-only qualification mode=%s directory=%s\n' "$mode" "$output" cd "$output" +python3 verify-package-boundary.py "${boundary_args[@]}" --preflight # Every attempt has a new isolated package cache and public HTTPS source mapping. # Disable the HTTP cache instead of changing the user's global NuGet cache/configuration. dotnet restore Consumer.fsproj --configfile NuGet.Config --packages "$output/packages" --no-cache --force-evaluate --disable-parallel -python3 verify-package-boundary.py +python3 verify-package-boundary.py "${boundary_args[@]}" +if [[ "$presentation" == true ]]; then + # Prove the genuinely generated lock survives a second restore unchanged. + cp packages.lock.json packages.lock.generated.json + dotnet restore Consumer.fsproj --configfile NuGet.Config --packages "$output/packages" --no-cache --locked-mode --force --disable-parallel + cmp packages.lock.generated.json packages.lock.json +fi dotnet build Consumer.fsproj -c Release --no-restore -m:1 --disable-build-servers # SDK pin and explicit runtime selection scope replay evidence to this profile. dotnet exec --fx-version 10.0.12 bin/Release/net10.0/Consumer.dll +if [[ "$presentation" == true ]]; then + dotnet exec --fx-version 10.0.12 bin/Release/net10.0/Consumer.dll --presentation + invalid_exit=0 + dotnet exec --fx-version 10.0.12 bin/Release/net10.0/Consumer.dll --invalid > invalid-argument.log 2>&1 || invalid_exit=$? + [[ "$invalid_exit" == 2 ]] && grep -qF 'Usage: Box2D.Portals [--presentation]' invalid-argument.log || { echo 'invalid presentation argument did not refuse with usage/exit 2' >&2; exit 1; } +fi printf 'Box2D package-only qualification passed mode=%s; lock and restored inputs retained in %s\n' "$mode" "$output" From 2e8831af75bc50df8c94a7559ccfd311a7b4335e Mon Sep 17 00:00:00 2001 From: EHotwagner Date: Sat, 10 Oct 2026 00:05:56 +0200 Subject: [PATCH 2/2] Record fresh public portal presentation receiver qualification --- docs/roadmaps/game-area-portals.md | 33 ++++++++++++++++++++++-------- 1 file changed, 24 insertions(+), 9 deletions(-) diff --git a/docs/roadmaps/game-area-portals.md b/docs/roadmaps/game-area-portals.md index 1602f9b0..73767216 100644 --- a/docs/roadmaps/game-area-portals.md +++ b/docs/roadmaps/game-area-portals.md @@ -218,13 +218,14 @@ The compiled `Box2D.Portals.dll` entrypoint also passed in both default and replay; presentation mode reported five ticks, twelve frames, twenty-four area nodes and twenty-four points. An invalid argument printed usage and exited with code 2. -P3 remains open. Public-only rendering-closure qualification, runtime performance -counters and fresh/retained adoption still need their own evidence. Historical +At this source qualification, public-only rendering-closure qualification remained +pending; the separate fresh receiver below supplies that bounded evidence. P3 remains +open for runtime performance counters and fresh/retained product adoption. Historical producer `0.17.0` acceptance and the retained resume04 failure/resume05 unrun reservation are unchanged; this source attempt does not retry those operations or transfer their custody. -### P3 public presentation consumer source preparation — 2026-10-09 +### P3 fresh public presentation package receiver — 2026-10-10 The existing Box2D package-consumer runner has an explicit public `--presentation` route and a separate exact-PackageReference project. It copies the delivered portal @@ -233,15 +234,29 @@ checks source/project inputs before restore and the expected seven-package rende closure before build, then requires unchanged locked restore and both entrypoint modes. The original four-package physics consumer remains a separate no-presentation route. -This is source preparation. Public acquisition, restore, compile and managed receiver -execution require a fresh independent admission and have not run for this receiver. Thirteen local source controls passed with synthetic assets and a stubbed `dotnet`: both valid routes reached restore, missing/linked source and foreign direct pins or ProjectReferences refused before restore, and missing Render, foreign packages, -project dependency nodes and disabled assertions refused at the boundary. These -controls do not authenticate public payloads or establish installed acceptance. P3 remains -open for the actual public rendering closure, performance and fresh/retained product -adoption. Original resume04/resume05 effects, caches and custody are not reused. +project dependency nodes and disabled assertions refused at the boundary. + +A separately admitted fresh public receiver then passed at source +`0a8665a9c8005f3edf2786ebd85bac311f1cd0d5`. Its new package and HTTP caches used +only nuget.org. Actual restored assets and nuspecs passed the exact seven-package +boundary: Game Core, Physics.Box2D and Render `0.17.0`, UI Scene and KeyboardInput +`0.31.0`, Box2D.NET `3.1.654`, and FSharp.Core `10.1.302`. The genuinely generated +lock remained byte-identical after locked restore. Release compilation reported no +warnings or errors. The copied actual default and presentation entrypoints exited 0; +an invalid argument printed usage and exited 2. Presentation reported five ticks, +twelve frames, twenty-four area nodes and twenty-four points. + +The receiver finished in 6.224 seconds on CPU 1 with a 1.5 GiB managed heap cap; +this duration is an operation observation, not a performance qualification. A separate +post-terminal census found no remaining member of its owned process group/session; +detached descendants and total RSS were not independently measured. This proves the +bounded public package rendering closure and copied-source entrypoints, not generated +product installation or retained adoption. P3 remains open for performance and +fresh/retained product adoption. Original resume04/resume05 effects, caches and custody +are unchanged and were not reused. A crate straddling an opening and colliding on both sides, or a rope/joint spanning areas, requires a separately designed and measured solver/constraint strategy. Body