Skip to content

Commit b043ef5

Browse files
thefallentreeclaude
andcommitted
Fix silent reconnect-mid-wizard stall in sanjiechuanshuo + catalog new §7.37 class
d/wiz/init.lpc: get_start0() (a reconnect-triggered call_out meant to auto-resume a stalled gift-selection wizard) called me->command("start") -- but the real command() efun takes no object argument; this is a call_other to a method named "command" that is not defined anywhere on the player object. FluffOS silently no-ops an undefined call_other with no error anywhere, so a player reconnecting mid-wizard saw nothing at all after "重新连线完毕。" -- no menu, no error, no hint -- while typing the undocumented "start" command manually worked fine (confirmed live). Fixed by replicating the target logic directly with tell_object(me, ...) instead of write() (write() needs this_player() context, unset inside a call_out) instead of round-tripping through the broken call. Verified live: reconnecting to a mid-wizard account now auto-displays the gift menu within seconds. A fresh uninterrupted registration was independently confirmed to already work correctly end-to-end. Cataloged as AGENTS.md §7.37 (new class): calling ob->efun_name(...) where efun_name matches a real driver efun name, but no method of that name is defined on ob, silently no-ops with zero error trace anywhere -- distinct from §8.3a's DECL_PRIVATE demotion (there was never a function to dispatch to here at all). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VyQCUoTo1Z93Py9aVFHQi1
1 parent 23461be commit b043ef5

9 files changed

Lines changed: 166 additions & 3 deletions

File tree

AGENTS.md

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2160,6 +2160,56 @@ save/quit that same handler exists to guarantee.
21602160

21612161
---
21622162

