diff --git a/AGENTS.md b/AGENTS.md index 8d83ab4c..3ced1009 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -41,7 +41,7 @@ } - 同一worktreeでの重複起動は絶対に避ける (被ると競合)。 - 研究完了前に実装を開始しない。 - - **すべてのコマンドに確実なタイムアウトを付与** (`test_smoke_80` は子プロセス `cppfm` を fork するため親だけを殺すと孤児化)。例: `timeout --foreground --kill-after=5 120 cmake -B build -G Ninja` / `timeout --foreground --kill-after=5 300 cmake --build build -j2` / `timeout --foreground --kill-after=5 450 ./build/test_smoke_80 ./build/cppfm`。終了確認は `pgrep -a -f 'cppfm --por[t]'` の後、対象が確認できた場合だけ `timeout --foreground --kill-after=5 10 pkill -9 -f 'cppfm --por[t]'` を使う。`ctest -R smoke80 --timeout 450` でも可。 + - **すべてのコマンドに確実なタイムアウトを付与** (`test_smoke_80` は子プロセス `cppfm` を fork するため親だけを殺すと孤児化)。例: `timeout --foreground --kill-after=5 120 cmake -B build -G Ninja` / `timeout --foreground --kill-after=5 300 cmake --build build -j2` / `timeout --foreground --kill-after=5 600 ./build/test_smoke_80 ./build/cppfm`。終了確認は `pgrep -a -f 'cppfm --por[t]'` の後、対象が確認できた場合だけ `timeout --foreground --kill-after=5 10 pkill -9 -f 'cppfm --por[t]'` を使う。`ctest -R smoke80 --timeout 600` でも可。 4. 並行で開発させ、すべてが終わったあと diff をレビューしマージ - `git diff main --stat` と `git log --oneline --graph` で確認 @@ -99,7 +99,7 @@ timeout --foreground --kill-after=5 60 ./build/test_native ./build/cppfm timeout --foreground --kill-after=5 30 ./build/test_scoreboard_reset # 22/22 PASS expected (ctest scoreboard_reset TIMEOUT 30) timeout --foreground --kill-after=5 60 ./build/test_spec_wire # 392 PASS 0 FAIL 0 SKIP (wire byte-identical lock; 実績は CURRENT_STATE §2) timeout --foreground --kill-after=5 30 ./build/test_fuzz # 23 PASS 0 FAIL (fuzz 23 cases, TIMEOUT 30) -timeout --foreground --kill-after=5 450 ./build/test_smoke_80 ./build/cppfm # 212 PASS 0 FAIL (実績は CURRENT_STATE §2) — 450s (600s under load) +timeout --foreground --kill-after=5 600 ./build/test_smoke_80 ./build/cppfm # 225 PASS 0 FAIL (実績は CURRENT_STATE §2) pgrep -a -f 'cppfm --por[t]' || true timeout --foreground --kill-after=5 10 pkill -9 -f 'cppfm --por[t]' 2>/dev/null || true timeout --foreground --kill-after=5 5 sleep 1 @@ -111,7 +111,7 @@ timeout --foreground --kill-after=5 400 python3 tests/soak_test.py --duration 30 timeout --foreground --kill-after=5 60 python3 tools/bench_chunk_gen.py --view-distance 32 --chunks 4225 --dry --strict # plan45 O-11 view32 # nightly 24h (plan45 O-06 — ctest外): nohup python3 tests/soak_test.py --duration 86400 --binary ./build/cppfm > /tmp/soak24h.log 2>&1 & ctest -R "native|scoreboard_reset|spec_wire|fuzz" --output-on-failure --timeout 60 -ctest -R smoke80 --output-on-failure --timeout 450 # 600 under load +ctest -R smoke80 --output-on-failure --timeout 600 ``` ## 6. Architecture Quick Map diff --git a/CMakeLists.txt b/CMakeLists.txt index 37e6f722..22b3a7fc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -487,7 +487,6 @@ if(TARGET cppfm_embedded_resources) endif() add_test(NAME smoke80 COMMAND test_smoke_80 $ WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}) -set_tests_properties(smoke80 PROPERTIES TIMEOUT 450) # plan51 §1–2: generation-safe handle and selective routing invariants. add_executable(test_jvm_handles diff --git a/docs/CURRENT_STATE.md b/docs/CURRENT_STATE.md index c27aafc6..a3aad6f7 100644 --- a/docs/CURRENT_STATE.md +++ b/docs/CURRENT_STATE.md @@ -82,7 +82,7 @@ artifact boundaries. | Official primitive oracle | `INSPECTED` | SHA-1-pinned Mojang 1.21.4 server artifact; VarInt, VarLong, Varint21 frame splitter, and packed BlockPos decoder/packing bytecode reviewed, not executed | | Primitive and stream regressions | `PASS` | `test_goal_network_bugs` 79/79; independent signed endpoints, non-minimal forms, Java terminal-payload truncation, generic sixth/eleventh-byte rejection/consumption, strict three-byte Varint21 frame rejection, plaintext/encrypted stream alignment, big-endian fixed-width fields, and packed Position axis limits | | Related CTest targets | `7/7 PASS` | `native`, `spec_wire`, `jvm_native_bridge`, `core_safety`, `goal_network_bugs`, `wire_full`, and `fuzz` | -| Smoke regression after tick-based chat pacing fix | `1/1 PASS` | `ctest --test-dir build -R smoke80 --output-on-failure --timeout 450`; 181.26 seconds, `225 PASS / 0 FAIL`. Reset feedback has a 30-second bounded wait and prints chat/disconnect diagnostics only on failure. Actions for prior SHA `54e7ad9` missed this feedback under the old 8-second wait after observing 120 ticks; the current PR-head Actions result is authoritative for verifying this test-only mitigation. | +| Smoke regression after tick-based chat pacing fix | `PASS LOCALLY; CI HARDENING IN FOLLOW-UP` | `ctest --test-dir build -R smoke80 --output-on-failure --timeout 600`; last recorded local run was 181.26 seconds, `225 PASS / 0 FAIL`. Reset feedback has a 30-second bounded wait and prints chat/disconnect diagnostics only on failure. A later Actions run exposed that the target-specific `TIMEOUT 450` overrode the workflow-wide `--timeout 600`; the follow-up removes that override and lowers the random-tick stress value while keeping it above the vanilla default. | | PR Actions gate | `PASS / SHA-SPECIFIC` | Combined head `bac90dfbce70a8d1c09ddf525b339fac479ff350` passed the full build and non-nightly CTest ([run 35828644414](https://github.com/nico2525nn/cpp-fabricmc/actions/runs/35828644414)); subsequent review changes in this branch still require a new exact-head run. | MISSING #71's row-specific evidence is now `PASS` in the 90-row coverage ledger @@ -424,7 +424,9 @@ baseline and protected manifest, a 90-row coverage ledger (`14 PASS`, `38 PARTIA with focused regression tests. The PR #1 review completed five hostile rounds and a final follow-up; the current verdict is `P0=0, P1=0, P2=0, P3=0` unresolved after the focused -fixes and 8/8 local CTest rerun. Exact-head CI is still pending. Source-order +fixes and 8/8 local CTest rerun. The exact PR-head Actions run +[`35865145988`](https://github.com/nico2525nn/cpp-fabricmc/actions/runs/35865145988) +passed before PR #1 was merged. Source-order guards cover security fixes where no authenticated client or external JVM fixture is available. Scoped production cleanup measured `-9` lines; the additional refactors are recorded in `goal-cleanup-runtime.md` and @@ -439,3 +441,52 @@ remaining-entry fixture passed `1/1` in `63.93s`, and the final two-launch protocol matrix passed with both owned processes returning `0` and no escalation. The requested 7200-second soak failed at `t=1200s` with server exit `-9`; no accepted long-soak result is claimed. + +The first post-merge main Actions run +[`35868844643`](https://github.com/nico2525nn/cpp-fabricmc/actions/runs/35868844643) +timed out `smoke80`; a same-SHA rerun completed the suite but exposed one +`goal_live_remaining` failure. That fixture shrank the world border while the +player was still far outside it, then issued a teleport; on a slower runner, +border damage could remove the command target before the teleport feedback was +observed. Locally, `smoke80` passed all 225 cases in `178.25s`, and the full +pre-fix non-nightly CTest run passed `55/55` in `522.97s`. The follow-up makes +the fixture teleport to its damage position before shrinking the border, then +checks the same border wire and damage effects; the targeted post-change test +passed `5/5` consecutive launches (`263.24s`). + +The PR #3 Actions run +[`35877465112`](https://github.com/nico2525nn/cpp-fabricmc/actions/runs/35877465112) +then exposed a separate `smoke80` issue: CTest reported `***Timeout 450.01 sec` +even though the workflow supplies `--timeout 600`, because the test had its own +`TIMEOUT 450` property. Its captured output reached the world-management cases, +then the block-tick scenario using `randomTickSpeed 100`; subsequent disconnect +and assertion failures were cascading output after the timeout. The follow-up +removes the target-specific timeout so the shared CTest limit applies and uses +`randomTickSpeed 10` (still above vanilla's default 3) with the existing 16×16 +crop field. This keeps the real random-tick behavior check while avoiding a +whole-simulation stress setting in the ordinary regression suite. + +The exact-head follow-up Actions run +[`35883033964`](https://github.com/nico2525nn/cpp-fabricmc/actions/runs/35883033964) +then completed the expanded CTest suite without the smoke timeout; one separate +`plan43` assertion (`W-02 m2/h0/s1 window opens`) failed, leaving the other 54 +tests passing. Investigation found that `summonHorseNear` accepted any horse +spawn observed after its snapshot, including an older horse from a delayed +initial entity stream. The test now snapshots the highest observed entity ID +and accepts only a horse with a larger ID. The revised `plan43` test passed five +consecutive local launches (`130.37s` total); the new PR head still requires its +own GitHub Actions run before merge. + +The following exact-head Actions run +[`35886973563`](https://github.com/nico2525nn/cpp-fabricmc/actions/runs/35886973563) +passed configure, build, static checks, and deterministic focused gates, but the +full CTest regression failed only in `plan43` (`W-02 atk0 lands (HurtAnimation +eid)`); the other 54 tests passed. This attack-only miss is distinct from the +earlier window assertion. The W-02 fixture previously began interaction as soon +as `join()` observed Join Game, before the initial chunk batch marked client +startup complete. It now waits for `ChunkBatchFinished` before selecting a +summoned horse and issuing interaction/attack packets, and exits that case +cleanly if setup fails. This is test-readiness hardening, not a production combat +change or proof of the failure's server-side cause. The revised `plan43` passed +10 consecutive local launches (`260.95s` total); the new PR head still requires +its own Actions run before merge. diff --git a/docs/VERIFICATION.md b/docs/VERIFICATION.md index a3aed1ce..599b9a8d 100644 --- a/docs/VERIFICATION.md +++ b/docs/VERIFICATION.md @@ -658,7 +658,7 @@ timeout --foreground --kill-after=5 120 python3 tests/bot_smoke.py --binary ./bu timeout --foreground --kill-after=5 400 python3 tools/soak_bot.py --duration 300 --binary ./build/cppfm timeout --foreground --kill-after=5 1200 ctest --test-dir build -LE 'nightly|package' --output-on-failure --timeout 600 timeout --foreground --kill-after=5 300 ctest --test-dir build -R 'native|scoreboard_reset|spec_wire|plan43|flood_net|fuzz|wire_full|gameplay_full|seed_parity|block_hardness_full|redstone_engine_full|mob_stats_full|mining_full|quality_audit|tautology_lint|mcproto_framing|bench|multi_client|bot_smoke|recipes_mirror|recovery|rcon_multi' --output-on-failure --timeout 120 -timeout --foreground --kill-after=5 600 ctest --test-dir build -R smoke80 --output-on-failure --timeout 450 +timeout --foreground --kill-after=5 700 ctest --test-dir build -R smoke80 --output-on-failure --timeout 600 ``` For cleanup, first inspect and then use exact PIDs; do not paste an unreviewed broad diff --git a/tests/test_goal_live_remaining.py b/tests/test_goal_live_remaining.py index db4658a6..655581eb 100644 --- a/tests/test_goal_live_remaining.py +++ b/tests/test_goal_live_remaining.py @@ -273,8 +273,12 @@ def run(binary: Path, artifact_root: Path) -> dict[str, object]: client.command("setblock 63 -60 50 minecraft:air", "Changed the block") border_before = len(client.packet_payloads) client.command("worldborder center 0 0", "Set world border center to 0.000000, 0.000000") - client.command("worldborder set 2", "Set world border to 2.000000 blocks wide") + # Move to the eventual damage position while the default border is + # still large. Shrinking it first leaves the player far outside the + # new border and can kill them before the following teleport command + # is processed on a slower runner. client.command("tp @s 10 -60 50", "Teleported 1 entity") + client.command("worldborder set 2", "Set world border to 2.000000 blocks wide") border_packets = client.packet_payloads[border_before:] border_packets += client.pump(2.2) border_health = [parse_health(payload) for packet_id, payload in border_packets diff --git a/tests/test_plan43.cpp b/tests/test_plan43.cpp index 28213c94..90a9aa47 100644 --- a/tests/test_plan43.cpp +++ b/tests/test_plan43.cpp @@ -18,7 +18,6 @@ #include #include #include -#include using namespace cppfm; using namespace cpptest; @@ -187,17 +186,20 @@ static std::int32_t summonHorseNear(TestClient& c, const char* who) { // NOTE: /summon takes NO position args (server spawns at player+(2,1,2)); // extra tokens fail brigadier parse, so send the bare command. (void)who; - // snapshot known horse eids BEFORE summoning (a warm server answers in - // known; - for (auto& s : c.spawns()) if (s.type == 63) known.insert(s.eid); + // Snapshot the entity-id high-water mark before summoning. A newly joined + // client's initial entity stream can still contain older horses after + // join() returns; treating any post-snapshot horse packet as ours makes + // this test race against that tail of the stream. + std::int32_t highestSeenEntityId = 0; + for (const auto& s : c.spawns()) + if (s.eid > highestSeenEntityId) highestSeenEntityId = s.eid; c.sendChatCommand("summon minecraft:horse"); auto deadline = std::chrono::steady_clock::now() + std::chrono::milliseconds(6000); while (std::chrono::steady_clock::now() < deadline) { auto ss = c.spawns(); std::int32_t found = -1; for (auto& s : ss) - if (s.type == 63 && !known.count(s.eid)) found = s.eid; // newest birth wins + if (s.type == 63 && s.eid > highestSeenEntityId) found = s.eid; if (found >= 0) return found; std::this_thread::sleep_for(std::chrono::milliseconds(100)); } @@ -248,7 +250,13 @@ static void tUseEntity(ServerProc& srv) { char msg[160]; snprintf(msg, sizeof(msg), "W-02 %s join", k.label); char nm[32]; snprintf(nm, sizeof(nm), "P43U%d", (int)(k.mouse * 4 + k.hand * 2 + (k.sneak ? 1 : 0))); - CHECK(c.connect("127.0.0.1", srv.port) && c.join(nm), msg); + const bool joined = c.connect("127.0.0.1", srv.port) && c.join(nm); + CHECK(joined, msg); + if (!joined) { c.close(); continue; } + const bool chunksReady = waitInitialChunks(c); + snprintf(msg, sizeof(msg), "W-02 %s initial chunk batch complete", k.label); + CHECK(chunksReady, msg); + if (!chunksReady) { c.close(); continue; } std::int32_t eid = summonHorseNear(c, k.label); snprintf(msg, sizeof(msg), "W-02 %s horse summoned", k.label); CHECK(eid >= 0, msg); @@ -267,9 +275,15 @@ static void tUseEntity(ServerProc& srv) { for (int s = 0; s < 2; ++s) { TestClient c; char nm[32]; snprintf(nm, sizeof(nm), "P43A%d", s); - CHECK(c.connect("127.0.0.1", srv.port) && c.join(nm), "W-02 atk join"); - std::int32_t eid = summonHorseNear(c, s ? "atk1" : "atk0"); + const bool joined = c.connect("127.0.0.1", srv.port) && c.join(nm); + CHECK(joined, "W-02 atk join"); + if (!joined) { c.close(); continue; } + const bool chunksReady = waitInitialChunks(c); char msg[160]; + snprintf(msg, sizeof(msg), "W-02 atk%d initial chunk batch complete", s); + CHECK(chunksReady, msg); + if (!chunksReady) { c.close(); continue; } + std::int32_t eid = summonHorseNear(c, s ? "atk1" : "atk0"); snprintf(msg, sizeof(msg), "W-02 atk%d horse summoned", s); CHECK(eid >= 0, msg); if (eid >= 0) { diff --git a/tests/test_smoke_80.cpp b/tests/test_smoke_80.cpp index db107272..66a1c7aa 100644 --- a/tests/test_smoke_80.cpp +++ b/tests/test_smoke_80.cpp @@ -270,8 +270,8 @@ static void testBlockBehaviors(ServerProc& srv){ CHECK(waitBlockUpdate(c,4,-59,0,4333,2000), "wheat field age 0 is installed before the crop tick test"); c.clearChatLines(); - c.sendChatCommand("gamerule randomTickSpeed 100"); - CHECK(waitChat(c,"100",2000), "randomTickSpeed 100 command accepted"); + c.sendChatCommand("gamerule randomTickSpeed 10"); + CHECK(waitChat(c,"10",2000), "randomTickSpeed 10 command accepted"); const auto cropDeadline = std::chrono::steady_clock::now() + std::chrono::milliseconds(6000); bool grew = false; @@ -286,9 +286,11 @@ static void testBlockBehaviors(ServerProc& srv){ } } CHECK(grew,"wheat random tick with high randomTickSpeed changes the crop state"); - // The server's vanilla chat-spam budget decays in simulation ticks, not - // wall-clock time. 100 random ticks/section can delay the tick thread, so - // observe six time-sync packets (120 ticks) before resetting the rule. + // Random ticks apply to every simulated section, not only this field. Keep + // the elevated value modest (10 vs. vanilla's default 3) so this gameplay + // assertion does not become a world-wide CPU stress test on CI. The vanilla + // chat-spam budget decays in simulation ticks, so observe 120 ticks before + // resetting the rule. const int resetAfterTicks = c.counters().timeUpdates + 6; CHECK(waitForTimeUpdates(c, resetAfterTicks, 30000), "server advances 120 ticks before gamerule reset");