6868 CAMPAIGN_SCENARIO : ${{ github.event_name == 'workflow_dispatch' && inputs.scenario || 'static-steady' }}
6969 CAMPAIGN_RUNS : ${{ github.event_name == 'workflow_dispatch' && inputs.runs || github.event.action == 'labeled' && '12' || '4' }}
7070 CAMPAIGN_ITEMS : ${{ github.event_name == 'workflow_dispatch' && inputs.items || github.event.action == 'labeled' && '4096' || '1024' }}
71+ CAMPAIGN_DROPPED_NEARBY_ITEMS : " 128"
7172 CAMPAIGN_WARMUP_SECONDS : ${{ github.event_name == 'workflow_dispatch' && inputs.warmup_seconds || github.event.action == 'labeled' && '120' || '10' }}
7273 CAMPAIGN_SETTLE_SECONDS : ${{ github.event_name == 'workflow_dispatch' && inputs.settle_seconds || github.event.action == 'labeled' && '20' || '5' }}
7374 CAMPAIGN_MEASURE_SECONDS : ${{ github.event_name == 'workflow_dispatch' && inputs.measure_seconds || github.event.action == 'labeled' && '180' || '10' }}
99100 bash -n tools/perf/prepare-phase2-protocol-client.sh
100101 bash -n tools/perf/run-phase2-runtime-once.sh
101102 bash tools/perf/run-phase2-runtime-once.sh --self-test
103+ python3 tools/perf/evaluate-dropped-item-gate.py --self-test
102104 node --check tools/perf/phase2-protocol-client.js
103105 pwsh -NoProfile -File tools/perf/analyze-phase2-abba.ps1 -SelfTest
104106 pwsh -NoProfile -File tools/perf/analyze-phase2-pcap.ps1 -SelfTest
@@ -145,6 +147,7 @@ jobs:
145147 SCENARIO : ${{ env.CAMPAIGN_SCENARIO }}
146148 RUNS : ${{ env.CAMPAIGN_RUNS }}
147149 ITEMS : ${{ env.CAMPAIGN_ITEMS }}
150+ DROPPED_NEARBY_ITEMS : ${{ env.CAMPAIGN_DROPPED_NEARBY_ITEMS }}
148151 WARMUP_SECONDS : ${{ env.CAMPAIGN_WARMUP_SECONDS }}
149152 SETTLE_SECONDS : ${{ env.CAMPAIGN_SETTLE_SECONDS }}
150153 MEASURE_SECONDS : ${{ env.CAMPAIGN_MEASURE_SECONDS }}
@@ -160,6 +163,7 @@ jobs:
160163 "$SCENARIO" == dropped-items ]]
161164 [[ "$RUNS" =~ ^(4|8|12)$ ]]
162165 [[ "$ITEMS" =~ ^[0-9]+$ ]]
166+ [[ "$DROPPED_NEARBY_ITEMS" =~ ^[0-9]+$ ]]
163167 if [[ "$SCENARIO" == block-* ]]; then
164168 # TileEntityUpdate.CheckingRange=1 covers the centered 32x32
165169 # block footprint completely; larger command scenes are valid but
@@ -207,6 +211,10 @@ jobs:
207211 echo "dropped-item-section-candidates A/B requires exactly 2048 dropped items" >&2
208212 exit 64
209213 fi
214+ if (( DROPPED_NEARBY_ITEMS != 128 )); then
215+ echo "dropped-item-section-candidates A/B requires exactly 128 nearby labels" >&2
216+ exit 64
217+ fi
210218 elif [[ "$SCENARIO" == dropped-items ]]; then
211219 echo "dropped-items is reserved for dropped-item-section-candidates A/B" >&2
212220 exit 64
@@ -252,9 +260,11 @@ jobs:
252260 sha256sum phase2-dependencies/paper.jar
253261 sha256sum phase2-dependencies/protocol-client/client-build-manifest.json
254262 sha256sum tools/perf/run-phase2-runtime-once.sh
263+ sha256sum tools/perf/evaluate-dropped-item-gate.py
255264 java -version 2>&1
256265 printf '%s\n' \
257- "abFactor=$AB_FACTOR" "scenario=$SCENARIO" "sparkProfile=$SPARK_PROFILE_MODE"
266+ "abFactor=$AB_FACTOR" "scenario=$SCENARIO" \
267+ "droppedNearbyItems=$DROPPED_NEARBY_ITEMS" "sparkProfile=$SPARK_PROFILE_MODE"
258268 } | sha256sum | awk '{print $1}'
259269 )
260270
@@ -287,6 +297,7 @@ jobs:
287297 PHASE2_VARIANT="$variant" \
288298 PHASE2_AB_FACTOR="$AB_FACTOR" \
289299 PHASE2_ITEM_COUNT="$ITEMS" \
300+ PHASE2_DROPPED_NEARBY_ITEM_COUNT="$DROPPED_NEARBY_ITEMS" \
290301 PHASE2_WARMUP_SECONDS="$WARMUP_SECONDS" \
291302 PHASE2_SETTLE_SECONDS="$SETTLE_SECONDS" \
292303 PHASE2_MEASURE_SECONDS="$MEASURE_SECONDS" \
@@ -413,107 +424,9 @@ jobs:
413424 -MinimumSeconds "$minimum_seconds" "${incomplete[@]}" \
414425 -OutputJson "$EVIDENCE_ROOT/droppedItemMs.analysis.json" -Overwrite
415426
416- python3 - "$MANIFEST" "$EVIDENCE_ROOT" <<'PY'
417- import csv
418- import json
419- import os
420- from pathlib import Path
421- import sys
422-
423- manifest_path = Path(sys.argv[1])
424- evidence_root = Path(sys.argv[2])
425-
426- def load_analysis(name):
427- return json.loads((evidence_root / f"{name}.analysis.json").read_text(encoding="utf-8"))
428-
429- dropped = load_analysis("droppedItemMs")
430- p95 = load_analysis("msptP95")
431- p99 = load_analysis("msptP99")
432- checks = {
433- "droppedItemMedianRatioAtMost0_50": dropped["medianBRatioToA"] <= 0.50,
434- "droppedItemCiExcludesRegression": dropped["ratioBootstrap95Ci"][1] < 1.0,
435- "msptP95CiUpperAtMost1_02": p95["ratioBootstrap95Ci"][1] <= 1.02,
436- "msptP99CiUpperAtMost1_05": p99["ratioBootstrap95Ci"][1] <= 1.05,
437- }
438-
439- candidate_runs = []
440- with manifest_path.open(encoding="utf-8", newline="") as stream:
441- for row in csv.DictReader(stream):
442- metrics_path = manifest_path.parent / row["SourcePath"]
443- metrics = json.loads(metrics_path.read_text(encoding="utf-8"))
444- candidate_runs.append({
445- "runId": row["RunId"],
446- "variant": row["Variant"],
447- "sourceOwned": metrics["droppedSourceOwnedSectionCandidates"],
448- "trackedItemsMax": metrics["droppedTrackedItemsMax"],
449- "labelsMax": metrics["droppedLabelsMax"],
450- "fullScanCandidates": metrics["droppedFullScanCandidates"],
451- "spatialCandidates": metrics["droppedSpatialCandidates"],
452- "viewerDistanceChecks": metrics["droppedViewerDistanceChecks"],
453- })
454- checks["allRunsTracked2048Labels"] = all(
455- run["trackedItemsMax"] == 2048 and run["labelsMax"] == 2048
456- for run in candidate_runs
457- )
458- checks["candidateHasZeroFullScans"] = all(
459- run["fullScanCandidates"] == 0
460- for run in candidate_runs if run["variant"] == "B"
461- )
462- checks["baselineExercisedFullScans"] = all(
463- run["fullScanCandidates"] > 0
464- for run in candidate_runs if run["variant"] == "A"
465- )
466- checks["candidateTreatmentIsolated"] = all(
467- run["sourceOwned"] is (run["variant"] == "B")
468- for run in candidate_runs
469- )
470- passed = all(checks.values())
471- gate = {
472- "schemaVersion": 1,
473- "scenario": "dropped-items",
474- "abFactor": "dropped-item-section-candidates",
475- "formalComplete": dropped.get("formalComplete") is True,
476- "serverRuntimeGatePassed": passed,
477- "checks": checks,
478- "ratios": {
479- "droppedItemMs": {
480- "medianBRatioToA": dropped["medianBRatioToA"],
481- "ratioBootstrap95Ci": dropped["ratioBootstrap95Ci"],
482- },
483- "msptP95": {
484- "medianBRatioToA": p95["medianBRatioToA"],
485- "ratioBootstrap95Ci": p95["ratioBootstrap95Ci"],
486- },
487- "msptP99": {
488- "medianBRatioToA": p99["medianBRatioToA"],
489- "ratioBootstrap95Ci": p99["ratioBootstrap95Ci"],
490- },
491- },
492- "runs": candidate_runs,
493- "scope": "Server runtime gate only; allocation and native-client frame gates remain separate.",
494- }
495- (evidence_root / "dropped-item-section-candidates.gate.json").write_text(
496- json.dumps(gate, ensure_ascii=False, indent=2) + "\n", encoding="utf-8"
497- )
498- summary = os.environ.get("GITHUB_STEP_SUMMARY")
499- if summary:
500- with open(summary, "a", encoding="utf-8", newline="\n") as stream:
501- stream.write("### Dropped-item section-candidate server gate\n\n")
502- stream.write(f"Result: `{'pass' if passed else 'fail'}`.\n\n")
503- stream.write(
504- f"droppedItemMs B/A: `{dropped['medianBRatioToA']}` "
505- f"(95% CI `{dropped['ratioBootstrap95Ci']}`).\n\n"
506- )
507- stream.write(
508- f"MSPT p95 CI upper: `{p95['ratioBootstrap95Ci'][1]}`; "
509- f"p99 CI upper: `{p99['ratioBootstrap95Ci'][1]}`.\n"
510- )
511- if not gate["formalComplete"]:
512- raise SystemExit("dropped-item gate is not a complete 12-run campaign")
513- if not passed:
514- failed = ", ".join(name for name, value in checks.items() if not value)
515- raise SystemExit(f"dropped-item server gate failed: {failed}")
516- PY
427+ python3 tools/perf/evaluate-dropped-item-gate.py \
428+ "$MANIFEST" "$EVIDENCE_ROOT" "$ITEMS" "$DROPPED_NEARBY_ITEMS" \
429+ "$EVIDENCE_ROOT/dropped-item-section-candidates.gate.json"
517430 fi
518431
519432 if [[ "$SCENARIO" == block-* ]]; then
0 commit comments