Add hook debug tooling and refine RT3 atlas

This commit is contained in:
Jan Petykiewicz 2026-04-08 16:31:33 -07:00
commit 57bf0666e0
38 changed files with 14437 additions and 873 deletions

7
Cargo.lock generated
View file

@ -118,12 +118,19 @@ name = "rrt-cli"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"rrt-model", "rrt-model",
"serde",
"serde_json",
"sha2", "sha2",
] ]
[[package]] [[package]]
name = "rrt-hook" name = "rrt-hook"
version = "0.1.0" version = "0.1.0"
dependencies = [
"rrt-model",
"serde",
"serde_json",
]
[[package]] [[package]]
name = "rrt-model" name = "rrt-model"

View file

@ -1,7 +1,2 @@
query_address,function_address,name,size,calling_convention,signature,caller_count,callers,callee_count,callees,data_ref_count,data_refs,entry_excerpt query_address,function_address,name,size,calling_convention,signature,caller_count,callers,callee_count,callees,data_ref_count,data_refs,entry_excerpt
0x00595440,0x00595440,multiplayer_transport_init_selector_slot,100,cdecl,"fcn.00595440(int32_t arg_4h, int32_t arg_39ch, int32_t arg_59bh);",2,0x00593841@0x00593790:multiplayer_transport_handle_names_query_response; 0x00593b25@0x00593b00:multiplayer_transport_handle_selector_update_response,3,0x00595490->0x005951f0:multiplayer_transport_service_status_pump; 0x00595499->0x00596fc0:multiplayer_transport_submit_profile_key_query_bundle_default; 0x0059547f->0x005a18a0:string_copy_bounded_zerofill,1,0x0059544f->0x005c87a8," 595420: movl $0x89ffff98, %esp # imm = 0x89FFFF98 | 595425: movl $0x9a4, %esi # imm = 0x9A4 | 59542a: popl %edi | 59542b: popl %esi | 59542c: addl $0x190, %esp # imm = 0x190 | 595432: retl | 595433: nop | 595434: nop | 595435: nop | 595436: nop | 595437: nop | 595438: nop | 595439: nop | 59543a: nop | 59543b: nop | 59543c: nop | 59543d: nop | 59543e: nop | 59543f: nop | 595440: movl 0x4(%esp), %eax | 595444: testl %eax, %eax | 595446: pushl %ebp | 595447: pushl %esi | 595448: pushl %edi | 595449: movl %edx, %edi | 59544b: movl %ecx, %esi | 59544d: jne 0x595454 <.text+0x194454> | 59544f: movl $0x5c87a8, %eax # imm = 0x5C87A8 | 595454: movl %edi, %ecx | 595456: shll $0x9, %ecx | 595459: leal (%ecx,%esi), %ebp | 59545c: pushl $0x200 # imm = 0x200" 0x004333f0,0x004333f0,shell_setup_build_file_list_records_from_current_root_and_pattern,676,cdecl,"fcn.004333f0(int32_t arg_8h, int32_t arg_c30h);",1,0x004336b8@0x004336a0:shell_setup_file_list_construct_and_scan_dataset,14,0x004334de->0x00433260:fcn.00433260; 0x004335b7->0x00433260:fcn.00433260; 0x00433409->0x004839b0:shell_setup_query_file_list_uses_map_extension_pattern; 0x0043344c->0x004839e0:shell_setup_query_file_list_root_dir_name; 0x00433433->0x00518de0:fcn.00518de0; 0x0043345e->0x00518de0:fcn.00518de0; 0x0043348b->0x00518de0:fcn.00518de0; 0x004334ae->0x00518de0:fcn.00518de0; 0x0043355a->0x00518de0:fcn.00518de0; 0x0043357d->0x00518de0:fcn.00518de0; 0x00433638->0x0051c920:localization_lookup_display_label_by_stem_or_fallback; 0x00433682->0x0051dc60:fcn.0051dc60; 0x004335f7->0x0051df90:fcn.0051df90; 0x0043350c->0x005a125d:fcn.005a125d,8,"0x004334b3->0x005c8190; 0x0043347f->0x005c9d08:""%s%s""; 0x0043354e->0x005c9d08:""%s%s""; 0x00433452->0x005c9d10; 0x0043342e->0x005c9d14:""*.gm*""; 0x00433427->0x005c9d1c:""*.smp""; 0x00433402->0x006cec74; 0x00433438->0x006cec74"," 4333d0: movl %ds, %edi | 4333d2: strw -0x29da1732(%ebx) | 4333d9: ltrw -0x75(%esi) | 4333dd: ldsl 0x5b(%ebp), %ebx | 4333e0: popl %edi | 4333e1: addl $0x404f4, %esp # imm = 0x404F4 | 4333e7: retl $0x4 | 4333ea: nop | 4333eb: nop | 4333ec: nop | 4333ed: nop | 4333ee: nop | 4333ef: nop | 4333f0: subl $0xcf8, %esp # imm = 0xCF8 | 4333f6: pushl %ebx | 4333f7: pushl %ebp | 4333f8: movl %ecx, %ebp | 4333fa: pushl %esi | 4333fb: movl $0x0, (%ebp) | 433402: movl 0x6cec74, %ecx | 433408: pushl %edi | 433409: calll 0x4839b0 <.text+0x829b0> | 43340e: testl %eax, %eax"
0x00596da0,0x00596da0,multiplayer_transport_submit_profile_key_query_bundle,497,cdecl,fcn.00596da0(uint_least32_t arg_4h);,2,0x00596faa@0x00596fa0:multiplayer_transport_submit_profile_key_query_bundle_with_context; 0x00596fc7@0x00596fc0:multiplayer_transport_submit_profile_key_query_bundle_default,13,0x00596eb6->0x0058ec50:multiplayer_transport_submit_getkey_command_and_wait; 0x00596f7a->0x0058ef20:multiplayer_transport_submit_setchankey_pair_list_command_and_wait; 0x00596e68->0x0058f380:tracked_heap_alloc_with_header; 0x00596f03->0x0058f380:tracked_heap_alloc_with_header; 0x00596ebf->0x0058f3c0:tracked_heap_free_with_header; 0x00596f83->0x0058f3c0:tracked_heap_free_with_header; 0x00596e56->0x0058f8f0:fcn.0058f8f0; 0x00596edf->0x0058f8f0:fcn.0058f8f0; 0x00596df0->0x0058f9c0:hashed_entry_table_lookup; 0x00596e22->0x0058f9c0:hashed_entry_table_lookup; 0x00596e36->0x0058f9c0:hashed_entry_table_lookup; 0x00596e8a->0x0058fa00:generic_callback_list_for_each; 0x00596f21->0x0058fa00:generic_callback_list_for_each,9,"0x00596e85->0x00596c80; 0x00596f1c->0x00596c80; 0x00596eaf->0x00596c90; 0x00596f67->0x00596ce0; 0x00596f6e->0x005e1e1c; 0x00596e1a->0x005e2260:""b_flags""; 0x00596f4d->0x005e2260:""b_flags""; 0x00596de8->0x005e22e8:""username""; 0x00596f32->0x005e22e8:""username"""," 596d80: calll 0x596b90 <.text+0x195b90> | 596d85: addl $0x4, %esi | 596d88: decl %ebx | 596d89: jne 0x596d70 <.text+0x195d70> | 596d8b: popl %edi | 596d8c: popl %esi | 596d8d: popl %ebp | 596d8e: popl %ebx | 596d8f: retl $0x18 | 596d92: nop | 596d93: nop | 596d94: nop | 596d95: nop | 596d96: nop | 596d97: nop | 596d98: nop | 596d99: nop | 596d9a: nop | 596d9b: nop | 596d9c: nop | 596d9d: nop | 596d9e: nop | 596d9f: nop | 596da0: subl $0x10, %esp | 596da3: pushl %ebp | 596da4: pushl %edi | 596da5: movl %eax, %edi | 596da7: movl 0x390(%esi,%edi,4), %eax | 596dae: xorl %ebp, %ebp | 596db0: cmpl %ebp, %eax | 596db2: jne 0x596dc1 <.text+0x195dc1> | 596db4: cmpl %ebp, 0x384(%esi,%edi,4) | 596dbb: je 0x596f89 <.text+0x195f89>"
0x00596fa0,0x00596fa0,multiplayer_transport_submit_profile_key_query_bundle_with_context,19,cdecl,fcn.00596fa0(int32_t arg_4h);,1,0x0059f935,1,0x00596faa->0x00596da0:multiplayer_transport_submit_profile_key_query_bundle,0,," 596f80: decl %esp | 596f81: andb $0x18, %al | 596f83: calll 0x58f3c0 <.text+0x18e3c0> | 596f88: popl %ebx | 596f89: popl %edi | 596f8a: popl %ebp | 596f8b: addl $0x10, %esp | 596f8e: retl $0x4 | 596f91: nop | 596f92: nop | 596f93: nop | 596f94: nop | 596f95: nop | 596f96: nop | 596f97: nop | 596f98: nop | 596f99: nop | 596f9a: nop | 596f9b: nop | 596f9c: nop | 596f9d: nop | 596f9e: nop | 596f9f: nop | 596fa0: pushl %esi | 596fa1: movl %edx, %eax | 596fa3: movl 0x8(%esp), %edx | 596fa7: pushl %edx | 596fa8: movl %ecx, %esi | 596faa: calll 0x596da0 <.text+0x195da0> | 596faf: popl %esi | 596fb0: retl $0x4 | 596fb3: nop | 596fb4: nop | 596fb5: nop | 596fb6: nop | 596fb7: nop | 596fb8: nop | 596fb9: nop | 596fba: nop | 596fbb: nop | 596fbc: nop | 596fbd: nop | 596fbe: nop | 596fbf: nop"
0x00596fc0,0x00596fc0,multiplayer_transport_submit_profile_key_query_bundle_default,14,cdecl,fcn.00596fc0();,2,0x00595499@0x00595440:multiplayer_transport_init_selector_slot; 0x0059fbb5,1,0x00596fc7->0x00596da0:multiplayer_transport_submit_profile_key_query_bundle,0,," 596fa0: pushl %esi | 596fa1: movl %edx, %eax | 596fa3: movl 0x8(%esp), %edx | 596fa7: pushl %edx | 596fa8: movl %ecx, %esi | 596faa: calll 0x596da0 <.text+0x195da0> | 596faf: popl %esi | 596fb0: retl $0x4 | 596fb3: nop | 596fb4: nop | 596fb5: nop | 596fb6: nop | 596fb7: nop | 596fb8: nop | 596fb9: nop | 596fba: nop | 596fbb: nop | 596fbc: nop | 596fbd: nop | 596fbe: nop | 596fbf: nop | 596fc0: pushl %esi | 596fc1: movl %edx, %eax | 596fc3: pushl $0x0 | 596fc5: movl %ecx, %esi | 596fc7: calll 0x596da0 <.text+0x195da0> | 596fcc: popl %esi | 596fcd: retl | 596fce: nop | 596fcf: nop | 596fd0: movl 0x4(%esp), %eax | 596fd4: pushl %esi | 596fd5: movl %edx, %esi | 596fd7: movl 0x398(%eax), %edx | 596fdd: testl %edx, %edx | 596fdf: pushl %edi"
0x00596fd0,0x00596fd0,multiplayer_transport_dispatch_status_route_event,219,cdecl,data.00596fd0(int32_t arg_4h);,0,,6,0x00597017->0x0058bce0:fcn.0058bce0; 0x00597054->0x0058bce0:fcn.0058bce0; 0x00597029->0x0058cd40:fcn.0058cd40; 0x0059703f->0x0058cd40:fcn.0058cd40; 0x0059706d->0x0058cd40:fcn.0058cd40; 0x00597084->0x0058cd40:fcn.0058cd40,3,"0x00597008->0x005970ac; 0x00597001->0x005970c4; 0x0059704d->0x005ce1c8:""openstaging"""," 596fb0: retl $0x4 | 596fb3: nop | 596fb4: nop | 596fb5: nop | 596fb6: nop | 596fb7: nop | 596fb8: nop | 596fb9: nop | 596fba: nop | 596fbb: nop | 596fbc: nop | 596fbd: nop | 596fbe: nop | 596fbf: nop | 596fc0: pushl %esi | 596fc1: movl %edx, %eax | 596fc3: pushl $0x0 | 596fc5: movl %ecx, %esi | 596fc7: calll 0x596da0 <.text+0x195da0> | 596fcc: popl %esi | 596fcd: retl | 596fce: nop | 596fcf: nop | 596fd0: movl 0x4(%esp), %eax | 596fd4: pushl %esi | 596fd5: movl %edx, %esi | 596fd7: movl 0x398(%eax), %edx | 596fdd: testl %edx, %edx | 596fdf: pushl %edi | 596fe0: je 0x597077 <.text+0x196077> | 596fe6: movl 0xb44(%eax), %edx | 596fec: testl %edx, %edx | 596fee: je 0x596ff9 <.text+0x195ff9>"
0x005973d0,0x005973d0,multiplayer_transport_try_connect_status_route,170,cdecl,fcn.005973d0(int32_t arg_4h);,2,0x0058daf1@0x0058dae0:fcn.0058dae0; 0x005965be@0x005965a0:multiplayer_transport_try_connect_status_route_once,4,0x00597460->0x0058bc90:fcn.0058bc90; 0x0059743c->0x0058c9b0:fcn.0058c9b0; 0x0059742d->0x0058cc40:fcn.0058cc40; 0x005973e7->0x00597350:multiplayer_transport_release_status_route,7,0x0059740f->0x00596fd0; 0x0059740a->0x005970e0; 0x00597405->0x00597180; 0x00597400->0x005971b0; 0x005973fb->0x00597270; 0x005973f6->0x005972c0; 0x0059745b->0x00597330," 5973b0: pushl %esi | 5973b1: movl %ecx, %esi | 5973b3: movl 0x1ecc(%esi), %ecx | 5973b9: testl %ecx, %ecx | 5973bb: je 0x5973cc <.text+0x1963cc> | 5973bd: calll 0x58cfd0 <.text+0x18bfd0> | 5973c2: movl $0x0, 0x1ecc(%esi) | 5973cc: popl %esi | 5973cd: retl | 5973ce: nop | 5973cf: nop | 5973d0: pushl %ebx | 5973d1: pushl %esi | 5973d2: movl %ecx, %esi | 5973d4: movl 0xaf0(%esi), %eax | 5973da: testl %eax, %eax | 5973dc: pushl %edi | 5973dd: leal 0xaf0(%esi), %edi | 5973e3: movl %edx, %ebx | 5973e5: je 0x5973ec <.text+0x1963ec> | 5973e7: calll 0x597350 <.text+0x196350> | 5973ec: cmpl $-0x1, %ebx | 5973ef: movl 0xb34(%esi), %eax"

1 query_address function_address name size calling_convention signature caller_count callers callee_count callees data_ref_count data_refs entry_excerpt
2 0x00595440 0x004333f0 0x00595440 0x004333f0 multiplayer_transport_init_selector_slot shell_setup_build_file_list_records_from_current_root_and_pattern 100 676 cdecl fcn.00595440(int32_t arg_4h, int32_t arg_39ch, int32_t arg_59bh); fcn.004333f0(int32_t arg_8h, int32_t arg_c30h); 2 1 0x00593841@0x00593790:multiplayer_transport_handle_names_query_response; 0x00593b25@0x00593b00:multiplayer_transport_handle_selector_update_response 0x004336b8@0x004336a0:shell_setup_file_list_construct_and_scan_dataset 3 14 0x00595490->0x005951f0:multiplayer_transport_service_status_pump; 0x00595499->0x00596fc0:multiplayer_transport_submit_profile_key_query_bundle_default; 0x0059547f->0x005a18a0:string_copy_bounded_zerofill 0x004334de->0x00433260:fcn.00433260; 0x004335b7->0x00433260:fcn.00433260; 0x00433409->0x004839b0:shell_setup_query_file_list_uses_map_extension_pattern; 0x0043344c->0x004839e0:shell_setup_query_file_list_root_dir_name; 0x00433433->0x00518de0:fcn.00518de0; 0x0043345e->0x00518de0:fcn.00518de0; 0x0043348b->0x00518de0:fcn.00518de0; 0x004334ae->0x00518de0:fcn.00518de0; 0x0043355a->0x00518de0:fcn.00518de0; 0x0043357d->0x00518de0:fcn.00518de0; 0x00433638->0x0051c920:localization_lookup_display_label_by_stem_or_fallback; 0x00433682->0x0051dc60:fcn.0051dc60; 0x004335f7->0x0051df90:fcn.0051df90; 0x0043350c->0x005a125d:fcn.005a125d 1 8 0x0059544f->0x005c87a8 0x004334b3->0x005c8190; 0x0043347f->0x005c9d08:"%s%s"; 0x0043354e->0x005c9d08:"%s%s"; 0x00433452->0x005c9d10; 0x0043342e->0x005c9d14:"*.gm*"; 0x00433427->0x005c9d1c:"*.smp"; 0x00433402->0x006cec74; 0x00433438->0x006cec74 595420: movl $0x89ffff98, %esp # imm = 0x89FFFF98 | 595425: movl $0x9a4, %esi # imm = 0x9A4 | 59542a: popl %edi | 59542b: popl %esi | 59542c: addl $0x190, %esp # imm = 0x190 | 595432: retl | 595433: nop | 595434: nop | 595435: nop | 595436: nop | 595437: nop | 595438: nop | 595439: nop | 59543a: nop | 59543b: nop | 59543c: nop | 59543d: nop | 59543e: nop | 59543f: nop | 595440: movl 0x4(%esp), %eax | 595444: testl %eax, %eax | 595446: pushl %ebp | 595447: pushl %esi | 595448: pushl %edi | 595449: movl %edx, %edi | 59544b: movl %ecx, %esi | 59544d: jne 0x595454 <.text+0x194454> | 59544f: movl $0x5c87a8, %eax # imm = 0x5C87A8 | 595454: movl %edi, %ecx | 595456: shll $0x9, %ecx | 595459: leal (%ecx,%esi), %ebp | 59545c: pushl $0x200 # imm = 0x200 4333d0: movl %ds, %edi | 4333d2: strw -0x29da1732(%ebx) | 4333d9: ltrw -0x75(%esi) | 4333dd: ldsl 0x5b(%ebp), %ebx | 4333e0: popl %edi | 4333e1: addl $0x404f4, %esp # imm = 0x404F4 | 4333e7: retl $0x4 | 4333ea: nop | 4333eb: nop | 4333ec: nop | 4333ed: nop | 4333ee: nop | 4333ef: nop | 4333f0: subl $0xcf8, %esp # imm = 0xCF8 | 4333f6: pushl %ebx | 4333f7: pushl %ebp | 4333f8: movl %ecx, %ebp | 4333fa: pushl %esi | 4333fb: movl $0x0, (%ebp) | 433402: movl 0x6cec74, %ecx | 433408: pushl %edi | 433409: calll 0x4839b0 <.text+0x829b0> | 43340e: testl %eax, %eax
0x00596da0 0x00596da0 multiplayer_transport_submit_profile_key_query_bundle 497 cdecl fcn.00596da0(uint_least32_t arg_4h); 2 0x00596faa@0x00596fa0:multiplayer_transport_submit_profile_key_query_bundle_with_context; 0x00596fc7@0x00596fc0:multiplayer_transport_submit_profile_key_query_bundle_default 13 0x00596eb6->0x0058ec50:multiplayer_transport_submit_getkey_command_and_wait; 0x00596f7a->0x0058ef20:multiplayer_transport_submit_setchankey_pair_list_command_and_wait; 0x00596e68->0x0058f380:tracked_heap_alloc_with_header; 0x00596f03->0x0058f380:tracked_heap_alloc_with_header; 0x00596ebf->0x0058f3c0:tracked_heap_free_with_header; 0x00596f83->0x0058f3c0:tracked_heap_free_with_header; 0x00596e56->0x0058f8f0:fcn.0058f8f0; 0x00596edf->0x0058f8f0:fcn.0058f8f0; 0x00596df0->0x0058f9c0:hashed_entry_table_lookup; 0x00596e22->0x0058f9c0:hashed_entry_table_lookup; 0x00596e36->0x0058f9c0:hashed_entry_table_lookup; 0x00596e8a->0x0058fa00:generic_callback_list_for_each; 0x00596f21->0x0058fa00:generic_callback_list_for_each 9 0x00596e85->0x00596c80; 0x00596f1c->0x00596c80; 0x00596eaf->0x00596c90; 0x00596f67->0x00596ce0; 0x00596f6e->0x005e1e1c; 0x00596e1a->0x005e2260:"b_flags"; 0x00596f4d->0x005e2260:"b_flags"; 0x00596de8->0x005e22e8:"username"; 0x00596f32->0x005e22e8:"username" 596d80: calll 0x596b90 <.text+0x195b90> | 596d85: addl $0x4, %esi | 596d88: decl %ebx | 596d89: jne 0x596d70 <.text+0x195d70> | 596d8b: popl %edi | 596d8c: popl %esi | 596d8d: popl %ebp | 596d8e: popl %ebx | 596d8f: retl $0x18 | 596d92: nop | 596d93: nop | 596d94: nop | 596d95: nop | 596d96: nop | 596d97: nop | 596d98: nop | 596d99: nop | 596d9a: nop | 596d9b: nop | 596d9c: nop | 596d9d: nop | 596d9e: nop | 596d9f: nop | 596da0: subl $0x10, %esp | 596da3: pushl %ebp | 596da4: pushl %edi | 596da5: movl %eax, %edi | 596da7: movl 0x390(%esi,%edi,4), %eax | 596dae: xorl %ebp, %ebp | 596db0: cmpl %ebp, %eax | 596db2: jne 0x596dc1 <.text+0x195dc1> | 596db4: cmpl %ebp, 0x384(%esi,%edi,4) | 596dbb: je 0x596f89 <.text+0x195f89>
0x00596fa0 0x00596fa0 multiplayer_transport_submit_profile_key_query_bundle_with_context 19 cdecl fcn.00596fa0(int32_t arg_4h); 1 0x0059f935 1 0x00596faa->0x00596da0:multiplayer_transport_submit_profile_key_query_bundle 0 596f80: decl %esp | 596f81: andb $0x18, %al | 596f83: calll 0x58f3c0 <.text+0x18e3c0> | 596f88: popl %ebx | 596f89: popl %edi | 596f8a: popl %ebp | 596f8b: addl $0x10, %esp | 596f8e: retl $0x4 | 596f91: nop | 596f92: nop | 596f93: nop | 596f94: nop | 596f95: nop | 596f96: nop | 596f97: nop | 596f98: nop | 596f99: nop | 596f9a: nop | 596f9b: nop | 596f9c: nop | 596f9d: nop | 596f9e: nop | 596f9f: nop | 596fa0: pushl %esi | 596fa1: movl %edx, %eax | 596fa3: movl 0x8(%esp), %edx | 596fa7: pushl %edx | 596fa8: movl %ecx, %esi | 596faa: calll 0x596da0 <.text+0x195da0> | 596faf: popl %esi | 596fb0: retl $0x4 | 596fb3: nop | 596fb4: nop | 596fb5: nop | 596fb6: nop | 596fb7: nop | 596fb8: nop | 596fb9: nop | 596fba: nop | 596fbb: nop | 596fbc: nop | 596fbd: nop | 596fbe: nop | 596fbf: nop
0x00596fc0 0x00596fc0 multiplayer_transport_submit_profile_key_query_bundle_default 14 cdecl fcn.00596fc0(); 2 0x00595499@0x00595440:multiplayer_transport_init_selector_slot; 0x0059fbb5 1 0x00596fc7->0x00596da0:multiplayer_transport_submit_profile_key_query_bundle 0 596fa0: pushl %esi | 596fa1: movl %edx, %eax | 596fa3: movl 0x8(%esp), %edx | 596fa7: pushl %edx | 596fa8: movl %ecx, %esi | 596faa: calll 0x596da0 <.text+0x195da0> | 596faf: popl %esi | 596fb0: retl $0x4 | 596fb3: nop | 596fb4: nop | 596fb5: nop | 596fb6: nop | 596fb7: nop | 596fb8: nop | 596fb9: nop | 596fba: nop | 596fbb: nop | 596fbc: nop | 596fbd: nop | 596fbe: nop | 596fbf: nop | 596fc0: pushl %esi | 596fc1: movl %edx, %eax | 596fc3: pushl $0x0 | 596fc5: movl %ecx, %esi | 596fc7: calll 0x596da0 <.text+0x195da0> | 596fcc: popl %esi | 596fcd: retl | 596fce: nop | 596fcf: nop | 596fd0: movl 0x4(%esp), %eax | 596fd4: pushl %esi | 596fd5: movl %edx, %esi | 596fd7: movl 0x398(%eax), %edx | 596fdd: testl %edx, %edx | 596fdf: pushl %edi
0x00596fd0 0x00596fd0 multiplayer_transport_dispatch_status_route_event 219 cdecl data.00596fd0(int32_t arg_4h); 0 6 0x00597017->0x0058bce0:fcn.0058bce0; 0x00597054->0x0058bce0:fcn.0058bce0; 0x00597029->0x0058cd40:fcn.0058cd40; 0x0059703f->0x0058cd40:fcn.0058cd40; 0x0059706d->0x0058cd40:fcn.0058cd40; 0x00597084->0x0058cd40:fcn.0058cd40 3 0x00597008->0x005970ac; 0x00597001->0x005970c4; 0x0059704d->0x005ce1c8:"openstaging" 596fb0: retl $0x4 | 596fb3: nop | 596fb4: nop | 596fb5: nop | 596fb6: nop | 596fb7: nop | 596fb8: nop | 596fb9: nop | 596fba: nop | 596fbb: nop | 596fbc: nop | 596fbd: nop | 596fbe: nop | 596fbf: nop | 596fc0: pushl %esi | 596fc1: movl %edx, %eax | 596fc3: pushl $0x0 | 596fc5: movl %ecx, %esi | 596fc7: calll 0x596da0 <.text+0x195da0> | 596fcc: popl %esi | 596fcd: retl | 596fce: nop | 596fcf: nop | 596fd0: movl 0x4(%esp), %eax | 596fd4: pushl %esi | 596fd5: movl %edx, %esi | 596fd7: movl 0x398(%eax), %edx | 596fdd: testl %edx, %edx | 596fdf: pushl %edi | 596fe0: je 0x597077 <.text+0x196077> | 596fe6: movl 0xb44(%eax), %edx | 596fec: testl %edx, %edx | 596fee: je 0x596ff9 <.text+0x195ff9>
0x005973d0 0x005973d0 multiplayer_transport_try_connect_status_route 170 cdecl fcn.005973d0(int32_t arg_4h); 2 0x0058daf1@0x0058dae0:fcn.0058dae0; 0x005965be@0x005965a0:multiplayer_transport_try_connect_status_route_once 4 0x00597460->0x0058bc90:fcn.0058bc90; 0x0059743c->0x0058c9b0:fcn.0058c9b0; 0x0059742d->0x0058cc40:fcn.0058cc40; 0x005973e7->0x00597350:multiplayer_transport_release_status_route 7 0x0059740f->0x00596fd0; 0x0059740a->0x005970e0; 0x00597405->0x00597180; 0x00597400->0x005971b0; 0x005973fb->0x00597270; 0x005973f6->0x005972c0; 0x0059745b->0x00597330 5973b0: pushl %esi | 5973b1: movl %ecx, %esi | 5973b3: movl 0x1ecc(%esi), %ecx | 5973b9: testl %ecx, %ecx | 5973bb: je 0x5973cc <.text+0x1963cc> | 5973bd: calll 0x58cfd0 <.text+0x18bfd0> | 5973c2: movl $0x0, 0x1ecc(%esi) | 5973cc: popl %esi | 5973cd: retl | 5973ce: nop | 5973cf: nop | 5973d0: pushl %ebx | 5973d1: pushl %esi | 5973d2: movl %ecx, %esi | 5973d4: movl 0xaf0(%esi), %eax | 5973da: testl %eax, %eax | 5973dc: pushl %edi | 5973dd: leal 0xaf0(%esi), %edi | 5973e3: movl %edx, %ebx | 5973e5: je 0x5973ec <.text+0x1963ec> | 5973e7: calll 0x597350 <.text+0x196350> | 5973ec: cmpl $-0x1, %ebx | 5973ef: movl 0xb34(%esi), %eax

View file

@ -5,685 +5,108 @@
## Function Targets ## Function Targets
### `0x00595440` -> `0x00595440` `multiplayer_transport_init_selector_slot` ### `0x004333f0` -> `0x004333f0` `shell_setup_build_file_list_records_from_current_root_and_pattern`
- Size: `100` - Size: `676`
- Calling convention: `cdecl` - Calling convention: `cdecl`
- Signature: `fcn.00595440(int32_t arg_4h, int32_t arg_39ch, int32_t arg_59bh);` - Signature: `fcn.004333f0(int32_t arg_8h, int32_t arg_c30h);`
Entry excerpt: Entry excerpt:
```asm ```asm
595420: movl $0x89ffff98, %esp # imm = 0x89FFFF98 4333d0: movl %ds, %edi
595425: movl $0x9a4, %esi # imm = 0x9A4 4333d2: strw -0x29da1732(%ebx)
59542a: popl %edi 4333d9: ltrw -0x75(%esi)
59542b: popl %esi 4333dd: ldsl 0x5b(%ebp), %ebx
59542c: addl $0x190, %esp # imm = 0x190 4333e0: popl %edi
595432: retl 4333e1: addl $0x404f4, %esp # imm = 0x404F4
595433: nop 4333e7: retl $0x4
595434: nop 4333ea: nop
595435: nop 4333eb: nop
595436: nop 4333ec: nop
595437: nop 4333ed: nop
595438: nop 4333ee: nop
595439: nop 4333ef: nop
59543a: nop 4333f0: subl $0xcf8, %esp # imm = 0xCF8
59543b: nop 4333f6: pushl %ebx
59543c: nop 4333f7: pushl %ebp
59543d: nop 4333f8: movl %ecx, %ebp
59543e: nop 4333fa: pushl %esi
59543f: nop 4333fb: movl $0x0, (%ebp)
595440: movl 0x4(%esp), %eax 433402: movl 0x6cec74, %ecx
595444: testl %eax, %eax 433408: pushl %edi
595446: pushl %ebp 433409: calll 0x4839b0 <.text+0x829b0>
595447: pushl %esi 43340e: testl %eax, %eax
595448: pushl %edi
595449: movl %edx, %edi
59544b: movl %ecx, %esi
59544d: jne 0x595454 <.text+0x194454>
59544f: movl $0x5c87a8, %eax # imm = 0x5C87A8
595454: movl %edi, %ecx
595456: shll $0x9, %ecx
595459: leal (%ecx,%esi), %ebp
59545c: pushl $0x200 # imm = 0x200
``` ```
Callers: Callers:
- `0x00593841` in `0x00593790` `multiplayer_transport_handle_names_query_response` - `0x004336b8` in `0x004336a0` `shell_setup_file_list_construct_and_scan_dataset`
- `0x00593b25` in `0x00593b00` `multiplayer_transport_handle_selector_update_response`
Caller xref excerpts: Caller xref excerpts:
#### `0x00593841` #### `0x004336b8`
```asm ```asm
593821: movl $0x1000000, %esp # imm = 0x1000000 433698: nop
593826: addb %al, (%eax) 433699: nop
593828: addb %ch, %bl 43369a: nop
59382a: orb %bh, %al 43369b: nop
59382c: xchgb %al, 0xb(%eax) 43369c: nop
59382f: addb %al, (%eax) 43369d: nop
593831: addl %eax, (%eax) 43369e: nop
593833: addb %al, (%eax) 43369f: nop
593835: movl 0x20(%edi), %edx 4336a0: movl 0x4(%esp), %eax
593838: pushl %ebx 4336a4: pushl %esi
593839: pushl %edx 4336a5: movl %ecx, %esi
59383a: movl $0x2, %edx 4336a7: pushl %eax
59383f: movl %esi, %ecx 4336a8: movl %eax, 0x4(%esi)
593841: calll 0x595440 <.text+0x194440> 4336ab: movl $0x0, 0x8(%esi)
593846: movl 0x1c(%esp), %eax 4336b2: movl $0x0, (%esi)
59384a: cmpl %ebp, %eax 4336b8: calll 0x4333f0 <.text+0x323f0>
59384c: jle 0x59387f <.text+0x19287f> 4336bd: movl %esi, %eax
59384e: movl 0x20(%esp), %ebx 4336bf: popl %esi
593852: movl 0x24(%esp), %ebp 4336c0: retl $0x4
593856: subl %ebx, %ebp 4336c3: nop
593858: movl %eax, 0x28(%esp) 4336c4: nop
59385c: leal (%esp), %esp 4336c5: nop
593860: movl (%ebx,%ebp), %eax 4336c6: nop
``` 4336c7: nop
4336c8: nop
#### `0x00593b25` 4336c9: nop
4336ca: nop
```asm 4336cb: nop
593b05: andb $0x20, %al 4336cc: nop
593b07: movl 0x38(%esi), %eax 4336cd: nop
593b0a: testl %eax, %eax 4336ce: nop
593b0c: movl (%esi), %ebx 4336cf: nop
593b0e: pushl %edi 4336d0: movl %ecx, %eax
593b0f: movl %edx, %edi 4336d2: xorl %ecx, %ecx
593b11: movl %edi, 0xc(%esp) 4336d4: movl %ecx, 0x4cae(%eax)
593b15: jne 0x593b93 <.text+0x192b93>
593b17: testl %edi, %edi
593b19: movl 0x1c(%esi), %edx
593b1c: movl %ebx, %ecx
593b1e: je 0x593b67 <.text+0x192b67>
593b20: pushl $0x5c87a8 # imm = 0x5C87A8
593b25: calll 0x595440 <.text+0x194440>
593b2a: movl 0x18(%esp), %eax
593b2e: testl %eax, %eax
593b30: jle 0x593b6e <.text+0x192b6e>
593b32: movl 0x1c(%esp), %edi
593b36: pushl %ebp
593b37: movl 0x24(%esp), %ebp
593b3b: subl %edi, %ebp
593b3d: movl %eax, 0x28(%esp)
593b41: movl (%edi,%ebp), %eax
593b44: movl 0x1c(%esi), %ecx
``` ```
Direct internal callees: Direct internal callees:
- `0x00595490` -> `0x005951f0` `multiplayer_transport_service_status_pump` - `0x004334de` -> `0x00433260` `fcn.00433260`
- `0x00595499` -> `0x00596fc0` `multiplayer_transport_submit_profile_key_query_bundle_default` - `0x004335b7` -> `0x00433260` `fcn.00433260`
- `0x0059547f` -> `0x005a18a0` `string_copy_bounded_zerofill` - `0x00433409` -> `0x004839b0` `shell_setup_query_file_list_uses_map_extension_pattern`
- `0x0043344c` -> `0x004839e0` `shell_setup_query_file_list_root_dir_name`
- `0x00433433` -> `0x00518de0` `fcn.00518de0`
- `0x0043345e` -> `0x00518de0` `fcn.00518de0`
- `0x0043348b` -> `0x00518de0` `fcn.00518de0`
- `0x004334ae` -> `0x00518de0` `fcn.00518de0`
- `0x0043355a` -> `0x00518de0` `fcn.00518de0`
- `0x0043357d` -> `0x00518de0` `fcn.00518de0`
- `0x00433638` -> `0x0051c920` `localization_lookup_display_label_by_stem_or_fallback`
- `0x00433682` -> `0x0051dc60` `fcn.0051dc60`
- `0x004335f7` -> `0x0051df90` `fcn.0051df90`
- `0x0043350c` -> `0x005a125d` `fcn.005a125d`
Data refs: Data refs:
- `0x0059544f` -> `0x005c87a8` - `0x004334b3` -> `0x005c8190`
- `0x0043347f` -> `0x005c9d08` "%s%s"
### `0x00596da0` -> `0x00596da0` `multiplayer_transport_submit_profile_key_query_bundle` - `0x0043354e` -> `0x005c9d08` "%s%s"
- `0x00433452` -> `0x005c9d10`
- Size: `497` - `0x0043342e` -> `0x005c9d14` "*.gm*"
- Calling convention: `cdecl` - `0x00433427` -> `0x005c9d1c` "*.smp"
- Signature: `fcn.00596da0(uint_least32_t arg_4h);` - `0x00433402` -> `0x006cec74`
- `0x00433438` -> `0x006cec74`
Entry excerpt:
```asm
596d80: calll 0x596b90 <.text+0x195b90>
596d85: addl $0x4, %esi
596d88: decl %ebx
596d89: jne 0x596d70 <.text+0x195d70>
596d8b: popl %edi
596d8c: popl %esi
596d8d: popl %ebp
596d8e: popl %ebx
596d8f: retl $0x18
596d92: nop
596d93: nop
596d94: nop
596d95: nop
596d96: nop
596d97: nop
596d98: nop
596d99: nop
596d9a: nop
596d9b: nop
596d9c: nop
596d9d: nop
596d9e: nop
596d9f: nop
596da0: subl $0x10, %esp
596da3: pushl %ebp
596da4: pushl %edi
596da5: movl %eax, %edi
596da7: movl 0x390(%esi,%edi,4), %eax
596dae: xorl %ebp, %ebp
596db0: cmpl %ebp, %eax
596db2: jne 0x596dc1 <.text+0x195dc1>
596db4: cmpl %ebp, 0x384(%esi,%edi,4)
596dbb: je 0x596f89 <.text+0x195f89>
```
Callers:
- `0x00596faa` in `0x00596fa0` `multiplayer_transport_submit_profile_key_query_bundle_with_context`
- `0x00596fc7` in `0x00596fc0` `multiplayer_transport_submit_profile_key_query_bundle_default`
Caller xref excerpts:
#### `0x00596faa`
```asm
596f8a: popl %ebp
596f8b: addl $0x10, %esp
596f8e: retl $0x4
596f91: nop
596f92: nop
596f93: nop
596f94: nop
596f95: nop
596f96: nop
596f97: nop
596f98: nop
596f99: nop
596f9a: nop
596f9b: nop
596f9c: nop
596f9d: nop
596f9e: nop
596f9f: nop
596fa0: pushl %esi
596fa1: movl %edx, %eax
596fa3: movl 0x8(%esp), %edx
596fa7: pushl %edx
596fa8: movl %ecx, %esi
596faa: calll 0x596da0 <.text+0x195da0>
596faf: popl %esi
596fb0: retl $0x4
596fb3: nop
596fb4: nop
596fb5: nop
596fb6: nop
596fb7: nop
596fb8: nop
596fb9: nop
596fba: nop
596fbb: nop
596fbc: nop
596fbd: nop
596fbe: nop
596fbf: nop
596fc0: pushl %esi
596fc1: movl %edx, %eax
596fc3: pushl $0x0
596fc5: movl %ecx, %esi
596fc7: calll 0x596da0 <.text+0x195da0>
```
#### `0x00596fc7`
```asm
596fa7: pushl %edx
596fa8: movl %ecx, %esi
596faa: calll 0x596da0 <.text+0x195da0>
596faf: popl %esi
596fb0: retl $0x4
596fb3: nop
596fb4: nop
596fb5: nop
596fb6: nop
596fb7: nop
596fb8: nop
596fb9: nop
596fba: nop
596fbb: nop
596fbc: nop
596fbd: nop
596fbe: nop
596fbf: nop
596fc0: pushl %esi
596fc1: movl %edx, %eax
596fc3: pushl $0x0
596fc5: movl %ecx, %esi
596fc7: calll 0x596da0 <.text+0x195da0>
596fcc: popl %esi
596fcd: retl
596fce: nop
596fcf: nop
596fd0: movl 0x4(%esp), %eax
596fd4: pushl %esi
596fd5: movl %edx, %esi
596fd7: movl 0x398(%eax), %edx
596fdd: testl %edx, %edx
596fdf: pushl %edi
596fe0: je 0x597077 <.text+0x196077>
596fe6: movl 0xb44(%eax), %edx
```
Direct internal callees:
- `0x00596eb6` -> `0x0058ec50` `multiplayer_transport_submit_getkey_command_and_wait`
- `0x00596f7a` -> `0x0058ef20` `multiplayer_transport_submit_setchankey_pair_list_command_and_wait`
- `0x00596e68` -> `0x0058f380` `tracked_heap_alloc_with_header`
- `0x00596f03` -> `0x0058f380` `tracked_heap_alloc_with_header`
- `0x00596ebf` -> `0x0058f3c0` `tracked_heap_free_with_header`
- `0x00596f83` -> `0x0058f3c0` `tracked_heap_free_with_header`
- `0x00596e56` -> `0x0058f8f0` `fcn.0058f8f0`
- `0x00596edf` -> `0x0058f8f0` `fcn.0058f8f0`
- `0x00596df0` -> `0x0058f9c0` `hashed_entry_table_lookup`
- `0x00596e22` -> `0x0058f9c0` `hashed_entry_table_lookup`
- `0x00596e36` -> `0x0058f9c0` `hashed_entry_table_lookup`
- `0x00596e8a` -> `0x0058fa00` `generic_callback_list_for_each`
- `0x00596f21` -> `0x0058fa00` `generic_callback_list_for_each`
Data refs:
- `0x00596e85` -> `0x00596c80`
- `0x00596f1c` -> `0x00596c80`
- `0x00596eaf` -> `0x00596c90`
- `0x00596f67` -> `0x00596ce0`
- `0x00596f6e` -> `0x005e1e1c`
- `0x00596e1a` -> `0x005e2260` "b_flags"
- `0x00596f4d` -> `0x005e2260` "b_flags"
- `0x00596de8` -> `0x005e22e8` "username"
- `0x00596f32` -> `0x005e22e8` "username"
### `0x00596fa0` -> `0x00596fa0` `multiplayer_transport_submit_profile_key_query_bundle_with_context`
- Size: `19`
- Calling convention: `cdecl`
- Signature: `fcn.00596fa0(int32_t arg_4h);`
Entry excerpt:
```asm
596f80: decl %esp
596f81: andb $0x18, %al
596f83: calll 0x58f3c0 <.text+0x18e3c0>
596f88: popl %ebx
596f89: popl %edi
596f8a: popl %ebp
596f8b: addl $0x10, %esp
596f8e: retl $0x4
596f91: nop
596f92: nop
596f93: nop
596f94: nop
596f95: nop
596f96: nop
596f97: nop
596f98: nop
596f99: nop
596f9a: nop
596f9b: nop
596f9c: nop
596f9d: nop
596f9e: nop
596f9f: nop
596fa0: pushl %esi
596fa1: movl %edx, %eax
596fa3: movl 0x8(%esp), %edx
596fa7: pushl %edx
596fa8: movl %ecx, %esi
596faa: calll 0x596da0 <.text+0x195da0>
596faf: popl %esi
596fb0: retl $0x4
596fb3: nop
596fb4: nop
596fb5: nop
596fb6: nop
596fb7: nop
596fb8: nop
596fb9: nop
596fba: nop
596fbb: nop
596fbc: nop
596fbd: nop
596fbe: nop
596fbf: nop
```
Callers:
- `0x0059f935`
Caller xref excerpts:
#### `0x0059f935`
```asm
59f915: <unknown>
59f917: testl %eax, %eax
59f919: je 0x59f92e <.text+0x19e92e>
59f91b: movl 0x10(%esp), %edx
59f91f: movl 0x8(%esp), %eax
59f923: pushl %edx
59f924: pushl %eax
59f925: movl %edi, %edx
59f927: movl %esi, %ecx
59f929: calll 0x594b60 <.text+0x193b60>
59f92e: movl 0x18(%esp), %edx
59f932: pushl %edi
59f933: movl %esi, %ecx
59f935: calll 0x596fa0 <.text+0x195fa0>
59f93a: movl 0x18(%esp), %edx
59f93e: pushl %edi
59f93f: movl %esi, %ecx
59f941: calll 0x592ee0 <.text+0x191ee0>
59f946: cmpl $0x2, 0x18(%esp)
59f94b: jne 0x59f9b1 <.text+0x19e9b1>
59f94d: movl 0xb54(%esi), %eax
59f953: testl %eax, %eax
```
Direct internal callees:
- `0x00596faa` -> `0x00596da0` `multiplayer_transport_submit_profile_key_query_bundle`
Data refs:
- none
### `0x00596fc0` -> `0x00596fc0` `multiplayer_transport_submit_profile_key_query_bundle_default`
- Size: `14`
- Calling convention: `cdecl`
- Signature: `fcn.00596fc0();`
Entry excerpt:
```asm
596fa0: pushl %esi
596fa1: movl %edx, %eax
596fa3: movl 0x8(%esp), %edx
596fa7: pushl %edx
596fa8: movl %ecx, %esi
596faa: calll 0x596da0 <.text+0x195da0>
596faf: popl %esi
596fb0: retl $0x4
596fb3: nop
596fb4: nop
596fb5: nop
596fb6: nop
596fb7: nop
596fb8: nop
596fb9: nop
596fba: nop
596fbb: nop
596fbc: nop
596fbd: nop
596fbe: nop
596fbf: nop
596fc0: pushl %esi
596fc1: movl %edx, %eax
596fc3: pushl $0x0
596fc5: movl %ecx, %esi
596fc7: calll 0x596da0 <.text+0x195da0>
596fcc: popl %esi
596fcd: retl
596fce: nop
596fcf: nop
596fd0: movl 0x4(%esp), %eax
596fd4: pushl %esi
596fd5: movl %edx, %esi
596fd7: movl 0x398(%eax), %edx
596fdd: testl %edx, %edx
596fdf: pushl %edi
```
Callers:
- `0x00595499` in `0x00595440` `multiplayer_transport_init_selector_slot`
- `0x0059fbb5`
Caller xref excerpts:
#### `0x00595499`
```asm
595479: addb %al, (%eax)
59547b: addb %al, (%eax)
59547d: addb %al, (%eax)
59547f: calll 0x5a18a0 <.text+0x1a08a0>
595484: addl $0xc, %esp
595487: movl %esi, %ecx
595489: movb $0x0, 0x59b(%ebp)
595490: calll 0x5951f0 <.text+0x1941f0>
595495: movl %edi, %edx
595497: movl %esi, %ecx
595499: calll 0x596fc0 <.text+0x195fc0>
59549e: popl %edi
59549f: popl %esi
5954a0: popl %ebp
5954a1: retl $0x4
5954a4: nop
5954a5: nop
5954a6: nop
5954a7: nop
5954a8: nop
5954a9: nop
5954aa: nop
5954ab: nop
5954ac: nop
5954ad: nop
5954ae: nop
5954af: nop
5954b0: pushl %ebx
5954b1: pushl %esi
5954b2: pushl %edi
5954b3: movl %edx, %edi
5954b5: movl %ecx, %esi
5954b7: movl 0x384(%esi,%edi,4), %eax
```
#### `0x0059fbb5`
```asm
59fb95: movl (%edi,%esi), %ecx
59fb98: movl 0x20(%esp), %edx
59fb9c: pushl %ecx
59fb9d: pushl %edx
59fb9e: movl (%esi), %edx
59fba0: movl %ebp, %ecx
59fba2: calll 0x594f20 <.text+0x193f20>
59fba7: addl $0x4, %esi
59fbaa: decl %ebx
59fbab: jne 0x59fb95 <.text+0x19eb95>
59fbad: popl %edi
59fbae: popl %esi
59fbaf: movl 0x18(%esp), %edx
59fbb3: movl %ebp, %ecx
59fbb5: calll 0x596fc0 <.text+0x195fc0>
59fbba: movl 0x18(%esp), %edx
59fbbe: movl %ebp, %ecx
59fbc0: calll 0x592fc0 <.text+0x191fc0>
59fbc5: popl %ebx
59fbc6: popl %ebp
59fbc7: retl $0x10
59fbca: nop
59fbcb: nop
59fbcc: nop
59fbcd: nop
59fbce: nop
59fbcf: nop
59fbd0: pushl %esi
59fbd1: movl 0x14(%esp), %esi
```
Direct internal callees:
- `0x00596fc7` -> `0x00596da0` `multiplayer_transport_submit_profile_key_query_bundle`
Data refs:
- none
### `0x00596fd0` -> `0x00596fd0` `multiplayer_transport_dispatch_status_route_event`
- Size: `219`
- Calling convention: `cdecl`
- Signature: `data.00596fd0(int32_t arg_4h);`
Entry excerpt:
```asm
596fb0: retl $0x4
596fb3: nop
596fb4: nop
596fb5: nop
596fb6: nop
596fb7: nop
596fb8: nop
596fb9: nop
596fba: nop
596fbb: nop
596fbc: nop
596fbd: nop
596fbe: nop
596fbf: nop
596fc0: pushl %esi
596fc1: movl %edx, %eax
596fc3: pushl $0x0
596fc5: movl %ecx, %esi
596fc7: calll 0x596da0 <.text+0x195da0>
596fcc: popl %esi
596fcd: retl
596fce: nop
596fcf: nop
596fd0: movl 0x4(%esp), %eax
596fd4: pushl %esi
596fd5: movl %edx, %esi
596fd7: movl 0x398(%eax), %edx
596fdd: testl %edx, %edx
596fdf: pushl %edi
596fe0: je 0x597077 <.text+0x196077>
596fe6: movl 0xb44(%eax), %edx
596fec: testl %edx, %edx
596fee: je 0x596ff9 <.text+0x195ff9>
```
Callers:
- none
Direct internal callees:
- `0x00597017` -> `0x0058bce0` `fcn.0058bce0`
- `0x00597054` -> `0x0058bce0` `fcn.0058bce0`
- `0x00597029` -> `0x0058cd40` `fcn.0058cd40`
- `0x0059703f` -> `0x0058cd40` `fcn.0058cd40`
- `0x0059706d` -> `0x0058cd40` `fcn.0058cd40`
- `0x00597084` -> `0x0058cd40` `fcn.0058cd40`
Data refs:
- `0x00597008` -> `0x005970ac`
- `0x00597001` -> `0x005970c4`
- `0x0059704d` -> `0x005ce1c8` "openstaging"
### `0x005973d0` -> `0x005973d0` `multiplayer_transport_try_connect_status_route`
- Size: `170`
- Calling convention: `cdecl`
- Signature: `fcn.005973d0(int32_t arg_4h);`
Entry excerpt:
```asm
5973b0: pushl %esi
5973b1: movl %ecx, %esi
5973b3: movl 0x1ecc(%esi), %ecx
5973b9: testl %ecx, %ecx
5973bb: je 0x5973cc <.text+0x1963cc>
5973bd: calll 0x58cfd0 <.text+0x18bfd0>
5973c2: movl $0x0, 0x1ecc(%esi)
5973cc: popl %esi
5973cd: retl
5973ce: nop
5973cf: nop
5973d0: pushl %ebx
5973d1: pushl %esi
5973d2: movl %ecx, %esi
5973d4: movl 0xaf0(%esi), %eax
5973da: testl %eax, %eax
5973dc: pushl %edi
5973dd: leal 0xaf0(%esi), %edi
5973e3: movl %edx, %ebx
5973e5: je 0x5973ec <.text+0x1963ec>
5973e7: calll 0x597350 <.text+0x196350>
5973ec: cmpl $-0x1, %ebx
5973ef: movl 0xb34(%esi), %eax
```
Callers:
- `0x0058daf1` in `0x0058dae0` `fcn.0058dae0`
- `0x005965be` in `0x005965a0` `multiplayer_transport_try_connect_status_route_once`
Caller xref excerpts:
#### `0x0058daf1`
```asm
58dad1: addb %bl, -0x3e(%esi)
58dad4: orb %al, (%eax)
58dad6: nop
58dad7: nop
58dad8: nop
58dad9: nop
58dada: nop
58dadb: nop
58dadc: nop
58dadd: nop
58dade: nop
58dadf: nop
58dae0: movb 0x60(%ecx), %al
58dae3: testb %al, %al
58dae5: jne 0x58daec <.text+0x18caec>
58dae7: xorl %eax, %eax
58dae9: retl $0x4
58daec: movl 0x4(%esp), %eax
58daf0: pushl %eax
58daf1: calll 0x5973d0 <.text+0x1963d0>
58daf6: negl %eax
58daf8: sbbl %eax, %eax
58dafa: negl %eax
58dafc: retl $0x4
58daff: nop
58db00: movb 0x60(%ecx), %al
58db03: testb %al, %al
58db05: je 0x58db16 <.text+0x18cb16>
58db07: movl $0x1, 0xb44(%ecx)
```
#### `0x005965be`
```asm
59659e: nop
59659f: nop
5965a0: movl 0xb40(%ecx), %eax
5965a6: testl %eax, %eax
5965a8: je 0x5965af <.text+0x1955af>
5965aa: xorl %eax, %eax
5965ac: retl $0x4
5965af: movl 0x4(%esp), %eax
5965b3: pushl %eax
5965b4: movl $0x1, 0xb40(%ecx)
5965be: calll 0x5973d0 <.text+0x1963d0>
5965c3: negl %eax
5965c5: sbbl %eax, %eax
5965c7: negl %eax
5965c9: retl $0x4
5965cc: nop
5965cd: nop
5965ce: nop
5965cf: nop
5965d0: testl %edx, %edx
5965d2: pushl %esi
5965d3: movl %ecx, %esi
5965d5: je 0x5965dc <.text+0x1955dc>
5965d7: calll 0x597350 <.text+0x196350>
5965dc: movl 0xb40(%esi), %eax
```
Direct internal callees:
- `0x00597460` -> `0x0058bc90` `fcn.0058bc90`
- `0x0059743c` -> `0x0058c9b0` `fcn.0058c9b0`
- `0x0059742d` -> `0x0058cc40` `fcn.0058cc40`
- `0x005973e7` -> `0x00597350` `multiplayer_transport_release_status_route`
Data refs:
- `0x0059740f` -> `0x00596fd0`
- `0x0059740a` -> `0x005970e0`
- `0x00597405` -> `0x00597180`
- `0x00597400` -> `0x005971b0`
- `0x005973fb` -> `0x00597270`
- `0x005973f6` -> `0x005972c0`
- `0x0059745b` -> `0x00597330`

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,92 @@
digraph shell_load {
graph [rankdir=LR, labelloc="t", labeljust="l"];
label="Setup Window Dispatch Subgraph";
node [shape=box, style="rounded,filled", fillcolor="#f8f8f8", color="#555555", fontname="Helvetica"];
edge [color="#666666", fontname="Helvetica"];
subgraph cluster_bootstrap {
label="bootstrap";
color="#cccccc";
"0x00482ec0" [label="0x00482ec0\\nshell_transition_mode", fillcolor="#f8f8f8"];
"0x004840e0" [label="0x004840e0\\nbootstrap_init_shell_window_services", fillcolor="#f8f8f8"];
}
subgraph cluster_map {
label="map";
color="#cccccc";
"0x00434300" [label="0x00434300\\nworld_runtime_release_global_services", fillcolor="#f8f8f8"];
"0x004384d0" [label="0x004384d0\\nworld_run_post_load_generation_pipeline", fillcolor="#f8f8f8"];
"0x00438890" [label="0x00438890\\nshell_active_mode_run_profile_startup_and_load_dispatch", fillcolor="#f8f8f8"];
"0x00443a50" [label="0x00443a50\\nworld_entry_transition_and_runtime_bringup", fillcolor="#f8f8f8"];
"0x00445ac0" [label="0x00445ac0\\nshell_map_file_entry_coordinator", fillcolor="#f8f8f8"];
}
subgraph cluster_shell {
label="shell";
color="#cccccc";
"0x0046b780" [label="0x0046b780\\nmultiplayer_preview_dataset_service_launch_state_and_warn_out_of_sync", fillcolor="#f8f8f8"];
"0x004b8dc0" [label="0x004b8dc0\\nshell_campaign_window_destroy", fillcolor="#f8f8f8"];
"0x004b8e60" [label="0x004b8e60\\nshell_campaign_window_construct", fillcolor="#f8f8f8"];
"0x004c7bc0" [label="0x004c7bc0\\nshell_credits_window_destroy", fillcolor="#f8f8f8"];
"0x004c7fc0" [label="0x004c7fc0\\nshell_credits_window_construct", fillcolor="#f8f8f8"];
"0x004dfbe0" [label="0x004dfbe0\\nshell_game_window_construct", fillcolor="#f8f8f8"];
"0x004dfd70" [label="0x004dfd70\\nshell_game_window_destroy", fillcolor="#f8f8f8"];
"0x004ea620" [label="0x004ea620\\nshell_load_screen_window_construct", fillcolor="#f8f8f8"];
"0x004ea730" [label="0x004ea730\\nshell_load_screen_window_destroy", fillcolor="#f8f8f8"];
"0x004ee3a0" [label="0x004ee3a0\\nmultiplayer_reset_tool_globals", fillcolor="#f8f8f8"];
"0x004efe80" [label="0x004efe80\\nmultiplayer_window_init_globals", fillcolor="#f8f8f8"];
"0x00502220" [label="0x00502220\\nshell_setup_window_publish_selected_profile_labels_and_preview_surface [seed]", fillcolor="#ffe9a8"];
"0x00502550" [label="0x00502550\\nshell_setup_window_refresh_selection_lists_and_summary_fields [seed]", fillcolor="#ffe9a8"];
"0x00502910" [label="0x00502910\\nshell_setup_window_refresh_mode_dependent_lists [seed]", fillcolor="#ffe9a8"];
"0x00502c00" [label="0x00502c00\\nshell_setup_window_select_launch_mode_and_apply_shell_state [seed]", fillcolor="#ffe9a8"];
"0x005033d0" [label="0x005033d0\\nshell_setup_window_handle_message [seed]", fillcolor="#ffe9a8"];
"0x00504010" [label="0x00504010\\nshell_setup_window_construct [seed]", fillcolor="#ffe9a8"];
"0x005174e0" [label="0x005174e0\\nshell_video_window_construct", fillcolor="#f8f8f8"];
"0x00517570" [label="0x00517570\\nshell_video_window_destroy", fillcolor="#f8f8f8"];
}
subgraph cluster_support {
label="support";
color="#cccccc";
"0x00559520" [label="0x00559520\\nsurface_init_rgba_pixel_buffer", fillcolor="#f8f8f8"];
}
"0x00434300" -> "0x00443a50";
"0x00434300" -> "0x00482ec0";
"0x00438890" -> "0x004384d0";
"0x00438890" -> "0x00445ac0";
"0x00438890" -> "0x005033d0";
"0x00443a50" -> "0x00438890";
"0x00443a50" -> "0x00482ec0";
"0x00445ac0" -> "0x00443a50";
"0x0046b780" -> "0x00438890";
"0x0046b780" -> "0x00445ac0";
"0x00482ec0" -> "0x00438890";
"0x00482ec0" -> "0x00443a50";
"0x00482ec0" -> "0x004840e0";
"0x00482ec0" -> "0x004b8dc0";
"0x00482ec0" -> "0x004b8e60";
"0x00482ec0" -> "0x004c7bc0";
"0x00482ec0" -> "0x004c7fc0";
"0x00482ec0" -> "0x004dfbe0";
"0x00482ec0" -> "0x004dfd70";
"0x00482ec0" -> "0x004ea620";
"0x00482ec0" -> "0x004ea730";
"0x00482ec0" -> "0x004efe80";
"0x00482ec0" -> "0x00504010";
"0x00482ec0" -> "0x005174e0";
"0x00482ec0" -> "0x00517570";
"0x004840e0" -> "0x00482ec0";
"0x004b8dc0" -> "0x004b8e60";
"0x004c7bc0" -> "0x004c7fc0";
"0x004dfbe0" -> "0x004dfd70";
"0x004ea620" -> "0x004ea730";
"0x004ea730" -> "0x004ea620";
"0x004ee3a0" -> "0x00482ec0";
"0x00502220" -> "0x00502c00";
"0x00502220" -> "0x00559520";
"0x00502910" -> "0x00502c00";
"0x00502c00" -> "0x00438890";
"0x00502c00" -> "0x005033d0";
"0x005033d0" -> "0x00502220";
"0x005033d0" -> "0x00502550";
"0x005033d0" -> "0x00502910";
"0x005033d0" -> "0x00502c00";
"0x005033d0" -> "0x00504010";
"0x00517570" -> "0x005174e0";
}

View file

@ -0,0 +1,102 @@
# Setup Window Dispatch Subgraph
- Nodes: `27`
- Edges: `43`
- Seeds: `0x00502220`, `0x00502550`, `0x00502910`, `0x00502c00`, `0x005033d0`, `0x00504010`
- Graphviz: `setup-window-subgraph.dot`
## Nodes
| Address | Name | Subsystem | Confidence |
| --- | --- | --- | --- |
| `0x00434300` | `world_runtime_release_global_services` | `map` | `3` |
| `0x004384d0` | `world_run_post_load_generation_pipeline` | `map` | `4` |
| `0x00438890` | `shell_active_mode_run_profile_startup_and_load_dispatch` | `map` | `4` |
| `0x00443a50` | `world_entry_transition_and_runtime_bringup` | `map` | `4` |
| `0x00445ac0` | `shell_map_file_entry_coordinator` | `map` | `4` |
| `0x0046b780` | `multiplayer_preview_dataset_service_launch_state_and_warn_out_of_sync` | `shell` | `4` |
| `0x00482ec0` | `shell_transition_mode` | `bootstrap` | `4` |
| `0x004840e0` | `bootstrap_init_shell_window_services` | `bootstrap` | `4` |
| `0x004b8dc0` | `shell_campaign_window_destroy` | `shell` | `4` |
| `0x004b8e60` | `shell_campaign_window_construct` | `shell` | `4` |
| `0x004c7bc0` | `shell_credits_window_destroy` | `shell` | `4` |
| `0x004c7fc0` | `shell_credits_window_construct` | `shell` | `4` |
| `0x004dfbe0` | `shell_game_window_construct` | `shell` | `4` |
| `0x004dfd70` | `shell_game_window_destroy` | `shell` | `4` |
| `0x004ea620` | `shell_load_screen_window_construct` | `shell` | `4` |
| `0x004ea730` | `shell_load_screen_window_destroy` | `shell` | `4` |
| `0x004ee3a0` | `multiplayer_reset_tool_globals` | `shell` | `3` |
| `0x004efe80` | `multiplayer_window_init_globals` | `shell` | `4` |
| `0x00502220` | `shell_setup_window_publish_selected_profile_labels_and_preview_surface` | `shell` | `3` |
| `0x00502550` | `shell_setup_window_refresh_selection_lists_and_summary_fields` | `shell` | `3` |
| `0x00502910` | `shell_setup_window_refresh_mode_dependent_lists` | `shell` | `3` |
| `0x00502c00` | `shell_setup_window_select_launch_mode_and_apply_shell_state` | `shell` | `4` |
| `0x005033d0` | `shell_setup_window_handle_message` | `shell` | `3` |
| `0x00504010` | `shell_setup_window_construct` | `shell` | `4` |
| `0x005174e0` | `shell_video_window_construct` | `shell` | `4` |
| `0x00517570` | `shell_video_window_destroy` | `shell` | `4` |
| `0x00559520` | `surface_init_rgba_pixel_buffer` | `support` | `3` |
## Edges
- `0x00434300` `world_runtime_release_global_services`
-> `0x00443a50` `world_entry_transition_and_runtime_bringup`
-> `0x00482ec0` `shell_transition_mode`
- `0x00438890` `shell_active_mode_run_profile_startup_and_load_dispatch`
-> `0x004384d0` `world_run_post_load_generation_pipeline`
-> `0x00445ac0` `shell_map_file_entry_coordinator`
-> `0x005033d0` `shell_setup_window_handle_message`
- `0x00443a50` `world_entry_transition_and_runtime_bringup`
-> `0x00438890` `shell_active_mode_run_profile_startup_and_load_dispatch`
-> `0x00482ec0` `shell_transition_mode`
- `0x00445ac0` `shell_map_file_entry_coordinator`
-> `0x00443a50` `world_entry_transition_and_runtime_bringup`
- `0x0046b780` `multiplayer_preview_dataset_service_launch_state_and_warn_out_of_sync`
-> `0x00438890` `shell_active_mode_run_profile_startup_and_load_dispatch`
-> `0x00445ac0` `shell_map_file_entry_coordinator`
- `0x00482ec0` `shell_transition_mode`
-> `0x00438890` `shell_active_mode_run_profile_startup_and_load_dispatch`
-> `0x00443a50` `world_entry_transition_and_runtime_bringup`
-> `0x004840e0` `bootstrap_init_shell_window_services`
-> `0x004b8dc0` `shell_campaign_window_destroy`
-> `0x004b8e60` `shell_campaign_window_construct`
-> `0x004c7bc0` `shell_credits_window_destroy`
-> `0x004c7fc0` `shell_credits_window_construct`
-> `0x004dfbe0` `shell_game_window_construct`
-> `0x004dfd70` `shell_game_window_destroy`
-> `0x004ea620` `shell_load_screen_window_construct`
-> `0x004ea730` `shell_load_screen_window_destroy`
-> `0x004efe80` `multiplayer_window_init_globals`
-> `0x00504010` `shell_setup_window_construct`
-> `0x005174e0` `shell_video_window_construct`
-> `0x00517570` `shell_video_window_destroy`
- `0x004840e0` `bootstrap_init_shell_window_services`
-> `0x00482ec0` `shell_transition_mode`
- `0x004b8dc0` `shell_campaign_window_destroy`
-> `0x004b8e60` `shell_campaign_window_construct`
- `0x004c7bc0` `shell_credits_window_destroy`
-> `0x004c7fc0` `shell_credits_window_construct`
- `0x004dfbe0` `shell_game_window_construct`
-> `0x004dfd70` `shell_game_window_destroy`
- `0x004ea620` `shell_load_screen_window_construct`
-> `0x004ea730` `shell_load_screen_window_destroy`
- `0x004ea730` `shell_load_screen_window_destroy`
-> `0x004ea620` `shell_load_screen_window_construct`
- `0x004ee3a0` `multiplayer_reset_tool_globals`
-> `0x00482ec0` `shell_transition_mode`
- `0x00502220` `shell_setup_window_publish_selected_profile_labels_and_preview_surface`
-> `0x00502c00` `shell_setup_window_select_launch_mode_and_apply_shell_state`
-> `0x00559520` `surface_init_rgba_pixel_buffer`
- `0x00502910` `shell_setup_window_refresh_mode_dependent_lists`
-> `0x00502c00` `shell_setup_window_select_launch_mode_and_apply_shell_state`
- `0x00502c00` `shell_setup_window_select_launch_mode_and_apply_shell_state`
-> `0x00438890` `shell_active_mode_run_profile_startup_and_load_dispatch`
-> `0x005033d0` `shell_setup_window_handle_message`
- `0x005033d0` `shell_setup_window_handle_message`
-> `0x00502220` `shell_setup_window_publish_selected_profile_labels_and_preview_surface`
-> `0x00502550` `shell_setup_window_refresh_selection_lists_and_summary_fields`
-> `0x00502910` `shell_setup_window_refresh_mode_dependent_lists`
-> `0x00502c00` `shell_setup_window_select_launch_mode_and_apply_shell_state`
-> `0x00504010` `shell_setup_window_construct`
- `0x00517570` `shell_video_window_destroy`
-> `0x005174e0` `shell_video_window_construct`

View file

@ -0,0 +1,398 @@
digraph shell_load {
graph [rankdir=LR, labelloc="t", labeljust="l"];
label="Setup Window Submode Subgraph (Depth 5, Forward Only)";
node [shape=box, style="rounded,filled", fillcolor="#f8f8f8", color="#555555", fontname="Helvetica"];
edge [color="#666666", fontname="Helvetica"];
subgraph cluster_bootstrap {
label="bootstrap";
color="#cccccc";
"0x00482ec0" [label="0x00482ec0\\nshell_transition_mode", fillcolor="#f8f8f8"];
"0x004840e0" [label="0x004840e0\\nbootstrap_init_shell_window_services", fillcolor="#f8f8f8"];
}
subgraph cluster_map {
label="map";
color="#cccccc";
"0x00402cb0" [label="0x00402cb0\\ncity_connection_try_build_route_with_optional_direct_site_placement", fillcolor="#f8f8f8"];
"0x00404640" [label="0x00404640\\ncity_connection_bonus_try_compact_route_builder_from_region_entry", fillcolor="#f8f8f8"];
"0x004046a0" [label="0x004046a0\\ncity_connection_bonus_build_peer_route_candidate", fillcolor="#f8f8f8"];
"0x00404c60" [label="0x00404c60\\ncity_connection_try_build_route_between_region_entry_pair", fillcolor="#f8f8f8"];
"0x0040e360" [label="0x0040e360\\nplaced_structure_refresh_linked_site_anchor_position_triplet_for_local_runtime", fillcolor="#f8f8f8"];
"0x0040e450" [label="0x0040e450\\nplaced_structure_refresh_cloned_local_runtime_record_from_current_candidate_stem", fillcolor="#f8f8f8"];
"0x0040ee10" [label="0x0040ee10\\nplaced_structure_refresh_local_runtime_position_triplet_and_linked_anchor_followon", fillcolor="#f8f8f8"];
"0x0040ef10" [label="0x0040ef10\\nplaced_structure_finalize_creation_or_rebuild_local_runtime_state", fillcolor="#f8f8f8"];
"0x00412ca0" [label="0x00412ca0\\nworld_region_pick_commercial_profile_label_by_region_rank", fillcolor="#f8f8f8"];
"0x004133b0" [label="0x004133b0\\nplaced_structure_collection_refresh_local_runtime_records_and_position_scalars", fillcolor="#f8f8f8"];
"0x004134d0" [label="0x004134d0\\nplaced_structure_collection_allocate_and_construct_entry", fillcolor="#f8f8f8"];
"0x00413f50" [label="0x00413f50\\nplaced_structure_local_runtime_site_id_queue_pop_next", fillcolor="#f8f8f8"];
"0x00414470" [label="0x00414470\\nplaced_structure_cache_projected_rect_profile_slot_id", fillcolor="#f8f8f8"];
"0x00414480" [label="0x00414480\\nplaced_structure_local_runtime_site_id_queue_count", fillcolor="#f8f8f8"];
"0x00415f20" [label="0x00415f20\\nplaced_structure_recursive_collect_connected_component_tile_bounds", fillcolor="#f8f8f8"];
"0x00416e20" [label="0x00416e20\\nindexed_collection_resolve_live_entry_id_by_stem_string", fillcolor="#f8f8f8"];
"0x00417840" [label="0x00417840\\nplaced_structure_project_candidate_grid_extent_offset_by_rotation", fillcolor="#f8f8f8"];
"0x00418a60" [label="0x00418a60\\nplaced_structure_clone_template_local_runtime_record_for_subject_and_refresh_component_bounds", fillcolor="#f8f8f8"];
"0x004197e0" [label="0x004197e0\\nplaced_structure_validate_projected_candidate_placement", fillcolor="#f8f8f8"];
"0x0041e220" [label="0x0041e220\\nstructure_candidate_is_enabled_for_current_year", fillcolor="#f8f8f8"];
"0x0041e2b0" [label="0x0041e2b0\\nstructure_candidate_rebuild_local_service_metrics", fillcolor="#f8f8f8"];
"0x0041ea50" [label="0x0041ea50\\nworld_setup_building_collection_phase", fillcolor="#f8f8f8"];
"0x0041f9b0" [label="0x0041f9b0\\nworld_region_count_structure_profiles_before_year_for_category", fillcolor="#f8f8f8"];
"0x0041fac0" [label="0x0041fac0\\nworld_region_read_structure_profile_label_and_weight_by_index", fillcolor="#f8f8f8"];
"0x00421b60" [label="0x00421b60\\nworld_region_collection_seed_default_regions", fillcolor="#f8f8f8"];
"0x00421c20" [label="0x00421c20\\nworld_region_collection_run_building_population_pass", fillcolor="#f8f8f8"];
"0x00422900" [label="0x00422900\\nworld_region_accumulate_structure_category_totals", fillcolor="#f8f8f8"];
"0x00422be0" [label="0x00422be0\\nworld_region_count_placed_structures_for_category", fillcolor="#f8f8f8"];
"0x00422ee0" [label="0x00422ee0\\nworld_region_try_place_candidate_structure", fillcolor="#f8f8f8"];
"0x004235c0" [label="0x004235c0\\nworld_region_balance_structure_demand_and_place_candidates", fillcolor="#f8f8f8"];
"0x00437220" [label="0x00437220\\nworld_build_chairman_profile_slot_records", fillcolor="#f8f8f8"];
"0x004377a0" [label="0x004377a0\\nworld_seed_default_chairman_profile_slots", fillcolor="#f8f8f8"];
"0x004384d0" [label="0x004384d0\\nworld_run_post_load_generation_pipeline", fillcolor="#f8f8f8"];
"0x00438890" [label="0x00438890\\nshell_active_mode_run_profile_startup_and_load_dispatch", fillcolor="#f8f8f8"];
"0x00443a50" [label="0x00443a50\\nworld_entry_transition_and_runtime_bringup", fillcolor="#f8f8f8"];
"0x00445ac0" [label="0x00445ac0\\nshell_map_file_entry_coordinator", fillcolor="#f8f8f8"];
"0x00446d40" [label="0x00446d40\\nworld_load_saved_runtime_state_bundle", fillcolor="#f8f8f8"];
"0x0044fb70" [label="0x0044fb70\\nworld_compute_transport_and_pricing_grid", fillcolor="#f8f8f8"];
"0x00477820" [label="0x00477820\\nprofile_collection_count_active_chairman_records", fillcolor="#f8f8f8"];
"0x00477860" [label="0x00477860\\nprofile_collection_get_nth_active_chairman_record", fillcolor="#f8f8f8"];
"0x0047d440" [label="0x0047d440\\nworld_conditionally_seed_named_starting_railroad_companies", fillcolor="#f8f8f8"];
"0x004882e0" [label="0x004882e0\\nworld_region_border_overlay_rebuild", fillcolor="#f8f8f8"];
"0x004a01a0" [label="0x004a01a0\\nroute_entry_collection_try_build_path_between_optional_endpoint_entries", fillcolor="#f8f8f8"];
"0x004a5280" [label="0x004a5280\\naux_route_entry_tracker_query_route_entry_pair_metric_via_weighted_recursive_search", fillcolor="#f8f8f8"];
"0x004a5900" [label="0x004a5900\\naux_route_entry_tracker_query_route_entry_pair_metric_via_recursive_neighbor_walk", fillcolor="#f8f8f8"];
"0x004a65b0" [label="0x004a65b0\\naux_route_entry_tracker_dispatch_route_entry_pair_metric_query", fillcolor="#f8f8f8"];
"0x004a6630" [label="0x004a6630\\naux_route_entry_tracker_query_best_route_entry_pair_metric_with_endpoint_fallbacks", fillcolor="#f8f8f8"];
"0x004c9da0" [label="0x004c9da0\\nmap_editor_chairman_slot_panel_format_slot_card", fillcolor="#f8f8f8"];
"0x004cc2d0" [label="0x004cc2d0\\nmap_editor_chairman_slot_panel_construct", fillcolor="#f8f8f8"];
}
subgraph cluster_shell {
label="shell";
color="#cccccc";
"0x0042a970" [label="0x0042a970\\nshell_open_file_dialog_copy_selected_path_and_restore_cwd", fillcolor="#f8f8f8"];
"0x004333f0" [label="0x004333f0\\nshell_setup_build_file_list_records_from_current_root_and_pattern", fillcolor="#f8f8f8"];
"0x004336a0" [label="0x004336a0\\nshell_setup_file_list_construct_and_scan_dataset", fillcolor="#f8f8f8"];
"0x00434050" [label="0x00434050\\nshell_has_auxiliary_preview_owner", fillcolor="#f8f8f8"];
"0x0046a6c0" [label="0x0046a6c0\\nmultiplayer_session_event_publish_registration_field", fillcolor="#f8f8f8"];
"0x004839b0" [label="0x004839b0\\nshell_setup_query_file_list_uses_map_extension_pattern", fillcolor="#f8f8f8"];
"0x004839e0" [label="0x004839e0\\nshell_setup_query_file_list_root_dir_name", fillcolor="#f8f8f8"];
"0x00484910" [label="0x00484910\\nshell_save_graphics_config", fillcolor="#f8f8f8"];
"0x004b8dc0" [label="0x004b8dc0\\nshell_campaign_window_destroy", fillcolor="#f8f8f8"];
"0x004b8e60" [label="0x004b8e60\\nshell_campaign_window_construct", fillcolor="#f8f8f8"];
"0x004b9a20" [label="0x004b9a20\\nshell_building_detail_refresh_flagged_service_capability_rows", fillcolor="#f8f8f8"];
"0x004ba3d0" [label="0x004ba3d0\\nshell_building_detail_refresh_subject_cargo_and_service_rows", fillcolor="#f8f8f8"];
"0x004bad20" [label="0x004bad20\\nshell_building_detail_refresh_subject_pair_value_rows", fillcolor="#f8f8f8"];
"0x004c2ca0" [label="0x004c2ca0\\nshell_company_detail_window_refresh_controls", fillcolor="#f8f8f8"];
"0x004c3890" [label="0x004c3890\\nshell_company_detail_issue_bond_offer_flow", fillcolor="#f8f8f8"];
"0x004c3f30" [label="0x004c3f30\\nshell_company_detail_issue_stock_offer_flow", fillcolor="#f8f8f8"];
"0x004c46d0" [label="0x004c46d0\\nshell_company_detail_buyback_stock_flow", fillcolor="#f8f8f8"];
"0x004c4c70" [label="0x004c4c70\\nshell_company_detail_setup_dividend_rate_adjust_controls", fillcolor="#f8f8f8"];
"0x004c4e30" [label="0x004c4e30\\nshell_company_detail_render_change_dividend_rate_dialog", fillcolor="#f8f8f8"];
"0x004c5140" [label="0x004c5140\\nshell_company_detail_handle_change_dividend_rate_dialog_message", fillcolor="#f8f8f8"];
"0x004c5360" [label="0x004c5360\\nshell_company_detail_change_dividend_rate_flow", fillcolor="#f8f8f8"];
"0x004c56a0" [label="0x004c56a0\\nshell_company_detail_window_handle_message", fillcolor="#f8f8f8"];
"0x004c5a0e" [label="0x004c5a0e\\nshell_company_detail_resign_chairmanship_flow", fillcolor="#f8f8f8"];
"0x004c5b99" [label="0x004c5b99\\nshell_company_detail_bankruptcy_flow", fillcolor="#f8f8f8"];
"0x004c5fc9" [label="0x004c5fc9\\nshell_company_detail_buy_territory_access_rights_flow", fillcolor="#f8f8f8"];
"0x004c7bc0" [label="0x004c7bc0\\nshell_credits_window_destroy", fillcolor="#f8f8f8"];
"0x004c7fc0" [label="0x004c7fc0\\nshell_credits_window_construct", fillcolor="#f8f8f8"];
"0x004c98a0" [label="0x004c98a0\\nshell_open_custom_modal_dialog_with_callbacks", fillcolor="#f8f8f8"];
"0x004dd010" [label="0x004dd010\\nshell_file_request_dialog_collect_target_path", fillcolor="#f8f8f8"];
"0x004ddbd0" [label="0x004ddbd0\\nshell_detail_panel_transition_manager", fillcolor="#f8f8f8"];
"0x004dfbe0" [label="0x004dfbe0\\nshell_game_window_construct", fillcolor="#f8f8f8"];
"0x004dfd70" [label="0x004dfd70\\nshell_game_window_destroy", fillcolor="#f8f8f8"];
"0x004e0ba0" [label="0x004e0ba0\\ngame_uppermost_window_handle_message", fillcolor="#f8f8f8"];
"0x004ea620" [label="0x004ea620\\nshell_load_screen_window_construct", fillcolor="#f8f8f8"];
"0x004ea730" [label="0x004ea730\\nshell_load_screen_window_destroy", fillcolor="#f8f8f8"];
"0x004eb0b0" [label="0x004eb0b0\\nshell_open_grayscale_map_tga_picker_and_stage_selection", fillcolor="#f8f8f8"];
"0x004ec640" [label="0x004ec640\\nshell_company_detail_attempt_merger_flow", fillcolor="#f8f8f8"];
"0x004ee0e0" [label="0x004ee0e0\\nmultiplayer_open_staged_text_entry_dialog", fillcolor="#f8f8f8"];
"0x004efe80" [label="0x004efe80\\nmultiplayer_window_init_globals", fillcolor="#f8f8f8"];
"0x004fe120" [label="0x004fe120\\nshell_has_settings_window", fillcolor="#f8f8f8"];
"0x00501e50" [label="0x00501e50\\nshell_open_settings_window", fillcolor="#f8f8f8"];
"0x00501f20" [label="0x00501f20\\nshell_query_registry_open_command_for_http_or_rtf_target", fillcolor="#f8f8f8"];
"0x00502030" [label="0x00502030\\nshell_setup_window_draw_table_driven_payload_category_row", fillcolor="#f8f8f8"];
"0x00502160" [label="0x00502160\\nshell_setup_window_set_first_persisted_selector_flag_or_index_and_save_config", fillcolor="#f8f8f8"];
"0x005021c0" [label="0x005021c0\\nshell_setup_window_set_second_persisted_selector_flag_or_index_and_save_config", fillcolor="#f8f8f8"];
"0x00502220" [label="0x00502220\\nshell_setup_window_publish_selected_profile_labels_and_preview_surface [seed]", fillcolor="#ffe9a8"];
"0x00502550" [label="0x00502550\\nshell_setup_window_refresh_selection_lists_and_summary_fields [seed]", fillcolor="#ffe9a8"];
"0x005027b0" [label="0x005027b0\\nshell_setup_window_refresh_file_backed_selection_list_panel [seed]", fillcolor="#ffe9a8"];
"0x00502910" [label="0x00502910\\nshell_setup_window_refresh_mode_dependent_lists [seed]", fillcolor="#ffe9a8"];
"0x00502c00" [label="0x00502c00\\nshell_setup_window_select_launch_mode_and_apply_shell_state [seed]", fillcolor="#ffe9a8"];
"0x005033d0" [label="0x005033d0\\nshell_setup_window_handle_message [seed]", fillcolor="#ffe9a8"];
"0x00504010" [label="0x00504010\\nshell_setup_window_construct [seed]", fillcolor="#ffe9a8"];
"0x0050ccc0" [label="0x0050ccc0\\nshell_company_detail_attempt_chairmanship_takeover_flow", fillcolor="#f8f8f8"];
"0x005174e0" [label="0x005174e0\\nshell_video_window_construct", fillcolor="#f8f8f8"];
"0x00517570" [label="0x00517570\\nshell_video_window_destroy", fillcolor="#f8f8f8"];
"0x0051c920" [label="0x0051c920\\nlocalization_lookup_display_label_by_stem_or_fallback", fillcolor="#f8f8f8"];
"0x0051eea0" [label="0x0051eea0\\nshell_save_display_runtime_config", fillcolor="#f8f8f8"];
"0x0053f830" [label="0x0053f830\\nshell_window_find_registered_child_control_by_id", fillcolor="#f8f8f8"];
"0x0053f9c0" [label="0x0053f9c0\\nshell_window_register_child_control_sorted_by_priority_and_optional_tag", fillcolor="#f8f8f8"];
"0x0053fa50" [label="0x0053fa50\\nshell_window_bind_resource_and_initialize_child_control_links", fillcolor="#f8f8f8"];
"0x005411c0" [label="0x005411c0\\nshell_query_tga_header_is_supported_truecolor_image", fillcolor="#f8f8f8"];
"0x00552560" [label="0x00552560\\nshell_queue_world_anchor_marker", fillcolor="#f8f8f8"];
"0x00558130" [label="0x00558130\\nshell_child_control_set_owner_resolve_caption_and_refresh", fillcolor="#f8f8f8"];
}
subgraph cluster_simulation {
label="simulation";
color="#cccccc";
"0x00404ce0" [label="0x00404ce0\\nsimulation_try_select_and_publish_company_start_or_city_connection_news", fillcolor="#f8f8f8"];
"0x00423d70" [label="0x00423d70\\ncompany_repay_bond_slot_and_compact_debt_table", fillcolor="#f8f8f8"];
"0x00426260" [label="0x00426260\\ncompany_compute_board_approved_dividend_rate_ceiling", fillcolor="#f8f8f8"];
"0x00426d60" [label="0x00426d60\\ncompany_deactivate_and_clear_chairman_share_links", fillcolor="#f8f8f8"];
"0x00437b20" [label="0x00437b20\\nsimulation_run_chunked_fast_forward_burst", fillcolor="#f8f8f8"];
"0x00482d10" [label="0x00482d10\\nruntime_query_cached_local_exe_version_float", fillcolor="#f8f8f8"];
"0x00482d80" [label="0x00482d80\\nruntime_query_cached_local_exe_version_string", fillcolor="#f8f8f8"];
"0x00482e00" [label="0x00482e00\\nruntime_query_hundredths_scaled_build_version", fillcolor="#f8f8f8"];
"0x00517d40" [label="0x00517d40\\nindexed_collection_entry_id_is_live", fillcolor="#f8f8f8"];
"0x00518140" [label="0x00518140\\nindexed_collection_resolve_live_entry_by_id", fillcolor="#f8f8f8"];
"0x00518380" [label="0x00518380\\nindexed_collection_get_nth_live_entry_id", fillcolor="#f8f8f8"];
}
subgraph cluster_support {
label="support";
color="#cccccc";
"0x00559520" [label="0x00559520\\nsurface_init_rgba_pixel_buffer", fillcolor="#f8f8f8"];
}
"0x00402cb0" -> "0x00404640";
"0x00402cb0" -> "0x004046a0";
"0x00402cb0" -> "0x00404c60";
"0x00402cb0" -> "0x00404ce0";
"0x00402cb0" -> "0x0040ef10";
"0x00402cb0" -> "0x004134d0";
"0x00402cb0" -> "0x00417840";
"0x00402cb0" -> "0x004197e0";
"0x00402cb0" -> "0x00482e00";
"0x00402cb0" -> "0x004a01a0";
"0x00404640" -> "0x00402cb0";
"0x00404640" -> "0x004046a0";
"0x004046a0" -> "0x00402cb0";
"0x004046a0" -> "0x00404640";
"0x00404c60" -> "0x00402cb0";
"0x00404c60" -> "0x00404ce0";
"0x00404ce0" -> "0x00404c60";
"0x0040e360" -> "0x0040ee10";
"0x0040e450" -> "0x004133b0";
"0x0040e450" -> "0x00414470";
"0x0040e450" -> "0x00416e20";
"0x0040e450" -> "0x00418a60";
"0x0040ee10" -> "0x0040e360";
"0x0040ee10" -> "0x004133b0";
"0x0040ee10" -> "0x00415f20";
"0x0040ee10" -> "0x00434050";
"0x00412ca0" -> "0x004235c0";
"0x004133b0" -> "0x0040e450";
"0x004133b0" -> "0x0040ee10";
"0x004133b0" -> "0x00413f50";
"0x004133b0" -> "0x00414480";
"0x004134d0" -> "0x00422ee0";
"0x00413f50" -> "0x004133b0";
"0x00414470" -> "0x0040e450";
"0x00414470" -> "0x00418a60";
"0x00414480" -> "0x004133b0";
"0x00415f20" -> "0x0040ee10";
"0x00416e20" -> "0x0040e450";
"0x00416e20" -> "0x00518140";
"0x00416e20" -> "0x00518380";
"0x00417840" -> "0x00402cb0";
"0x00417840" -> "0x004197e0";
"0x00418a60" -> "0x0040e450";
"0x004197e0" -> "0x00402cb0";
"0x004197e0" -> "0x00417840";
"0x0041e2b0" -> "0x0041e220";
"0x0041e2b0" -> "0x0041ea50";
"0x0041ea50" -> "0x0041e2b0";
"0x0041ea50" -> "0x00518140";
"0x0041ea50" -> "0x00518380";
"0x0041f9b0" -> "0x004235c0";
"0x00421b60" -> "0x004384d0";
"0x00421c20" -> "0x004235c0";
"0x00422900" -> "0x004235c0";
"0x00422be0" -> "0x004235c0";
"0x00422ee0" -> "0x004134d0";
"0x00422ee0" -> "0x004235c0";
"0x004235c0" -> "0x00412ca0";
"0x004235c0" -> "0x0041f9b0";
"0x004235c0" -> "0x0041fac0";
"0x004235c0" -> "0x00422900";
"0x004235c0" -> "0x00422be0";
"0x004235c0" -> "0x00422ee0";
"0x00426260" -> "0x004c5140";
"0x004333f0" -> "0x004336a0";
"0x004333f0" -> "0x004839b0";
"0x004333f0" -> "0x004839e0";
"0x004333f0" -> "0x0051c920";
"0x004336a0" -> "0x004333f0";
"0x004336a0" -> "0x00502c00";
"0x00437220" -> "0x004384d0";
"0x004377a0" -> "0x004cc2d0";
"0x00437b20" -> "0x004384d0";
"0x004384d0" -> "0x004133b0";
"0x004384d0" -> "0x0041ea50";
"0x004384d0" -> "0x00421b60";
"0x004384d0" -> "0x00421c20";
"0x004384d0" -> "0x00437220";
"0x004384d0" -> "0x004377a0";
"0x004384d0" -> "0x00437b20";
"0x004384d0" -> "0x0044fb70";
"0x004384d0" -> "0x0047d440";
"0x004384d0" -> "0x004882e0";
"0x00438890" -> "0x004384d0";
"0x00438890" -> "0x00445ac0";
"0x00438890" -> "0x005033d0";
"0x00443a50" -> "0x00438890";
"0x00443a50" -> "0x00482ec0";
"0x00445ac0" -> "0x00443a50";
"0x00445ac0" -> "0x00446d40";
"0x00445ac0" -> "0x004dd010";
"0x0046a6c0" -> "0x00482d80";
"0x00477820" -> "0x0047d440";
"0x00477860" -> "0x0047d440";
"0x0047d440" -> "0x004377a0";
"0x0047d440" -> "0x00477820";
"0x0047d440" -> "0x00477860";
"0x00482d10" -> "0x00482d80";
"0x00482d10" -> "0x00482e00";
"0x00482d80" -> "0x0046a6c0";
"0x00482d80" -> "0x00482d10";
"0x00482e00" -> "0x00402cb0";
"0x00482e00" -> "0x00482d10";
"0x00482e00" -> "0x004a65b0";
"0x00482ec0" -> "0x00438890";
"0x00482ec0" -> "0x00443a50";
"0x00482ec0" -> "0x004840e0";
"0x00482ec0" -> "0x004b8dc0";
"0x00482ec0" -> "0x004b8e60";
"0x00482ec0" -> "0x004c7bc0";
"0x00482ec0" -> "0x004c7fc0";
"0x00482ec0" -> "0x004dfbe0";
"0x00482ec0" -> "0x004dfd70";
"0x00482ec0" -> "0x004ea620";
"0x00482ec0" -> "0x004ea730";
"0x00482ec0" -> "0x004efe80";
"0x00482ec0" -> "0x00504010";
"0x00482ec0" -> "0x005174e0";
"0x00482ec0" -> "0x00517570";
"0x004839b0" -> "0x004333f0";
"0x004839e0" -> "0x004333f0";
"0x004840e0" -> "0x00482ec0";
"0x00484910" -> "0x0051eea0";
"0x004882e0" -> "0x004384d0";
"0x004a01a0" -> "0x00402cb0";
"0x004a01a0" -> "0x00518140";
"0x004a5280" -> "0x004a65b0";
"0x004a5900" -> "0x004a5280";
"0x004a5900" -> "0x004a65b0";
"0x004a65b0" -> "0x00482e00";
"0x004a65b0" -> "0x004a5280";
"0x004a65b0" -> "0x004a5900";
"0x004a65b0" -> "0x004a6630";
"0x004a6630" -> "0x004a65b0";
"0x004b8dc0" -> "0x004b8e60";
"0x004ba3d0" -> "0x004b9a20";
"0x004ba3d0" -> "0x004bad20";
"0x004ba3d0" -> "0x00517d40";
"0x004ba3d0" -> "0x00518140";
"0x004ba3d0" -> "0x0051c920";
"0x004bad20" -> "0x004ba3d0";
"0x004c2ca0" -> "0x004c56a0";
"0x004c3890" -> "0x004c56a0";
"0x004c3f30" -> "0x004c56a0";
"0x004c46d0" -> "0x004c56a0";
"0x004c4c70" -> "0x004c4e30";
"0x004c4c70" -> "0x004c5140";
"0x004c4c70" -> "0x004c5360";
"0x004c4c70" -> "0x0053f9c0";
"0x004c4e30" -> "0x004c5360";
"0x004c4e30" -> "0x004c98a0";
"0x004c5140" -> "0x00426260";
"0x004c5140" -> "0x004c5360";
"0x004c5140" -> "0x004c98a0";
"0x004c5360" -> "0x004c4c70";
"0x004c5360" -> "0x004c4e30";
"0x004c5360" -> "0x004c5140";
"0x004c5360" -> "0x004c56a0";
"0x004c56a0" -> "0x00423d70";
"0x004c56a0" -> "0x00426d60";
"0x004c56a0" -> "0x004c2ca0";
"0x004c56a0" -> "0x004c3890";
"0x004c56a0" -> "0x004c3f30";
"0x004c56a0" -> "0x004c46d0";
"0x004c56a0" -> "0x004c5360";
"0x004c56a0" -> "0x004c5a0e";
"0x004c56a0" -> "0x004c5b99";
"0x004c56a0" -> "0x004c5fc9";
"0x004c56a0" -> "0x004ddbd0";
"0x004c56a0" -> "0x004ec640";
"0x004c56a0" -> "0x0050ccc0";
"0x004c5a0e" -> "0x004c56a0";
"0x004c5b99" -> "0x004c56a0";
"0x004c5fc9" -> "0x004c56a0";
"0x004c7bc0" -> "0x004c7fc0";
"0x004c7fc0" -> "0x0053fa50";
"0x004c98a0" -> "0x004ee0e0";
"0x004cc2d0" -> "0x004c9da0";
"0x004dd010" -> "0x004839b0";
"0x004dfbe0" -> "0x004dfd70";
"0x004dfbe0" -> "0x0053fa50";
"0x004e0ba0" -> "0x0053f830";
"0x004ea620" -> "0x004ea730";
"0x004ea620" -> "0x0053fa50";
"0x004ea730" -> "0x004ea620";
"0x004ec640" -> "0x004c56a0";
"0x004ee0e0" -> "0x00484910";
"0x004ee0e0" -> "0x004c98a0";
"0x00502030" -> "0x00504010";
"0x00502030" -> "0x00552560";
"0x00502160" -> "0x00484910";
"0x00502160" -> "0x005021c0";
"0x00502160" -> "0x00504010";
"0x005021c0" -> "0x00484910";
"0x005021c0" -> "0x00502160";
"0x005021c0" -> "0x00504010";
"0x00502220" -> "0x00502c00";
"0x00502220" -> "0x0053f830";
"0x00502220" -> "0x00559520";
"0x005027b0" -> "0x004336a0";
"0x005027b0" -> "0x00502220";
"0x005027b0" -> "0x00502c00";
"0x00502910" -> "0x005027b0";
"0x00502910" -> "0x00502c00";
"0x00502c00" -> "0x004336a0";
"0x00502c00" -> "0x00438890";
"0x00502c00" -> "0x00482d80";
"0x00502c00" -> "0x004eb0b0";
"0x00502c00" -> "0x005027b0";
"0x00502c00" -> "0x00502910";
"0x00502c00" -> "0x005033d0";
"0x005033d0" -> "0x0042a970";
"0x005033d0" -> "0x00484910";
"0x005033d0" -> "0x004eb0b0";
"0x005033d0" -> "0x004fe120";
"0x005033d0" -> "0x00501e50";
"0x005033d0" -> "0x00501f20";
"0x005033d0" -> "0x00502220";
"0x005033d0" -> "0x00502550";
"0x005033d0" -> "0x00502910";
"0x005033d0" -> "0x00502c00";
"0x005033d0" -> "0x00504010";
"0x005033d0" -> "0x005411c0";
"0x00504010" -> "0x00502030";
"0x00504010" -> "0x00502160";
"0x00504010" -> "0x005021c0";
"0x00504010" -> "0x00502550";
"0x00504010" -> "0x00502910";
"0x00504010" -> "0x00502c00";
"0x00504010" -> "0x0053f830";
"0x00504010" -> "0x0053f9c0";
"0x00504010" -> "0x0053fa50";
"0x0050ccc0" -> "0x004c56a0";
"0x005174e0" -> "0x0053fa50";
"0x00517570" -> "0x00484910";
"0x00517570" -> "0x005174e0";
"0x00517d40" -> "0x004ba3d0";
"0x00517d40" -> "0x00518140";
"0x0051c920" -> "0x004ba3d0";
"0x0053f830" -> "0x004e0ba0";
"0x0053f830" -> "0x00502220";
"0x0053f830" -> "0x00504010";
"0x0053f9c0" -> "0x004c4c70";
"0x0053f9c0" -> "0x00558130";
"0x005411c0" -> "0x005033d0";
"0x00558130" -> "0x0053f9c0";
}

View file

@ -0,0 +1,486 @@
# Setup Window Submode Subgraph (Depth 5, Forward Only)
- Nodes: `126`
- Edges: `246`
- Seeds: `0x00502220`, `0x00502550`, `0x005027b0`, `0x00502910`, `0x00502c00`, `0x005033d0`, `0x00504010`
- Graphviz: `setup-window-submodes-depth5-forward-subgraph.dot`
## Nodes
| Address | Name | Subsystem | Confidence |
| --- | --- | --- | --- |
| `0x00402cb0` | `city_connection_try_build_route_with_optional_direct_site_placement` | `map` | `3` |
| `0x00404640` | `city_connection_bonus_try_compact_route_builder_from_region_entry` | `map` | `3` |
| `0x004046a0` | `city_connection_bonus_build_peer_route_candidate` | `map` | `4` |
| `0x00404c60` | `city_connection_try_build_route_between_region_entry_pair` | `map` | `3` |
| `0x00404ce0` | `simulation_try_select_and_publish_company_start_or_city_connection_news` | `simulation` | `3` |
| `0x0040e360` | `placed_structure_refresh_linked_site_anchor_position_triplet_for_local_runtime` | `map` | `2` |
| `0x0040e450` | `placed_structure_refresh_cloned_local_runtime_record_from_current_candidate_stem` | `map` | `2` |
| `0x0040ee10` | `placed_structure_refresh_local_runtime_position_triplet_and_linked_anchor_followon` | `map` | `2` |
| `0x0040ef10` | `placed_structure_finalize_creation_or_rebuild_local_runtime_state` | `map` | `3` |
| `0x00412ca0` | `world_region_pick_commercial_profile_label_by_region_rank` | `map` | `4` |
| `0x004133b0` | `placed_structure_collection_refresh_local_runtime_records_and_position_scalars` | `map` | `2` |
| `0x004134d0` | `placed_structure_collection_allocate_and_construct_entry` | `map` | `3` |
| `0x00413f50` | `placed_structure_local_runtime_site_id_queue_pop_next` | `map` | `3` |
| `0x00414470` | `placed_structure_cache_projected_rect_profile_slot_id` | `map` | `3` |
| `0x00414480` | `placed_structure_local_runtime_site_id_queue_count` | `map` | `3` |
| `0x00415f20` | `placed_structure_recursive_collect_connected_component_tile_bounds` | `map` | `3` |
| `0x00416e20` | `indexed_collection_resolve_live_entry_id_by_stem_string` | `map` | `3` |
| `0x00417840` | `placed_structure_project_candidate_grid_extent_offset_by_rotation` | `map` | `3` |
| `0x00418a60` | `placed_structure_clone_template_local_runtime_record_for_subject_and_refresh_component_bounds` | `map` | `2` |
| `0x004197e0` | `placed_structure_validate_projected_candidate_placement` | `map` | `3` |
| `0x0041e220` | `structure_candidate_is_enabled_for_current_year` | `map` | `3` |
| `0x0041e2b0` | `structure_candidate_rebuild_local_service_metrics` | `map` | `3` |
| `0x0041ea50` | `world_setup_building_collection_phase` | `map` | `3` |
| `0x0041f9b0` | `world_region_count_structure_profiles_before_year_for_category` | `map` | `4` |
| `0x0041fac0` | `world_region_read_structure_profile_label_and_weight_by_index` | `map` | `4` |
| `0x00421b60` | `world_region_collection_seed_default_regions` | `map` | `4` |
| `0x00421c20` | `world_region_collection_run_building_population_pass` | `map` | `4` |
| `0x00422900` | `world_region_accumulate_structure_category_totals` | `map` | `3` |
| `0x00422be0` | `world_region_count_placed_structures_for_category` | `map` | `3` |
| `0x00422ee0` | `world_region_try_place_candidate_structure` | `map` | `4` |
| `0x004235c0` | `world_region_balance_structure_demand_and_place_candidates` | `map` | `4` |
| `0x00423d70` | `company_repay_bond_slot_and_compact_debt_table` | `simulation` | `4` |
| `0x00426260` | `company_compute_board_approved_dividend_rate_ceiling` | `simulation` | `4` |
| `0x00426d60` | `company_deactivate_and_clear_chairman_share_links` | `simulation` | `4` |
| `0x0042a970` | `shell_open_file_dialog_copy_selected_path_and_restore_cwd` | `shell` | `3` |
| `0x004333f0` | `shell_setup_build_file_list_records_from_current_root_and_pattern` | `shell` | `3` |
| `0x004336a0` | `shell_setup_file_list_construct_and_scan_dataset` | `shell` | `3` |
| `0x00434050` | `shell_has_auxiliary_preview_owner` | `shell` | `4` |
| `0x00437220` | `world_build_chairman_profile_slot_records` | `map` | `4` |
| `0x004377a0` | `world_seed_default_chairman_profile_slots` | `map` | `4` |
| `0x00437b20` | `simulation_run_chunked_fast_forward_burst` | `simulation` | `3` |
| `0x004384d0` | `world_run_post_load_generation_pipeline` | `map` | `4` |
| `0x00438890` | `shell_active_mode_run_profile_startup_and_load_dispatch` | `map` | `4` |
| `0x00443a50` | `world_entry_transition_and_runtime_bringup` | `map` | `4` |
| `0x00445ac0` | `shell_map_file_entry_coordinator` | `map` | `4` |
| `0x00446d40` | `world_load_saved_runtime_state_bundle` | `map` | `4` |
| `0x0044fb70` | `world_compute_transport_and_pricing_grid` | `map` | `3` |
| `0x0046a6c0` | `multiplayer_session_event_publish_registration_field` | `shell` | `3` |
| `0x00477820` | `profile_collection_count_active_chairman_records` | `map` | `4` |
| `0x00477860` | `profile_collection_get_nth_active_chairman_record` | `map` | `4` |
| `0x0047d440` | `world_conditionally_seed_named_starting_railroad_companies` | `map` | `4` |
| `0x00482d10` | `runtime_query_cached_local_exe_version_float` | `simulation` | `3` |
| `0x00482d80` | `runtime_query_cached_local_exe_version_string` | `simulation` | `3` |
| `0x00482e00` | `runtime_query_hundredths_scaled_build_version` | `simulation` | `3` |
| `0x00482ec0` | `shell_transition_mode` | `bootstrap` | `4` |
| `0x004839b0` | `shell_setup_query_file_list_uses_map_extension_pattern` | `shell` | `3` |
| `0x004839e0` | `shell_setup_query_file_list_root_dir_name` | `shell` | `3` |
| `0x004840e0` | `bootstrap_init_shell_window_services` | `bootstrap` | `4` |
| `0x00484910` | `shell_save_graphics_config` | `shell` | `4` |
| `0x004882e0` | `world_region_border_overlay_rebuild` | `map` | `4` |
| `0x004a01a0` | `route_entry_collection_try_build_path_between_optional_endpoint_entries` | `map` | `3` |
| `0x004a5280` | `aux_route_entry_tracker_query_route_entry_pair_metric_via_weighted_recursive_search` | `map` | `3` |
| `0x004a5900` | `aux_route_entry_tracker_query_route_entry_pair_metric_via_recursive_neighbor_walk` | `map` | `3` |
| `0x004a65b0` | `aux_route_entry_tracker_dispatch_route_entry_pair_metric_query` | `map` | `3` |
| `0x004a6630` | `aux_route_entry_tracker_query_best_route_entry_pair_metric_with_endpoint_fallbacks` | `map` | `3` |
| `0x004b8dc0` | `shell_campaign_window_destroy` | `shell` | `4` |
| `0x004b8e60` | `shell_campaign_window_construct` | `shell` | `4` |
| `0x004b9a20` | `shell_building_detail_refresh_flagged_service_capability_rows` | `shell` | `3` |
| `0x004ba3d0` | `shell_building_detail_refresh_subject_cargo_and_service_rows` | `shell` | `3` |
| `0x004bad20` | `shell_building_detail_refresh_subject_pair_value_rows` | `shell` | `3` |
| `0x004c2ca0` | `shell_company_detail_window_refresh_controls` | `shell` | `4` |
| `0x004c3890` | `shell_company_detail_issue_bond_offer_flow` | `shell` | `4` |
| `0x004c3f30` | `shell_company_detail_issue_stock_offer_flow` | `shell` | `4` |
| `0x004c46d0` | `shell_company_detail_buyback_stock_flow` | `shell` | `4` |
| `0x004c4c70` | `shell_company_detail_setup_dividend_rate_adjust_controls` | `shell` | `4` |
| `0x004c4e30` | `shell_company_detail_render_change_dividend_rate_dialog` | `shell` | `4` |
| `0x004c5140` | `shell_company_detail_handle_change_dividend_rate_dialog_message` | `shell` | `4` |
| `0x004c5360` | `shell_company_detail_change_dividend_rate_flow` | `shell` | `4` |
| `0x004c56a0` | `shell_company_detail_window_handle_message` | `shell` | `4` |
| `0x004c5a0e` | `shell_company_detail_resign_chairmanship_flow` | `shell` | `4` |
| `0x004c5b99` | `shell_company_detail_bankruptcy_flow` | `shell` | `4` |
| `0x004c5fc9` | `shell_company_detail_buy_territory_access_rights_flow` | `shell` | `4` |
| `0x004c7bc0` | `shell_credits_window_destroy` | `shell` | `4` |
| `0x004c7fc0` | `shell_credits_window_construct` | `shell` | `4` |
| `0x004c98a0` | `shell_open_custom_modal_dialog_with_callbacks` | `shell` | `4` |
| `0x004c9da0` | `map_editor_chairman_slot_panel_format_slot_card` | `map` | `4` |
| `0x004cc2d0` | `map_editor_chairman_slot_panel_construct` | `map` | `4` |
| `0x004dd010` | `shell_file_request_dialog_collect_target_path` | `shell` | `4` |
| `0x004ddbd0` | `shell_detail_panel_transition_manager` | `shell` | `3` |
| `0x004dfbe0` | `shell_game_window_construct` | `shell` | `4` |
| `0x004dfd70` | `shell_game_window_destroy` | `shell` | `4` |
| `0x004e0ba0` | `game_uppermost_window_handle_message` | `shell` | `4` |
| `0x004ea620` | `shell_load_screen_window_construct` | `shell` | `4` |
| `0x004ea730` | `shell_load_screen_window_destroy` | `shell` | `4` |
| `0x004eb0b0` | `shell_open_grayscale_map_tga_picker_and_stage_selection` | `shell` | `3` |
| `0x004ec640` | `shell_company_detail_attempt_merger_flow` | `shell` | `4` |
| `0x004ee0e0` | `multiplayer_open_staged_text_entry_dialog` | `shell` | `3` |
| `0x004efe80` | `multiplayer_window_init_globals` | `shell` | `4` |
| `0x004fe120` | `shell_has_settings_window` | `shell` | `4` |
| `0x00501e50` | `shell_open_settings_window` | `shell` | `4` |
| `0x00501f20` | `shell_query_registry_open_command_for_http_or_rtf_target` | `shell` | `3` |
| `0x00502030` | `shell_setup_window_draw_table_driven_payload_category_row` | `shell` | `3` |
| `0x00502160` | `shell_setup_window_set_first_persisted_selector_flag_or_index_and_save_config` | `shell` | `3` |
| `0x005021c0` | `shell_setup_window_set_second_persisted_selector_flag_or_index_and_save_config` | `shell` | `3` |
| `0x00502220` | `shell_setup_window_publish_selected_profile_labels_and_preview_surface` | `shell` | `3` |
| `0x00502550` | `shell_setup_window_refresh_selection_lists_and_summary_fields` | `shell` | `3` |
| `0x005027b0` | `shell_setup_window_refresh_file_backed_selection_list_panel` | `shell` | `3` |
| `0x00502910` | `shell_setup_window_refresh_mode_dependent_lists` | `shell` | `3` |
| `0x00502c00` | `shell_setup_window_select_launch_mode_and_apply_shell_state` | `shell` | `4` |
| `0x005033d0` | `shell_setup_window_handle_message` | `shell` | `3` |
| `0x00504010` | `shell_setup_window_construct` | `shell` | `4` |
| `0x0050ccc0` | `shell_company_detail_attempt_chairmanship_takeover_flow` | `shell` | `4` |
| `0x005174e0` | `shell_video_window_construct` | `shell` | `4` |
| `0x00517570` | `shell_video_window_destroy` | `shell` | `4` |
| `0x00517d40` | `indexed_collection_entry_id_is_live` | `simulation` | `4` |
| `0x00518140` | `indexed_collection_resolve_live_entry_by_id` | `simulation` | `4` |
| `0x00518380` | `indexed_collection_get_nth_live_entry_id` | `simulation` | `4` |
| `0x0051c920` | `localization_lookup_display_label_by_stem_or_fallback` | `shell` | `4` |
| `0x0051eea0` | `shell_save_display_runtime_config` | `shell` | `4` |
| `0x0053f830` | `shell_window_find_registered_child_control_by_id` | `shell` | `3` |
| `0x0053f9c0` | `shell_window_register_child_control_sorted_by_priority_and_optional_tag` | `shell` | `3` |
| `0x0053fa50` | `shell_window_bind_resource_and_initialize_child_control_links` | `shell` | `4` |
| `0x005411c0` | `shell_query_tga_header_is_supported_truecolor_image` | `shell` | `3` |
| `0x00552560` | `shell_queue_world_anchor_marker` | `shell` | `3` |
| `0x00558130` | `shell_child_control_set_owner_resolve_caption_and_refresh` | `shell` | `3` |
| `0x00559520` | `surface_init_rgba_pixel_buffer` | `support` | `3` |
## Edges
- `0x00402cb0` `city_connection_try_build_route_with_optional_direct_site_placement`
-> `0x00404640` `city_connection_bonus_try_compact_route_builder_from_region_entry`
-> `0x004046a0` `city_connection_bonus_build_peer_route_candidate`
-> `0x00404c60` `city_connection_try_build_route_between_region_entry_pair`
-> `0x00404ce0` `simulation_try_select_and_publish_company_start_or_city_connection_news`
-> `0x0040ef10` `placed_structure_finalize_creation_or_rebuild_local_runtime_state`
-> `0x004134d0` `placed_structure_collection_allocate_and_construct_entry`
-> `0x00417840` `placed_structure_project_candidate_grid_extent_offset_by_rotation`
-> `0x004197e0` `placed_structure_validate_projected_candidate_placement`
-> `0x00482e00` `runtime_query_hundredths_scaled_build_version`
-> `0x004a01a0` `route_entry_collection_try_build_path_between_optional_endpoint_entries`
- `0x00404640` `city_connection_bonus_try_compact_route_builder_from_region_entry`
-> `0x00402cb0` `city_connection_try_build_route_with_optional_direct_site_placement`
-> `0x004046a0` `city_connection_bonus_build_peer_route_candidate`
- `0x004046a0` `city_connection_bonus_build_peer_route_candidate`
-> `0x00402cb0` `city_connection_try_build_route_with_optional_direct_site_placement`
-> `0x00404640` `city_connection_bonus_try_compact_route_builder_from_region_entry`
- `0x00404c60` `city_connection_try_build_route_between_region_entry_pair`
-> `0x00402cb0` `city_connection_try_build_route_with_optional_direct_site_placement`
-> `0x00404ce0` `simulation_try_select_and_publish_company_start_or_city_connection_news`
- `0x00404ce0` `simulation_try_select_and_publish_company_start_or_city_connection_news`
-> `0x00404c60` `city_connection_try_build_route_between_region_entry_pair`
- `0x0040e360` `placed_structure_refresh_linked_site_anchor_position_triplet_for_local_runtime`
-> `0x0040ee10` `placed_structure_refresh_local_runtime_position_triplet_and_linked_anchor_followon`
- `0x0040e450` `placed_structure_refresh_cloned_local_runtime_record_from_current_candidate_stem`
-> `0x004133b0` `placed_structure_collection_refresh_local_runtime_records_and_position_scalars`
-> `0x00414470` `placed_structure_cache_projected_rect_profile_slot_id`
-> `0x00416e20` `indexed_collection_resolve_live_entry_id_by_stem_string`
-> `0x00418a60` `placed_structure_clone_template_local_runtime_record_for_subject_and_refresh_component_bounds`
- `0x0040ee10` `placed_structure_refresh_local_runtime_position_triplet_and_linked_anchor_followon`
-> `0x0040e360` `placed_structure_refresh_linked_site_anchor_position_triplet_for_local_runtime`
-> `0x004133b0` `placed_structure_collection_refresh_local_runtime_records_and_position_scalars`
-> `0x00415f20` `placed_structure_recursive_collect_connected_component_tile_bounds`
-> `0x00434050` `shell_has_auxiliary_preview_owner`
- `0x00412ca0` `world_region_pick_commercial_profile_label_by_region_rank`
-> `0x004235c0` `world_region_balance_structure_demand_and_place_candidates`
- `0x004133b0` `placed_structure_collection_refresh_local_runtime_records_and_position_scalars`
-> `0x0040e450` `placed_structure_refresh_cloned_local_runtime_record_from_current_candidate_stem`
-> `0x0040ee10` `placed_structure_refresh_local_runtime_position_triplet_and_linked_anchor_followon`
-> `0x00413f50` `placed_structure_local_runtime_site_id_queue_pop_next`
-> `0x00414480` `placed_structure_local_runtime_site_id_queue_count`
- `0x004134d0` `placed_structure_collection_allocate_and_construct_entry`
-> `0x00422ee0` `world_region_try_place_candidate_structure`
- `0x00413f50` `placed_structure_local_runtime_site_id_queue_pop_next`
-> `0x004133b0` `placed_structure_collection_refresh_local_runtime_records_and_position_scalars`
- `0x00414470` `placed_structure_cache_projected_rect_profile_slot_id`
-> `0x0040e450` `placed_structure_refresh_cloned_local_runtime_record_from_current_candidate_stem`
-> `0x00418a60` `placed_structure_clone_template_local_runtime_record_for_subject_and_refresh_component_bounds`
- `0x00414480` `placed_structure_local_runtime_site_id_queue_count`
-> `0x004133b0` `placed_structure_collection_refresh_local_runtime_records_and_position_scalars`
- `0x00415f20` `placed_structure_recursive_collect_connected_component_tile_bounds`
-> `0x0040ee10` `placed_structure_refresh_local_runtime_position_triplet_and_linked_anchor_followon`
- `0x00416e20` `indexed_collection_resolve_live_entry_id_by_stem_string`
-> `0x0040e450` `placed_structure_refresh_cloned_local_runtime_record_from_current_candidate_stem`
-> `0x00518140` `indexed_collection_resolve_live_entry_by_id`
-> `0x00518380` `indexed_collection_get_nth_live_entry_id`
- `0x00417840` `placed_structure_project_candidate_grid_extent_offset_by_rotation`
-> `0x00402cb0` `city_connection_try_build_route_with_optional_direct_site_placement`
-> `0x004197e0` `placed_structure_validate_projected_candidate_placement`
- `0x00418a60` `placed_structure_clone_template_local_runtime_record_for_subject_and_refresh_component_bounds`
-> `0x0040e450` `placed_structure_refresh_cloned_local_runtime_record_from_current_candidate_stem`
- `0x004197e0` `placed_structure_validate_projected_candidate_placement`
-> `0x00402cb0` `city_connection_try_build_route_with_optional_direct_site_placement`
-> `0x00417840` `placed_structure_project_candidate_grid_extent_offset_by_rotation`
- `0x0041e2b0` `structure_candidate_rebuild_local_service_metrics`
-> `0x0041e220` `structure_candidate_is_enabled_for_current_year`
-> `0x0041ea50` `world_setup_building_collection_phase`
- `0x0041ea50` `world_setup_building_collection_phase`
-> `0x0041e2b0` `structure_candidate_rebuild_local_service_metrics`
-> `0x00518140` `indexed_collection_resolve_live_entry_by_id`
-> `0x00518380` `indexed_collection_get_nth_live_entry_id`
- `0x0041f9b0` `world_region_count_structure_profiles_before_year_for_category`
-> `0x004235c0` `world_region_balance_structure_demand_and_place_candidates`
- `0x00421b60` `world_region_collection_seed_default_regions`
-> `0x004384d0` `world_run_post_load_generation_pipeline`
- `0x00421c20` `world_region_collection_run_building_population_pass`
-> `0x004235c0` `world_region_balance_structure_demand_and_place_candidates`
- `0x00422900` `world_region_accumulate_structure_category_totals`
-> `0x004235c0` `world_region_balance_structure_demand_and_place_candidates`
- `0x00422be0` `world_region_count_placed_structures_for_category`
-> `0x004235c0` `world_region_balance_structure_demand_and_place_candidates`
- `0x00422ee0` `world_region_try_place_candidate_structure`
-> `0x004134d0` `placed_structure_collection_allocate_and_construct_entry`
-> `0x004235c0` `world_region_balance_structure_demand_and_place_candidates`
- `0x004235c0` `world_region_balance_structure_demand_and_place_candidates`
-> `0x00412ca0` `world_region_pick_commercial_profile_label_by_region_rank`
-> `0x0041f9b0` `world_region_count_structure_profiles_before_year_for_category`
-> `0x0041fac0` `world_region_read_structure_profile_label_and_weight_by_index`
-> `0x00422900` `world_region_accumulate_structure_category_totals`
-> `0x00422be0` `world_region_count_placed_structures_for_category`
-> `0x00422ee0` `world_region_try_place_candidate_structure`
- `0x00426260` `company_compute_board_approved_dividend_rate_ceiling`
-> `0x004c5140` `shell_company_detail_handle_change_dividend_rate_dialog_message`
- `0x004333f0` `shell_setup_build_file_list_records_from_current_root_and_pattern`
-> `0x004336a0` `shell_setup_file_list_construct_and_scan_dataset`
-> `0x004839b0` `shell_setup_query_file_list_uses_map_extension_pattern`
-> `0x004839e0` `shell_setup_query_file_list_root_dir_name`
-> `0x0051c920` `localization_lookup_display_label_by_stem_or_fallback`
- `0x004336a0` `shell_setup_file_list_construct_and_scan_dataset`
-> `0x004333f0` `shell_setup_build_file_list_records_from_current_root_and_pattern`
-> `0x00502c00` `shell_setup_window_select_launch_mode_and_apply_shell_state`
- `0x00437220` `world_build_chairman_profile_slot_records`
-> `0x004384d0` `world_run_post_load_generation_pipeline`
- `0x004377a0` `world_seed_default_chairman_profile_slots`
-> `0x004cc2d0` `map_editor_chairman_slot_panel_construct`
- `0x00437b20` `simulation_run_chunked_fast_forward_burst`
-> `0x004384d0` `world_run_post_load_generation_pipeline`
- `0x004384d0` `world_run_post_load_generation_pipeline`
-> `0x004133b0` `placed_structure_collection_refresh_local_runtime_records_and_position_scalars`
-> `0x0041ea50` `world_setup_building_collection_phase`
-> `0x00421b60` `world_region_collection_seed_default_regions`
-> `0x00421c20` `world_region_collection_run_building_population_pass`
-> `0x00437220` `world_build_chairman_profile_slot_records`
-> `0x004377a0` `world_seed_default_chairman_profile_slots`
-> `0x00437b20` `simulation_run_chunked_fast_forward_burst`
-> `0x0044fb70` `world_compute_transport_and_pricing_grid`
-> `0x0047d440` `world_conditionally_seed_named_starting_railroad_companies`
-> `0x004882e0` `world_region_border_overlay_rebuild`
- `0x00438890` `shell_active_mode_run_profile_startup_and_load_dispatch`
-> `0x004384d0` `world_run_post_load_generation_pipeline`
-> `0x00445ac0` `shell_map_file_entry_coordinator`
-> `0x005033d0` `shell_setup_window_handle_message`
- `0x00443a50` `world_entry_transition_and_runtime_bringup`
-> `0x00438890` `shell_active_mode_run_profile_startup_and_load_dispatch`
-> `0x00482ec0` `shell_transition_mode`
- `0x00445ac0` `shell_map_file_entry_coordinator`
-> `0x00443a50` `world_entry_transition_and_runtime_bringup`
-> `0x00446d40` `world_load_saved_runtime_state_bundle`
-> `0x004dd010` `shell_file_request_dialog_collect_target_path`
- `0x0046a6c0` `multiplayer_session_event_publish_registration_field`
-> `0x00482d80` `runtime_query_cached_local_exe_version_string`
- `0x00477820` `profile_collection_count_active_chairman_records`
-> `0x0047d440` `world_conditionally_seed_named_starting_railroad_companies`
- `0x00477860` `profile_collection_get_nth_active_chairman_record`
-> `0x0047d440` `world_conditionally_seed_named_starting_railroad_companies`
- `0x0047d440` `world_conditionally_seed_named_starting_railroad_companies`
-> `0x004377a0` `world_seed_default_chairman_profile_slots`
-> `0x00477820` `profile_collection_count_active_chairman_records`
-> `0x00477860` `profile_collection_get_nth_active_chairman_record`
- `0x00482d10` `runtime_query_cached_local_exe_version_float`
-> `0x00482d80` `runtime_query_cached_local_exe_version_string`
-> `0x00482e00` `runtime_query_hundredths_scaled_build_version`
- `0x00482d80` `runtime_query_cached_local_exe_version_string`
-> `0x0046a6c0` `multiplayer_session_event_publish_registration_field`
-> `0x00482d10` `runtime_query_cached_local_exe_version_float`
- `0x00482e00` `runtime_query_hundredths_scaled_build_version`
-> `0x00402cb0` `city_connection_try_build_route_with_optional_direct_site_placement`
-> `0x00482d10` `runtime_query_cached_local_exe_version_float`
-> `0x004a65b0` `aux_route_entry_tracker_dispatch_route_entry_pair_metric_query`
- `0x00482ec0` `shell_transition_mode`
-> `0x00438890` `shell_active_mode_run_profile_startup_and_load_dispatch`
-> `0x00443a50` `world_entry_transition_and_runtime_bringup`
-> `0x004840e0` `bootstrap_init_shell_window_services`
-> `0x004b8dc0` `shell_campaign_window_destroy`
-> `0x004b8e60` `shell_campaign_window_construct`
-> `0x004c7bc0` `shell_credits_window_destroy`
-> `0x004c7fc0` `shell_credits_window_construct`
-> `0x004dfbe0` `shell_game_window_construct`
-> `0x004dfd70` `shell_game_window_destroy`
-> `0x004ea620` `shell_load_screen_window_construct`
-> `0x004ea730` `shell_load_screen_window_destroy`
-> `0x004efe80` `multiplayer_window_init_globals`
-> `0x00504010` `shell_setup_window_construct`
-> `0x005174e0` `shell_video_window_construct`
-> `0x00517570` `shell_video_window_destroy`
- `0x004839b0` `shell_setup_query_file_list_uses_map_extension_pattern`
-> `0x004333f0` `shell_setup_build_file_list_records_from_current_root_and_pattern`
- `0x004839e0` `shell_setup_query_file_list_root_dir_name`
-> `0x004333f0` `shell_setup_build_file_list_records_from_current_root_and_pattern`
- `0x004840e0` `bootstrap_init_shell_window_services`
-> `0x00482ec0` `shell_transition_mode`
- `0x00484910` `shell_save_graphics_config`
-> `0x0051eea0` `shell_save_display_runtime_config`
- `0x004882e0` `world_region_border_overlay_rebuild`
-> `0x004384d0` `world_run_post_load_generation_pipeline`
- `0x004a01a0` `route_entry_collection_try_build_path_between_optional_endpoint_entries`
-> `0x00402cb0` `city_connection_try_build_route_with_optional_direct_site_placement`
-> `0x00518140` `indexed_collection_resolve_live_entry_by_id`
- `0x004a5280` `aux_route_entry_tracker_query_route_entry_pair_metric_via_weighted_recursive_search`
-> `0x004a65b0` `aux_route_entry_tracker_dispatch_route_entry_pair_metric_query`
- `0x004a5900` `aux_route_entry_tracker_query_route_entry_pair_metric_via_recursive_neighbor_walk`
-> `0x004a5280` `aux_route_entry_tracker_query_route_entry_pair_metric_via_weighted_recursive_search`
-> `0x004a65b0` `aux_route_entry_tracker_dispatch_route_entry_pair_metric_query`
- `0x004a65b0` `aux_route_entry_tracker_dispatch_route_entry_pair_metric_query`
-> `0x00482e00` `runtime_query_hundredths_scaled_build_version`
-> `0x004a5280` `aux_route_entry_tracker_query_route_entry_pair_metric_via_weighted_recursive_search`
-> `0x004a5900` `aux_route_entry_tracker_query_route_entry_pair_metric_via_recursive_neighbor_walk`
-> `0x004a6630` `aux_route_entry_tracker_query_best_route_entry_pair_metric_with_endpoint_fallbacks`
- `0x004a6630` `aux_route_entry_tracker_query_best_route_entry_pair_metric_with_endpoint_fallbacks`
-> `0x004a65b0` `aux_route_entry_tracker_dispatch_route_entry_pair_metric_query`
- `0x004b8dc0` `shell_campaign_window_destroy`
-> `0x004b8e60` `shell_campaign_window_construct`
- `0x004ba3d0` `shell_building_detail_refresh_subject_cargo_and_service_rows`
-> `0x004b9a20` `shell_building_detail_refresh_flagged_service_capability_rows`
-> `0x004bad20` `shell_building_detail_refresh_subject_pair_value_rows`
-> `0x00517d40` `indexed_collection_entry_id_is_live`
-> `0x00518140` `indexed_collection_resolve_live_entry_by_id`
-> `0x0051c920` `localization_lookup_display_label_by_stem_or_fallback`
- `0x004bad20` `shell_building_detail_refresh_subject_pair_value_rows`
-> `0x004ba3d0` `shell_building_detail_refresh_subject_cargo_and_service_rows`
- `0x004c2ca0` `shell_company_detail_window_refresh_controls`
-> `0x004c56a0` `shell_company_detail_window_handle_message`
- `0x004c3890` `shell_company_detail_issue_bond_offer_flow`
-> `0x004c56a0` `shell_company_detail_window_handle_message`
- `0x004c3f30` `shell_company_detail_issue_stock_offer_flow`
-> `0x004c56a0` `shell_company_detail_window_handle_message`
- `0x004c46d0` `shell_company_detail_buyback_stock_flow`
-> `0x004c56a0` `shell_company_detail_window_handle_message`
- `0x004c4c70` `shell_company_detail_setup_dividend_rate_adjust_controls`
-> `0x004c4e30` `shell_company_detail_render_change_dividend_rate_dialog`
-> `0x004c5140` `shell_company_detail_handle_change_dividend_rate_dialog_message`
-> `0x004c5360` `shell_company_detail_change_dividend_rate_flow`
-> `0x0053f9c0` `shell_window_register_child_control_sorted_by_priority_and_optional_tag`
- `0x004c4e30` `shell_company_detail_render_change_dividend_rate_dialog`
-> `0x004c5360` `shell_company_detail_change_dividend_rate_flow`
-> `0x004c98a0` `shell_open_custom_modal_dialog_with_callbacks`
- `0x004c5140` `shell_company_detail_handle_change_dividend_rate_dialog_message`
-> `0x00426260` `company_compute_board_approved_dividend_rate_ceiling`
-> `0x004c5360` `shell_company_detail_change_dividend_rate_flow`
-> `0x004c98a0` `shell_open_custom_modal_dialog_with_callbacks`
- `0x004c5360` `shell_company_detail_change_dividend_rate_flow`
-> `0x004c4c70` `shell_company_detail_setup_dividend_rate_adjust_controls`
-> `0x004c4e30` `shell_company_detail_render_change_dividend_rate_dialog`
-> `0x004c5140` `shell_company_detail_handle_change_dividend_rate_dialog_message`
-> `0x004c56a0` `shell_company_detail_window_handle_message`
- `0x004c56a0` `shell_company_detail_window_handle_message`
-> `0x00423d70` `company_repay_bond_slot_and_compact_debt_table`
-> `0x00426d60` `company_deactivate_and_clear_chairman_share_links`
-> `0x004c2ca0` `shell_company_detail_window_refresh_controls`
-> `0x004c3890` `shell_company_detail_issue_bond_offer_flow`
-> `0x004c3f30` `shell_company_detail_issue_stock_offer_flow`
-> `0x004c46d0` `shell_company_detail_buyback_stock_flow`
-> `0x004c5360` `shell_company_detail_change_dividend_rate_flow`
-> `0x004c5a0e` `shell_company_detail_resign_chairmanship_flow`
-> `0x004c5b99` `shell_company_detail_bankruptcy_flow`
-> `0x004c5fc9` `shell_company_detail_buy_territory_access_rights_flow`
-> `0x004ddbd0` `shell_detail_panel_transition_manager`
-> `0x004ec640` `shell_company_detail_attempt_merger_flow`
-> `0x0050ccc0` `shell_company_detail_attempt_chairmanship_takeover_flow`
- `0x004c5a0e` `shell_company_detail_resign_chairmanship_flow`
-> `0x004c56a0` `shell_company_detail_window_handle_message`
- `0x004c5b99` `shell_company_detail_bankruptcy_flow`
-> `0x004c56a0` `shell_company_detail_window_handle_message`
- `0x004c5fc9` `shell_company_detail_buy_territory_access_rights_flow`
-> `0x004c56a0` `shell_company_detail_window_handle_message`
- `0x004c7bc0` `shell_credits_window_destroy`
-> `0x004c7fc0` `shell_credits_window_construct`
- `0x004c7fc0` `shell_credits_window_construct`
-> `0x0053fa50` `shell_window_bind_resource_and_initialize_child_control_links`
- `0x004c98a0` `shell_open_custom_modal_dialog_with_callbacks`
-> `0x004ee0e0` `multiplayer_open_staged_text_entry_dialog`
- `0x004cc2d0` `map_editor_chairman_slot_panel_construct`
-> `0x004c9da0` `map_editor_chairman_slot_panel_format_slot_card`
- `0x004dd010` `shell_file_request_dialog_collect_target_path`
-> `0x004839b0` `shell_setup_query_file_list_uses_map_extension_pattern`
- `0x004dfbe0` `shell_game_window_construct`
-> `0x004dfd70` `shell_game_window_destroy`
-> `0x0053fa50` `shell_window_bind_resource_and_initialize_child_control_links`
- `0x004e0ba0` `game_uppermost_window_handle_message`
-> `0x0053f830` `shell_window_find_registered_child_control_by_id`
- `0x004ea620` `shell_load_screen_window_construct`
-> `0x004ea730` `shell_load_screen_window_destroy`
-> `0x0053fa50` `shell_window_bind_resource_and_initialize_child_control_links`
- `0x004ea730` `shell_load_screen_window_destroy`
-> `0x004ea620` `shell_load_screen_window_construct`
- `0x004ec640` `shell_company_detail_attempt_merger_flow`
-> `0x004c56a0` `shell_company_detail_window_handle_message`
- `0x004ee0e0` `multiplayer_open_staged_text_entry_dialog`
-> `0x00484910` `shell_save_graphics_config`
-> `0x004c98a0` `shell_open_custom_modal_dialog_with_callbacks`
- `0x00502030` `shell_setup_window_draw_table_driven_payload_category_row`
-> `0x00504010` `shell_setup_window_construct`
-> `0x00552560` `shell_queue_world_anchor_marker`
- `0x00502160` `shell_setup_window_set_first_persisted_selector_flag_or_index_and_save_config`
-> `0x00484910` `shell_save_graphics_config`
-> `0x005021c0` `shell_setup_window_set_second_persisted_selector_flag_or_index_and_save_config`
-> `0x00504010` `shell_setup_window_construct`
- `0x005021c0` `shell_setup_window_set_second_persisted_selector_flag_or_index_and_save_config`
-> `0x00484910` `shell_save_graphics_config`
-> `0x00502160` `shell_setup_window_set_first_persisted_selector_flag_or_index_and_save_config`
-> `0x00504010` `shell_setup_window_construct`
- `0x00502220` `shell_setup_window_publish_selected_profile_labels_and_preview_surface`
-> `0x00502c00` `shell_setup_window_select_launch_mode_and_apply_shell_state`
-> `0x0053f830` `shell_window_find_registered_child_control_by_id`
-> `0x00559520` `surface_init_rgba_pixel_buffer`
- `0x005027b0` `shell_setup_window_refresh_file_backed_selection_list_panel`
-> `0x004336a0` `shell_setup_file_list_construct_and_scan_dataset`
-> `0x00502220` `shell_setup_window_publish_selected_profile_labels_and_preview_surface`
-> `0x00502c00` `shell_setup_window_select_launch_mode_and_apply_shell_state`
- `0x00502910` `shell_setup_window_refresh_mode_dependent_lists`
-> `0x005027b0` `shell_setup_window_refresh_file_backed_selection_list_panel`
-> `0x00502c00` `shell_setup_window_select_launch_mode_and_apply_shell_state`
- `0x00502c00` `shell_setup_window_select_launch_mode_and_apply_shell_state`
-> `0x004336a0` `shell_setup_file_list_construct_and_scan_dataset`
-> `0x00438890` `shell_active_mode_run_profile_startup_and_load_dispatch`
-> `0x00482d80` `runtime_query_cached_local_exe_version_string`
-> `0x004eb0b0` `shell_open_grayscale_map_tga_picker_and_stage_selection`
-> `0x005027b0` `shell_setup_window_refresh_file_backed_selection_list_panel`
-> `0x00502910` `shell_setup_window_refresh_mode_dependent_lists`
-> `0x005033d0` `shell_setup_window_handle_message`
- `0x005033d0` `shell_setup_window_handle_message`
-> `0x0042a970` `shell_open_file_dialog_copy_selected_path_and_restore_cwd`
-> `0x00484910` `shell_save_graphics_config`
-> `0x004eb0b0` `shell_open_grayscale_map_tga_picker_and_stage_selection`
-> `0x004fe120` `shell_has_settings_window`
-> `0x00501e50` `shell_open_settings_window`
-> `0x00501f20` `shell_query_registry_open_command_for_http_or_rtf_target`
-> `0x00502220` `shell_setup_window_publish_selected_profile_labels_and_preview_surface`
-> `0x00502550` `shell_setup_window_refresh_selection_lists_and_summary_fields`
-> `0x00502910` `shell_setup_window_refresh_mode_dependent_lists`
-> `0x00502c00` `shell_setup_window_select_launch_mode_and_apply_shell_state`
-> `0x00504010` `shell_setup_window_construct`
-> `0x005411c0` `shell_query_tga_header_is_supported_truecolor_image`
- `0x00504010` `shell_setup_window_construct`
-> `0x00502030` `shell_setup_window_draw_table_driven_payload_category_row`
-> `0x00502160` `shell_setup_window_set_first_persisted_selector_flag_or_index_and_save_config`
-> `0x005021c0` `shell_setup_window_set_second_persisted_selector_flag_or_index_and_save_config`
-> `0x00502550` `shell_setup_window_refresh_selection_lists_and_summary_fields`
-> `0x00502910` `shell_setup_window_refresh_mode_dependent_lists`
-> `0x00502c00` `shell_setup_window_select_launch_mode_and_apply_shell_state`
-> `0x0053f830` `shell_window_find_registered_child_control_by_id`
-> `0x0053f9c0` `shell_window_register_child_control_sorted_by_priority_and_optional_tag`
-> `0x0053fa50` `shell_window_bind_resource_and_initialize_child_control_links`
- `0x0050ccc0` `shell_company_detail_attempt_chairmanship_takeover_flow`
-> `0x004c56a0` `shell_company_detail_window_handle_message`
- `0x005174e0` `shell_video_window_construct`
-> `0x0053fa50` `shell_window_bind_resource_and_initialize_child_control_links`
- `0x00517570` `shell_video_window_destroy`
-> `0x00484910` `shell_save_graphics_config`
-> `0x005174e0` `shell_video_window_construct`
- `0x00517d40` `indexed_collection_entry_id_is_live`
-> `0x004ba3d0` `shell_building_detail_refresh_subject_cargo_and_service_rows`
-> `0x00518140` `indexed_collection_resolve_live_entry_by_id`
- `0x0051c920` `localization_lookup_display_label_by_stem_or_fallback`
-> `0x004ba3d0` `shell_building_detail_refresh_subject_cargo_and_service_rows`
- `0x0053f830` `shell_window_find_registered_child_control_by_id`
-> `0x004e0ba0` `game_uppermost_window_handle_message`
-> `0x00502220` `shell_setup_window_publish_selected_profile_labels_and_preview_surface`
-> `0x00504010` `shell_setup_window_construct`
- `0x0053f9c0` `shell_window_register_child_control_sorted_by_priority_and_optional_tag`
-> `0x004c4c70` `shell_company_detail_setup_dividend_rate_adjust_controls`
-> `0x00558130` `shell_child_control_set_owner_resolve_caption_and_refresh`
- `0x005411c0` `shell_query_tga_header_is_supported_truecolor_image`
-> `0x005033d0` `shell_setup_window_handle_message`
- `0x00558130` `shell_child_control_set_owner_resolve_caption_and_refresh`
-> `0x0053f9c0` `shell_window_register_child_control_sorted_by_priority_and_optional_tag`

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,141 @@
digraph shell_load {
graph [rankdir=LR, labelloc="t", labeljust="l"];
label="Shell Load Startup Subgraph";
node [shape=box, style="rounded,filled", fillcolor="#f8f8f8", color="#555555", fontname="Helvetica"];
edge [color="#666666", fontname="Helvetica"];
subgraph cluster_bootstrap {
label="bootstrap";
color="#cccccc";
"0x00482ec0" [label="0x00482ec0\\nshell_transition_mode [seed]", fillcolor="#ffe9a8"];
"0x004840e0" [label="0x004840e0\\nbootstrap_init_shell_window_services", fillcolor="#f8f8f8"];
"0x00521390" [label="0x00521390\\nbootstrap_destroy_shell_service_bundle", fillcolor="#f8f8f8"];
}
subgraph cluster_map {
label="map";
color="#cccccc";
"0x004133b0" [label="0x004133b0\\nplaced_structure_collection_refresh_local_runtime_records_and_position_scalars", fillcolor="#f8f8f8"];
"0x0041ea50" [label="0x0041ea50\\nworld_setup_building_collection_phase", fillcolor="#f8f8f8"];
"0x00421b60" [label="0x00421b60\\nworld_region_collection_seed_default_regions", fillcolor="#f8f8f8"];
"0x00421c20" [label="0x00421c20\\nworld_region_collection_run_building_population_pass", fillcolor="#f8f8f8"];
"0x00434300" [label="0x00434300\\nworld_runtime_release_global_services", fillcolor="#f8f8f8"];
"0x00437220" [label="0x00437220\\nworld_build_chairman_profile_slot_records", fillcolor="#f8f8f8"];
"0x004377a0" [label="0x004377a0\\nworld_seed_default_chairman_profile_slots", fillcolor="#f8f8f8"];
"0x004384d0" [label="0x004384d0\\nworld_run_post_load_generation_pipeline", fillcolor="#f8f8f8"];
"0x00438890" [label="0x00438890\\nshell_active_mode_run_profile_startup_and_load_dispatch [seed]", fillcolor="#ffe9a8"];
"0x00443a50" [label="0x00443a50\\nworld_entry_transition_and_runtime_bringup", fillcolor="#f8f8f8"];
"0x00445ac0" [label="0x00445ac0\\nshell_map_file_entry_coordinator", fillcolor="#f8f8f8"];
"0x00446d40" [label="0x00446d40\\nworld_load_saved_runtime_state_bundle", fillcolor="#f8f8f8"];
"0x0044fb70" [label="0x0044fb70\\nworld_compute_transport_and_pricing_grid", fillcolor="#f8f8f8"];
"0x0047d440" [label="0x0047d440\\nworld_conditionally_seed_named_starting_railroad_companies", fillcolor="#f8f8f8"];
"0x004882e0" [label="0x004882e0\\nworld_region_border_overlay_rebuild", fillcolor="#f8f8f8"];
}
subgraph cluster_render {
label="render";
color="#cccccc";
"0x0043f640" [label="0x0043f640\\nworld_render_station_candidate_service_map_overlay", fillcolor="#f8f8f8"];
}
subgraph cluster_shell {
label="shell";
color="#cccccc";
"0x0046b780" [label="0x0046b780\\nmultiplayer_preview_dataset_service_launch_state_and_warn_out_of_sync", fillcolor="#f8f8f8"];
"0x00483f70" [label="0x00483f70\\nshell_service_pump_iteration", fillcolor="#f8f8f8"];
"0x00484910" [label="0x00484910\\nshell_save_graphics_config", fillcolor="#f8f8f8"];
"0x004b8dc0" [label="0x004b8dc0\\nshell_campaign_window_destroy", fillcolor="#f8f8f8"];
"0x004b8e60" [label="0x004b8e60\\nshell_campaign_window_construct", fillcolor="#f8f8f8"];
"0x004c7bc0" [label="0x004c7bc0\\nshell_credits_window_destroy", fillcolor="#f8f8f8"];
"0x004c7fc0" [label="0x004c7fc0\\nshell_credits_window_construct", fillcolor="#f8f8f8"];
"0x004dd010" [label="0x004dd010\\nshell_file_request_dialog_collect_target_path", fillcolor="#f8f8f8"];
"0x004dfbe0" [label="0x004dfbe0\\nshell_game_window_construct", fillcolor="#f8f8f8"];
"0x004dfd70" [label="0x004dfd70\\nshell_game_window_destroy", fillcolor="#f8f8f8"];
"0x004dfdf0" [label="0x004dfdf0\\nshell_ensure_game_message_window", fillcolor="#f8f8f8"];
"0x004e3a80" [label="0x004e3a80\\nshell_load_screen_window_handle_message", fillcolor="#f8f8f8"];
"0x004e5300" [label="0x004e5300\\nshell_load_screen_render_player_detail_stock_holdings_panel", fillcolor="#f8f8f8"];
"0x004e5a80" [label="0x004e5a80\\nshell_render_company_overview_panel_header_and_optional_change_affordance", fillcolor="#f8f8f8"];
"0x004e5cf0" [label="0x004e5cf0\\nshell_format_company_governance_and_economy_status_panel", fillcolor="#f8f8f8"];
"0x004ea620" [label="0x004ea620\\nshell_load_screen_window_construct", fillcolor="#f8f8f8"];
"0x004ea720" [label="0x004ea720\\nshell_load_screen_window_is_open", fillcolor="#f8f8f8"];
"0x004ea730" [label="0x004ea730\\nshell_load_screen_window_destroy", fillcolor="#f8f8f8"];
"0x004ee3a0" [label="0x004ee3a0\\nmultiplayer_reset_tool_globals", fillcolor="#f8f8f8"];
"0x004ee950" [label="0x004ee950\\nmultiplayer_load_selected_map_preview_surface", fillcolor="#f8f8f8"];
"0x004efe80" [label="0x004efe80\\nmultiplayer_window_init_globals", fillcolor="#f8f8f8"];
"0x00502720" [label="0x00502720\\npaint_terrain_tool_init_globals", fillcolor="#f8f8f8"];
"0x00504010" [label="0x00504010\\nshell_setup_window_construct", fillcolor="#f8f8f8"];
"0x005174e0" [label="0x005174e0\\nshell_video_window_construct", fillcolor="#f8f8f8"];
"0x00517570" [label="0x00517570\\nshell_video_window_destroy", fillcolor="#f8f8f8"];
"0x005204b0" [label="0x005204b0\\nshell_flush_deferred_work_queues", fillcolor="#f8f8f8"];
}
subgraph cluster_simulation {
label="simulation";
color="#cccccc";
"0x00437b20" [label="0x00437b20\\nsimulation_run_chunked_fast_forward_burst", fillcolor="#f8f8f8"];
}
"0x00421b60" -> "0x004384d0";
"0x00434300" -> "0x00443a50";
"0x00434300" -> "0x00446d40";
"0x00434300" -> "0x00482ec0";
"0x00437220" -> "0x004384d0";
"0x00437b20" -> "0x004384d0";
"0x004384d0" -> "0x004133b0";
"0x004384d0" -> "0x0041ea50";
"0x004384d0" -> "0x00421b60";
"0x004384d0" -> "0x00421c20";
"0x004384d0" -> "0x00437220";
"0x004384d0" -> "0x004377a0";
"0x004384d0" -> "0x00437b20";
"0x004384d0" -> "0x0044fb70";
"0x004384d0" -> "0x0047d440";
"0x004384d0" -> "0x004882e0";
"0x00438890" -> "0x004384d0";
"0x00438890" -> "0x00445ac0";
"0x0043f640" -> "0x0046b780";
"0x00443a50" -> "0x00438890";
"0x00443a50" -> "0x00482ec0";
"0x00445ac0" -> "0x00443a50";
"0x00445ac0" -> "0x00446d40";
"0x00445ac0" -> "0x004dd010";
"0x0046b780" -> "0x00438890";
"0x0046b780" -> "0x00445ac0";
"0x0047d440" -> "0x004377a0";
"0x00482ec0" -> "0x00438890";
"0x00482ec0" -> "0x00443a50";
"0x00482ec0" -> "0x004840e0";
"0x00482ec0" -> "0x004b8dc0";
"0x00482ec0" -> "0x004b8e60";
"0x00482ec0" -> "0x004c7bc0";
"0x00482ec0" -> "0x004c7fc0";
"0x00482ec0" -> "0x004dfbe0";
"0x00482ec0" -> "0x004dfd70";
"0x00482ec0" -> "0x004ea620";
"0x00482ec0" -> "0x004ea730";
"0x00482ec0" -> "0x004efe80";
"0x00482ec0" -> "0x00504010";
"0x00482ec0" -> "0x005174e0";
"0x00482ec0" -> "0x00517570";
"0x004840e0" -> "0x00482ec0";
"0x004840e0" -> "0x00483f70";
"0x004840e0" -> "0x00521390";
"0x004882e0" -> "0x004384d0";
"0x004b8dc0" -> "0x004b8e60";
"0x004c7bc0" -> "0x004c7fc0";
"0x004dfbe0" -> "0x004dfd70";
"0x004dfbe0" -> "0x004dfdf0";
"0x004e3a80" -> "0x004e5300";
"0x004e3a80" -> "0x004e5a80";
"0x004e3a80" -> "0x004e5cf0";
"0x004e5a80" -> "0x004e5cf0";
"0x004ea620" -> "0x004e3a80";
"0x004ea620" -> "0x004e5300";
"0x004ea620" -> "0x004e5a80";
"0x004ea620" -> "0x004e5cf0";
"0x004ea620" -> "0x004ea720";
"0x004ea620" -> "0x004ea730";
"0x004ea720" -> "0x004ea620";
"0x004ea720" -> "0x004ea730";
"0x004ea730" -> "0x004ea620";
"0x004ee3a0" -> "0x00482ec0";
"0x004ee950" -> "0x004efe80";
"0x00502720" -> "0x004ee3a0";
"0x00517570" -> "0x00484910";
"0x00517570" -> "0x005174e0";
"0x005204b0" -> "0x00443a50";
}

View file

@ -0,0 +1,156 @@
# Shell Load Startup Subgraph
- Nodes: `46`
- Edges: `69`
- Seeds: `0x00438890`, `0x00482ec0`
- Graphviz: `shell-load-subgraph.dot`
## Nodes
| Address | Name | Subsystem | Confidence |
| --- | --- | --- | --- |
| `0x004133b0` | `placed_structure_collection_refresh_local_runtime_records_and_position_scalars` | `map` | `2` |
| `0x0041ea50` | `world_setup_building_collection_phase` | `map` | `3` |
| `0x00421b60` | `world_region_collection_seed_default_regions` | `map` | `4` |
| `0x00421c20` | `world_region_collection_run_building_population_pass` | `map` | `4` |
| `0x00434300` | `world_runtime_release_global_services` | `map` | `3` |
| `0x00437220` | `world_build_chairman_profile_slot_records` | `map` | `4` |
| `0x004377a0` | `world_seed_default_chairman_profile_slots` | `map` | `4` |
| `0x00437b20` | `simulation_run_chunked_fast_forward_burst` | `simulation` | `3` |
| `0x004384d0` | `world_run_post_load_generation_pipeline` | `map` | `4` |
| `0x00438890` | `shell_active_mode_run_profile_startup_and_load_dispatch` | `map` | `4` |
| `0x0043f640` | `world_render_station_candidate_service_map_overlay` | `render` | `4` |
| `0x00443a50` | `world_entry_transition_and_runtime_bringup` | `map` | `4` |
| `0x00445ac0` | `shell_map_file_entry_coordinator` | `map` | `4` |
| `0x00446d40` | `world_load_saved_runtime_state_bundle` | `map` | `4` |
| `0x0044fb70` | `world_compute_transport_and_pricing_grid` | `map` | `3` |
| `0x0046b780` | `multiplayer_preview_dataset_service_launch_state_and_warn_out_of_sync` | `shell` | `4` |
| `0x0047d440` | `world_conditionally_seed_named_starting_railroad_companies` | `map` | `4` |
| `0x00482ec0` | `shell_transition_mode` | `bootstrap` | `4` |
| `0x00483f70` | `shell_service_pump_iteration` | `shell` | `4` |
| `0x004840e0` | `bootstrap_init_shell_window_services` | `bootstrap` | `4` |
| `0x00484910` | `shell_save_graphics_config` | `shell` | `4` |
| `0x004882e0` | `world_region_border_overlay_rebuild` | `map` | `4` |
| `0x004b8dc0` | `shell_campaign_window_destroy` | `shell` | `4` |
| `0x004b8e60` | `shell_campaign_window_construct` | `shell` | `4` |
| `0x004c7bc0` | `shell_credits_window_destroy` | `shell` | `4` |
| `0x004c7fc0` | `shell_credits_window_construct` | `shell` | `4` |
| `0x004dd010` | `shell_file_request_dialog_collect_target_path` | `shell` | `4` |
| `0x004dfbe0` | `shell_game_window_construct` | `shell` | `4` |
| `0x004dfd70` | `shell_game_window_destroy` | `shell` | `4` |
| `0x004dfdf0` | `shell_ensure_game_message_window` | `shell` | `4` |
| `0x004e3a80` | `shell_load_screen_window_handle_message` | `shell` | `4` |
| `0x004e5300` | `shell_load_screen_render_player_detail_stock_holdings_panel` | `shell` | `4` |
| `0x004e5a80` | `shell_render_company_overview_panel_header_and_optional_change_affordance` | `shell` | `4` |
| `0x004e5cf0` | `shell_format_company_governance_and_economy_status_panel` | `shell` | `4` |
| `0x004ea620` | `shell_load_screen_window_construct` | `shell` | `4` |
| `0x004ea720` | `shell_load_screen_window_is_open` | `shell` | `4` |
| `0x004ea730` | `shell_load_screen_window_destroy` | `shell` | `4` |
| `0x004ee3a0` | `multiplayer_reset_tool_globals` | `shell` | `3` |
| `0x004ee950` | `multiplayer_load_selected_map_preview_surface` | `shell` | `4` |
| `0x004efe80` | `multiplayer_window_init_globals` | `shell` | `4` |
| `0x00502720` | `paint_terrain_tool_init_globals` | `shell` | `4` |
| `0x00504010` | `shell_setup_window_construct` | `shell` | `4` |
| `0x005174e0` | `shell_video_window_construct` | `shell` | `4` |
| `0x00517570` | `shell_video_window_destroy` | `shell` | `4` |
| `0x005204b0` | `shell_flush_deferred_work_queues` | `shell` | `4` |
| `0x00521390` | `bootstrap_destroy_shell_service_bundle` | `bootstrap` | `4` |
## Edges
- `0x00421b60` `world_region_collection_seed_default_regions`
-> `0x004384d0` `world_run_post_load_generation_pipeline`
- `0x00434300` `world_runtime_release_global_services`
-> `0x00443a50` `world_entry_transition_and_runtime_bringup`
-> `0x00446d40` `world_load_saved_runtime_state_bundle`
-> `0x00482ec0` `shell_transition_mode`
- `0x00437220` `world_build_chairman_profile_slot_records`
-> `0x004384d0` `world_run_post_load_generation_pipeline`
- `0x00437b20` `simulation_run_chunked_fast_forward_burst`
-> `0x004384d0` `world_run_post_load_generation_pipeline`
- `0x004384d0` `world_run_post_load_generation_pipeline`
-> `0x004133b0` `placed_structure_collection_refresh_local_runtime_records_and_position_scalars`
-> `0x0041ea50` `world_setup_building_collection_phase`
-> `0x00421b60` `world_region_collection_seed_default_regions`
-> `0x00421c20` `world_region_collection_run_building_population_pass`
-> `0x00437220` `world_build_chairman_profile_slot_records`
-> `0x004377a0` `world_seed_default_chairman_profile_slots`
-> `0x00437b20` `simulation_run_chunked_fast_forward_burst`
-> `0x0044fb70` `world_compute_transport_and_pricing_grid`
-> `0x0047d440` `world_conditionally_seed_named_starting_railroad_companies`
-> `0x004882e0` `world_region_border_overlay_rebuild`
- `0x00438890` `shell_active_mode_run_profile_startup_and_load_dispatch`
-> `0x004384d0` `world_run_post_load_generation_pipeline`
-> `0x00445ac0` `shell_map_file_entry_coordinator`
- `0x0043f640` `world_render_station_candidate_service_map_overlay`
-> `0x0046b780` `multiplayer_preview_dataset_service_launch_state_and_warn_out_of_sync`
- `0x00443a50` `world_entry_transition_and_runtime_bringup`
-> `0x00438890` `shell_active_mode_run_profile_startup_and_load_dispatch`
-> `0x00482ec0` `shell_transition_mode`
- `0x00445ac0` `shell_map_file_entry_coordinator`
-> `0x00443a50` `world_entry_transition_and_runtime_bringup`
-> `0x00446d40` `world_load_saved_runtime_state_bundle`
-> `0x004dd010` `shell_file_request_dialog_collect_target_path`
- `0x0046b780` `multiplayer_preview_dataset_service_launch_state_and_warn_out_of_sync`
-> `0x00438890` `shell_active_mode_run_profile_startup_and_load_dispatch`
-> `0x00445ac0` `shell_map_file_entry_coordinator`
- `0x0047d440` `world_conditionally_seed_named_starting_railroad_companies`
-> `0x004377a0` `world_seed_default_chairman_profile_slots`
- `0x00482ec0` `shell_transition_mode`
-> `0x00438890` `shell_active_mode_run_profile_startup_and_load_dispatch`
-> `0x00443a50` `world_entry_transition_and_runtime_bringup`
-> `0x004840e0` `bootstrap_init_shell_window_services`
-> `0x004b8dc0` `shell_campaign_window_destroy`
-> `0x004b8e60` `shell_campaign_window_construct`
-> `0x004c7bc0` `shell_credits_window_destroy`
-> `0x004c7fc0` `shell_credits_window_construct`
-> `0x004dfbe0` `shell_game_window_construct`
-> `0x004dfd70` `shell_game_window_destroy`
-> `0x004ea620` `shell_load_screen_window_construct`
-> `0x004ea730` `shell_load_screen_window_destroy`
-> `0x004efe80` `multiplayer_window_init_globals`
-> `0x00504010` `shell_setup_window_construct`
-> `0x005174e0` `shell_video_window_construct`
-> `0x00517570` `shell_video_window_destroy`
- `0x004840e0` `bootstrap_init_shell_window_services`
-> `0x00482ec0` `shell_transition_mode`
-> `0x00483f70` `shell_service_pump_iteration`
-> `0x00521390` `bootstrap_destroy_shell_service_bundle`
- `0x004882e0` `world_region_border_overlay_rebuild`
-> `0x004384d0` `world_run_post_load_generation_pipeline`
- `0x004b8dc0` `shell_campaign_window_destroy`
-> `0x004b8e60` `shell_campaign_window_construct`
- `0x004c7bc0` `shell_credits_window_destroy`
-> `0x004c7fc0` `shell_credits_window_construct`
- `0x004dfbe0` `shell_game_window_construct`
-> `0x004dfd70` `shell_game_window_destroy`
-> `0x004dfdf0` `shell_ensure_game_message_window`
- `0x004e3a80` `shell_load_screen_window_handle_message`
-> `0x004e5300` `shell_load_screen_render_player_detail_stock_holdings_panel`
-> `0x004e5a80` `shell_render_company_overview_panel_header_and_optional_change_affordance`
-> `0x004e5cf0` `shell_format_company_governance_and_economy_status_panel`
- `0x004e5a80` `shell_render_company_overview_panel_header_and_optional_change_affordance`
-> `0x004e5cf0` `shell_format_company_governance_and_economy_status_panel`
- `0x004ea620` `shell_load_screen_window_construct`
-> `0x004e3a80` `shell_load_screen_window_handle_message`
-> `0x004e5300` `shell_load_screen_render_player_detail_stock_holdings_panel`
-> `0x004e5a80` `shell_render_company_overview_panel_header_and_optional_change_affordance`
-> `0x004e5cf0` `shell_format_company_governance_and_economy_status_panel`
-> `0x004ea720` `shell_load_screen_window_is_open`
-> `0x004ea730` `shell_load_screen_window_destroy`
- `0x004ea720` `shell_load_screen_window_is_open`
-> `0x004ea620` `shell_load_screen_window_construct`
-> `0x004ea730` `shell_load_screen_window_destroy`
- `0x004ea730` `shell_load_screen_window_destroy`
-> `0x004ea620` `shell_load_screen_window_construct`
- `0x004ee3a0` `multiplayer_reset_tool_globals`
-> `0x00482ec0` `shell_transition_mode`
- `0x004ee950` `multiplayer_load_selected_map_preview_surface`
-> `0x004efe80` `multiplayer_window_init_globals`
- `0x00502720` `paint_terrain_tool_init_globals`
-> `0x004ee3a0` `multiplayer_reset_tool_globals`
- `0x00517570` `shell_video_window_destroy`
-> `0x00484910` `shell_save_graphics_config`
-> `0x005174e0` `shell_video_window_construct`
- `0x005204b0` `shell_flush_deferred_work_queues`
-> `0x00443a50` `world_entry_transition_and_runtime_bringup`

View file

@ -6,4 +6,6 @@ license.workspace = true
[dependencies] [dependencies]
rrt-model = { path = "../rrt-model" } rrt-model = { path = "../rrt-model" }
serde.workspace = true
serde_json.workspace = true
sha2.workspace = true sha2.workspace = true

View file

@ -6,10 +6,34 @@ use std::path::{Path, PathBuf};
use rrt_model::{ use rrt_model::{
BINARY_SUMMARY_PATH, CANONICAL_EXE_PATH, CONTROL_LOOP_ATLAS_PATH, FUNCTION_MAP_PATH, BINARY_SUMMARY_PATH, CANONICAL_EXE_PATH, CONTROL_LOOP_ATLAS_PATH, FUNCTION_MAP_PATH,
REQUIRED_ATLAS_HEADINGS, REQUIRED_EXPORTS, load_binary_summary, load_function_map, REQUIRED_ATLAS_HEADINGS, REQUIRED_EXPORTS,
finance::{FinanceOutcome, FinanceSnapshot},
load_binary_summary, load_function_map,
}; };
use serde::Serialize;
use serde_json::Value;
use sha2::{Digest, Sha256}; use sha2::{Digest, Sha256};
enum Command {
Validate { repo_root: PathBuf },
FinanceEval { snapshot_path: PathBuf },
FinanceDiff { left_path: PathBuf, right_path: PathBuf },
}
#[derive(Debug, Serialize)]
struct FinanceDiffEntry {
path: String,
left: Value,
right: Value,
}
#[derive(Debug, Serialize)]
struct FinanceDiffReport {
matches: bool,
difference_count: usize,
differences: Vec<FinanceDiffEntry>,
}
fn main() { fn main() {
if let Err(err) = real_main() { if let Err(err) = real_main() {
eprintln!("error: {err}"); eprintln!("error: {err}");
@ -18,22 +42,155 @@ fn main() {
} }
fn real_main() -> Result<(), Box<dyn std::error::Error>> { fn real_main() -> Result<(), Box<dyn std::error::Error>> {
let repo_root = parse_repo_root()?; match parse_command()? {
Command::Validate { repo_root } => {
validate_required_files(&repo_root)?; validate_required_files(&repo_root)?;
validate_binary_summary(&repo_root)?; validate_binary_summary(&repo_root)?;
validate_function_map(&repo_root)?; validate_function_map(&repo_root)?;
validate_control_loop_atlas(&repo_root)?; validate_control_loop_atlas(&repo_root)?;
println!("baseline validation passed"); println!("baseline validation passed");
}
Command::FinanceEval { snapshot_path } => {
run_finance_eval(&snapshot_path)?;
}
Command::FinanceDiff {
left_path,
right_path,
} => {
run_finance_diff(&left_path, &right_path)?;
}
}
Ok(()) Ok(())
} }
fn parse_repo_root() -> Result<PathBuf, Box<dyn std::error::Error>> { fn parse_command() -> Result<Command, Box<dyn std::error::Error>> {
let mut args = env::args().skip(1); let mut args = env::args().skip(1);
match (args.next().as_deref(), args.next(), args.next()) { match (args.next().as_deref(), args.next(), args.next(), args.next()) {
(None, None, None) => Ok(env::current_dir()?), (None, None, None, None) => Ok(Command::Validate {
(Some("validate"), None, None) => Ok(env::current_dir()?), repo_root: env::current_dir()?,
(Some("validate"), Some(path), None) => Ok(PathBuf::from(path)), }),
_ => Err("usage: rrt-cli [validate [repo-root]]".into()), (Some("validate"), None, None, None) => Ok(Command::Validate {
repo_root: env::current_dir()?,
}),
(Some("validate"), Some(path), None, None) => Ok(Command::Validate {
repo_root: PathBuf::from(path),
}),
(Some("finance"), Some(subcommand), Some(path), None) if subcommand == "eval" => {
Ok(Command::FinanceEval {
snapshot_path: PathBuf::from(path),
})
}
(Some("finance"), Some(subcommand), Some(left), Some(right)) if subcommand == "diff" => {
Ok(Command::FinanceDiff {
left_path: PathBuf::from(left),
right_path: PathBuf::from(right),
})
}
_ => Err(
"usage: rrt-cli [validate [repo-root] | finance eval <snapshot.json> | finance diff <left.json> <right.json>]"
.into(),
),
}
}
fn run_finance_eval(snapshot_path: &Path) -> Result<(), Box<dyn std::error::Error>> {
let outcome = load_finance_outcome(snapshot_path)?;
println!("{}", serde_json::to_string_pretty(&outcome)?);
Ok(())
}
fn run_finance_diff(
left_path: &Path,
right_path: &Path,
) -> Result<(), Box<dyn std::error::Error>> {
let left = load_finance_outcome(left_path)?;
let right = load_finance_outcome(right_path)?;
let report = diff_finance_outcomes(&left, &right)?;
println!("{}", serde_json::to_string_pretty(&report)?);
Ok(())
}
fn load_finance_outcome(path: &Path) -> Result<FinanceOutcome, Box<dyn std::error::Error>> {
let text = fs::read_to_string(path)?;
if let Ok(snapshot) = serde_json::from_str::<FinanceSnapshot>(&text) {
return Ok(snapshot.evaluate());
}
if let Ok(outcome) = serde_json::from_str::<FinanceOutcome>(&text) {
return Ok(outcome);
}
Err(format!(
"unable to parse {} as FinanceSnapshot or FinanceOutcome",
path.display()
)
.into())
}
fn diff_finance_outcomes(
left: &FinanceOutcome,
right: &FinanceOutcome,
) -> Result<FinanceDiffReport, Box<dyn std::error::Error>> {
let left_value = serde_json::to_value(left)?;
let right_value = serde_json::to_value(right)?;
let mut differences = Vec::new();
collect_json_differences("$", &left_value, &right_value, &mut differences);
Ok(FinanceDiffReport {
matches: differences.is_empty(),
difference_count: differences.len(),
differences,
})
}
fn collect_json_differences(
path: &str,
left: &Value,
right: &Value,
differences: &mut Vec<FinanceDiffEntry>,
) {
match (left, right) {
(Value::Object(left_map), Value::Object(right_map)) => {
let mut keys = BTreeSet::new();
keys.extend(left_map.keys().cloned());
keys.extend(right_map.keys().cloned());
for key in keys {
let next_path = format!("{path}.{key}");
match (left_map.get(&key), right_map.get(&key)) {
(Some(left_value), Some(right_value)) => {
collect_json_differences(&next_path, left_value, right_value, differences);
}
(left_value, right_value) => differences.push(FinanceDiffEntry {
path: next_path,
left: left_value.cloned().unwrap_or(Value::Null),
right: right_value.cloned().unwrap_or(Value::Null),
}),
}
}
}
(Value::Array(left_items), Value::Array(right_items)) => {
let max_len = left_items.len().max(right_items.len());
for index in 0..max_len {
let next_path = format!("{path}[{index}]");
match (left_items.get(index), right_items.get(index)) {
(Some(left_value), Some(right_value)) => {
collect_json_differences(&next_path, left_value, right_value, differences);
}
(left_value, right_value) => differences.push(FinanceDiffEntry {
path: next_path,
left: left_value.cloned().unwrap_or(Value::Null),
right: right_value.cloned().unwrap_or(Value::Null),
}),
}
}
}
_ if left != right => differences.push(FinanceDiffEntry {
path: path.to_string(),
left: left.clone(),
right: right.clone(),
}),
_ => {}
} }
} }
@ -143,3 +300,62 @@ fn sha256_file(path: &Path) -> Result<String, Box<dyn std::error::Error>> {
Ok(format!("{:x}", hasher.finalize())) Ok(format!("{:x}", hasher.finalize()))
} }
#[cfg(test)]
mod tests {
use super::*;
use rrt_model::finance::{AnnualFinanceDecision, AnnualFinanceEvaluation, CompanyFinanceState, DebtRestructureSummary};
#[test]
fn loads_snapshot_as_outcome() {
let snapshot = FinanceSnapshot {
policy: rrt_model::finance::AnnualFinancePolicy {
dividends_allowed: false,
..rrt_model::finance::AnnualFinancePolicy::default()
},
company: CompanyFinanceState::default(),
};
let path = write_temp_json("snapshot", &snapshot);
let outcome = load_finance_outcome(&path).expect("snapshot should load");
assert_eq!(outcome.evaluation.decision, AnnualFinanceDecision::NoAction);
let _ = fs::remove_file(path);
}
#[test]
fn diffs_outcomes_recursively() {
let left = FinanceOutcome {
evaluation: AnnualFinanceEvaluation::no_action(),
post_company: CompanyFinanceState::default(),
};
let mut right = left.clone();
right.post_company.current_cash = 123;
right.evaluation.debt_restructure = DebtRestructureSummary {
retired_principal: 10,
issued_principal: 20,
};
let report = diff_finance_outcomes(&left, &right).expect("diff should succeed");
assert!(!report.matches);
assert!(report
.differences
.iter()
.any(|entry| entry.path == "$.post_company.current_cash"));
assert!(report
.differences
.iter()
.any(|entry| entry.path == "$.evaluation.debt_restructure.retired_principal"));
}
fn write_temp_json<T: Serialize>(stem: &str, value: &T) -> PathBuf {
let nonce = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.expect("system time should be after epoch")
.as_nanos();
let path = std::env::temp_dir().join(format!("rrt-cli-{stem}-{nonce}.json"));
let bytes = serde_json::to_vec_pretty(value).expect("json serialization should succeed");
fs::write(&path, bytes).expect("temp json should be written");
path
}
}

View file

@ -7,3 +7,8 @@ license.workspace = true
[lib] [lib]
name = "dinput8" name = "dinput8"
crate-type = ["cdylib", "rlib"] crate-type = ["cdylib", "rlib"]
[dependencies]
rrt-model = { path = "../rrt-model" }
serde.workspace = true
serde_json.workspace = true

View file

@ -1,10 +1,144 @@
#![cfg_attr(not(windows), allow(dead_code))] #![cfg_attr(not(windows), allow(dead_code))]
use std::fs;
use std::io;
use std::path::{Path, PathBuf};
use rrt_model::finance::{
AnnualFinancePolicy, BondPosition, CompanyFinanceState, FinanceOutcome, FinanceSnapshot,
};
use serde::Serialize;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct FinanceLogPaths {
pub snapshot_path: PathBuf,
pub outcome_path: PathBuf,
}
pub fn sample_finance_snapshot() -> FinanceSnapshot {
FinanceSnapshot {
policy: AnnualFinancePolicy {
dividends_allowed: false,
..AnnualFinancePolicy::default()
},
company: CompanyFinanceState {
current_cash: 100_000,
support_adjusted_share_price: 27.5,
book_value_per_share: 20.0,
outstanding_share_count: 60_000,
recent_net_profits: [40_000, 30_000, 20_000],
recent_revenue_totals: [250_000, 240_000, 230_000],
bonds: vec![
BondPosition {
principal: 150_000,
coupon_rate: 0.12,
years_remaining: 12,
},
BondPosition {
principal: 10_000,
coupon_rate: 0.10,
years_remaining: 10,
},
],
..CompanyFinanceState::default()
},
}
}
pub fn write_finance_snapshot_bundle(
base_dir: &Path,
stem: &str,
snapshot: &FinanceSnapshot,
) -> io::Result<FinanceLogPaths> {
fs::create_dir_all(base_dir)?;
let snapshot_path = base_dir.join(format!("rrt_finance_{stem}_snapshot.json"));
let outcome_path = base_dir.join(format!("rrt_finance_{stem}_outcome.json"));
let outcome: FinanceOutcome = snapshot.evaluate();
let snapshot_json = serde_json::to_vec_pretty(snapshot)
.map_err(|err| io::Error::other(format!("serialize snapshot: {err}")))?;
let outcome_json = serde_json::to_vec_pretty(&outcome)
.map_err(|err| io::Error::other(format!("serialize outcome: {err}")))?;
fs::write(&snapshot_path, snapshot_json)?;
fs::write(&outcome_path, outcome_json)?;
Ok(FinanceLogPaths {
snapshot_path,
outcome_path,
})
}
pub fn write_finance_snapshot_only(
base_dir: &Path,
stem: &str,
snapshot: &FinanceSnapshot,
) -> io::Result<PathBuf> {
fs::create_dir_all(base_dir)?;
let snapshot_path = base_dir.join(format!("rrt_finance_{stem}_snapshot.json"));
let snapshot_json = serde_json::to_vec_pretty(snapshot)
.map_err(|err| io::Error::other(format!("serialize snapshot: {err}")))?;
fs::write(&snapshot_path, snapshot_json)?;
Ok(snapshot_path)
}
#[derive(Debug, Clone, Serialize)]
pub struct IndexedCollectionProbeRow {
pub entry_id: usize,
pub live: bool,
pub resolved_ptr: usize,
pub active_flag: Option<u8>,
}
#[derive(Debug, Clone, Serialize)]
pub struct IndexedCollectionProbe {
pub collection_addr: usize,
pub flat_payload: bool,
pub stride: u32,
pub id_bound: i32,
pub payload_ptr: usize,
pub tombstone_ptr: usize,
pub first_rows: Vec<IndexedCollectionProbeRow>,
}
pub fn write_indexed_collection_probe(
base_dir: &Path,
stem: &str,
probe: &IndexedCollectionProbe,
) -> io::Result<PathBuf> {
fs::create_dir_all(base_dir)?;
let path = base_dir.join(format!("rrt_finance_{stem}_collection_probe.json"));
let json = serde_json::to_vec_pretty(probe)
.map_err(|err| io::Error::other(format!("serialize collection probe: {err}")))?;
fs::write(&path, json)?;
Ok(path)
}
#[cfg(windows)] #[cfg(windows)]
mod windows_hook { mod windows_hook {
use super::{
IndexedCollectionProbe, IndexedCollectionProbeRow, sample_finance_snapshot,
write_finance_snapshot_bundle, write_finance_snapshot_only, write_indexed_collection_probe,
};
use core::ffi::{c_char, c_void}; use core::ffi::{c_char, c_void};
use core::mem; use core::mem;
use core::ptr; use core::ptr;
use std::env;
use std::fmt::Write as _;
use std::path::PathBuf;
use std::sync::OnceLock;
use std::sync::atomic::{AtomicBool, AtomicU32, Ordering};
use std::thread;
use std::time::Duration;
use rrt_model::finance::{
AnnualFinancePolicy, BondPosition, CompanyFinanceState, FinanceSnapshot, GrowthSetting,
};
const DLL_PROCESS_ATTACH: u32 = 1; const DLL_PROCESS_ATTACH: u32 = 1;
const E_FAIL: i32 = 0x8000_4005_u32 as i32; const E_FAIL: i32 = 0x8000_4005_u32 as i32;
@ -17,10 +151,92 @@ mod windows_hook {
static LOG_PATH: &[u8] = b"rrt_hook_attach.log\0"; static LOG_PATH: &[u8] = b"rrt_hook_attach.log\0";
static ATTACH_MESSAGE: &[u8] = b"rrt-hook: process attach\n"; static ATTACH_MESSAGE: &[u8] = b"rrt-hook: process attach\n";
static FINANCE_CAPTURE_STARTED_MESSAGE: &[u8] = b"rrt-hook: finance capture thread started\n";
static FINANCE_CAPTURE_SCAN_MESSAGE: &[u8] =
b"rrt-hook: finance capture raw collection scan\n";
static FINANCE_CAPTURE_PROBE_DUMP_WRITTEN_MESSAGE: &[u8] =
b"rrt-hook: finance collection probe written\n";
static FINANCE_CAPTURE_COMPANY_RESOLVED_MESSAGE: &[u8] =
b"rrt-hook: finance capture company resolved\n";
static FINANCE_CAPTURE_PROBE_WRITTEN_MESSAGE: &[u8] =
b"rrt-hook: finance probe snapshot written\n";
static FINANCE_CAPTURE_TIMEOUT_MESSAGE: &[u8] =
b"rrt-hook: finance capture timed out\n";
static AUTO_LOAD_STARTED_MESSAGE: &[u8] =
b"rrt-hook: auto load hook armed\n";
static AUTO_LOAD_HOOK_INSTALLED_MESSAGE: &[u8] =
b"rrt-hook: auto load shell-pump hook installed\n";
static AUTO_LOAD_READY_MESSAGE: &[u8] =
b"rrt-hook: auto load ready gate passed\n";
static AUTO_LOAD_DEFERRED_MESSAGE: &[u8] =
b"rrt-hook: auto load restore deferred to later shell-pump turn\n";
static AUTO_LOAD_CALLING_MESSAGE: &[u8] =
b"rrt-hook: auto load restore calling\n";
static AUTO_LOAD_OWNER_ENTRY_MESSAGE: &[u8] =
b"rrt-hook: auto load larger owner entering\n";
static AUTO_LOAD_OWNER_RETURNED_MESSAGE: &[u8] =
b"rrt-hook: auto load larger owner returned\n";
static AUTO_LOAD_TRIGGERED_MESSAGE: &[u8] =
b"rrt-hook: auto load restore invoked\n";
static AUTO_LOAD_SUCCESS_MESSAGE: &[u8] =
b"rrt-hook: auto load request reported success\n";
static AUTO_LOAD_FAILURE_MESSAGE: &[u8] =
b"rrt-hook: auto load request reported failure\n";
static DEBUG_MESSAGE: &[u8] = b"rrt-hook: DllMain process attach\0"; static DEBUG_MESSAGE: &[u8] = b"rrt-hook: DllMain process attach\0";
static DIRECT_INPUT8_CREATE_NAME: &[u8] = b"DirectInput8Create\0"; static DIRECT_INPUT8_CREATE_NAME: &[u8] = b"DirectInput8Create\0";
static mut REAL_DINPUT8_CREATE: Option<DirectInput8CreateFn> = None; static mut REAL_DINPUT8_CREATE: Option<DirectInput8CreateFn> = None;
static FINANCE_TEMPLATE_EMITTED: AtomicBool = AtomicBool::new(false);
static FINANCE_CAPTURE_STARTED: AtomicBool = AtomicBool::new(false);
static FINANCE_COLLECTION_PROBE_WRITTEN: AtomicBool = AtomicBool::new(false);
static AUTO_LOAD_THREAD_STARTED: AtomicBool = AtomicBool::new(false);
static AUTO_LOAD_HOOK_INSTALLED: AtomicBool = AtomicBool::new(false);
static AUTO_LOAD_ATTEMPTED: AtomicBool = AtomicBool::new(false);
static AUTO_LOAD_IN_PROGRESS: AtomicBool = AtomicBool::new(false);
static AUTO_LOAD_DEFERRED: AtomicBool = AtomicBool::new(false);
static AUTO_LOAD_LAST_GATE_MASK: AtomicU32 = AtomicU32::new(u32::MAX);
static AUTO_LOAD_READY_COUNT: AtomicU32 = AtomicU32::new(0);
static AUTO_LOAD_SAVE_STEM: OnceLock<String> = OnceLock::new();
static mut SHELL_PUMP_TRAMPOLINE: usize = 0;
const COMPANY_COLLECTION_ADDR: usize = 0x0062be10;
const SHELL_CONTROLLER_PTR_ADDR: usize = 0x006d4024;
const SHELL_STATE_PTR_ADDR: usize = 0x006cec74;
const ACTIVE_MODE_PTR_ADDR: usize = 0x006cec78;
const SHELL_PUMP_ADDR: usize = 0x00483f70;
const SHELL_STATE_ACTIVE_MODE_OFFSET: usize = 0x08;
const SHELL_STATE_ACTIVE_MODE_OBJECT_OFFSET: usize = 0x0c;
const RUNTIME_PROFILE_PTR_ADDR: usize = 0x006cec7c;
const RUNTIME_PROFILE_MANUAL_LOAD_PATH_OFFSET: usize = 0x11;
const RUNTIME_PROFILE_PENDING_LOAD_BYTE_OFFSET: usize = 0x97;
const INDEXED_COLLECTION_FLAT_FLAG_OFFSET: usize = 0x04;
const INDEXED_COLLECTION_STRIDE_OFFSET: usize = 0x08;
const INDEXED_COLLECTION_ID_BOUND_OFFSET: usize = 0x14;
const INDEXED_COLLECTION_PAYLOAD_OFFSET: usize = 0x30;
const INDEXED_COLLECTION_TOMBSTONE_BITSET_OFFSET: usize = 0x34;
const COMPANY_ACTIVE_OFFSET: usize = 0x3f;
const COMPANY_OUTSTANDING_SHARES_OFFSET: usize = 0x47;
const COMPANY_COMPANY_VALUE_OFFSET: usize = 0x57;
const COMPANY_BOND_COUNT_OFFSET: usize = 0x5b;
const COMPANY_BOND_TABLE_OFFSET: usize = 0x5f;
const COMPANY_FOUNDING_YEAR_OFFSET: usize = 0x157;
const COMPANY_LAST_BANKRUPTCY_YEAR_OFFSET: usize = 0x163;
const COMPANY_CITY_CONNECTION_LATCH_OFFSET: usize = 0x0d18;
const COMPANY_LINKED_TRANSIT_LATCH_OFFSET: usize = 0x0d56;
const SCENARIO_CURRENT_YEAR_OFFSET: usize = 0x0d;
const SCENARIO_BUILDING_DENSITY_GROWTH_OFFSET: usize = 0x4c7c;
const SCENARIO_BANKRUPTCY_TOGGLE_OFFSET: usize = 0x4a8f;
const SCENARIO_BOND_TOGGLE_OFFSET: usize = 0x4a8b;
const SCENARIO_STOCK_TOGGLE_OFFSET: usize = 0x4a87;
const SCENARIO_DIVIDEND_TOGGLE_OFFSET: usize = 0x4a93;
const MAX_CAPTURE_POLL_ATTEMPTS: usize = 120;
const CAPTURE_POLL_INTERVAL: Duration = Duration::from_secs(1);
const AUTO_LOAD_READY_POLLS: u32 = 30;
const AUTO_LOAD_DEFER_POLLS: u32 = 5;
const MEM_COMMIT: u32 = 0x1000;
const MEM_RESERVE: u32 = 0x2000;
const PAGE_EXECUTE_READWRITE: u32 = 0x40;
unsafe extern "system" { unsafe extern "system" {
fn CreateFileA( fn CreateFileA(
lp_file_name: *const c_char, lp_file_name: *const c_char,
@ -46,10 +262,28 @@ mod windows_hook {
) -> i32; ) -> i32;
fn CloseHandle(handle: isize) -> i32; fn CloseHandle(handle: isize) -> i32;
fn DisableThreadLibraryCalls(module: *mut c_void) -> i32; fn DisableThreadLibraryCalls(module: *mut c_void) -> i32;
fn FlushInstructionCache(
process: *mut c_void,
base_address: *const c_void,
size: usize,
) -> i32;
fn GetCurrentProcess() -> *mut c_void;
fn GetSystemDirectoryA(buffer: *mut u8, size: u32) -> u32; fn GetSystemDirectoryA(buffer: *mut u8, size: u32) -> u32;
fn GetProcAddress(module: isize, name: *const c_char) -> *mut c_void; fn GetProcAddress(module: isize, name: *const c_char) -> *mut c_void;
fn LoadLibraryA(name: *const c_char) -> isize; fn LoadLibraryA(name: *const c_char) -> isize;
fn OutputDebugStringA(output: *const c_char); fn OutputDebugStringA(output: *const c_char);
fn VirtualAlloc(
address: *mut c_void,
size: usize,
allocation_type: u32,
protect: u32,
) -> *mut c_void;
fn VirtualProtect(
address: *mut c_void,
size: usize,
new_protect: u32,
old_protect: *mut u32,
) -> i32;
} }
#[repr(C)] #[repr(C)]
@ -67,7 +301,8 @@ mod windows_hook {
out: *mut *mut c_void, out: *mut *mut c_void,
outer: *mut c_void, outer: *mut c_void,
) -> i32; ) -> i32;
type ShellPumpFn = unsafe extern "thiscall" fn(*mut u8) -> i32;
type LargerManualLoadOwnerFn = unsafe extern "thiscall" fn(*mut u8, u32, u32);
#[unsafe(no_mangle)] #[unsafe(no_mangle)]
pub extern "system" fn DllMain( pub extern "system" fn DllMain(
module: *mut c_void, module: *mut c_void,
@ -92,6 +327,10 @@ mod windows_hook {
out: *mut *mut c_void, out: *mut *mut c_void,
outer: *mut c_void, outer: *mut c_void,
) -> i32 { ) -> i32 {
maybe_emit_finance_template_bundle();
maybe_start_finance_capture_thread();
maybe_install_auto_load_hook();
let direct_input8_create = unsafe { load_direct_input8_create() }; let direct_input8_create = unsafe { load_direct_input8_create() };
match direct_input8_create { match direct_input8_create {
Some(callback) => unsafe { callback(instance, version, riid, out, outer) }, Some(callback) => unsafe { callback(instance, version, riid, out, outer) },
@ -100,6 +339,10 @@ mod windows_hook {
} }
unsafe fn append_attach_log() { unsafe fn append_attach_log() {
append_log_message(ATTACH_MESSAGE);
}
fn append_log_message(message: &[u8]) {
let handle = unsafe { let handle = unsafe {
CreateFileA( CreateFileA(
LOG_PATH.as_ptr().cast(), LOG_PATH.as_ptr().cast(),
@ -120,8 +363,8 @@ mod windows_hook {
let _ = unsafe { let _ = unsafe {
WriteFile( WriteFile(
handle, handle,
ATTACH_MESSAGE.as_ptr().cast(), message.as_ptr().cast(),
ATTACH_MESSAGE.len() as u32, message.len() as u32,
&mut bytes_written, &mut bytes_written,
ptr::null_mut(), ptr::null_mut(),
) )
@ -129,6 +372,613 @@ mod windows_hook {
let _ = unsafe { CloseHandle(handle) }; let _ = unsafe { CloseHandle(handle) };
} }
fn append_log_line(line: &str) {
append_log_message(line.as_bytes());
}
fn maybe_emit_finance_template_bundle() {
if env::var_os("RRT_WRITE_FINANCE_TEMPLATE").is_none() {
return;
}
if FINANCE_TEMPLATE_EMITTED.swap(true, Ordering::AcqRel) {
return;
}
let base_dir = env::current_dir().unwrap_or_else(|_| PathBuf::from("."));
let _ = write_finance_snapshot_bundle(
&base_dir,
"attach_template",
&sample_finance_snapshot(),
);
}
fn maybe_start_finance_capture_thread() {
if env::var_os("RRT_WRITE_FINANCE_CAPTURE").is_none() {
return;
}
if FINANCE_CAPTURE_STARTED.swap(true, Ordering::AcqRel) {
return;
}
append_log_message(FINANCE_CAPTURE_STARTED_MESSAGE);
let base_dir = env::current_dir().unwrap_or_else(|_| PathBuf::from("."));
let _ = thread::Builder::new()
.name("rrt-finance-capture".to_string())
.spawn(move || {
for _ in 0..MAX_CAPTURE_POLL_ATTEMPTS {
if !FINANCE_COLLECTION_PROBE_WRITTEN.load(Ordering::Acquire) {
if let Some(probe) = unsafe { capture_company_collection_probe() } {
if write_indexed_collection_probe(&base_dir, "attach_probe", &probe)
.is_ok()
{
FINANCE_COLLECTION_PROBE_WRITTEN.store(true, Ordering::Release);
append_log_message(FINANCE_CAPTURE_PROBE_DUMP_WRITTEN_MESSAGE);
}
}
}
if let Some(snapshot) = unsafe { try_capture_probe_snapshot() } {
append_log_message(FINANCE_CAPTURE_COMPANY_RESOLVED_MESSAGE);
if write_finance_snapshot_only(&base_dir, "attach_probe", &snapshot)
.is_ok()
{
append_log_message(FINANCE_CAPTURE_PROBE_WRITTEN_MESSAGE);
return;
}
}
thread::sleep(CAPTURE_POLL_INTERVAL);
}
append_log_message(FINANCE_CAPTURE_TIMEOUT_MESSAGE);
});
}
fn maybe_install_auto_load_hook() {
let save_stem = match env::var("RRT_AUTO_LOAD_SAVE") {
Ok(value) if !value.trim().is_empty() => value,
_ => return,
};
let _ = AUTO_LOAD_SAVE_STEM.set(save_stem);
if AUTO_LOAD_HOOK_INSTALLED.swap(true, Ordering::AcqRel) {
return;
}
append_log_message(AUTO_LOAD_STARTED_MESSAGE);
AUTO_LOAD_THREAD_STARTED.store(true, Ordering::Release);
if unsafe { install_shell_pump_hook() } {
append_log_message(AUTO_LOAD_HOOK_INSTALLED_MESSAGE);
} else {
append_log_message(AUTO_LOAD_FAILURE_MESSAGE);
}
}
fn run_auto_load_worker(save_stem: &str) {
append_log_message(AUTO_LOAD_CALLING_MESSAGE);
let staged = unsafe { invoke_manual_load_branch(save_stem) };
if staged {
append_log_message(AUTO_LOAD_TRIGGERED_MESSAGE);
append_log_message(AUTO_LOAD_SUCCESS_MESSAGE);
} else {
append_log_message(AUTO_LOAD_FAILURE_MESSAGE);
}
AUTO_LOAD_IN_PROGRESS.store(false, Ordering::Release);
}
unsafe fn invoke_manual_load_branch(save_stem: &str) -> bool {
if save_stem.is_empty() || save_stem.as_bytes().contains(&0) {
return false;
}
let shell_state = unsafe { read_ptr(SHELL_STATE_PTR_ADDR as *const u8) };
let runtime_profile = unsafe { read_ptr(RUNTIME_PROFILE_PTR_ADDR as *const u8) };
let active_mode = unsafe { resolve_active_mode_ptr() };
if shell_state.is_null() || runtime_profile.is_null() || active_mode.is_null() {
return false;
}
let path_seed = unsafe { runtime_profile.add(RUNTIME_PROFILE_MANUAL_LOAD_PATH_OFFSET) };
if unsafe { write_c_string(path_seed, 260, save_stem.as_bytes()) }.is_none() {
return false;
}
unsafe {
ptr::write_unaligned(
runtime_profile
.add(RUNTIME_PROFILE_PENDING_LOAD_BYTE_OFFSET)
.cast::<u8>(),
0,
)
};
let larger_owner: LargerManualLoadOwnerFn =
unsafe { mem::transmute(0x00438890usize) };
let global_active_mode = unsafe { read_ptr(ACTIVE_MODE_PTR_ADDR as *const u8) };
if global_active_mode.is_null() {
unsafe {
ptr::write_unaligned(
(ACTIVE_MODE_PTR_ADDR as *mut u8).cast::<usize>(),
active_mode as usize,
)
};
}
append_log_message(AUTO_LOAD_OWNER_ENTRY_MESSAGE);
unsafe { larger_owner(active_mode, 1, 0) };
append_log_message(AUTO_LOAD_OWNER_RETURNED_MESSAGE);
if global_active_mode.is_null() {
unsafe {
ptr::write_unaligned((ACTIVE_MODE_PTR_ADDR as *mut u8).cast::<usize>(), 0)
};
}
true
}
unsafe fn write_c_string(
destination: *mut u8,
capacity: usize,
bytes: &[u8],
) -> Option<()> {
if bytes.len() + 1 > capacity {
return None;
}
unsafe { ptr::write_bytes(destination, 0, capacity) };
unsafe { ptr::copy_nonoverlapping(bytes.as_ptr(), destination, bytes.len()) };
Some(())
}
unsafe fn try_capture_probe_snapshot() -> Option<FinanceSnapshot> {
append_log_message(FINANCE_CAPTURE_SCAN_MESSAGE);
let company = unsafe { resolve_first_active_company()? };
Some(unsafe { capture_probe_snapshot_from_company(company) })
}
unsafe fn runtime_saved_world_restore_gate_mask() -> u32 {
let mut mask = 0_u32;
let shell_state = unsafe { read_ptr(SHELL_STATE_PTR_ADDR as *const u8) };
if !shell_state.is_null() {
mask |= 0x1;
}
let shell_controller = unsafe { read_ptr(SHELL_CONTROLLER_PTR_ADDR as *const u8) };
if !shell_controller.is_null() {
mask |= 0x2;
}
let active_mode = unsafe { resolve_active_mode_ptr() };
if !active_mode.is_null() {
mask |= 0x4;
}
mask
}
unsafe fn current_mode_id() -> u32 {
let shell_state = unsafe { read_ptr(SHELL_STATE_PTR_ADDR as *const u8) };
if shell_state.is_null() {
return 0;
}
unsafe { read_u32(shell_state.add(SHELL_STATE_ACTIVE_MODE_OFFSET)) }
}
fn auto_load_ready_polls() -> u32 {
env::var("RRT_AUTO_LOAD_READY_POLLS")
.ok()
.and_then(|value| value.parse::<u32>().ok())
.filter(|value| *value > 0)
.unwrap_or(AUTO_LOAD_READY_POLLS)
}
fn auto_load_defer_polls() -> u32 {
env::var("RRT_AUTO_LOAD_DEFER_POLLS")
.ok()
.and_then(|value| value.parse::<u32>().ok())
.unwrap_or(AUTO_LOAD_DEFER_POLLS)
}
unsafe extern "fastcall" fn shell_pump_detour(this: *mut u8, _edx: usize) -> i32 {
let trampoline: ShellPumpFn = unsafe { mem::transmute(SHELL_PUMP_TRAMPOLINE) };
let result = unsafe { trampoline(this) };
maybe_service_auto_load_on_main_thread();
result
}
fn maybe_service_auto_load_on_main_thread() {
if !AUTO_LOAD_HOOK_INSTALLED.load(Ordering::Acquire)
|| AUTO_LOAD_ATTEMPTED.load(Ordering::Acquire)
|| AUTO_LOAD_IN_PROGRESS.load(Ordering::Acquire)
{
return;
}
let gate_mask = unsafe { runtime_saved_world_restore_gate_mask() };
let last_gate_mask = AUTO_LOAD_LAST_GATE_MASK.swap(gate_mask, Ordering::AcqRel);
if gate_mask != last_gate_mask {
log_auto_load_gate_mask(gate_mask);
}
let mode_id = unsafe { current_mode_id() };
let ready = gate_mask == 0x7 && mode_id == 2;
let ready_count = if ready {
AUTO_LOAD_READY_COUNT.fetch_add(1, Ordering::AcqRel) + 1
} else {
AUTO_LOAD_READY_COUNT.store(0, Ordering::Release);
AUTO_LOAD_DEFERRED.store(false, Ordering::Release);
0
};
let ready_polls = auto_load_ready_polls();
if ready_count < ready_polls {
return;
}
if !AUTO_LOAD_DEFERRED.load(Ordering::Acquire) {
AUTO_LOAD_DEFERRED.store(true, Ordering::Release);
append_log_message(AUTO_LOAD_READY_MESSAGE);
append_log_message(AUTO_LOAD_DEFERRED_MESSAGE);
return;
}
if ready_count < ready_polls.saturating_add(auto_load_defer_polls()) {
return;
}
if AUTO_LOAD_ATTEMPTED.swap(true, Ordering::AcqRel) {
return;
}
let Some(save_stem) = AUTO_LOAD_SAVE_STEM.get() else {
append_log_message(AUTO_LOAD_FAILURE_MESSAGE);
return;
};
AUTO_LOAD_IN_PROGRESS.store(true, Ordering::Release);
append_log_message(AUTO_LOAD_READY_MESSAGE);
run_auto_load_worker(save_stem);
}
fn log_auto_load_gate_mask(mask: u32) {
let mut line = String::from("rrt-hook: auto load gate mask ");
let global_active_mode = unsafe { read_ptr(ACTIVE_MODE_PTR_ADDR as *const u8) } as usize;
let shell_state = unsafe { read_ptr(SHELL_STATE_PTR_ADDR as *const u8) };
let mode_id = if shell_state.is_null() {
0
} else {
unsafe { read_u32(shell_state.add(SHELL_STATE_ACTIVE_MODE_OFFSET)) as usize }
};
let field_active_mode_object = if shell_state.is_null() {
0
} else {
unsafe { read_ptr(shell_state.add(SHELL_STATE_ACTIVE_MODE_OBJECT_OFFSET)) as usize }
};
let _ = write!(
&mut line,
"0x{mask:01x} shell_state={} shell_controller={} active_mode={} global_active_mode=0x{global_active_mode:08x} mode_id=0x{mode_id:08x} field_active_mode_object=0x{field_active_mode_object:08x}\n",
(mask & 0x1) != 0,
(mask & 0x2) != 0,
(mask & 0x4) != 0,
);
append_log_line(&line);
}
unsafe fn resolve_active_mode_ptr() -> *mut u8 {
let global_active_mode = unsafe { resolve_global_active_mode_ptr() };
if !global_active_mode.is_null() {
return global_active_mode;
}
let shell_state = unsafe { read_ptr(SHELL_STATE_PTR_ADDR as *const u8) };
if shell_state.is_null() {
return ptr::null_mut();
}
unsafe { read_ptr(shell_state.add(SHELL_STATE_ACTIVE_MODE_OBJECT_OFFSET)) }
}
unsafe fn resolve_global_active_mode_ptr() -> *mut u8 {
unsafe { read_ptr(ACTIVE_MODE_PTR_ADDR as *const u8) }
}
unsafe fn install_shell_pump_hook() -> bool {
const STOLEN_LEN: usize = 8;
let target = SHELL_PUMP_ADDR as *mut u8;
let trampoline_size = STOLEN_LEN + 5;
let trampoline = unsafe {
VirtualAlloc(
ptr::null_mut(),
trampoline_size,
MEM_COMMIT | MEM_RESERVE,
PAGE_EXECUTE_READWRITE,
)
} as *mut u8;
if trampoline.is_null() {
return false;
}
unsafe { ptr::copy_nonoverlapping(target, trampoline, STOLEN_LEN) };
unsafe {
write_rel32_jump(
trampoline.add(STOLEN_LEN),
target.add(STOLEN_LEN) as usize,
)
};
let mut old_protect = 0_u32;
if unsafe {
VirtualProtect(
target.cast(),
STOLEN_LEN,
PAGE_EXECUTE_READWRITE,
&mut old_protect,
)
} == 0
{
return false;
}
unsafe { write_rel32_jump(target, shell_pump_detour as *const () as usize) };
unsafe { ptr::write(target.add(5), 0x90) };
unsafe { ptr::write(target.add(6), 0x90) };
unsafe { ptr::write(target.add(7), 0x90) };
let mut restore_protect = 0_u32;
let _ = unsafe {
VirtualProtect(
target.cast(),
STOLEN_LEN,
old_protect,
&mut restore_protect,
)
};
let _ = unsafe {
FlushInstructionCache(
GetCurrentProcess(),
target.cast(),
STOLEN_LEN,
)
};
unsafe {
SHELL_PUMP_TRAMPOLINE = trampoline as usize;
}
true
}
unsafe fn write_rel32_jump(location: *mut u8, destination: usize) {
unsafe { ptr::write(location, 0xE9) };
let next_ip = unsafe { location.add(5) } as usize;
let relative = (destination as isize - next_ip as isize) as i32;
unsafe { ptr::write_unaligned(location.add(1).cast::<i32>(), relative) };
}
unsafe fn resolve_first_active_company() -> Option<*mut u8> {
let collection = COMPANY_COLLECTION_ADDR as *const u8;
let id_bound = unsafe { read_i32(collection.add(INDEXED_COLLECTION_ID_BOUND_OFFSET)) };
if id_bound <= 0 {
return None;
}
for entry_id in 1..=id_bound as usize {
if unsafe { indexed_collection_entry_id_is_live(collection, entry_id) } {
let company = unsafe { indexed_collection_resolve_live_entry_by_id(collection, entry_id) };
if !company.is_null() && unsafe { read_u8(company.add(COMPANY_ACTIVE_OFFSET)) != 0 } {
return Some(company);
}
}
}
None
}
unsafe fn capture_company_collection_probe() -> Option<IndexedCollectionProbe> {
let collection = COMPANY_COLLECTION_ADDR as *const u8;
let id_bound = unsafe { read_i32(collection.add(INDEXED_COLLECTION_ID_BOUND_OFFSET)) };
if id_bound <= 0 {
return Some(IndexedCollectionProbe {
collection_addr: COMPANY_COLLECTION_ADDR,
flat_payload: unsafe {
read_u32(collection.add(INDEXED_COLLECTION_FLAT_FLAG_OFFSET)) != 0
},
stride: unsafe { read_u32(collection.add(INDEXED_COLLECTION_STRIDE_OFFSET)) },
id_bound,
payload_ptr: unsafe {
read_ptr(collection.add(INDEXED_COLLECTION_PAYLOAD_OFFSET)) as usize
},
tombstone_ptr: unsafe {
read_ptr(collection.add(INDEXED_COLLECTION_TOMBSTONE_BITSET_OFFSET)) as usize
},
first_rows: Vec::new(),
});
}
let mut first_rows = Vec::new();
let sample_bound = (id_bound as usize).min(8);
for entry_id in 1..=sample_bound {
let live = unsafe { indexed_collection_entry_id_is_live(collection, entry_id) };
let resolved_ptr = unsafe {
indexed_collection_resolve_live_entry_by_id(collection, entry_id) as usize
};
let active_flag = if resolved_ptr == 0 {
None
} else {
Some(unsafe { read_u8((resolved_ptr as *const u8).add(COMPANY_ACTIVE_OFFSET)) })
};
first_rows.push(IndexedCollectionProbeRow {
entry_id,
live,
resolved_ptr,
active_flag,
});
}
Some(IndexedCollectionProbe {
collection_addr: COMPANY_COLLECTION_ADDR,
flat_payload: unsafe {
read_u32(collection.add(INDEXED_COLLECTION_FLAT_FLAG_OFFSET)) != 0
},
stride: unsafe { read_u32(collection.add(INDEXED_COLLECTION_STRIDE_OFFSET)) },
id_bound,
payload_ptr: unsafe { read_ptr(collection.add(INDEXED_COLLECTION_PAYLOAD_OFFSET)) as usize },
tombstone_ptr: unsafe {
read_ptr(collection.add(INDEXED_COLLECTION_TOMBSTONE_BITSET_OFFSET)) as usize
},
first_rows,
})
}
unsafe fn capture_probe_snapshot_from_company(company: *mut u8) -> FinanceSnapshot {
let scenario = unsafe { read_ptr(ACTIVE_MODE_PTR_ADDR as *const u8) } as *const u8;
let current_year = unsafe { read_u16(scenario.add(SCENARIO_CURRENT_YEAR_OFFSET)) };
let founding_year = unsafe { read_u16(company.add(COMPANY_FOUNDING_YEAR_OFFSET)) };
let last_bankruptcy_year =
unsafe { read_u16(company.add(COMPANY_LAST_BANKRUPTCY_YEAR_OFFSET)) };
let outstanding_share_count =
unsafe { read_u32(company.add(COMPANY_OUTSTANDING_SHARES_OFFSET)) };
let bonds = unsafe { capture_bonds(company, current_year) };
let company_value = unsafe { read_u32(company.add(COMPANY_COMPANY_VALUE_OFFSET)) as i64 };
let growth_setting = unsafe {
growth_setting_from_raw(read_u8(
scenario.add(SCENARIO_BUILDING_DENSITY_GROWTH_OFFSET),
))
};
FinanceSnapshot {
policy: AnnualFinancePolicy {
annual_mode: 0x0c,
bankruptcy_allowed: unsafe {
read_u8(scenario.add(SCENARIO_BANKRUPTCY_TOGGLE_OFFSET)) == 0
},
bond_issuance_allowed: unsafe {
read_u8(scenario.add(SCENARIO_BOND_TOGGLE_OFFSET)) == 0
},
stock_actions_allowed: unsafe {
read_u8(scenario.add(SCENARIO_STOCK_TOGGLE_OFFSET)) == 0
},
dividends_allowed: unsafe {
read_u8(scenario.add(SCENARIO_DIVIDEND_TOGGLE_OFFSET)) == 0
},
growth_setting,
..AnnualFinancePolicy::default()
},
company: CompanyFinanceState {
active: unsafe { read_u8(company.add(COMPANY_ACTIVE_OFFSET)) != 0 },
years_since_founding: year_delta(current_year, founding_year),
years_since_last_bankruptcy: year_delta(current_year, last_bankruptcy_year),
current_company_value: company_value,
outstanding_share_count,
city_connection_bonus_latch: unsafe {
read_u8(company.add(COMPANY_CITY_CONNECTION_LATCH_OFFSET)) != 0
},
linked_transit_service_latch: unsafe {
read_u8(company.add(COMPANY_LINKED_TRANSIT_LATCH_OFFSET)) != 0
},
chairman_buyback_factor: None,
bonds,
..CompanyFinanceState::default()
},
}
}
unsafe fn capture_bonds(company: *mut u8, current_year: u16) -> Vec<BondPosition> {
let bond_count = unsafe { read_u8(company.add(COMPANY_BOND_COUNT_OFFSET)) as usize };
let table = unsafe { company.add(COMPANY_BOND_TABLE_OFFSET) };
let mut bonds = Vec::with_capacity(bond_count);
for index in 0..bond_count {
let slot = unsafe { table.add(index * 12) };
let principal = unsafe { read_i32(slot) } as i64;
let maturity_year = unsafe { read_u32(slot.add(4)) };
let coupon_rate = unsafe { read_f32(slot.add(8)) } as f64;
bonds.push(BondPosition {
principal,
coupon_rate,
years_remaining: maturity_year
.saturating_sub(current_year as u32)
.min(u8::MAX as u32) as u8,
});
}
bonds
}
fn growth_setting_from_raw(raw: u8) -> GrowthSetting {
match raw {
1 => GrowthSetting::ExpansionBias,
2 => GrowthSetting::DividendSuppressed,
_ => GrowthSetting::Neutral,
}
}
fn year_delta(current_year: u16, past_year: u16) -> u8 {
current_year
.saturating_sub(past_year)
.min(u8::MAX as u16) as u8
}
unsafe fn indexed_collection_entry_id_is_live(collection: *const u8, entry_id: usize) -> bool {
let id_bound = unsafe { read_i32(collection.add(INDEXED_COLLECTION_ID_BOUND_OFFSET)) };
if entry_id == 0 || entry_id > id_bound.max(0) as usize {
return false;
}
let tombstone_bits = unsafe {
read_ptr(collection.add(INDEXED_COLLECTION_TOMBSTONE_BITSET_OFFSET))
};
if tombstone_bits.is_null() {
return true;
}
let bit_index = entry_id as u32;
let word = unsafe {
ptr::read_unaligned(tombstone_bits.add((bit_index / 32) as usize).cast::<u32>())
};
(word & (1_u32 << (bit_index % 32))) == 0
}
unsafe fn indexed_collection_resolve_live_entry_by_id(
collection: *const u8,
entry_id: usize,
) -> *mut u8 {
if !unsafe { indexed_collection_entry_id_is_live(collection, entry_id) } {
return ptr::null_mut();
}
let payload = unsafe { read_ptr(collection.add(INDEXED_COLLECTION_PAYLOAD_OFFSET)) };
if payload.is_null() {
return ptr::null_mut();
}
let stride = unsafe { read_u32(collection.add(INDEXED_COLLECTION_STRIDE_OFFSET)) as usize };
let flat = unsafe { read_u32(collection.add(INDEXED_COLLECTION_FLAT_FLAG_OFFSET)) != 0 };
if flat {
unsafe { payload.add(stride * entry_id) }
} else {
unsafe { ptr::read_unaligned(payload.add(stride * entry_id).cast::<*mut u8>()) }
}
}
unsafe fn read_u8(address: *const u8) -> u8 {
unsafe { ptr::read_unaligned(address) }
}
unsafe fn read_u16(address: *const u8) -> u16 {
unsafe { ptr::read_unaligned(address.cast::<u16>()) }
}
unsafe fn read_u32(address: *const u8) -> u32 {
unsafe { ptr::read_unaligned(address.cast::<u32>()) }
}
unsafe fn read_i32(address: *const u8) -> i32 {
unsafe { ptr::read_unaligned(address.cast::<i32>()) }
}
unsafe fn read_f32(address: *const u8) -> f32 {
unsafe { ptr::read_unaligned(address.cast::<f32>()) }
}
unsafe fn read_ptr(address: *const u8) -> *mut u8 {
unsafe { ptr::read_unaligned(address.cast::<*mut u8>()) }
}
unsafe fn load_direct_input8_create() -> Option<DirectInput8CreateFn> { unsafe fn load_direct_input8_create() -> Option<DirectInput8CreateFn> {
if let Some(callback) = unsafe { REAL_DINPUT8_CREATE } { if let Some(callback) = unsafe { REAL_DINPUT8_CREATE } {
return Some(callback); return Some(callback);
@ -168,3 +1018,30 @@ mod windows_hook {
pub fn host_build_marker() -> &'static str { pub fn host_build_marker() -> &'static str {
"rrt-hook host build" "rrt-hook host build"
} }
#[cfg(test)]
mod tests {
use super::*;
use std::time::{SystemTime, UNIX_EPOCH};
#[test]
fn writes_snapshot_bundle_to_disk() {
let nonce = SystemTime::now()
.duration_since(UNIX_EPOCH)
.expect("system time should be after epoch")
.as_nanos();
let dir = std::env::temp_dir().join(format!("rrt-hook-finance-{nonce}"));
let paths = write_finance_snapshot_bundle(&dir, "testcase", &sample_finance_snapshot())
.expect("bundle should be written");
let snapshot_json = fs::read_to_string(&paths.snapshot_path).expect("snapshot should exist");
let outcome_json = fs::read_to_string(&paths.outcome_path).expect("outcome should exist");
assert!(snapshot_json.contains("\"policy\""));
assert!(outcome_json.contains("\"evaluation\""));
let _ = fs::remove_file(&paths.snapshot_path);
let _ = fs::remove_file(&paths.outcome_path);
let _ = fs::remove_dir(&dir);
}
}

View file

@ -0,0 +1,957 @@
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
pub enum GrowthSetting {
#[default]
Neutral,
ExpansionBias,
DividendSuppressed,
}
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
pub struct BondPosition {
pub principal: i64,
pub coupon_rate: f64,
pub years_remaining: u8,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum BankruptcyReason {
EarlyStress,
DeepDistress,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum AnnualReportMetric {
NetProfits,
RevenueAggregate,
FuelCost,
RevenuePerShare,
EarningsPerShare,
DividendPerShare,
BookValuePerShare,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum DebtNewsOutcome {
RefinanceOnly,
RefinanceAndBorrow,
RefinanceAndPayDown,
DebtPayoffOnly,
NewBorrowingOnly,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
pub struct DebtRestructureSummary {
pub retired_principal: i64,
pub issued_principal: i64,
}
impl DebtRestructureSummary {
pub fn classify(self) -> Option<DebtNewsOutcome> {
match (self.retired_principal > 0, self.issued_principal > 0) {
(false, false) => None,
(false, true) => Some(DebtNewsOutcome::NewBorrowingOnly),
(true, false) => Some(DebtNewsOutcome::DebtPayoffOnly),
(true, true) if self.retired_principal == self.issued_principal => {
Some(DebtNewsOutcome::RefinanceOnly)
}
(true, true) if self.issued_principal > self.retired_principal => {
Some(DebtNewsOutcome::RefinanceAndBorrow)
}
(true, true) => Some(DebtNewsOutcome::RefinanceAndPayDown),
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum AnnualFinanceDecision {
NoAction,
DeclareBankruptcy {
reason: BankruptcyReason,
},
IssueBond {
count: u32,
principal_per_bond: i64,
term_years: u8,
},
RepurchasePublicShares {
share_count: u32,
price_per_share: f64,
},
IssuePublicShares {
share_count_per_tranche: u32,
tranche_count: u32,
price_per_share: f64,
},
AdjustDividend {
old_rate: f64,
new_rate: f64,
},
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct AnnualFinanceEvaluation {
pub decision: AnnualFinanceDecision,
pub debt_restructure: DebtRestructureSummary,
pub debt_news: Option<DebtNewsOutcome>,
pub repurchased_share_count: u32,
pub issued_share_count: u32,
}
impl AnnualFinanceEvaluation {
pub fn no_action() -> Self {
Self {
decision: AnnualFinanceDecision::NoAction,
debt_restructure: DebtRestructureSummary::default(),
debt_news: None,
repurchased_share_count: 0,
issued_share_count: 0,
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct AnnualFinancePolicy {
pub annual_mode: u8,
pub build_103_plus: bool,
pub bankruptcy_allowed: bool,
pub bond_issuance_allowed: bool,
pub stock_actions_allowed: bool,
pub dividends_allowed: bool,
pub growth_setting: GrowthSetting,
pub stock_issue_cash_buffer: i64,
}
impl Default for AnnualFinancePolicy {
fn default() -> Self {
Self {
annual_mode: 0x0c,
build_103_plus: true,
bankruptcy_allowed: true,
bond_issuance_allowed: true,
stock_actions_allowed: true,
dividends_allowed: true,
growth_setting: GrowthSetting::Neutral,
stock_issue_cash_buffer: 30_000,
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct CompanyFinanceState {
pub active: bool,
pub years_since_founding: u8,
pub years_since_last_bankruptcy: u8,
pub current_cash: i64,
pub current_company_value: i64,
pub support_adjusted_share_price: f64,
pub book_value_per_share: f64,
pub current_fuel_cost: i64,
pub current_dividend_per_share: f64,
pub board_dividend_ceiling: f64,
pub outstanding_share_count: u32,
pub unassigned_share_count: u32,
pub city_connection_bonus_latch: bool,
pub linked_transit_service_latch: bool,
pub chairman_buyback_factor: Option<f64>,
pub recent_net_profits: [i64; 3],
pub recent_revenue_totals: [i64; 3],
pub recent_revenue_per_share: [f64; 3],
pub recent_earnings_per_share: [f64; 3],
pub recent_dividend_per_share: [f64; 3],
pub bonds: Vec<BondPosition>,
}
impl Default for CompanyFinanceState {
fn default() -> Self {
Self {
active: true,
years_since_founding: 5,
years_since_last_bankruptcy: 20,
current_cash: 0,
current_company_value: 1_000_000,
support_adjusted_share_price: 25.0,
book_value_per_share: 20.0,
current_fuel_cost: 0,
current_dividend_per_share: 0.0,
board_dividend_ceiling: 2.0,
outstanding_share_count: 20_000,
unassigned_share_count: 10_000,
city_connection_bonus_latch: false,
linked_transit_service_latch: false,
chairman_buyback_factor: None,
recent_net_profits: [10_000, 10_000, 10_000],
recent_revenue_totals: [200_000, 180_000, 160_000],
recent_revenue_per_share: [1.2, 1.1, 1.0],
recent_earnings_per_share: [1.0, 0.9, 0.8],
recent_dividend_per_share: [0.2, 0.2, 0.1],
bonds: Vec::new(),
}
}
}
impl CompanyFinanceState {
pub const BOND_PRINCIPAL: i64 = 500_000;
pub const BOND_TERM_YEARS: u8 = 30;
pub const SHARE_LOT: u32 = 1_000;
pub fn total_debt_principal(&self) -> i64 {
self.bonds.iter().map(|bond| bond.principal.max(0)).sum()
}
pub fn highest_coupon_bond(&self) -> Option<BondPosition> {
self.bonds
.iter()
.copied()
.max_by(|left, right| left.coupon_rate.total_cmp(&right.coupon_rate))
}
pub fn simulate_cash_after_full_bond_repayment(&self) -> i64 {
self.current_cash - self.total_debt_principal()
}
pub fn declare_bankruptcy(&mut self) {
for bond in &mut self.bonds {
bond.principal /= 2;
}
self.current_company_value /= 2;
self.years_since_last_bankruptcy = 0;
}
pub fn issue_bond(&mut self, coupon_rate: f64, count: u32) {
for _ in 0..count {
self.bonds.push(BondPosition {
principal: Self::BOND_PRINCIPAL,
coupon_rate,
years_remaining: Self::BOND_TERM_YEARS,
});
self.current_cash += Self::BOND_PRINCIPAL;
}
}
pub fn repurchase_public_shares(&mut self, share_count: u32, price_per_share: f64) {
let repurchased = share_count.min(self.unassigned_share_count);
self.unassigned_share_count -= repurchased;
self.outstanding_share_count = self.outstanding_share_count.saturating_sub(repurchased);
self.current_cash -= (repurchased as f64 * price_per_share).round() as i64;
}
pub fn issue_public_shares(&mut self, share_count: u32, price_per_share: f64) {
self.outstanding_share_count = self.outstanding_share_count.saturating_add(share_count);
self.unassigned_share_count = self.unassigned_share_count.saturating_add(share_count);
self.current_cash += (share_count as f64 * price_per_share).round() as i64;
}
pub fn set_dividend_rate(&mut self, new_rate: f64) {
self.current_dividend_per_share = new_rate.clamp(0.0, self.board_dividend_ceiling);
}
pub fn read_recent_metric(
&self,
metric: AnnualReportMetric,
years_ago: usize,
) -> Option<f64> {
match metric {
AnnualReportMetric::FuelCost if years_ago == 0 => Some(self.current_fuel_cost as f64),
AnnualReportMetric::BookValuePerShare if years_ago == 0 => Some(self.book_value_per_share),
AnnualReportMetric::NetProfits => self
.recent_net_profits
.get(years_ago)
.copied()
.map(|value| value as f64),
AnnualReportMetric::RevenueAggregate => self
.recent_revenue_totals
.get(years_ago)
.copied()
.map(|value| value as f64),
AnnualReportMetric::RevenuePerShare => {
self.recent_revenue_per_share.get(years_ago).copied()
}
AnnualReportMetric::EarningsPerShare => {
self.recent_earnings_per_share.get(years_ago).copied()
}
AnnualReportMetric::DividendPerShare => {
self.recent_dividend_per_share.get(years_ago).copied()
}
_ => None,
}
}
pub fn read_recent_metric_window(
&self,
metric: AnnualReportMetric,
years: usize,
) -> Vec<f64> {
(0..years)
.filter_map(|years_ago| self.read_recent_metric(metric, years_ago))
.collect()
}
pub fn weighted_recent_metric(
&self,
metric: AnnualReportMetric,
weights: &[f64],
) -> Option<f64> {
let mut numerator = 0.0;
let mut denominator = 0.0;
for (years_ago, weight) in weights.iter().copied().enumerate() {
let value = self.read_recent_metric(metric, years_ago)?;
numerator += value * weight;
denominator += weight;
}
(denominator > 0.0).then_some(numerator / denominator)
}
pub fn apply_annual_decision(&mut self, decision: &AnnualFinanceDecision) {
match *decision {
AnnualFinanceDecision::NoAction => {}
AnnualFinanceDecision::DeclareBankruptcy { .. } => self.declare_bankruptcy(),
AnnualFinanceDecision::IssueBond {
count,
principal_per_bond: _,
term_years: _,
} => {
let coupon = self
.highest_coupon_bond()
.map(|bond| bond.coupon_rate)
.unwrap_or(0.10);
self.issue_bond(coupon, count);
}
AnnualFinanceDecision::RepurchasePublicShares {
share_count,
price_per_share,
} => self.repurchase_public_shares(share_count, price_per_share),
AnnualFinanceDecision::IssuePublicShares {
share_count_per_tranche,
tranche_count,
price_per_share,
} => self.issue_public_shares(
share_count_per_tranche.saturating_mul(tranche_count),
price_per_share,
),
AnnualFinanceDecision::AdjustDividend { new_rate, .. } => {
self.set_dividend_rate(new_rate);
}
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct FinanceSnapshot {
pub policy: AnnualFinancePolicy,
pub company: CompanyFinanceState,
}
impl FinanceSnapshot {
pub fn evaluate(&self) -> FinanceOutcome {
let evaluation = evaluate_annual_finance_policy_detailed(&self.policy, &self.company);
let mut post_company = self.company.clone();
post_company.apply_annual_decision(&evaluation.decision);
FinanceOutcome {
evaluation,
post_company,
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct FinanceOutcome {
pub evaluation: AnnualFinanceEvaluation,
pub post_company: CompanyFinanceState,
}
pub fn evaluate_annual_finance_policy(
policy: &AnnualFinancePolicy,
company: &CompanyFinanceState,
) -> AnnualFinanceDecision {
evaluate_annual_finance_policy_detailed(policy, company).decision
}
pub fn evaluate_annual_finance_policy_detailed(
policy: &AnnualFinancePolicy,
company: &CompanyFinanceState,
) -> AnnualFinanceEvaluation {
if !company.active {
return AnnualFinanceEvaluation::no_action();
}
if should_bankrupt_early(policy, company) {
return AnnualFinanceEvaluation {
decision: AnnualFinanceDecision::DeclareBankruptcy {
reason: BankruptcyReason::EarlyStress,
},
..AnnualFinanceEvaluation::no_action()
};
}
if let Some(evaluation) = issue_bond_evaluation(policy, company) {
return evaluation;
}
if let Some(evaluation) = repurchase_evaluation(policy, company) {
return evaluation;
}
if should_bankrupt_deep_distress(policy, company) {
return AnnualFinanceEvaluation {
decision: AnnualFinanceDecision::DeclareBankruptcy {
reason: BankruptcyReason::DeepDistress,
},
..AnnualFinanceEvaluation::no_action()
};
}
if let Some(evaluation) = issue_stock_evaluation(policy, company) {
return evaluation;
}
if let Some(evaluation) = dividend_evaluation(policy, company) {
return evaluation;
}
AnnualFinanceEvaluation::no_action()
}
fn should_bankrupt_early(policy: &AnnualFinancePolicy, company: &CompanyFinanceState) -> bool {
if policy.annual_mode != 0x0c || !policy.bankruptcy_allowed {
return false;
}
if company.years_since_last_bankruptcy < 13 || company.years_since_founding < 4 {
return false;
}
let current_revenue = company.recent_revenue_totals[0];
let stress_ladder: i64 = if current_revenue < 120_000 {
-600_000
} else if current_revenue < 230_000 {
-1_100_000
} else if current_revenue < 340_000 {
-1_600_000
} else {
-2_000_000
};
let failed_profit_years = company
.recent_net_profits
.iter()
.filter(|profit| **profit <= 0)
.count();
let net_profit_sum: i64 = company.recent_net_profits.iter().sum();
let share_price_floor = if failed_profit_years == 3 { 20.0 } else { 15.0 };
let fuel_gate = (stress_ladder.abs() as f64 * 0.08).round() as i64;
failed_profit_years >= 2
&& net_profit_sum <= -60_000
&& company.support_adjusted_share_price >= share_price_floor
&& company.current_fuel_cost >= fuel_gate
}
fn should_bankrupt_deep_distress(
policy: &AnnualFinancePolicy,
company: &CompanyFinanceState,
) -> bool {
policy.bankruptcy_allowed
&& company.current_cash < -300_000
&& company.years_since_founding >= 3
&& company.years_since_last_bankruptcy >= 5
&& company.recent_net_profits.iter().all(|profit| *profit <= -20_000)
}
fn issue_bond_evaluation(
policy: &AnnualFinancePolicy,
company: &CompanyFinanceState,
) -> Option<AnnualFinanceEvaluation> {
if !policy.bond_issuance_allowed {
return None;
}
let simulated_cash = company.simulate_cash_after_full_bond_repayment();
let target_floor = if company.linked_transit_service_latch {
-30_000
} else {
-250_000
};
if simulated_cash >= target_floor {
return None;
}
let shortfall = (target_floor - simulated_cash).max(0) as u64;
let count = shortfall.div_ceil(CompanyFinanceState::BOND_PRINCIPAL as u64) as u32;
let issued_principal = count.max(1) as i64 * CompanyFinanceState::BOND_PRINCIPAL;
let debt_restructure = DebtRestructureSummary {
retired_principal: 0,
issued_principal,
};
Some(AnnualFinanceEvaluation {
decision: AnnualFinanceDecision::IssueBond {
count: count.max(1),
principal_per_bond: CompanyFinanceState::BOND_PRINCIPAL,
term_years: CompanyFinanceState::BOND_TERM_YEARS,
},
debt_news: debt_restructure.classify(),
debt_restructure,
..AnnualFinanceEvaluation::no_action()
})
}
fn repurchase_evaluation(
policy: &AnnualFinancePolicy,
company: &CompanyFinanceState,
) -> Option<AnnualFinanceEvaluation> {
if !policy.stock_actions_allowed
|| !company.city_connection_bonus_latch
|| matches!(policy.growth_setting, GrowthSetting::DividendSuppressed)
|| company.unassigned_share_count < CompanyFinanceState::SHARE_LOT
|| company.current_company_value < 800_000
{
return None;
}
let mut factor = company.chairman_buyback_factor.unwrap_or(1.0);
if matches!(policy.growth_setting, GrowthSetting::ExpansionBias) {
factor *= 1.6;
}
let batch = CompanyFinanceState::SHARE_LOT;
let affordability_gate = company.support_adjusted_share_price * factor * batch as f64 * 1.2;
if company.current_cash < affordability_gate.round() as i64 {
return None;
}
Some(AnnualFinanceEvaluation {
decision: AnnualFinanceDecision::RepurchasePublicShares {
share_count: batch,
price_per_share: company.support_adjusted_share_price,
},
repurchased_share_count: batch,
..AnnualFinanceEvaluation::no_action()
})
}
fn issue_stock_evaluation(
policy: &AnnualFinancePolicy,
company: &CompanyFinanceState,
) -> Option<AnnualFinanceEvaluation> {
if !policy.build_103_plus
|| !policy.stock_actions_allowed
|| !policy.bond_issuance_allowed
|| company.bonds.len() < 2
|| company.years_since_founding < 1
|| company.support_adjusted_share_price < 22.0
|| company.book_value_per_share <= 0.0
{
return None;
}
let highest_coupon = company.highest_coupon_bond()?;
if company.current_cash >= highest_coupon.principal + policy.stock_issue_cash_buffer {
return None;
}
let mut tranche =
((company.outstanding_share_count / 10) / CompanyFinanceState::SHARE_LOT)
* CompanyFinanceState::SHARE_LOT;
tranche = tranche.max(2_000);
while tranche >= CompanyFinanceState::SHARE_LOT
&& company.support_adjusted_share_price * tranche as f64 > 55_000.0
{
tranche -= CompanyFinanceState::SHARE_LOT;
}
if tranche < CompanyFinanceState::SHARE_LOT {
return None;
}
let price_to_book = company.support_adjusted_share_price / company.book_value_per_share;
if price_to_book < required_price_to_book_ratio(highest_coupon.coupon_rate) {
return None;
}
Some(AnnualFinanceEvaluation {
decision: AnnualFinanceDecision::IssuePublicShares {
share_count_per_tranche: tranche,
tranche_count: 2,
price_per_share: company.support_adjusted_share_price,
},
issued_share_count: tranche * 2,
..AnnualFinanceEvaluation::no_action()
})
}
fn dividend_evaluation(
policy: &AnnualFinancePolicy,
company: &CompanyFinanceState,
) -> Option<AnnualFinanceEvaluation> {
if !policy.dividends_allowed || company.years_since_founding < 2 {
return None;
}
let weighted_target = company
.weighted_recent_metric(AnnualReportMetric::EarningsPerShare, &[3.0, 2.0, 1.0])
.unwrap_or(0.0);
let mut target = weighted_target.max(0.0);
if company.unassigned_share_count < CompanyFinanceState::SHARE_LOT
&& company.outstanding_share_count > 0
&& company.current_cash > 0
{
target += company.current_cash as f64 / company.outstanding_share_count as f64;
}
target = match policy.growth_setting {
GrowthSetting::Neutral => target,
GrowthSetting::ExpansionBias => target * 0.66,
GrowthSetting::DividendSuppressed => 0.0,
};
target = quantize_tenths(target.clamp(0.0, company.board_dividend_ceiling));
if (target - company.current_dividend_per_share).abs() <= 0.1 {
return None;
}
Some(AnnualFinanceEvaluation {
decision: AnnualFinanceDecision::AdjustDividend {
old_rate: company.current_dividend_per_share,
new_rate: target,
},
..AnnualFinanceEvaluation::no_action()
})
}
fn required_price_to_book_ratio(coupon_rate: f64) -> f64 {
if coupon_rate <= 0.07 {
1.3
} else if coupon_rate <= 0.08 {
1.2
} else if coupon_rate <= 0.09 {
1.1
} else if coupon_rate <= 0.10 {
0.95
} else if coupon_rate <= 0.11 {
0.8
} else if coupon_rate <= 0.12 {
0.62
} else if coupon_rate <= 0.13 {
0.5
} else {
0.35
}
}
fn quantize_tenths(value: f64) -> f64 {
(value * 10.0).round() / 10.0
}
#[cfg(test)]
mod tests {
use super::*;
fn base_policy() -> AnnualFinancePolicy {
AnnualFinancePolicy::default()
}
fn base_company() -> CompanyFinanceState {
CompanyFinanceState {
bonds: vec![
BondPosition {
principal: 400_000,
coupon_rate: 0.10,
years_remaining: 10,
},
BondPosition {
principal: 300_000,
coupon_rate: 0.12,
years_remaining: 12,
},
],
..CompanyFinanceState::default()
}
}
#[test]
fn early_bankruptcy_precedes_other_actions() {
let policy = base_policy();
let company = CompanyFinanceState {
current_fuel_cost: 90_000,
support_adjusted_share_price: 21.0,
recent_net_profits: [-30_000, -25_000, -20_000],
recent_revenue_totals: [150_000, 140_000, 130_000],
city_connection_bonus_latch: true,
linked_transit_service_latch: true,
..base_company()
};
let decision = evaluate_annual_finance_policy(&policy, &company);
assert_eq!(
decision,
AnnualFinanceDecision::DeclareBankruptcy {
reason: BankruptcyReason::EarlyStress,
}
);
}
#[test]
fn bond_issue_precedes_stock_issue_when_cash_window_fails() {
let policy = base_policy();
let company = CompanyFinanceState {
current_cash: -900_000,
support_adjusted_share_price: 30.0,
book_value_per_share: 20.0,
linked_transit_service_latch: true,
recent_net_profits: [20_000, 10_000, 5_000],
..base_company()
};
let decision = evaluate_annual_finance_policy(&policy, &company);
assert_eq!(
decision,
AnnualFinanceDecision::IssueBond {
count: 4,
principal_per_bond: CompanyFinanceState::BOND_PRINCIPAL,
term_years: CompanyFinanceState::BOND_TERM_YEARS,
}
);
}
#[test]
fn stock_issue_checks_liquidity_before_valuation() {
let policy = AnnualFinancePolicy {
bond_issuance_allowed: false,
dividends_allowed: false,
..base_policy()
};
let company = CompanyFinanceState {
current_cash: 500_000,
support_adjusted_share_price: 30.0,
book_value_per_share: 20.0,
recent_net_profits: [40_000, 30_000, 20_000],
recent_revenue_totals: [250_000, 240_000, 230_000],
..base_company()
};
let decision = evaluate_annual_finance_policy(&policy, &company);
assert_eq!(decision, AnnualFinanceDecision::NoAction);
}
#[test]
fn stock_issue_emits_two_tranches_when_gates_pass() {
let policy = AnnualFinancePolicy {
dividends_allowed: false,
..base_policy()
};
let company = CompanyFinanceState {
current_cash: 100_000,
support_adjusted_share_price: 27.5,
book_value_per_share: 20.0,
outstanding_share_count: 60_000,
recent_net_profits: [40_000, 30_000, 20_000],
recent_revenue_totals: [250_000, 240_000, 230_000],
bonds: vec![
BondPosition {
principal: 150_000,
coupon_rate: 0.12,
years_remaining: 12,
},
BondPosition {
principal: 10_000,
coupon_rate: 0.10,
years_remaining: 10,
},
],
..base_company()
};
let decision = evaluate_annual_finance_policy(&policy, &company);
assert_eq!(
decision,
AnnualFinanceDecision::IssuePublicShares {
share_count_per_tranche: 2_000,
tranche_count: 2,
price_per_share: 27.5,
}
);
}
#[test]
fn recent_metric_reader_exposes_report_lanes() {
let company = CompanyFinanceState {
current_fuel_cost: 12_345,
recent_net_profits: [11_000, 22_000, 33_000],
recent_revenue_totals: [101_000, 102_000, 103_000],
recent_revenue_per_share: [1.6, 1.5, 1.4],
recent_earnings_per_share: [1.3, 1.2, 1.1],
recent_dividend_per_share: [0.4, 0.3, 0.2],
book_value_per_share: 19.5,
..base_company()
};
assert_eq!(
company.read_recent_metric(AnnualReportMetric::NetProfits, 1),
Some(22_000.0)
);
assert_eq!(
company.read_recent_metric(AnnualReportMetric::RevenuePerShare, 2),
Some(1.4)
);
assert_eq!(
company.read_recent_metric(AnnualReportMetric::FuelCost, 0),
Some(12_345.0)
);
assert_eq!(
company.read_recent_metric(AnnualReportMetric::BookValuePerShare, 0),
Some(19.5)
);
assert_eq!(
company.weighted_recent_metric(AnnualReportMetric::EarningsPerShare, &[3.0, 2.0, 1.0]),
Some(1.2333333333333334)
);
}
#[test]
fn debt_news_classifies_borrow_and_paydown_paths() {
assert_eq!(
DebtRestructureSummary {
retired_principal: 500_000,
issued_principal: 500_000,
}
.classify(),
Some(DebtNewsOutcome::RefinanceOnly)
);
assert_eq!(
DebtRestructureSummary {
retired_principal: 300_000,
issued_principal: 500_000,
}
.classify(),
Some(DebtNewsOutcome::RefinanceAndBorrow)
);
assert_eq!(
DebtRestructureSummary {
retired_principal: 500_000,
issued_principal: 300_000,
}
.classify(),
Some(DebtNewsOutcome::RefinanceAndPayDown)
);
}
#[test]
fn detailed_evaluation_carries_share_and_debt_side_effects() {
let policy = base_policy();
let company = CompanyFinanceState {
current_cash: -900_000,
support_adjusted_share_price: 30.0,
book_value_per_share: 20.0,
linked_transit_service_latch: true,
recent_net_profits: [20_000, 10_000, 5_000],
..base_company()
};
let evaluation = evaluate_annual_finance_policy_detailed(&policy, &company);
assert_eq!(
evaluation.decision,
AnnualFinanceDecision::IssueBond {
count: 4,
principal_per_bond: CompanyFinanceState::BOND_PRINCIPAL,
term_years: CompanyFinanceState::BOND_TERM_YEARS,
}
);
assert_eq!(
evaluation.debt_news,
Some(DebtNewsOutcome::NewBorrowingOnly)
);
assert_eq!(evaluation.debt_restructure.issued_principal, 2_000_000);
assert_eq!(evaluation.repurchased_share_count, 0);
assert_eq!(evaluation.issued_share_count, 0);
}
#[test]
fn snapshot_evaluation_applies_post_state_transition() {
let snapshot = FinanceSnapshot {
policy: AnnualFinancePolicy {
dividends_allowed: false,
..base_policy()
},
company: CompanyFinanceState {
current_cash: 100_000,
support_adjusted_share_price: 27.5,
book_value_per_share: 20.0,
outstanding_share_count: 60_000,
recent_net_profits: [40_000, 30_000, 20_000],
recent_revenue_totals: [250_000, 240_000, 230_000],
bonds: vec![
BondPosition {
principal: 150_000,
coupon_rate: 0.12,
years_remaining: 12,
},
BondPosition {
principal: 10_000,
coupon_rate: 0.10,
years_remaining: 10,
},
],
..base_company()
},
};
let outcome = snapshot.evaluate();
assert_eq!(
outcome.evaluation.decision,
AnnualFinanceDecision::IssuePublicShares {
share_count_per_tranche: 2_000,
tranche_count: 2,
price_per_share: 27.5,
}
);
assert_eq!(outcome.evaluation.issued_share_count, 4_000);
assert_eq!(outcome.post_company.outstanding_share_count, 64_000);
assert_eq!(outcome.post_company.unassigned_share_count, 14_000);
assert_eq!(outcome.post_company.current_cash, 210_000);
}
#[test]
fn dividend_target_is_quantized_and_clamped() {
let policy = AnnualFinancePolicy {
bond_issuance_allowed: false,
..base_policy()
};
let company = CompanyFinanceState {
current_cash: 20_000,
board_dividend_ceiling: 0.9,
current_dividend_per_share: 0.2,
unassigned_share_count: 500,
outstanding_share_count: 10_000,
recent_earnings_per_share: [1.4, 1.1, 0.9],
..base_company()
};
let decision = evaluate_annual_finance_policy(&policy, &company);
assert_eq!(
decision,
AnnualFinanceDecision::AdjustDividend {
old_rate: 0.2,
new_rate: 0.9,
}
);
}
#[test]
fn bankruptcy_mutator_halves_bond_principal() {
let mut company = base_company();
company.current_company_value = 900_000;
company.years_since_last_bankruptcy = 25;
company.apply_annual_decision(&AnnualFinanceDecision::DeclareBankruptcy {
reason: BankruptcyReason::DeepDistress,
});
assert_eq!(company.bonds[0].principal, 200_000);
assert_eq!(company.bonds[1].principal, 150_000);
assert_eq!(company.current_company_value, 450_000);
assert_eq!(company.years_since_last_bankruptcy, 0);
}
}

View file

@ -1,3 +1,5 @@
pub mod finance;
use std::collections::BTreeMap; use std::collections::BTreeMap;
use std::fmt; use std::fmt;
use std::fs::File; use std::fs::File;

View file

@ -64,7 +64,7 @@ Current bounded compatibility impact:
`0x00408f70`, because that target sums raw site cache `+0x12` rather than the weighted `+0x0e` `0x00408f70`, because that target sums raw site cache `+0x12` rather than the weighted `+0x0e`
or promoted `+0x16` lanes or promoted `+0x16` lanes
Highest-value open edge: Latest finance closure:
- The remaining semantic meaning of the annual finance policy branches is now narrower: - The remaining semantic meaning of the annual finance policy branches is now narrower:
stat-family `0x2329/0x1d` is now bounded as current `Book Value Per Share` in the stock-issue stat-family `0x2329/0x1d` is now bounded as current `Book Value Per Share` in the stock-issue
@ -72,27 +72,80 @@ Highest-value open edge:
bond-rate-versus-support ladder, the broader support-adjusted share-price or public-support lane bond-rate-versus-support ladder, the broader support-adjusted share-price or public-support lane
is now bounded through `company_compute_public_support_adjusted_share_price_scalar` `0x00424fd0`, is now bounded through `company_compute_public_support_adjusted_share_price_scalar` `0x00424fd0`,
and its immediate feeder is now bounded as and its immediate feeder is now bounded as
`company_compute_cached_recent_per_share_performance_subscore` `0x004248d0`, which weighs recent `company_compute_cached_recent_per_share_performance_subscore` `0x004248d0`, which uses an
`Revenue Per Share`, `Earnings Per Share`, and `Dividend Per Share` lanes `0x1e/0x1f/0x20` explicit recent-history ladder: the current partial year is weighted by `(5 * [world+0x0f]) - 5`,
against current `Book Value Per Share`, while `0x21` now routes through the prior four full years use `48/36/24/12` for `Earnings Per Share` and `Revenue Per Share`
lanes `0x1f/0x1e`, and the adjacent `Dividend Per Share` non-decline trend comparisons on lane
`0x20` use weights `9/8/7/6`, all before the tail folds those lanes against current `Book Value
Per Share`, while `0x21` now routes through
`company_compute_five_year_weighted_shareholder_return` `0x004246b0` and its paired hidden `company_compute_five_year_weighted_shareholder_return` `0x004246b0` and its paired hidden
shareholder-payout lane `0x23`, shareholder-payout lane `0x23`,
raw slot `0x09` aligns with the Income Statement fuel-cost lane surfaced by tooltip `1309`, and raw slot `0x09` aligns with the Income Statement fuel-cost lane surfaced by tooltip `1309`, and
derived slot `0x2b` now reads as the rolling net-profits lane. The main remaining gap is the derived slot `0x2b` now reads as the rolling net-profits lane. The exact stock-issue cutoff
exact weight schedule and gate ordering used inside the already-bounded annual bankruptcy, ordering is now closed, and the current grounded read is:
debt, repurchase, stock-issue, and dividend branches. The current strongest read is now:
bankruptcy = first the year, toggle, and cooldown gates, then the three-year scan, then the bankruptcy = first the year, toggle, and cooldown gates, then the three-year scan, then the
revenue-band-selected cash-and-debt ladder, then the support-adjusted share-price threshold, revenue-band-selected cash-and-debt ladder, then the support-adjusted share-price threshold,
then the fuel-cost lane and net-profit accumulator; the later deep-distress fallback then then the fuel-cost lane and net-profit accumulator; the later deep-distress fallback then
re-checks cash below `-300000` plus the first three recent profit years at or below `-20000`. re-checks cash below `-300000` plus the first three recent profit years at or below `-20000`.
Stock issuance = first the bond/stock toggle gates, then the outstanding-share tranche sizing, Stock issuance = first the bond/stock toggles, live-bond count, and founding-age gates, then
then the `55000` proceeds cap, then the support-adjusted price-to-book screen against the tranche sizing from outstanding shares, then the `55000` proceeds-cap trim, then the pressured
highest live coupon band. Dividend adjustment = first the toggle and age gates, then the support-adjusted share-price recompute and price-to-book derivation, followed by the tested
weighted recent-profit-per-share target, then the small-unassigned-share cash supplement, then gates in fixed order: share-price floor `22`, proceeds floor `55000`, current cash
the board ceiling clamp. The remaining gap is now mostly the near-cutoff ordering inside the `0x2329/0x0d` against highest-coupon principal plus `5000`, one later stock-issue cooldown gate
stock-issue lane, and the current strongest read places the liquidity gate at `0x2329/0x0d` driven by the current issue mixed-radix calendar tuple at `[company+0x16b/+0x16f]` versus the
before the valuation gate at `0x2329/0x1d`. The dividend-ceiling clamp is already separately active world absolute calendar counter `[world+0x15]`, and only then the coupon-versus-price-to-book ladder
bounded on the dividend branch. That is now a final precedence question, not a missing `0.07/1.3 .. 0.14/0.35`. Dividend
ownership gap in the annual finance verbs themselves. The debt-news tail is also now explicit: adjustment = first the toggle and age gates, then the weighted recent-profit-per-share target,
then the small-unassigned-share cash supplement, then the board ceiling clamp. The debt-news tail
is also now explicit:
it compares total retired versus newly issued principal to pick `2882..2886`, and the it compares total retired versus newly issued principal to pick `2882..2886`, and the
stock-buyback tail separately publishes `2887` from the accumulated repurchased-share count. stock-buyback tail separately publishes `2887` from the accumulated repurchased-share count.
Current finance closure detail:
- The stock-issue cooldown stamp is now structurally closed:
`[company+0x16b/+0x16f]` holds the current issue calendar-point tuple,
`[company+0x173/+0x177]` preserves the prior tuple,
and the cooldown gate converts the current tuple through `0x0051d3c0` before comparing against
the active world absolute calendar counter `[world+0x15]`. The tuple model is no longer just
“calendar-like”: current local arithmetic now bounds it as the same mixed-radix
`12 x 28 x 3 x 60` family unpacked by `0x0051d460`.
- The recent per-share performance feeder `0x004248d0` is now tighter than before:
its four tail lanes are `EPS * 10`, `Revenue Per Share`, a dividend-strength lane derived from
the weighted non-decline ratio on slot `0x20`, and current `Book Value Per Share` from slot
`0x1d`; base lane weights are `40/10/20/30`, startup companies ramp those weights with the
age table `0/0/0/100 -> 25/25/35/100 -> 50/50/65/100 -> 75/75/85/100 -> 100/100/100/100`,
the strongest lane is boosted by `1.25` while the weakest is reduced by `0.8`, the bounded
intermediate `[company+0x0d19]` uses `((difficulty + 1.0) / 2.0)` with a floor at `0.5`,
and the final cached score uses the raw difficulty table
`0.8/0.9/1.0/1.1/1.2/0.9/0.95/1.0` plus the post-bankruptcy smoothing factor.
- The broader support-adjusted share-price consumer `0x00424fd0` is tighter now too:
it starts from that recent per-share feeder, can interpolate it against the older support field
`[company+0x57]` on the young-company path using one capped progress term from
`[world+0x15]`, `[company+0x0d07]`, and founding year `[company+0x157]`, clamps the caller's
share-count pressure term to `[-0.2, 0.2]`, can refresh mutable support field `[company+0x4f]`
from that staged pressure, derives one share-count growth term from `(shares / 20000)^0.33`,
then runs the resulting support scalar through the later threshold ladder
`0.6 / 0.45 / 0.3 / 1.7 / 2.5 / 4.0 / 6.0` before multiplying by scenario issue `0x37` and
rounding into the cached share-price lane `[company+0x0d7b]`.
- The surrounding finance issue split is now much tighter:
`0x00425320` is the bounded company `Credit Rating` score used by the shell debt summary and the
bond-offer rejection path on localized id `974`, with raw issue slot `0x38` folded in at the
tail; `0x00424580` is the explicit `Prime Rate` lane fed by raw issue slot `0x39`; and
`0x00424fd0` then multiplies the equity-side share-price scalar by issue slot `0x37`.
So the strongest current read is:
`0x38 = debt-market / creditworthiness`,
`0x39 = prime-rate / market-rate pressure`,
`0x37 = broader equity-market / investor confidence in company or chairman performance`.
- The raw formula side is now mostly closed, and what remains is mostly whether RT3 exposed one exact
UI caption for issue `0x37` beyond the already-grounded investor-performance strings. The
negative results now sharpen that last edge too: the nearby `0x460a90` / `0x473620`
registration blocks are camera-view and related shell-command families, not finance issue
owners; and the editor-side `Stock Prices` label belongs to a separate float-tuning block
`[state+0x0bde..0x0bf6]`, not the issue table behind `0x37`. The shell-resource side is now
tighter too: extracting `CompanyDetail.win` from `rt3_2WIN.PK4` did not uncover any separate
plain-English finance caption or investor label for this lane, and the recovered shell owner path
already routes section-0 through the dynamic-text widget `0x947f` plus formatter
`shell_format_company_governance_and_economy_status_panel` `0x004e5cf0`. So the strongest
current UI-facing text for issue `0x37` remains the investor-attitude sentence family
`1217` and `3048/3049`, not one standalone caption row like `Credit Rating:` or `Prime Rate:`.

View file

@ -34,7 +34,10 @@ Current grounded runtime chain:
- `placed_structure_refresh_local_service_score_bundle` - `placed_structure_refresh_local_service_score_bundle`
- `placed_structure_query_candidate_local_service_metrics` - `placed_structure_query_candidate_local_service_metrics`
- `placed_structure_count_candidates_with_local_service_metrics` - `placed_structure_count_candidates_with_local_service_metrics`
- `placed_structure_get_nth_candidate_id_with_local_service_metrics`
- `placed_structure_query_cached_express_service_class_score` - `placed_structure_query_cached_express_service_class_score`
- `placed_structure_refresh_candidate_local_service_comparison_cache_against_peer_site`
- `placed_structure_select_best_candidate_id_by_local_service_score`
Current grounded shell-side consumers: Current grounded shell-side consumers:
@ -51,14 +54,19 @@ What this note is for:
- Per-site local service tables and caps - Per-site local service tables and caps
- Station-detail and station-list read-side summaries - Station-detail and station-list read-side summaries
Highest-value open edge: Resolved edge:
- The deeper gameplay meaning of the per-site local service tables is now mostly about whether - The per-site local service query family is now bounded as predominantly read-side. Above the
those scores stay confined to placement, overlay, and station-summary presentation through the mutation and rebuild lane, the grounded helpers are:
query helpers, or whether the same query outputs also feed live cargo-economy filter flags, `placed_structure_query_candidate_local_service_metrics`,
per-site service-cap rebuilds, or nearby-structure behavior beyond the currently bounded shell `placed_structure_count_candidates_with_local_service_metrics`,
summaries. The grounded chain strongly suggests the read-side path is dominant, the query `placed_structure_get_nth_candidate_id_with_local_service_metrics`,
helpers themselves still read as read-only consumers, and the only grounded mutation bridge is `placed_structure_query_cached_express_service_class_score`,
the collection-wide cargo-economy filter refresh. The remaining open question is whether that `placed_structure_refresh_candidate_local_service_comparison_cache_against_peer_site`, and
filter refresh is the full policy sink for the local service tables or whether an additional `placed_structure_select_best_candidate_id_by_local_service_score`.
mutation-side writer exists outside the local corpus. - Their grounded callers are shell summaries, shell detail rows and popups, the world preview
overlay, one company-side autoroute score cache rebuild, and one peer-site comparison cache.
None of the newly closed callers acts as a policy writer back into candidate or site state.
- The only grounded mutation bridge on this edge remains
`structure_candidate_collection_refresh_cargo_economy_filter_flags`, which writes the derived
enabled or filtered state into `[entry+0x56]` before rebuilding the visible counts.

View file

@ -31,9 +31,9 @@ What this note is for:
- GameSpy-facing callback and live-route semantics - GameSpy-facing callback and live-route semantics
- Selector-view refresh, retry, and probe state - Selector-view refresh, retry, and probe state
Highest-value open edge: Latest local closure:
- The remaining multiplayer edge is narrower now: - The remaining multiplayer branch is narrower now:
the status-route callback vector is bounded through selector-text or averaged-sample publication, the status-route callback vector is bounded through selector-text or averaged-sample publication,
control-id-list seeding, scalar-query forwarding, and the validated cookie or extended-payload control-id-list seeding, scalar-query forwarding, and the validated cookie or extended-payload
callbacks. The selector-view sidecar is tighter too: `[entry+0x80]` now reads as the averaged callbacks. The selector-view sidecar is tighter too: `[entry+0x80]` now reads as the averaged
@ -49,6 +49,10 @@ Highest-value open edge:
`0x5906f0` tears down the decoded schema dictionary rooted at `[this+0x08]`, `0x590540/0x5905a0` `0x5906f0` tears down the decoded schema dictionary rooted at `[this+0x08]`, `0x590540/0x5905a0`
acquire and release refcounted shared schema strings through the global pool, and `0x5908c0` acquire and release refcounted shared schema strings through the global pool, and `0x5908c0`
now reads as the live receive/decode state machine serviced by `0x591290` in table states `2/3`. now reads as the live receive/decode state machine serviced by `0x591290` in table states `2/3`.
The owner-callback mode split above that runtime is tighter now too: mode `0` comes from the
generic append-notify lane `0x590370`, mode `1` from compact upsert `0x590d00`, mode `2` from
generic remove-notify `0x590430`, and modes `6/5/3` from the receive/decode state machine
`0x5908c0`.
The higher transport bring-up split is tighter too: `0x596090` now clearly constructs The higher transport bring-up split is tighter too: `0x596090` now clearly constructs
`[transport+0xba4]` through `0x5905e0` with owner callback `0x595a40`, then seeds the local `[transport+0xba4]` through `0x5905e0` with owner callback `0x595a40`, then seeds the local
field-cache family `[transport+0x1724]` through `0x5a08f0` with helper `0x595b60`, and then field-cache family `[transport+0x1724]` through `0x5a08f0` with helper `0x595b60`, and then
@ -83,15 +87,31 @@ Highest-value open edge:
`+0x0c/+0x10/+0x18` metadata triplet and the replay modes later consume the pointer through `+0x0c/+0x10/+0x18` metadata triplet and the replay modes later consume the pointer through
`0x5933a0`. The negative result is stronger too: local text-side xrefs still show no direct `0x5933a0`. The negative result is stronger too: local text-side xrefs still show no direct
store to `[transport+0x1778]`, and a wider sweep also failed to show any obvious `lea`-based store to `[transport+0x1778]`, and a wider sweep also failed to show any obvious `lea`-based
replay-band writer. So the sidecar writer remains upstream of this leaf capacity publisher. The replay-band writer. The owned request lifecycle now tightens that further too:
`0x593330/0x593370/0x593380/0x5934e0/0x5933a0` fully own `[transport+0x1780]`, `0x1784`, and
`0x1788`, while still leaving `[transport+0x1778]` outside that family; the neighboring active
opcode reset path `0x5929a0` also only targets `[transport+0x17fc]`. Constructor and teardown
passes around `0x596090/0x5961b0/0x5962e0` tighten that negative result further: those owners
seed or clear the neighboring replay-band fields while still leaving `[transport+0x1778]`
untouched. A full-binary literal-offset sweep tightens it further still: the only direct
`0x1778` hit in `RT3.exe` is the read in `0x595bc0`. One nearby ambiguity is now closed too: the
mode-`5` mirror path in `0x595a40` and `0x595e10` does not seed `[transport+0x1778]`; it writes
`[transport+0x54]` and mirrors the same staged route companion dword only into queue-side slot
`[transport+0x1724+0x24]` through `0x005a0940`. So the sidecar writer remains upstream of this
leaf capacity publisher. The
payload split is tighter too: payload split is tighter too:
`0x592ae0` now grounds opcode `2` as a seven-dword descriptor payload with an owned string slot `0x592ae0` now grounds opcode `2` as a seven-dword descriptor payload with an owned string slot
at `+0x08`, so live mode supplies a populated payload while modes `3` and `5` deliberately at `+0x08`, so live mode supplies a populated payload while modes `3` and `5` deliberately
enqueue an all-zero payload and reuse only the sidecar metadata in the wrapper. Those two modes enqueue an all-zero payload and reuse only the sidecar metadata in the wrapper. Those two modes
are tighter now too: they are not generic replay guesses, they are the live receive-state owner are tighter now too: they are not generic replay guesses, they are the live receive-state owner
callbacks emitted by `0x5911e0 -> 0x5908c0`. So they are best read as delayed metadata replays callbacks emitted by `0x5911e0 -> 0x5908c0`. So they are best read as delayed metadata replays
over one cached work record, not over a standalone custom cache object. The producer side is over one cached work record, not over a standalone custom cache object. The capacity owner split
tighter now too: callback-table attach, itself is tighter now too: `0x595bc0` only does real work for modes
`0`, `3`, and `5`; the upstream table still delivers modes `1`, `2`, and `6`, but those are
explicit no-ops in the capacity leaf. So the callback-owner edge is now effectively closed: the
route-callback table feeds the capacity publisher only through the live append lane and the two
replay lanes, while the remaining missing piece is still the upstream sidecar producer, not the
owner-mode wiring. The producer side is tighter now too: callback-table attach,
bound-route requests, selector-text route requests, and the type-`9` text fastpath all stage bound-route requests, selector-text route requests, and the type-`9` text fastpath all stage
that same `+0x0c/+0x10/+0x18` triplet through `0x5934e0`, so the capacity replay sidecar is that same `+0x0c/+0x10/+0x18` triplet through `0x5934e0`, so the capacity replay sidecar is
clearly reusing one broader transport work-record family. The generic owner-callback split above that family is tighter now clearly reusing one broader transport work-record family. The generic owner-callback split above that family is tighter now
@ -125,34 +145,52 @@ Highest-value open edge:
`0x5904d0` family, not a vague collection clear. `0x5904d0` family, not a vague collection clear.
The callback-table attach side now The callback-table attach side now
constrains the same work-record metadata family a little further too: `0x593650` deliberately constrains the same work-record metadata family a little further too: `0x593650` deliberately
duplicates one caller metadata dword into both work fields `+0x0c` and `+0x18`, while preserving duplicates its first caller metadata dword into both work fields `+0x0c` and `+0x10`, while
the remaining caller callback function pointer in `+0x10`. The lower opcode wrappers are tighter carrying the second caller metadata dword in `+0x18`. The lower opcode wrappers are tighter now
now too: both `0x592a40` and `0x592a70` consume that staged triplet in the order `( callback fn too: `0x592a40` turned out not to be the real 0x08-byte binding leaf at all, but the explicit
+0x10, callback companion +0x18, drain context id +0x0c )`. So the replay-side triplet is opcode-`1` trigger wrapper whose constructor is a no-op and whose active-side service is
clearly a broader transport callback-wrapper family, not one fixed route-only tuple. The type-`9` `0x5913c0`. The real lower binding leaf is `0x592c40`, which builds the local `0x08`-byte
text fastpath confirms the same split from the other side too: `0x593d00` only emits the payload later deep-copied by the explicit opcode-`5` family `0x591540/0x591570/0x591580`. The
follow-on callback lane when work field `+0x10` is nonnull, and then forwards `(+0x10, +0x18, earlier opcode-`4` reading was just the table-indexing mistake: `0x5928a0` multiplies the pushed
+0x0c)` into `0x593170` as callback function, callback companion, and trailing drain context. selector by `0x10`, so selector `4` lands on the row at `0x5e2044`, not the row at `0x5e2034`.
So
the replay-side triplet is still a broader transport callback-wrapper family, not one fixed
route-only tuple. The type-`9` text fastpath confirms the same split from the other side too:
`0x593d00` only emits the follow-on callback lane when work field `+0x10` is nonnull, and then
forwards `(+0x10, +0x18, +0x0c)` into `0x593170` as callback function, callback companion, and
trailing drain context.
The nearby field-subscription side is tighter too: `0x592b50` now clearly uses The nearby field-subscription side is tighter too: `0x592b50` now clearly uses
`[transport+0x1774]` as a cached progress percentage under the `[transport+0xba4]` callback `[transport+0x1774]` as a cached progress percentage under the `[transport+0xba4]` callback
table, and `0x5962e0` seeds that percentage to `1` just before the first immediate mode-`3` table, and `0x5962e0` seeds that percentage to `1` just before the first immediate mode-`3`
snapshot. The nearby route-callback-table lifecycle is tighter now too: `0x596090` seeds snapshot. The nearby route-callback-table lifecycle is tighter now too: `0x596090` seeds
`[transport+0xba0]` as the callback-plumbing enable latch, clears staged payload slot `[transport+0xba0]` as the callback-plumbing enable latch, clears staged payload slot
`[transport+0xb50]`, and constructs the three owner branches rooted at `[transport+0xba4]`, `[transport+0xb50]`, and constructs the three owner branches rooted at `[transport+0xba4]`,
`[transport+0x1164]`, and `[transport+0x18bc]`. The matching local cleanup is tighter too: `[transport+0x1164]`, and `[transport+0x18bc]`; `0x596210` is the recurring service sweep over
`0x5962c0` is the explicit staged route-callback payload clear on `[transport+0xb50]`, while those same three tables plus the local field-cache and queued-descriptor families; `0x596060`
`0x595ce0` now clearly resets only the capacity-descriptor route callback table at is the explicit `gsi_am_rating` reset; and `0x596530` is the reopen-from-stored-label sibling
`[transport+0x1164]`, not the field-subscription table at `[transport+0xba4]`. The only above that same am-rating table. The matching local cleanup is tighter too: `0x5962c0` is the
meaningful gap left on the capacity side is the still-unrecovered writer that stages explicit staged route-callback payload clear on `[transport+0xb50]`, while `0x595ce0` now
`[transport+0x1778]`. The carried sidecar fields are no longer anonymous: current evidence now clearly resets only the capacity-descriptor route callback table at `[transport+0x1164]`, not
says they are just the same cached callback-wrapper triplet reused by other work-record families, the field-subscription table at `[transport+0xba4]`. The only meaningful gap left on the
namely drain context id `+0x0c`, callback function `+0x10`, and callback companion `+0x18`. capacity side is no longer a local writer search. The carried
The negative result is stronger now too: the neighboring replay-band fields sidecar fields are no longer anonymous: current evidence now says they are just the same cached
`[transport+0x176c]`, `[transport+0x1770]`, `[transport+0x1774]`, `[transport+0x177c]`, callback-wrapper triplet reused by other work-record families, namely drain context id `+0x0c`,
`[transport+0x1780]`, and `[transport+0x1784]` all have direct local lifecycle owners, but callback function `+0x10`, and callback companion `+0x18`. The negative result is stronger now
`[transport+0x1778]` still only appears as the single read in `0x595bc0`. So this is no longer too: the neighboring replay-band fields `[transport+0x176c]`, `[transport+0x1770]`,
a local ownership gap: no local writer is grounded, and the remaining staging path is best read `[transport+0x1774]`, `[transport+0x177c]`, `[transport+0x1780]`, and `[transport+0x1784]` all
as upstream and indirect rather than one ordinary direct field store in the local text cluster. have direct local lifecycle owners, but `[transport+0x1778]` still only appears as the single
read in `0x595bc0`; even the broader callback-owner lifecycle now skips it while touching those
neighbors, including bring-up `0x596090`, recurring service `0x596210`, am-rating reset/reopen
`0x596060/0x596530`, teardown `0x5961b0`, and field-subscription open `0x5962e0`. The
constructor now closes that negative result further: `0x58dc50` bulk-zeroes the full transport
body and still never seeds `[transport+0x1778]` before later explicit neighbor initialization.
So this edge is now locally closed: no writer is grounded anywhere in `RT3.exe`, and the
remaining staging path is best read as an upstream callback or worker handoff rather than one
missing ordinary field store in the local text cluster. The callback-binding family at
`0x5934e0 -> 0x593650 -> 0x58f2f0` now gives the cleanest local boundary for that claim:
RT3 stages and later consumes the shared work-record metadata triplet, but the sidecar itself
still appears only as a borrowed cached pointer at `0x595bc0`, never as a locally seeded
replay-band field.
One adjacent staged-route callback is One adjacent staged-route callback is
tighter now too: `0x595860` is the submit-result handler below tighter now too: `0x595860` is the submit-result handler below
`0x5958e0`, using the already-grounded third selector-generation counter at `[transport+0xac0]` `0x5958e0`, using the already-grounded third selector-generation counter at `[transport+0xac0]`
@ -212,10 +250,10 @@ Highest-value open edge:
gate and the same queued fallback, mode `2` removes pending descriptors from the queued family, gate and the same queued fallback, mode `2` removes pending descriptors from the queued family,
mode `3` forces mode `2` when the primary-endpoint table is empty, mode `4` updates the deferred mode `3` forces mode `2` when the primary-endpoint table is empty, mode `4` updates the deferred
route-status state around `[this+0x1ed4]` and `[this+0x1ed8]`, and mode `5` copies the staged route-status state around `[this+0x1ed4]` and `[this+0x1ed8]`, and mode `5` copies the staged
route companion dword at `[this+0x490]` into `[this+0x54]` while mirroring that value into the route companion dword at `[this+0x490]` into `[this+0x54]` while mirroring that value into
local field-cache family. The route-event dispatcher side is cleaner too: queue-side slot `[this+0x1724+0x24]` through `0x005a0940`. The route-event dispatcher side is cleaner too:
the mode-`5` tails in both callback families do not copy a descriptor-local field. They mirror the the mode-`5` tails in both callback families do not copy a descriptor-local field. They mirror the
transport-staged companion dword at `[this+0x490]` into `[this+0x54]` and the local field-cache transport-staged companion dword at `[this+0x490]` into `[this+0x54]` and that same queue-side
family instead. The `gsi_am_rating` maintenance lane is tighter now too: it sorts the slot instead, not into `[this+0x1778]`. The `gsi_am_rating` maintenance lane is tighter now too: it sorts the
primary-endpoint descriptor table through `0x590310` in mode `1` with key `gsi_am_rating`, then primary-endpoint descriptor table through `0x590310` in mode `1` with key `gsi_am_rating`, then
selects the new head through `0x590480` rather than treating the table as pure insertion order. selects the new head through `0x590480` rather than treating the table as pure insertion order.

View file

@ -125,17 +125,19 @@ What this note is for:
- Route-style peer-link emission and route-link state - Route-style peer-link emission and route-link state
- Track-laying-capacity interactions with route synthesis - Track-laying-capacity interactions with route synthesis
Highest-value open edge: Latest local closure:
- The remaining semantic edge here is now mostly about ranking, not company pressure: - The linked-transit cache split is now locally bounded rather than just suggestive:
the pre-`1.03` versus `1.03+` tracker metric split still looks meaningful for the weighted the pre-`1.03` versus `1.03+` tracker metric dispatcher still matters for the weighted
linked-transit site-score cache at `company_rebuild_linked_transit_autoroute_site_score_cache` autoroute cache lanes at `company_rebuild_linked_transit_autoroute_site_score_cache`
`0x00407bd0` and the seeded peer-route chooser at `company_build_linked_transit_autoroute_entry` `0x00407bd0`, but the grounded downstream chain now stops at route choice rather than company
`0x00408380`, but current evidence says the company train-pressure target still sums raw site pressure. Cache lane `+0x16` feeds the owned-site selector
cache `+0x12` rather than the weighted lanes fed by step count and continuity. The lower `company_select_best_owned_linked_transit_site_by_autoroute_score` `0x00408280`, and the
route-entry pair metric still has other grounded consumers such as the initial route sweep and weighted peer-side bands then feed `company_build_linked_transit_autoroute_entry`
the train route validator through `0x004a6630`, but no downstream consumer of the cached weighted `0x00408380`; train-side append and add-train helpers `0x00409770` and `0x00409830` only
site lanes `+0x0e/+0x16` is currently grounded beyond the autoroute selector `0x00408280` and inherit that weighted choice by calling the builder. By contrast, the company-wide roster target
builder `0x00408380`. So the remaining question here is no longer about current local ownership; at `company_compute_owned_linked_transit_site_score_total` `0x00408f70` still sums only raw
it is only whether some still-ungrounded consumer outside the current corpus also reads those cache `+0x12` before `company_balance_linked_transit_train_roster` `0x00409950` reacts. So this
cached weighted lanes. local semantic edge is effectively closed: the compatibility split perturbs ranked site choice
and seeded autoroute peer choice, not the later train-count target, and any further
weighted-lane consumer would have to live outside the currently grounded local chain.

View file

@ -129,12 +129,11 @@ What this note is for:
- Track-lay and station-placement semantics - Track-lay and station-placement semantics
- Train detail and trainbuy command ownership - Train detail and trainbuy command ownership
Highest-value open edge: Current bounded conclusion:
- Whether any later gameplay-owned mode introduces a distinct input or frame owner for ordinary - Current evidence points to one stable reading: no later gameplay-owned mode introduces a distinct
world interaction, or whether the grounded `shell_controller_window_message_dispatch` plus outer input or frame owner for ordinary world interaction. The grounded
`shell_controller_window_message_dispatch` plus
`simulation_frame_accumulate_and_step_world` path remains the sole coordinator after world `simulation_frame_accumulate_and_step_world` path remains the sole coordinator after world
entry and the first grounded non-camera world-input coordinator. Current evidence now points entry and the first grounded non-camera world-input coordinator, and no separate outer
strongly to the second reading: the shell-fed input and frame path remains the only grounded gameplay loop or gameplay-only input object is grounded in the local corpus.
coordinator after world entry, and no separate outer gameplay loop or gameplay-only input object
is grounded in the local corpus.

View file

@ -28,7 +28,7 @@ What this note is for:
- File-flow and content-load boundaries - File-flow and content-load boundaries
- World bring-up versus shell-owned cadence questions - World bring-up versus shell-owned cadence questions
Highest-value open edge: Current bounded conclusion:
- Whether any long-lived gameplay cadence later escapes the bootstrap-owned shell service loop - No later long-lived gameplay cadence is currently grounded outside the bootstrap-owned shell
entirely, or only nests under it. service loop; the local evidence still supports the nested-under-shell reading.

View file

@ -103,15 +103,18 @@ transition.
## Map and Scenario Content Load ## Map and Scenario Content Load
- Roots: `shell_map_file_entry_coordinator` at `0x00445ac0`, the first grounded world-entry branch - Roots: `shell_map_file_entry_coordinator` at `0x00445ac0`, the larger active-mode profile owner
`shell_active_mode_run_profile_startup_and_load_dispatch` at `0x00438890`, the shell-mode
switcher `shell_transition_mode` at `0x00482ec0`, the first grounded world-entry branch
`world_entry_transition_and_runtime_bringup` at `0x00443a50`, `world_entry_transition_and_runtime_bringup` at `0x00443a50`,
`shell_map_file_world_bundle_coordinator` at `0x00445de0`, reference-database setup via `shell_map_file_world_bundle_coordinator` at `0x00445de0`, reference-database setup via
`map_bundle_open_reference_databases` at `0x00444dd0`, and narrower loaders such as `map_bundle_open_reference_databases` at `0x00444dd0`, and narrower loaders such as
`map_load_geographic_label_database` and `map_load_city_database`. `map_load_geographic_label_database` and `map_load_city_database`.
- Trigger/Cadence: shell tutorial launch, editor or detail-panel file actions through `fileopt.win`, - Trigger/Cadence: shell tutorial launch, editor or detail-panel file actions through `fileopt.win`,
map-scenario open paths, and scenario-text export batch commands. map-scenario open paths, and scenario-text export batch commands.
- Key Dispatchers: `shell_map_file_entry_coordinator`, `world_entry_transition_and_runtime_bringup`, - Key Dispatchers: `shell_map_file_entry_coordinator`,
`world_runtime_release_global_services`, `shell_map_file_world_bundle_coordinator`, `shell_active_mode_run_profile_startup_and_load_dispatch`, `shell_transition_mode`,
`world_entry_transition_and_runtime_bringup`, `world_runtime_release_global_services`, `shell_map_file_world_bundle_coordinator`,
`map_bundle_open_reference_databases`, `map_load_geographic_label_database`, `map_bundle_open_reference_databases`, `map_load_geographic_label_database`,
`map_load_city_database`, `scenario_text_export_build_language_file`, `map_load_city_database`, `scenario_text_export_build_language_file`,
`scenario_text_export_report_language_file`, `scenario_text_export_batch_process_maps`. `scenario_text_export_report_language_file`, `scenario_text_export_batch_process_maps`.
@ -137,7 +140,188 @@ transition.
mode `11` is tighter now too: it still maps to `.gmt`, but instead of looking like another mode `11` is tighter now too: it still maps to `.gmt`, but instead of looking like another
gameplay save family it conditionally diverts into the same `.gmt` preview-surface pipeline owned gameplay save family it conditionally diverts into the same `.gmt` preview-surface pipeline owned
by the Multiplayer preview dataset object at `0x006cd8d8`, and only falls back to the normal by the Multiplayer preview dataset object at `0x006cd8d8`, and only falls back to the normal
reference-bundle path when that dataset object is absent. reference-bundle path when that dataset object is absent. The shell-side mode owner above those
file coordinators is clearer now too. `shell_transition_mode` no longer reads like a generic mode
switch: its constructor jump table now resolves mode `1` to `Game.win`, mode `2` to `Setup.win`,
mode `3` to `Video.win`, mode `4` to `LoadScreen.win`, mode `5` to `Multiplayer.win`, mode `6`
to `Credits.win`, and mode `7` to `Campaign.win`. The strongest current load-side owner inside
that table remains the mode-`4` branch around `0x4830ca`, which publishes the new active mode
object into `0x006cec78` and then calls `shell_active_mode_run_profile_startup_and_load_dispatch`
as `thiscall(active_mode, 1, 0)`. The caller split above that owner is tighter now too:
`world_entry_transition_and_runtime_bringup` reaches the same owner at `0x443b57` with `(0, 0)`
after dismissing the current shell detail panel and servicing `0x4834e0(0, 0)`, while the
saved-runtime path at `0x446d7f` does the same before immediately building the `.smp` bundle
payloads through `0x530c80/0x531150/0x531360`. That makes the `LoadScreen.win` startup lane the
only currently grounded caller that enters `0x438890` with `(1, 0)` instead of `(0, 0)`. The
internal selector split in `0x438890` is tighter now too: `[0x006cec7c+0x01]` is a separate
seven-way startup selector, not the shell mode id. Values `1` and `7` load `Tutorial_2.gmp` and
`Tutorial_1.gmp`, values `3/5/6` collapse into the same profile-seeded file-load lane through
`0x445ac0([0x006cec7c]+0x11, 4, &out_success)`, value `2` is a world-root initialization lane
that allocates `0x0062c120` and then forces selector `3`, and value `4` is the setup-side world
reset or regeneration lane that rebuilds `0x0062c120` from `0x006d14cc/0x006d14d0` before later
world setup continues. The write side is tighter now too: `Campaign.win` writes selector `6`,
`Multiplayer.win` writes selector `3` on one pending-status path, and the larger `Setup.win`
dispatcher writes selectors `2`, `3`, `4`, and `5` on its validated launch branches. That makes
the file-load subfamily read less like one generic save-open branch and more like a shared
profile-file lane reused by setup, multiplayer, and campaign owners. The world-entry owner
boundary is tighter now too: `world_entry_transition_and_runtime_bringup` at `0x00443a50` no
longer stops at the initial shell transition and world allocation head. The same grounded
function continues through the larger post-load generation tail up to `0x00444dc2`, which means
the later `Setting up Players and Companies...` and neighboring post-load passes are not
floating raw callsites after all. That same owner now clearly covers the event-runtime refresh
through `0x433130`, chairman-profile materialization through `0x437220`, neighboring route and
tracker refresh families, and the one-shot kind-`8` runtime-effect service through `0x432f40`
before clearing shell-profile latch `[0x006cec7c+0x97]`. The `Setup.win` dispatcher
is less opaque now too: the early `0x0bc1..0x0c24` family is mostly fixed submode selection above
`0x00502c00`, except for the separate `0x0bc2/0x0bc5/0x0bc6/0x0bc7` shell-open quartet above
`0x00501f20`; `0x0c1f` is the settings-window escape; `0x0c1e/0x0c20/0x0c22` are direct shell
requests into `0x00482150`; the fixed submode buttons now have concrete lower targets such as
`0x0bc1/0x0bc8 -> 15`, `0x0bc3 -> 16`, `0x0bc4 -> 1`, `0x0c80 -> 13`, `0x0c1c -> 17`,
`0x0c1d -> 2`, `0x0c24 -> 14`, `0x0c81 -> 14`, `0x0c82 -> 6`, `0x0c83 -> 10`, and
`0x0c84 -> 12`; the
`0x0ce6/0x0ce7/0x0d49/0x0d4a/0x0e82/0x0e83` branches are bounded list or slider adjustments on
staged setup fields; and the later `0x0dca/0x0dcb/0x0de9/0x0df3/0x0e81/0x0f6f/0x0f70` controls
are the explicit selector-writing launch buttons rather than one anonymous validated-launch blob.
The constructor-side callbacks are tighter too: control `0x0ce8` is a table-driven payload-label
draw callback above `0x00502030`, not another launch root, and controls `0x0e86` and `0x0e87`
do not select more setup roots; they update the persisted shell-state selector pairs at
`[0x006cec74+0x233/+0x237]` and `[0x006cec74+0x23b/+0x23f]` through `0x00502160` and
`0x005021c0`, then immediately save config through `0x00484910(1)`. The constructor body is
tighter too: it seeds the initial `Setup.win` state by running `0x502910`, `0x502550`, and
`0x502c00(1)` before the user interacts with the window, installs `0x0c80..0x0c86` and
`0x0f6f..0x0f71` as homogeneous button bands, and treats `0x0e88` as one separate special
control with retuned float fields rather than another ordinary launch root. The remaining
optional constructor child `0x0bd0` is tighter now too: it is built from a separate template
block and optional owned heap object before registration, not another hidden setup-root button.
The generic shell helper layer beneath that constructor is tighter now too: `0x53fa50` is the
shared resource-bind and child-list initialization helper, `0x53f830` is the child-control
lookup-by-id helper over the intrusive list at `[this+0x70]`, `0x558130` is the child-control
finalizer that stamps the owner pointer and resolves localized captions before the control goes
live, and `0x53f9c0` is the ordered child-control registration helper used by the optional
`0x0bd0` branch and other shell dialogs.
The submode selector itself is tighter now too because its button-state lane is mostly decoded:
`0xbc5` tracks submode `15`, `0xbc6` tracks `16`, `0xbba` tracks `2`, `0xbbb` tracks `3`,
`0xbc3` tracks `13`, `0xbbc` groups `3/4/5`, `0xbbd` tracks `4`, `0xbbe` tracks `5`, `0xbbf`
groups `6/12/14`, `0xbc0` tracks `7`, `0xbc1` tracks `8`, `0xbc2` tracks `9`, `0xe75` tracks
`10`, and `0xf6e` tracks `17`. RT3.lng and the setup art families now also make several of those
top-level roots read less like anonymous ids and more like real menu panels: submode `1` is the
strongest current landing-panel fit, `2` is the `Single Player` root, `7/8/9` are the
`Editor` / `New Map` / `Load Map` family, `15` is `Extras`, and `17` is `Tutorial`. By
elimination against the separate shell `Credits.win` mode, the remaining top-level `Setup.win`
roots now most safely read as `13 = Multi Player` and `16 = High Scores`.
The file-backed side is tighter too: `0x502c00` now maps submodes exactly as `4 -> dataset 5`,
`9 -> 4`, `6 -> 8`, `10 -> 6`, `12 -> 10`, and `14 -> 9`, so the saved-game-backed family is
no longer one blurred list pane but a small set of stable dataset variants above
`0x4333f0/0x4336a0`.
The file-backed header split is tighter too: `0x5027b0` now maps the top header-control ids as
`4 -> 0xd4b`, `9 -> 0xde8`, `6/12/14 -> 0xdf2`, and `10 -> 0xe85`. RT3.lng closes one earlier
mistake here: those values are local setup control or resource ids, not localized text ids.
The setup art bundle now tightens that family split one step further too: under `rt3_2WIN.PK4`
the distinct file-backed setup art families are the shared `Setup_Load` lane and the separate
`Setup_Sandbox` lane, which matches the selector-side evidence that mode `10` is the
sandbox-backed `new` list while mode `12` is its `load` sibling. Combined with the builder at
`0x4333f0`, that shows only submodes `4` and `10` using the alternate localized-stem list-label
path; `6`, `9`, `12`, and `14` keep the direct filename-normalization lane.
The grouped `3/4/5` family is narrower now too: `0x0ce5` is no longer part of it, because that
control writes selector `3` and then selects submode `9` as the `Load Map` sibling. The nearby
`0x0dcb` branch instead conditionally selects submode `5` or `3`, which keeps `3` as the
strongest current `New Game` / `Options` companion and `5` as the strongest current `Sandbox`
companion. The file-backed single-player side is tighter in the same way now: modes `4` and `10`
are the only siblings using the alternate localized-stem row-label path, so they now read most
safely as the two setup-local template or profile list variants rather than ordinary save lists.
Mode `10` is the stronger one of the pair because neighboring validated launch control `0x0e81`
both routes into selector `5` and sets sandbox byte `[0x006cec7c+0x82] = 1`, which makes it the
strongest current fit for the setup-local `New Sandbox` list. The distinct `Setup_Sandbox` art
family in `rt3_2WIN.PK4` now reinforces that same split one step further, which makes mode `12`
the strongest closed fit for the paired `Load Sandbox` lane; mode `4` is therefore the strongest
remaining non-sandbox peer in that same pair and now most safely reads as the setup-local `New
Scenario`-style chooser. Modes `6`, `12`, and `14` now tighten one step further as the three
selector-`3` direct-filename setup-local `load` siblings because they stay on the direct
filename-normalization lane, share the same `0xdf2` header family, and clear the same
presence-style latch `[0x006cec7c+0x97]`; mode `14` is the strongest current landing panel for
that cluster because `0x0c24` jumps to it directly while `0x0c82` and `0x0c84` only reach the
sibling modes `6` and `12` from inside the same load family. Current control-pairing and
setup-art evidence now make `12 = Load Sandbox` the strongest closed per-submode assignment. The
remaining non-sandbox pair is closed now too: the deeper bundle filter at `0x433260`
distinguishes dataset `9` from dataset `8` by one extra nonzero payload-flag family, and the
editor-side metadata path now grounds `[0x006cec78+0x66de]` as the direct `Campaign Scenario`
checkbox bit because `editorDetail.win` ties control `0x5b6e` to localized ids `3160/3161`.
That makes dataset `9` the campaign-designated load family and dataset `8` the ordinary scenario
load family, so `14 = Load Campaign` and `6 = Load Scenario` now read as grounded rather than
residual. `0x502910` is
tighter in a
corrective way too: it is not a mode-`3`-only helper after all, but the shared non-file-backed
payload panel that formats
`0xcf3/0xcf4/0xcf5/0xd4f`, mirrors option byte `[0x006cec7c+0x7d]` into `0x0ce9..0x0ced`,
rebuilds row host `0x0ce8` from payload bytes `[+0x31b/+0x31c]`, and mirrors live row markers
into `[0x006cec7c+0x87]`. That makes mode `3` just one user of the shared payload-driven panel,
not the sole owner of it.
The payload-helper cluster under that panel is tighter now too: `0x502220` does not just
republish labels and the preview surface. It first re-enters
`shell_setup_load_selected_profile_bundle_into_payload_record` `0x442400`, which clears one full
`0x100f2`-byte setup payload record, builds a rooted path from the staged profile stem, opens the
selected bundle through `0x530c80`, and then reads either the ordinary saved-profile chunk family
or the map-style chunk family through `0x531150/0x531360` depending on the selected extension
shape. Only after that does `0x502220` copy payload fields `+0x14/+0x3b2/+0x3ba/+0x20` into the
staged runtime profile through `0x47be50`, which in turn normalizes the payload category bytes at
`[payload+0x31a + row*9]` and the local marker-slot bytes at `[payload+0x2c9..]` through
`0x47bc80`. The adjacent region-side worker family is tighter in a negative way too: the setup
payload loader is now clearly separate from the broader region-building and placement cluster
around `0x422320..0x423d30`, whose current grounded helpers now include `0x422320`, `0x4228b0`,
`0x422900`, `0x422a70`, `0x422be0`, `0x422ee0`, `0x4234e0`, `0x4235c0`, and
`world_region_refresh_cached_category_totals_and_weight_slots` `0x423d30` rather than one hidden
setup-only panel. The leading region helper is no longer unnamed either: `0x422320` is now
bounded as the recurring cached-structure-scalar normalization pass that writes
`[region+0x2e2/+0x2e6/+0x2ea/+0x2ee]`, while `0x422a70` is the shared placement-validation and
commit gate beneath both the per-region worker and one second world-side placement loop. The
neighboring `0x4234e0` accessor is tighter too: it is the shared projected structure-count scalar
query by category that later feeds both the per-region placement family and the map-editor city
count stats report. So the remaining gap is no longer “what are these setup payload helpers
doing,” but only how aggressive we want to be when naming the last top-level setup roots from
mostly RT3.lng and asset-side evidence.
The adjacent summary helper is tighter too: `0x502550` is not another hidden submode owner. It
republishes the staged path tail in `0xe7f`, the scalar summary in `0xe84`, the two persisted
selector lists in `0xe86/0xe87`, and the config toggles in `0xe88/0xe89/0xe8a`.
The remaining top-level gap is cleaner now too: submode `16` still has no distinct downstream
helper or launch branch in the local selector/refresh family, but it is no longer a blank bucket.
Current evidence now bounds it as the `0x0bc3 -> 16` top-level button whose visual-state lane is
surfaced through control `0xbc6`, and the strongest residual fit is `High Scores` because
`Credits` already lives in separate shell mode `6`.
The validated launch lane is tighter now too: it no longer just writes selector `3` or `5` as
one undifferentiated blob, and it no longer includes `0x0ce5` or `0x0dcb`. `0x0ce5` is the
`Load Map` selector because it writes selector `3` and then selects submode `9`, while `0x0dcb`
is the later conditional `5/3` companion-selector sibling rather than a file launch. The actual
validated staged-profile lane is now bounded more narrowly: `0x0cf6` and `0x0e81` are the
selector-`5` siblings, while the neighboring selector-`3` validated lane is at least shared by
`0x0de9` and `0x0df3`; the shared bridge at `0x4425d0` then forces `[0x006cec7c+0xc5] = 1`,
mirrors payload bytes into `[profile+0xc4]`, `[profile+0x7d]`, and `[profile+0xc6..+0xd5]`, and
only then issues shell request `0x0cc`. Only `0x0e81` also sets `[0x006cec7c+0x82] = 1`, which
is currently the strongest sandbox-side anchor beneath the later `.gmx` load family.
That launch band is slightly tighter now too: `0x0dca` is the grayscale-map picker branch above
the `TGA Files (*.tga)` / `.\Data\GrayscaleMaps` helper at `0x004eb0b0`, not another ordinary
save-file validator. That launch band is tighter in a second way too: `0x0ddf` is not a lobby
branch after all, but the separate windowed-mode-gated grayscale-heightmap generation path. It
reopens the `TGA Files (*.tga)` / `.\Data\GrayscaleMaps` picker through `0x0042a970`, validates
the chosen image through `0x005411c0`, and only then writes startup selector `2`. The fixed-
button side is tighter too: submode `15` now aligns with the `Extras` family because it sits
above the Readme/Weblinks shell-open quartet and the return-to-extras sibling, while submode
`17` now aligns with the tutorial chooser because it is the only branch that later exposes
startup selectors `1` and `7`, which RT3 uses for `Tutorial_2.gmp` and `Tutorial_1.gmp`. The
map-root family is tighter too: submodes `7/8/9` are the only setup branch that flips the file
root into
`maps\\*.gm*`, with mode `8` specifically the grayscale-heightmap picker and mode `9` the
file-backed map-list sibling.
Submode `13` is slightly tighter now too: current local evidence shows that it stays outside
both the dedicated file-backed pane and the mode-`3` option-heavy pane, so it is best read for
now as one top-level non-file-backed setup branch rather than another saved-game list variant.
The setup-side file roots are tighter now too: the shared file-list builder at `0x4333f0`
formats either `saved games\\*.smp` or `maps\\*.gm*` from the shell-state fields at
`[0x006cec74+0x68/+0x6c]`, with the separate `data\\tutorial` root only appearing on the
tutorial-side `[shell+0x6c] == 2` branch. That means the `Setup.win` submode families are no
longer one generic file pane: the ordinary setup-backed `.smp` family is broader than the
narrower file-list pane, because modes `3/4/5/6/10/12/14` stay on the ordinary saved-game side
while only `4/6/9/10/12/14` re-enter the dedicated file-list panel at `0x5027b0`; the `maps`
branch is the narrower setup or tutorial launch family above the same shared record builder.
- Evidence: function-map map/scenario rows, analysis-context exports for `0x00445ac0`, `0x00445de0`, - Evidence: function-map map/scenario rows, analysis-context exports for `0x00445ac0`, `0x00445de0`,
`0x00443a50`, `0x00434300`, and `0x00444dd0`, plus objdump string and mode-table evidence for `0x00443a50`, `0x00434300`, and `0x00444dd0`, plus objdump string and mode-table evidence for
`.gmp`, `.gmx`, `.gmc`, `.gms`, `.gmt`, `.smp`, `Quicksave`, the `0x004dd010` mode table at `.gmp`, `.gmx`, `.gmc`, `.gms`, `.gmt`, `.smp`, `Quicksave`, the `0x004dd010` mode table at
@ -276,6 +460,10 @@ transition.
callback `0x595bc0`, while callback `0x595bc0`, while
`multiplayer_transport_route_callback_table_service_receive_decode_state_machine` `0x5908c0` `multiplayer_transport_route_callback_table_service_receive_decode_state_machine` `0x5908c0`
is the current live receive/decode state machine serviced by `0x591290` in table states `2/3`. is the current live receive/decode state machine serviced by `0x591290` in table states `2/3`.
The callback-owner mode split above that runtime is now explicit too: append-notify `0x590370`
publishes mode `0`, compact upsert `0x590d00` publishes mode `1`, remove-notify `0x590430`
publishes mode `2`, and the live receive/decode path `0x5908c0` publishes modes `6`, `5`, and
`3`.
The route-handle lifecycle above that decode path is tighter now too: `0x590740` cleanly resets The route-handle lifecycle above that decode path is tighter now too: `0x590740` cleanly resets
one table's live route plus decode-side runtime without destroying the outer object; `0x5907a0` one table's live route plus decode-side runtime without destroying the outer object; `0x5907a0`
is the broader destroy path that also releases the active descriptor collection; `0x590ed0` is the broader destroy path that also releases the active descriptor collection; `0x590ed0`
@ -303,8 +491,18 @@ transition.
in `0x595bc0` reads the same `+0x0c/+0x10/+0x18` metadata triplet and replay modes later consume in `0x595bc0` reads the same `+0x0c/+0x10/+0x18` metadata triplet and replay modes later consume
the pointer through `0x5933a0`. The negative result is stronger too: local text-side xrefs still the pointer through `0x5933a0`. The negative result is stronger too: local text-side xrefs still
show no direct store to `[transport+0x1778]`, and a wider local sweep also failed to show any show no direct store to `[transport+0x1778]`, and a wider local sweep also failed to show any
obvious `lea`-based replay-band writer, so the sidecar writer remains upstream of this leaf obvious `lea`-based replay-band writer. The transient-request lifecycle tightens that further:
publisher. Mode `0` is now also tied more cleanly to the generic descriptor append-notify lane `0x593330/0x593370/0x593380/0x5934e0/0x5933a0` now fully bound `[transport+0x1780]`, `0x1784`,
and `0x1788` without ever touching `[transport+0x1778]`, and the neighboring active-opcode reset
helper `0x5929a0` is likewise scoped only to `[transport+0x17fc]`. A broader constructor and
teardown pass tightened that further too: `0x596090`, `0x5961b0`, and `0x5962e0` all touch the
neighboring replay-band fields without ever seeding `[transport+0x1778]`. A full-binary
literal-offset sweep tightens it further still: the only direct `0x1778` hit in `RT3.exe` is
the read in `0x595bc0`. One nearby ambiguity is now closed too: the
mode-`5` mirror path in `0x595a40` and `0x595e10` does not target `[transport+0x1778]`; it
writes `[transport+0x54]` and mirrors the same staged route companion dword only into queue-side
slot `[transport+0x1724+0x24]` through `0x005a0940`. So the sidecar writer remains upstream of this
leaf publisher. Mode `0` is now also tied more cleanly to the generic descriptor append-notify lane
at `0x590370`, while mode `2` stays outside this helper as the separate at `0x590370`, while mode `2` stays outside this helper as the separate
remove-notify-and-stage path at `0x590430`. The opcode-`2` payload boundary is tighter too: remove-notify-and-stage path at `0x590430`. The opcode-`2` payload boundary is tighter too:
`0x592ae0` now grounds that payload `0x592ae0` now grounds that payload
@ -314,6 +512,10 @@ transition.
receive-state owner callbacks emitted by `0x5911e0 -> 0x5908c0`, not loose generic replay receive-state owner callbacks emitted by `0x5911e0 -> 0x5908c0`, not loose generic replay
guesses. So those paths are better read as delayed metadata replays over one cached work record, guesses. So those paths are better read as delayed metadata replays over one cached work record,
not over a separate anonymous cache blob. The neighboring not over a separate anonymous cache blob. The neighboring
capacity-owner split is tighter now too: `0x595bc0` only stages descriptor records for modes
`0`, `3`, and `5`; the upstream route-callback-table owner still delivers modes `1`, `2`, and
`6`, but those are explicit no-ops in this capacity leaf. So the owner wiring itself is no
longer the open edge; only the upstream sidecar producer remains unresolved. The neighboring
work queue is tighter too: `0x593330/0x593370/0x593380` now bound `[transport+0x1780]` as the work queue is tighter too: `0x593330/0x593370/0x593380` now bound `[transport+0x1780]` as the
construct/clear/destroy owner family, while `0x5933a0`, `0x5934e0`, and `0x593570` ground the construct/clear/destroy owner family, while `0x5933a0`, `0x5934e0`, and `0x593570` ground the
remove, allocate, and completion side over that same collection. The small sibling `0x593400` is remove, allocate, and completion side over that same collection. The small sibling `0x593400` is
@ -335,11 +537,13 @@ transition.
remove-notify-and-stage lane, `0x590490` releases the staged list, and `0x5904d0` releases the remove-notify-and-stage lane, `0x590490` releases the staged list, and `0x5904d0` releases the
active descriptor collection before tearing that staged list down. That also makes the earlier active descriptor collection before tearing that staged list down. That also makes the earlier
`0x5962e0` “release active descriptors” step explicit. The callback-table attach side now constrains the `0x5962e0` “release active descriptors” step explicit. The callback-table attach side now constrains the
same work-record metadata family a little further too: `0x593650` deliberately duplicates one same work-record metadata family a little further too: `0x593650` deliberately duplicates its
caller metadata dword into both fields `+0x0c` and `+0x18`, while preserving the remaining first caller metadata dword into both fields `+0x0c` and `+0x10`, while carrying the second
caller callback function pointer in `+0x10`. The lower opcode wrappers are tighter now too: caller metadata dword in `+0x18`. The lower opcode wrappers are tighter now too: `0x592a40`
`0x592a40` and `0x592a70` both consume that staged triplet in the order `( callback fn +0x10, turned out to be the explicit opcode-`1` trigger wrapper whose constructor is a no-op and whose
callback companion +0x18, drain context id +0x0c )`. The producer side is tighter too: active-side service is `0x5913c0`, while `0x592c40` is the real `0x08`-byte explicit opcode-`5`
binding leaf. The earlier opcode-`4` read was just the table-indexing mistake in `0x5928a0`:
selector `4` lands on the row at `0x5e2044`, not the row at `0x5e2034`. The producer side is tighter too:
bound-route requests, selector-text route requests, and the type-`9` text fastpath also stage bound-route requests, selector-text route requests, and the type-`9` text fastpath also stage
that same triplet through `0x5934e0`, and the fastpath shim `0x593d00` now gives the cleanest that same triplet through `0x5934e0`, and the fastpath shim `0x593d00` now gives the cleanest
proof of the callback split by only re-emitting the follow-on lane when `+0x10` is nonnull and proof of the callback split by only re-emitting the follow-on lane when `+0x10` is nonnull and
@ -351,19 +555,29 @@ transition.
`1` just before the first immediate mode-`3` snapshot. The nearby route-callback-table `1` just before the first immediate mode-`3` snapshot. The nearby route-callback-table
lifecycle is tighter now too: `0x596090` seeds `[transport+0xba0]` as the callback-plumbing lifecycle is tighter now too: `0x596090` seeds `[transport+0xba0]` as the callback-plumbing
enable latch, clears staged payload slot `[transport+0xb50]`, and constructs the three owner enable latch, clears staged payload slot `[transport+0xb50]`, and constructs the three owner
branches rooted at `[transport+0xba4]`, `[transport+0x1164]`, and `[transport+0x18bc]`. The branches rooted at `[transport+0xba4]`, `[transport+0x1164]`, and `[transport+0x18bc]`;
`0x596210` is the recurring service sweep over those same three tables plus the local field
cache and queued-descriptor family; `0x596060` is the explicit `gsi_am_rating` reset; and
`0x596530` is the reopen-from-stored-label sibling above that same am-rating table. The
matching local cleanup is tighter too: `0x5962c0` is the explicit staged route-callback payload matching local cleanup is tighter too: `0x5962c0` is the explicit staged route-callback payload
clear on `[transport+0xb50]`, while `0x595ce0` now clearly resets only the capacity-descriptor clear on `[transport+0xb50]`, while `0x595ce0` now clearly resets only the capacity-descriptor
route callback table at `[transport+0x1164]`, not the field-subscription table at route callback table at `[transport+0x1164]`, not the field-subscription table at
`[transport+0xba4]`. The remaining gap on the capacity side is `[transport+0xba4]`. The remaining gap on the capacity side is therefore narrower: the carried
therefore narrower: mainly the still-unrecovered writer that stages `[transport+0x1778]`. The sidecar fields themselves now read more cleanly as the cached callback-wrapper triplet reused
carried sidecar fields themselves now read more cleanly as the cached callback-wrapper triplet elsewhere (`drain context id +0x0c`, `callback fn +0x10`, `callback companion +0x18`), and the
reused elsewhere (`drain context id +0x0c`, `callback fn +0x10`, `callback companion +0x18`), negative result is stronger too: nearby replay-band fields `[transport+0x176c]`,
and the negative result is stronger too: nearby replay-band fields `[transport+0x176c]`,
`[transport+0x1770]`, `[transport+0x1774]`, `[transport+0x177c]`, `[transport+0x1780]`, and `[transport+0x1770]`, `[transport+0x1774]`, `[transport+0x177c]`, `[transport+0x1780]`, and
`[transport+0x1784]` all have direct local owners while `[transport+0x1778]` still appears only `[transport+0x1784]` all have direct local owners while `[transport+0x1778]` still appears only
as the single read in `0x595bc0`. So the remaining writer looks upstream and indirect rather as the single read in `0x595bc0`; even the broader callback-owner lifecycle now skips it while
than like one missing ordinary field store in the local cluster. The adjacent staged-route seeding, servicing, resetting, reopening, or tearing down those neighboring tables and caches.
The constructor now closes that local search further: `0x58dc50` bulk-zeroes the full transport
body and still never writes a nonzero value into `[transport+0x1778]` before later explicit
neighbor initialization. The callback-binding owner stack now tightens that boundary too:
`0x5934e0` stages the shared work-record metadata triplet, `0x593650` binds it into the
callback-table worker path, and `0x593570` later consumes and republishes it, while
`[transport+0x1778]` still appears only as the borrowed sidecar read in `0x595bc0`. So this
edge is now locally closed, and the remaining producer looks like an upstream callback or worker
handoff rather than one missing ordinary field store in the local cluster. The adjacent staged-route
callback side is tighter too: `0x595860` is now bounded as the callback side is tighter too: `0x595860` is now bounded as the
submit-result handler beneath `0x5958e0`, and the old `[transport+0xac0]` ambiguity there is now submit-result handler beneath `0x5958e0`, and the old `[transport+0xac0]` ambiguity there is now
gone. That branch is using the already-grounded third selector-generation counter at `[0xac0]` gone. That branch is using the already-grounded third selector-generation counter at `[0xac0]`
@ -424,7 +638,7 @@ transition.
callback-marshaling wrappers `0x591480` and `0x591510`, not read through any dedicated semantic callback-marshaling wrappers `0x591480` and `0x591510`, not read through any dedicated semantic
accessor yet. The route-event dispatcher side is tighter too: the mode-`5` tails in both accessor yet. The route-event dispatcher side is tighter too: the mode-`5` tails in both
callback families do not copy a descriptor-local field but instead mirror the transport-staged callback families do not copy a descriptor-local field but instead mirror the transport-staged
companion dword at `[this+0x490]` into `[this+0x54]` and the local field-cache family. The companion dword at `[this+0x490]` into `[this+0x54]` and queue-side slot `[this+0x1724+0x24]`. The
`gsi_am_rating` maintenance lane is tighter now too: after pruning failed descriptors it sorts `gsi_am_rating` maintenance lane is tighter now too: after pruning failed descriptors it sorts
the surviving primary-endpoint table through `0x590310` in mode `1` with key `gsi_am_rating`, the surviving primary-endpoint table through `0x590310` in mode `1` with key `gsi_am_rating`,
then selects the new head through `0x590480` before re-entering the route-transition path. The then selects the new head through `0x590480` before re-entering the route-transition path. The
@ -492,7 +706,7 @@ transition.
queued `gsi_am_rating` split, mode `1` for the ready-bit plus queued fallback, mode `2` for queued `gsi_am_rating` split, mode `1` for the ready-bit plus queued fallback, mode `2` for
pending-descriptor cleanup, mode `3` for the empty-table fallback, mode `4` for deferred pending-descriptor cleanup, mode `3` for the empty-table fallback, mode `4` for deferred
route-status recovery, and mode `5` for copying the staged route companion dword into `[this+0x54]` route-status recovery, and mode `5` for copying the staged route companion dword into `[this+0x54]`
and the local field cache. The current grounded mode transitions still switch by releasing route and queue-side slot `[this+0x1724+0x24]`. The current grounded mode transitions still switch by releasing route
objects through `multiplayer_gamespy_route_release_and_free` and rebuilding them through objects through `multiplayer_gamespy_route_release_and_free` and rebuilding them through
`multiplayer_transport_try_connect_live_route`, not by mutating callback slots in place. The `multiplayer_transport_try_connect_live_route`, not by mutating callback slots in place. The
parser behavior is now tighter as well: semicolon lines only dispatch when parser behavior is now tighter as well: semicolon lines only dispatch when
@ -553,13 +767,43 @@ transition.
stepper `simulation_advance_to_target_calendar_point`, but current grounded callers put that stepper `simulation_advance_to_target_calendar_point`, but current grounded callers put that
helper inside the larger post-load generation pipeline `world_run_post_load_generation_pipeline` helper inside the larger post-load generation pipeline `world_run_post_load_generation_pipeline`
at `0x004384d0` under the `Seeding Economy...` phase rather than under the ordinary player-facing at `0x004384d0` under the `Seeding Economy...` phase rather than under the ordinary player-facing
speed buttons. That setup pipeline is now clearer at the progress-banner level too: localized id speed buttons. That setup pipeline is now clearer at the progress-banner level too: on the fuller
`318` `Computing Transportation and Pricing...` stays visible while the pipeline runs setup path it first runs one preliminary named-candidate availability prepass through `0x00437743`
before any visible progress banner is posted. After that, localized id `318`
`Computing Transportation and Pricing...` stays visible while the pipeline runs
`world_compute_transport_and_pricing_grid` `0x0044fb70`, the early collection-owned staging pass `world_compute_transport_and_pricing_grid` `0x0044fb70`, the early collection-owned staging pass
`world_setup_building_collection_phase` `0x0041ea50`, and the conditional region pair `world_setup_building_collection_phase` `0x0041ea50`, and the conditional region pair
`world_region_collection_seed_default_regions` `0x00421b60` plus `world_region_collection_seed_default_regions` `0x00421b60` plus
`world_region_border_overlay_rebuild` `0x004882e0`; only then does the code post id `319` `Setting `world_region_border_overlay_rebuild` `0x004882e0`; that border pass is now tighter too: the
up Players and Companies...`. That `319` lane is no longer just a shell-state placeholder: its outer owner refreshes the companion region set `0x006cfc9c` through the reset-and-assign wrapper
`0x00487650` above the heavier record initializer `0x00487540`, then re-enters `0x004881b0` to
refresh the raw per-region cell-count band from the live world raster, and the inner emitter
`0x00487de0` clears prior chunked segment queues through `0x00533cf0`, scans the live region
raster, and appends fresh border-segment records through `0x00536ea0`. The lower presentation
helper strip under the same owner is now explicit too: `0x00533970/0x00533980` query the cached
world-grid X/Y maxima, `0x00533ae0/0x00533af0/0x00533b00` expose the secondary-raster and
companion byte-raster roots, and `0x00533b20/0x00533b30/0x00533b70/0x00533b90` expose the
normalized coordinate, strip-offset, and sample-triplet tables used by shell-side overlay
staging. If shell-state gate
`[0x006cec74+0x174]` is set it then posts id `320` `Setting Up Buildings...` for
`world_region_collection_run_building_population_pass` `0x00421c20`; if `[0x006cec74+0x178] > 0`
it then posts id `321` `Seeding Economy...` for `simulation_run_chunked_fast_forward_burst`
`0x00437b20`; only after those setup-side gates does the code post id `319`
`Setting up Players and Companies...`. That `319` lane is no longer just a shell-state placeholder: its
earlier hidden prepass is tighter now too: `0x00437743` is the scenario-side named
candidate-availability seeding pass over the live candidate pool `0x0062b268`, feeding the
override collection at `[state+0x66b2]` through `0x00434f20` before any visible banner is posted.
That candidate-side table now has a grounded fixed record layout too: each entry is a `0x22`-byte
blob with a zero-terminated candidate-name slot at `[entry+0x00..+0x1d]` and one trailing
availability dword at `[entry+0x1e]`, read through `0x00434ea0` and mirrored later into
`[candidate+0x7ac]`. The same scenario state keeps a locomotive-side sibling collection at
`[state+0x66b6]`, read through `0x00435030` and updated through `0x004350b0`, which is now the
shared named locomotive-availability lane beneath the `Locomotives` editor page and the
startup-side locomotive seeding branches. That locomotive-side table is the same pattern at a
larger width: each entry is a `0x41`-byte blob with a zero-terminated locomotive-name slot at
`[entry+0x00..+0x3c]` and one trailing availability dword at `[entry+0x3d]`, later mirrored into
`[loco+0x7b]`. Both override dwords now read most safely as simple availability bits rather than
wider mode enums. That `319` lane is no longer just a shell-state placeholder: its
primary grounded work is still the chairman-profile pair primary grounded work is still the chairman-profile pair
`world_seed_default_chairman_profile_slots` `0x004377a0` plus `world_seed_default_chairman_profile_slots` `0x004377a0` plus
`world_build_chairman_profile_slot_records` `0x00437220`, which seed the 16 selector bytes at `world_build_chairman_profile_slot_records` `0x00437220`, which seed the 16 selector bytes at
@ -582,8 +826,12 @@ transition.
surface beside that chairman panel is clearer now too. surface beside that chairman panel is clearer now too.
`map_editor_scenario_metadata_panel_refresh_controls` `0x004ca790` republishes the scenario `map_editor_scenario_metadata_panel_refresh_controls` `0x004ca790` republishes the scenario
description from `[0x006cec78+0x672e]`, the start-year trio `[+0x66ca]`, `[+0x66d2]`, and description from `[0x006cec78+0x672e]`, the start-year trio `[+0x66ca]`, `[+0x66d2]`, and
`[+0x66ce]`, and the paired boolean toggles `[+0x66de]` plus inverse `[+0x66f3]` across control `[+0x66ce]`, the direct campaign-designated bit `[+0x66de]` through control `0x5b6e`, and the
band `0x5b69..0x5b74`, while `map_editor_scenario_metadata_panel_refresh_briefing_mode` inverse of the paired metadata byte `[+0x66f3]` through control `0x5b74`. The resource-side
anchor is now explicit too: `editorDetail.win` carries localized ids `3160/3161` `Campaign
Scenario` and `If checked, this map will be reserved as a campaign scenario.` inside the control
record rooted at `0x5b6e`, so `[+0x66de]` is now the grounded campaign-scenario flag rather than
an anonymous metadata boolean. `map_editor_scenario_metadata_panel_refresh_briefing_mode`
`0x004ca670` now bounds the single-player versus multiplayer briefing switch by flipping selector `0x004ca670` now bounds the single-player versus multiplayer briefing switch by flipping selector
`0x621f50`, publishing localized headings `1491` and `3586`, and refreshing the two briefing texts `0x621f50`, publishing localized headings `1491` and `3586`, and refreshing the two briefing texts
from `[state+0x4f30]` and `[+0x5ae9]`. The companion dispatcher from `[state+0x4f30]` and `[+0x5ae9]`. The companion dispatcher
@ -695,10 +943,24 @@ transition.
longer just an abstract issue table lookup because its merger callsite uses issue id `0x3a`, which longer just an abstract issue table lookup because its merger callsite uses issue id `0x3a`, which
lines up directly with localized id `726` saying merger votes depend on public attitude toward the lines up directly with localized id `726` saying merger votes depend on public attitude toward the
management of the two companies. By contrast the broader support-adjusted share-price helper management of the two companies. By contrast the broader support-adjusted share-price helper
`company_compute_public_support_adjusted_share_price_scalar` `0x00424fd0` uses the broader issue `company_compute_public_support_adjusted_share_price_scalar` `0x00424fd0` uses issue id `0x37`,
id `0x37`, which currently looks like a more general company-management or public-sentiment slot while the finance-side debt helpers now bound `0x38` and `0x39` separately as the explicit
rather than the merger-only attitude term. The supporting stat layer is bounded more cleanly now credit-rating and prime-rate lanes. That leaves `0x37` as the broader investor-confidence lane
too: the behind equity support, share price, and adjacent governance pressure, with the strongest current
text anchors coming from the investor-attitude strings `1217` and `3048/3049` about company or
chairman performance rather than from the merger-only management-attitude term. One older local lead is now ruled out too: the
`0x460a90` / `0x473620` setup block is the camera-view hotkey family over localized ids
`3482..3489` (`Select/Assign Camera View 5..8`), not issue-opinion infrastructure, so it should
no longer be used as evidence for the player-facing meaning of issue `0x37`. The editor-side
`Stock Prices` label is also no longer being used as direct evidence here: the `0x4ca980` /
`0x4cadf0` panel owns a separate float-tuning block `[state+0x0bde..0x0bf6]`, with
`[state+0x0bde]` merely mirroring `[state+0x0be2]`, so that label belongs to a different
settings family than the issue table behind `0x37`. A direct shell-resource follow-up now narrows
the remaining caption question too: the extracted `CompanyDetail.win` blob from `rt3_2WIN.PK4`
contains no plain-English investor or finance caption for this lane, which matches the owner-side
read that section-0 is a dynamic `0x947f` text widget fed by
`shell_format_company_governance_and_economy_status_panel` `0x004e5cf0` rather than a separate
fixed label row. The supporting stat layer is bounded more cleanly now too: the
surrounding `0x0b` setup in the merger and takeover offer builders is formatter mode rather than a surrounding `0x0b` setup in the merger and takeover offer builders is formatter mode rather than a
player-facing issue number, while the company-side stat wrapper player-facing issue number, while the company-side stat wrapper
`company_read_year_or_control_transfer_metric_value` `0x0042a5d0` now reads as a generic `company_read_year_or_control_transfer_metric_value` `0x0042a5d0` now reads as a generic
@ -722,14 +984,43 @@ transition.
`world_region_collection_run_building_population_pass` `0x00421c20` plus the deeper per-region `world_region_collection_run_building_population_pass` `0x00421c20` plus the deeper per-region
worker `world_region_balance_structure_demand_and_place_candidates` `0x004235c0`, while worker `world_region_balance_structure_demand_and_place_candidates` `0x004235c0`, while
`[0x006cec74+0x178]` directly fronts id `321` `Seeding Economy...` and the chunked burst helper `[0x006cec74+0x178]` directly fronts id `321` `Seeding Economy...` and the chunked burst helper
`simulation_run_chunked_fast_forward_burst`; id `322` then fronts `Calculating Heights...`. The `simulation_run_chunked_fast_forward_burst`, whose grounded tail now also sweeps the live region
collection through `world_region_refresh_cached_category_totals_and_weight_slots` `0x00423d30`;
id `322` then fronts `Calculating Heights...`. The
master `+0x68` flag is no longer just structural: the shell load/save coordinators now use the master `+0x68` flag is no longer just structural: the shell load/save coordinators now use the
same flag to force the editor-map `.gmp` family, so current evidence treats it as the broader same flag to force the editor-map `.gmp` family, so current evidence treats it as the broader
editor-map mode above those later setup branches rather than an unnamed generic toggle. That editor-map mode above those later setup branches rather than an unnamed generic toggle. That
worker is no longer one opaque building bucket: current grounded categories split into `House`, a worker is no longer one opaque building bucket: current grounded categories split into `House`, a
weighted region-profile family surfaced through the `Industry Weightings` stats path, and a third weighted region-profile family surfaced through the `Industry Weightings` stats path, and a third
branch whose low-level fallback token is `Commercial` but whose aligned stats label is `City branch whose low-level fallback token is `Commercial` but whose aligned stats label is `City
Support`. The same lower helper also reappears later on a slower simulation-side cadence with Support`. The same placement-commit gate beneath it, `world_region_validate_and_commit_candidate_placement`
`0x00422a70`, is also corroborated by the separate world-side randomized batch helper
`world_try_place_random_structure_batch_from_compact_record` `0x00430270`, which retries
placements around one compact center/radius record rather than the ordinary per-region demand
deficits. That batch placer now sits under a wider compact runtime-effect dispatcher,
`world_apply_compact_runtime_effect_record_to_resolved_targets` `0x00431b20`, so the world-side
branch is no longer just “another caller near `0x43051e`” but one real effect family alongside
the ordinary region-demand worker. Above that, the live scenario event collection at `0x0062be18`
now has a tighter runtime-effect lane too: `scenario_runtime_effect_record_service_and_dispatch_linked_compact_effects`
`0x004323a0` services one live runtime-effect record, dispatches its linked compact effects
through `0x00431b20`, and can synthesize follow-on records through
`scenario_runtime_effect_record_build_followon_effect_from_compact_record_and_targets`
`0x00430b50`, which in turn allocates new live records through
`scenario_event_collection_allocate_runtime_effect_record_from_compact_payload` `0x00432ea0`.
Above that, `scenario_event_collection_service_runtime_effect_records_for_trigger_kind`
`0x00432f40` now bounds the collection-wide loop that services those live runtime-effect records
for one trigger kind. That trigger split is tighter now too: recurring simulation maintenance
drives kinds `1`, `0`, `3`, and `2`; the neighboring route-style follow-on drives `5` and `4`;
startup-company and named-railroad creation branches drive `7`; kind `6` is now bounded as a
mixed post-change family spanning the placed-structure post-create tail, one build-version-gated
company-startup or roster-refresh tail, and the route-entry post-change sweep on `0x006cfca8`;
one world-entry-side one-shot gate drives `8` and then clears shell-profile latch
`[0x006cec7c+0x97]`; the briefing-text query lane is kind `9`; and the collection dirty latch
still forces the internal rerun at kind `0x0a`.
That moves this branch out of the “isolated world-side placement oddity” bucket and into a real
scenario-runtime effect pipeline with a mostly bounded trigger-kind family.
The same lower helper also reappears later on a slower
simulation-side cadence with
scale `1/12`, so it no longer looks like one-time setup glue only. The actual game-speed control scale `1/12`, so it no longer looks like one-time setup glue only. The actual game-speed control
family remains separate, rooted at `world_set_game_speed_mode` `0x00434680`, family remains separate, rooted at `world_set_game_speed_mode` `0x00434680`,
`world_adjust_game_speed_mode_delta` `0x00434850`, and `world_toggle_pause_or_restore_game_speed` `world_adjust_game_speed_mode_delta` `0x00434850`, and `world_toggle_pause_or_restore_game_speed`
@ -1355,7 +1646,10 @@ transition.
current anchor site, then either appends that entry through `0x004b3160` and refreshes the new current anchor site, then either appends that entry through `0x004b3160` and refreshes the new
trailing selection through `0x004b2f00`, or rotates one existing slot in place when the local trailing selection through `0x004b2f00`, or rotates one existing slot in place when the local
two-entry cap has already been reached. So this edge now reaches an actual train-side autoroute two-entry cap has already been reached. So this edge now reaches an actual train-side autoroute
append lane rather than stopping at anonymous company-side cache cells. append lane rather than stopping at anonymous company-side cache cells. The weighted cache lanes
still do not escape that route-choice family directly: this train-side append path only inherits
the weighted site and peer choice by calling `0x00408380`, not by reading cache `+0x0e/+0x16`
itself.
The train-side follow-on above that seed path is bounded now too. The owning company can count The train-side follow-on above that seed path is bounded now too. The owning company can count
its live roster through `company_count_owned_trains` `0x004264c0`, measure one aggregate linked its live roster through `company_count_owned_trains` `0x004264c0`, measure one aggregate linked
transit site pressure through `company_compute_owned_linked_transit_site_score_total` transit site pressure through `company_compute_owned_linked_transit_site_score_total`
@ -1367,7 +1661,9 @@ transition.
branch when exactly two eligible linked transit sites survive. branch when exactly two eligible linked transit sites survive.
Because it consumes `+0x12` rather than `+0x0e` or `+0x16`, current evidence now says the tracker Because it consumes `+0x12` rather than `+0x0e` or `+0x16`, current evidence now says the tracker
compatibility split is more important for seeded route choice and ranked site choice than for the compatibility split is more important for seeded route choice and ranked site choice than for the
final company train-count target itself. final company train-count target itself. The local linked-transit chain is now bounded enough to
say that directly: weighted cache lanes feed `0x00408280 -> 0x00408380 -> 0x00409770/0x00409830`,
while the separate pressure or roster lane feeds `0x00408f70 -> 0x00409950` from raw `+0x12`.
The balancer then applies two age bands to company-owned trains: The balancer then applies two age bands to company-owned trains:
very old trains are removed when the roster already exceeds target or upgraded when it still very old trains are removed when the roster already exceeds target or upgraded when it still
needs capacity, while the mid-age band can trigger one narrower upgrade pass. needs capacity, while the mid-age band can trigger one narrower upgrade pass.
@ -1469,12 +1765,16 @@ transition.
`[+0x4a8b]` and `[+0x4a87]` clear, at least two bond slots live, and at least one year since `[+0x4a8b]` and `[+0x4a87]` clear, at least two bond slots live, and at least one year since
founding. It derives one issue batch from outstanding shares rounded down to `1000`-share lots founding. It derives one issue batch from outstanding shares rounded down to `1000`-share lots
with floor `2000`, trims that batch until the broader support-adjusted share-price scalar times with floor `2000`, trims that batch until the broader support-adjusted share-price scalar times
batch no longer exceeds the `55000` gate, requires that scalar at least `22`, resolves the batch no longer exceeds the `55000` gate, recomputes the pressured support-adjusted share-price
highest-coupon live bond slot, and then uses current cash from `0x2329/0x0d` as a gate against scalar and the paired `price/book` ratio, then tests the remaining gates in a fixed order:
that slot's principal plus a small fixed buffer before it compares the chosen bond-rate lane share-price floor `22`, proceeds floor `55000`, current cash from `0x2329/0x0d` against the
against a normalized scalar ratio built from the same support-adjusted share-price lane and chosen highest-coupon bond principal plus `5000`, one later stock-issue cooldown gate that
current `Book Value Per Share` from `0x2329/0x1d` through the piecewise approval ladder converts the current issue mixed-radix calendar tuple at `[company+0x16b/+0x16f]` through
`0.07/1.3 -> 0.14/0.35`. On success it issues two `calendar_point_pack_tuple_to_absolute_counter` `0x0051d3c0` and compares the result against the
active world absolute calendar counter `[world+0x15]`, and only then the coupon-versus-price-to-book
approval ladder `0.07/1.3 -> 0.14/0.35`. The issue mutator preserves the previous tuple in
`[company+0x173/+0x177]` and refreshes the current tuple from `[world+0x0d/+0x11]` on the
derived-pricing lane. On success it issues two
same-sized tranches through repeated `company_issue_public_shares_and_raise_capital` calls and same-sized tranches through repeated `company_issue_public_shares_and_raise_capital` calls and
publishes a separate equity-offering news family rooted at localized id `4053`, not the earlier publishes a separate equity-offering news family rooted at localized id `4053`, not the earlier
debt or buyback headline family. debt or buyback headline family.
@ -1615,24 +1915,149 @@ transition.
too: category `0` falls back to `House`, category `2` is the year-gated weighted region-profile too: category `0` falls back to `House`, category `2` is the year-gated weighted region-profile
family that also feeds the localized `Industry Weightings` stats panel, and category `3` now family that also feeds the localized `Industry Weightings` stats panel, and category `3` now
reaches a separate pool-driven picker whose fallback label is `Commercial` but whose aligned reaches a separate pool-driven picker whose fallback label is `Commercial` but whose aligned
player-facing stats bucket is `City Support`. The remaining setup-side uncertainty has therefore player-facing stats bucket is `City Support`. The normalized region band is tighter too:
narrowed again: the region seed and border-overlay pair clearly complete before the `Setting up `world_region_normalize_cached_structure_balance_scalars` `0x00422320` no longer just writes an
Players and Companies...` banner is posted; `[0x006cec74+0x174]` now looks like the direct anonymous cached preview band at `[region+0x2e2/+0x2e6/+0x2ea/+0x2ee]`. Current growth-report
building-population gate; `[0x006cec74+0x178]` now looks like the direct seeding-burst gate; and evidence now grounds `[region+0x2e2]` as the weighted-profit-margin scalar and `[region+0x2ee]`
`[0x006cec74+0x68]` now aligns with editor-map mode because the same flag forces the `.gmp` family as the annual-density-adjust scalar later formatted as a percent in `Stats - City/Region`, with
in the shell file coordinators while suppressing the later building and seeding branches and `[region+0x2e6/+0x2ea]` left as the intermediate normalized-delta and clamped companion slots
diverting the deeper region worker into alternate logic. The `319` lane itself is no longer the beneath that final adjust term. The remaining setup-side uncertainty has therefore narrowed
again: the region seed and border-overlay pair clearly complete before the `Setting up Players and
Companies...` banner is posted; `[0x006cec74+0x174]` now looks like the direct building-population
gate; `[0x006cec74+0x178]` now looks like the direct seeding-burst gate; and `[0x006cec74+0x68]`
now aligns with editor-map mode because the same flag forces the `.gmp` family in the shell file
coordinators while suppressing the later building and seeding branches and diverting the deeper
region worker into alternate logic. The `319` lane itself is no longer the
open structural gap; it now clearly owns chairman-profile slot seeding, profile-record open structural gap; it now clearly owns chairman-profile slot seeding, profile-record
materialization, a shell editor surface over the same local record family, and a separate materialization, a shell editor surface over the same local record family, and a separate
live-company presentation path through the company-list window. The later interior order of that live-company presentation path through the company-list window. The later interior order of that
same `319` lane is tighter now too: after the route-entry collection refresh on `0x006cfca8` it same `319` lane is tighter now too: after the route-entry collection refresh on `0x006cfca8` it
refreshes the auxiliary route-entry tracker collection `0x006cfcb4`, then runs refreshes the auxiliary route-entry tracker collection `0x006cfcb4`, then runs
`placed_structure_collection_refresh_local_runtime_records_and_position_scalars` `0x004133b0`, `placed_structure_collection_refresh_local_runtime_records_and_position_scalars` `0x004133b0`,
then a flagged world-grid cleanup sweep through `0x00448af0/0x00533fe0`, and only after that the then a flagged world-grid cleanup sweep through the compact grid-flag query
later route-entry post-pass at `0x00491c20`. The same later lane now also reaches a separate `0x00448af0` plus the neighboring local chunk-cell write helper `0x00533fe0`, and only after
event-side runtime branch: the live event collection at `0x0062be18` re-enters that the later route-entry post-pass at `0x00491c20`. The same `319` lane is tighter internally
`scenario_event_collection_refresh_runtime_records_from_packed_state` `0x00433130`, which in now too:
turn materializes each live event record through before that later world and shell reactivation tail, `world_entry_transition_and_runtime_bringup`
runs one distinct post-bundle status and runtime refresh phase that posts progress ids `0x196`
and `0x197` through `0x005193f0/0x00540120` with paired `0x004834e0` follow-ons, refreshes the
live event collection at `0x0062be18` through
`scenario_event_collection_refresh_runtime_records_from_packed_state` `0x00433130`, rebuilds the
scenario-side port-or-warehouse cargo recipe runtime tables through `0x00435630`, and then runs
the named-candidate availability preseed through `0x00437743`. One later subphase is tighter now
too: before the broad world-reactivation sweep it posts progress ids `0x32dc/0x3714/0x3715`,
reloads one `0x108`-byte packed profile block through `0x00531150`, conditionally copies staged
runtime-profile bytes back into `0x006cec7c` while latch `[profile+0x97]` is set, mirrors the
grounded campaign-scenario bit `[profile+0xc5]` and sandbox bit `[profile+0x82]` into world
bytes `[world+0x66de]` and `[world+0x66f2]`, restores the selected year/profile lane through
`[profile+0x77]` into `[world+0x05/+0x09/+0x15]` through
`world_set_selected_year_and_refresh_calendar_presentation_state` `0x00409e80`; that restore now
has the explicit companion `world_refresh_selected_year_bucket_scalar_band` `0x00433bd0`, which
rebuilds the dependent selected-year bucket floats after the packed year changes; and then
rehydrates the named locomotive availability collection at `[world+0x66b6]` through
`locomotive_collection_refresh_runtime_availability_overrides_and_usage_state` `0x00461e00`.
That locomotive-side restore is tighter now too: its tail explicitly re-enters
`scenario_state_refresh_cached_available_locomotive_rating` `0x00436af0`, which rebuilds one
cached available-locomotive rating at `[state+0x4cbe]` from the current year plus the strongest
surviving available locomotive-side rating scalar `[loco+0x20]`, and the tiny query sibling
`0x00434080` is now bounded as the shell-side clamped read helper over that same cached field,
with the grounded shell-side reader later bucketing that value against `40/50/70/85/100`. The
same rehydrate band also refreshes the live structure-candidate filter and year-visible counts
through `structure_candidate_collection_refresh_filter_and_year_visible_counts` `0x0041e970`,
rebuilding the paired per-slot bands at `[candidates+0x246]` and `[candidates+0x16e]` and the
aggregate counts at `[candidates+0x31a]` and `[candidates+0x242]`; the same late checkpoint also
re-enters `placed_structure_collection_seed_candidate_subtype2_runtime_latch` `0x00434d40`,
which seeds runtime dword `[candidate+0x7b0]` across subtype-`2` candidate records before the
later world-wide reactivation sweep. That checkpoint also now has an explicit shell-facing scalar
publisher: `world_publish_shell_controller_progress_scalar_from_year_thresholds_or_selector_overrides`
`0x004354a0` writes one clamped `0..255` value into the current shell presentation object,
sourcing it either from the shell selector override pairs or from the scenario-side year-threshold
band rooted at `[state+0x3a/+0x51/+0x55/+0x59/+0x5d/+0x61]`; and just ahead of the later
scenario-side recipe rebuild, the same band also re-enters
`scenario_state_ensure_derived_year_threshold_band` `0x00435603`, which only falls into its
heavier rebuild body while `[state+0x3a] < 2` and otherwise leaves the derived year-threshold
companion slots `[state+0x51/+0x55/+0x59/+0x5d/+0x61]` unchanged. The neighboring late status
checkpoints around progress ids `0x196` and `0x197` also share one explicit stage gate now:
`world_query_global_stage_counter_reached_late_reactivation_threshold` `0x00444dc5` compares the
global counter `0x00620e94` against threshold `0x9901`, and the two current callers use a
negative result to clear `[world+0x39]` before the broader world and shell reactivation sweep.
The later reactivation tail is tighter now too: it includes the region-center world-grid flag
reseed pass
`0x0044c4b0`, which clears bit `0x10` across the live grid and then marks one representative
center cell for each class-`0` region through `0x00455f60`; its immediate sibling `0x0044c450`
then reruns `placed_structure_rebuild_candidate_cargo_service_bitsets` `0x0042c690` across every
live grid cell, and the next helper `0x0044ce60` scans the secondary raster at `[world+0x2135]`
for cells with any bits in mask `0x3e`, caching min/max bounds plus a marked-cell count in
`[world+0x21c6..+0x21d6]`; the larger sibling `0x0044c670` then consumes those cached bounds to
normalize the same raster and rebuild one dependent overlay/cache surface before the later
route-style rebuild, shell-window, and briefing branches. That overlay side is tighter now too:
after `0x0044c670` resolves scaled surface dimensions through `0x00534c50`, it walks one local
`3 x 32` sample lattice through the static offset tables at `0x00624b28/0x00624b48`, keeps only
secondary-raster classes `4..0x0d`, folds several interpolated `0x0051db80` samples into one
strongest local score, writes packed overlay pixels into the staged surface buffer, and only then
publishes that staged overlay through `0x00534af0`. The lower helper layer under that overlay
pass is tighter now too: `0x00534e10` is the reusable secondary-raster class-set
predicate for classes `1/3/4/5`, `0x00534e50` is the smaller neighboring class-subset predicate
for `1/4`, `0x00534ec0` covers `2/4/5`, `0x00534f00` covers `3/5`, `0x00534e90` is the
marked-bit query over the same 3-byte cell family, and
`0x00533e70` and `0x00534160` are the coarser siblings over the overlay table at `[world+0x1685]`:
the first clears coarse chunk objects across one clamped rectangle, while the second ensures one
chunk object and seeds local marks through its deeper stamp helper. One level up, the neighboring
rect owner `0x005374d0` now reads as the shared secondary-overlay refresh pass: it reruns the
local sample and unsigned-word reducers `0x00536230/0x00536420`, rebuilds the signed vector byte
planes through `0x00536710`, and then rebuilds the multiscale support surfaces through
`0x00533890`, whose inner reducers now explicitly target the packed sample-triplet buffer plus
the float and unsigned-word support planes rooted at the five-entry per-scale families
`[world+0x15f1..+0x1601]`, `[world+0x1605..+0x1615]`, and `[world+0x1619..+0x1629]`. The setup
side of that same family is tighter now too:
`0x005375c0` is the shared ensure-and-seed owner that allocates the sample, sidecar, mask,
raster, vector, and coarse-cell tables together; crucially, it seeds `[world+0x1655]` with byte
`0x02` and `[world+0x1659]` with byte `0x01`, which closes the default-fill split. The local
evidence now also supports a stronger negative conclusion: unlike `[world+0x1655]`, that second
mask plane is not part of the actively rebuilt runtime overlay path, and in the grounded local
corpus it behaves only as a separately seeded, cleared, and persisted sibling plane. One level
lower, the
base-plane allocator `0x00532c80` now reads more cleanly too: it is the narrower owner that
clears `[world+0x15e1]`, optionally applies the current grid dimensions, allocates the base
float-summary plane `[world+0x1605]`, the four sidecar byte planes `[world+0x1631..+0x163d]`,
both one-byte mask planes `[world+0x1655/+0x1659]`, and the packed secondary raster
`[world+0x165d]`, then seeds those planes with the same `0x02/0x01/0x00` default split. One
level higher again, the broader world-presentation reinitializer `0x00537e60` now sits above
that base allocator and the larger support-family ensure path `0x005375c0`: it stores the live
grid dimensions, hard-resets the whole overlay runtime family through `0x00532590`,
reinitializes the secondary-overlay family for those dimensions, and then republishes the
neighboring overlay constants and support owners used by both the world-side reattach branch and
the `.smp` restore-side presentation rebuild path, including several owners that all funnel
through the shared static-template slot allocator `0x00532ad0` over the local `0x100` pointer
band at `[world+0x08]`. Those neighboring owners are tighter now too: `0x00535070` is the small
primary overlay-surface-or-template setup owner, while `0x005356e0` and `0x00535890` seed two
larger static-template slot bands rooted at `[world+0x1568/+0x156c/+0x1574/+0x1578]` and
`[world+0x1560/+0x1564]` respectively; the remaining heavier sibling `0x00535430` now reads as a
shared four-slot overlay-surface rebuild owner that resamples one source or fallback descriptor
into a short local slot strip above `[world+0x155c]`. The tail of that same reinitializer is
tighter now too: after the larger support-family setup it seeds one seven-entry default overlay
companion set through `0x005373b0`, whose inner allocator `0x00535950` populates the local
`0x1b`-entry slot table from the static template rows `0x005dd300..0x005dd378`. The lifecycle
side is tighter in the same way now: `0x00536044` is the shared teardown owner that frees those same
three five-entry support families together with both mask planes, the packed secondary raster,
the vector-byte planes, the local staging buffer, and the neighboring sidecar or coarse-cell
tables. The remaining base-float lane is tighter too: the larger rebuild owner
`0x00538360` now clearly writes one base float-summary field into `[world+0x1605]`, clears both
one-byte mask planes, and then only repopulates the primary mask plane `[world+0x1655]` for the
qualifying class-`1` interior cells before re-entering `0x00532d90` to normalize that base
float-summary plane globally and `0x00532f60` to expand positive cells through one caller radius.
That asymmetry is now enough to close the local semantic edge: `[world+0x1655]` is the actively
rebuilt primary overlay mask, while `[world+0x1659]` is only the separately seeded and persisted
secondary mask sibling with no comparably grounded distinct read-side consumer. The only grounded
getter call to its root accessor `0x00533b60` is the shell staging branch at `0x00525bad`, and
that branch immediately discards the returned pointer. The bundle side is now explicit too:
`.smp` save-load treats the two mask planes as separate payloads with chunk ids `0x2cee` for
`[world+0x1655]` and `0x2d51` for `[world+0x1659]`, while the neighboring `0x2d49/0x2d50`
branches are the separate packed secondary-raster import lanes rather than alternate consumers
of the second mask plane. So, in the mapped local code, `0x1659` is best treated as a persisted
compatibility or seed-state sibling, not as a second actively consumed runtime overlay mask. The event
side is tighter too:
that `0x00433130` pass in turn materializes each live event record through
`scenario_event_refresh_runtime_record_from_packed_state` `0x0042db20`. Current shell-side xrefs `scenario_event_refresh_runtime_record_from_packed_state` `0x0042db20`. Current shell-side xrefs
now tighten that event branch too: the first rebuilt linked row family under `0x0042db20` aligns now tighten that event branch too: the first rebuilt linked row family under `0x0042db20` aligns
with the standalone condition list later queried by `EventConditions.win`, while the second with the standalone condition list later queried by `EventConditions.win`, while the second
@ -1754,18 +2179,31 @@ transition.
`company_compute_cached_recent_per_share_performance_subscore`, `company_compute_cached_recent_per_share_performance_subscore`,
`company_compute_five_year_weighted_shareholder_return`, and `company_compute_five_year_weighted_shareholder_return`, and
`company_compute_public_support_adjusted_share_price_scalar` bound the recent per-share `company_compute_public_support_adjusted_share_price_scalar` bound the recent per-share
performance and investor-support/share-price blend beneath those vote resolvers, performance and investor-support/share-price blend beneath those vote resolvers; the recent
per-share feeder now has a grounded four-lane tail too, with current partial-year weight
`(5 * [world+0x0f]) - 5`, prior full-year weights `48/36/24/12` on `0x1f/0x1e`, dividend
non-decline pair weights `9/8/7/6` on `0x20`, lane weights `40/10/20/30`, the startup age ramp
`0/0/0/100 -> 25/25/35/100 -> 50/50/65/100 -> 75/75/85/100 -> 100/100/100/100`, a strongest-lane
`*1.25` boost, a weakest-lane `*0.8` reduction, and separate bounded-intermediate versus final
difficulty applications under `0x005f33b8`; the next consumer `0x00424fd0` is also tighter now,
with the young-company interpolation against `[company+0x57]`, caller pressure clamped to
`[-0.2, 0.2]`, one `(shares / 20000)^0.33` share-count growth term, and the later threshold
ladder `0.6 / 0.45 / 0.3 / 1.7 / 2.5 / 4.0 / 6.0` before the issue-`0x37` multiplier,
`scenario_state_compute_issue_opinion_multiplier` now bounds the `scenario_state_compute_issue_opinion_multiplier` now bounds the
next layer of optional company, chairman, and territory-specific opinion overrides on the active next layer of optional company, chairman, and territory-specific opinion overrides on the active
scenario state, and the broader stat-reader family around scenario state, and the broader stat-reader family around
`company_read_control_transfer_metric_slot` and `company_read_control_transfer_metric_slot` and
`company_read_year_or_control_transfer_metric_value` is no longer just a merger-premium helper. `company_read_year_or_control_transfer_metric_value` is no longer just a merger-premium helper.
Current grounded callers show the same metric family feeding the annual shareholder-revolt and Current grounded callers show the same metric family feeding the annual shareholder-revolt and
creditor-liquidation lane surfaced by localized ids `300..304`, so the remaining gap is now mostly creditor-liquidation lane surfaced by localized ids `300..304`, while the debt-side shell and
semantic: the exact player-facing names of the support-and-governance metric slots behind issue bond lane now separately close `0x38` as `Credit Rating` and `0x39` as `Prime Rate`. That means
ids `0x37` and `0x3a`, plus any later chairmanship-control side effects beyond the already the remaining gap is now mostly gone on the UI side too: issue `0x37` is already bounded to the
grounded success or failure commit points. The packed simulation calendar tuple semantics also same investor-confidence family as the equity-support and governance-pressure paths, and current
remain open. The `TrackLay.win` family now clearly owns `Lay single track.` `Lay double track.` grounded UI evidence still stops at the investor-attitude sentence family rather than one
standalone caption. The calendar side is tighter now too:
`[world+0x15]` is the absolute counter for the same mixed-radix `12 x 28 x 3 x 60`
year-plus-subfield tuple packed by `0x0051d3c0` and unpacked by `0x0051d460`, not just a vague
“calendar-like” blob. The `TrackLay.win` family now clearly owns `Lay single track.` `Lay double track.`
and `Bulldoze` as its three primary modes, its bridge selector, its wrapped frequency preferences, and `Bulldoze` as its three primary modes, its bridge selector, its wrapped frequency preferences,
and a strongly aligned pair of `Auto-Hide Trees During Track Lay` and `Auto-Show Grade During and a strongly aligned pair of `Auto-Hide Trees During Track Lay` and `Auto-Show Grade During
Track Lay` toggles; the `StationPlace.win` family now clearly owns its six top-level category Track Lay` toggles; the `StationPlace.win` family now clearly owns its six top-level category
@ -2055,10 +2493,16 @@ transition.
`0x0042cb30` is the final recent-service clamp over the primary per-candidate word table. Above `0x0042cb30` is the final recent-service clamp over the primary per-candidate word table. Above
that refresh lane, `placed_structure_query_candidate_local_service_metrics` `0x0047e240` is the that refresh lane, `placed_structure_query_candidate_local_service_metrics` `0x0047e240` is the
first higher-level query, `placed_structure_count_candidates_with_local_service_metrics` first higher-level query, `placed_structure_count_candidates_with_local_service_metrics`
`0x0047e330` counts how many candidates currently produce that query, and `0x0047e330` counts how many candidates currently produce that query,
`placed_structure_get_nth_candidate_id_with_local_service_metrics` `0x0047e620` is the ordinal
selector over that same visible candidate family,
`placed_structure_query_cached_express_service_class_score` `0x0047e390` caches one parallel class `placed_structure_query_cached_express_service_class_score` `0x0047e390` caches one parallel class
score for the express family now strongly aligned with `Passengers`, `Mail`, and `Troops`. Those score for the express family now strongly aligned with `Passengers`, `Mail`, and `Troops`,
site-side scores now have grounded shell read-side consumers too: `placed_structure_refresh_candidate_local_service_comparison_cache_against_peer_site`
`0x0047eb90` rebuilds one peer-site comparison cache over the same local-service inputs, and
`placed_structure_select_best_candidate_id_by_local_service_score` `0x0047f910` is the best-hit
selector above the direct and directional local-service query pair. Those site-side scores now
have grounded shell read-side consumers too:
`shell_station_detail_format_freight_and_express_summary` `0x00506be0` formats the visible `shell_station_detail_format_freight_and_express_summary` `0x00506be0` formats the visible
`Freight: %1` and `Express: %1` lines in `StationDetail.win`, and the same station-detail family `Freight: %1` and `Express: %1` lines in `StationDetail.win`, and the same station-detail family
now has a deeper candidate-service lane: now has a deeper candidate-service lane:

353
docs/debug-load-workflow.md Normal file
View file

@ -0,0 +1,353 @@
# Debug Load Workflow
Use this when comparing:
- one successful manual load of `hh`
- one failing hook-driven auto-load attempt
The goal is to compare the real successful owner path above `0x00445ac0` against the failing hook-driven path.
## Current Findings
From the current logs:
- successful manual load now has a grounded pre-call site at `0x004390cb` with:
- `ECX = 0x02af5840`
- `[0x006cec78] = 0x02af5840`
- `[0x006cec74] = 0x01d81230`
- top-of-stack dwords:
- `arg1 = 0x01db4739`
- `arg2 = 4`
- `arg3 = 0x0022fb50`
- next dword = `0x026d7b88`
- the subsequent successful `0x00445ac0` entry still has:
- `ret = 0x004390d0`
- `arg1 = 0x01db4739`
- `arg2 = 4`
- `arg3 = 0x0022fb50`
- older failing auto-load attempts never reached `0x00445ac0`
- the earlier failing breakpoint was `0x00517cf0` with:
- `[0x006cec78] = 0`
- `[0x006cec74] = 0x01d81230`
- the staged request globals at `0x006ce9b8..0x006ce9c4` and `0x006d1270..0x006d127c` are zero on the successful manual path
That older `0x00517cf0` result is no longer the current blocker. The hook now reaches the real coordinator entry, so the remaining gap is later shell timing or re-entrancy, not request-latch shape.
The disassembly at `0x004390b0..0x004390cb` is now the strongest grounded manual-load branch:
- it writes `[0x006cec74+0x6c] = 1`
- it computes `arg1` from `([0x006cec7c] + 0x11)`
- it pushes `arg2 = 4`
- it passes `arg3 = &out_success`
- and then calls `0x00445ac0`
So any hook experiment that does not reproduce that exact shape is no longer a plausible match for the successful manual path.
## Latest Auto-Load Comparison
The newest hook-driven debugger run now reaches `0x00445ac0` directly.
At the auto-load `0x00445ac0` breakpoint:
- stack:
- `ret = 0x7650505c` inside `dinput8`
- `arg1 = 0x01db4739`
- `arg2 = 4`
- `arg3 = 0x0022fcf8`
- globals:
- `[0x006cec74] = 0x01d81230`
- `[0x006cec7c] = 0x01db4728`
- `[0x006cec78] = 0x026d7b88`
Compared to the successful manual path:
- `arg1` matches exactly: `0x01db4739`
- `arg2` matches exactly: `4`
- `[0x006cec74]` matches exactly: `0x01d81230`
- `[0x006cec7c]` still matches the same runtime-profile base used to derive `arg1`
- `[0x006cec78]` is now non-null and published before entry
So the hook is no longer missing the coordinator entry shape. The remaining question is no longer "can we reach `0x00445ac0`?" but "does the live non-debugger call return successfully and trigger the actual restore transition?"
## Latest Live Crash
The latest non-debugger auto-load run now reaches:
- `rrt-hook: auto load ready gate passed`
- `rrt-hook: auto load restore calling`
and then crashes at:
- `0x0053fea6`
The local disassembly around `0x0053fe90` shows a shell-side list traversal over `[this+0x74]` that walks linked entries and calls a virtual method on each. The crash instruction at `0x0053fea6` dereferences one traversed entry:
- `mov eax, DWORD PTR [esi]`
That strongly suggests the current hook is invoking the restore from the right call shape but on the wrong shell-pump turn. The active hypothesis is now timing or re-entrancy:
- the hook detects readiness and fires restore on the same shell-pump turn
- RT3 later re-enters shell object traversal in a phase where one list entry is still invalid
So the next experiment is to defer the actual restore by additional ready shell-pump turns instead of firing on the first ready turn.
## Manual Owner Tail
The branch at `0x004390b0..0x004390ea` now has a grounded post-call tail too:
- `0x004390cb` calls `0x00445ac0`
- `0x004390d0` immediately calls `0x004834e0(0, 1)` on `0x006cec74`
- if `out_success != 0` or `esi != 0`, `0x004390ea` calls `0x004384d0`
- then `0x004390ef` calls `0x0053f310` on `0x00ccbb20`
- then `0x00439104` calls `0x004834e0(0, 1)` again
The successful manual breakpoint at `0x004390cb` shows `ESI = 0` and `EDI = 1`, so the manual load branch only forces the `0x004384d0` post-load pipeline when `out_success` comes back nonzero.
That makes the current hook gap narrower still: even with the correct `0x00445ac0` arguments, returning directly into `dinput8` skips RT3's own owner-tail work unless we mirror it ourselves.
## Owner Xrefs Above `0x438890`
The containing owner at `0x00438890` is now grounded as a larger `thiscall` shell owner with two stack arguments. Current xrefs found in local disassembly are:
- `0x00443b57`
- `0x00446d7f`
- `0x0046b8bc`
- `0x004830ca`
The strongest caller so far is `0x004830ca`:
- it publishes `0x006cec78 = eax`
- then calls `0x00438890` as `thiscall(active_mode, 1, 0)`
- it sits inside `shell_transition_mode`
- it is the branch that constructs `LoadScreen.win` through `0x004ea620`
- and it continues through shell-window follow-up on `0x006d401c` after the `0x00438890` call
The surrounding mode map is tighter now too:
- mode `1` = `Game.win`
- mode `2` = `Setup.win`
- mode `3` = `Video.win`
- mode `4` = `LoadScreen.win`
- mode `5` = `Multiplayer.win`
- mode `6` = `Credits.win`
- mode `7` = `Campaign.win`
That makes `0x00438890(active_mode, 1, 0)` the strongest current RT3-native entry candidate for reproducing the successful manual load branch, because it owns the internal dispatch that later reaches `0x004390cb`.
Current static xrefs also tighten the broader ownership split:
- `0x00443b57` calls `0x00438890` from the world-entry side, but with `(0, 0)` after dismissing the current shell detail panel and servicing `0x4834e0(0, 0)`
- `0x00446d7f` calls it from the saved-runtime restore side with the same `(0, 0)` shape before immediately building `.smp` bundle payloads through `0x530c80/0x531150/0x531360`
- `0x0046b8bc` calls it from the multiplayer preview family before a later `0x00445ac0` call
- `0x004830ca` calls it from the shell-side active-mode branch with the clearest `(1, 0)` setup
So the function is no longer just a guessed hook target. It is now a real shared owner above world-entry, saved-runtime restore, multiplayer preview, and shell-side active-mode startup branches.
The internal selector split inside `0x00438890` is tighter now too:
- `[0x006cec7c+0x01]` is a startup-profile selector, not the shell mode id
- selector values `1` and `7` share the tutorial lane at `0x00438f67`, which writes
`[0x006cec74+0x6c] = 2` and loads `Tutorial_2.gmp` or `Tutorial_1.gmp`
- selector `2` is a world-root initialization lane at `0x00438fbe` that allocates `0x0062c120`
when needed, runs `0x0044faf0`, and then forces the selector to `3`
- selector `4` is a setup-side world reset or regeneration lane at `0x00439038` that rebuilds
`0x0062c120` from setup globals `0x006d14cc/0x006d14d0`, then runs `0x00535100` and `0x0040b830`
- selector values `3`, `5`, and `6` collapse into the same profile-seeded file-load lane at
`0x004390b0..0x004390ea`
- selector `6` is the one variant that explicitly writes `[0x006cec74+0x6c] = 1` before the
shared file-load call
Current grounded writers now tighten those values too:
- `Campaign.win` writes selector `6` at `0x004b8a2f`
- `Multiplayer.win` writes selector `3` on one pending-status branch at `0x004f041e`
- the larger `Setup.win` dispatcher around `0x005033d0..0x00503b7b` writes selectors `2`, `3`, `4`,
and `5` on several validated launch branches
- so the shared file-load lane is now best read as one reused profile-file startup family rather
than one owner-specific manual-load path
That means the successful manual-load branch is not the whole function. It is one three-selector
subfamily inside a broader startup dispatcher that also owns tutorial and fresh-world setup lanes.
The multiplayer preview side is also tighter now:
- `0x0046b8bc` publishes `0x006cec78`
- calls `0x00438890` as `thiscall(active_mode, 0, 0)`
- clears `[0x006cec74+0x6c]`
- and only then calls `0x00445ac0(0x006ce630, [0x006ce9c0], 0)`
That makes the preview relaunch path clearly different from the manual load branch, not just a differently staged copy of it.
## Latest Headless Debugger Result
The scripted auto-load debugger run is now useful without manual interaction:
- all breakpoints were set successfully:
- `0x00438890`
- `0x004390cb`
- `0x00445ac0`
- `0x0053fea6`
- but only `0x0053fea6` actually fired in the captured run
So the current non-interactive path is good enough to gather repeatable crash-side state, but it also tells us that the current auto-load code path is still not obviously traversing the larger-owner breakpoints under `winedbg`. The next step is therefore more hook-side logging around the `0x00438890` call itself rather than more manual debugger work.
The latest static pivot also means the next reverse-engineering step does not require a live run:
- compare the mode-`4` `LoadScreen.win` owner path at `0x004830ca` against the world-entry and
saved-runtime callers of `0x00438890`
- compare how the `(1, 0)` `LoadScreen.win` lane diverges from the `(0, 0)` world-entry and
saved-runtime lanes before control reaches the shared `0x004390b0` manual-load branch
- only then return to hook experiments
## Launchers
Manual debugger run:
```bash
tools/run_rt3_winedbg.sh
```
Auto-load debugger run:
```bash
tools/run_hook_auto_load_winedbg.sh hh
```
Both scripts use `/opt/wine-stable/bin/winedbg` explicitly, so they do not depend on `winedbg` being on `PATH`.
They also default to:
- their matching command file in [tools/](/home/jan/projects/rrt/tools)
- a logfile in the repo root:
- [rt3_manual_load_winedbg.log](/home/jan/projects/rrt/rt3_manual_load_winedbg.log)
- [rt3_auto_load_winedbg.log](/home/jan/projects/rrt/rt3_auto_load_winedbg.log)
To save the full interactive debugger session to a file, set `RRT_WINEDBG_LOG`:
```bash
RRT_WINEDBG_LOG=/tmp/rt3-manual-load-winedbg.log tools/run_rt3_winedbg.sh
```
or:
```bash
RRT_WINEDBG_LOG=/tmp/rt3-auto-load-winedbg.log tools/run_hook_auto_load_winedbg.sh hh
```
Those wrappers use `script`, so both the commands you type and the debugger output are captured.
`winedbg` under `/opt/wine-stable` also supports command files directly:
```bash
tools/run_rt3_winedbg.sh
```
and:
```bash
tools/run_hook_auto_load_winedbg.sh hh
```
Override either default if needed:
```bash
RRT_WINEDBG_LOG=/tmp/rt3-manual-load-winedbg.log tools/run_rt3_winedbg.sh
```
Ready-made debugger command files are also provided:
- [winedbg_manual_load_445ac0.cmd](/home/jan/projects/rrt/tools/winedbg_manual_load_445ac0.cmd)
- [winedbg_auto_load_compare.cmd](/home/jan/projects/rrt/tools/winedbg_auto_load_compare.cmd)
If you do not use `RRT_WINEDBG_CMD_FILE`, you can still open those files and paste their contents into the debugger manually.
Both scripts rebuild `rrt-hook`, copy `dinput8.dll` into the Wine RT3 directory, and launch RT3 under `winedbg`.
## Successful Manual Load
1. Launch:
```bash
tools/run_rt3_winedbg.sh
```
2. The default command file now breaks on both:
- `0x004390cb` first
- `0x00445ac0` second
3. In RT3, load save `hh` manually.
4. The command file will dump:
- registers
- top-of-stack dwords
- `0x006cec74`
- `0x006cec7c`
- `0x006cec78`
- `0x006ce9b8..0x006ce9c4`
- `0x006d1270..0x006d127c`
- backtrace
Focus on:
- whether the first hit is `0x004390cb` or `0x00445ac0`
- caller address
- `ecx`
- the three stack arguments
- `0x006cec74`
- `0x006cec7c`
- `0x006cec78`
- `0x006ce9b8..0x006ce9c4`
- `0x006d1270..`
## Failing Auto-Load Run
1. Launch:
```bash
tools/run_hook_auto_load_winedbg.sh hh
```
2. The default command file now scripts a fuller non-interactive capture sequence:
- `0x00438890`
- `0x004390cb`
- `0x00445ac0`
- `0x0053fea6`
3. Let the hook run.
4. The command file will dump the same register, stack, global, and backtrace state at the first hit.
5. Compare that output directly against the successful manual run.
So the current auto debugger path is now mostly headless:
- launch `tools/run_hook_auto_load_winedbg.sh hh`
- let the scripted breakpoints run
- inspect [rt3_auto_load_winedbg.log](/home/jan/projects/rrt/rt3_auto_load_winedbg.log)
Manual typing is no longer required for the main auto-load comparison path unless we need an additional ad hoc breakpoint.
If the run still crashes and you need even earlier crash-side inspection after that, add one temporary extra breakpoint manually for:
- `0x00517cf0`
## Optional Host-Side GDB Fallback
If `winedbg` is too clumsy for repeated crashes, attach host `gdb` to the crashing Wine process after RT3 starts:
```bash
pgrep -af 'wine.*RT3.exe'
gdb -p <pid>
```
Useful commands in `gdb`:
```gdb
set pagination off
handle SIGSEGV stop print
continue
bt
info registers
x/16wx $esp
```
This is mainly for cleaner backtraces after the fault PC is already known from `winedbg`.

170
docs/shell-load-graph.md Normal file
View file

@ -0,0 +1,170 @@
# Shell Load Graph
Generated shell-load startup graph artifacts live under `artifacts/exports/rt3-1.06/`:
- `shell-load-subgraph.dot`
- `shell-load-subgraph.md`
- `setup-window-subgraph.dot`
- `setup-window-subgraph.md`
- `setup-window-submodes-depth5-subgraph.dot`
- `setup-window-submodes-depth5-subgraph.md`
- `setup-window-submodes-depth5-forward-subgraph.dot`
- `setup-window-submodes-depth5-forward-subgraph.md`
- `runtime-effect-service-depth7-subgraph.dot`
- `runtime-effect-service-depth7-subgraph.md`
- `runtime-effect-service-depth7-forward-subgraph.dot`
- `runtime-effect-service-depth7-forward-subgraph.md`
The current graph is intentionally bounded rather than whole-program:
- seed nodes:
- `0x00438890` `shell_active_mode_run_profile_startup_and_load_dispatch`
- `0x00482ec0` `shell_transition_mode`
- traversal:
- note-address references in `function-map.csv`
- depth `2`
- backreferences enabled
This keeps the artifact useful for naming and branch comparison instead of exploding into generic
utility helpers.
The paired `Setup.win` graph is a narrower owner-family export:
- seeds:
- `0x00504010` `shell_setup_window_construct`
- `0x005033d0` `shell_setup_window_handle_message`
- `0x00502c00` `shell_setup_window_select_launch_mode_and_apply_shell_state`
- `0x00502910` `shell_setup_window_refresh_mode_dependent_lists`
- `0x00502550` `shell_setup_window_refresh_selection_lists_and_summary_fields`
- `0x00502220` `shell_setup_window_publish_selected_profile_labels_and_preview_surface`
Regenerate it with:
```bash
python3 tools/py/export_function_subgraph.py \
artifacts/exports/rt3-1.06/function-map.csv \
artifacts/exports/rt3-1.06/shell-load-subgraph \
--seed 0x00438890 \
--seed 0x00482ec0 \
--depth 2 \
--include-backrefs \
--title "Shell Load Startup Subgraph"
```
And for the narrower `Setup.win` graph:
```bash
python3 tools/py/export_function_subgraph.py \
artifacts/exports/rt3-1.06/function-map.csv \
artifacts/exports/rt3-1.06/setup-window-subgraph \
--seed 0x00504010 \
--seed 0x005033d0 \
--seed 0x00502c00 \
--seed 0x00502910 \
--seed 0x00502550 \
--seed 0x00502220 \
--depth 2 \
--include-backrefs \
--title "Setup Window Dispatch Subgraph"
```
The current exporter is intentionally note-driven:
- nodes come from `function-map.csv`
- edges come from address references embedded in notes
- so it is best used as a bounded reasoning aid over already-curated rows, not as a substitute for
raw disassembly or xref recovery
There is now also a deeper `Setup.win` submode export:
- the backref-enabled depth-5 variant is exploratory and broad
- seeds: the same constructor, dispatcher, selector, and refresh owners
- depth: `5`
- backrefs: enabled
- current size: `369` nodes / `753` edges
- the forward-only depth-5 variant is the recommended artifact for submode work
- seeds: the same constructor, dispatcher, selector, and refresh owners
- depth: `5`
- backrefs: disabled
- current size: `126` nodes / `246` edges
Generate the exploratory backref-enabled graph with:
```bash
python3 tools/py/export_function_subgraph.py \
artifacts/exports/rt3-1.06/function-map.csv \
artifacts/exports/rt3-1.06/setup-window-submodes-depth5-subgraph \
--seed 0x00504010 \
--seed 0x005033d0 \
--seed 0x00502c00 \
--seed 0x00502910 \
--seed 0x005027b0 \
--seed 0x00502550 \
--seed 0x00502220 \
--depth 5 \
--include-backrefs \
--title "Setup Window Submode Subgraph (Depth 5)"
```
Generate the narrower forward-only graph with:
```bash
python3 tools/py/export_function_subgraph.py \
artifacts/exports/rt3-1.06/function-map.csv \
artifacts/exports/rt3-1.06/setup-window-submodes-depth5-forward-subgraph \
--seed 0x00504010 \
--seed 0x005033d0 \
--seed 0x00502c00 \
--seed 0x00502910 \
--seed 0x005027b0 \
--seed 0x00502550 \
--seed 0x00502220 \
--depth 5 \
--title "Setup Window Submode Subgraph (Depth 5, Forward Only)"
```
There is now also a deeper runtime-effect export around the scenario event collection service loop:
- the backref-enabled depth-7 variant is exploratory and broad
- seeds:
- `0x004323a0` `scenario_runtime_effect_record_service_and_dispatch_linked_compact_effects`
- `0x00431b20` `world_apply_compact_runtime_effect_record_to_resolved_targets`
- `0x00430b50` `scenario_runtime_effect_record_build_followon_effect_from_compact_record_and_targets`
- `0x00432ea0` `scenario_event_collection_allocate_runtime_effect_record_from_compact_payload`
- depth: `7`
- backrefs: enabled
- current size: `525` nodes / `1235` edges
- the forward-only depth-7 variant is the recommended artifact for this branch
- same seeds
- depth: `7`
- backrefs: disabled
- current size: `293` nodes / `752` edges
Generate the exploratory backref-enabled graph with:
```bash
python3 tools/py/export_function_subgraph.py \
artifacts/exports/rt3-1.06/function-map.csv \
artifacts/exports/rt3-1.06/runtime-effect-service-depth7-subgraph \
--seed 0x004323a0 \
--seed 0x00431b20 \
--seed 0x00430b50 \
--seed 0x00432ea0 \
--depth 7 \
--include-backrefs \
--title "Scenario Runtime Effect Service Subgraph (Depth 7)"
```
Generate the narrower forward-only graph with:
```bash
python3 tools/py/export_function_subgraph.py \
artifacts/exports/rt3-1.06/function-map.csv \
artifacts/exports/rt3-1.06/runtime-effect-service-depth7-forward-subgraph \
--seed 0x004323a0 \
--seed 0x00431b20 \
--seed 0x00430b50 \
--seed 0x00432ea0 \
--depth 7 \
--title "Scenario Runtime Effect Service Subgraph (Depth 7, Forward Only)"
```

View file

@ -0,0 +1,260 @@
#!/usr/bin/env python3
from __future__ import annotations
import argparse
import csv
import re
from collections import deque
from dataclasses import dataclass
from pathlib import Path
ADDRESS_RE = re.compile(r"0x[0-9a-fA-F]{8}")
@dataclass(frozen=True)
class Row:
address: int
size: str
name: str
subsystem: str
calling_convention: str
prototype_status: str
source_tool: str
confidence: str
notes: str
verified_against: str
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(
description="Export a bounded function subgraph from function-map.csv notes."
)
parser.add_argument("function_map", type=Path)
parser.add_argument("output_prefix", type=Path)
parser.add_argument(
"--seed",
action="append",
default=[],
help="Seed function address in hex. May be repeated.",
)
parser.add_argument(
"--depth",
type=int,
default=2,
help="Traversal depth over note-address references.",
)
parser.add_argument(
"--title",
default="Function Subgraph",
help="Title used in the markdown summary.",
)
parser.add_argument(
"--include-backrefs",
action="store_true",
help="Also traverse rows that reference currently included nodes.",
)
args = parser.parse_args()
if not args.seed:
parser.error("at least one --seed is required")
return args
def parse_hex(text: str) -> int:
value = text.strip().lower()
if value.startswith("0x"):
value = value[2:]
return int(value, 16)
def fmt_addr(value: int) -> str:
return f"0x{value:08x}"
def load_rows(path: Path) -> dict[int, Row]:
with path.open(newline="", encoding="utf-8") as handle:
reader = csv.DictReader(handle)
rows: dict[int, Row] = {}
for raw in reader:
address = parse_hex(raw["address"])
rows[address] = Row(
address=address,
size=raw["size"],
name=raw["name"],
subsystem=raw["subsystem"],
calling_convention=raw["calling_convention"],
prototype_status=raw["prototype_status"],
source_tool=raw["source_tool"],
confidence=raw["confidence"],
notes=raw["notes"],
verified_against=raw["verified_against"],
)
return rows
def extract_note_refs(rows: dict[int, Row]) -> dict[int, set[int]]:
refs: dict[int, set[int]] = {}
known = set(rows)
for address, row in rows.items():
hits = {parse_hex(match.group(0)) for match in ADDRESS_RE.finditer(row.notes)}
refs[address] = {hit for hit in hits if hit in known and hit != address}
return refs
def build_backrefs(refs: dict[int, set[int]]) -> dict[int, set[int]]:
backrefs: dict[int, set[int]] = {address: set() for address in refs}
for src, dsts in refs.items():
for dst in dsts:
backrefs.setdefault(dst, set()).add(src)
return backrefs
def walk_subgraph(
rows: dict[int, Row],
refs: dict[int, set[int]],
seeds: list[int],
depth: int,
include_backrefs: bool,
) -> set[int]:
backrefs = build_backrefs(refs)
seen: set[int] = set()
queue: deque[tuple[int, int]] = deque((seed, 0) for seed in seeds if seed in rows)
while queue:
address, level = queue.popleft()
if address in seen:
continue
seen.add(address)
if level >= depth:
continue
for dst in sorted(refs.get(address, ())):
if dst not in seen:
queue.append((dst, level + 1))
if include_backrefs:
for src in sorted(backrefs.get(address, ())):
if src not in seen:
queue.append((src, level + 1))
return seen
def quote_dot(text: str) -> str:
return text.replace("\\", "\\\\").replace('"', '\\"')
def emit_dot(
rows: dict[int, Row],
refs: dict[int, set[int]],
included: set[int],
seeds: set[int],
output_path: Path,
title: str,
) -> None:
subsystems: dict[str, list[Row]] = {}
for address in sorted(included):
row = rows[address]
subsystems.setdefault(row.subsystem, []).append(row)
lines: list[str] = [
"digraph shell_load {",
' graph [rankdir=LR, labelloc="t", labeljust="l"];',
f' label="{quote_dot(title)}";',
' node [shape=box, style="rounded,filled", fillcolor="#f8f8f8", color="#555555", fontname="Helvetica"];',
' edge [color="#666666", fontname="Helvetica"];',
]
for subsystem in sorted(subsystems):
cluster_id = subsystem.replace("-", "_")
lines.append(f' subgraph cluster_{cluster_id} {{')
lines.append(f' label="{quote_dot(subsystem)}";')
lines.append(' color="#cccccc";')
for row in subsystems[subsystem]:
seed_mark = " [seed]" if row.address in seeds else ""
label = f"{fmt_addr(row.address)}\\n{row.name}{seed_mark}"
fill = "#ffe9a8" if row.address in seeds else "#f8f8f8"
lines.append(
f' "{fmt_addr(row.address)}" [label="{quote_dot(label)}", fillcolor="{fill}"];'
)
lines.append(" }")
for src in sorted(included):
for dst in sorted(refs.get(src, ())):
if dst not in included:
continue
lines.append(f' "{fmt_addr(src)}" -> "{fmt_addr(dst)}";')
lines.append("}")
output_path.write_text("\n".join(lines) + "\n", encoding="utf-8")
def emit_markdown(
rows: dict[int, Row],
refs: dict[int, set[int]],
included: set[int],
seeds: set[int],
output_path: Path,
title: str,
dot_path: Path,
) -> None:
included_rows = [rows[address] for address in sorted(included)]
edge_count = sum(
1
for src in included
for dst in refs.get(src, ())
if dst in included
)
lines = [
f"# {title}",
"",
f"- Nodes: `{len(included_rows)}`",
f"- Edges: `{edge_count}`",
f"- Seeds: {', '.join(f'`{fmt_addr(seed)}`' for seed in sorted(seeds))}",
f"- Graphviz: `{dot_path.name}`",
"",
"## Nodes",
"",
"| Address | Name | Subsystem | Confidence |",
"| --- | --- | --- | --- |",
]
for row in included_rows:
lines.append(
f"| `{fmt_addr(row.address)}` | `{row.name}` | `{row.subsystem}` | `{row.confidence}` |"
)
lines.extend(["", "## Edges", ""])
for src in sorted(included):
dsts = [dst for dst in sorted(refs.get(src, ())) if dst in included]
if not dsts:
continue
src_row = rows[src]
lines.append(f"- `{fmt_addr(src)}` `{src_row.name}`")
for dst in dsts:
dst_row = rows[dst]
lines.append(f" -> `{fmt_addr(dst)}` `{dst_row.name}`")
output_path.write_text("\n".join(lines) + "\n", encoding="utf-8")
def main() -> int:
args = parse_args()
rows = load_rows(args.function_map)
refs = extract_note_refs(rows)
seeds = [parse_hex(seed) for seed in args.seed]
included = walk_subgraph(rows, refs, seeds, args.depth, args.include_backrefs)
output_prefix = args.output_prefix.resolve()
output_prefix.parent.mkdir(parents=True, exist_ok=True)
dot_path = output_prefix.with_suffix(".dot")
md_path = output_prefix.with_suffix(".md")
emit_dot(rows, refs, included, set(seeds), dot_path, args.title)
emit_markdown(rows, refs, included, set(seeds), md_path, args.title, dot_path)
return 0
if __name__ == "__main__":
raise SystemExit(main())

43
tools/run_hook_auto_load.sh Executable file
View file

@ -0,0 +1,43 @@
#!/usr/bin/env bash
set -euo pipefail
repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
game_dir="$repo_root/rt3_wineprefix/drive_c/rt3"
log_path="$game_dir/rrt_hook_attach.log"
proxy_path="$game_dir/dinput8.dll"
save_stem="${1:-hh}"
timeout_seconds="${RRT_HOOK_TIMEOUT_SECONDS:-60}"
. "$HOME/.local/share/cargo/env"
cargo build -p rrt-hook --target i686-pc-windows-gnu
rm -f "$log_path"
cp "$repo_root/target/i686-pc-windows-gnu/debug/dinput8.dll" "$proxy_path"
echo "launcher: start=$(date --iso-8601=seconds)"
echo "launcher: save_stem=$save_stem timeout_seconds=$timeout_seconds"
set +e
(
cd "$game_dir"
export RRT_AUTO_LOAD_SAVE="$save_stem"
export WINEPREFIX="$repo_root/rt3_wineprefix"
export WINEDLLOVERRIDES="dinput8=n,b"
timeout "${timeout_seconds}s" /opt/wine-stable/bin/wine RT3.exe
) >/tmp/rrt-hook-auto-load-wine.log 2>&1 &
launcher_pid=$!
echo "launcher: wrapper_pid=$launcher_pid"
wait "$launcher_pid"
launcher_status=$?
set -e
echo "launcher: exit_status=$launcher_status"
echo "launcher: end=$(date --iso-8601=seconds)"
if [[ -f "$log_path" ]]; then
cat "$log_path"
else
echo "attach-log-missing" >&2
exit 1
fi

View file

@ -0,0 +1,31 @@
#!/usr/bin/env bash
set -euo pipefail
repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
game_dir="$repo_root/rt3_wineprefix/drive_c/rt3"
proxy_path="$game_dir/dinput8.dll"
save_stem="${1:-hh}"
. "$HOME/.local/share/cargo/env"
cargo build -p rrt-hook --target i686-pc-windows-gnu
cp "$repo_root/target/i686-pc-windows-gnu/debug/dinput8.dll" "$proxy_path"
cd "$game_dir"
export RRT_AUTO_LOAD_SAVE="$save_stem"
export WINEPREFIX="$repo_root/rt3_wineprefix"
export WINEDLLOVERRIDES="dinput8=n,b"
cmd=(/opt/wine-stable/bin/winedbg)
cmd_file="${RRT_WINEDBG_CMD_FILE:-$repo_root/tools/winedbg_auto_load_compare.cmd}"
if [[ -n "$cmd_file" ]]; then
cmd+=(--file "$cmd_file")
fi
cmd+=(RT3.exe)
log_file="${RRT_WINEDBG_LOG:-$repo_root/rt3_auto_load_winedbg.log}"
if [[ -n "$log_file" ]]; then
cmd_string="$(printf '%q ' "${cmd[@]}")"
exec script -qefc "${cmd_string% }" "$log_file"
fi
exec "${cmd[@]}"

View file

@ -13,9 +13,11 @@ cargo build -p rrt-hook --target i686-pc-windows-gnu
rm -f "$log_path" rm -f "$log_path"
cp "$repo_root/target/i686-pc-windows-gnu/debug/dinput8.dll" "$proxy_path" cp "$repo_root/target/i686-pc-windows-gnu/debug/dinput8.dll" "$proxy_path"
#RRT_WRITE_FINANCE_TEMPLATE=1 \
( (
cd "$game_dir" cd "$game_dir"
timeout 8s env \ timeout 600s env \
RRT_WRITE_FINANCE_CAPTURE=1 \
WINEPREFIX="$repo_root/rt3_wineprefix" \ WINEPREFIX="$repo_root/rt3_wineprefix" \
WINEDLLOVERRIDES="dinput8=n,b" \ WINEDLLOVERRIDES="dinput8=n,b" \
/opt/wine-stable/bin/wine RT3.exe /opt/wine-stable/bin/wine RT3.exe

29
tools/run_rt3_winedbg.sh Executable file
View file

@ -0,0 +1,29 @@
#!/usr/bin/env bash
set -euo pipefail
repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
game_dir="$repo_root/rt3_wineprefix/drive_c/rt3"
proxy_path="$game_dir/dinput8.dll"
. "$HOME/.local/share/cargo/env"
cargo build -p rrt-hook --target i686-pc-windows-gnu
cp "$repo_root/target/i686-pc-windows-gnu/debug/dinput8.dll" "$proxy_path"
cd "$game_dir"
export WINEPREFIX="$repo_root/rt3_wineprefix"
export WINEDLLOVERRIDES="dinput8=n,b"
cmd=(/opt/wine-stable/bin/winedbg)
cmd_file="${RRT_WINEDBG_CMD_FILE:-$repo_root/tools/winedbg_manual_load_445ac0.cmd}"
if [[ -n "$cmd_file" ]]; then
cmd+=(--file "$cmd_file")
fi
cmd+=(RT3.exe)
log_file="${RRT_WINEDBG_LOG:-$repo_root/rt3_manual_load_winedbg.log}"
if [[ -n "$log_file" ]]; then
cmd_string="$(printf '%q ' "${cmd[@]}")"
exec script -qefc "${cmd_string% }" "$log_file"
fi
exec "${cmd[@]}"

View file

@ -0,0 +1,76 @@
break *0x00438890
break *0x004390cb
break *0x00445ac0
break *0x0053fea6
cont
info reg
print/x *(unsigned int*)($esp)
print/x *(unsigned int*)($esp+4)
print/x *(unsigned int*)($esp+8)
print/x *(unsigned int*)($esp+12)
print/x *(unsigned int*)0x006cec74
print/x *(unsigned int*)0x006cec7c
print/x *(unsigned int*)0x006cec78
print/x *(unsigned int*)0x006ce9b8
print/x *(unsigned int*)0x006ce9bc
print/x *(unsigned int*)0x006ce9c0
print/x *(unsigned int*)0x006ce9c4
print/x *(unsigned int*)0x006d1270
print/x *(unsigned int*)0x006d1274
print/x *(unsigned int*)0x006d1278
print/x *(unsigned int*)0x006d127c
bt
cont
info reg
print/x *(unsigned int*)($esp)
print/x *(unsigned int*)($esp+4)
print/x *(unsigned int*)($esp+8)
print/x *(unsigned int*)($esp+12)
print/x *(unsigned int*)0x006cec74
print/x *(unsigned int*)0x006cec7c
print/x *(unsigned int*)0x006cec78
print/x *(unsigned int*)0x006ce9b8
print/x *(unsigned int*)0x006ce9bc
print/x *(unsigned int*)0x006ce9c0
print/x *(unsigned int*)0x006ce9c4
print/x *(unsigned int*)0x006d1270
print/x *(unsigned int*)0x006d1274
print/x *(unsigned int*)0x006d1278
print/x *(unsigned int*)0x006d127c
bt
cont
info reg
print/x *(unsigned int*)($esp)
print/x *(unsigned int*)($esp+4)
print/x *(unsigned int*)($esp+8)
print/x *(unsigned int*)($esp+12)
print/x *(unsigned int*)0x006cec74
print/x *(unsigned int*)0x006cec7c
print/x *(unsigned int*)0x006cec78
print/x *(unsigned int*)0x006ce9b8
print/x *(unsigned int*)0x006ce9bc
print/x *(unsigned int*)0x006ce9c0
print/x *(unsigned int*)0x006ce9c4
print/x *(unsigned int*)0x006d1270
print/x *(unsigned int*)0x006d1274
print/x *(unsigned int*)0x006d1278
print/x *(unsigned int*)0x006d127c
bt
cont
info reg
print/x *(unsigned int*)($esp)
print/x *(unsigned int*)($esp+4)
print/x *(unsigned int*)($esp+8)
print/x *(unsigned int*)($esp+12)
print/x *(unsigned int*)0x006cec74
print/x *(unsigned int*)0x006cec7c
print/x *(unsigned int*)0x006cec78
print/x *(unsigned int*)0x006ce9b8
print/x *(unsigned int*)0x006ce9bc
print/x *(unsigned int*)0x006ce9c0
print/x *(unsigned int*)0x006ce9c4
print/x *(unsigned int*)0x006d1270
print/x *(unsigned int*)0x006d1274
print/x *(unsigned int*)0x006d1278
print/x *(unsigned int*)0x006d127c
bt

View file

@ -0,0 +1,20 @@
break *0x004390cb
break *0x00445ac0
cont
info reg
print/x *(unsigned int*)($esp)
print/x *(unsigned int*)($esp+4)
print/x *(unsigned int*)($esp+8)
print/x *(unsigned int*)($esp+12)
print/x *(unsigned int*)0x006cec74
print/x *(unsigned int*)0x006cec7c
print/x *(unsigned int*)0x006cec78
print/x *(unsigned int*)0x006ce9b8
print/x *(unsigned int*)0x006ce9bc
print/x *(unsigned int*)0x006ce9c0
print/x *(unsigned int*)0x006ce9c4
print/x *(unsigned int*)0x006d1270
print/x *(unsigned int*)0x006d1274
print/x *(unsigned int*)0x006d1278
print/x *(unsigned int*)0x006d127c
bt