global, cmd/horsegen: don't generate json anymore

Everything gets the data from the mdb directly instead now.
This commit is contained in:
2026-06-05 14:34:50 -04:00
parent a848c8eb72
commit d431d3382a
23 changed files with 1 additions and 267806 deletions
-121
View File
@@ -1,121 +0,0 @@
package main
import (
"bufio"
"context"
_ "embed"
"encoding/json"
"errors"
"flag"
"log/slog"
"os"
"os/signal"
"path/filepath"
"golang.org/x/sync/errgroup"
"zombiezen.com/go/sqlite"
"zombiezen.com/go/sqlite/sqlitex"
"git.sunturtle.xyz/zephyr/horse/mdb"
)
func main() {
var (
mdbf string
out string
region string
)
flag.StringVar(&mdbf, "mdb", os.ExpandEnv(`$USERPROFILE\AppData\LocalLow\Cygames\Umamusume\master\master.mdb`), "`path` to Umamusume master.mdb")
flag.StringVar(&out, "o", `.`, "`dir`ectory for output files")
flag.StringVar(&region, "region", "global", "region the database is for (global, jp)")
flag.Parse()
slog.Info("open", slog.String("mdb", mdbf))
db, err := sqlitex.NewPool(mdbf, sqlitex.PoolOptions{Flags: sqlite.OpenReadOnly})
if err != nil {
slog.Error("opening mdb", slog.String("mdb", mdbf), slog.Any("err", err))
os.Exit(1)
}
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt)
go func() {
<-ctx.Done()
stop()
}()
loadgroup, ctx1 := errgroup.WithContext(ctx)
charas := load(ctx1, loadgroup, db, "characters", mdb.Characters)
aff := load(ctx1, loadgroup, db, "pair affinity", mdb.AffinitySummary)
umas := load(ctx1, loadgroup, db, "umas", mdb.Umas)
sg := load(ctx1, loadgroup, db, "skill groups", mdb.SkillGroups)
skills := load(ctx1, loadgroup, db, "skills", mdb.Skills)
races := load(ctx1, loadgroup, db, "races", mdb.Races)
saddles := load(ctx1, loadgroup, db, "saddles", mdb.Saddles)
scenarios := load(ctx1, loadgroup, db, "scenarios", mdb.Scenarios)
sparks := load(ctx1, loadgroup, db, "sparks", mdb.Sparks)
convos := load(ctx1, loadgroup, db, "lobby conversations", mdb.Conversations)
if err := os.MkdirAll(filepath.Join(out, region), 0775); err != nil {
slog.Error("create output dir", slog.Any("err", err))
os.Exit(1)
}
writegroup, ctx2 := errgroup.WithContext(ctx)
writegroup.Go(func() error { return write(ctx2, out, region, "character.json", charas) })
writegroup.Go(func() error { return write(ctx2, out, region, "affinity.json", aff) })
writegroup.Go(func() error { return write(ctx2, out, region, "uma.json", umas) })
writegroup.Go(func() error { return write(ctx2, out, region, "skill-group.json", sg) })
writegroup.Go(func() error { return write(ctx2, out, region, "skill.json", skills) })
writegroup.Go(func() error { return write(ctx2, out, region, "race.json", races) })
writegroup.Go(func() error { return write(ctx2, out, region, "saddle.json", saddles) })
writegroup.Go(func() error { return write(ctx2, out, region, "scenario.json", scenarios) })
writegroup.Go(func() error { return write(ctx2, out, region, "spark.json", sparks) })
writegroup.Go(func() error { return write(ctx2, out, region, "conversation.json", convos) })
if err := writegroup.Wait(); err != nil {
slog.ErrorContext(ctx, "write", slog.Any("err", err))
os.Exit(1)
}
slog.InfoContext(ctx, "done")
}
func load[T any](ctx context.Context, group *errgroup.Group, db *sqlitex.Pool, kind string, get func(context.Context, *sqlitex.Pool) ([]T, error)) func() ([]T, error) {
slog.InfoContext(ctx, "load", slog.String("kind", kind))
var r []T
group.Go(func() error {
got, err := get(ctx, db)
r = got
return err
})
return func() ([]T, error) {
err := group.Wait()
if err == context.Canceled {
// After the first wait, all future ones return context.Canceled.
// We want to be able to wait any number of times, so hide it.
err = nil
}
return r, err
}
}
func write[T any](ctx context.Context, out, region, name string, v func() (T, error)) error {
p := filepath.Join(out, region, name)
r, err := v()
if err != nil {
return err
}
slog.InfoContext(ctx, "write", slog.String("path", p))
f, err := os.Create(p)
if err != nil {
return err
}
defer f.Close()
w := bufio.NewWriter(f)
enc := json.NewEncoder(w)
enc.SetEscapeHTML(false)
enc.SetIndent("", "\t")
err = enc.Encode(r)
err = errors.Join(err, w.Flush())
slog.InfoContext(ctx, "marshaled", slog.String("path", p))
return err
}
-60
View File
@@ -1,60 +0,0 @@
WITH pairs AS (
SELECT
a.id AS id_a,
b.id AS id_b
FROM chara_data a
JOIN chara_data b ON a.id < b.id
-- Exclude characters who have no succession relations defined.
WHERE a.id IN (SELECT chara_id FROM succession_relation_member)
AND b.id IN (SELECT chara_id FROM succession_relation_member)
), trios AS (
SELECT
a.id AS id_a,
b.id AS id_b,
c.id AS id_c
FROM chara_data a
JOIN chara_data b ON a.id < b.id
JOIN chara_data c ON a.id < c.id AND b.id < c.id
-- Exclude characters who have no succession relations defined.
WHERE a.id IN (SELECT chara_id FROM succession_relation_member)
AND b.id IN (SELECT chara_id FROM succession_relation_member)
AND c.id IN (SELECT chara_id FROM succession_relation_member)
), pair_relations AS (
SELECT
ra.relation_type,
ra.chara_id AS id_a,
rb.chara_id AS id_b
FROM succession_relation_member ra
JOIN succession_relation_member rb ON ra.relation_type = rb.relation_type
), trio_relations AS (
SELECT
ra.relation_type,
ra.chara_id AS id_a,
rb.chara_id AS id_b,
rc.chara_id AS id_c
FROM succession_relation_member ra
JOIN succession_relation_member rb ON ra.relation_type = rb.relation_type
JOIN succession_relation_member rc ON ra.relation_type = rc.relation_type
), affinity AS (
SELECT
pairs.*,
0 AS id_c,
SUM(IFNULL(relation_point, 0)) AS base_affinity
FROM pairs
LEFT JOIN pair_relations rp ON pairs.id_a = rp.id_a AND pairs.id_b = rp.id_b
LEFT JOIN succession_relation sr ON rp.relation_type = sr.relation_type
GROUP BY pairs.id_a, pairs.id_b
UNION ALL
SELECT
trios.*,
SUM(IFNULL(relation_point, 0)) AS base_affinity
FROM trios
LEFT JOIN trio_relations rt ON trios.id_a = rt.id_a AND trios.id_b = rt.id_b AND trios.id_c = rt.id_c
LEFT JOIN succession_relation sr ON rt.relation_type = sr.relation_type
GROUP BY trios.id_a, trios.id_b, trios.id_c
)
SELECT * FROM affinity
WHERE base_affinity != 0
ORDER BY id_a, id_b, id_c
-6
View File
@@ -1,6 +0,0 @@
SELECT
"index" AS "id",
"text" AS "name"
FROM text_data
WHERE category = 6
ORDER BY "id"
-10
View File
@@ -1,10 +0,0 @@
SELECT
gallery_chara_id,
disp_order,
pos_id,
chara_id_1,
chara_id_2,
chara_id_3,
condition_type
FROM home_story_trigger
ORDER BY gallery_chara_id, disp_order
-14
View File
@@ -1,14 +0,0 @@
WITH race_names AS (
SELECT "index" AS id, "text" AS name FROM text_data WHERE category = 33
)
SELECT
race.id,
race_names.name,
race.grade,
race.thumbnail_id,
MIN(race.id) OVER (PARTITION BY race_names.name) AS "primary",
ROW_NUMBER() OVER (PARTITION BY race_names.name ORDER BY race.id) - 1 AS "alternate"
FROM race
JOIN race_names ON race.id = race_names.id
WHERE race."group" = 1
ORDER BY race.id
-20
View File
@@ -1,20 +0,0 @@
WITH saddle_names AS (
SELECT "index" AS id, "text" AS name
FROM text_data
WHERE category = 111
)
SELECT
s.id,
n.name,
ri1.id AS race1,
IFNULL(ri2.id, 0) AS race2,
IFNULL(ri3.id, 0) AS race3,
s.win_saddle_type,
MIN(s.id) OVER (PARTITION BY n.name) AS "primary",
ROW_NUMBER() OVER (PARTITION BY n.name ORDER BY s.id) - 1 AS "alternate"
FROM single_mode_wins_saddle s
JOIN race_instance ri1 ON s.race_instance_id_1 = ri1.id
LEFT JOIN race_instance ri2 ON s.race_instance_id_2 = ri2.id
LEFT JOIN race_instance ri3 ON s.race_instance_id_3 = ri3.id
LEFT JOIN saddle_names n ON s.id = n.id
ORDER BY s.id
-17
View File
@@ -1,17 +0,0 @@
WITH scenario_name AS (
SELECT "index" AS id, "text" AS name
FROM text_data
WHERE category = 237
), scenario_title AS (
SELECT "index" AS id, "text" AS title
FROM text_data
WHERE category = 119
)
SELECT
sc.id,
n.name,
t.title
FROM single_mode_scenario sc
JOIN scenario_name n ON sc.id = n.id
JOIN scenario_title t ON sc.id = t.id
ORDER BY sc.id
-17
View File
@@ -1,17 +0,0 @@
WITH skill_groups AS (
SELECT DISTINCT group_id FROM skill_data
)
SELECT
g.group_id,
COALESCE(inh_from.id, s1.id, 0) AS skill1,
COALESCE(inh_to.id, s2.id, 0) AS skill2,
IFNULL(s3.id, 0) AS skill3,
IFNULL(m1.id, 0) AS skill_bad
FROM skill_groups g
LEFT JOIN skill_data s1 ON g.group_id = s1.group_id AND s1.group_rate = 1
LEFT JOIN skill_data s2 ON g.group_id = s2.group_id AND s2.group_rate = 2
LEFT JOIN skill_data s3 ON g.group_id = s3.group_id AND s3.group_rate = 3
LEFT JOIN skill_data m1 ON g.group_id = m1.group_id AND m1.group_rate = -1
LEFT JOIN skill_data inh_to ON s1.id = inh_to.unique_skill_id_1
LEFT JOIN skill_data inh_from ON s2.unique_skill_id_1 = inh_from.id
ORDER BY g.group_id
-98
View File
@@ -1,98 +0,0 @@
WITH skill_names AS (
SELECT
n."index" AS "id",
n."text" AS "name",
d."text" AS "description"
FROM text_data n
JOIN text_data d ON n."index" = d."index" AND n."category" = 47 AND d."category" = 48
), skill_groups AS (
SELECT
group_id,
name
FROM skill_data d
JOIN skill_names n ON d.id = n.id
WHERE group_rate = 1
), card_name AS (
SELECT
"index" AS "id",
"text" AS "name"
FROM text_data n
WHERE category = 4
), card_unique AS (
SELECT DISTINCT
ss.skill_id1 AS unique_id,
card_name.id AS owner_id,
card_name.name
FROM card_data card
JOIN card_name ON card.id = card_name.id
JOIN card_rarity_data rd ON card.id = rd.card_id
JOIN skill_set ss ON rd.skill_set = ss.id
)
SELECT
d.id,
n.name,
n.description,
IIF(d.unique_skill_id_1 = 0, d.group_id, ud.group_id) AS group_id,
CASE
WHEN g.name IS NOT NULL THEN g.name
WHEN d.unique_skill_id_1 != 0 THEN n.name
ELSE ''
END AS group_name,
d.rarity,
d.group_rate,
d.grade_value,
d.activate_lot,
d.precondition_1,
d.condition_1,
d.float_ability_time_1,
d.ability_time_usage_1,
d.float_cooldown_time_1,
d.ability_type_1_1,
d.ability_value_usage_1_1,
d.float_ability_value_1_1,
d.target_type_1_1,
d.target_value_1_1,
d.ability_type_1_2,
d.ability_value_usage_1_2,
d.float_ability_value_1_2,
d.target_type_1_2,
d.target_value_1_2,
d.ability_type_1_3,
d.ability_value_usage_1_3,
d.float_ability_value_1_3,
d.target_type_1_3,
d.target_value_1_3,
d.precondition_2,
d.condition_2,
d.float_ability_time_2,
d.ability_time_usage_2,
d.float_cooldown_time_2,
d.ability_type_2_1,
d.ability_value_usage_2_1,
d.float_ability_value_2_1,
d.target_type_2_1,
d.target_value_2_1,
d.ability_type_2_2,
d.ability_value_usage_2_2,
d.float_ability_value_2_2,
d.target_type_2_2,
d.target_value_2_2,
d.ability_type_2_3,
d.ability_value_usage_2_3,
d.float_ability_value_2_3,
d.target_type_2_3,
d.target_value_2_3,
IFNULL(p.need_skill_point, 0) AS sp_cost,
d.unique_skill_id_1,
COALESCE(u.owner_id, iu.owner_id, 0) AS unique_owner_id,
COALESCE(u.name, iu.name, '') AS unique_owner,
d.icon_id,
d.tag_id
FROM skill_data d
JOIN skill_names n ON d.id = n.id
LEFT JOIN skill_data ud ON d.unique_skill_id_1 = ud.id
LEFT JOIN skill_groups g ON d.group_id = g.group_id
LEFT JOIN single_mode_skill_need_point p ON d.id = p.id
LEFT JOIN card_unique u ON d.id = u.unique_id
LEFT JOIN card_unique iu ON d.unique_skill_id_1 = iu.unique_id
ORDER BY d.id
-9
View File
@@ -1,9 +0,0 @@
SELECT
factor_group_id,
effect_id,
target_type,
value_1,
value_2
FROM succession_factor_effect
WHERE factor_group_id NOT IN (40001) -- exclude Carnival Bonus
ORDER BY factor_group_id, effect_id, id
-20
View File
@@ -1,20 +0,0 @@
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
sf.factor_id,
spark.name,
spark.description,
sf.factor_group_id,
sf.rarity,
sf.factor_type
FROM spark
JOIN succession_factor sf ON spark.id = sf.factor_id
WHERE sf.factor_type != 7 -- exclude Carnival Bonus
ORDER BY sf.factor_id
-59
View File
@@ -1,59 +0,0 @@
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
-199328
View File
File diff suppressed because it is too large Load Diff
-346
View File
@@ -1,346 +0,0 @@
[
{
"chara_id": 1001,
"name": "Special Week"
},
{
"chara_id": 1002,
"name": "Silence Suzuka"
},
{
"chara_id": 1003,
"name": "Tokai Teio"
},
{
"chara_id": 1004,
"name": "Maruzensky"
},
{
"chara_id": 1005,
"name": "Fuji Kiseki"
},
{
"chara_id": 1006,
"name": "Oguri Cap"
},
{
"chara_id": 1007,
"name": "Gold Ship"
},
{
"chara_id": 1008,
"name": "Vodka"
},
{
"chara_id": 1009,
"name": "Daiwa Scarlet"
},
{
"chara_id": 1010,
"name": "Taiki Shuttle"
},
{
"chara_id": 1011,
"name": "Grass Wonder"
},
{
"chara_id": 1012,
"name": "Hishi Amazon"
},
{
"chara_id": 1013,
"name": "Mejiro McQueen"
},
{
"chara_id": 1014,
"name": "El Condor Pasa"
},
{
"chara_id": 1015,
"name": "T.M. Opera O"
},
{
"chara_id": 1016,
"name": "Narita Brian"
},
{
"chara_id": 1017,
"name": "Symboli Rudolf"
},
{
"chara_id": 1018,
"name": "Air Groove"
},
{
"chara_id": 1019,
"name": "Agnes Digital"
},
{
"chara_id": 1020,
"name": "Seiun Sky"
},
{
"chara_id": 1021,
"name": "Tamamo Cross"
},
{
"chara_id": 1022,
"name": "Fine Motion"
},
{
"chara_id": 1023,
"name": "Biwa Hayahide"
},
{
"chara_id": 1024,
"name": "Mayano Top Gun"
},
{
"chara_id": 1025,
"name": "Manhattan Cafe"
},
{
"chara_id": 1026,
"name": "Mihono Bourbon"
},
{
"chara_id": 1027,
"name": "Mejiro Ryan"
},
{
"chara_id": 1028,
"name": "Hishi Akebono"
},
{
"chara_id": 1029,
"name": "Yukino Bijin"
},
{
"chara_id": 1030,
"name": "Rice Shower"
},
{
"chara_id": 1031,
"name": "Ines Fujin"
},
{
"chara_id": 1032,
"name": "Agnes Tachyon"
},
{
"chara_id": 1033,
"name": "Admire Vega"
},
{
"chara_id": 1034,
"name": "Inari One"
},
{
"chara_id": 1035,
"name": "Winning Ticket"
},
{
"chara_id": 1036,
"name": "Air Shakur"
},
{
"chara_id": 1037,
"name": "Eishin Flash"
},
{
"chara_id": 1038,
"name": "Curren Chan"
},
{
"chara_id": 1039,
"name": "Kawakami Princess"
},
{
"chara_id": 1040,
"name": "Gold City"
},
{
"chara_id": 1041,
"name": "Sakura Bakushin O"
},
{
"chara_id": 1042,
"name": "Seeking the Pearl"
},
{
"chara_id": 1043,
"name": "Shinko Windy"
},
{
"chara_id": 1044,
"name": "Sweep Tosho"
},
{
"chara_id": 1045,
"name": "Super Creek"
},
{
"chara_id": 1046,
"name": "Smart Falcon"
},
{
"chara_id": 1047,
"name": "Zenno Rob Roy"
},
{
"chara_id": 1048,
"name": "Tosen Jordan"
},
{
"chara_id": 1049,
"name": "Nakayama Festa"
},
{
"chara_id": 1050,
"name": "Narita Taishin"
},
{
"chara_id": 1051,
"name": "Nishino Flower"
},
{
"chara_id": 1052,
"name": "Haru Urara"
},
{
"chara_id": 1053,
"name": "Bamboo Memory"
},
{
"chara_id": 1054,
"name": "Biko Pegasus"
},
{
"chara_id": 1055,
"name": "Marvelous Sunday"
},
{
"chara_id": 1056,
"name": "Matikanefukukitaru"
},
{
"chara_id": 1057,
"name": "Mr. C.B."
},
{
"chara_id": 1058,
"name": "Meisho Doto"
},
{
"chara_id": 1059,
"name": "Mejiro Dober"
},
{
"chara_id": 1060,
"name": "Nice Nature"
},
{
"chara_id": 1061,
"name": "King Halo"
},
{
"chara_id": 1062,
"name": "Matikanetannhauser"
},
{
"chara_id": 1063,
"name": "Ikuno Dictus"
},
{
"chara_id": 1064,
"name": "Mejiro Palmer"
},
{
"chara_id": 1065,
"name": "Daitaku Helios"
},
{
"chara_id": 1066,
"name": "Twin Turbo"
},
{
"chara_id": 1067,
"name": "Satono Diamond"
},
{
"chara_id": 1068,
"name": "Kitasan Black"
},
{
"chara_id": 1069,
"name": "Sakura Chiyono O"
},
{
"chara_id": 1070,
"name": "Sirius Symboli"
},
{
"chara_id": 1071,
"name": "Mejiro Ardan"
},
{
"chara_id": 1072,
"name": "Yaeno Muteki"
},
{
"chara_id": 1073,
"name": "Tsurumaru Tsuyoshi"
},
{
"chara_id": 1074,
"name": "Mejiro Bright"
},
{
"chara_id": 1077,
"name": "Narita Top Road"
},
{
"chara_id": 1078,
"name": "Yamanin Zephyr"
},
{
"chara_id": 2001,
"name": "Happy Meek"
},
{
"chara_id": 2002,
"name": "Bitter Glasse"
},
{
"chara_id": 2003,
"name": "Little Cocon"
},
{
"chara_id": 2004,
"name": "Montjeu"
},
{
"chara_id": 9001,
"name": "Tazuna Hayakawa"
},
{
"chara_id": 9002,
"name": "Director Akikawa"
},
{
"chara_id": 9003,
"name": "Etsuko Otonashi"
},
{
"chara_id": 9004,
"name": "Trainer Kiryuin"
},
{
"chara_id": 9005,
"name": "Sasami Anshinzawa"
},
{
"chara_id": 9006,
"name": "Riko Kashimoto"
}
]
File diff suppressed because it is too large Load Diff
-1712
View File
File diff suppressed because it is too large Load Diff
-1420
View File
File diff suppressed because it is too large Load Diff
-17
View File
@@ -1,17 +0,0 @@
[
{
"scenario_id": 1,
"name": "URA Finale",
"title": "The Beginning: URA Finale"
},
{
"scenario_id": 2,
"name": "Unity Cup",
"title": "Unity Cup: Shine On, Team Spirit!"
},
{
"scenario_id": 4,
"name": "TS Climax",
"title": "Trackblazer: Start of the Climax"
}
]
File diff suppressed because it is too large Load Diff
-19649
View File
File diff suppressed because it is too large Load Diff
-38522
View File
File diff suppressed because it is too large Load Diff
-2018
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -7,7 +7,6 @@ require (
github.com/go-chi/chi/v5 v5.2.5
github.com/google/go-cmp v0.6.0
github.com/junegunn/fzf v0.67.0
golang.org/x/sync v0.20.0
zombiezen.com/go/sqlite v1.4.2
)
@@ -26,6 +25,7 @@ require (
github.com/sasha-s/go-csync v0.0.0-20240107134140-fcbab37b09ad // indirect
golang.org/x/crypto v0.46.0 // indirect
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 // indirect
golang.org/x/sync v0.20.0 // indirect
golang.org/x/sys v0.39.0 // indirect
modernc.org/libc v1.65.7 // indirect
modernc.org/mathutil v1.7.1 // indirect