Skip to content
Open

Dev #119

Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
5823b65
perf(render): skip unchanged ray lifecycle work
Arbousier1 Jul 17, 2026
783087d
Merge pull request #96 from EllanServer/codex/perf-network-packet-delta
Arbousier1 Jul 17, 2026
9259ee5
perf(render): reuse stable viewer membership plans
Arbousier1 Jul 17, 2026
0d67447
Merge pull request #97 from EllanServer/codex/perf-snapshot-membershi…
Arbousier1 Jul 17, 2026
778dca1
perf(render): reduce layout precompute churn
Arbousier1 Jul 17, 2026
a77e0fd
Merge pull request #98 from EllanServer/codex/perf-render-fingerprint…
Arbousier1 Jul 17, 2026
8e14bf6
perf(live): make plugin scenario reproducible
Arbousier1 Jul 18, 2026
39e4bf6
Merge pull request #110 from EllanServer/codex/perf-live-fixture
Arbousier1 Jul 18, 2026
074e2fd
perf: add hotspot lifecycle benchmarks
Arbousier1 Jul 18, 2026
51f2614
Merge pull request #109 from EllanServer/codex/perf-hotspot-baselines
Arbousier1 Jul 18, 2026
c9a82f6
perf(render): reuse stable session layouts
Arbousier1 Jul 18, 2026
49d4d2d
Merge pull request #114 from EllanServer/codex/perf-session-layout-cache
Arbousier1 Jul 18, 2026
520f80c
perf(scheduler): publish capability caches atomically
Arbousier1 Jul 19, 2026
4dab96b
Merge pull request #115 from EllanServer/codex/perf-scheduler-capabil…
Arbousier1 Jul 19, 2026
c437d6b
perf(scheduler): cache immutable dispatch plans
Arbousier1 Jul 19, 2026
8d65030
Merge pull request #116 from EllanServer/codex/perf-scheduler-dispatc…
Arbousier1 Jul 19, 2026
774d0a9
perf(render): cache stable ray proxy identities
Arbousier1 Jul 19, 2026
14c879c
perf(render): bypass redundant viewer lookups
Arbousier1 Jul 19, 2026
ef9ec5e
perf(render): isolate the viewer lookup fast path
Arbousier1 Jul 19, 2026
8456926
Merge pull request #117 from EllanServer/codex/perf-ray-proxy-stable-…
Arbousier1 Jul 20, 2026
082f1ad
fix(interact): widen click dedup window to stop cross-tick double-firing
Arbousier1 Jul 21, 2026
02eb8bf
Merge pull request #118 from EllanServer/codex/fix-click-dedup-cross-…
Arbousier1 Jul 21, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .github/workflows/performance-ab.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,10 @@ on:
- layout
- scheduler-reflection
- ray-proxy
- riichi-reaction
- sichuan-hu
- region-apply
- layout-cache
- infra
forks:
description: Trusted CI override for JMH forks (blank uses base config)
Expand Down Expand Up @@ -157,6 +161,10 @@ jobs:
"performance-layout": "layout",
"performance-scheduler-reflection": "scheduler-reflection",
"performance-ray-proxy": "ray-proxy",
"performance-riichi-reaction": "riichi-reaction",
"performance-sichuan-hu": "sichuan-hu",
"performance-region-apply": "region-apply",
"performance-layout-cache": "layout-cache",
}
selected = [profile for label, profile in choices.items() if label in labels]
if len(selected) != 1:
Expand Down
26 changes: 15 additions & 11 deletions docs/performance-testing.md
Original file line number Diff line number Diff line change
Expand Up @@ -362,16 +362,16 @@ The dependency surface is deliberately fixed:
| External client runtime | Node `22.23.1` |
| Protocol implementation | `minecraft-protocol` `1.66.2`, locked by npm integrity |
| Paper | `1.20.1` build `196`, SHA-256 `234a9b32098100c6fc116664d64e36ccdb58b5b649af0f80bcccb08b0255eaea` |
| CraftEngine for plugin runs | Paper `26.7.3` (`Len451or`), SHA-512 from the committed artifact lock |
| CraftEngine for plugin runs | Paper `26.7.4` (`aINSQrXC`), SHA-512 from the committed artifact lock |

