6 Commits
Author SHA1 Message Date
zephyr 0ffd2c099a horse, mdb: include id in lobby conversations
ci/woodpecker/push/horsebot Pipeline was successful
ci/woodpecker/push/zenno Pipeline was successful
2026-09-17 12:59:28 -04:00
zephyr 6828b1bd88 horse, mdb: put skill groups in skills
ci/woodpecker/push/horsebot Pipeline was successful
ci/woodpecker/push/zenno Pipeline was successful
Fixes #13.
2026-09-15 11:00:10 -04:00
zephyr 78aee43e5f zenno: format
ci/woodpecker/push/horsebot Pipeline was successful
ci/woodpecker/push/zenno Pipeline was successful
why the h*ck does vscode not format my .ts files on save even though i
have it configured to always format on save!!!!!!
2026-09-14 17:21:51 -04:00
zephyr 745c9d01dd zenno/doc/frbm: delete jbc sprint
Fixes #46.
2026-09-14 17:20:40 -04:00
zephyr e00fe0965a zenno/doc/mdb: emphasize that support card event outcomes are not in the mdb
ci/woodpecker/push/horsebot Pipeline was successful
ci/woodpecker/push/zenno Pipeline failed
2026-09-14 17:07:25 -04:00
zephyr caa19dd85a zenno/doc/mdb: document races
ci/woodpecker/push/horsebot Pipeline failed
ci/woodpecker/push/zenno Pipeline failed
2026-09-11 18:14:14 -04:00
11 changed files with 858 additions and 200 deletions
+2
View File
@@ -28,6 +28,8 @@ type AffinityDetail struct {
// Conversation describes a lobby conversation. // Conversation describes a lobby conversation.
type Conversation struct { type Conversation struct {
// ID is the conversation ID.
ID int32 `json:"id"`
// CharacterID is the ID of the character who has the conversation as // CharacterID is the ID of the character who has the conversation as
// a gallery entry. // a gallery entry.
CharacterID CharacterID `json:"chara_id"` CharacterID CharacterID `json:"chara_id"`
+8 -7
View File
@@ -89,13 +89,14 @@ func Umas(ctx context.Context, db *sqlitex.Pool) ([]horse.Uma, error) {
func Conversations(ctx context.Context, db *sqlitex.Pool) ([]horse.Conversation, error) { func Conversations(ctx context.Context, db *sqlitex.Pool) ([]horse.Conversation, error) {
return load(ctx, db, make([]horse.Conversation, 0, 1024), conversationSQL, func(s *sqlite.Stmt) horse.Conversation { return load(ctx, db, make([]horse.Conversation, 0, 1024), conversationSQL, func(s *sqlite.Stmt) horse.Conversation {
return horse.Conversation{ return horse.Conversation{
CharacterID: horse.CharacterID(s.ColumnInt(0)), ID: s.ColumnInt32(0),
Number: s.ColumnInt(1), CharacterID: horse.CharacterID(s.ColumnInt(1)),
Location: horse.LobbyConversationLocationID(s.ColumnInt(2)), Number: s.ColumnInt(2),
Chara1: horse.CharacterID(s.ColumnInt(3)), Location: horse.LobbyConversationLocationID(s.ColumnInt(3)),
Chara2: horse.CharacterID(s.ColumnInt(4)), Chara1: horse.CharacterID(s.ColumnInt(4)),
Chara3: horse.CharacterID(s.ColumnInt(5)), Chara2: horse.CharacterID(s.ColumnInt(5)),
ConditionType: s.ColumnInt(6), Chara3: horse.CharacterID(s.ColumnInt(6)),
ConditionType: s.ColumnInt(7),
} }
}) })
} }
+11
View File
@@ -238,12 +238,14 @@ func TestConversations(t *testing.T) {
} }
want := []horse.Conversation{ want := []horse.Conversation{
{ {
ID: 1,
CharacterID: 1001, CharacterID: 1001,
Number: 1, Number: 1,
Location: 410, Location: 410,
Chara1: 1001, Chara1: 1001,
}, },
{ {
ID: 2,
CharacterID: 1001, CharacterID: 1001,
Number: 2, Number: 2,
Location: 510, Location: 510,
@@ -251,6 +253,7 @@ func TestConversations(t *testing.T) {
ConditionType: 1, ConditionType: 1,
}, },
{ {
ID: 3,
CharacterID: 1001, CharacterID: 1001,
Number: 3, Number: 3,
Location: 310, Location: 310,
@@ -258,6 +261,7 @@ func TestConversations(t *testing.T) {
ConditionType: 1, ConditionType: 1,
}, },
{ {
ID: 76,
CharacterID: 1001, CharacterID: 1001,
Number: 4, Number: 4,
Location: 120, Location: 120,
@@ -266,6 +270,7 @@ func TestConversations(t *testing.T) {
ConditionType: 2, ConditionType: 2,
}, },
{ {
ID: 77,
CharacterID: 1001, CharacterID: 1001,
Number: 5, Number: 5,
Location: 520, Location: 520,
@@ -274,6 +279,7 @@ func TestConversations(t *testing.T) {
ConditionType: 3, ConditionType: 3,
}, },
{ {
ID: 126,
CharacterID: 1001, CharacterID: 1001,
Number: 6, Number: 6,
Location: 430, Location: 430,
@@ -283,12 +289,14 @@ func TestConversations(t *testing.T) {
ConditionType: 1, ConditionType: 1,
}, },
{ {
ID: 4,
CharacterID: 1002, CharacterID: 1002,
Number: 1, Number: 1,
Location: 310, Location: 310,
Chara1: 1002, Chara1: 1002,
}, },
{ {
ID: 5,
CharacterID: 1002, CharacterID: 1002,
Number: 2, Number: 2,
Location: 210, Location: 210,
@@ -296,6 +304,7 @@ func TestConversations(t *testing.T) {
ConditionType: 1, ConditionType: 1,
}, },
{ {
ID: 6,
CharacterID: 1002, CharacterID: 1002,
Number: 3, Number: 3,
Location: 110, Location: 110,
@@ -303,6 +312,7 @@ func TestConversations(t *testing.T) {
ConditionType: 1, ConditionType: 1,
}, },
{ {
ID: 78,
CharacterID: 1002, CharacterID: 1002,
Number: 4, Number: 4,
Location: 520, Location: 520,
@@ -311,6 +321,7 @@ func TestConversations(t *testing.T) {
ConditionType: 3, ConditionType: 3,
}, },
{ {
ID: 79,
CharacterID: 1002, CharacterID: 1002,
Number: 5, Number: 5,
Location: 220, Location: 220,
+123 -77
View File
@@ -18,6 +18,8 @@ var (
skillGroupSQL string skillGroupSQL string
//go:embed sql/skill.sql //go:embed sql/skill.sql
skillSQL string skillSQL string
//go:embed sql/skill-group-skills.sql
skillGroupSkillsSQL string
) )
// SkillGroups retrieves all skill groups. // SkillGroups retrieves all skill groups.
@@ -35,84 +37,128 @@ func SkillGroups(ctx context.Context, db *sqlitex.Pool) ([]horse.SkillGroup, err
// Skills retrieves all skills. // Skills retrieves all skills.
func Skills(ctx context.Context, db *sqlitex.Pool) ([]horse.Skill, error) { func Skills(ctx context.Context, db *sqlitex.Pool) ([]horse.Skill, error) {
return load(ctx, db, nil, skillSQL, func(s *sqlite.Stmt) horse.Skill { // We don't use func load here because we have multiple queries to run.
return horse.Skill{ conn, err := db.Take(ctx)
ID: horse.SkillID(s.ColumnInt(0)), defer db.Put(conn)
Name: s.ColumnText(1), if err != nil {
Description: s.ColumnText(2), return nil, err
Group: horse.SkillGroupID(s.ColumnInt32(3)), }
Rarity: int8(s.ColumnInt(5)), var sk []horse.Skill
GroupRate: int8(s.ColumnInt(6)), {
GradeValue: s.ColumnInt32(7), stmt := conn.Prep(skillSQL)
WitCheck: s.ColumnBool(8), defer stmt.Reset()
Activations: trimActivations([]horse.Activation{ for {
{ ok, err := stmt.Step()
Precondition: s.ColumnText(9), if err != nil {
Condition: s.ColumnText(10), return nil, err
Duration: horse.TenThousandths(s.ColumnInt(11)), }
DurScale: horse.DurScale(s.ColumnInt(12)), if !ok {
Cooldown: horse.TenThousandths(s.ColumnInt(13)), break
Abilities: trimAbilities([]horse.Ability{ }
{ s := horse.Skill{
Type: horse.AbilityType(s.ColumnInt(14)), ID: horse.SkillID(stmt.ColumnInt(0)),
ValueUsage: horse.AbilityValueUsage(s.ColumnInt(15)), Name: stmt.ColumnText(1),
Value: horse.TenThousandths(s.ColumnInt(16)), Description: stmt.ColumnText(2),
Target: horse.AbilityTarget(s.ColumnInt(17)), GroupSkills: make([]horse.GroupSkill, 0, 3),
TargetValue: s.ColumnInt32(18), Group: horse.SkillGroupID(stmt.ColumnInt32(3)),
}, Rarity: int8(stmt.ColumnInt(5)),
{ GroupRate: int8(stmt.ColumnInt(6)),
Type: horse.AbilityType(s.ColumnInt(19)), GradeValue: stmt.ColumnInt32(7),
ValueUsage: horse.AbilityValueUsage(s.ColumnInt(20)), WitCheck: stmt.ColumnBool(8),
Value: horse.TenThousandths(s.ColumnInt(21)), Activations: trimActivations([]horse.Activation{
Target: horse.AbilityTarget(s.ColumnInt(22)), {
TargetValue: s.ColumnInt32(23), Precondition: stmt.ColumnText(9),
}, Condition: stmt.ColumnText(10),
{ Duration: horse.TenThousandths(stmt.ColumnInt(11)),
Type: horse.AbilityType(s.ColumnInt(24)), DurScale: horse.DurScale(stmt.ColumnInt(12)),
ValueUsage: horse.AbilityValueUsage(s.ColumnInt(25)), Cooldown: horse.TenThousandths(stmt.ColumnInt(13)),
Value: horse.TenThousandths(s.ColumnInt(26)), Abilities: trimAbilities([]horse.Ability{
Target: horse.AbilityTarget(s.ColumnInt(27)), {
TargetValue: s.ColumnInt32(28), Type: horse.AbilityType(stmt.ColumnInt(14)),
}, ValueUsage: horse.AbilityValueUsage(stmt.ColumnInt(15)),
}), Value: horse.TenThousandths(stmt.ColumnInt(16)),
}, Target: horse.AbilityTarget(stmt.ColumnInt(17)),
{ TargetValue: stmt.ColumnInt32(18),
Precondition: s.ColumnText(29), },
Condition: s.ColumnText(30), {
Duration: horse.TenThousandths(s.ColumnInt(31)), Type: horse.AbilityType(stmt.ColumnInt(19)),
DurScale: horse.DurScale(s.ColumnInt(32)), ValueUsage: horse.AbilityValueUsage(stmt.ColumnInt(20)),
Cooldown: horse.TenThousandths(s.ColumnInt(33)), Value: horse.TenThousandths(stmt.ColumnInt(21)),
Abilities: trimAbilities([]horse.Ability{ Target: horse.AbilityTarget(stmt.ColumnInt(22)),
{ TargetValue: stmt.ColumnInt32(23),
Type: horse.AbilityType(s.ColumnInt(34)), },
ValueUsage: horse.AbilityValueUsage(s.ColumnInt(35)), {
Value: horse.TenThousandths(s.ColumnInt(36)), Type: horse.AbilityType(stmt.ColumnInt(24)),
Target: horse.AbilityTarget(s.ColumnInt(37)), ValueUsage: horse.AbilityValueUsage(stmt.ColumnInt(25)),
TargetValue: s.ColumnInt32(38), Value: horse.TenThousandths(stmt.ColumnInt(26)),
}, Target: horse.AbilityTarget(stmt.ColumnInt(27)),
{ TargetValue: stmt.ColumnInt32(28),
Type: horse.AbilityType(s.ColumnInt(39)), },
ValueUsage: horse.AbilityValueUsage(s.ColumnInt(40)), }),
Value: horse.TenThousandths(s.ColumnInt(41)), },
Target: horse.AbilityTarget(s.ColumnInt(42)), {
TargetValue: s.ColumnInt32(43), Precondition: stmt.ColumnText(29),
}, Condition: stmt.ColumnText(30),
{ Duration: horse.TenThousandths(stmt.ColumnInt(31)),
Type: horse.AbilityType(s.ColumnInt(44)), DurScale: horse.DurScale(stmt.ColumnInt(32)),
ValueUsage: horse.AbilityValueUsage(s.ColumnInt(45)), Cooldown: horse.TenThousandths(stmt.ColumnInt(33)),
Value: horse.TenThousandths(s.ColumnInt(46)), Abilities: trimAbilities([]horse.Ability{
Target: horse.AbilityTarget(s.ColumnInt(47)), {
TargetValue: s.ColumnInt32(48), Type: horse.AbilityType(stmt.ColumnInt(34)),
}, ValueUsage: horse.AbilityValueUsage(stmt.ColumnInt(35)),
}), Value: horse.TenThousandths(stmt.ColumnInt(36)),
}, Target: horse.AbilityTarget(stmt.ColumnInt(37)),
}), TargetValue: stmt.ColumnInt32(38),
UniqueOwner: s.ColumnText(52), // TODO(zeph): should be id, not name },
Tags: parseTags(s.ColumnText(54)), {
SPCost: s.ColumnInt(49), Type: horse.AbilityType(stmt.ColumnInt(39)),
IconID: s.ColumnInt(53), ValueUsage: horse.AbilityValueUsage(stmt.ColumnInt(40)),
Value: horse.TenThousandths(stmt.ColumnInt(41)),
Target: horse.AbilityTarget(stmt.ColumnInt(42)),
TargetValue: stmt.ColumnInt32(43),
},
{
Type: horse.AbilityType(stmt.ColumnInt(44)),
ValueUsage: horse.AbilityValueUsage(stmt.ColumnInt(45)),
Value: horse.TenThousandths(stmt.ColumnInt(46)),
Target: horse.AbilityTarget(stmt.ColumnInt(47)),
TargetValue: stmt.ColumnInt32(48),
},
}),
},
}),
UniqueOwner: stmt.ColumnText(52), // TODO(zeph): should be id, not name
Tags: parseTags(stmt.ColumnText(54)),
SPCost: stmt.ColumnInt(49),
IconID: stmt.ColumnInt(53),
}
sk = append(sk, s)
} }
}) }
stmt := conn.Prep(skillGroupSkillsSQL)
defer stmt.Reset()
cur := sk
for {
ok, err := stmt.Step()
if err != nil {
return nil, err
}
if !ok {
break
}
// We sort skills by ID first, so we can add group skills in a single pass.
id := stmt.ColumnInt(0)
for len(cur) != 0 && cur[0].ID != horse.SkillID(id) {
cur = cur[1:]
}
g := horse.GroupSkill{
ID: horse.SkillID(stmt.ColumnInt(1)),
GroupRate: int8(stmt.ColumnInt(2)),
Rarity: int8(stmt.ColumnInt(3)),
}
cur[0].GroupSkills = append(cur[0].GroupSkills, g)
}
return sk, nil
} }
func parseTags(s string) []uint16 { func parseTags(s string) []uint16 {
+182 -95
View File
@@ -49,11 +49,16 @@ func TestSkills(t *testing.T) {
ID: 10351, ID: 10351,
Name: "V Is for Victory!", Name: "V Is for Victory!",
Description: "Moderately increase velocity with winning ambition when positioned toward the front on the final straight after engaging in a challenge on the final corner or later.", Description: "Moderately increase velocity with winning ambition when positioned toward the front on the final straight after engaging in a challenge on the final corner or later.",
Group: 1035, GroupSkills: []horse.GroupSkill{
Rarity: 3, {ID: 100351, GroupRate: 1, Rarity: 4},
GroupRate: 1, {ID: 10351, GroupRate: 1, Rarity: 3},
GradeValue: 240, {ID: 900351, GroupRate: 2, Rarity: 1},
WitCheck: false, },
Group: 1035,
Rarity: 3,
GroupRate: 1,
GradeValue: 240,
WitCheck: false,
Activations: []horse.Activation{ Activations: []horse.Activation{
{ {
Precondition: "is_finalcorner==1&blocked_side_continuetime>=2", Precondition: "is_finalcorner==1&blocked_side_continuetime>=2",
@@ -81,11 +86,15 @@ func TestSkills(t *testing.T) {
ID: 100011, ID: 100011,
Name: "Shooting Star", Name: "Shooting Star",
Description: "Ride the momentum to increase velocity and very slightly increase acceleration after passing another runner toward the front late-race.", Description: "Ride the momentum to increase velocity and very slightly increase acceleration after passing another runner toward the front late-race.",
Group: 10001, GroupSkills: []horse.GroupSkill{
Rarity: 5, {ID: 100011, GroupRate: 1, Rarity: 5},
GroupRate: 1, {ID: 900011, GroupRate: 2, Rarity: 1},
GradeValue: 340, },
WitCheck: false, Group: 10001,
Rarity: 5,
GroupRate: 1,
GradeValue: 340,
WitCheck: false,
Activations: []horse.Activation{ Activations: []horse.Activation{
{ {
Precondition: "", Precondition: "",
@@ -120,11 +129,16 @@ func TestSkills(t *testing.T) {
ID: 100351, ID: 100351,
Name: "Our Ticket to Win!", Name: "Our Ticket to Win!",
Description: "Increase velocity with winning ambition when positioned toward the front on the final straight after engaging in a challenge on the final corner or later.", Description: "Increase velocity with winning ambition when positioned toward the front on the final straight after engaging in a challenge on the final corner or later.",
Group: 10035, GroupSkills: []horse.GroupSkill{
Rarity: 4, {ID: 100351, GroupRate: 1, Rarity: 4},
GroupRate: 1, {ID: 10351, GroupRate: 1, Rarity: 3},
GradeValue: 340, {ID: 900351, GroupRate: 2, Rarity: 1},
WitCheck: false, },
Group: 10035,
Rarity: 4,
GroupRate: 1,
GradeValue: 340,
WitCheck: false,
Activations: []horse.Activation{ Activations: []horse.Activation{
{ {
Precondition: "is_finalcorner==1&blocked_side_continuetime>=2", Precondition: "is_finalcorner==1&blocked_side_continuetime>=2",
@@ -152,11 +166,15 @@ func TestSkills(t *testing.T) {
ID: 110241, ID: 110241,
Name: "Flowery☆Maneuver", Name: "Flowery☆Maneuver",
Description: "Increase velocity when passing another runner toward the front on the final corner. If passing toward the back, increase acceleration instead.", Description: "Increase velocity when passing another runner toward the front on the final corner. If passing toward the back, increase acceleration instead.",
Group: 11024, GroupSkills: []horse.GroupSkill{
Rarity: 5, {ID: 110241, GroupRate: 1, Rarity: 5},
GroupRate: 1, {ID: 910241, GroupRate: 2, Rarity: 1},
GradeValue: 340, },
WitCheck: false, Group: 11024,
Rarity: 5,
GroupRate: 1,
GradeValue: 340,
WitCheck: false,
Activations: []horse.Activation{ Activations: []horse.Activation{
{ {
Precondition: "", Precondition: "",
@@ -200,11 +218,17 @@ func TestSkills(t *testing.T) {
ID: 200011, ID: 200011,
Name: "Right-Handed ◎", Name: "Right-Handed ◎",
Description: "Increase performance on right-handed tracks.", Description: "Increase performance on right-handed tracks.",
Group: 20001, GroupSkills: []horse.GroupSkill{
Rarity: 1, {ID: 200013, GroupRate: -1, Rarity: 1},
GroupRate: 2, {ID: 200012, GroupRate: 1, Rarity: 1},
GradeValue: 174, {ID: 200011, GroupRate: 2, Rarity: 1},
WitCheck: false, {ID: 200014, GroupRate: 3, Rarity: 2},
},
Group: 20001,
Rarity: 1,
GroupRate: 2,
GradeValue: 174,
WitCheck: false,
Activations: []horse.Activation{ Activations: []horse.Activation{
{ {
Precondition: "", Precondition: "",
@@ -231,11 +255,17 @@ func TestSkills(t *testing.T) {
ID: 200012, ID: 200012,
Name: "Right-Handed ○", Name: "Right-Handed ○",
Description: "Moderately increase performance on right-handed tracks.", Description: "Moderately increase performance on right-handed tracks.",
Group: 20001, GroupSkills: []horse.GroupSkill{
Rarity: 1, {ID: 200013, GroupRate: -1, Rarity: 1},
GroupRate: 1, {ID: 200012, GroupRate: 1, Rarity: 1},
GradeValue: 129, {ID: 200011, GroupRate: 2, Rarity: 1},
WitCheck: false, {ID: 200014, GroupRate: 3, Rarity: 2},
},
Group: 20001,
Rarity: 1,
GroupRate: 1,
GradeValue: 129,
WitCheck: false,
Activations: []horse.Activation{ Activations: []horse.Activation{
{ {
Precondition: "", Precondition: "",
@@ -262,11 +292,17 @@ func TestSkills(t *testing.T) {
ID: 200013, ID: 200013,
Name: "Right-Handed ×", Name: "Right-Handed ×",
Description: "Moderately decrease performance on right-handed tracks.", Description: "Moderately decrease performance on right-handed tracks.",
Group: 20001, GroupSkills: []horse.GroupSkill{
Rarity: 1, {ID: 200013, GroupRate: -1, Rarity: 1},
GroupRate: -1, {ID: 200012, GroupRate: 1, Rarity: 1},
GradeValue: -129, {ID: 200011, GroupRate: 2, Rarity: 1},
WitCheck: false, {ID: 200014, GroupRate: 3, Rarity: 2},
},
Group: 20001,
Rarity: 1,
GroupRate: -1,
GradeValue: -129,
WitCheck: false,
Activations: []horse.Activation{ Activations: []horse.Activation{
{ {
Precondition: "", Precondition: "",
@@ -293,11 +329,17 @@ func TestSkills(t *testing.T) {
ID: 200014, ID: 200014,
Name: "Right-Handed Demon", Name: "Right-Handed Demon",
Description: "Increase proficiency in right-handed tracks, increasing Speed and Power.", Description: "Increase proficiency in right-handed tracks, increasing Speed and Power.",
Group: 20001, GroupSkills: []horse.GroupSkill{
Rarity: 2, {ID: 200013, GroupRate: -1, Rarity: 1},
GroupRate: 3, {ID: 200012, GroupRate: 1, Rarity: 1},
GradeValue: 461, {ID: 200011, GroupRate: 2, Rarity: 1},
WitCheck: false, {ID: 200014, GroupRate: 3, Rarity: 2},
},
Group: 20001,
Rarity: 2,
GroupRate: 3,
GradeValue: 461,
WitCheck: false,
Activations: []horse.Activation{ Activations: []horse.Activation{
{ {
Precondition: "", Precondition: "",
@@ -331,11 +373,16 @@ func TestSkills(t *testing.T) {
ID: 200021, ID: 200021,
Name: "Left-Handed ◎", Name: "Left-Handed ◎",
Description: "Increase performance on left-handed tracks.", Description: "Increase performance on left-handed tracks.",
Group: 20002, GroupSkills: []horse.GroupSkill{
Rarity: 1, {ID: 200023, GroupRate: -1, Rarity: 1},
GroupRate: 2, {ID: 200022, GroupRate: 1, Rarity: 1},
GradeValue: 174, {ID: 200021, GroupRate: 2, Rarity: 1},
WitCheck: false, },
Group: 20002,
Rarity: 1,
GroupRate: 2,
GradeValue: 174,
WitCheck: false,
Activations: []horse.Activation{ Activations: []horse.Activation{
{ {
Precondition: "", Precondition: "",
@@ -362,11 +409,16 @@ func TestSkills(t *testing.T) {
ID: 200022, ID: 200022,
Name: "Left-Handed ○", Name: "Left-Handed ○",
Description: "Moderately increase performance on left-handed tracks.", Description: "Moderately increase performance on left-handed tracks.",
Group: 20002, GroupSkills: []horse.GroupSkill{
Rarity: 1, {ID: 200023, GroupRate: -1, Rarity: 1},
GroupRate: 1, {ID: 200022, GroupRate: 1, Rarity: 1},
GradeValue: 129, {ID: 200021, GroupRate: 2, Rarity: 1},
WitCheck: false, },
Group: 20002,
Rarity: 1,
GroupRate: 1,
GradeValue: 129,
WitCheck: false,
Activations: []horse.Activation{ Activations: []horse.Activation{
{ {
Precondition: "", Precondition: "",
@@ -393,11 +445,16 @@ func TestSkills(t *testing.T) {
ID: 200023, ID: 200023,
Name: "Left-Handed ×", Name: "Left-Handed ×",
Description: "Moderately decrease performance on left-handed tracks.", Description: "Moderately decrease performance on left-handed tracks.",
Group: 20002, GroupSkills: []horse.GroupSkill{
Rarity: 1, {ID: 200023, GroupRate: -1, Rarity: 1},
GroupRate: -1, {ID: 200022, GroupRate: 1, Rarity: 1},
GradeValue: -129, {ID: 200021, GroupRate: 2, Rarity: 1},
WitCheck: false, },
Group: 20002,
Rarity: 1,
GroupRate: -1,
GradeValue: -129,
WitCheck: false,
Activations: []horse.Activation{ Activations: []horse.Activation{
{ {
Precondition: "", Precondition: "",
@@ -424,11 +481,15 @@ func TestSkills(t *testing.T) {
ID: 200361, ID: 200361,
Name: "Beeline Burst", Name: "Beeline Burst",
Description: "Increase velocity on a straight.", Description: "Increase velocity on a straight.",
Group: 20036, GroupSkills: []horse.GroupSkill{
Rarity: 2, {ID: 200362, GroupRate: 1, Rarity: 1},
GroupRate: 2, {ID: 200361, GroupRate: 2, Rarity: 2},
GradeValue: 508, },
WitCheck: true, Group: 20036,
Rarity: 2,
GroupRate: 2,
GradeValue: 508,
WitCheck: true,
Activations: []horse.Activation{ Activations: []horse.Activation{
{ {
Precondition: "", Precondition: "",
@@ -455,11 +516,15 @@ func TestSkills(t *testing.T) {
ID: 200362, ID: 200362,
Name: "Straightaway Adept", Name: "Straightaway Adept",
Description: "Slightly increase velocity on a straight.", Description: "Slightly increase velocity on a straight.",
Group: 20036, GroupSkills: []horse.GroupSkill{
Rarity: 1, {ID: 200362, GroupRate: 1, Rarity: 1},
GroupRate: 1, {ID: 200361, GroupRate: 2, Rarity: 2},
GradeValue: 217, },
WitCheck: true, Group: 20036,
Rarity: 1,
GroupRate: 1,
GradeValue: 217,
WitCheck: true,
Activations: []horse.Activation{ Activations: []horse.Activation{
{ {
Precondition: "", Precondition: "",
@@ -486,11 +551,14 @@ func TestSkills(t *testing.T) {
ID: 200831, ID: 200831,
Name: "Subdued Front Runners", Name: "Subdued Front Runners",
Description: "Slightly increase fatigue for front runners early-race.", Description: "Slightly increase fatigue for front runners early-race.",
Group: 20083, GroupSkills: []horse.GroupSkill{
Rarity: 1, {ID: 200831, GroupRate: 1, Rarity: 1},
GroupRate: 1, },
GradeValue: 217, Group: 20083,
WitCheck: true, Rarity: 1,
GroupRate: 1,
GradeValue: 217,
WitCheck: true,
Activations: []horse.Activation{ Activations: []horse.Activation{
{ {
Precondition: "", Precondition: "",
@@ -517,11 +585,14 @@ func TestSkills(t *testing.T) {
ID: 201801, ID: 201801,
Name: "♡ 3D Nail Art", Name: "♡ 3D Nail Art",
Description: "Moderately decrease performance on firm ground.", Description: "Moderately decrease performance on firm ground.",
Group: 20180, GroupSkills: []horse.GroupSkill{
Rarity: 1, {ID: 201801, GroupRate: -1, Rarity: 1},
GroupRate: -1, },
GradeValue: -129, Group: 20180,
WitCheck: false, Rarity: 1,
GroupRate: -1,
GradeValue: -129,
WitCheck: false,
Activations: []horse.Activation{ Activations: []horse.Activation{
{ {
Precondition: "", Precondition: "",
@@ -548,11 +619,14 @@ func TestSkills(t *testing.T) {
ID: 300011, ID: 300011,
Name: "Unquenched Thirst", Name: "Unquenched Thirst",
Description: "Moderately increase performance with the desire to race.", Description: "Moderately increase performance with the desire to race.",
Group: 30001, GroupSkills: []horse.GroupSkill{
Rarity: 1, {ID: 300011, GroupRate: 1, Rarity: 1},
GroupRate: 1, },
GradeValue: 0, Group: 30001,
WitCheck: false, Rarity: 1,
GroupRate: 1,
GradeValue: 0,
WitCheck: false,
Activations: []horse.Activation{ Activations: []horse.Activation{
{ {
Precondition: "", Precondition: "",
@@ -579,11 +653,15 @@ func TestSkills(t *testing.T) {
ID: 900011, ID: 900011,
Name: "Shooting Star", Name: "Shooting Star",
Description: "Slightly increase velocity and very minimally increase acceleration after passing another runner toward the front late-race.", Description: "Slightly increase velocity and very minimally increase acceleration after passing another runner toward the front late-race.",
Group: 10001, GroupSkills: []horse.GroupSkill{
Rarity: 1, {ID: 100011, GroupRate: 1, Rarity: 5},
GroupRate: 2, {ID: 900011, GroupRate: 2, Rarity: 1},
GradeValue: 180, },
WitCheck: true, Group: 10001,
Rarity: 1,
GroupRate: 2,
GradeValue: 180,
WitCheck: true,
Activations: []horse.Activation{ Activations: []horse.Activation{
{ {
Precondition: "", Precondition: "",
@@ -618,11 +696,16 @@ func TestSkills(t *testing.T) {
ID: 900351, ID: 900351,
Name: "Our Ticket to Win!", Name: "Our Ticket to Win!",
Description: "Slightly increase velocity when positioned toward the front on the final straight after engaging in a challenge on the final corner or later.", Description: "Slightly increase velocity when positioned toward the front on the final straight after engaging in a challenge on the final corner or later.",
Group: 10035, GroupSkills: []horse.GroupSkill{
Rarity: 1, {ID: 100351, GroupRate: 1, Rarity: 4},
GroupRate: 2, {ID: 10351, GroupRate: 1, Rarity: 3},
GradeValue: 180, {ID: 900351, GroupRate: 2, Rarity: 1},
WitCheck: true, },
Group: 10035,
Rarity: 1,
GroupRate: 2,
GradeValue: 180,
WitCheck: true,
Activations: []horse.Activation{ Activations: []horse.Activation{
{ {
Precondition: "is_finalcorner==1&blocked_side_continuetime>=2", Precondition: "is_finalcorner==1&blocked_side_continuetime>=2",
@@ -650,11 +733,15 @@ func TestSkills(t *testing.T) {
ID: 910241, ID: 910241,
Name: "Flowery☆Maneuver", Name: "Flowery☆Maneuver",
Description: "Slightly increase velocity when passing another runner toward the front on the final corner. If passing toward the back, slightly increase acceleration instead.", Description: "Slightly increase velocity when passing another runner toward the front on the final corner. If passing toward the back, slightly increase acceleration instead.",
Group: 11024, GroupSkills: []horse.GroupSkill{
Rarity: 1, {ID: 110241, GroupRate: 1, Rarity: 5},
GroupRate: 2, {ID: 910241, GroupRate: 2, Rarity: 1},
GradeValue: 180, },
WitCheck: true, Group: 11024,
Rarity: 1,
GroupRate: 2,
GradeValue: 180,
WitCheck: true,
Activations: []horse.Activation{ Activations: []horse.Activation{
{ {
Precondition: "", Precondition: "",
+1
View File
@@ -1,4 +1,5 @@
SELECT SELECT
id,
gallery_chara_id, gallery_chara_id,
disp_order, disp_order,
pos_id, pos_id,
+43
View File
@@ -0,0 +1,43 @@
WITH g AS (
SELECT
l.id AS l,
r.id AS r,
r.group_rate,
r.rarity
FROM skill_data l
JOIN skill_data r ON l.group_id = r.group_id
UNION ALL
SELECT
s.id AS l,
i.id AS r,
i.group_rate,
i.rarity
FROM skill_data s
JOIN skill_data i ON s.id IN (i.unique_skill_id_1, i.unique_skill_id_2)
UNION ALL
SELECT
s.id AS l,
i.id AS r,
i.group_rate,
i.rarity
FROM skill_data s
JOIN skill_data i ON i.id IN (s.unique_skill_id_1, s.unique_skill_id_2)
UNION ALL
SELECT
l.id AS l,
r.id AS r,
r.group_rate,
r.rarity
FROM skill_data i
JOIN skill_data l ON l.id IN (i.unique_skill_id_1, i.unique_skill_id_2)
JOIN skill_data r ON r.id IN (i.unique_skill_id_1, i.unique_skill_id_2)
WHERE l.id != r.id
)
SELECT * FROM g
ORDER BY l, group_rate, rarity DESC
+7
View File
@@ -30,6 +30,7 @@ type Skill struct {
ID SkillID `json:"skill_id"` ID SkillID `json:"skill_id"`
Name string `json:"name"` Name string `json:"name"`
Description string `json:"description"` Description string `json:"description"`
GroupSkills []GroupSkill `json:"group_skills"`
Group SkillGroupID `json:"group"` Group SkillGroupID `json:"group"`
Rarity int8 `json:"rarity"` Rarity int8 `json:"rarity"`
GroupRate int8 `json:"group_rate"` GroupRate int8 `json:"group_rate"`
@@ -42,6 +43,12 @@ type Skill struct {
IconID int `json:"icon_id"` IconID int `json:"icon_id"`
} }
type GroupSkill struct {
ID SkillID `json:"skill_id"`
GroupRate int8 `json:"group_rate"`
Rarity int8 `json:"rarity"`
}
// Activation is the parameters controlling when a skill activates. // Activation is the parameters controlling when a skill activates.
type Activation struct { type Activation struct {
Precondition string `json:"precondition,omitzero"` Precondition string `json:"precondition,omitzero"`
+10 -5
View File
@@ -49,11 +49,16 @@ export function convoCharas(c: Conversation): number[] {
export function requiredCharas(c: Conversation): number[] { export function requiredCharas(c: Conversation): number[] {
switch (c.condition_type) { switch (c.condition_type) {
case Required.None: return [] case Required.None:
case Required.All: return convoCharas(c); return [];
case Required.Chara1: return [c.chara_1]; case Required.All:
case Required.Chara2: return [c.chara_2!]; return convoCharas(c);
case Required.Chara3: return [c.chara_3!]; case Required.Chara1:
return [c.chara_1];
case Required.Chara2:
return [c.chara_2!];
case Required.Chara3:
return [c.chara_3!];
} }
} }
+8 -16
View File
@@ -1143,22 +1143,14 @@
>. (This race is why I made it.) >. (This race is why I made it.)
</p> </p>
<Sec h={3} id="jbc-sprint">JBC Sprint (Ooi ver.)</Sec> <div id="jbc-sprint" class="hidden target:block">
<p> <h3>JBC Sprint</h3>
An even more anti-front track is Ooi 1200 Dirt. This one is actively malicious. Visually, it looks like late race starts on a <p>
corner, but the portion before the stretch is a special <i>neither corner nor straight</i> property. That means Angling won't activate, It used to be the case that Oi 1200 Dirt had a strange neither-straight-nor-corner segment at the start of late race so that
and VPP is delayed (though still within the accel period). Angling wouldn't activate. As of the 1 Jul update, the corner is now connected to the final straight, so there is no longer
</p> anything remarkable about this track.
<p> </p>
Fortunately, JBC Sprint is after summer, which means you should be able to stat diff your opponents. If you are planning to </div>
win this race, you may want to prioritize a bit of extra speed training, or take Front Straights/Corners. If MANT, don't be
too surprised if you lose a clock or five to a Taiki Shuttle rival.
</p>
<p>
Since the 1 Jul update, the JBC races take place at a randomly selected one of the NAR courses (Ooi, Kawasaki, Funabashi,
Morioka) each year. If you're hunting for a completionist title on a front runner, you have the option of just not picking an
Ooi JBC Sprint; the others are all Angling tracks.
</p>
<Sec h={3} id="3k">Kikuka Sho &amp; Tenno Sho (Spring)</Sec> <Sec h={3} id="3k">Kikuka Sho &amp; Tenno Sho (Spring)</Sec>
<p> <p>
+463
View File
@@ -17,8 +17,107 @@
} }
}); });
const raceGroups = [
[1, 'Common'],
[2, 'Daily Race'],
[3, '"Training Practice"'],
[4, '"Story Condition"'],
[5, '"Team Building"'],
[6, 'Practice'],
[7, 'Career-Exclusive'],
[8, 'Legend Race'],
[9, 'Team Trials'],
[10, 'Unity Cup Team Race'],
[11, "Champions' Meet"],
[12, 'Room Match'],
[13, '"Challenge Match"'],
[14, 'League of Heroes Race'],
[61, 'Custom G1'],
] as const;
const raceGrades = [
[100, 'G1'],
[200, 'G2'],
[300, 'G3'],
[400, 'OP'],
[700, 'Pre-OP'],
[800, 'Maiden'],
[900, 'Debut'],
[999, 'Daily Race or Room Match (?)'],
[1000, 'Unity Cup Team Race (?)'],
] as const;
const raceTimes = [
[1, 'Morning (unused)'],
[2, 'Daytime'],
[3, 'Evening'],
[4, 'Night'],
] as const;
const raceInitialLanes = [
[1, 9, 0.03333333],
[2, 0, 0],
[3, 14, 0.10333333],
[4, 8, 0.03333333],
] as const;
const raceAreas = [
[0, 'Hokkaido'],
[1, 'Tohoku'],
[2, 'Kanto'],
[3, 'Western Japan'],
[4, 'Kokura'],
[5, 'France'],
[6, 'America'],
] as const;
const raceLoops = [
[1, 'None'],
[2, 'Inner'],
[3, 'Outer'],
[4, 'Outer→Inner'],
] as const;
const raceRotations = [
[1, 'Right'],
[2, 'Left'],
[3, 'Rightward Stretch (unused)'],
[4, 'Leftward Stretch'],
] as const;
const raceCourseThresholds = [
[1, 1, 0],
[2, 2, 0],
[3, 3, 0],
[4, 4, 0],
[5, 5, 0],
[6, 1, 2],
[7, 2, 3],
[8, 2, 4],
[9, 3, 5],
[10, 2, 5],
[11, 4, 5],
] as const;
const raceSaddleTypes = [
[0, 'Special'],
[1, 'G3'],
[2, 'G2'],
[3, 'G1'],
] as const;
const racePermissions = [
[1, 'Junior'],
[2, 'Classic'],
[3, 'Classic and Senior'],
[4, 'Senior'],
[5, 'Finale'],
] as const;
const skills = $derived({ const skills = $derived({
rickey: skillMap.get(100981), rickey: skillMap.get(100981),
right1: skillMap.get(200012),
left1: skillMap.get(200022),
fall2: skillMap.get(200191), fall2: skillMap.get(200191),
fall1: skillMap.get(200192), fall1: skillMap.get(200192),
fallX: skillMap.get(200193), fallX: skillMap.get(200193),
@@ -299,6 +398,10 @@
in-game shows a "Connecting..." indicator while it loads, it probably isn't in the mdb. Notably, this includes everything in-game shows a "Connecting..." indicator while it loads, it probably isn't in the mdb. Notably, this includes everything
related to career event outcomes. (Event information on sites like GameTora and GameWith are massive crowd-sourced efforts.) related to career event outcomes. (Event information on sites like GameTora and GameWith are massive crowd-sourced efforts.)
</p> </p>
<p>Since I see this question about weekly, allow me to reiterate.</p>
<p class="text-4xl font-bold">
Support card event outcomes are not in master.mdb or any other locally stored file. Not even the list of possible skills.
</p>
<p> <p>
This document is oriented toward programmers with understanding of SQL databases. Mentions of <Mono>Gallop::</Mono> are references This document is oriented toward programmers with understanding of SQL databases. Mentions of <Mono>Gallop::</Mono> are references
to types visible in decompilations of the game, but no C# knowledge is needed. to types visible in decompilations of the game, but no C# knowledge is needed.
@@ -330,6 +433,363 @@
>, but a few categories that have useful strings are missing from there. >, but a few categories that have useful strings are missing from there.
</p> </p>
<Sec h={2} id="race">Races</Sec>
<p>First, some terminology, as the game uses it internally. (Some UI phrasing disagrees.)</p>
<p>
A <i>race</i> is an organized running competition, typically held annually. A <i>race instance</i> is a single holding of a
race at a specific date and time; this includes special race instances like Team Trials races, Aim for the Stars races,
&amp;c. Race instances are held on a given <i>race course</i>, which is a fixed-length segment of a physical location called a
<i>race track</i>. A <i>win saddle</i> is a given horse's victory of a race or a specific group of races, e.g. Classic Triple Crown
or Dual Grand Prix. This section will cover races, race instances, race tracks, race courses, and win saddles.
</p>
<p>
<Mono>text_data</Mono> categories relevant to races include:
</p>
<ul class="ml-4 list-disc pb-4">
<li>32 and 33 are both race names, the latter for jikkyo (the announcer's lines).</li>
<li>
28 is race instance names. 29 is abbreviated race instance names, e.g. <Mono>Mile Ch.</Mono> instead of <Mono
>Mile Championship</Mono
>.
</li>
<li>
31 and 34 are race track full names, the former for jikkyo, e.g. <Mono>Kyoto Racecourse</Mono>. 35 is race track names
without "Racecourse".
</li>
<li>111 is win saddle names.</li>
</ul>
<Sec h={3} id="race-race">Race Info</Sec>
<p>
Races are defined in <Mono>race</Mono>, which is quite a direct name innit.
</p>
<ul class="ml-4 list-disc pb-4">
<li>
Race ID is just <Mono>id</Mono>.
</li>
<li>
<Mono>group</Mono> is the game situation where the race occurs.
<table class="table-fixed border text-center">
<thead>
<tr>
<th scope="col" class="px-2">Group</th>
<th scope="col" class="px-2">Meaning</th>
</tr>
</thead>
<tbody>
{#each raceGroups as [id, name] (id)}
<tr class="even:bg-mist-300 dark:even:bg-mist-900">
<td class="px-2">{id}</td>
<td class="px-2">{name}</td>
</tr>
{/each}
</tbody>
</table>
</li>
<li>
<Mono>grade</Mono> defines the race grade.
<table class="table-fixed border text-center">
<thead>
<tr>
<th scope="col" class="px-2">Grade ID</th>
<th scope="col" class="px-2">Meaning</th>
</tr>
</thead>
<tbody>
{#each raceGrades as [id, name] (id)}
<tr class="even:bg-mist-300 dark:even:bg-mist-900">
<td class="px-2">{id}</td>
<td class="px-2">{name}</td>
</tr>
{/each}
</tbody>
</table>
</li>
<li><Mono>course_set</Mono> gives the <a href="#race-course">race course</a> where the race occurs.</li>
<li><Mono>thumbnail_id</Mono> is the resource ID of the race's thumbnail image.</li>
<li><Mono>entry_num</Mono> is the maximum number of horses that can run the race.</li>
</ul>
<p>
Some races are ostensibly defined twice to account for variant races run during some characters' careers, e.g. Maruzensky's
five opponent Spring Stakes, McQueen/Ryan/Rice's Kyoto Takarazuka Kinen, &amp;c.
</p>
<Sec h={3} id="race-instances">Race Instances</Sec>
<p>
The table is <Mono>race_instance</Mono>.
</p>
<ul class="ml-4 list-disc pb-4">
<li><Mono>id</Mono> is the race instance ID.</li>
<li><Mono>race_id</Mono> joins with <Mono>race.id</Mono>.</li>
<li>
<Mono>npc_group_id</Mono> should define the NPC racers allowed to appear in the race instance, although I haven't looked into
it.
</li>
<li>
<Mono>date</Mono> gives the month and day-of-month when the race instance occurs, expressed as M&times;100+d &ndash; 1 Jan is
101 and 31 Dec is 1231. This appears to decide the season that the race instance occurs in and thus the applicable green skill.
E.g., we can see in career that Challenge Cup is offered on the Early December turn, but its race instance has a <Mono
>date</Mono
> of 1130 (i.e. 30 Nov), so it is a fall rather than winter race. All races occur in 2019.
</li>
<li>
<Mono>time</Mono> is the time of day that the race instance is held.
<table class="table-fixed border text-center">
<thead>
<tr>
<th scope="col" class="px-2">Time</th>
<th scope="col" class="px-2">Meaning</th>
</tr>
</thead>
<tbody>
{#each raceTimes as [id, name] (id)}
<tr class="even:bg-mist-300 dark:even:bg-mist-900">
<td class="px-2">{id}</td>
<td class="px-2">{name}</td>
</tr>
{/each}
</tbody>
</table>
</li>
<li>
<Mono>clock_time</Mono> has a value of 1605 for one race and is otherwise always 0. It might be a specific hour and minute that
a race instance occurs, but I'm not sure why that would ever matter.
</li>
<li>
<Mono>race_number</Mono> takes every value between -1 and 12, but I couldn't find any explanation for it.
</li>
</ul>
<p>
Similarly to races, some race instances are ostensibly defined twice to account for variant races run during some characters'
careers.
</p>
<Sec h={3} id="race-track">Race Tracks</Sec>
<p>
Since race tracks are the physical locations where races are held, there are not many rows in <Mono>race_track</Mono>. Most of
its purpose is to define appearance characteristics, but some fields are mechanically relevant.
</p>
<ul class="ml-4 list-disc pb-4">
<li><Mono>id</Mono> is the race track ID.</li>
<li>
<Mono>initial_lane_type</Mono> describes the starting lane positions of each horse by determining the location and width of the
gap between posts.
<table class="table-fixed border text-center">
<thead>
<tr>
<th scope="col" class="px-2">ID</th>
<th scope="col" class="px-2">Gap After Gate</th>
<th scope="col" class="px-2">Gap Width (CW)</th>
</tr>
</thead>
<tbody>
{#each raceInitialLanes as [id, gate, w] (id)}
<tr class="even:bg-mist-300 dark:even:bg-mist-900">
<td>{id}</td>
<td>{gate}</td>
<td>{w}</td>
</tr>
{/each}
</tbody>
</table>
Horses in gates
<i>after</i> the Gap After Gate listed in this table have the gap width added to their starting lane.
<Mono>initial_lane_type</Mono> is 2 on Oi, Kawasaki, and Funabashi; 3 on Longchamp; 4 on Morioka; and 1 elsewhere. (The race mechanics
doc's
<a
href="https://docs.google.com/document/d/15VzW9W2tXBBTibBRbZ8IVpW6HaMX8H0RP03kq6Az7Xg/edit?tab=t.0#heading=h.ohdxzwro17n5"
target="_blank"
rel="noopener noreferrer">description of initial lane</a
>
uses horse index rather than gate number and describes lanes in units of horse lanes rather than course widths.)
</li>
<li>
<Mono>area</Mono> defines the geographic region where the race track exists.
<table class="table-fixed border text-center">
<thead>
<tr>
<th scope="col" class="px-2">ID</th>
<th scope="col" class="px-2">Region</th>
</tr>
</thead>
<tbody>
{#each raceAreas as [id, name] (id)}
<tr class="even:bg-mist-300 dark:even:bg-mist-900">
<td class="px-2">{id}</td>
<td class="px-2">{name}</td>
</tr>
{/each}
</tbody>
</table>
</li>
</ul>
<Sec h={3} id="race-course">Race Courses</Sec>
<p>
Race courses are physical segments of race tracks. They are defined in <Mono>race_course_set</Mono>.
</p>
<p>
Most tracks have one or two turf loops (the "(Inner)" and "(Outer)" or sometimes "(Outer&rarr;Inner)") and one dirt loop. The
length of a race course is determined solely by the position of the gates: since the race course is a physical structure with
the audience stands in a specific location, and since said audience wants to see the race finish clearly, courses of all
lengths end at the same place on the front stretch. The straight opposite the front stretch is called the backstretch. Corners
are counted backward from the front stretch in the order 4, 3, 2, 1; all races except Ibis Summer Dash include corners 3 and
4, while long and sometimes medium races run corners multiple times. Some courses start on an extra segment of the track which
is <i>neither straight nor corner</i> (which means <Mono>straight_random==1</Mono> skills won't fire there).
</p>
<ul class="ml-4 list-disc pb-4">
<li><Mono>id</Mono> gives the race course ID.</li>
<li><Mono>race_track_id</Mono> joins with <Mono>race_track.id</Mono>.</li>
<li><Mono>distance</Mono> is the race course length in meters.</li>
<li><Mono>ground</Mono> is 1 for turf and 2 for dirt.</li>
<li>
<Mono>inout</Mono> gives the loop for the surface type where the race occurs.
<table class="table-fixed border text-center">
<thead>
<tr>
<th scope="col" class="px-2">ID</th>
<th scope="col" class="px-2">Loop</th>
</tr>
</thead>
<tbody>
{#each raceLoops as [id, name] (id)}
<tr class="even:bg-mist-300 dark:even:bg-mist-900">
<td class="px-2">{id}</td>
<td class="px-2">{name}</td>
</tr>
{/each}
</tbody>
</table>
</li>
<li>
<Mono>turn</Mono> gives the handedness of the track, i.e. the direction from horses to the rail as they round the track's corners.
<table class="table-fixed border text-center">
<thead>
<tr>
<th scope="col" class="px-2">ID</th>
<th scope="col" class="px-2">Handedness</th>
</tr>
</thead>
<tbody>
{#each raceRotations as [id, name] (id)}
<tr class="even:bg-mist-300 dark:even:bg-mist-900">
<td class="px-2">{id}</td>
<td class="px-2">{name}</td>
</tr>
{/each}
</tbody>
</table>
Stretch types have no corners and do not activate <Skill skill={skills.right1} /> or <Skill skill={skills.left1} /> or their variants.
Niigata 1000 is the only such race course in the game.
</li>
<li>
<Mono>float_lane_max</Mono> gives the <i>initial</i> race course width in units of ten thousandths of course widths. (Confusingly,
no race course has a width of 1 course width.) Some race courses have locations where the lane max changes, but these are defined
as events and are not in the mdb.
</li>
<li>
<Mono>course_set_status_id</Mono> joins with <Mono>race_course_set_status.course_set_status_id</Mono> to define the stat thresholds.
A value of 0 means no thresholds.
<table class="table-fixed border text-center">
<caption><Mono>race_course_set_status</Mono></caption>
<thead>
<tr>
<th scope="col" class="px-2"><Mono>course_set_status_id</Mono></th>
<th scope="col" class="px-2"><Mono>target_status_1</Mono></th>
<th scope="col" class="px-2"><Mono>target_status_2</Mono></th>
</tr>
</thead>
<tbody>
{#each raceCourseThresholds as [id, stat1, stat2] (id)}
<tr class="even:bg-mist-300 dark:even:bg-mist-900">
<td class="px-2">{id}</td>
<td class="px-2">{stat1}</td>
<td class="px-2">{stat2}</td>
</tr>
{/each}
</tbody>
</table>
Status of 1 is Speed, 2 is Power, and so on up to 5 for Wit. 0 means no (second) threshold.
</li>
</ul>
<p>
Characteristics like straight and corner locations, slopes, lane max changes, &amp;c. are <i>not</i> in the mdb. They are defined
as assets instead and must be extracted using special tools.
</p>
<Sec h={3} id="race-saddle">Win Saddles</Sec>
<p>
<Mono>single_mode_wins_saddle</Mono> defines the awards given to the horse (not the player) for winning a race or specific group
of races. These are the medals displayed as important wins for a horse at the end of a career. They are now purely aesthetic, but
I am documenting them anyway because I have the notes already since they used to determine affinity.
</p>
<ul class="ml-4 list-disc pb-4">
<li><Mono>id</Mono> is the win saddle ID.</li>
<li><Mono>priority</Mono> is the sort order for win saddles.</li>
<li>
<Mono>group_id</Mono> associates multiple win saddles for the same race, for horses whose careers have special versions of some
races.
</li>
<li>
<Mono>win_saddle_type</Mono> is the type of the win saddle, used to determine the color of the medal displayed.
<table class="table-fixed border text-center">
<thead>
<tr>
<th scope="col" class="px-2">ID</th>
<th scope="col" class="px-2">Type</th>
</tr>
</thead>
<tbody>
{#each raceSaddleTypes as [id, name] (id)}
<tr class="even:bg-mist-300 dark:even:bg-mist-900">
<td class="px-2">{id}</td>
<td class="px-2">{name}</td>
</tr>
{/each}
</tbody>
</table>
Special win saddles are for win saddles requiring multiple wins, e.g. Classic Triple Crown.
</li>
<li>
<Mono>race_instance_id_1</Mono> through <Mono>race_instance_id_8</Mono> define the race instance IDs that must be won to be awarded
the win saddle. Columns beyond <Mono>race_instance_id_3</Mono> are unused.
</li>
</ul>
<Sec h={3} id="race-program">Single Mode Program</Sec>
<p>
The table <Mono>single_mode_program</Mono> defines the race instances offered each turn in a career. Confusingly, races that permit
both classic and senior year horses are only defined here once.
</p>
<ul class="ml-4 list-disc pb-4">
<li><Mono>race_instance_id</Mono> joins with <Mono>race_instance.id</Mono>.</li>
<li>
<Mono>race_permission</Mono> gives the career years that offer the race. (More precisely, it gives the age ranges of horses that
are permitted to enter.)
<table class="table-fixed border text-center">
<thead>
<tr>
<th scope="col" class="px-2">ID</th>
<th scope="col" class="px-2">Meaning</th>
</tr>
</thead>
<tbody>
{#each racePermissions as [id, name] (id)}
<tr class="even:bg-mist-300 dark:even:bg-mist-900">
<td class="px-2">{id}</td>
<td class="px-2">{name}</td>
</tr>
{/each}
</tbody>
</table>
</li>
<li><Mono>month</Mono> and <Mono>half</Mono> give the turn that the race is held.</li>
</ul>
<p>
I was unable to identify whether any of the columns indicate maiden races, despite the UI restrictions on when they can
appear.
</p>
<Sec h={2} id="skills">Skills</Sec> <Sec h={2} id="skills">Skills</Sec>
<p> <p>
Relevant <Mono>text_data</Mono> categories include 47 for skill names and 48 for skill descriptions. Otherwise, info for skills Relevant <Mono>text_data</Mono> categories include 47 for skill names and 48 for skill descriptions. Otherwise, info for skills
@@ -816,6 +1276,9 @@
Relevant <Mono>text_data</Mono> categories include 75 for name including variant, 76 for variant alone, 77 for character name. Category Relevant <Mono>text_data</Mono> categories include 75 for name including variant, 76 for variant alone, 77 for character name. Category
151 gives support card effect names, 154 gives effect descriptions, 150 gives unique effect names, and 155 gives unique effect descriptions. 151 gives support card effect names, 154 gives effect descriptions, 150 gives unique effect names, and 155 gives unique effect descriptions.
</p> </p>
<p class="text-4xl font-bold">
Support card event outcomes are not in master.mdb or any other locally stored file. Not even the list of possible skills.
</p>
<Sec h={3} id="support_card_data">Support Card Data</Sec> <Sec h={3} id="support_card_data">Support Card Data</Sec>
<p> <p>