Skip to content

Commit bcccf93

Browse files
authored
fix: harden gameplay and client runtime integration (#78)
Validated by GitHub Actions across Linux, Windows, macOS, native GB integration, JMH smoke, standard server.jar smoke, and the paired performance regression gate.
1 parent c3a1b5d commit bcccf93

225 files changed

Lines changed: 28821 additions & 6696 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.gitattributes

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
* text=auto eol=lf
2+
3+
*.bat text eol=crlf
4+
*.cmd text eol=crlf
5+
6+
*.dat binary
7+
*.dll binary
8+
*.dylib binary
9+
*.jar binary
10+
*.png binary
11+
*.so binary
12+
*.zip binary

.github/workflows/build.yml

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ jobs:
4646
# Gate the build on tests so a native-platform build never ships a jar
4747
# with broken Java/Kotlin. Native tasks are skipped here; they are
4848
# exercised in the build-platform matrix below.
49-
run: ./gradlew test "-PmahjongPaperDevBundle=1.20.1-R0.1-SNAPSHOT" "-PmahjongJavaToolchain=25" "-PmahjongJavaTarget=17" -x configureGbMahjongNative -x buildGbMahjongNative -x packageGbMahjongNative
49+
run: ./gradlew test "-PmahjongPaperDevBundle=1.20.1-R0.1-SNAPSHOT" "-PmahjongJavaToolchain=25" "-PmahjongJavaTarget=21" -x configureGbMahjongNative -x buildGbMahjongNative -x packageGbMahjongNative
5050

5151
build-platform:
5252
needs: test-gate
@@ -126,7 +126,7 @@ jobs:
126126
# Pin to the lowest supported Paper baseline so the released jar stays runtime-compatible
127127
# with Paper/Folia 1.20.1 -> 26.2. PaperCompatibility handles newer APIs at runtime.
128128
# Quote each -P argument because PowerShell on Windows runners mis-parses `1.20.1-R0.1-SNAPSHOT`.
129-
run: ${{ matrix.gradle }} build -x test "-PmahjongPaperDevBundle=1.20.1-R0.1-SNAPSHOT" "-PmahjongJavaTarget=17" "-PmahjongJavaToolchain=25"
129+
run: ${{ matrix.gradle }} build "-PmahjongPaperDevBundle=1.20.1-R0.1-SNAPSHOT" "-PmahjongJavaTarget=21" "-PmahjongJavaToolchain=25" "-PmahjongRequireNative=true"
130130

131131
- name: Verify artifact compatibility (Linux only)
132132
if: runner.os == 'Linux'
@@ -147,7 +147,7 @@ jobs:
147147
python3 - "$JAR" <<'PY'
148148
import sys, zipfile, struct
149149
jar = sys.argv[1]
150-
allowed_max = 61 # Java 17
150+
allowed_max = 65 # Java 21
151151
bad = []
152152
with zipfile.ZipFile(jar) as z:
153153
for n in z.namelist():
@@ -161,11 +161,11 @@ jobs:
161161
if major > allowed_max:
162162
bad.append((n, major))
163163
if bad:
164-
print(f'::error::Found {len(bad)} class files compiled above Java 17:')
164+
print(f'::error::Found {len(bad)} class files compiled above Java 21:')
165165
for n, m in bad[:20]:
166166
print(f' {n}: major={m} (Java {m-44})')
167167
sys.exit(1)
168-
print('All classes compiled to Java 17 or lower.')
168+
print('All classes compiled to Java 21 or lower.')
169169
PY
170170
171171
- name: Upload platform jar
@@ -232,6 +232,18 @@ jobs:
232232
(cd extract/macos && jar xf "$GITHUB_WORKSPACE/$MACOS_JAR" native)
233233
jar uf "$UNIVERSAL_JAR" -C extract/windows native -C extract/macos native
234234
235+
jar tf "$UNIVERSAL_JAR" > universal-entries.txt
236+
for required in \
237+
native/linux-x86_64/mahjongpaper_gb.so \
238+
native/windows-x86_64/mahjongpaper_gb.dll \
239+
native/windows-x86_64/libwinpthread-1.dll \
240+
native/macos-x86_64/mahjongpaper_gb.dylib; do
241+
if ! grep -Fxq "$required" universal-entries.txt; then
242+
echo "::error::Universal jar is missing $required"
243+
exit 1
244+
fi
245+
done
246+
235247
- name: Upload universal jar
236248
uses: actions/upload-artifact@v7
237249
with:

.github/workflows/release.yml

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ jobs:
7777
# Pin to the lowest supported Paper baseline so the released jar stays runtime-compatible
7878
# with Paper/Folia 1.20.1 -> 26.2. PaperCompatibility handles newer APIs at runtime.
7979
# Quote each -P argument because PowerShell on Windows runners mis-parses `1.20.1-R0.1-SNAPSHOT`.
80-
run: ${{ matrix.gradle }} build -x test "-PmahjongPaperDevBundle=1.20.1-R0.1-SNAPSHOT" "-PmahjongJavaTarget=17" "-PmahjongJavaToolchain=25"
80+
run: ${{ matrix.gradle }} build "-PmahjongPaperDevBundle=1.20.1-R0.1-SNAPSHOT" "-PmahjongJavaTarget=21" "-PmahjongJavaToolchain=25" "-PmahjongRequireNative=true"
8181

8282
- name: Verify artifact compatibility (Linux only)
8383
if: runner.os == 'Linux'
@@ -98,7 +98,7 @@ jobs:
9898
python3 - "$JAR" <<'PY'
9999
import sys, zipfile, struct
100100
jar = sys.argv[1]
101-
allowed_max = 61 # Java 17
101+
allowed_max = 65 # Java 21
102102
bad = []
103103
with zipfile.ZipFile(jar) as z:
104104
for n in z.namelist():
@@ -112,11 +112,11 @@ jobs:
112112
if major > allowed_max:
113113
bad.append((n, major))
114114
if bad:
115-
print(f'::error::Found {len(bad)} class files compiled above Java 17:')
115+
print(f'::error::Found {len(bad)} class files compiled above Java 21:')
116116
for n, m in bad[:20]:
117117
print(f' {n}: major={m} (Java {m-44})')
118118
sys.exit(1)
119-
print('All classes compiled to Java 17 or lower.')
119+
print('All classes compiled to Java 21 or lower.')
120120
PY
121121
122122
- name: Upload platform jar
@@ -183,6 +183,18 @@ jobs:
183183
(cd extract/macos && jar xf "$GITHUB_WORKSPACE/$MACOS_JAR" native)
184184
jar uf "$UNIVERSAL_JAR" -C extract/windows native -C extract/macos native
185185
186+
jar tf "$UNIVERSAL_JAR" > universal-entries.txt
187+
for required in \
188+
native/linux-x86_64/mahjongpaper_gb.so \
189+
native/windows-x86_64/mahjongpaper_gb.dll \
190+
native/windows-x86_64/libwinpthread-1.dll \
191+
native/macos-x86_64/mahjongpaper_gb.dylib; do
192+
if ! grep -Fxq "$required" universal-entries.txt; then
193+
echo "::error::Universal jar is missing $required"
194+
exit 1
195+
fi
196+
done
197+
186198
- name: Upload universal jar
187199
uses: actions/upload-artifact@v7
188200
with:
@@ -238,6 +250,22 @@ jobs:
238250
fi
239251
echo "file=$NOTES_FILE" >> "$GITHUB_OUTPUT"
240252
253+
- name: Verify release version metadata
254+
shell: bash
255+
run: |
256+
set -euo pipefail
257+
VERSION="${{ steps.tag.outputs.tag }}"
258+
VERSION="${VERSION#v}"
259+
BUILD_VERSION="$(sed -n 's/^version = "\([^"]*\)"/\1/p' build.gradle.kts | head -n 1)"
260+
if [[ -z "$BUILD_VERSION" || "$BUILD_VERSION" != "$VERSION" ]]; then
261+
echo "::error::Release tag version $VERSION does not match build.gradle.kts version $BUILD_VERSION"
262+
exit 1
263+
fi
264+
if [[ ! -f CHANGELOG.md ]] || ! grep -Eq "^## ${VERSION//./\\.}([[:space:]]|$)" CHANGELOG.md; then
265+
echo "::error::CHANGELOG.md has no section for $VERSION"
266+
exit 1
267+
fi
268+
241269
- name: Download universal jar
242270
uses: actions/download-artifact@v8
243271
with:

.github/workflows/test.yml

Lines changed: 140 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,18 +26,24 @@ jobs:
2626
- os: ubuntu-latest
2727
gradle: ./gradlew
2828
needs_chmod: true
29+
require_native: true
30+
native_args: ""
2931
# Full test suite on Linux (primary platform).
3032
test_task: test jacocoTestReport
3133
- os: windows-latest
3234
gradle: .\gradlew.bat
3335
needs_chmod: false
36+
require_native: false
37+
native_args: -x configureGbMahjongNative -x buildGbMahjongNative -x packageGbMahjongNative
3438
# Smoke test only on Windows: run tests but skip native build (CMake
3539
# toolchain is set up in build.yml). Catches platform-specific
3640
# regressions like NamespacedKey casing and File.separator usage.
3741
test_task: test
3842
- os: macos-15-intel
3943
gradle: ./gradlew
4044
needs_chmod: true
45+
require_native: false
46+
native_args: -x configureGbMahjongNative -x buildGbMahjongNative -x packageGbMahjongNative
4147
test_task: test
4248

4349
runs-on: ${{ matrix.os }}
@@ -66,20 +72,20 @@ jobs:
6672
run: chmod +x ./gradlew
6773

6874
- name: Cache native build (CMake)
75+
if: runner.os == 'Linux'
6976
uses: actions/cache@v6
7077
with:
7178
path: |
7279
build/native/gbmahjong
7380
native/gbmahjong/build
7481
key: native-cmake-${{ runner.os }}-${{ hashFiles('native/gbmahjong/**/CMakeLists.txt', 'native/gbmahjong/**/*.cpp', 'native/gbmahjong/**/*.h') }}
75-
restore-keys: |
76-
native-cmake-${{ runner.os }}-
7782

7883
- name: Run tests
7984
# Pin to the lowest supported Paper baseline so the released jar stays
80-
# runtime-compatible with Paper/Folia 1.20.1 -> 26.2. Skip native tasks
81-
# on non-Linux runners where the CMake toolchain may not be configured.
82-
run: ${{ matrix.gradle }} ${{ matrix.test_task }} "-PmahjongPaperDevBundle=1.20.1-R0.1-SNAPSHOT" "-PmahjongJavaToolchain=25" "-PmahjongJavaTarget=17" -x configureGbMahjongNative -x buildGbMahjongNative -x packageGbMahjongNative
85+
# runtime-compatible with Paper/Folia 1.20.1 -> 26.2. Linux builds and
86+
# requires the current native source; other platforms exercise Java
87+
# behavior without restoring an unrelated native binary.
88+
run: ${{ matrix.gradle }} ${{ matrix.test_task }} "-PmahjongPaperDevBundle=1.20.1-R0.1-SNAPSHOT" "-PmahjongJavaToolchain=25" "-PmahjongJavaTarget=21" "-PmahjongRequireNative=${{ matrix.require_native }}" ${{ matrix.native_args }}
8389

8490
- name: Upload test reports
8591
if: always()
@@ -98,3 +104,132 @@ jobs:
98104
path: |
99105
build/reports/jacoco/test/html/**
100106
build/reports/jacoco/test/jacocoTestReport.xml
107+
108+
performance:
109+
needs: wrapper-validation
110+
runs-on: ubuntu-latest
111+
env:
112+
PERF_RUNS: "3"
113+
PERF_WARMUPS: "8"
114+
PERF_ITERATIONS: "15"
115+
PERF_REGRESSION_THRESHOLDS: "render.snapshot.create.started_session=25;render.region_fingerprints.precompute.started_snapshot=25;render.layout.precompute.started_snapshot=30"
116+
117+
steps:
118+
- name: Checkout
119+
uses: actions/checkout@v7
120+
with:
121+
fetch-depth: 0
122+
123+
- name: Set up JDK 25
124+
uses: actions/setup-java@v5
125+
with:
126+
distribution: temurin
127+
java-version: "25"
128+
129+
- name: Set up Gradle
130+
uses: gradle/actions/setup-gradle@v6
131+
132+
- name: Make Gradle wrapper executable
133+
run: chmod +x ./gradlew
134+
135+
- name: Prepare comparison baseline
136+
shell: bash
137+
env:
138+
PULL_REQUEST_BASE_SHA: ${{ github.event.pull_request.base.sha }}
139+
PUSH_BEFORE_SHA: ${{ github.event.before }}
140+
run: |
141+
base_sha="$PULL_REQUEST_BASE_SHA"
142+
if [[ -z "$base_sha" ]]; then
143+
base_sha="$PUSH_BEFORE_SHA"
144+
fi
145+
if [[ -z "$base_sha" || "$base_sha" =~ ^0+$ ]] || ! git cat-file -e "$base_sha^{commit}"; then
146+
base_sha="$(git rev-parse HEAD^)"
147+
fi
148+
baseline_worktree="$RUNNER_TEMP/mahjong-perf-baseline"
149+
git worktree add --detach "$baseline_worktree" "$base_sha"
150+
chmod +x "$baseline_worktree/gradlew"
151+
echo "PERF_BASE_SHA=$base_sha" >> "$GITHUB_ENV"
152+
echo "PERF_BASELINE_WORKTREE=$baseline_worktree" >> "$GITHUB_ENV"
153+
154+
- name: Run paired performance benchmarks
155+
shell: bash
156+
run: |
157+
set -euo pipefail
158+
comparison_dir="$GITHUB_WORKSPACE/build/perf-comparison"
159+
mkdir -p "$comparison_dir/baseline" "$comparison_dir/candidate"
160+
161+
run_benchmark() {
162+
local project_dir="$1"
163+
local series="$2"
164+
local run_id="$3"
165+
rm -rf \
166+
"$project_dir/build/reports/performance" \
167+
"$project_dir/build/reports/tests/perfTest" \
168+
"$project_dir/build/test-results/perfTest"
169+
(
170+
cd "$project_dir"
171+
./gradlew perfTest \
172+
"-PperfWarmups=$PERF_WARMUPS" \
173+
"-PperfIterations=$PERF_ITERATIONS" \
174+
"-PmahjongPaperDevBundle=1.20.1-R0.1-SNAPSHOT" \
175+
"-PmahjongJavaToolchain=25" \
176+
"-PmahjongJavaTarget=21" \
177+
-x configureGbMahjongNative \
178+
-x buildGbMahjongNative \
179+
-x packageGbMahjongNative \
180+
--no-build-cache \
181+
--console plain
182+
)
183+
cp "$project_dir/build/reports/performance/results.json" \
184+
"$comparison_dir/$series/run-$run_id.json"
185+
cp "$project_dir/build/reports/performance/results.md" \
186+
"$comparison_dir/$series/run-$run_id.md"
187+
}
188+
189+
for run_id in $(seq 1 "$PERF_RUNS"); do
190+
if (( run_id % 2 == 1 )); then
191+
run_benchmark "$PERF_BASELINE_WORKTREE" baseline "$run_id"
192+
run_benchmark "$GITHUB_WORKSPACE" candidate "$run_id"
193+
else
194+
run_benchmark "$GITHUB_WORKSPACE" candidate "$run_id"
195+
run_benchmark "$PERF_BASELINE_WORKTREE" baseline "$run_id"
196+
fi
197+
done
198+
199+
- name: Check performance regressions
200+
run: >-
201+
./gradlew perfRegressionCheck
202+
"-PperfBaselineDir=${{ github.workspace }}/build/perf-comparison/baseline"
203+
"-PperfCandidateDir=${{ github.workspace }}/build/perf-comparison/candidate"
204+
"-PperfRegressionMinRuns=${{ env.PERF_RUNS }}"
205+
"-PperfRegressionThresholds=${{ env.PERF_REGRESSION_THRESHOLDS }}"
206+
"-PperfWarmups=${{ env.PERF_WARMUPS }}"
207+
"-PperfIterations=${{ env.PERF_ITERATIONS }}"
208+
"-PmahjongPaperDevBundle=1.20.1-R0.1-SNAPSHOT"
209+
"-PmahjongJavaToolchain=25"
210+
"-PmahjongJavaTarget=21"
211+
-x configureGbMahjongNative
212+
-x buildGbMahjongNative
213+
-x packageGbMahjongNative
214+
--console plain
215+
216+
- name: Add performance summary
217+
if: always()
218+
shell: bash
219+
run: |
220+
if [[ -f build/reports/performance/regression.md ]]; then
221+
cat build/reports/performance/regression.md >> "$GITHUB_STEP_SUMMARY"
222+
fi
223+
224+
- name: Upload performance reports
225+
if: always()
226+
uses: actions/upload-artifact@v7
227+
with:
228+
name: mahjong-performance-reports
229+
path: |
230+
build/reports/performance/**
231+
build/perf-comparison/**
232+
build/reports/tests/perfTest/**
233+
build/test-results/perfTest/**
234+
build/reports/tests/perfRegressionCheck/**
235+
build/test-results/perfRegressionCheck/**

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
/buildSrc/.gradle/
66
/buildSrc/.kotlin/
77
/buildSrc/build/
8+
/buildSrc/.codex-build/
89
/logs/
910
/_upstream/
1011
/.tmp-gb-ref/

0 commit comments

Comments
 (0)