`perf/live/artifacts.lock.json` contains immutable URLs, byte sizes and hashes. Downloads are
written to a temporary file, size/hash verified, fsynced, then atomically moved into place.
The Paper URL is the official Paper downloads-service object. The optional CraftEngine entry
is artifact `craftengine-paper-26.7.3`, the latest Paper/Folia/Purpur file published for that
is artifact `craftengine-paper-26.7.4`, the latest Paper/Folia/Purpur file published for that
version. Do not substitute the same-version Bukkit/Spigot file: Modrinth marks that separate
artifact for Minecraft 26.x only, while the locked Paper file explicitly includes Paper 1.20.1
through 26.2. Its committed metadata source is
`https://api.modrinth.com/v2/version/Len451or`.
`https://api.modrinth.com/v2/version/aINSQrXC`.

### What is actually counted

Expand Down Expand Up @@ -424,13 +424,16 @@ or substitutes a stub named `CraftEngine`.

```powershell
python perf/live/download_locked.py `
--artifact craftengine-paper-26.7.3 `
--destination build/live-cache/craft-engine-paper-plugin-26.7.3.jar
--artifact craftengine-paper-26.7.4 `
--destination build/live-cache/craft-engine-paper-plugin-26.7.4.jar
python perf/live/run_server.py `
--paper-jar build/live-cache/server.jar `
--plugin-jar build/libs/mahjong-paper-1.5.0.jar `
--craftengine-jar build/live-cache/craft-engine-paper-plugin-26.7.3.jar `
--plugin-config perf/live/fixtures/mahjongpaper-smoke-config.yml `
--craftengine-jar build/live-cache/craft-engine-paper-plugin-26.7.4.jar `
--required-plugin MahjongPaper `
--setup-command 'execute at MahjongPerfBot run fill ~-16 ~-1 ~-16 ~16 ~-1 ~16 minecraft:stone' `
--setup-command 'execute at MahjongPerfBot run fill ~-16 ~ ~-16 ~16 ~10 ~16 minecraft:air' `
--scenario-name majsoul-hanchan-four-bot-spectator `
--scenario-command "/mahjong botmatch MAJSOUL_HANCHAN" `
--scenario-ready-pattern 'Round .+ \| Turn .+ \| Wall [1-9][0-9]* \| Spectators 1' `
Expand All @@ -449,11 +452,11 @@ and must observe the committed ready pattern before warmup and measurement begin
traffic is a hard failure, not a fallback to an empty-server keepalive run.

The standalone measurement-infrastructure PR deliberately does not execute this plugin profile
as its required pull-request check. The current foundation baseline does not expose Caffeine to
the plugin classloader on a fresh Paper instance, so the real plugin profile correctly stops at
the required-plugin enablement check. That dependency blocker must be fixed in the foundation
change and then exercised by a stacked run; this harness does not patch the plugin descriptor,
inject a fake dependency, or turn the known failure green.
as its required pull-request check. Plugin runs use the committed fixture config to disable game
room placement restrictions, persistence and ranking equally for base and candidate, then create
a flat, clear table footprint relative to the connected protocol client before starting the
botmatch. The harness still loads the real locked CraftEngine plugin and does not patch plugin
metadata or inject a fake dependency.

### Measurement rules

