Skip to content

Commit c8e19dd

Browse files
thefallentreeclaude
andcommitted
Fix 6 bugs found in nitan6's deep functional test (F_DBASE bootstrapping trap + type-mismatch)
Ported nitan170911's 4 documented F_DBASE bare-call fixes (AGENTS.md §7.15) after confirming byte-identical pre-fix shapes: - feature/name.lpc, feature/command.lpc: this_object() redirects on bare set()/query() calls, which otherwise silently hit the shared simul_efun dbase instead of the caller's own. - adm/daemons/databased.lpc: guarded db_restore_all()'s ten restore_variable() calls against NULL/unpopulated DB columns. - adm/kernel/simul_efun/message.lpc: forward prototype + varargs so tell_room()/etc. bind to the local exclude-normalizing wrapper instead of the raw efun. Found a 5th instance of the same F_DBASE class (missed by nitan170911's own pass): feature/apprentice.lpc's sect/family accessors were reading and writing the shared dbase, corrupting every character's family data. Live-reproduced (a character's 门派 field changed with no bai ever succeeding) and fixed identically. Also fixed a new, unrelated bug: d/city/npc/gongzi.lpc and guidao.lpc called is_killing(who) with an object where feature/attack.lpc's is_killing() takes a string id, a hard compile-time type mismatch that silently prevented these NPCs from ever spawning. Fixed to match the query("id", who) convention used at every other call site. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VyQCUoTo1Z93Py9aVFHQi1
1 parent a69188e commit c8e19dd

11 files changed

Lines changed: 489 additions & 54 deletions

File tree

libs/nitan6/NOTES.md

Lines changed: 341 additions & 0 deletions
Large diffs are not rendered by default.

libs/nitan6/work/adm/daemons/databased.lpc

Lines changed: 27 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -410,18 +410,33 @@ int db_restore_all(object user) {
410410
f_alias = res[n + 9];
411411
f_user = res[n + 10];
412412
char_idname = res[n + 11];
413-
user->set_dbase(restore_variable(f_dbase));
414-
user->set_autoload_info(restore_variable(f_autoload));
415-
user->set_CONDITION(restore_variable(f_condition));
416-
user->set_business(restore_variable(f_business));
417-
user->set_mail(restore_variable(f_mail));
418-
user->set_ALIAS(restore_variable(f_alias));
419-
user->set_ATTACK(restore_variable(f_attack));
420-
user->set_ghost(restore_variable(f_damage));
421-
user->set_SKILL(restore_variable(f_skill));
422-
user->set_USER(restore_variable(f_user));
423-
user->set_IDNAME(restore_variable(char_idname));
424-
if (objectp(myob)) myob->set_dbase(restore_variable(login_dbase));
413+
// Every f_*/char_idname/login_dbase column here is NULL (comes back
414+
// from db_fetch() as int 0, not "") for any account whose row was
415+
// written by db_new_player() -- that INSERT only ever populates a
416+
// handful of columns at creation time, leaving every other column
417+
// NULL until the player's first real save. restore_variable() rejects
418+
// a non-string argument outright ("Bad argument 1 to
419+
// restore_variable() Expected: string Got: 0"), and since none of
420+
// these calls were wrapped in catch(), the FIRST such error aborted
421+
// db_restore_all() entirely -- every restore call after the failing
422+
// one silently never ran, leaving a just-logged-in character
423+
// half-initialized (same shape confirmed live on sibling
424+
// nitan170911, see that lib's NOTES.md). Guard each with stringp()
425+
// and fall back to the same empty value each setter's own callers
426+
// already treat as "nothing saved yet" elsewhere in the codebase.
427+
user->set_dbase(stringp(f_dbase) ? restore_variable(f_dbase) : ([]));
428+
user->set_autoload_info(stringp(f_autoload) ? restore_variable(f_autoload) : ({}));
429+
user->set_CONDITION(stringp(f_condition) ? restore_variable(f_condition) : ([]));
430+
user->set_business(stringp(f_business) ? restore_variable(f_business) : ([]));
431+
user->set_mail(stringp(f_mail) ? restore_variable(f_mail) : ({}));
432+
user->set_ALIAS(stringp(f_alias) ? restore_variable(f_alias) : ([]));
433+
user->set_ATTACK(stringp(f_attack) ? restore_variable(f_attack) : ([]));
434+
user->set_ghost(stringp(f_damage) ? restore_variable(f_damage) : 0);
435+
user->set_SKILL(stringp(f_skill) ? restore_variable(f_skill) : ([]));
436+
user->set_USER(stringp(f_user) ? restore_variable(f_user) : ([]));
437+
user->set_IDNAME(stringp(char_idname) ? restore_variable(char_idname) : 0);
438+
if (objectp(myob))
439+
myob->set_dbase(stringp(login_dbase) ? restore_variable(login_dbase) : ([]));
425440
n = 1;
426441
}
427442

libs/nitan6/work/adm/kernel/simul_efun/message.lpc

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,17 @@
22
// Update by Doing
33
// Update by Lonely
44

5+
// Forward prototype: tell_object()/tell_room()/shout()/write()/say()/etc.
6+
// below all call bare message() before its own definition later in this
7+
// file. Without a prototype, FluffOS's single-pass compiler resolves
8+
// those earlier bare calls directly to the driver EFUN `message()`
9+
// instead of the local wrapper defined below -- silently bypassing the
10+
// exclude-argument normalization that wrapper exists to provide (see its
11+
// own comment) for every caller ABOVE its definition. This is the exact
12+
// bug confirmed live on sibling nitan170911 (AGENTS.md §6.5/§7.12 combo);
13+
// this prototype makes every call site in this file bind to the local
14+
// function instead.
15+
varargs void message(mixed arg, string message, mixed target, mixed exclude);
516

617
string sort_msg(string input) {
718
#ifdef BINARY_SUPPORT
@@ -328,8 +339,16 @@ varargs void say(string str, mixed exclude) {
328339
message("say", str, environment(this_player()), this_player());
329340
}
330341

331-
void message(mixed arg, string message, mixed target, mixed exclude) {
332-
efun::message(arg, message, target, exclude || ({}));
342+
// `exclude` is passed as bare mixed (not `varargs object|object *`) because
343+
// call sites throughout this mudlib either omit it or pass literal `0`,
344+
// relying on LPC's "missing trailing arg becomes int 0" behavior.
345+
// efun::message()'s real signature only accepts `void | object | object *`
346+
// for this argument and throws "Bad argument 4 to EFUN message()" at
347+
// runtime on int(0) -- normalize here instead of touching every call
348+
// site (same fix as sibling nitan170911).
349+
varargs void message(mixed arg, string message, mixed target, mixed exclude) {
350+
if (!arrayp(exclude) && !objectp(exclude)) exclude = ({});
351+
efun::message(arg, message, target, exclude);
333352
}
334353

335354
void message_system(string message) {

libs/nitan6/work/d/city/npc/gongzi.lpc

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,15 @@ string ask_me(object who) {
5252
object *ob;
5353

5454
if (query("revealed")) {
55-
if (is_killing(who))
55+
// is_killing() (feature/attack.lpc) takes a string `id`, not an
56+
// object -- passing `who` directly is a driver-rejected type
57+
// mismatch ("Bad type for argument 1 of is_killing (string vs
58+
// object)"), a hard compile error that made this whole file fail
59+
// to load (`new()` in inherit/room/room.lpc's make_inventory()
60+
// silently returns 0, so this NPC never actually spawns wherever
61+
// it's listed). Fixed to match the query("id", ob) convention used
62+
// at every other is_killing() call site in this codebase.
63+
if (is_killing(query("id", who)))
5664
return "你既然知道了我的秘密,今日休想活命!\n";
5765
else {
5866
kill_ob(who);

libs/nitan6/work/d/city/npc/guidao.lpc

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,9 @@ string ask_me(object who) {
5252
object *ob;
5353

5454
if (query("revealed")) {
55-
if (is_killing(who)) return "你既然知道了我的身分,今日休想活命!\n";
55+
// Same is_killing(object) vs is_killing(string) type-mismatch bug
56+
// as sibling /d/city/npc/gongzi.lpc -- see that file's comment.
57+
if (is_killing(query("id", who))) return "你既然知道了我的身分,今日休想活命!\n";
5658
else {
5759
kill_ob(who);
5860
who->fight_ob(this_object());
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
#/clone/user/login.lpc
2+
dbase (["purename":"风","registered":0,"password":"$1$92953668$BGk5u89gVSf65p4/DU2es.","last_from":"127.0.0.1","id":"xiakebug","ad_password":"$1$60330041$FndmuR3279XofxI/DsnNj/","login_times":1,"surname":"秦","body":"/clone/user/user",])
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
#/clone/user/user.lpc
2+
killer ({})
3+
want_kills ({})
4+
dbase (["max_jing":650,"qi":614,"title":"普通百姓","jingli":1200,"special_skill":(["ironskin":1,"clever":1,]),"dex":20,"jing":464,"water":217,"max_jingli":1200,"newbie":1,"channels":915655,"birthday":1784959297,"per":29,"default_actions":,"sec_id":"$6$1h/wupTpHi/zpUjS$DYcL5xDOu5./zpZxy5DzucBSgZehPN8JlGXDFihSG.FXo38QqBaT.GJhP/nfSdGBN5jggpytO7hvR/zJsgbFF/","race":"人类","env":(["wimpy":60,]),"analecta_last_read":(["2026":1784959301,]),"startroom":"/d/city/wumiao","gender":"男性","character":"光明磊落","con":20,"msg":(["rumor":1,"chat":1,"size":100,"emotion":1,"say":1,"tell":1,"time":1784959300,]),"eff_jing":465,"neili":2200,"surname":"秦","potential":102853,"newbie_mygift":(["cur_quest_number":"1",]),"max_neili":2200,"gift_xinshoucun":1,"mud_age":0,"born":"古村","name":"秦风","int":20,"newbie_quest_completed":(["can_out":1,]),"purename":"风","registered":1,"id":"xiakebug","combat_exp":53207,"attitude":"peaceful","score":163,"limbs":({"头部","颈部","胸口","后心","左肩","右肩","左臂","右臂","左手","右手","两肋","左脸","腰间","小腹","左腿","右腿","右脸","左脚","右脚","左耳","右耳",}),"on_birthday":1,"monfee":1793599297,"last_read_news":971000000,"food":217,"email":"test-xiakebug@example.com","kar":30,"last_save":1784959323,"shen_type":0,"max_qi":900,"unit":"位","age":11,"level":1,"can_speak":1,"last_on":1784959955,"str":20,"born_family":"没有","shen":0,"chann":(["tch":1,"auc":1,"sos":1,]),"eff_qi":615,])
5+
mail_log ({})
6+
skills (["blade":164,"force":164,"unarmed":164,"dodge":164,"sword":164,"parry":164,])
7+
learned (["blade":16211,"force":16211,"unarmed":16211,"dodge":16211,"sword":16211,"parry":16211,])
8+
autoload ({})
9+
all ({})
10+
at_time 1784959974
11+
ban_say_msg ""
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
0/clone/cloth/male3-clothoriginal_data0({"cloth",})
2+
0/clone/cloth/male-shoeoriginal_data0({"skin shoes","shoe",})
3+
0/clone/misc/newbieoriginal_data0({"book","newbie book",})

libs/nitan6/work/feature/apprentice.lpc

Lines changed: 31 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,30 @@
11
// apprentice.c
22
// Written by Lonely@nitan.org
33

4+
// feature/apprentice.lpc is inherited (as F_APPRENTICE) as a SIBLING of
5+
// F_DBASE by char.lpc, not through it -- same F_DBASE bare-call
6+
// bootstrapping trap as feature/name.lpc/feature/command.lpc (AGENTS.md
7+
// §7.15): every bare set()/query() call below that omits the `ob`
8+
// redirect silently reads/writes the SIMUL_EFUN OBJECT's own shared
9+
// dbase instead of this_object()'s, so sect/family data (query_family()
10+
// et al) was being read from and written to ONE property bag shared by
11+
// every player and NPC in the game. Confirmed live: score's 门派 field
12+
// changed on its own between two sessions of the same idle character
13+
// with no `bai` ever succeeding, because another object's bare call
14+
// elsewhere was clobbering the same shared "family" key. Fixed with
15+
// explicit this_object() redirects on every self-referring call (calls
16+
// that already redirect to a different `ob` parameter are untouched).
417
int is_apprentice_of(object ob) {
518
mapping family;
619
string *fams;
720

8-
if (!mapp(family = query("family"))) return 0;
21+
if (!mapp(family = query("family", this_object()))) return 0;
922

1023
if (family["master_id"] == (string)query("id", ob) &&
1124
family["master_name"] == (string)query("name", ob))
1225
return 1;
1326

14-
if (!arrayp(fams = query("reborn/fams")) ||
27+
if (!arrayp(fams = query("reborn/fams", this_object())) ||
1528
!query("family/family_name", ob))
1629
return 0;
1730

@@ -24,20 +37,20 @@ int is_apprentice_of(object ob) {
2437
void assign_apprentice(string title, int privs) {
2538
mapping family;
2639

27-
if (!mapp(family = query("family"))) return;
40+
if (!mapp(family = query("family", this_object()))) return;
2841
family["title"] = title;
2942
family["privs"] = privs;
30-
if (userp(this_object()) || !query("title")) {
43+
if (userp(this_object()) || !query("title", this_object())) {
3144
switch (family["generation"]) {
3245
case 0:
33-
set("title", family["family_name"] + family["title"]);
46+
set("title", family["family_name"] + family["title"], this_object());
3447
break;
3548
case 1:
36-
set("title", family["family_name"] + "开山祖师");
49+
set("title", family["family_name"] + "开山祖师", this_object());
3750
break;
3851
default:
3952
set("title", sprintf("%s第%s代%s", family["family_name"],
40-
chinese_number(family["generation"]), family["title"]));
53+
chinese_number(family["generation"]), family["title"]), this_object());
4154
break;
4255
}
4356
}
@@ -49,7 +62,7 @@ void create_family(string family_name, int generation, string title) {
4962
family = allocate_mapping(6);
5063
family["family_name"] = family_name;
5164
family["generation"] = generation;
52-
set("family", family);
65+
set("family", family, this_object());
5366
// priv = -1 for ALL privileges.
5467
assign_apprentice(title, -1);
5568
}
@@ -62,7 +75,7 @@ int recruit_apprentice(object ob) {
6275
if (ob->is_apprentice_of(this_object()))
6376
return 0;
6477

65-
if (!mapp(my_family = query("family")))
78+
if (!mapp(my_family = query("family", this_object())))
6679
return 0;
6780

6881
// 这里处理判师的bug
@@ -77,16 +90,16 @@ int recruit_apprentice(object ob) {
7790
}
7891
}
7992

80-
class1 = query("class");
93+
class1 = query("class", this_object());
8194
class2 = query("class", ob);
8295
if (stringp(class1) &&
8396
class2 != "bonze" && class2 != "eunach" &&
8497
class1 != "bonze" && class1 != "eunach")
8598
set("class", class1, ob);
8699

87100
family = allocate_mapping(sizeof(my_family));
88-
family["master_id"] = query("id");
89-
family["master_name"] = query("name");
101+
family["master_id"] = query("id", this_object());
102+
family["master_name"] = query("name", this_object());
90103
family["family_name"] = my_family["family_name"];
91104
family["generation"] = my_family["generation"] + 1;
92105
family["enter_time"] = time();
@@ -135,11 +148,11 @@ int recruit_apprentice(object ob) {
135148
if (query("family/first", ob) && family_name == family["family_name"])
136149
family["first"] = 1;
137150

138-
if (query("inherit_title")) {
151+
if (query("inherit_title", this_object())) {
139152
set("title", query("inherit_title", this_object()), ob);
140153
set("family", family, ob);
141154
return 1;
142-
} else if (query("born_family") != "没有")
155+
} else if (query("born_family", this_object()) != "没有")
143156
family["title"] = "传人";
144157
else
145158
family["title"] = "弟子";
@@ -150,17 +163,17 @@ int recruit_apprentice(object ob) {
150163
}
151164

152165
string query_bunch() {
153-
return query("bunch/bunch_name");
166+
return query("bunch/bunch_name", this_object());
154167
}
155168

156169
string query_family() {
157-
return query("family/family_name");
170+
return query("family/family_name", this_object());
158171
}
159172

160173
string query_master() {
161-
return query("family/master_name");
174+
return query("family/master_name", this_object());
162175
}
163176

164177
string query_generation() {
165-
return query("family/generation");
178+
return query("family/generation", this_object());
166179
}

libs/nitan6/work/feature/command.lpc

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -138,10 +138,19 @@ nomask void enable_player() {
138138
object me;
139139
string *my_path;
140140

141-
if (stringp(query("id")))
142-
set_living_name(query("id"));
141+
// feature/command.lpc inherits nothing and is composed as a SIBLING of
142+
// F_DBASE (via char.lpc's `inherit F_COMMAND`), so bare query() here
143+
// hits the same F_DBASE bare-call bootstrapping trap as feature/name.lpc
144+
// (AGENTS.md §7.15): it silently reads the SIMUL_EFUN object's own
145+
// shared dbase instead of this_object()'s, always returning 0 -- which
146+
// made every character (player or NPC) call set_living_name(0) and
147+
// error out every time enable_player() ran, leaving living_name
148+
// permanently unset mudlib-wide. Explicit this_object() redirect fixes
149+
// it the same way as nitan170911's identical shape.
150+
if (stringp(query("id", this_object())))
151+
set_living_name(query("id", this_object()));
143152
else
144-
set_living_name(query("name"));
153+
set_living_name(query("name", this_object()));
145154

146155
if (query_temp("disable_input", this_object()))
147156
delete_temp("disable_input", this_object());

0 commit comments

Comments
 (0)