acdream/docs/research/2026-07-13-retail-skill-training-confirmation-pseudocode.md
Erik 85980fe13f fix(ui): restore retail skill training confirmation
Route untrained-skill promotion through DialogFactory with retail's exact prompt and stored skill/credit properties. Defer the character-sheet refresh until accept or reject completes while leaving trained-skill XP raises immediate.

Co-Authored-By: Codex <codex@openai.com>
2026-07-13 17:57:29 +02:00

79 lines
2.6 KiB
Markdown

# Retail skill-training confirmation
Date: 2026-07-13
## Scope
This note covers the confirmation shown when an untrained skill is promoted to
trained by spending skill credits. It does **not** apply when experience is
added to an already trained or specialized skill; those raises are sent
immediately.
## Named retail sources
- `gmSkillUI::TrainSkillDialogCallback @ 0x0049C210`
- `gmSkillUI::TrainSkill @ 0x0049C5F0`
- `gmSkillUI::RaiseSelection @ 0x0049C8C0`
- `DialogFactory::MakeCallbackDialogInCurrentUI @ 0x00478430`
- literal `data_7A7864`: `Are you sure you want to spend %d credits to train %s?`
The dialog property keys are shared with the existing DialogFactory port:
```text
0x8E dialog type (1 = ConfirmationDialog)
0x92 confirmation result written by ConfirmationDialog
0xC5 message StringInfo
0x10000040 selected skill id
0x10000041 skill-credit cost
```
## Pseudocode
```text
gmSkillUI.TrainSkill(playerQualities):
if selectedSkill == 0:
return false
skillName = SkillSystem.InqSkillName(selectedSkill)
creditCost = GetCostToRaise(playerQualities)
message = format(
"Are you sure you want to spend %d credits to train %s?",
creditCost,
skillName)
data = PropertyCollection()
data[0x8E] = 1 // ConfirmationDialog
data[0xC5] = message
data[0x10000040] = selectedSkill
data[0x10000041] = creditCost
DialogFactory.MakeCallbackDialogInCurrentUI(
data,
gmSkillUI.TrainSkillDialogCallback)
return true
gmSkillUI.TrainSkillDialogCallback(data):
if data.bool(0x92) is not true:
return
skillId = data.uint(0x10000040)
creditCost = data.uint(0x10000041)
if skillId != 0 and creditCost != 0:
CM_Train.Event_TrainSkillAdvancementClass(skillId, creditCost)
```
`gmSkillUI::RaiseSelection` branches on advancement class. Untrained skills
enter `TrainSkill`; trained/specialized skills call `CM_Train::Event_TrainSkill`
directly with the XP cost. Therefore a confirmation on every skill XP raise
would be a retail divergence.
## Cross-reference
- ACE `GameActionTrainSkill.Handle` reads exactly two fields, skill id and
credits spent, then calls `HandleActionTrainSkill`.
- Chorizite's generated native client bindings expose
`CM_Train::Event_TrainSkillAdvancementClass(UInt32 skill, UInt32 credits)` and
separately expose `CM_Train::Event_TrainSkill(UInt32 skill, UInt32 xp)`.
These corroborate the callback's two stored properties and the distinction
between training a skill and adding XP to a trained skill. The named retail
client remains the behavioral oracle for when the dialog appears.