Expand All @@ -480,6 +483,7 @@ run-manifest.json hashes, versions, ports, timings, shutdown res
paper-summary.json derived TPS/MSPT sample distributions
raw/paper-samples.jsonl exact timestamped Paper command responses plus parsed values
raw/server-stdout.log complete server console output
raw/inputs/plugin-config.yml exact staged plugin fixture configuration, when supplied
raw/instance/logs/latest.log Paper's own latest.log
raw/runtime-jars.json SHA-256 of server/plugin/runtime jars actually loaded
raw/protocol-client/packets.jsonl one record per externally observed Minecraft frame
Expand Down
226 changes: 226 additions & 0 deletions perf/ab/gate-config.json
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,88 @@
"top/ellan/mahjong/runtime/ServerScheduler.class"
]
},
"riichi-reaction": {
"include": "^top\\.ellan\\.mahjong\\.riichi\\.RiichiReactionBenchmark\\.(?:structurallyImpossibleCall|structurallyImpossibleRonOnly|firstPossiblePonAnalysis)$",
"benchmark_ids": [
"riichi.impossible.time",
"riichi.impossible.alloc",
"riichi.ron-only.time",
"riichi.ron-only.alloc",
"riichi.pon.time",
"riichi.pon.alloc"
],
"minimum_passes": 2,
"must_pass": [
"riichi.impossible.time",
"riichi.ron-only.time"
],
"required_classes": [
"top/ellan/mahjong/riichi/RiichiPlayerAnalysisState.class",
"top/ellan/mahjong/riichi/RiichiPlayerState.class"
]
},
"sichuan-hu": {
"include": "^top\\.ellan\\.mahjong\\.table\\.core\\.round\\.SichuanHuEvaluatorBenchmark\\.(?:waitingTilesStandardHand|evaluateStandardWin|canWinWithFixedMeld)$",
"benchmark_ids": [
"sichuan.waiting.time",
"sichuan.waiting.alloc",
"sichuan.evaluate.time",
"sichuan.evaluate.alloc",
"sichuan.can-win.time",
"sichuan.can-win.alloc"
],
"minimum_passes": 3,
"must_pass": [
"sichuan.waiting.time",
"sichuan.evaluate.time",
"sichuan.can-win.time"
],
"required_classes": [
"top/ellan/mahjong/table/core/round/SichuanHuEvaluator.class"
]
},
"region-apply": {
"include": "^top\\.ellan\\.mahjong\\.table\\.render\\.RegionRenderApplyBenchmark\\.(?:planAndDeferRepresentativeTableRegions|applyStableStartedTable|precomputeCompleteStartedTableRegions)$",
"benchmark_ids": [
"region.apply-deferred.time",
"region.apply-deferred.alloc",
"region.apply-stable.time",
"region.apply-stable.alloc",
"region.complete-fingerprints.time",
"region.complete-fingerprints.alloc"
],
"minimum_passes": 3,
"must_pass": [
"region.apply-deferred.time",
"region.apply-stable.time",
"region.complete-fingerprints.time"
],
"required_classes": [
"top/ellan/mahjong/table/render/TableRegionDisplayCoordinator.class",
"top/ellan/mahjong/table/render/TableRegionFingerprintService.class"
]
},
"layout-cache": {
"include": "^top\\.ellan\\.mahjong\\.table\\.core\\.SessionRenderLayoutCacheBenchmark\\.(?:stableSessionCacheHit|representativeSeatComponentMiss|representativeWallComponentMiss)$",
"benchmark_ids": [
"layout-cache.hit.time",
"layout-cache.hit.alloc",
"layout-cache.seat-miss.time",
"layout-cache.seat-miss.alloc",
"layout-cache.wall-miss.time",
"layout-cache.wall-miss.alloc"
],
"minimum_passes": 3,
"must_pass": [
"layout-cache.hit.time",
"layout-cache.seat-miss.time",
"layout-cache.wall-miss.time"
],
"required_classes": [
"top/ellan/mahjong/render/layout/TableRenderLayout.class",
"top/ellan/mahjong/table/core/MahjongTableSession.class"
]
},
"ray-proxy": {
"include": "^top\\.ellan\\.mahjong\\.perf\\.RayProxyCoordinatorBenchmark\\.proxyPlan(?:1Viewer|4Viewers|32Viewers)$",
"benchmark_ids": [
Expand Down Expand Up @@ -414,6 +496,150 @@
"metric": "gc.alloc.rate.norm",
"direction": "lower"
},
{
"id": "riichi.impossible.time",
"benchmark": "top.ellan.mahjong.riichi.RiichiReactionBenchmark.structurallyImpossibleCall",
"metric": "primary",
"direction": "lower"
},
{
"id": "riichi.impossible.alloc",
"benchmark": "top.ellan.mahjong.riichi.RiichiReactionBenchmark.structurallyImpossibleCall",
"metric": "gc.alloc.rate.norm",
"direction": "lower"
},
{
"id": "riichi.ron-only.time",
"benchmark": "top.ellan.mahjong.riichi.RiichiReactionBenchmark.structurallyImpossibleRonOnly",
"metric": "primary",
"direction": "lower"
},
{
"id": "riichi.ron-only.alloc",
"benchmark": "top.ellan.mahjong.riichi.RiichiReactionBenchmark.structurallyImpossibleRonOnly",
"metric": "gc.alloc.rate.norm",
"direction": "lower"
},
{
"id": "riichi.pon.time",
"benchmark": "top.ellan.mahjong.riichi.RiichiReactionBenchmark.firstPossiblePonAnalysis",
"metric": "primary",
"direction": "lower"
},
{
"id": "riichi.pon.alloc",
"benchmark": "top.ellan.mahjong.riichi.RiichiReactionBenchmark.firstPossiblePonAnalysis",
"metric": "gc.alloc.rate.norm",
"direction": "lower"
},
{
"id": "sichuan.waiting.time",
"benchmark": "top.ellan.mahjong.table.core.round.SichuanHuEvaluatorBenchmark.waitingTilesStandardHand",
"metric": "primary",
"direction": "lower"
},
{
"id": "sichuan.waiting.alloc",
"benchmark": "top.ellan.mahjong.table.core.round.SichuanHuEvaluatorBenchmark.waitingTilesStandardHand",
"metric": "gc.alloc.rate.norm",
"direction": "lower"
},
{
"id": "sichuan.evaluate.time",
"benchmark": "top.ellan.mahjong.table.core.round.SichuanHuEvaluatorBenchmark.evaluateStandardWin",
"metric": "primary",
"direction": "lower"
},
{
"id": "sichuan.evaluate.alloc",
"benchmark": "top.ellan.mahjong.table.core.round.SichuanHuEvaluatorBenchmark.evaluateStandardWin",
"metric": "gc.alloc.rate.norm",
"direction": "lower"
},
{
"id": "sichuan.can-win.time",
"benchmark": "top.ellan.mahjong.table.core.round.SichuanHuEvaluatorBenchmark.canWinWithFixedMeld",
"metric": "primary",
"direction": "lower"
},
{
"id": "sichuan.can-win.alloc",
"benchmark": "top.ellan.mahjong.table.core.round.SichuanHuEvaluatorBenchmark.canWinWithFixedMeld",
"metric": "gc.alloc.rate.norm",
"direction": "lower"
},
{
"id": "region.apply-deferred.time",
"benchmark": "top.ellan.mahjong.table.render.RegionRenderApplyBenchmark.planAndDeferRepresentativeTableRegions",
"metric": "primary",
"direction": "lower"
},
{
"id": "region.apply-deferred.alloc",
"benchmark": "top.ellan.mahjong.table.render.RegionRenderApplyBenchmark.planAndDeferRepresentativeTableRegions",
"metric": "gc.alloc.rate.norm",
"direction": "lower"
},
{
"id": "region.apply-stable.time",
"benchmark": "top.ellan.mahjong.table.render.RegionRenderApplyBenchmark.applyStableStartedTable",
"metric": "primary",
"direction": "lower"
},
{
"id": "region.apply-stable.alloc",
"benchmark": "top.ellan.mahjong.table.render.RegionRenderApplyBenchmark.applyStableStartedTable",
"metric": "gc.alloc.rate.norm",
"direction": "lower"
},
{
"id": "region.complete-fingerprints.time",
"benchmark": "top.ellan.mahjong.table.render.RegionRenderApplyBenchmark.precomputeCompleteStartedTableRegions",
"metric": "primary",
"direction": "lower"
},
{
"id": "region.complete-fingerprints.alloc",
"benchmark": "top.ellan.mahjong.table.render.RegionRenderApplyBenchmark.precomputeCompleteStartedTableRegions",
"metric": "gc.alloc.rate.norm",
"direction": "lower"
},
{
"id": "layout-cache.hit.time",
"benchmark": "top.ellan.mahjong.table.core.SessionRenderLayoutCacheBenchmark.stableSessionCacheHit",
"metric": "primary",
"direction": "lower"
},
{
"id": "layout-cache.hit.alloc",
"benchmark": "top.ellan.mahjong.table.core.SessionRenderLayoutCacheBenchmark.stableSessionCacheHit",
"metric": "gc.alloc.rate.norm",
"direction": "lower"
},
{
"id": "layout-cache.seat-miss.time",
"benchmark": "top.ellan.mahjong.table.core.SessionRenderLayoutCacheBenchmark.representativeSeatComponentMiss",
"metric": "primary",
"direction": "lower"
},
{
"id": "layout-cache.seat-miss.alloc",
"benchmark": "top.ellan.mahjong.table.core.SessionRenderLayoutCacheBenchmark.representativeSeatComponentMiss",
"metric": "gc.alloc.rate.norm",
"direction": "lower"
},
{
"id": "layout-cache.wall-miss.time",
"benchmark": "top.ellan.mahjong.table.core.SessionRenderLayoutCacheBenchmark.representativeWallComponentMiss",
"metric": "primary",
"direction": "lower"
},
{
"id": "layout-cache.wall-miss.alloc",
"benchmark": "top.ellan.mahjong.table.core.SessionRenderLayoutCacheBenchmark.representativeWallComponentMiss",
"metric": "gc.alloc.rate.norm",
"direction": "lower"
},
{
"id": "ray.viewers1.time",
"benchmark": "top.ellan.mahjong.perf.RayProxyCoordinatorBenchmark.proxyPlan1Viewer",
Expand Down
20 changes: 10 additions & 10 deletions perf/live/artifacts.lock.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,13 @@
},
"source_metadata": "https://fill.papermc.io/v3/projects/paper/versions/1.20.1/builds"
},
"craftengine-paper-26.7.3": {
"craftengine-paper-26.7.4": {
"kind": "server-plugin",
"project": "craftengine",
"distribution": "paper",
"version": "26.7.3",
"version": "26.7.4",
"release_type": "beta",
"modrinth_version_id": "Len451or",
"modrinth_version_id": "aINSQrXC",
"loaders": [
"folia",
"paper",
Expand Down Expand Up @@ -53,18 +53,18 @@
"26.1.2",
"26.2"
],
"filename": "craft-engine-paper-plugin-26.7.3.jar",
"url": "https://cdn.modrinth.com/data/tRX6FMfQ/versions/Len451or/craft-engine-paper-plugin-26.7.3.jar",
"size": 8907932,
"filename": "craft-engine-paper-plugin-26.7.4.jar",
"url": "https://cdn.modrinth.com/data/tRX6FMfQ/versions/aINSQrXC/craft-engine-paper-plugin-26.7.4.jar",
"size": 8970694,
"hash": {
"algorithm": "sha512",
"digest": "e87351417e4f99504cf0a8868bad03a88b12bc9bda5c36f0ee4c7451e3525f08146b2130b61cd34b222ee41c38ca75aa9ced9ada65bc3540c80aa66c3d39d689"
"digest": "1d6982e325552fd51a9a481252e0c055c32a5b6e3c2d816cf69ca4b2f355a5f7af1abed00ff9d29c08fb32ff293d9d11837ad4372c34675b73682a98b1ec86b0"
},
"published_hashes": {
"sha1": "467fa7317635cf9f76812bd73a47d984c57cf53f",
"sha512": "e87351417e4f99504cf0a8868bad03a88b12bc9bda5c36f0ee4c7451e3525f08146b2130b61cd34b222ee41c38ca75aa9ced9ada65bc3540c80aa66c3d39d689"
"sha1": "179e7b8b191093e41beca56e3d52ae608f46e161",
"sha512": "1d6982e325552fd51a9a481252e0c055c32a5b6e3c2d816cf69ca4b2f355a5f7af1abed00ff9d29c08fb32ff293d9d11837ad4372c34675b73682a98b1ec86b0"
},
"source_metadata": "https://api.modrinth.com/v2/version/Len451or"
"source_metadata": "https://api.modrinth.com/v2/version/aINSQrXC"
}
}
}
19 changes: 19 additions & 0 deletions perf/live/fixtures/mahjongpaper-smoke-config.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Deterministic live-performance fixture. Base and candidate runs must use this exact file.
database:
enabled: false

tables:
persistence:
enabled: false

gameRooms:
enabled: false
restrictNewTables: false
enterExitMessages: false

ranking:
enabled: false

integrations:
craftengine:
exportBundleOnEnable: true
Loading