Implement playable Mac client and rendering validation

This commit is contained in:
shen
2026-09-11 14:58:22 +08:00
parent 1ab68bd06e
commit 374d4165d8
233 changed files with 6546 additions and 284 deletions
+1
View File
@@ -0,0 +1 @@
PLAYABLE FIXTURE: child completed without renderer errors
+19
View File
@@ -0,0 +1,19 @@
{
"schema_version": 1,
"run_id": "fixture-blocked",
"suite": "playable",
"status": "BLOCKED",
"build": {
"engine_sha256": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"extension_sha256": "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
"pck_sha256": "cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc",
"arch": "arm64"
},
"environment": {"os": "macOS", "renderer": "Metal", "resolution": [1280, 720]},
"cases": [{"id": "FIXTURE-01", "status": "BLOCKED", "duration_ms": 0, "reason": "fixture_not_ready", "evidence": []}],
"events": "events.jsonl",
"failures": [],
"blocked": ["fixture_not_ready"],
"coverage": {"required": 1, "passed": 0},
"exit_gate": {"checked": false, "process_code": null, "errors": []}
}
+19
View File
@@ -0,0 +1,19 @@
{
"schema_version": 1,
"run_id": "fixture-valid",
"suite": "playable",
"status": "PASS",
"build": {
"engine_sha256": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"extension_sha256": "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
"pck_sha256": "cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc",
"arch": "arm64"
},
"environment": {"os": "macOS", "renderer": "Metal", "resolution": [1280, 720]},
"cases": [{"id": "FIXTURE-01", "status": "PASS", "duration_ms": 1, "reason": "ok", "evidence": []}],
"events": "events.jsonl",
"failures": [],
"blocked": [],
"coverage": {"required": 1, "passed": 1},
"exit_gate": {"checked": false, "process_code": null, "errors": []}
}
+1
View File
@@ -0,0 +1 @@
WARNING: 3 shaders of type CanvasItem were never freed.
+19
View File
@@ -0,0 +1,19 @@
{
"schema_version": 1,
"scenario_id": "first-mac-playable-forest",
"protocol": "classic",
"server": {
"server_index": 0,
"channel_index": 0
},
"character_slot": 0,
"map_key": "",
"waypoints_cm": [],
"allowed_mob_vnums": [],
"allowed_drop_vnums": [],
"skill_cases": [],
"resolution": [1280, 720],
"loops": 3,
"timeout_seconds": 120,
"notes": "Copy to a local ignored file and fill from the confirmed server fixture. Never add credentials here."
}
+71 -55
View File
@@ -4,91 +4,107 @@
"races": {
"0": {
"body_gr2": "warrior_novice.gr2",
"hair_bright_frac": 0.0224071972085593,
"body_sha256": "7ac881b0c60ced40c9388d6186bdf02a353a58382d12804345cb65fc3dc1fa74",
"hair_bright_frac": 0.0961239332408458,
"hair_gr2": "hair_1_1.gr2",
"hair_lum_p99": 0.254901960784314,
"hair_mean_lum": 0.0607960189162477,
"hair_mean_lum_ctrl": 0.097848223242717,
"head_cover": 0.324674209407673,
"head_px": 46579,
"silhouette_px": 35073
"hair_lum_p99": 0.270588235294118,
"hair_mean_lum": 0.0862069988797638,
"hair_mean_lum_ctrl": 0.118492882502141,
"hair_sha256": "2e3c00bda5397d579a2399cfb38a69974667e4f7b92dca6b2a709058a37d0a84",
"head_cover": 0.272221606648199,
"head_px": 45125,
"silhouette_px": 35078
},
"1": {
"body_gr2": "assassin_novice.gr2",
"hair_bright_frac": 0.316447908121411,
"body_sha256": "176d88073cc5a6a4fcd4b4f62d9c401a5a63b27d50a816ad0d5fb463d86f1eab",
"hair_bright_frac": 0.0740650293050516,
"hair_gr2": "hair_1_1.gr2",
"hair_lum_p99": 0.349019607843137,
"hair_mean_lum": 0.184415208398222,
"hair_mean_lum_ctrl": 0.195338585878083,
"head_cover": 0.244265393733581,
"head_px": 62428,
"silhouette_px": 36052
"hair_lum_p99": 0.411764705882353,
"hair_mean_lum": 0.122540537286642,
"hair_mean_lum_ctrl": 0.16191718244652,
"hair_sha256": "61424a024587b0bc2dce5febc7d2af5bd1d42177611e2c2772b7fea0d21aad0f",
"head_cover": 0.163433908045977,
"head_px": 27840,
"silhouette_px": 24776
},
"2": {
"body_gr2": "sura_novice.gr2",
"hair_bright_frac": 0.834898912836162,
"body_sha256": "edf32d40ea5cbc080fc3fd9798ce9a2637d2f4a7435e93b48601b05bfe490af1",
"hair_bright_frac": 0.841908258630555,
"hair_gr2": "hair_1_1.gr2",
"hair_lum_p99": 0.435294117647059,
"hair_mean_lum": 0.260437632997124,
"hair_mean_lum_ctrl": 0.281076636261981,
"head_cover": 0.0281083316817606,
"head_px": 116051,
"hair_lum_p99": 0.67843137254902,
"hair_mean_lum": 0.345470992135172,
"hair_mean_lum_ctrl": 0.36430273043797,
"hair_sha256": "8de6c62889d3c55ef59d6e913f7a25e2a7cdf8748eeae5c68aded52cb1ab4eef",
"head_cover": 0.0245020445851471,
"head_px": 121296,
"silhouette_px": 39682
},
"3": {
"body_gr2": "shaman_novice.gr2",
"hair_bright_frac": 0.122538024274082,
"body_sha256": "1ecfd2e4e2e5e9c2280b18be0f48d749392f6de0006645668126e4ddd8935fca",
"hair_bright_frac": 0.138202264691681,
"hair_gr2": "hair_1_1.gr2",
"hair_lum_p99": 0.32156862745098,
"hair_mean_lum": 0.134457749300365,
"hair_mean_lum_ctrl": 0.166435011552631,
"head_cover": 0.295606972434941,
"head_px": 60983,
"silhouette_px": 36257
"hair_lum_p99": 0.325490196078431,
"hair_mean_lum": 0.114180786249986,
"hair_mean_lum_ctrl": 0.148597906212311,
"hair_sha256": "ef76fc009f6df5981637de614a5d68045e5ce778a1a76cc85154293f05c673fe",
"head_cover": 0.696943300601837,
"head_px": 37884,
"silhouette_px": 21923
},
"4": {
"body_gr2": "warrior_novice.gr2",
"hair_bright_frac": 0.0294142102856193,
"body_sha256": "fef02eabbd4c4e4ab9b001e98af9cf04a58e04fdcc0955621fc8a2a5539ce8e9",
"hair_bright_frac": 0.232798403525548,
"hair_gr2": "hair_1_1.gr2",
"hair_lum_p99": 0.23921568627451,
"hair_mean_lum": 0.0852886966119754,
"hair_mean_lum_ctrl": 0.107547303626061,
"head_cover": 0.190623521275434,
"head_px": 27473,
"silhouette_px": 27104
"hair_lum_p99": 0.356862745098039,
"hair_mean_lum": 0.138821295782272,
"hair_mean_lum_ctrl": 0.171530843244045,
"hair_sha256": "9f17eb53e552508d9554df18fe7810e1f237f49040a47edf28aee2e3fc6218db",
"head_cover": 0.222755996904823,
"head_px": 31016,
"silhouette_px": 27285
},
"5": {
"body_gr2": "assassin_novice.gr2",
"hair_bright_frac": 0.0384454367848172,
"body_sha256": "4a9b7cd7bcfdb8e94578d760bc40c05c827ec000e67d193522eaef92c3849d78",
"hair_bright_frac": 0.860336341263331,
"hair_gr2": "hair_1_1.gr2",
"hair_lum_p99": 0.270588235294118,
"hair_mean_lum": 0.0734998604521351,
"hair_mean_lum_ctrl": 0.0947519741918537,
"head_cover": 0.25690174015439,
"head_px": 30572,
"silhouette_px": 24776
"hair_lum_p99": 0.427450980392157,
"hair_mean_lum": 0.282471313531006,
"hair_mean_lum_ctrl": 0.307527867586739,
"hair_sha256": "0bf02587413c9b7b5687cc051a1afbfed6fba1ee0ebeb1093fe0b65a1ba74178",
"head_cover": 0.216423497313214,
"head_px": 60109,
"silhouette_px": 36029
},
"6": {
"body_gr2": "sura_novice.gr2",
"hair_bright_frac": 0.905465795806119,
"body_sha256": "03640fdc3502a29ea313d18a206bb826a08e644e771b3b40136c7f250c4b03aa",
"hair_bright_frac": 0.956428325885184,
"hair_gr2": "hair_1_1.gr2",
"hair_lum_p99": 0.427450980392157,
"hair_mean_lum": 0.277393012894398,
"hair_mean_lum_ctrl": 0.298905357949299,
"head_cover": 0.617005584237699,
"head_px": 37964,
"silhouette_px": 30280
"hair_lum_p99": 0.698039215686274,
"hair_mean_lum": 0.406448884125668,
"hair_mean_lum_ctrl": 0.423375393471242,
"hair_sha256": "0e2e6eebaad5925540d48645a72073bad7c0556a859c581a091c5e2570227444",
"head_cover": 0.649506781750925,
"head_px": 38928,
"silhouette_px": 24781
},
"7": {
"body_gr2": "shaman_novice.gr2",
"hair_bright_frac": 0.0142828216749996,
"body_sha256": "6cb2a74fc0f7b85cd58b1dbe239c5ddcaa68281d5ee37469cea8ec4b209baa40",
"hair_bright_frac": 0.356644645874942,
"hair_gr2": "hair_1_1.gr2",
"hair_lum_p99": 0.219607843137255,
"hair_mean_lum": 0.0624432979584058,
"hair_mean_lum_ctrl": 0.0774457732565792,
"head_cover": 0.715665995318709,
"head_px": 36742,
"silhouette_px": 21953
"hair_lum_p99": 0.415686274509804,
"hair_mean_lum": 0.182649001834564,
"hair_mean_lum_ctrl": 0.204288481408849,
"hair_sha256": "5c6a22c5184737111a1eafcac9b0a8f950145a923da6c59faf09bdf09ec4bb4a",
"head_cover": 0.262338937708471,
"head_px": 59061,
"silhouette_px": 36786
}
}
}
+1
View File
@@ -0,0 +1 @@
.godot/
+31
View File
@@ -0,0 +1,31 @@
# Particle shader lifetime reproducer
Independent Godot project: no game assets, autoloads, GDExtension, network,
EffectPlayer, or particles emitter. Eight short-lived ParticleProcessMaterials
cycle through three emission shapes. Weak references must all be dead before
exit; a RenderingServer synchronization also runs before quitting.
Export this project with the installed macOS template using `--export-release`
and `--export-debug` into separate directories under the repository's `build/`.
Run each exported executable and preserve both stdout and stderr until process
exit. A run is clean only if `ISOLATED_PARTICLE_EXIT alive=0` is present and no
shader/RID error follows. The success marker alone is not a clean-exit verdict.
2026-09-10, Godot 4.7.1 official, Apple M4/Metal:
- Release: zero surviving materials, three ParticlesShaderRD/Shader RID leaks.
- Debug: zero surviving materials, no shader/RID leak.
Logs: `build/rendering/particle-isolated-release.log` and
`build/rendering/particle-isolated-debug.log`. Path overrides (`--main-pack`,
external `--script`) are not supported by the installed release template;
export a standalone app instead. This project contains no copyrighted artwork
and may be used as a minimal upstream reproduction if separately authorized.
2026-09-11: an unmodified same-revision arm64 release build using
`debug_symbols=yes optimize=speed lto=none vulkan=no` exits cleanly in this
project and in two complete game package checks. Rebuild it with
`bash script/build_macos_release_engine.sh arm64` from the repository root.
This is a verified alternative build path, not proof of a specific compiler
bug or a source fix to the installed official template. Intel and Vulkan are
outside this result.
@@ -0,0 +1,9 @@
[preset.0]
name="macOS"
platform="macOS"
runnable=true
export_filter="all_resources"
include_filter=""
exclude_filter=""
[preset.0.options]
application/bundle_identifier="org.metin2.particleprobe"
+17
View File
@@ -0,0 +1,17 @@
extends Node
var refs: Array[WeakRef] = []
func _ready() -> void: call_deferred("run")
func run() -> void:
for i in 8: create_material(i)
for i in 10: await get_tree().process_frame
RenderingServer.force_sync()
var alive := 0
for reference in refs:
if reference.get_ref() != null: alive += 1
print("ISOLATED_PARTICLE_EXIT alive=", alive)
get_tree().quit(1 if alive else 0)
func create_material(index: int) -> void:
var material := ParticleProcessMaterial.new()
material.emission_shape = index % 3
material.get_rid()
refs.append(weakref(material))
+1
View File
@@ -0,0 +1 @@
uid://b3vjaypwq6yx6
+4
View File
@@ -0,0 +1,4 @@
[gd_scene load_steps=2 format=3]
[ext_resource type="Script" path="res://main.gd" id="1"]
[node name="Probe" type="Node"]
script = ExtResource("1")
@@ -0,0 +1,9 @@
config_version=5
[application]
config/name="Particle lifetime isolation"
run/main_scene="res://main.tscn"
[display]
window/size/viewport_width=320
window/size/viewport_height=240
[rendering]
textures/vram_compression/import_etc2_astc=true