2163+
### 7.37 Calling `ob->efun_name(...)` where `efun_name` matches a real driver efun, but no method of that name is actually defined on `ob`, silently no-ops
2164+
2165+
Found on `sanjiechuanshuo`'s deep functional test (§10.7): a room's
2166+
reconnect-triggered `call_out` tried to automatically resume a stalled
2167+
gift-selection wizard via `me->command("start")` — but the real
2168+
`command()` efun (`core.spec`: `int command(string)`) takes NO object
2169+
argument; it always operates on the current command-giver context.
2170+
`ob->command(str)` is call_other syntax, which requires `ob` to define
2171+
its OWN function literally named `command`, which nothing in this lib
2172+
did anywhere (confirmed via a lib-wide grep). This driver raises NO
2173+
error for a call_other to an undefined function — not to the caller,
2174+
not to debug.log, nowhere — it just silently returns 0. The result was
2175+
a completely silent dead end: a player reconnecting mid-wizard saw
2176+
"重新连线完毕。" and then nothing else, forever, with zero indication
2177+
anything was wrong, while the SAME verb typed directly by the player
2178+
(going through the driver's own normal command dispatch, which doesn't
2179+
depend on this broken call) worked perfectly fine — a discrepancy that
2180+
made this easy to initially misdiagnose as "the add_action registration
2181+
must be broken" when in fact it was fine.
2182+
2183+
This is a distinct trap from §8.3a (`private``DECL_HIDDEN` demotion
2184+
breaking a real, DEFINED function's dispatch) — here there was never
2185+
any function to dispatch to in the first place; the bug is confusing
2186+
"an efun that happens to share this name" with "a method call," a
2187+
mistake the compiler cannot catch because call_other targets are
2188+
resolved dynamically at runtime, not statically.
2189+
2190+
Detection: whenever you see `ob-><efun_name>(...)` for any of this
2191+
project's common efuns (`command`, `write`, `tell_object`, `message`,
2192+
etc. — check the real signature in `core.spec` or the relevant
2193+
package's `.spec` file), verify the target object actually defines a
2194+
same-named method (grep the whole lib, not just the obvious base
2195+
classes) before assuming the call does what its name suggests.
2196+
Reproduce live by exercising the actual caller path (here: reconnect
2197+
while mid-wizard, not just fresh registration) and watching for
2198+
complete silence with no debug.log trace — that combination (call
2199+
succeeds with no error, but visibly does nothing) is the tell.
2200+
2201+
Fix: replace the broken round-trip with a direct call to the underlying
2202+
logic using the already-available explicit target object, being
2203+
careful that anything relying on implicit `this_player()` context
2204+
(`write()`, and similarly `tell_object`-adjacent, ambient-target efuns)
2205+
gets rewritten to take the explicit target instead — `write()` and
2206+
friends silently target `this_player()`, which is typically unset
2207+
inside a `call_out`, so simply removing the broken `ob->command(...)`
2208+
call without ALSO auditing what it was trying to reach just moves the
2209+
silent-failure point one level deeper.
2210+
2211+
---
2212+
21632213
## 8. Login and registration flow bugs
21642214

21652215
Registration is where restoration succeeds or fails: it exercises the

libs/sanjiechuanshuo/NOTES.md

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,3 +125,69 @@ python3 ../../scripts/mudclient.py 127.0.0.1 40097 --timeout 15 \
125125
--send "you@example.com" --send "m" --send "9" --send "y" \
126126
--send "look" --send "score" --send "quit"
127127
```
128+
129+
## 深度功能测试 / Deep functional test (round two, AGENTS.md §10.7)
130+
131+
Native driver, port 40097, tested via raw socket (finer control over
132+
timing than `mudclient.py` for this lib's charset→age-check→id/New→
133+
Chinese-name→password×2→email→gender→gift-selection wizard). Test
134+
characters: `qfthree`/秦风三 (password `TestPassA`) is a clean, fully-
135+
completed registration used for the main playthrough; `qftester` and
136+
`qftwo` are two earlier registrations deliberately left stuck mid-gift-
137+
wizard (see bug below) and kept as before/after evidence.
138+
139+
**Bug found and fixed, live-reproduced and re-verified**: `d/wiz/init.lpc`'s
140+
`init()` schedules `call_out("get_start0", 0, me)` on every reconnect
141+
while the account's gift-selection wizard is still incomplete
142+
(`no_gift` still set), meant to automatically re-show the gift menu so
143+
the player isn't left wondering what happened. `get_start0()` tried to
144+
do this via `me->command("start")` — but the real `command()` efun
145+
(`core.spec`: `int command(string)`) takes no object argument, so this
146+
is a call_other to a method named `command` that is not defined
147+
anywhere on the player object (confirmed via a lib-wide grep). FluffOS
148+
silently no-ops an undefined call_other with no error raised anywhere
149+
(not even in debug.log), so this reconnect path produced **zero visible
150+
output**: a player reconnecting mid-wizard saw nothing at all after
151+
"重新连线完毕。" — no menu, no error, no hint — and the only way out
152+
was to already know to type the undocumented `start` command
153+
themselves (confirmed live: typing `start` manually DID correctly
154+
redisplay the menu, since that add_action registration itself works
155+
fine). Root-caused by directly inspecting `data/user/q/*.o` for
156+
`no_gift`, then confirming live with a raw-socket test that showed
157+
identical silence even after a genuine 10-second wait.
158+
159+
Fixed by rewriting `get_start0()` to replicate `get_start1()`'s body
160+
directly (using `tell_object(me, ...)` instead of `write()`, since
161+
`write()` implicitly targets `this_player()`, which is unset inside a
162+
call_out) and calling `get_start(me)` directly instead of round-
163+
tripping through the broken `command()` call. Also fixed `show_gift()`
164+
the same way (`write()``tell_object(me, ...)`), since it's reached
165+
via the same call_out path. Verified live: after the fix, reconnecting
166+
to a mid-wizard account (`qftwo`) auto-displays the full gift menu
167+
within seconds with no manual `start` needed, matching the intended
168+
"重新连线完毕" → immediate resume UX. A fresh, uninterrupted
169+
registration (id → New → Chinese name → password ×2 → email → gender →
170+
gift accept `9`+`y`) was independently confirmed to already work
171+
correctly end-to-end (lands cleanly in 南城客栈/South City Inn per the
172+
newbie doc), so this bug specifically affects the reconnect-while-
173+
mid-wizard path, not first-time registration.
174+
175+
**Verified working**: full registration flow, `look`/`score`/`hp` all
176+
render correctly with rich, properly-escaped ANSI formatting (no
177+
orphaned escape sequences), clean `quit`. debug.log/driver stdout show
178+
only pre-existing harmless compile warnings (unused locals, unknown
179+
`#pragma`) throughout — no new runtime errors from any of this session's
180+
testing.
181+
182+
**Not verified live this pass** (time budget went to the reconnect-
183+
wizard investigation above): net-dead disconnect/reconnect after actual
184+
gameplay has begun, skill learning, sect/faction join, shop purchase,
185+
combat. Given the newbie doc's own detailed skill/combat/shop
186+
instructions, this lib very likely has full playable content beyond
187+
the registration wizard — a future pass should pick up from a
188+
`qfthree`-style completed character and continue the rest of the §10.7
189+
checklist.
190+
191+
**Files modified**: `libs/sanjiechuanshuo/work/d/wiz/init.lpc` (the fix
192+
above). Test character saves kept as before/after evidence rather than
193+
removed.

libs/sanjiechuanshuo/work/d/wiz/init.lpc

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -65,8 +65,34 @@ void init()
6565
void get_start0(object me)
6666
{
6767
if(!me) return;
68-
69-
me->command("start");
68+
69+
// BUG FIX (deep functional test, AGENTS.md §10.7): this call_out fires
70+
// on every reconnect while no_gift is still set, meant to automatically
71+
// re-show the gift-selection menu. me->command("start") is a call_other
72+
// to a "command" method that is NOT defined anywhere on the player
73+
// object -- the real command() is a bare driver efun (int command
74+
// (string), see core.spec), not something callable via ->. FluffOS
75+
// silently no-ops an undefined call_other with no error, so this
76+
// reconnect path produced no output at all: the player saw nothing
77+
// and had no clue that typing "start" themselves (the only other
78+
// add_action bound to this verb) would actually work. Confirmed live:
79+
// a raw socket test showed zero output even after a 10s wait, while
80+
// manually sending "start" correctly redisplayed the full gift menu.
81+
// Fixed by replicating get_start1()'s body directly here with
82+
// tell_object(me, ...) instead of write() (write() implicitly targets
83+
// this_player(), which is not set inside a call_out) and calling
84+
// get_start(me) directly instead of round-tripping through the
85+
// broken command() call -- get_start() is already player-explicit.
86+
tell_object(me, "\n\n欢迎你来到三界传说!!\n");
87+
tell_object(me, @LONG
88+
89+
在开始你的MUD历程之前,首先要为自己所创造的人物选择一个合适
90+
的天赋,因为这将对你今后的发展有重大的影响。本站中的人物天
91+
赋共有四项(详见 help gifts ),每项由一个十到三十之间的整数
92+
来表示,一般数值越大越好,但各项的总和是固定不变的。
93+
LONG
94+
);
95+
get_start(me);
7096
}
7197

7298
int get_start1(string arg)
@@ -136,7 +162,13 @@ void get_finish(string arg, object me)
136162

137163
void show_gift(object me)
138164
{
139-
write(_show_gift(me));
165+
// BUG FIX (deep functional test, AGENTS.md §10.7): write() implicitly
166+
// targets this_player(), which is unset when show_gift() is reached
167+
// from get_start0()'s call_out context (see that function's own
168+
// comment) -- tell_object(me, ...) targets the explicit parameter
169+
// instead, correct in both the call_out path and the normal
170+
// player-typed "start" path.
171+
tell_object(me, _show_gift(me));
140172
}
141173

142174
string _show_gift(object me)
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":"qftester@test.local","last_from":"127.0.0.1","password":"$6$HtC9zcufalxpWvLh$0lP/lTGioQZZK469alFqUDad7iwzWJaAIWxc5PnQ/9y/MJe8LcP/Fs8D7AkeOTwnI5LPdOzJxnxSDxADPoSyv/","id":"qftester","last_on":1784996495,"body":"/obj/user","name":"秦风",])
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":"qfthree@test.local","password":"$6$jbIhI9rWaXql5CMy$e9xKkDm7E8UxQ9e8BXLnGzqObQiH6jesoNlLBiVxiUFkexNL33Ii35M4mAdmzQiZVnHBVw/ftNpajowfE0XmJ0","last_from":"localhost","id":"qfthree","last_on":1784997215,"body":"/obj/user","name":"秦风三",])
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":"qftwo@test.local","last_from":"127.0.0.1","password":"$6$B0daUVjP9ljZZUi8$U/6QI2aU7FEX29S6mDpcjE.SA3cyqT01BJoXfHtSwaVIWSuI1FIIRHJ5rFy87zF.u5RDPi.VSHV159WS7EpGq/","id":"qftwo","last_on":1784997102,"body":"/obj/user","name":"秦风二",])
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
#/obj/user.lpc
2+
dbase (["int":25,"eff_gin":100,"sen":250,"title":"三界侠客","id":"qftester","no_gift":1,"no_magic":1,"actions":,"attitude":"peaceful","water":345,"cps":25,"limbs":({"头部","颈部","胸口","后心","左肩","右肩","左臂","右臂","左手","右手","腰间","小腹","左腿","右腿","左脚","右脚",}),"channels":({"chat","rumor","sldh","es","job",}),"birthday":1784996380,"per":25,"life":(["life_time":92,"init_life_time":92,]),"default_actions":,"food":345,"race":"人类","kee":250,"kar":25,"gender":"男性","con":25,"eff_sen":250,"unit":"位","age":14,"eff_kee":250,"can_speak":1,"max_gin":100,"cor":25,"str":25,"potential":0,"spi":25,"max_sen":250,"max_kee":250,"mud_age":108,"gin":100,"name":"秦风",])
3+
autoload ({})
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
#/obj/user.lpc
2+
dbase (["quit_time":1784997215,"int":25,"eff_gin":100,"sen":250,"title":"三界侠客","id":"qfthree","no_magic":1,"actions":,"attitude":"peaceful","water":347,"cps":30,"limbs":({"头部","颈部","胸口","后心","左肩","右肩","左臂","右臂","左手","右手","腰间","小腹","左腿","右腿","左脚","右脚",}),"channels":({"chat","rumor","sldh","es","job",}),"birthday":1784997179,"per":22,"life":(["life_time":82,"init_life_time":82,]),"default_actions":,"food":347,"race":"人类","kee":250,"kar":26,"gender":"男性","con":25,"eff_sen":250,"unit":"位","age":14,"eff_kee":250,"can_speak":1,"max_gin":100,"added_title":"无名","cor":22,"str":25,"potential":0,"spi":25,"max_sen":250,"max_kee":250,"mud_age":31,"gin":100,"name":"秦风三",])
3+
autoload ({})
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
#/obj/user.lpc
2+
dbase (["sen":250,"title":"三界侠客","no_magic":1,"water":335,"per":25,"birthday":1784996593,"channels":({"chat","rumor","sldh","es","job",}),"default_actions":,"race":"人类","kee":250,"gender":"男性","con":25,"max_gin":100,"cor":25,"spi":25,"potential":0,"max_kee":250,"name":"秦风二","mud_age":235,"int":25,"eff_gin":100,"id":"qftwo","no_gift":1,"attitude":"peaceful","actions":,"cps":25,"limbs":({"头部","颈部","胸口","后心","左肩","右肩","左臂","右臂","左手","右手","腰间","小腹","左腿","右腿","左脚","右脚",}),"life":(["init_life_time":79,"life_time":79,]),"food":335,"kar":25,"eff_sen":250,"unit":"位","age":14,"can_speak":1,"eff_kee":250,"str":25,"max_sen":250,"gin":100,])
3+
autoload ({})

0 commit comments

Comments
 (0)