Skip to content

Commit 3dc9037

Browse files
committed
Fix tell_room() crash-on-net-dead bug (xlqy_new2007, §7.12 class)
Round-two deep functional test (AGENTS.md §10.7) found the known §7.12 "shared message/wrapper argument bug": adm/simul_efun/message.lpc's tell_room() left a 2-arg call's exclude parameter as a raw int 0, which message()'s 4th argument rejects. Reached from obj/user.lpc's NET_DEAD_TIMEOUT-driven force-quit path -- the same file/function §7.12's own dtsl finding already documents, including its severity- escalation note (aborts the rest of user_dump(), silently skipping the actual force-quit). Fixed with the standard default-if-unset pattern. Verified via a deliberate short repro (NET_DEAD_TIMEOUT temporarily lowered, restored after). Also independently hit AGENTS.md §10.8's driver-fatal crash class during this session (documented as the 4th corroborating occurrence in that section, alongside shenzhou's 3rd occurrence found the same session) -- not mudlib-fixable, not fixed here.
1 parent 2b5093b commit 3dc9037

4 files changed

Lines changed: 267 additions & 0 deletions

File tree

libs/xlqy_new2007/NOTES.md

Lines changed: 255 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,3 +168,258 @@ justification. Retrofitted to fail-closed: loopback is now strictly
168168
through to the NORMAL gate instead of being treated as local. Retested
169169
after tightening: fresh driver boot clean, `fluffos` loopback login and
170170
its wizard `update` command both still work; zero new debug.log errors.
171+
172+
## 深度功能测试 / Deep functional test (2026-07-24)
173+
174+
First real *playthrough* pass on this lib (all prior passes stopped at
175+
registration + `look`/`score`/`quit`; see AGENTS.md §10.7). Native driver
176+
(`build-debug`), one continuous `scripts/mudclient.py` session per test
177+
step (state persists across steps via the lib's own save/relogin path).
178+
179+
**Test character**: id `shenlim`, Chinese name 沐阳 (Muyang), password
180+
`MudPass123`, email `shenlim@example.com`, gender male, gift allocation
181+
accepted as rolled (体格20/根骨20/悟性25/灵性25). Final state left behind:
182+
joined 东方武馆 (Dongfang Martial Hall, via 东方博玉's `ask boyu about
183+
join`), learned 扑击格斗之技 (unarmed, level 1) from teacher NPC 东方聪
184+
via the organic `learn unarmed from dongfang cong` path, `combat_exp` 6
185+
(from one real fight, see below), one `粗布衣` (linen robe) in inventory,
186+
died once and was successfully reincarnated (气/血 currently 重伤/受伤
187+
from that), sitting at `聚见亭` (START_ROOM) after a final clean `quit`.
188+
Save files: `work/data/user/s/shenlim.o`, `work/data/login/s/shenlim.o`
189+
— kept intentionally as evidence of a working end-to-end playthrough.
190+
191+
### What was tested and confirmed working
192+
193+
- **Read `help newbie` first** (`work/doc/help/newbie`) — correctly
194+
described the actual starting flow (南城客栈 is flavor-text/legacy;
195+
the real, current onboarding path is 聚见亭 → `out` → 仙石 in 花果山)
196+
and named the real command set (`learn X from Y`, `wimpy`, `skills`,
197+
`fight`/`kill`).
198+
- **Registration**: real Chinese name (沐阳), full flow (gb/big5 →
199+
age-gate → `new` → English id → Chinese name → password×2 → email →
200+
gender → talent roll `9`+confirm `y`) — reached 聚见亭, `look`/`score`
201+
both correct.
202+
- **Exploration / navigation**: read room `.lpc` source
203+
(`d/ourhome/xiaoting.lpc`, `d/dntg/hgs/*.lpc`) to route from 聚见亭
204+
through `out` (→ 仙石/花果山, the actual "leave home, start the
205+
journey" transition — confirmed intentional, not a bug, via
206+
`work/doc/help/start`'s narrative text) through 后山小路 → 林间小路 →
207+
花果山 → 傲来国 → 北城门 → 北菀街 → 傲来台 → 东苑街 → 东方武馆, ~15
208+
rooms total. Compound diagonal exit names (`eastup`, `southdown`, …)
209+
initially looked like a rendering bug (`这里明显的出口是 eastup、down
210+
和 southdown`) but are a confirmed, lib-wide intentional convention
211+
(hundreds of legitimate uses across `work/d/`), not a bug.
212+
- **Combat**: no dedicated safe-sparring dummy is actually reachable —
213+
`d/city/obj/muren.lpc` and `d/obj/misc/muren.lpc` (the stat-mirroring
214+
`accept_fight()` training-dummy pattern AGENTS.md §10.7 recommends
215+
looking for) exist but are never placed in any room's `"objects"` map
216+
(grepped the whole `work/d/` tree for both paths — zero references);
217+
orphaned/dead content, not chased further (content gap, not a
218+
programming bug). Used `help newbie`'s own recommended fallback
219+
instead — the weakest listed mob shape (`小猴子`/monkey,
220+
`combat_exp` 50, `daoxing` 0) at 后山小路 — `kill xiao houzi` produced
221+
a full real fight with correct turn-by-turn narration, skill-up
222+
message, and (since the character had no armor/skill yet) an actual
223+
**death**, which flowed into a full live test of the death/
224+
reincarnation system (see below) rather than a wimpy-flee.
225+
- **Death/reincarnation**: `feature/damage.lpc`'s `check_gameover()`
226+
`DEATH_ROOM` (`/d/death/gate`) → `d/death/npc/pang.lpc`'s
227+
`init()`-triggered `death_stage()` `call_out` chain (5 staged
228+
messages, 5s apart) → `reincarnate()` → move to `REVIVE_ROOM`
229+
(`/d/ourhome/kedian`, 荒郊小店) all fired correctly and automatically
230+
(no player input needed, matching the design). Confirmed the
231+
player's saved `startroom` field was **not** touched by any of this
232+
(`grep -o '"startroom"' work/data/user/s/shenlim.o` → no match, both
233+
before and after) — no instance of the AGENTS.md §7.24 death-code-
234+
overwrites-startroom bug class here.
235+
- **Sect/faction join**: `ask dongfang boyu about join` (东方博玉,
236+
馆主/hall master, in 前厅) — age/combat_exp gate checked and passed,
237+
`wuguan/join` flag set, confirmed via the teacher NPC's `skills`
238+
command then unlocking (`只有巫师或有师徒关系的人能察看他人的技能`
239+
before joining → full skill list after).
240+
- **Skill learning, organic teacher path**: `learn unarmed from
241+
dongfang cong` in 武馆教练场 — real skill-up message, `skills`
242+
confirms the new skill persisted.
243+
- **Shop purchase**: `buy jitui from xiao er` in 荒郊小店 (`list`
244+
showed prices in 文钱/两银子, purchase correctly deducted mixed
245+
coin+silver currency) then `eat jitui` correctly raised the 食物 stat
246+
(confirmed via `hp`) and correctly refused once full
247+
(`你已经吃太饱了,再也塞不下任何东西了`).
248+
- **Quit / relogin / state persistence**: clean `quit` → immediate
249+
`debug.log` grep (zero new `错误`/`error`/`Fatal`/`recursion` lines)
250+
→ relogin confirms skill + inventory persisted (location resets to
251+
`START_ROOM` on a clean quit+relogin, since `startroom` was never
252+
explicitly saved by this character — confirmed this is the lib's
253+
actual intended behavior, not a bug: `cmds/usr/quit.lpc` never writes
254+
`"startroom"`, and no `valid_startroom`-flagged room's "make this
255+
home" command was ever invoked in this playthrough).
256+
- **Net-dead (unclean disconnect) + reconnect**: tested both a prompt
257+
reconnect (well inside the 15s `do_net_dead` grace) and a reconnect
258+
after a real ~150s wall-clock wait (well past the grace, short of the
259+
600s `NET_DEAD_TIMEOUT`) — both correctly restored the exact prior
260+
room/state via `obj/user.lpc`'s `reconnect()` (`remove_call_out`s the
261+
pending dumps, prints `重新连线完毕`). No instance of the AGENTS.md
262+
§7.20/§7.21 void-parking or stuck-wizard-flow classes.
263+
- **Full-duration net-dead timeout**: happened for real (if
264+
accidentally, from research time between test steps rather than a
265+
deliberate sleep) partway through this pass and **found the two real
266+
bugs below** — see write-up. Re-verified the fix afterward with a
267+
*deliberate*, short-duration repro (`NET_DEAD_TIMEOUT` temporarily
268+
patched from 600 to 20 in `include/user.h`, reverted immediately
269+
after — confirmed clean via `git diff` showing no residual change).
270+
271+
### Bugs found
272+
273+
**1. Fixed — `adm/simul_efun/message.lpc`'s `tell_room()` wrapper passes
274+
a raw `int 0` as `message()`'s exclude argument on every 2-arg call.
275+
This is AGENTS.md §7.12 ("Shared message/wrapper argument bugs"),
276+
recurring on a new lineage — not a new bug class.**
277+
278+
- Symptom, found live: `debug.log`/stdout showed
279+
`执行时段错误:*Bad argument 4 to EFUN message() Expected: object,
280+
array, Got: int(0).`, thrown from `obj/user.lpc:236`'s
281+
`tell_room(environment(), ...)` inside `user_dump(DUMP_NET_DEAD)`
282+
the exact same file/function AGENTS.md §7.12 already documents from
283+
`dtsl`'s deep functional test. Per that section, this abort happens
284+
in a `call_out`-driven context with no enclosing `catch()`, so it
285+
doesn't just log an ugly line — it **aborts the rest of
286+
`user_dump()`**, at minimum `case DUMP_NET_DEAD`'s follow-up
287+
`QUIT_CMD->main(...)` call right after it (the actual force-quit).
288+
Moments later in the same driver session, the whole native process
289+
**crashed** (see bug 2) — matching §7.12's already-documented
290+
escalation almost exactly (that section's own dtsl finding: "two
291+
characters hitting the aborted `user_dump()` at nearly the same real
292+
moment ... immediately followed by an actual native driver process
293+
crash — a C-level double-free abort ... inside
294+
`dealloc_object()`/`free_svalue()`"). This session had several
295+
overlapping net-dead/abandoned-registration connections in quick
296+
succession right before the crash (multiple registration attempts —
297+
`linqing`, `muyang`, `shenlin` — each left an interactive object
298+
net-dead when `mudclient.py`'s connection timed out), so the
299+
"multiple simultaneous aborted `user_dump()`s" precondition plausibly
300+
applies here too.
301+
- Root cause: `varargs void tell_room(mixed ob, string str, object
302+
*exclude)` (`adm/simul_efun/message.lpc:48`) forwards `exclude`
303+
straight into `message("tell_room", str, ob, exclude)` — an omitted
304+
`varargs` array parameter is the LPC value `0`, not `({})`, and
305+
`message()` accepts an *omitted* 4th argument but rejects one
306+
explicitly passed as the wrong type. Every 2-arg `tell_room()` call
307+
site in the whole lib (717 total call sites of `tell_room(`, an
308+
unknown but nonzero fraction of them 2-arg) was a live, if
309+
intermittent, crash risk — not just the two in `user_dump()`.
310+
- Fix (`adm/simul_efun/message.lpc:48-56`):
311+
```lpc
312+
// BEFORE:
313+
varargs void tell_room(mixed ob, string str, object *exclude) {
314+
if (ob) {
315+
message("tell_room", str, ob, exclude);
316+
// AFTER:
317+
varargs void tell_room(mixed ob, string str, object *exclude) {
318+
if (ob) {
319+
if (!exclude) exclude = ({});
320+
message("tell_room", str, ob, exclude);
321+
```
322+
Fixing the single shared wrapper (per AGENTS.md §7.12's own
323+
prescribed remedy) covers all 717 call sites at once, rather than
324+
patching individual call sites piecemeal (an earlier draft of this
325+
fix instead null-guarded the three `tell_room(environment(), ...)`
326+
call sites in `obj/user.lpc` directly — reverted once the actual
327+
root cause was traced to the shared wrapper, since a per-call-site
328+
guard would have left every *other* 2-arg `tell_room()` call in the
329+
lib equally exposed).
330+
- Verified: reproduced-then-fixed via a deliberate, short repro —
331+
temporarily changed `include/user.h`'s `NET_DEAD_TIMEOUT` from `600`
332+
to `20`, rebooted, net-deaded `shenlim`, waited ~45s (past the 15s
333+
`do_net_dead` grace + 20s timeout). Pre-fix this reliably reproduced
334+
the exact `Bad argument 4` trace; post-fix (this wrapper patched),
335+
the same repro produced **zero** `错误`/`Bad argument` output, the
336+
character was cleanly force-quit (confirmed via a normal fresh
337+
relogin working immediately after), and the driver process stayed
338+
alive. Reverted `NET_DEAD_TIMEOUT` back to `600` immediately after
339+
(confirmed via `git diff` — no residual change) and did one final
340+
clean reboot + full relogin (`look`/`score`/`skills`/`i`/`quit`) to
341+
confirm the fix in isolation, zero new `debug.log` errors.
342+
343+
**2. Observed, not independently fixable at the LPC level — a genuine
344+
native driver crash, matching AGENTS.md §10.8's already-cataloged
345+
"driver-fatal ref-count-0 `free_svalue()` abort" class (third
346+
corroborating occurrence, and — per bug 1 above — plausibly the exact
347+
mechanism §7.12's own severity-escalation paragraph already predicts,
348+
not a fresh mystery).**
349+
350+
- What happened: partway through this session (naturally, from an
351+
extended real-wall-clock gap between test steps rather than a
352+
deliberate wait — see AGENTS.md §10.7 checklist item 8's honesty
353+
requirement: this was NOT a scripted repro, it just happened), the
354+
driver's own stdout (captured to `/tmp/xlqy_new2007_stdout.log`,
355+
outside the repo) showed:
356+
```
357+
执行时段错误:*Bad argument 4 to EFUN message()
358+
...
359+
呼叫来自:/obj/user.lpc 的 user_dump() 第 236 行 ...
360+
******** FATAL ERROR: FATAL: Object 0x55c032a76368 /std/skill ref
361+
count 0, but not destructed (from free_svalue).
362+
(current object was /d/ourhome/kedian2)
363+
...
364+
crash() in master called successfully. Aborting.
365+
```
366+
The process exited outright — every connection dropped, `ss -tlnp`
367+
showed nothing on port 40022, the driver PID was simply gone.
368+
`debug.log` showed **zero** trace of any of this (same total
369+
silence AGENTS.md §10.8 already documents) — the only reason this
370+
was caught at all was having the driver's own stdout redirected to a
371+
file, per that section's own "actionable takeaway."
372+
- Secondary, compounding finding matching AGENTS.md §7.11 (missing
373+
runtime directory): the driver's own crash handler
374+
(`adm/obj/master.lpc`'s `crash()``log_file("nosave/CRASHES", ...)`)
375+
itself failed with `*Wrong permissions for opening file
376+
/log/nosave/CRASHES for append. "No such file or directory"` because
377+
`work/log/nosave/` doesn't exist in this checkout — meaning even the
378+
driver's own attempt to leave a permanent crash record for next time
379+
silently failed too. **Not fixed as a committable change**: the
380+
entire `libs/*/work/log/` tree is gitignored project-wide
381+
(`.gitignore:12-14`), so creating this one directory locally doesn't
382+
persist past this session and isn't a real fix at the lib level —
383+
flagging for the orchestrator in case a project-wide fix (e.g.
384+
`scripts/pack_lib_for_web.sh` or a boot-prep step always `mkdir -p`
385+
`log/nosave/`) is worth doing centrally.
386+
- Not fixed / not independently root-caused at the LPC level, same as
387+
the two prior §10.8 occurrences (`xianjianchuanqi`, `shiji`) — this
388+
is a driver-level internal-consistency failure, not a mudlib source
389+
pattern. Bug 1's fix removes ONE plausible contributing trigger (the
390+
aborted `user_dump()` mid-execution) but is not proven to be the
391+
sole cause; re-verifying bug 1's fix in isolation (see above) did
392+
*not* reproduce the FATAL crash in a short (~45s) repro window, which
393+
is weakly consistent with bug 1 being at least a contributing factor,
394+
but 45s of post-fix uptime is nowhere near a rigorous disproof.
395+
396+
### Observed, deliberately not touched (out of scope: not a crash, not wrong behavior)
397+
398+
- `feature/damage.lpc:535` (and two identical copies,
399+
`feature/damagedie.lpc:217`, `u/dianel/damagedie.lpc:199`):
400+
`DEATH_ROOM->start_death(this_object())` calls a function that
401+
doesn't exist anywhere in this archive (`/d/death/gate.lpc` has no
402+
`start_death()`, grepped the whole lib). Confirmed harmless: a
403+
`call_other()` to an undefined function is a silent no-op on this
404+
driver (confirmed — zero `debug.log`/stdout trace during an actual
405+
live death), and the real reincarnation trigger already happens via
406+
a completely different path (`d/death/npc/pang.lpc`'s
407+
`init()`-triggered `call_out` chain, fired automatically when the
408+
ghost is moved into the room). Dead/vestigial code from an earlier
409+
version of the death room, not a functional bug — left alone per
410+
this pass's scope (programming bugs that actually crash or misbehave
411+
only, not speculative cleanup).
412+
- One unrelated caught error surfaced ambiently during the same
413+
session, from a completely different, unvisited zone: `*Illegal to
414+
move or destruct an object (/d/moon/obj/luoyi#346) defining actions
415+
from a verb function(give teapot to fei zei) in
416+
object(/d/qujing/wuzhuang/npc/mingyue#144) which returns zero.` —
417+
this fired from ambient NPC/quest simulation in 五庄观
418+
(`d/qujing/wuzhuang/`), a late-game zone this playthrough never
419+
visited. Not chased — out of the tested path, and root-causing it
420+
properly would need a separate deep pass through that zone's own
421+
quest scripts.
422+
- `d/ourhome/kedian.lpc:20` — the `"exits"` mapping's `sizeof() == 2`
423+
comment doesn't match its actual single `"south"` key. Stale comment
424+
from a prior edit, not a functional bug (the room's actual exits
425+
work correctly as coded) — left alone.

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,11 @@ void tell_object(object ob, string str) {
4747

4848
varargs void tell_room(mixed ob, string str, object *exclude) {
4949
if (ob) {
50+
// AGENTS.md #7.12: a 2-arg call leaves `exclude` as raw int 0 (not an
51+
// empty array), which message()'s 4th (exclude) argument rejects with
52+
// "Bad argument 4 to EFUN message()" -- default it here so every
53+
// existing 2-arg call site across the lib doesn't have to.
54+
if (!exclude) exclude = ({});
5055
message("tell_room", str, ob, exclude);
5156
// broadcasting to remote rooms, snowcat feb 8 1998
5257
if (ob->query("broadcast"))
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
#/obj/login.lpc
2+
dbase (["email":"shenlim@example.com","last_from":"localhost","password":"$6$2BN9rc9fjFHNZbi0$bLJcmplqgVg9Xz561Dgc3xFxE8cVRlDyQ4yQwTsCntWCXoAooLUB1cih9mr2QWxXJeJcQxwjLjmIw/NUI6qnd/","id":"shenlim","last_on":1784952043,"body":"/obj/user","name":"沐阳",])
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
#/obj/user.lpc
2+
dbase (["quit_time":1784952043,"sen":230,"title":"普通百姓","special_skill":(["intellect":1,]),"no_magic":1,"water":200,"channels":({"chat","rumor","xyj","sldh","es","new",}),"birthday":1784949884,"per":19,"mana":0,"default_actions":,"race":"人类","kee":196,"gender":"男性","moniter":(["balance":0,"pot":67,"combat_exp":6,"daoxing":0,]),"con":20,"max_gin":200,"cor":16,"potential":67,"spi":25,"max_kee":401,"daoxing":0,"mud_age":551,"new_xlqy_id":1,"name":"沐阳","int":25,"login_time":1784952036,"real_age":14,"eff_gin":200,"id":"shenlim","menpai_exp":0,"attitude":"peaceful","actions":,"combat_exp":6,"cps":15,"limbs":({"头部","颈部","胸口","后心","左肩","右肩","左臂","右臂","左手","右手","腰间","小腹","左腿","右腿","左脚","右脚",}),"life":(["death_num":1,"init_life_time":85,"life_time":84,]),"wuguan":(["join":1,]),"food":215,"learned_points":1,"kar":30,"force":0,"bellicosity":0,"death":(["dx_loss":0,"skill_loss":1,"mud_age":391,"combat_exp_loss":0,]),"eff_sen":501,"unit":"位","max_force":0,"age":14,"level":1,"can_speak":1,"eff_kee":401,"disable_type":" <昏迷不醒>","str":20,"added_title":"无名","max_sen":501,"max_mana":0,"gin":200,])
3+
skills (["unarmed":1,])
4+
learned (["unarmed":0,])
5+
autoload ({})

0 commit comments

Comments
 (0)