# doc

This is documentation relevant to the project.
SQL files are master.mdb dumps labeled by date and region.
This file is my notes from exploring the database.

# text_data categories

- 6 is character names, 4 is [variant] character name, 5 is [variant], 14 is clothing names
- 47 is skill names, 48 is skill descriptions
- 75 is support card names incl. variant, 76 is support card variant, 77 is support card character
- 147 is spark names, 172 is spark descriptions
- 33 is race names by race id, 28 is race names by race instance id, 31 is race courses, 111 is saddle names
- 65 is player titles, 66 is title descriptions - ties with honor_data?
- 119 is scenario full titles (e.g. The Beginning: URA Finale), 120 is scenario descriptions, 237 is scenario names (e.g. URA Finale)
- 130 is epithet names, 131 is epithet descriptions

in the decomp, text data categories are named in `public enum MasterString.Category`.
some categories seem to be missing, though.

# succession factor (sparks)

tables are succession_factor and succession_factor_effect

factor_type:
- 1 stat
- 2 aptitude
- 5 race
- 4 skill
- 6 scenario
- 7 carnival bonus
- 10 surface gene (white, on jp)
- 8 distance gene (white)
- 11 style gene (white)
- 9 hidden (white, for things like many wins in west japan, summer sprint series, &c.)
- 3 unique

target_type:
- 1 speed; value 1 is amount, value 2 is 0
- 2 stam
- 3 power
- 4 guts
- 5 wit
- 6 skill points
- 7 random stat; value 1 is amount, value 2 is always 1?
- 11 turf; value 1 is number of levels (1 or 2), value 2 is 0
- 12 dirt
- 21 front
- 22 pace
- 23 late
- 24 end
- 31 sprint
- 32 mile
- 33 medium
- 34 long
- 41 skill; value 1 is skill id, value 2 is hint level (1-5)
- 51 carnival bonus; value 1 is skill id, value 2 is 1
- 61 speed cap; value 1 is presumably amount but the numbers are very small, 1-4; value 2 is 0
- 62 stam cap
- 63 power cap
- 64 guts cap
- 65 wit cap

grade is 2 for unique sparks and 1 otherwise.

every possible result has a row in succession_factor_effect.
effect_id distinguishes possibilities; factors with multiple effects (race and scenario sparks) have multiple rows with equal effect_id.
effect_group_id determines the pmf, but no tables have non-empty joins with the same column name, so the distribution values are mystery.
even searching for 51 and 52 (effect group ids for 1\* and 2\* race and scenario sparks) on consecutive lines gives nothing.

getting all interesting spark data, fully expanded with all effects:
```sql
WITH spark AS (
    SELECT
        n."index" AS "id",
        n."text" AS "name",
        d."text" AS "description"
    FROM text_data n
        LEFT JOIN text_data d ON n."index" = d."index" AND d."category" = 172
    WHERE n.category = 147
)
SELECT
    spark.name,
    spark.description,
    sf.factor_id,
    sf.factor_group_id,
    sf.factor_type,
    sf.effect_group_id,
    sfe.effect_id,
    sfe.target_type,
    sfe.value_1,
    sfe.value_2
FROM spark
    JOIN succession_factor sf ON spark.id = sf.factor_id
    JOIN succession_factor_effect sfe ON sf.factor_group_id = sfe.factor_group_id
ORDER BY sf.factor_id, sfe.effect_id, sfe.target_type
```

