horsegen: generate umas

This commit is contained in:
2026-02-26 19:02:49 -05:00
parent 3fa30903cd
commit 7972bab46c
13 changed files with 3747 additions and 15 deletions

View File

@@ -6,7 +6,7 @@ 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 variant
- 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
@@ -64,9 +64,6 @@ effect_id distinguishes possibilities; factors with multiple effects (race and s
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.
sf.grade = 1 unless it is a unique (green) spark, then sf.grade = 2.
=> sf.grade = 2 iff sf.factor_type = 3
getting all interesting spark data, fully expanded with all effects:
```sql
WITH spark AS (

View File

@@ -0,0 +1,31 @@
// Code generated by "stringer -type AptitudeLevel -trimprefix AptitudeLv"; DO NOT EDIT.
package horse
import "strconv"
func _() {
// An "invalid array index" compiler error signifies that the constant values have changed.
// Re-run the stringer command to generate them again.
var x [1]struct{}
_ = x[AptitudeLvG-1]
_ = x[AptitudeLvF-2]
_ = x[AptitudeLvE-3]
_ = x[AptitudeLvD-4]
_ = x[AptitudeLvC-5]
_ = x[AptitudeLvB-6]
_ = x[AptitudeLvA-7]
_ = x[AptitudeLvS-8]
}
const _AptitudeLevel_name = "GFEDCBAS"
var _AptitudeLevel_index = [...]uint8{0, 1, 2, 3, 4, 5, 6, 7, 8}
func (i AptitudeLevel) String() string {
idx := int(i) - 1
if i < 1 || idx >= len(_AptitudeLevel_index)-1 {
return "AptitudeLevel(" + strconv.FormatInt(int64(i), 10) + ")"
}
return _AptitudeLevel_name[_AptitudeLevel_index[idx]:_AptitudeLevel_index[idx+1]]
}

1686
horse/global/uma.go Normal file

File diff suppressed because it is too large Load Diff

1550
horse/global/uma.kk Normal file

File diff suppressed because it is too large Load Diff

32
horse/uma.go Normal file
View File

@@ -0,0 +1,32 @@
package horse
type UmaID int32
type Uma struct {
ID UmaID
CharacterID CharacterID
Name string
Variant string
Sprint, Mile, Medium, Long AptitudeLevel
Front, Pace, Late, End AptitudeLevel
Turf, Dirt AptitudeLevel
Unique SkillID
Skill1, Skill2, Skill3 SkillID
SkillPL2, SkillPL3, SkillPL4, SkillPL5 SkillID
}
type AptitudeLevel int8
//go:generate go run golang.org/x/tools/cmd/stringer@v0.41.0 -type AptitudeLevel -trimprefix AptitudeLv
const (
AptitudeLvG AptitudeLevel = iota + 1
AptitudeLvF
AptitudeLvE
AptitudeLvD
AptitudeLvC
AptitudeLvB
AptitudeLvA
AptitudeLvS
)

View File

@@ -7,13 +7,21 @@ import horse/movement
pub struct uma-detail
uma-id: uma-id
character-id: character-id
turf: level
dirt: level
sprint: level
mile: level
medium: level
long: level
front-runner: level
pace-chaser: level
late-surger: level
end-closer: level
sprint: aptitude-level
mile: aptitude-level
medium: aptitude-level
long: aptitude-level
front-runner: aptitude-level
pace-chaser: aptitude-level
late-surger: aptitude-level
end-closer: aptitude-level
turf: aptitude-level
dirt: aptitude-level
unique: skill-id
skill1: skill-id
skill2: skill-id
skill3: skill-id
skill-pl2: skill-id
skill-pl3: skill-id
skill-pl4: skill-id
skill-pl5: skill-id

View File

@@ -159,7 +159,23 @@ func ExecSparks(t *template.Template, region string, kk, g io.Writer, sparks []S
return err
}
const wordSeps = " ,!?/-+();#○☆♡'=♪∀゚∴"
func ExecUmas(t *template.Template, region string, kk, g io.Writer, umas []Uma) error {
data := struct {
Region string
Umas []Uma
UmaCount int
}{region, umas, len(umas)}
var err error
if kk != nil {
err = errors.Join(err, t.ExecuteTemplate(kk, "koka-uma", &data))
}
if g != nil {
err = errors.Join(err, t.ExecuteTemplate(g, "go-uma", &data))
}
return err
}
const wordSeps = " ,!?/-+();#○☆♡'=♪∀゚∴[]:"
var (
kkReplace = func() *strings.Replacer {

View File

@@ -17,6 +17,9 @@ var characterAffinity2SQL string
//go:embed character.affinity3.sql
var characterAffinity3SQL string
//go:embed uma.sql
var umaSQL string
//go:embed skill-group.sql
var skillGroupSQL string
@@ -541,3 +544,70 @@ func SparkEffects(ctx context.Context, db *sqlitex.Pool) (map[int]map[int][]Spar
}
return r, nil
}
type Uma struct {
ID int
CharacterID int
Name string
Variant string
CharacterName string
Sprint, Mile, Medium, Long int
Front, Pace, Late, End int
Turf, Dirt int
UniqueID int
Skill1, Skill2, Skill3 int
SkillPL2, SkillPL3, SkillPL4, SkillPL5 int
}
func Umas(ctx context.Context, db *sqlitex.Pool) ([]Uma, error) {
conn, err := db.Take(ctx)
defer db.Put(conn)
if err != nil {
return nil, fmt.Errorf("couldn't get connection for umas: %w", err)
}
stmt, _, err := conn.PrepareTransient(umaSQL)
if err != nil {
return nil, fmt.Errorf("couldn't prepare statement for umas: %w", err)
}
defer stmt.Finalize()
var r []Uma
for {
ok, err := stmt.Step()
if err != nil {
return nil, fmt.Errorf("error stepping umas: %w", err)
}
if !ok {
break
}
uma := Uma{
ID: stmt.ColumnInt(0),
CharacterID: stmt.ColumnInt(1),
Name: stmt.ColumnText(2),
Variant: stmt.ColumnText(3),
CharacterName: stmt.ColumnText(4),
Sprint: stmt.ColumnInt(5),
Mile: stmt.ColumnInt(6),
Medium: stmt.ColumnInt(7),
Long: stmt.ColumnInt(8),
Front: stmt.ColumnInt(9),
Pace: stmt.ColumnInt(10),
Late: stmt.ColumnInt(11),
End: stmt.ColumnInt(12),
Turf: stmt.ColumnInt(13),
Dirt: stmt.ColumnInt(14),
UniqueID: stmt.ColumnInt(15),
Skill1: stmt.ColumnInt(16),
Skill2: stmt.ColumnInt(17),
Skill3: stmt.ColumnInt(18),
SkillPL2: stmt.ColumnInt(19),
SkillPL3: stmt.ColumnInt(20),
SkillPL4: stmt.ColumnInt(21),
SkillPL5: stmt.ColumnInt(22),
}
r = append(r, uma)
}
return r, nil
}

View File

@@ -55,6 +55,7 @@ func main() {
scens []Scenario
sparks []Spark
sparkeff map[int]map[int][]SparkEffect
umas []Uma
)
eg.Go(func() error {
slog.Info("get characters")
@@ -116,6 +117,12 @@ func main() {
sparkeff = r
return err
})
eg.Go(func() error {
slog.Info("get umas")
r, err := Umas(ctx, db)
umas = r
return err
})
if err := eg.Wait(); err != nil {
slog.Error("load", slog.Any("err", err))
os.Exit(1)
@@ -199,6 +206,18 @@ func main() {
slog.Info("write sparks")
return ExecSparks(t, region, kf, gf, sparks, sparkeff)
})
eg.Go(func() error {
kf, err := os.Create(filepath.Join(out, region, "uma.kk"))
if err != nil {
return err
}
gf, err := os.Create(filepath.Join(out, region, "uma.go"))
if err != nil {
return err
}
slog.Info("write umas")
return ExecUmas(t, region, kf, gf, umas)
})
if err := eg.Wait(); err != nil {
slog.Error("generate", slog.Any("err", err))
os.Exit(1)

42
horsegen/uma.go.template Normal file
View File

@@ -0,0 +1,42 @@
{{- define "go-uma" -}}
package {{ $.Region }}
// Automatically generated with horsegen; DO NOT EDIT
import . "git.sunturtle.xyz/zephyr/horse/horse"
const (
{{- range $uma := $.Umas }}
{{ goenum $uma.CharacterName }}{{ goenum $uma.Variant }} UmaID = {{ $uma.ID }} // {{ $uma.Name }}
{{- end }}
)
var AllUmas = map[UmaID]Uma{
{{- range $uma := $.Umas }}
{{ goenum $uma.CharacterName }}{{ goenum $uma.Variant }}: {
ID: {{ $uma.ID }},
CharacterID: {{ $uma.CharacterID }},
Name: {{ printf "%q" $uma.Name }},
Variant: {{ printf "%q" $uma.Variant }},
Sprint: {{ $uma.Sprint }},
Mile: {{ $uma.Mile }},
Medium: {{ $uma.Medium }},
Long: {{ $uma.Long }},
Front: {{ $uma.Front }},
Pace: {{ $uma.Pace }},
Late: {{ $uma.Late }},
End: {{ $uma.End }},
Turf: {{ $uma.Turf }},
Dirt: {{ $uma.Dirt }},
Unique: {{ $uma.UniqueID }},
Skill1: {{ $uma.Skill1 }},
Skill2: {{ $uma.Skill2 }},
Skill3: {{ $uma.Skill3 }},
SkillPL2: {{ $uma.SkillPL2 }},
SkillPL3: {{ $uma.SkillPL3 }},
SkillPL4: {{ $uma.SkillPL4 }},
SkillPL5: {{ $uma.SkillPL5 }},
},
{{- end }}
}
{{ end }}

221
horsegen/uma.kk.template Normal file
View File

@@ -0,0 +1,221 @@
{{- define "koka-uma" -}}
module horse/{{ $.Region }}/uma
// Automatically generated with horsegen; DO NOT EDIT
import std/core/delayed
import std/core/vector
import std/core-extras
import horse/game-id
import horse/movement
pub import horse/uma
extern create-id-table(): vector<int>
c inline "int32_t arr[] = { {{- range $uma := $.Umas }}{{ $uma.ID }},{{ end -}} };\nkk_vector_from_cint32array(arr, (kk_ssize_t){{ $.UmaCount }}, kk_context())"
js inline "[{{ range $uma := $.Umas }}{{ $uma.ID }},{{ end }}]"
// Vector of all Uma IDs in order for easy iterating.
val vall = once(create-id-table)
// Get the name for an Uma.
// The name includes the costume variant, e.g. `[Special Dreamer] Special Week`.
// If no Uma matches the ID, the result contains the numeric ID.
pub fun show(uma: uma-id): string
match uma.game-id
{{- range $uma := $.Umas }}
{{ $uma.ID }} -> {{ printf "%q" $uma.Name }}
{{- end }}
x -> "uma " ++ x.show
// Get the costume variant for an Uma, e.g. `[Special Dreamer]`.
// If no Uma matches the ID, the result contains the numeric ID.
pub fun variant(uma: uma-id): string
match uma.game-id
{{- range $uma := $.Umas }}
{{ $uma.ID }} -> {{ printf "%q" $uma.Variant }}
{{- end }}
x -> "uma " ++ x.show
// Get the character ID for an Uma.
// If no Uma matches the ID, the result is an invalid ID.
pub fun character-id(uma: uma-id): character-id
match uma.game-id
{{- range $uma := $.Umas }}
{{ $uma.ID }} -> Character-id({{ $uma.CharacterID }})
{{- end }}
_ -> Character-id(0)
// Get the sprint aptitude for an Uma.
// If no Uma matches the ID, the result is G.
pub fun sprint(uma: uma-id): aptitude-level
match uma.game-id
{{- range $uma := $.Umas }}
{{ $uma.ID }} -> {{ template "koka-aptitude-level" $uma.Sprint }}
{{- end }}
_ -> G
// Get the mile aptitude for an Uma.
// If no Uma matches the ID, the result is G.
pub fun mile(uma: uma-id): aptitude-level
match uma.game-id
{{- range $uma := $.Umas }}
{{ $uma.ID }} -> {{ template "koka-aptitude-level" $uma.Mile }}
{{- end }}
_ -> G
// Get the medium aptitude for an Uma.
// If no Uma matches the ID, the result is G.
pub fun medium(uma: uma-id): aptitude-level
match uma.game-id
{{- range $uma := $.Umas }}
{{ $uma.ID }} -> {{ template "koka-aptitude-level" $uma.Medium }}
{{- end }}
_ -> G
// Get the long aptitude for an Uma.
// If no Uma matches the ID, the result is G.
pub fun long(uma: uma-id): aptitude-level
match uma.game-id
{{- range $uma := $.Umas }}
{{ $uma.ID }} -> {{ template "koka-aptitude-level" $uma.Long }}
{{- end }}
_ -> G
// Get the front runner aptitude for an Uma.
// If no Uma matches the ID, the result is G.
pub fun front-runner(uma: uma-id): aptitude-level
match uma.game-id
{{- range $uma := $.Umas }}
{{ $uma.ID }} -> {{ template "koka-aptitude-level" $uma.Front }}
{{- end }}
_ -> G
// Get the pace chaser aptitude for an Uma.
// If no Uma matches the ID, the result is G.
pub fun pace-chaser(uma: uma-id): aptitude-level
match uma.game-id
{{- range $uma := $.Umas }}
{{ $uma.ID }} -> {{ template "koka-aptitude-level" $uma.Pace }}
{{- end }}
_ -> G
// Get the late surger aptitude for an Uma.
// If no Uma matches the ID, the result is G.
pub fun late-surger(uma: uma-id): aptitude-level
match uma.game-id
{{- range $uma := $.Umas }}
{{ $uma.ID }} -> {{ template "koka-aptitude-level" $uma.Late }}
{{- end }}
_ -> G
// Get the end closer aptitude for an Uma.
// If no Uma matches the ID, the result is G.
pub fun end-closer(uma: uma-id): aptitude-level
match uma.game-id
{{- range $uma := $.Umas }}
{{ $uma.ID }} -> {{ template "koka-aptitude-level" $uma.End }}
{{- end }}
_ -> G
// Get the turf aptitude for an Uma.
// If no Uma matches the ID, the result is G.
pub fun turf(uma: uma-id): aptitude-level
match uma.game-id
{{- range $uma := $.Umas }}
{{ $uma.ID }} -> {{ template "koka-aptitude-level" $uma.Turf }}
{{- end }}
_ -> G
// Get the dirt aptitude for an Uma.
// If no Uma matches the ID, the result is G.
pub fun dirt(uma: uma-id): aptitude-level
match uma.game-id
{{- range $uma := $.Umas }}
{{ $uma.ID }} -> {{ template "koka-aptitude-level" $uma.Dirt }}
{{- end }}
_ -> G
// Get the unique skill for an Uma.
// If no Uma matches the ID, the result is an invalid ID.
pub fun unique(uma: uma-id): skill-id
match uma.game-id
{{- range $uma := $.Umas }}
{{ $uma.ID }} -> Skill-id({{ $uma.UniqueID }})
{{- end }}
_ -> Skill-id(0)
// Get the first built-in skill for an Uma.
// If no Uma matches the ID, the result is an invalid ID.
pub fun skill1(uma: uma-id): skill-id
match uma.game-id
{{- range $uma := $.Umas }}
{{ $uma.ID }} -> Skill-id({{ $uma.Skill1 }})
{{- end }}
_ -> Skill-id(0)
// Get the second built-in skill for an Uma.
// If no Uma matches the ID, the result is an invalid ID.
pub fun skill2(uma: uma-id): skill-id
match uma.game-id
{{- range $uma := $.Umas }}
{{ $uma.ID }} -> Skill-id({{ $uma.Skill2 }})
{{- end }}
_ -> Skill-id(0)
// Get the third built-in skill for an Uma.
// If no Uma matches the ID, the result is an invalid ID.
pub fun skill3(uma: uma-id): skill-id
match uma.game-id
{{- range $uma := $.Umas }}
{{ $uma.ID }} -> Skill-id({{ $uma.Skill3 }})
{{- end }}
_ -> Skill-id(0)
// Get the potential level 2 skill for an Uma.
// If no Uma matches the ID, the result is an invalid ID.
pub fun skill-pl2(uma: uma-id): skill-id
match uma.game-id
{{- range $uma := $.Umas }}
{{ $uma.ID }} -> Skill-id({{ $uma.SkillPL2 }})
{{- end }}
_ -> Skill-id(0)
// Get the potential level 3 skill for an Uma.
// If no Uma matches the ID, the result is an invalid ID.
pub fun skill-pl3(uma: uma-id): skill-id
match uma.game-id
{{- range $uma := $.Umas }}
{{ $uma.ID }} -> Skill-id({{ $uma.SkillPL3 }})
{{- end }}
_ -> Skill-id(0)
// Get the potential level 4 skill for an Uma.
// If no Uma matches the ID, the result is an invalid ID.
pub fun skill-pl4(uma: uma-id): skill-id
match uma.game-id
{{- range $uma := $.Umas }}
{{ $uma.ID }} -> Skill-id({{ $uma.SkillPL4 }})
{{- end }}
_ -> Skill-id(0)
// Get the potential level 5 skill for an Uma.
// If no Uma matches the ID, the result is an invalid ID.
pub fun skill-pl5(uma: uma-id): skill-id
match uma.game-id
{{- range $uma := $.Umas }}
{{ $uma.ID }} -> Skill-id({{ $uma.SkillPL5 }})
{{- end }}
_ -> Skill-id(0)
{{ end }}
{{- define "koka-aptitude-level" -}}
{{- if eq . 1 -}} G
{{- else if eq . 2 -}} F
{{- else if eq . 3 -}} E
{{- else if eq . 4 -}} D
{{- else if eq . 5 -}} C
{{- else if eq . 6 -}} B
{{- else if eq . 7 -}} A
{{- else if eq . 8 -}} S
{{- else -}} ??? aptitude={{ . }}
{{- end -}}
{{- end -}}

59
horsegen/uma.sql Normal file
View File

@@ -0,0 +1,59 @@
WITH uma_name AS (
SELECT "index" AS id, "text" AS name
FROM text_data
WHERE category = 4
), uma_variant AS (
SELECT "index" AS id, "text" AS variant
FROM text_data
WHERE category = 5
), chara_name AS (
SELECT "index" AS id, "text" AS name
FROM text_data
WHERE category = 6
), skills AS (
SELECT
uma.id,
s.skill_id,
s.need_rank,
ROW_NUMBER() OVER (PARTITION BY s.available_skill_set_id, s.need_rank) AS idx
FROM card_data uma
LEFT JOIN available_skill_set s ON uma.available_skill_set_id = s.available_skill_set_id
)
SELECT
uma.card_id,
card_data.chara_id,
n.name,
v.variant,
c.name AS chara_name,
uma.proper_distance_short,
uma.proper_distance_mile,
uma.proper_distance_middle,
uma.proper_distance_long,
uma.proper_running_style_nige,
uma.proper_running_style_senko,
uma.proper_running_style_sashi,
uma.proper_running_style_oikomi,
uma.proper_ground_turf,
uma.proper_ground_dirt,
su.skill_id1 AS unique_skill,
s1.skill_id AS skill1,
s2.skill_id AS skill2,
s3.skill_id AS skill3,
sp2.skill_id AS skill_pl2,
sp3.skill_id AS skill_pl3,
sp4.skill_id AS skill_pl4,
sp5.skill_id AS skill_pl5
FROM card_data
JOIN card_rarity_data uma ON card_data.id = uma.card_id
JOIN chara_name c ON card_data.chara_id = c.id
JOIN skill_set su ON uma.skill_set = su.id
JOIN skills s1 ON uma.card_id = s1.id AND s1.need_rank = 0 AND s1.idx = 1
JOIN skills s2 ON uma.card_id = s2.id AND s2.need_rank = 0 AND s2.idx = 2
JOIN skills s3 ON uma.card_id = s3.id AND s3.need_rank = 0 AND s3.idx = 3
JOIN skills sp2 ON uma.card_id = sp2.id AND sp2.need_rank = 2
JOIN skills sp3 ON uma.card_id = sp3.id AND sp3.need_rank = 3
JOIN skills sp4 ON uma.card_id = sp4.id AND sp4.need_rank = 4
JOIN skills sp5 ON uma.card_id = sp5.id AND sp5.need_rank = 5
LEFT JOIN uma_name n ON uma.card_id = n.id
LEFT JOIN uma_variant v ON uma.card_id = v.id
WHERE uma.rarity = 5

View File

@@ -6,6 +6,7 @@ import horse/global/saddle
import horse/global/scenario
import horse/global/skill
import horse/global/spark
import horse/global/uma
pub fun main()
()