Skip to content

Commit bd1412d

Browse files
thefallentreeclaude
andcommitted
Fix tell_room() §7.12 crash in xiakexing100's deep functional test
adm/simul_efun/message.lpc: tell_room()'s 2-arg call form passed a bare int 0 for exclude, which message() rejects. Hit from clone/user/user.lpc's user_dump() (the 900s NET_DEAD_TIMEOUT force-quit handler), so a net-dead player who never reconnected would crash the handler and never actually get force-quit-and-saved. Live-reproduced by temporarily shortening the timeout, confirmed fixed, then reverted the timeout change. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VyQCUoTo1Z93Py9aVFHQi1
1 parent 9e77ce3 commit bd1412d

4 files changed

Lines changed: 178 additions & 1 deletion

File tree

libs/xiakexing100/NOTES.md

Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,3 +192,171 @@ further here). Test characters removed afterward; fluffos kept.
192192
Reverted an incidental `data/topten.o` leaderboard diff caused by the
193193
test registrations (pre-existing tracked file, not meant to churn from
194194
QA runs).
195+
196+
## 深度功能测试 / Deep functional test (AGENTS.md §10.7, 2026-07-24)
197+
198+
One continuous native session (`~/src/fluffos/build-debug/src/driver`,
199+
port 40037), following the §10.7 checklist end to end: read `help
200+
intro`/`help newbie`/`help newbie-basic`/`help newbie-shaolin1` first,
201+
registered a real Chinese name (`qftester` / 秦风, password `test1234`,
202+
born 嘉兴/group `3`, talents 20/20/20/20, personality `0`), landed at
203+
`/d/jiaxing/yanyu` ("烟雨楼"), `look`/`score`/`i`/`hp` all correct.
204+
Explored on foot toward 牛家村 reading room `.lpc` source
205+
(`d/jiaxing/*.lpc`) to navigate since exit lists intermittently hide
206+
under the lib's night-time darkness gate. Used the wizard `spgo`/
207+
`summon` commands (via the seeded `fluffos` admin) purely to fast-travel
208+
the SAME test character to distant content (少林寺 练武场/广场) rather
209+
than trekking the whole map by foot — the actual sect-join, skill-learn,
210+
combat, and shop interactions below were all performed by the ordinary
211+
player character, not the admin.
212+
213+
**Safe sparring**: the newbie guide's suggested targets (a village
214+
`小孩`/`女孩`) both declined `fight` — traced this to their `attitude`
215+
being genuinely `"friendly"` in their own `create()` (`/d/village/npc/
216+
kid.lpc`, `/d/village/npc/girl.lpc`), which `inherit/char/npc.lpc`'s
217+
`accept_fight()` is coded to always decline (`case "friendly": ...
218+
return 0;`) — working as designed, not a bug (a different, unrelated
219+
`/d/jiaxing/npc/boy.lpc`/`/d/village/npc/boy.lpc` with `attitude:
220+
"peaceful"` momentarily looked like a mismatch until the actual live
221+
object path was confirmed via `call obj->query(...)` — red herring, not
222+
reused). Used the lib's own documented, code-confirmed-safe wooden-dummy
223+
mechanic instead (`d/shaolin/obj/mu-ren.lpc`, `accept_fight()` checks
224+
`me->query("damaged")`/a `random(fight_times)>=10` break-chance, exactly
225+
the §10.7-item-3 shape): `fight mu` at `/d/shaolin/wuchang1` exchanged
226+
several rounds of blows with correct combat text, `halt` ended it
227+
cleanly, `hp`/`i` afterward correct, zero `debug.log` errors.
228+
229+
**Skill/sect join**: followed `help newbie-shaolin1`'s documented path —
230+
`apprentice biqiu` (清为比丘 at `/d/shaolin/guangchang1w`) immediately
231+
accepted as a 俗家弟子/少林派第四十一代弟子 (no manual `ask ... about
232+
出家`/`kneel` needed for this particular monk); `score` showed 师父/称谓
233+
updated correctly; `skills biqiu` listed the master's own skill roster;
234+
`xue biqiu dodge 1` (organic NPC-teacher path) advanced 基本躲闪 from
235+
unlearned to `1/0`, `cha` confirmed it. Both the sect-join and
236+
skill-learn steps worked correctly, no separate admin-command shortcut
237+
was tested (none is needed/expected in this lib — `apprentice` IS the
238+
join mechanism).
239+
240+
**`quit` → debug.log → reconnect (§10.7 items 5, 8, 9)**: clean `quit`
241+
produced no warning (confirms the pre-existing `savequit()` fix from the
242+
"Bonus fix" section above still holds) and left zero new `debug.log`
243+
lines. Net-dead (dropped the connection without `quit`) and reconnected
244+
PROMPTLY (a few seconds later): worked correctly, position/state intact.
245+
**Net-dead and NOT reconnecting until the real timeout elapsed found a
246+
new, live-reproduced bug — see below.** Separately, clean `quit`
247+
followed by a genuine ~75-real-second wall-clock gap, then a FULL fresh
248+
login (not a net-dead reconnect) via `qftester`/`test1234`: correctly
249+
restored sect membership, skill, and all `score` fields from the
250+
on-disk save file (`data/user/q/qftester.o``family` mapping,
251+
`skills`/`learned` mappings, `dbase.qi`/`jing` etc. all intact).
252+
253+
**Shop purchase (§10.7 item 6)**: character had no starting funds
254+
("你没有存款"), so used the admin account to `clone /clone/money/coin`,
255+
`call coin->set_amount(500)`, `spgive qftester coin` (funding only —
256+
the purchase itself was performed by the ordinary player character).
257+
`list` at 烟雨楼's 店小二 showed the menu correctly; `buy jitui` (30
258+
文) succeeded, correctly made change into higher denominations (500
259+
铜钱 → 1 烤鸡腿 + 4 两白银 + 70 文铜钱); `eat jitui` consumed it. No
260+
errors.
261+
262+
**Not verified live**: progression to a real death/respawn cycle — the
263+
session budget went to the net-dead bug investigation below instead;
264+
code review only (`d/city/ghost*.lpc`/鬼门关 revival flow referenced by
265+
`help newbie-basic`'s〖死亡〗section was read but not exercised). A
266+
sect promotion (拜达摩/升辈) is a multi-hour undertaking per the
267+
newbie guide's own admission and was not attempted.
268+
269+
### Bug found and fixed: `tell_room()` 2-arg wrapper crashes the net-dead force-quit handler — matches AGENTS.md §7.12 exactly (severity-escalated instance)
270+
271+
`adm/simul_efun/message.lpc`'s `tell_room()` wrapper:
272+
273+
```lpc
274+
varargs void tell_room(mixed ob, string str, object *exclude) {
275+
if (ob) message("tell_room", str, ob, exclude); // BEFORE
276+
}
277+
```
278+
279+
Every 2-argument call site leaves `exclude` as LPC's default `int 0`
280+
(not an empty array), and the real `message()` efun's 4th argument
281+
rejects a bare `0`. `clone/user/user.lpc`'s `user_dump()` — the
282+
`NET_DEAD_TIMEOUT`-driven (900s / 15 real minutes, `include/user.h`)
283+
force-quit handler that runs when a net-dead player never reconnects —
284+
calls `tell_room(environment(), ..., "\n")` with only 2 args at its very
285+
first line (the DUMP_NET_DEAD case). This is byte-for-byte the same
286+
lib/function/case/timeout shape AGENTS.md §7.12 documents as
287+
"severity-escalated" from `dtsl`'s deep functional test: the crash
288+
aborts `user_dump()` at that exact statement, so the two lines after it
289+
(`"/cmds/usr/exit"->savequit(this_object())`) never run — **silently
290+
disabling the entire net-dead force-quit safety net**: a player who
291+
net-deads and never manually reconnects would stay alive in server
292+
memory forever (never actually saved-and-removed via this path) until a
293+
driver restart.
294+
295+
**Live-reproduced** (not just pattern-matched): temporarily lowered
296+
`NET_DEAD_TIMEOUT` from 900 to 12 (`include/user.h`), ran the wizard
297+
`update /clone/user/user` command to recompile it, connected as
298+
`qftester`, dropped the connection without `quit`, waited past the
299+
shortened timeout:
300+
301+
```
302+
执行时段错误:*Bad argument 4 to EFUN message()
303+
Expected: object, array, Got: int(0).
304+
程式:/adm/single/simul_efun.lpc 第 77 行
305+
呼叫来自:/clone/user/user.lpc 的 user_dump() 第 75 行,物件: /clone/user/user#105 ("秦风")
306+
呼叫来自:/adm/single/simul_efun.lpc 的 tell_room() 第 77 行
307+
```
308+
309+
**Fixed** (the standard AGENTS.md §7.12 wrapper fix, protects every
310+
2-arg call site in the lib at once, not just this one):
311+
312+
```lpc
313+
varargs void tell_room(mixed ob, string str, object *exclude) {
314+
if (ob) message("tell_room", str, ob, exclude || ({})); // AFTER
315+
}
316+
```
317+
318+
Re-verified with the same shortened-timeout methodology: no crash,
319+
`debug.log` clean, and `who` afterward showed only the admin online —
320+
`qftester` was correctly force-quit-and-saved this time (confirms the
321+
two previously-dead lines, including `savequit()`, now execute).
322+
Restored `NET_DEAD_TIMEOUT` to 900 and did a full clean driver restart
323+
before finishing (the test-only timeout value was never left in place).
324+
325+
**Note for the project**: two other confirmed "Century/`adm-single`"
326+
lineage siblings (AGENTS.md §11) — `shiji` and `xianjianchuanqi` — still
327+
carry the exact same unfixed 2-arg `tell_room()` wrapper in their own
328+
`adm/simul_efun/message.lpc` (checked via grep during this pass, not
329+
fixed — out of scope for this task, which is `xiakexing100`-only).
330+
`shujian2008`, `xiakexinzhuan2`, and `zhonghua2` (same family) already
331+
carry the fix from earlier passes. Worth a follow-up pass on `shiji`/
332+
`xianjianchuanqi` specifically for this bug given `xianjianchuanqi` is
333+
also the lib AGENTS.md §10.8 documents hitting an unrelated driver-fatal
334+
crash ~25 minutes into its own net-dead soak — the two are different
335+
bugs but both live in the same net-dead code path family.
336+
337+
### Observation, not fixed (design ambiguity, see §10.7's scope note): two independent day/night clocks can visibly contradict each other
338+
339+
`cmds/std/look.lpc`'s exit-listing darkness gate
340+
(`天色太黑了,你看不清明显的出路`) keys off `NATURE_D->game_time()`,
341+
which derives an in-game shichen (時辰) hour from a heavily-compressed
342+
calendar clock (`adm/daemons/natured.lpc`: `#define TIME_TICK
343+
((time()%(60*24*3600))*365)` — 60 mud-years pass every 60 real days,
344+
i.e. roughly 6 mud-hours per real minute, confirmed live: the session's
345+
in-game date advanced across several 时辰 within a couple of real
346+
minutes). The SEPARATE sun-position flavor line
347+
(`NATURE_D->outdoor_room_description()`) keys off `current_day_phase`,
348+
a `nosave` variable advanced by its own independent real-time
349+
`call_out("update_day_phase", ...)` cycle. These two clocks are not
350+
synchronized and were observed live disagreeing outright in the same
351+
room printout — "一轮火红的夕阳正徘徊在西方的地平线上"(sunset flavor
352+
text) immediately followed by "天色太黑了,你看不清明显的出路"
353+
(darkness gate, only fires for 亥/子/丑/寅时) moments apart, and at one
354+
point "太阳正高挂在西方的天空中"(afternoon flavor text) paired with the
355+
SAME darkness message. Purely cosmetic/exit-listing (movement in a
356+
known direction still works even when the list is hidden — confirmed by
357+
navigating through several "dark" rooms via directions read from
358+
source), and both clocks' formulas are pure LPC business logic with no
359+
encoding/efun/grammar involvement, so this is very plausibly authentic
360+
original-game behavior rather than anything this project's conversion
361+
introduced. Documented per §10.7's scope note rather than "fixed" by
362+
guessing which clock should govern the gate.

libs/xiakexing100/work/adm/simul_efun/message.lpc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ void tell_object(object ob, string str) {
7474
}
7575

7676
varargs void tell_room(mixed ob, string str, object *exclude) {
77-
if (ob) message("tell_room", str, ob, exclude);
77+
if (ob) message("tell_room", str, ob, exclude || ({}));
7878
}
7979

8080
void shout(string str) {
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
#/clone/user/login.lpc
2+
dbase (["registered":1,"email":"test@example.com","last_from":"localhost","password":"$6$f3K.TW8Zec6FnxUG$H3CVz1KFmfNZwCcWqELCNntqcHop9tPfIb3sWamBDuHd4aGbmJNOTy9LbuJC9Lq4Ze5ttnn1vfV/fbdlOzZq0/","id":"qftester","last_on":1784960731,"created_from":"localhost","base":(["int":20,"kar":26,"sta":30,"con":20,"hometown":"嘉兴","dex":20,"cor":14,"str":20,"spi":28,"cps":21,"area":"3","START_ROOM":"/d/jiaxing/yanyu","per":24,]),"body":"/clone/user/user","created_on":"Fri Jul 24 22:58:15 2026","name":"秦风",])
3+
wiz_ip ({"210.34.1.193","210.34.4.222","127.0.0.1","localhost",})
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
#/clone/user/user.lpc
2+
conditions (["autosave":2,])
3+
dbase (["max_jing":100,"qi":98,"title":"少林派第四十一代弟子","tili":100,"dex":20,"jing":92,"max_balance":500000000,"water":294,"pighead":0,"channels":({"chat","rumor","party","xkx","sing","es","snow",}),"per":24,"birthday":1784959095,"default_actions":,"max_tili":100,"race":"人类","gender":"男性","scborn":(["times":0,]),"character":"0","con":20,"eff_jing":100,"cor":14,"neili":0,"potential":99,"spi":28,"behavior_exp":0,"max_neili":0,"mud_age":612,"born":(["hometown":"嘉兴","area":"3",]),"name":"秦风","int":20,"registered":1,"sta":30,"last":(["lastquit":1784960731,]),"id":"qftester","actions":,"cps":21,"base":(["int":20,"kar":26,"con":20,"sta":30,"hometown":"嘉兴","dex":20,"str":20,"cor":14,"spi":28,"area":"3","cps":21,"START_ROOM":"/d/jiaxing/yanyu","per":24,]),"START_ROOM":"/d/jiaxing/yanyu","quest_exp":140,"food":334,"learned_points":1,"kar":26,"shen_type":0,"max_qi":100,"age":14,"str":20,"family":(["enter_time":1784959973,"fealty":0,"privs":0,"title":"弟子","master_id":"qingwei biqiu","family_name":"少林派","master_name":"清为比丘","generation":41,]),"shen":0,"eff_qi":100,])
4+
skills (["dodge":1,])
5+
learned (["dodge":0,])
6+
autoload ({"/clone/money/silver:4","/clone/money/coin:70",})

0 commit comments

Comments
 (0)