(change joins to left to include sparks that aren't implemented yet, mostly uniques)

getting all skills' levels that each spark can give:
```sql
WITH spark AS (
    SELECT
        n."index" AS "id",
        n."text" AS "name",
        d."text" AS "description"
    FROM text_data n
        LEFT JOIN text_data d ON n."index" = d."index" AND d."category" = 172
    WHERE n.category = 147
), spark_skills AS (
    SELECT DISTINCT
        factor_group_id,
        value_1,
        value_2
    FROM succession_factor_effect
    WHERE target_type = 41
)
SELECT
    spark.name,
    spark.description,
    sf.factor_id,
    sf.factor_group_id,
    sf.factor_type,
    sf.effect_group_id,
    ss.value_1 AS skill,
    JSON_GROUP_ARRAY(ss.value_2) AS levels
FROM spark
    JOIN succession_factor sf ON spark.id = sf.factor_id
    JOIN spark_skills ss ON sf.factor_group_id = ss.factor_group_id
GROUP BY sf.factor_id
ORDER BY sf.factor_id
```
although realistically you can just use effect_group_id to determine both probabilities and support.
race sparks with skills always give +1, skill sparks always give +1-5, unique sparks always give +1-3.

# support cards

- hints are defined in single_mode_hint_gain, grouped by hint_group
- hint_gain_type is 0 for skill, 1 for stat or skill points
- hint_value_1 is skill id or stat target 1 = speed 2 = stam ... 5 = guts; 30 = skill points
- hint_value_2 appears to be base skill hint level (1 always) or stat amount (6, 2, 1 for guts speed/pow, 5 for wit skill points)

# skills

- single_mode_skill_need_point is base number of skill points to buy each skill
- support card skill hints are defined in single_mode_hint_gain
- skill_set includes trainee unique starting skills, among many other things
- available_skill_set has trainee starting skills other than their uniques

skill categories:
- 0 passive
- 1 early race
- 2 mid-race
- 3 late race or last spurt
- 4 anytime
- 5 unique

unique_skill_id_1 is for inherited uniques, points to non-inherited version.
unique_skill_id_2 is same but points to 1\*/2\* version.

exp_type appears to be whether the skill gains levels, i.e. own unique.

ability time and cooldown time are given in tenths of milliseconds, i.e. divide by 10000 to get seconds.

ability types:
- 1 speed bonus => ability_value / 10000 is flat gain
- 2 stamina bonus
- 3 power bonus
- 4 guts bonus
- 5 wit bonus
- 6 runaway => ability_value = 0
- 8 vision => ability_value / 10000 is amount (what are the units? what does vision do??)
- 9 heal or stam debuff => ability_value / 10000 is hp modify
- 10 starting gate delay (focus, concentration, gatekept) => ability_value / 10000 is multiplier
- 13 frenzy => ability_value / 10000 is rush time modifier (add?)
- 21 current speed => ability_value / 10000 is multiplier modifier
- 27 target speed => ''
- 28 lane change speed => ''
- 31 accel => ''
- 35 force lane change? it's only on dodging danger/sixth sense with a value of 5000 on both, gametora says "change lane (50% of the track)"

target types:
- 0 none (second and third abilities on skills that only have one)
- 1 self
- 2 others who have sympathy? only on autumn neo universe's rare green skill which triggers everyone's sympathy, and there's no target value
- 4 others in view
- 7 frontmost? only on daiichi ruby's unique, has a target value of 5 but the description says 先頭から4人まで
- 9 others ahead, target_value is number of targets (18 for all)
- 10 others behind, target_value is number of targets
- 11 all teammates
- 18 others in style, target_value is style (1=front, 2=pace, 3=late, 4=end)
- 19 rushing others ahead
- 20 rushing others behind
- 21 rushing others in style, target_value is style
- 22 specific character, target value is character id
- 23 other who triggered the skill

ability_value_usage can be 1 for plain or 2-6 for aoharu stat skill stat scaling

seems to be activate_lot = 1 means wit check, 0 means guaranteed

tag_id is a slash-separated list of numeric IDs that roughly describe all effects relevant to the skill.
- 101..104 style front, pace, late, end
- 201..204 distance sprint, mile, medium, long
- 301..303 timing early, mid, late race
- 401 affects speed stat or target speed
- 402 affects stamina stat or hp
- 403 affects power stat or acceleration
- 404 affects guts stat
- 405 affects wit stat or vision
- 406 is a debuff of any type, or is a main story self-debuff (creeping anxiety, blatant fear), or is Behold Thine Emperor's Divine Might (both own unique and inherited versions are tagged only 406??)
- 407 modifies gate delay
- 501..502 turf/dirt
- 801..812 scenario skill

600 series applies to skills available in global but are only used in jp.
- 601 ground condition passive
- 602 weather passive
- 603 season passive
- 604 race track passive
- 605 time of day passive
- 606 exchange race passive (kawasaki, funabashi, morioka, ooi)
- 607 distance passive (non/standard)
- 608 track direction or corner count passive
- 609 gate position passive
- 610 ground type passive
- 611 popularity passive
- 612 running style passive
- 613 passive depending on other horses' styles or skills
- 614 base stat threshold passive
- 615 mood passive

unrelated, query to get all condition types:
```sql
WITH RECURSIVE condition AS (
    SELECT DISTINCT precondition_1 AS c FROM skill_data
    UNION
    SELECT DISTINCT condition_1 AS c FROM skill_data
    UNION
    SELECT DISTINCT precondition_2 AS c FROM skill_data
    UNION
    SELECT DISTINCT condition_2 AS c FROM skill_data
), disj AS (
    SELECT
        IIF(INSTR(c, '@') != 0, SUBSTR(c, 1, INSTR(c, '@')-1), c) AS c,
        IIF(INSTR(c, '@') != 0, SUBSTR(c, 1 + INSTR(c, '@')), '') AS rest
    FROM condition
    UNION
    SELECT
        IIF(INSTR(rest, '@') != 0, SUBSTR(rest, 1, INSTR(rest, '@')-1), rest) AS c,
        IIF(INSTR(rest, '@') != 0, SUBSTR(rest, 1 + INSTR(rest, '@')), '') AS rest
    FROM disj
    WHERE rest != ''
), term AS (
    SELECT
        IIF(INSTR(c, '&') != 0, SUBSTR(c, 1, INSTR(c, '&')-1), c) AS term,
        IIF(INSTR(c, '&') != 0, SUBSTR(c, 1 + INSTR(c, '&')), '') AS rest
    FROM disj
    UNION
    SELECT
        IIF(INSTR(rest, '&') != 0, SUBSTR(rest, 1, INSTR(rest, '&')-1), rest) AS term,
        IIF(INSTR(rest, '&') != 0, SUBSTR(rest, 1 + INSTR(rest, '&')), '') AS rest
    FROM term
    WHERE rest != ''
), op AS (
    SELECT
        term,
        CASE
            WHEN INSTR(term, '==') != 0 THEN '=='
            WHEN INSTR(term, '!=') != 0 THEN '!='
            WHEN INSTR(term, '<=') != 0 THEN '<='
            WHEN INSTR(term, '>=') != 0 THEN '>='
            WHEN INSTR(term, '<') != 0 THEN '<'
            WHEN INSTR(term, '>') != 0 THEN '>'
        END AS op
    FROM term WHERE term != ''
), type_op_arg AS (
    SELECT DISTINCT
        term,
        SUBSTR(term, 1, INSTR(term, op) - 1) AS "type",
        op,
        CAST(SUBSTR(term, INSTR(term, op) + LENGTH(op)) AS INTEGER) AS arg
    FROM op
)
SELECT DISTINCT CONCAT(type, op), arg FROM type_op_arg
```

final select can be adjusted to get different info, but type+op together seems most useful

# races

- group 1, grade: g1 100, g2 200, g3 300, op 400, pre-op 700
- group 61 is ....?? don't match anything
- several races are defined twice for "weird" races that appear in certain careers (maruzensky's 5 opponent spring stakes, mcqueen/ryan/rice's kyoto takarazuka kinen, &c.); these **do not** count as the same race as the normal versions for shared saddle bonus

single_mode_wins_saddle defines titles (classic triple crown, tenno sweep, &c.) using win_saddle_type = 0

race_instance is a combination of race, npc group, race date (month*100 + day), and time of day.
it isn't actually anything i care about.

which is to say, what i do care about is mapping races to each turn they're offered, and having a "race instance" enum like Hopeful-Stakes-Junior, Yasuda-Kinen-Classic, &c.

single_mode_program defines the race instances available for each turn, but the year is expressed a bit weirdly in the race_permission column:
- 1 = junior year
- 2 = classic year
- 3 = classic and senior year
- 4 = senior year
- 5 = ura finale

grade_rate_id appears to be consistently 800 iff maiden race and 900 iff debut race, but the values particularly for g1s are all over the place.
recommend_class_id appears to be consistently 1 iff maiden race or debut race, 2 iff pre-op, 3 iff op; but other values are confusing.
so, it doesn't seem like there's a particular flag that identifies maiden races, despite the restrictions on when they appear in the ui.

# trainee definitions

- card_data has universal trainee stats: base skill set, stat growth bonuses ("talent"), default running style
- card_rarity_data has per-star-level stats: initial numbers, stat caps (!!), aptitudes (!!)
- card_talent_upgrade has costs to increase potential level, but it doesn't seem to have skill sets
- card_talent_hint_upgrade has costs to raise hint levels, but it's actually universal, only six rows
- single_mode_route_race is career goals (not only races)
- available_skill_set has starting skills including those unlocked by potential level given under need_rank (0 for pl1, 2 for pl2)
- single_mode_special_chara is scenario links

# gl

single_mode_live_song_list has the song list for lives ha ha.
this is defined from the perspective of bonuses they grant when you have them, not the shop per se.
- text_data category for song names is 210, look up by live_id; for post-concert effect text, category 208 indexed by song list id
- command_id and live_id are always equal
- level is 1 for all songs except the two instances of girls' legend u
- master_bonus_content_text_id corresponds to text_data categories 207 for pickup effect text and 209 for song name
- live_bonus_type is 1 for specialty prio, 2 for chain frequency, 3 for fb
- girls' legend u is defined twice as ids 22 and 24

costs are in single_mode_live_square.
- square_type is 1 for stat up, 2 for skill up, 3 for energy up, 4 for live
- perf_type_n is token type: 1 for dance, 2 for passion, 3 for vocals, 4 for visuals, 5 for composure
- perf_value_n is number of tokens

immediate bonuses are in single_mode_live_master_bonus.
- master_bonus_type is 1 for stat up, 2 for skill hint, 3 for energy up, 4 for song
- master_bonus_type_value is 1 for type 1, live id for type 4, 0 otherwise
- master_bonus_gain_type_n is 1 for stat, 2 for skill hint, 3 for energy, 5 for permanent training bonus
    + 1 for stat: then master_bonus_gain_value_n_1 is 1-5 for stat id or 6 for sp, n_2 is 1 for fixed or 2 for random (not actually in the game), n_3 is lower bound on gain, n_4 is 0 for fixed or upper bound for random
    + 2 for skill hint: then gain_value_n_1 is 3(*), n_2 is skill tag, n_3 is hint level
    + 3 for energy: then gain_value_n_1 is amount
    + 5 for permanent training bonus: then gain_value_n_1 is stat id (6 for sp), n_2 is amount
(*) MasterSingleModeLiveMasterBonus.SingleModeLiveMasterBonus.GainSkillHintTypeEnum defines values of Random = 1 and SkillId = 2, but they are unused in the db.

there is some ostensibly unused data.
- master bonus ids 12001-12206 grant random amounts of stats
- id 20000 corresponds to "Group Lesson" "Skill hint appropriate for aptitude"

# lobby conversations!!!

table is home_story_trigger.

pos_id values:
- 110 right side, toward the front
- 120 same, but two characters
- 130 same, but three characters
- 210 left side table
- 220
- 310 center back seat
- 410 center posters
- 420
- 430
- 510 left school map
- 520
- 530

num is how many characters are involved, but also can just check chara_id_{1,2,3} for nonzero.

unsure what condition_type is.
values of 2 and 3 always have two or three characters, and values of 4 (jp only) always have three, but 0 and 1 can have any number.
there's no requirement for stories like having a horse at all, much less an affinity level.

gallery_chara_id is the character whose conversation it is; chara_id_{1,2,3} are the characters involved.
gallery_chara_id is always one of the three, but it can be any one of the three.
disp_order then is the conversation number within their gallery.

getting all conversation data:
```sql
WITH chara_name AS (
    SELECT "index" AS id, "text" AS name
    FROM text_data
    WHERE category = 6
), convo_loc_names AS (
    SELECT 110 AS pos_id, 'right side front' AS name UNION ALL
    SELECT 120 AS pos_id, 'right side front' AS name UNION ALL
    SELECT 130 AS pos_id, 'right side front' AS name UNION ALL
    SELECT 210 AS pos_id, 'left side table' AS name UNION ALL
    SELECT 220 AS pos_id, 'left side table' AS name UNION ALL
    SELECT 310 AS pos_id, 'center back seat' AS name UNION ALL
    SELECT 410 AS pos_id, 'center posters' AS name UNION ALL
    SELECT 420 AS pos_id, 'center posters' AS name UNION ALL
    SELECT 430 AS pos_id, 'center posters' AS name UNION ALL
    SELECT 510 AS pos_id, 'left side school map' AS name UNION ALL
    SELECT 520 AS pos_id, 'left side school map' AS name UNION ALL
    SELECT 530 AS pos_id, 'left side school map' AS name
)
SELECT
    n.name,
    s.disp_order,
    l.name,
    c1.name,
    c2.name,
    c3.name,
    s.condition_type
FROM home_story_trigger s
    LEFT JOIN chara_name n ON s.gallery_chara_id = n.id
    LEFT JOIN chara_name c1 ON s.chara_id_1 = c1.id
    LEFT JOIN chara_name c2 ON s.chara_id_2 = c2.id
    LEFT JOIN chara_name c3 ON s.chara_id_3 = c3.id
    LEFT JOIN convo_loc_names l ON s.pos_id = l.pos_id
ORDER BY s.gallery_chara_id, s.disp_order
```

# update diffs

complete list of tables with inserts in both the 2026-01-15 update adding fine motion, manhattan cafe ssr, inari one sr and the 2026-01-22 update adding tamamo cross and main story 5:

- announce_character
- announce_data
- available_skill_set
- background_data
- banner_data
- campaign_chara_story_schedule
- campaign_data
- campaign_single_race_add_data
- campaign_single_race_add_reward
- card_data
- card_rarity_data
- card_talent_upgrade
- champions_news_chara_comment
- chara_category_motion
- character_system_lottery
- character_system_text
- chara_motion_set
- chara_story_data
- dress_data
- gacha_available
- gacha_data
- gacha_exchange
- gacha_top_bg
- home_story_trigger
- home_walk_group
- honor_data
- item_exchange
- item_place
- jukebox_chara_tag_data
- jukebox_comment
- jukebox_reaction_data
- mission_data
- nickname
- piece_data
- race
- race_bgm_cutin_extension_time
- race_instance
- race_jikkyo_base
- race_jikkyo_race
- race_jikkyo_trigger
- single_mode_chara_program
- single_mode_conclusion_set
- single_mode_event_production
- single_mode_hint_gain
- single_mode_npc
- single_mode_rival
- single_mode_route
- single_mode_route_race
- single_mode_scout_chara
- single_mode_skill_need_point
- single_mode_story_data
- single_mode_tag_card_pos
- skill_data
- skill_set
- succession_factor
- succession_factor_effect
- succession_relation_member
- support_card_data
- support_card_effect_table
- support_card_unique_effect
- text_data
