From a5000edf11094e00ebef0fc362aa4adc322f2df6 Mon Sep 17 00:00:00 2001 From: Ludovic Marie <136255825+ludovic111@users.noreply.github.com> Date: Sun, 4 Oct 2026 11:26:47 +0200 Subject: [PATCH] Check recommended Mac profiles in demo worlds and retain separate Vulkan research --- .github/workflows/ci.yml | 34 +++---------- .github/workflows/macos-vulkan-research.yml | 50 ++++++++++++++++++ .../metrics/ClientGraphicsIntegration.java | 38 ++++++++++++-- tests/test_fixtures.py | 19 +++++++ tools/test_client_graphics.py | 51 +++++++++++++------ 5 files changed, 146 insertions(+), 46 deletions(-) create mode 100644 .github/workflows/macos-vulkan-research.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f32895345..23da7622d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -59,34 +59,13 @@ jobs: run: python -c "from tools.build_mod import benchmark_headers; benchmark_headers()" - name: Test pack integrity and comparisons run: python -m unittest discover -s tests -v - - name: Start native macOS clients and verify framebuffer readback + - name: Start recommended OpenGL macOS clients and verify framebuffer readback if: runner.os == 'macOS' run: | - python - <<'PY' - import json, subprocess, sys - from pathlib import Path - cases = [ - ['--profile', 'baseline', '--backend', 'vulkan'], - ['--profile', 'core-performance', '--backend', 'vulkan'], - ['--profile', 'core-performance'], - ['--profile', 'm2-balanced'], - ['--profile', 'vulkan'], - ] - failed = [] - observations = [] - for case in cases: - result = subprocess.run([sys.executable, 'tools/test_client_graphics.py', '--native-display', *case]) - observation = {'case': case, 'exit_code': result.returncode} - if result.returncode: - failed.append(case) - else: - observation['report'] = json.loads(Path('dist/client-graphics-smoke.json').read_text()) - observations.append(observation) - Path('dist/client-graphics-macos-cases.json').write_text(json.dumps(observations, indent=2) + '\n') - if failed: - print('HORIZON_MAC_GRAPHICS_FAILED_CASES', failed, flush=True) - sys.exit(1) - PY + python tools/test_client_graphics.py --native-display + cp dist/client-graphics-smoke.json dist/client-graphics-macos-title.json + python tools/test_client_graphics.py --native-display --world + cp dist/client-graphics-smoke.json dist/client-graphics-macos-world.json - name: Compile the private dispatch fixture run: python -c "from tools.build_mod import build_dispatch_fixture; build_dispatch_fixture()" - name: Compile the private tick-manager state fixture @@ -152,4 +131,5 @@ jobs: dist/quad-reuse-integration.json dist/quad-size-integration.json dist/client-graphics-smoke.json - dist/client-graphics-macos-cases.json + dist/client-graphics-macos-title.json + dist/client-graphics-macos-world.json diff --git a/.github/workflows/macos-vulkan-research.yml b/.github/workflows/macos-vulkan-research.yml new file mode 100644 index 000000000..adea87de5 --- /dev/null +++ b/.github/workflows/macos-vulkan-research.yml @@ -0,0 +1,50 @@ +name: macOS Vulkan research +on: + workflow_dispatch: +permissions: + contents: read +jobs: + native-probes: + runs-on: macos-26 + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: '3.12' + - uses: actions/setup-java@v4 + with: + distribution: temurin + java-version: '25' + - name: Build original mod and private probe dependencies + run: python tools/build_mod.py + - name: Probe vanilla, core and distant-terrain Vulkan cases + run: | + python - <<'PY' + import json, subprocess, sys + from pathlib import Path + cases = [ + ['--profile', 'baseline', '--backend', 'vulkan'], + ['--profile', 'core-performance', '--backend', 'vulkan'], + ['--profile', 'vulkan'], + ] + observations = [] + for case in cases: + result = subprocess.run([sys.executable, 'tools/test_client_graphics.py', '--native-display', *case], capture_output=True, text=True) + print(result.stdout, end='', flush=True) + print(result.stderr, end='', flush=True) + observation = {'case': case, 'exit_code': result.returncode} + if result.returncode: + marker = 'HORIZON_FIXTURE_FAILURE ' + observation['failure'] = [json.loads(line.split(marker, 1)[1]) for line in result.stdout.splitlines() if marker in line] + else: + observation['report'] = json.loads(Path('dist/client-graphics-smoke.json').read_text()) + observations.append(observation) + Path('dist/client-graphics-vulkan-research.json').write_text(json.dumps(observations, indent=2) + '\n') + sys.exit(int(any(item['exit_code'] for item in observations))) + PY + - name: Retain selected observations including failed cases + if: always() + uses: actions/upload-artifact@v4 + with: + name: macos-vulkan-observations + path: dist/client-graphics-vulkan-research.json diff --git a/mods/horizon-lab/src/test/java/dev/horizonlab/metrics/ClientGraphicsIntegration.java b/mods/horizon-lab/src/test/java/dev/horizonlab/metrics/ClientGraphicsIntegration.java index 2bf27e4f6..4ec9d35bf 100644 --- a/mods/horizon-lab/src/test/java/dev/horizonlab/metrics/ClientGraphicsIntegration.java +++ b/mods/horizon-lab/src/test/java/dev/horizonlab/metrics/ClientGraphicsIntegration.java @@ -8,23 +8,52 @@ import java.util.HashSet; import java.util.HexFormat; import net.fabricmc.api.ClientModInitializer; import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents; +import net.fabricmc.loader.api.FabricLoader; import net.minecraft.client.Screenshot; import net.minecraft.client.gui.screens.TitleScreen; +import net.minecraft.network.chat.contents.TranslatableContents; +import net.minecraft.server.MinecraftServer; +import net.minecraft.world.level.levelgen.WorldOptions; +import net.minecraft.world.level.levelgen.presets.WorldPresets; /** Private startup/readback probe. Never packaged in a playable release. */ public final class ClientGraphicsIntegration implements ClientModInitializer { private long titleStarted; private int titleTicks; private boolean requested; + private boolean opening; + private final boolean worldMode = Files.exists(FabricLoader.getInstance().getConfigDir().resolve("client-graphics-world.flag")); @Override public void onInitializeClient() { System.out.println("HORIZON_CLIENT_GRAPHICS_INITIALIZER_READY"); ClientTickEvents.END_CLIENT_TICK.register(client -> { - if (requested || !(client.gui.screen() instanceof TitleScreen)) return; + if (requested) return; + if (worldMode) { + if (!opening && client.gui.screen() instanceof TitleScreen screen) { + opening = true; + if (Files.isRegularFile(client.gameDirectory.toPath().resolve("saves/Demo_World/level.dat"))) { + client.createWorldOpenFlows().openWorld("Demo_World", () -> { + throw new AssertionError("Disposable graphics world did not open"); + }); + } else { + client.createWorldOpenFlows().createFreshLevel("Demo_World", MinecraftServer.DEMO_SETTINGS, + WorldOptions.DEMO_OPTIONS, WorldPresets::createNormalWorldDimensions, screen); + } + return; + } + if (client.gui.screen() != null && client.gui.screen().getTitle().getContents() instanceof TranslatableContents title + && title.getKey().equals("demo.help.title")) { + client.gui.setScreen(null); + return; + } + if (client.level == null || client.player == null || client.gui.screen() != null + || client.isPaused() || !client.isWindowActive()) return; + } else if (!(client.gui.screen() instanceof TitleScreen)) return; if (titleStarted == 0) titleStarted = System.nanoTime(); titleTicks++; if (titleTicks < 40 || System.nanoTime() - titleStarted < 2_000_000_000L) return; requested = true; + System.out.println("HORIZON_CLIENT_GRAPHICS_READBACK_REQUESTED"); Screenshot.takeScreenshot(client.gameRenderer.mainRenderTarget(), image -> { try (image) { int width = image.getWidth(), height = image.getHeight(); @@ -42,8 +71,11 @@ public final class ClientGraphicsIntegration implements ClientModInitializer { image.writeToFile(screenshot); var device = RenderSystem.getDevice().getDeviceInfo(); var result = new JsonObject(); - result.addProperty("title_screen_observed", true); - result.addProperty("title_client_ticks", titleTicks); + result.addProperty("observed_mode", worldMode ? "world" : "title"); + result.addProperty("title_screen_observed", !worldMode); + result.addProperty("world_loaded", worldMode && client.level != null); + result.addProperty("ready_client_ticks", titleTicks); + result.addProperty("window_active", client.isWindowActive()); result.addProperty("framebuffer_width", width); result.addProperty("framebuffer_height", height); result.addProperty("sampled_pixels", samples); diff --git a/tests/test_fixtures.py b/tests/test_fixtures.py index e4642eb05..4b10553bc 100644 --- a/tests/test_fixtures.py +++ b/tests/test_fixtures.py @@ -3,9 +3,28 @@ import subprocess import unittest from tools.test_dispatch import failure_summary +from tools.test_client_graphics import graphics_failure_summary class FixtureDiagnosticTests(unittest.TestCase): + def test_graphics_diagnostics_separate_demo_auth_and_exclude_methods_messages(self): + output = '\n'.join([ + 'com.mojang.authlib.exceptions.InvalidCredentialsException: private-token', + ' at com.mojang.authlib.minecraft.client.MinecraftClient.buildHttpException(File.java:1)', + 'java.lang.AssertionError: /private/world/path', + 'Caused by: java.lang.RuntimeException: private-message VK_ERROR_FEATURE_NOT_PRESENT', + 'HORIZON_CLIENT_GRAPHICS_INITIALIZER_READY', + 'HORIZON_CLIENT_GRAPHICS_READBACK_REQUESTED', + ]) + result = graphics_failure_summary(subprocess.CompletedProcess([], 1, output), 0) + self.assertEqual(result['java_exception_types'], ['java.lang.AssertionError', 'java.lang.RuntimeException']) + self.assertTrue(result['expected_demo_authentication_errors_observed']) + self.assertTrue(result['client_initializer_reached']) + self.assertTrue(result['readback_requested']) + self.assertEqual(result['vulkan_error_codes'], ['VK_ERROR_FEATURE_NOT_PRESENT']) + self.assertNotIn('private', json.dumps(result)) + self.assertNotIn('buildHttpException', json.dumps(result)) + def test_only_exit_count_and_exception_types_leave_private_log(self): output = '\n'.join([ 'Exception in thread "private-thread-name" java.lang.NoClassDefFoundError: private-token', diff --git a/tools/test_client_graphics.py b/tools/test_client_graphics.py index 5421b5252..dbd86a383 100644 --- a/tools/test_client_graphics.py +++ b/tools/test_client_graphics.py @@ -21,11 +21,31 @@ except ImportError: from test_dispatch import failure_summary + +def graphics_failure_summary(run, report_count): + """Selected stages/types/codes only; messages, paths and raw logs stay private.""" + result = failure_summary(run, report_count) + pattern = r'(?