Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
6 changes: 3 additions & 3 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -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` で確認
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down
1 change: 0 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -487,7 +487,6 @@ if(TARGET cppfm_embedded_resources)
endif()
add_test(NAME smoke80 COMMAND test_smoke_80 $<TARGET_FILE:cppfm>
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
Expand Down
55 changes: 53 additions & 2 deletions docs/CURRENT_STATE.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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.
2 changes: 1 addition & 1 deletion docs/VERIFICATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 5 additions & 1 deletion tests/test_goal_live_remaining.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
32 changes: 23 additions & 9 deletions tests/test_plan43.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@
#include <chrono>
#include <thread>
#include <cmath>
#include <unordered_set>

using namespace cppfm;
using namespace cpptest;
Expand Down Expand Up @@ -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
// <ms; snapshotting after the send would swallow our own horse).
std::unordered_set<std::int32_t> 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));
}
Expand Down Expand Up @@ -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);
Expand All @@ -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) {
Expand Down
12 changes: 7 additions & 5 deletions tests/test_smoke_80.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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");
Expand Down
Loading