Compare commits
1
Commits
main
..
factorfactor
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6262e3c430 |
@@ -0,0 +1,77 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"log/slog"
|
||||
|
||||
"git.sunturtle.xyz/zephyr/horse"
|
||||
"git.sunturtle.xyz/zephyr/horse/umadump/ism"
|
||||
)
|
||||
|
||||
// For each gain type from inspiration (stats, caps, aptitudes, skills):
|
||||
// find all activated sparks that can give that gain type.
|
||||
// Then, find all subsets of effect groups among those sparks
|
||||
// that have effects summing to those observed
|
||||
// (or more if the .end_info result is maxed),
|
||||
// with the restriction that one comes from each spark.
|
||||
// Deduplicate permutations.
|
||||
// Lastly, find the intersection of possible effects from sparks that appear
|
||||
// multiple times.
|
||||
//
|
||||
// To some extent, we rely on there being no sparks with the random stats effect type,
|
||||
// although we also don't know the distribution of stats from overcapped skill hints.
|
||||
|
||||
type target struct {
|
||||
spark *horse.Spark
|
||||
group int
|
||||
effect horse.SparkEffect
|
||||
}
|
||||
|
||||
func skilltargets(sparks []horse.Spark) map[horse.SkillID]map[horse.SparkID][]target {
|
||||
r := make(map[horse.SkillID]map[horse.SparkID][]target)
|
||||
for i, sp := range sparks {
|
||||
for j, g := range sp.Effects {
|
||||
for _, e := range g {
|
||||
if e.Target != horse.SparkSkillHint {
|
||||
continue
|
||||
}
|
||||
if r[horse.SkillID(e.Value1)] == nil {
|
||||
r[horse.SkillID(e.Value1)] = make(map[horse.SparkID][]target)
|
||||
}
|
||||
r[horse.SkillID(e.Value1)][sp.ID] = append(r[horse.SkillID(e.Value1)][sp.ID], target{&sparks[i], j, e})
|
||||
}
|
||||
}
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
func resolveSkills(tab tables, career *ism.WorkIdleSingleMode)
|
||||
|
||||
// skillsparks appends all activated sparks that can yield a given skill to r.
|
||||
// It assumes that no spark can give multiple skills.
|
||||
func skillsparks(tab tables, sparks []ism.Factor, id horse.SkillID, r []*horse.Spark) []*horse.Spark {
|
||||
for _, f := range sparks {
|
||||
sp := tab.sparks[f.FactorID]
|
||||
if sp == nil {
|
||||
// ATM we do generally exclude Carnival Bonus from spark info.
|
||||
// Better to check.
|
||||
slog.Warn("skipped spark", slog.Int("id", int(f.FactorID)))
|
||||
continue
|
||||
}
|
||||
if sparkHasSkill(sp, id) {
|
||||
r = append(r, sp)
|
||||
}
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
func sparkHasSkill(sp *horse.Spark, id horse.SkillID) bool {
|
||||
for _, g := range sp.Effects {
|
||||
for _, e := range g {
|
||||
if e.Target != horse.SparkSkillHint {
|
||||
continue
|
||||
}
|
||||
return id == horse.SkillID(e.Value1)
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"os"
|
||||
"os/signal"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
|
||||
"git.sunturtle.xyz/zephyr/horse/mdb"
|
||||
"git.sunturtle.xyz/zephyr/horse/umadump/ism"
|
||||
"golang.org/x/sync/errgroup"
|
||||
"zombiezen.com/go/sqlite"
|
||||
"zombiezen.com/go/sqlite/sqlitex"
|
||||
)
|
||||
|
||||
func main() {
|
||||
var (
|
||||
mdbf string
|
||||
dirs []string
|
||||
files []string
|
||||
)
|
||||
flag.StringVar(&mdbf, "-mdb", "", "`path` to master.mdb")
|
||||
flag.Func("d", "idle_single_mode `dir`ectory (may be specified multiple times)", func(s string) error {
|
||||
dirs = append(dirs, s)
|
||||
return nil
|
||||
})
|
||||
flag.Parse()
|
||||
|
||||
files = slices.Clone(flag.Args())
|
||||
for _, dir := range dirs {
|
||||
ds, err := os.ReadDir(dir)
|
||||
if err != nil {
|
||||
slog.Error("getting files in directory", slog.String("dir", dir), slog.Any("err", err))
|
||||
os.Exit(1)
|
||||
}
|
||||
for _, d := range ds {
|
||||
if d.IsDir() {
|
||||
continue
|
||||
}
|
||||
files = append(files, filepath.Join(dir, d.Name()))
|
||||
}
|
||||
}
|
||||
|
||||
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt)
|
||||
go func() {
|
||||
<-ctx.Done()
|
||||
stop()
|
||||
}()
|
||||
|
||||
db, err := sqlitex.NewPool(mdbf, sqlitex.PoolOptions{Flags: sqlite.OpenReadOnly})
|
||||
if err != nil {
|
||||
slog.Error("mdb", slog.Any("err", err))
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
slog.Info("load skills")
|
||||
skills, err := mdb.Skills(ctx, db)
|
||||
if err != nil {
|
||||
slog.Error("skills", slog.Any("err", err))
|
||||
os.Exit(1)
|
||||
}
|
||||
slog.Info("load sparks")
|
||||
sparks, err := mdb.Sparks(ctx, db)
|
||||
if err != nil {
|
||||
slog.Error("sparks", slog.Any("err", err))
|
||||
os.Exit(1)
|
||||
}
|
||||
slog.Info("load umas")
|
||||
umas, err := mdb.Umas(ctx, db)
|
||||
if err != nil {
|
||||
slog.Error("umas", slog.Any("err", err))
|
||||
os.Exit(1)
|
||||
}
|
||||
slog.Info("mdb",
|
||||
slog.Int("sparks", len(sparks)),
|
||||
slog.Int("skills", len(skills)),
|
||||
slog.Int("umas", len(umas)),
|
||||
)
|
||||
tab := makeTables(skills, sparks, umas)
|
||||
|
||||
g, ctx := errgroup.WithContext(ctx)
|
||||
g.SetLimit(64)
|
||||
for _, file := range files {
|
||||
g.Go(func() error {
|
||||
return dofile(ctx, file)
|
||||
})
|
||||
}
|
||||
if err := g.Wait(); err != nil {
|
||||
slog.Error("process", slog.Any("err", err))
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func dofile(ctx context.Context, file string) error {
|
||||
f, err := os.Open(file)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var career ism.WorkIdleSingleMode
|
||||
d := json.NewDecoder(f)
|
||||
d.DisallowUnknownFields()
|
||||
if err := d.Decode(&career); err != nil {
|
||||
return fmt.Errorf("%s: %w", file, err)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package main
|
||||
|
||||
import "git.sunturtle.xyz/zephyr/horse"
|
||||
|
||||
type tables struct {
|
||||
skills map[horse.SkillID]*horse.Skill
|
||||
groups map[horse.SkillGroupID]map[int8]*horse.Skill
|
||||
sparks map[horse.SparkID]*horse.Spark
|
||||
umas map[horse.UmaID]*horse.Uma
|
||||
}
|
||||
|
||||
func makeTables(skills []horse.Skill, sparks []horse.Spark, umas []horse.Uma) tables {
|
||||
tab := tables{
|
||||
skills: make(map[horse.SkillID]*horse.Skill, len(skills)),
|
||||
groups: make(map[horse.SkillGroupID]map[int8]*horse.Skill, len(skills)/2),
|
||||
sparks: make(map[horse.SparkID]*horse.Spark, len(sparks)),
|
||||
umas: make(map[horse.UmaID]*horse.Uma, len(umas)),
|
||||
}
|
||||
for i, sk := range skills {
|
||||
tab.skills[sk.ID] = &skills[i]
|
||||
if tab.groups[sk.Group] == nil {
|
||||
tab.groups[sk.Group] = make(map[int8]*horse.Skill, 4)
|
||||
}
|
||||
// Only add the skill to the groups table if they're rare, the base
|
||||
// version of the common skill, or there isn't anything else there.
|
||||
if sk.Rarity == 2 || sk.GroupRate == 1 || tab.groups[sk.Group][sk.Rarity] == nil {
|
||||
tab.groups[sk.Group][sk.Rarity] = &skills[i]
|
||||
}
|
||||
}
|
||||
for i, sp := range sparks {
|
||||
tab.sparks[sp.ID] = &sparks[i]
|
||||
}
|
||||
for i, u := range umas {
|
||||
tab.umas[u.ID] = &umas[i]
|
||||
}
|
||||
return tab
|
||||
}
|
||||
@@ -1,325 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"cmp"
|
||||
"context"
|
||||
_ "embed"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
|
||||
"zombiezen.com/go/sqlite"
|
||||
"zombiezen.com/go/sqlite/sqlitemigration"
|
||||
"zombiezen.com/go/sqlite/sqlitex"
|
||||
|
||||
"git.sunturtle.xyz/zephyr/horse/umadump/replay"
|
||||
)
|
||||
|
||||
const appID int32 = 0x4acedb
|
||||
|
||||
//go:embed schema-00.sql
|
||||
var schema0 string
|
||||
|
||||
//go:embed schema-final.sql
|
||||
var schemaFinal string
|
||||
|
||||
func opendb(uri, journalMode string) *sqlitemigration.Pool {
|
||||
schema := sqlitemigration.Schema{
|
||||
Migrations: []string{
|
||||
schema0,
|
||||
},
|
||||
AppID: appID,
|
||||
RepeatableMigration: schemaFinal,
|
||||
}
|
||||
j := "PRAGMA journal_mode=" + journalMode
|
||||
opts := sqlitemigration.Options{
|
||||
Flags: sqlite.OpenReadWrite | sqlite.OpenCreate | sqlite.OpenURI,
|
||||
PrepareConn: func(conn *sqlite.Conn) error {
|
||||
if journalMode == "" {
|
||||
return nil
|
||||
}
|
||||
stmt, _, err := conn.PrepareTransient(j)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = stmt.Step()
|
||||
return errors.Join(err, stmt.Finalize())
|
||||
},
|
||||
}
|
||||
db := sqlitemigration.NewPool(uri, schema, opts)
|
||||
return db
|
||||
}
|
||||
|
||||
func insert(ctx context.Context, db *sqlitemigration.Pool, log *slog.Logger, race *replay.Race, simdata *replay.RaceSimulateData) (err error) {
|
||||
conn, err := db.Take(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer db.Put(conn)
|
||||
|
||||
defer sqlitex.Transaction(conn)(&err)
|
||||
|
||||
log.DebugContext(ctx, "horses",
|
||||
slog.Int("num", int(race.NumRaceHorses)),
|
||||
slog.Int("actual", len(race.RaceHorse)),
|
||||
)
|
||||
var trainedCharas [18]int64
|
||||
for i := range race.RaceHorse {
|
||||
c := &race.RaceHorse[i]
|
||||
id, err := insertTrainedChara(conn, c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
trainedCharas[i] = id
|
||||
if err := insertTrainedCharaSkill(conn, c, id); err != nil {
|
||||
return err
|
||||
}
|
||||
log.DebugContext(ctx, "horse",
|
||||
slog.String("trainer", c.TrainerName),
|
||||
slog.Int("trained", int(c.ResponseHorseData.TrainedCharaID)),
|
||||
slog.Int("card", int(c.ResponseHorseData.CardID)),
|
||||
slog.Int64("id", id),
|
||||
)
|
||||
}
|
||||
if err = insertRaceCourseSet(conn, race); err != nil {
|
||||
return err
|
||||
}
|
||||
log.DebugContext(ctx, "course", slog.Int("id", int(race.RaceCourseSet.ID)))
|
||||
configID, err := insertRaceConfig(conn, race)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log.DebugContext(ctx, "config", slog.Int64("id", configID))
|
||||
raceID, err := insertRace(conn, race, simdata, &trainedCharas, configID)
|
||||
if err != nil {
|
||||
code := sqlite.ErrCode(err)
|
||||
if code == sqlite.ResultConstraintUnique {
|
||||
log.InfoContext(ctx, "already loaded")
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
log.DebugContext(ctx, "race", slog.Int64("id", raceID))
|
||||
err = errors.Join(
|
||||
insertRaceHorses(ctx, log, conn, race, simdata, &trainedCharas, raceID),
|
||||
insertRaceFrames(ctx, log, conn, simdata, raceID),
|
||||
insertRaceEvents(ctx, log, conn, simdata, raceID),
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
||||
//go:embed insert-trained_chara.sql
|
||||
var insertTrainedCharaSQL string
|
||||
|
||||
func insertTrainedChara(conn *sqlite.Conn, c *replay.HorseData) (int64, error) {
|
||||
stmt := conn.Prep(insertTrainedCharaSQL)
|
||||
trainer := cmp.Or(c.ResponseHorseData.ViewerID, c.ResponseHorseData.OwnerViewerID)
|
||||
stmt.SetText("$trainer", c.TrainerName)
|
||||
stmt.SetInt64("$trainer_id", trainer)
|
||||
stmt.SetInt64("$trained_chara_id", int64(c.ResponseHorseData.TrainedCharaID))
|
||||
stmt.SetInt64("$chara_id", int64(c.CharaID))
|
||||
stmt.SetText("$chara_name", c.CharaName)
|
||||
stmt.SetInt64("$card_id", int64(c.ResponseHorseData.CardID))
|
||||
stmt.SetInt64("$rarity", int64(c.Rarity))
|
||||
stmt.SetInt64("$mob_id", int64(c.MobID))
|
||||
stmt.SetInt64("$raw_speed", int64(c.ResponseHorseData.Speed))
|
||||
stmt.SetInt64("$raw_stamina", int64(c.ResponseHorseData.Stamina))
|
||||
stmt.SetInt64("$raw_power", int64(c.ResponseHorseData.Pow))
|
||||
stmt.SetInt64("$raw_guts", int64(c.ResponseHorseData.Guts))
|
||||
stmt.SetInt64("$raw_wit", int64(c.ResponseHorseData.Wiz))
|
||||
stmt.SetInt64("$turf", int64(c.ResponseHorseData.ProperGroundTurf))
|
||||
stmt.SetInt64("$dirt", int64(c.ResponseHorseData.ProperGroundDirt))
|
||||
stmt.SetInt64("$sprint", int64(c.ResponseHorseData.ProperDistanceShort))
|
||||
stmt.SetInt64("$mile", int64(c.ResponseHorseData.ProperDistanceMile))
|
||||
stmt.SetInt64("$medium", int64(c.ResponseHorseData.ProperDistanceMiddle))
|
||||
stmt.SetInt64("$long", int64(c.ResponseHorseData.ProperDistanceLong))
|
||||
stmt.SetInt64("$front_runner", int64(c.ResponseHorseData.ProperRunningStyleNige))
|
||||
stmt.SetInt64("$pace_chaser", int64(c.ResponseHorseData.ProperRunningStyleSenko))
|
||||
stmt.SetInt64("$late_surger", int64(c.ResponseHorseData.ProperRunningStyleSashi))
|
||||
stmt.SetInt64("$end_closer", int64(c.ResponseHorseData.ProperRunningStyleOikomi))
|
||||
stmt.SetInt64("$fans", int64(c.TrainedCharaData.Fans))
|
||||
stmt.SetInt64("$rank", int64(c.TrainedCharaData.Rank))
|
||||
stmt.SetInt64("$rank_score", int64(c.TrainedCharaData.RankScore))
|
||||
r, err := sqlitex.ResultInt64(stmt)
|
||||
err = errors.Join(err, stmt.Reset())
|
||||
if err != nil {
|
||||
err = fmt.Errorf("inserting trained chara: %w", err)
|
||||
}
|
||||
return r, err
|
||||
}
|
||||
|
||||
func insertTrainedCharaSkill(conn *sqlite.Conn, c *replay.HorseData, id int64) error {
|
||||
const sql = `INSERT OR IGNORE INTO trained_chara_skill VALUES (?, ?, ?)`
|
||||
stmt := conn.Prep(sql)
|
||||
stmt.BindInt64(1, id)
|
||||
for _, sk := range c.ResponseHorseData.SkillArray {
|
||||
stmt.BindInt64(2, int64(sk.SkillID))
|
||||
stmt.BindInt64(3, int64(sk.Level))
|
||||
_, err := stmt.Step()
|
||||
err = errors.Join(err, stmt.Reset())
|
||||
if err != nil {
|
||||
return fmt.Errorf("inserting trained chara id %d skill %d: %w", id, sk.SkillID, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func insertRaceCourseSet(conn *sqlite.Conn, race *replay.Race) error {
|
||||
const sql = `INSERT OR IGNORE INTO race_course_set VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
||||
stmt := conn.Prep(sql)
|
||||
stmt.BindInt64(1, int64(race.RaceCourseSet.ID))
|
||||
stmt.BindInt64(2, int64(race.RaceCourseSet.RaceTrackID))
|
||||
stmt.BindInt64(3, int64(race.RaceCourseSet.Distance))
|
||||
stmt.BindInt64(4, int64(race.RaceCourseSet.Ground))
|
||||
stmt.BindInt64(5, int64(race.RaceCourseSet.Inout))
|
||||
stmt.BindInt64(6, int64(race.RaceCourseSet.Turn))
|
||||
stmt.BindInt64(7, int64(race.RaceCourseSet.FenceSet))
|
||||
stmt.BindInt64(8, int64(race.RaceCourseSet.FloatLaneMax))
|
||||
stmt.BindInt64(9, int64(race.RaceCourseSet.CourseSetStatusID))
|
||||
stmt.BindInt64(10, int64(race.RaceCourseSet.FinishTimeMin))
|
||||
stmt.BindInt64(11, int64(race.RaceCourseSet.FinishTimeMinRandomRange))
|
||||
stmt.BindInt64(12, int64(race.RaceCourseSet.FinishTimeMax))
|
||||
stmt.BindInt64(13, int64(race.RaceCourseSet.FinishTimeMaxRandomRange))
|
||||
stmt.BindFloat(14, float64(race.CourseSectionDistance))
|
||||
stmt.BindInt64(15, int64(race.CourseDistanceType))
|
||||
stmt.BindInt64(16, int64(race.CourseFurlongNum))
|
||||
stmt.BindFloat(17, float64(race.BaseSpeed))
|
||||
_, err := stmt.Step()
|
||||
err = errors.Join(err, stmt.Reset())
|
||||
if err != nil {
|
||||
return fmt.Errorf("inserting race course: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
//go:embed insert-race_config.sql
|
||||
var insertRaceConfigSQL string
|
||||
|
||||
func insertRaceConfig(conn *sqlite.Conn, race *replay.Race) (int64, error) {
|
||||
stmt := conn.Prep(insertRaceConfigSQL)
|
||||
stmt.SetInt64("$type", int64(race.RaceType))
|
||||
stmt.SetInt64("$course_set_id", int64(race.RaceCourseSet.ID))
|
||||
stmt.SetInt64("$ground_condition", int64(race.GroundCondition))
|
||||
stmt.SetInt64("$weather", int64(race.Weather))
|
||||
stmt.SetInt64("$season", int64(race.Season))
|
||||
stmt.SetInt64("$time", int64(race.Time))
|
||||
stmt.SetInt64("$num_horse", int64(race.NumRaceHorses))
|
||||
r, err := sqlitex.ResultInt64(stmt)
|
||||
err = errors.Join(err, stmt.Reset())
|
||||
if err != nil {
|
||||
err = fmt.Errorf("inserting race config: %w", err)
|
||||
}
|
||||
return r, err
|
||||
}
|
||||
|
||||
//go:embed insert-race.sql
|
||||
var insertRaceSQL string
|
||||
|
||||
func insertRace(conn *sqlite.Conn, race *replay.Race, simdata *replay.RaceSimulateData, trainedCharas *[18]int64, configID int64) (int64, error) {
|
||||
stmt := conn.Prep(insertRaceSQL)
|
||||
stmt.SetInt64("$config_id", configID)
|
||||
stmt.SetInt64("$seed", int64(race.RandomSeed))
|
||||
stmt.SetFloat("$distance_diff_max", float64(simdata.DistanceDiffMax))
|
||||
stmt.SetInt64("$frame_count", int64(simdata.FrameCount))
|
||||
// zombiezen/sqlite doesn't seem to expose an API to find the index of a
|
||||
// named parameter, so even though we were using names before, we switch
|
||||
// to indices for trained chara IDs.
|
||||
for i, id := range trainedCharas {
|
||||
stmt.BindInt64(5+i, id)
|
||||
}
|
||||
r, err := sqlitex.ResultInt64(stmt)
|
||||
err = errors.Join(err, stmt.Reset())
|
||||
if err != nil {
|
||||
err = fmt.Errorf("inserting race: %w", err)
|
||||
}
|
||||
return r, err
|
||||
}
|
||||
|
||||
//go:embed insert-race_horse.sql
|
||||
var insertRaceHorseSQL string
|
||||
|
||||
func insertRaceHorses(ctx context.Context, log *slog.Logger, conn *sqlite.Conn, race *replay.Race, simdata *replay.RaceSimulateData, trainedCharas *[18]int64, raceID int64) error {
|
||||
stmt := conn.Prep(insertRaceHorseSQL)
|
||||
stmt.SetInt64("$race", raceID)
|
||||
for i, c := range race.RaceHorse {
|
||||
log.DebugContext(ctx, "race horse", slog.Int64("trained", trainedCharas[i]), slog.Int("index", int(c.HorseIndex)))
|
||||
stmt.SetInt64("$horse", trainedCharas[i])
|
||||
stmt.SetInt64("$horse_index", int64(c.HorseIndex))
|
||||
stmt.SetInt64("$post", int64(c.PostNumber))
|
||||
stmt.SetInt64("$finish_order", int64(c.FinishOrder))
|
||||
stmt.SetFloat("$finish_time_raw", float64(c.FinishTimeRaw))
|
||||
stmt.SetFloat("$finish_time_scaled", float64(c.FinishTimeScaled))
|
||||
stmt.SetFloat("$finish_diff_time_from_prev", float64(c.FinishDiffTimeFromPrev))
|
||||
stmt.SetInt64("$running_style", int64(c.ResponseHorseData.RunningStyle))
|
||||
stmt.SetInt64("$running_style_ex", int64(c.RunningStyleEx))
|
||||
stmt.SetInt64("$active_distance_aptitude", int64(c.ActiveProperDistance))
|
||||
stmt.SetInt64("$active_surface_aptitude", int64(c.ActiveProperGroundType))
|
||||
stmt.SetInt64("$mood", int64(c.RaceParam.Motivation))
|
||||
stmt.SetFloat("$mood_coef", float64(c.RaceParam.MotivationCoef))
|
||||
stmt.SetFloat("$base_speed", float64(c.RaceParam.BaseSpeed))
|
||||
stmt.SetFloat("$base_stamina", float64(c.RaceParam.BaseStamina))
|
||||
stmt.SetFloat("$base_power", float64(c.RaceParam.BasePow))
|
||||
stmt.SetFloat("$base_guts", float64(c.RaceParam.BaseGuts))
|
||||
stmt.SetFloat("$base_wit", float64(c.RaceParam.BaseWiz))
|
||||
stmt.SetInt64("$popularity", int64(c.Popularity))
|
||||
stmt.SetInt64("$popularity_rank_left", int64(c.PopularityRankLeft))
|
||||
stmt.SetInt64("$popularity_rank_center", int64(c.PopularityRankCenter))
|
||||
stmt.SetInt64("$popularity_rank_right", int64(c.PopularityRankRight))
|
||||
stmt.SetFloat("$start_delay", float64(simdata.HorseResult[i].StartDelayTime))
|
||||
stmt.SetFloat("$spurt_start_distance", float64(simdata.HorseResult[i].LastSpurtStartDistance))
|
||||
_, err := stmt.Step()
|
||||
err = errors.Join(err, stmt.Reset())
|
||||
if err != nil {
|
||||
return fmt.Errorf("inserting race horses: %w", err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func insertRaceFrames(ctx context.Context, log *slog.Logger, conn *sqlite.Conn, simdata *replay.RaceSimulateData, raceID int64) error {
|
||||
const sql = `INSERT INTO race_frame VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
||||
stmt := conn.Prep(sql)
|
||||
stmt.BindInt64(1, raceID)
|
||||
for _, f := range simdata.Frame {
|
||||
log.DebugContext(ctx, "frame", slog.Float64("time", float64(f.Time)))
|
||||
stmt.BindFloat(2, float64(f.Time))
|
||||
for i, h := range f.HorseFrame {
|
||||
stmt.BindInt64(3, int64(i))
|
||||
stmt.BindFloat(4, float64(h.Distance))
|
||||
stmt.BindInt64(5, int64(h.LanePosition))
|
||||
stmt.BindInt64(6, int64(h.Speed))
|
||||
stmt.BindInt64(7, int64(h.HP))
|
||||
stmt.BindInt64(8, int64(h.TemptationMode))
|
||||
stmt.BindInt64(9, int64(h.BlockFrontHorseIndex))
|
||||
_, err := stmt.Step()
|
||||
err = errors.Join(err, stmt.Reset())
|
||||
if err != nil {
|
||||
return fmt.Errorf("inserting race frames: %w", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func insertRaceEvents(ctx context.Context, log *slog.Logger, conn *sqlite.Conn, simdata *replay.RaceSimulateData, raceID int64) error {
|
||||
const sql = `INSERT INTO race_event VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
||||
stmt := conn.Prep(sql)
|
||||
for _, e := range simdata.Event {
|
||||
log.DebugContext(ctx, "event", slog.Float64("time", float64(e.Event.FrameTime)), slog.Int("type", int(e.Event.Type)))
|
||||
// Easier to clear all bindings every time now
|
||||
// than to bind nulls to all the parameters that need them below.
|
||||
stmt.ClearBindings()
|
||||
stmt.BindInt64(1, raceID)
|
||||
stmt.BindFloat(2, float64(e.Event.FrameTime))
|
||||
stmt.BindInt64(3, int64(e.Event.Type))
|
||||
for i, p := range e.Event.Param {
|
||||
stmt.BindInt64(4+i, int64(p))
|
||||
}
|
||||
_, err := stmt.Step()
|
||||
err = errors.Join(err, stmt.Reset())
|
||||
if err != nil {
|
||||
return fmt.Errorf("inserting race events: %w", err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
INSERT OR ROLLBACK INTO race (
|
||||
config_id,
|
||||
seed,
|
||||
distance_diff_max,
|
||||
frame_count,
|
||||
horse_1,
|
||||
horse_2,
|
||||
horse_3,
|
||||
horse_4,
|
||||
horse_5,
|
||||
horse_6,
|
||||
horse_7,
|
||||
horse_8,
|
||||
horse_9,
|
||||
horse_10,
|
||||
horse_11,
|
||||
horse_12,
|
||||
horse_13,
|
||||
horse_14,
|
||||
horse_15,
|
||||
horse_16,
|
||||
horse_17,
|
||||
horse_18
|
||||
)
|
||||
VALUES (
|
||||
$config_id,
|
||||
$seed,
|
||||
$distance_diff_max,
|
||||
$frame_count,
|
||||
$horse_1,
|
||||
$horse_2,
|
||||
$horse_3,
|
||||
$horse_4,
|
||||
$horse_5,
|
||||
$horse_6,
|
||||
$horse_7,
|
||||
$horse_8,
|
||||
$horse_9,
|
||||
$horse_10,
|
||||
$horse_11,
|
||||
$horse_12,
|
||||
$horse_13,
|
||||
$horse_14,
|
||||
$horse_15,
|
||||
$horse_16,
|
||||
$horse_17,
|
||||
$horse_18
|
||||
)
|
||||
RETURNING id
|
||||
@@ -1,4 +0,0 @@
|
||||
INSERT INTO race_config (type, course_set_id, ground_condition, weather, season, time, num_horse)
|
||||
VALUES ($type, $course_set_id, $ground_condition, $weather, $season, $time, $num_horse)
|
||||
ON CONFLICT DO UPDATE SET id=id
|
||||
RETURNING id
|
||||
@@ -1,12 +0,0 @@
|
||||
INSERT INTO race_horse
|
||||
VALUES (
|
||||
$race, $horse,
|
||||
$horse_index, $post,
|
||||
$finish_order, $finish_time_raw, $finish_time_scaled, $finish_diff_time_from_prev,
|
||||
$running_style, $running_style_ex,
|
||||
$active_distance_aptitude, $active_surface_aptitude,
|
||||
$mood, $mood_coef,
|
||||
$base_speed, $base_stamina, $base_power, $base_guts, $base_wit,
|
||||
$popularity, $popularity_rank_left, $popularity_rank_center, $popularity_rank_right,
|
||||
$start_delay, $spurt_start_distance
|
||||
)
|
||||
@@ -1,19 +0,0 @@
|
||||
INSERT INTO trained_chara (
|
||||
trainer, trainer_id, trained_chara_id,
|
||||
chara_id, chara_name, card_id, rarity, mob_id,
|
||||
raw_speed, raw_stamina, raw_power, raw_guts, raw_wit,
|
||||
turf, dirt,
|
||||
sprint, mile, medium, long,
|
||||
front_runner, pace_chaser, late_surger, end_closer,
|
||||
fans, rank, rank_score
|
||||
) VALUES (
|
||||
$trainer, $trainer_id, $trained_chara_id,
|
||||
$chara_id, $chara_name, $card_id, $rarity, $mob_id,
|
||||
$raw_speed, $raw_stamina, $raw_power, $raw_guts, $raw_wit,
|
||||
$turf, $dirt,
|
||||
$sprint, $mile, $medium, $long,
|
||||
$front_runner, $pace_chaser, $late_surger, $end_closer,
|
||||
$fans, $rank, $rank_score
|
||||
)
|
||||
ON CONFLICT DO UPDATE SET id=id
|
||||
RETURNING id
|
||||
@@ -1,74 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"log/slog"
|
||||
"os"
|
||||
"os/signal"
|
||||
"path/filepath"
|
||||
|
||||
"git.sunturtle.xyz/zephyr/horse/umadump/replay"
|
||||
)
|
||||
|
||||
func main() {
|
||||
var (
|
||||
uri, journalMode string
|
||||
fs []string
|
||||
)
|
||||
flag.StringVar(&uri, "db", "race.sqlite3", "output database `URI` or filename")
|
||||
flag.StringVar(&journalMode, "journal", "memory", "journal `mode` to apply to database connections")
|
||||
flag.Func("d", "`dir`ectory containing race dumps to load (repeatable)", func(s string) error {
|
||||
es, err := os.ReadDir(s)
|
||||
for _, e := range es {
|
||||
if e.IsDir() {
|
||||
continue
|
||||
}
|
||||
fs = append(fs, filepath.Join(s, e.Name()))
|
||||
}
|
||||
return err
|
||||
})
|
||||
flag.Parse()
|
||||
fs = append(fs, flag.Args()...)
|
||||
|
||||
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt)
|
||||
go func() {
|
||||
<-ctx.Done()
|
||||
stop()
|
||||
}()
|
||||
|
||||
db := opendb(uri, journalMode)
|
||||
ok := 0
|
||||
for _, n := range fs {
|
||||
log := slog.With(slog.String("file", n))
|
||||
log.Info("begin")
|
||||
f, err := os.Open(n)
|
||||
if err != nil {
|
||||
log.Error("open", slog.Any("err", err))
|
||||
continue
|
||||
}
|
||||
var race replay.Race
|
||||
d := json.NewDecoder(f)
|
||||
d.DisallowUnknownFields()
|
||||
log.Debug("decode")
|
||||
if err := d.Decode(&race); err != nil {
|
||||
log.Error("load", slog.Any("err", err))
|
||||
continue
|
||||
}
|
||||
log.Debug("simdata")
|
||||
simdata, err := replay.DecodeSim(race.SimDataBase64)
|
||||
if err != nil {
|
||||
log.Error("simdata", slog.Any("err", err))
|
||||
continue
|
||||
}
|
||||
log.Debug("insert")
|
||||
err = insert(ctx, db, log, &race, simdata)
|
||||
if err != nil {
|
||||
log.Error("insert", slog.Any("err", err))
|
||||
continue
|
||||
}
|
||||
ok++
|
||||
}
|
||||
slog.Info("done", slog.Int("races", ok))
|
||||
}
|
||||
@@ -1,234 +0,0 @@
|
||||
-- trained_chara lists horses who appear in races.
|
||||
CREATE TABLE IF NOT EXISTS trained_chara (
|
||||
-- id is the DB-internal ID of the Uma.
|
||||
-- It does not come from the game and should not be supplied in DDL.
|
||||
id INTEGER PRIMARY KEY,
|
||||
|
||||
trainer TEXT NOT NULL,
|
||||
trainer_id INTEGER NOT NULL, -- 0 for NPC umas e.g. in practice races
|
||||
trained_chara_id INTEGER NOT NULL,
|
||||
chara_id INTEGER NOT NULL,
|
||||
chara_name TEXT NOT NULL,
|
||||
-- card_id is the Uma herself, e.g. 100201 for [Innocent Silence] Silence Suzuka.
|
||||
card_id INTEGER NOT NULL,
|
||||
rarity INTEGER NOT NULL,
|
||||
mob_id INTEGER NOT NULL, -- 0 for Umas that are not mobs
|
||||
|
||||
-- Raw stats.
|
||||
raw_speed INTEGER NOT NULL,
|
||||
raw_stamina INTEGER NOT NULL,
|
||||
raw_power INTEGER NOT NULL,
|
||||
raw_guts INTEGER NOT NULL,
|
||||
raw_wit INTEGER NOT NULL,
|
||||
|
||||
-- Aptitudes.
|
||||
turf INTEGER NOT NULL,
|
||||
dirt INTEGER NOT NULL,
|
||||
sprint INTEGER NOT NULL,
|
||||
mile INTEGER NOT NULL,
|
||||
medium INTEGER NOT NULL,
|
||||
long INTEGER NOT NULL,
|
||||
front_runner INTEGER NOT NULL,
|
||||
pace_chaser INTEGER NOT NULL,
|
||||
late_surger INTEGER NOT NULL,
|
||||
end_closer INTEGER NOT NULL,
|
||||
|
||||
-- Other data.
|
||||
fans INTEGER NOT NULL,
|
||||
rank INTEGER NOT NULL,
|
||||
rank_score INTEGER NOT NULL,
|
||||
|
||||
UNIQUE (trainer_id, trained_chara_id)
|
||||
) STRICT;
|
||||
|
||||
-- Insert trained chara ID 0 to represent no Uma.
|
||||
INSERT OR IGNORE INTO trained_chara (
|
||||
id,
|
||||
trainer,
|
||||
trainer_id,
|
||||
trained_chara_id,
|
||||
chara_id, chara_name,
|
||||
card_id,
|
||||
mob_id,
|
||||
raw_speed, raw_stamina, raw_power, raw_guts, raw_wit,
|
||||
turf, dirt,
|
||||
sprint, mile, medium, long,
|
||||
front_runner, pace_chaser, late_surger, end_closer,
|
||||
fans,
|
||||
rank, rank_score
|
||||
)
|
||||
VALUES (0, '', 0, 0, 0, '', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
|
||||
|
||||
-- trained_chara_skill lists the skills each observed horse owns.
|
||||
CREATE TABLE IF NOT EXISTS trained_chara_skill (
|
||||
trained_chara INTEGER NOT NULL REFERENCES trained_chara (id) ON DELETE CASCADE,
|
||||
skill_id INTEGER NOT NULL,
|
||||
level INTEGER NOT NULL,
|
||||
|
||||
UNIQUE (trained_chara, skill_id)
|
||||
) STRICT;
|
||||
CREATE INDEX IF NOT EXISTS trained_chara_skill_by_trained_chara ON trained_chara_skill (trained_chara);
|
||||
|
||||
-- race_course_set is the accumulated information of observed race courses.
|
||||
CREATE TABLE IF NOT EXISTS race_course_set (
|
||||
id INTEGER PRIMARY KEY,
|
||||
race_track_id INTEGER NOT NULL,
|
||||
distance INTEGER NOT NULL,
|
||||
ground INTEGER NOT NULL,
|
||||
inout INTEGER NOT NULL,
|
||||
turn INTEGER NOT NULL,
|
||||
fence_set INTEGER NOT NULL,
|
||||
float_lane_max INTEGER NOT NULL,
|
||||
course_set_status_id INTEGER NOT NULL,
|
||||
finish_time_min INTEGER NOT NULL,
|
||||
finish_time_min_random_range INTEGER NOT NULL,
|
||||
finish_time_max INTEGER NOT NULL,
|
||||
finish_time_max_random_range INTEGER NOT NULL,
|
||||
|
||||
section_distance REAL NOT NULL,
|
||||
distance_type INTEGER NOT NULL,
|
||||
furlong_number INTEGER NOT NULL,
|
||||
base_speed REAL NOT NULL
|
||||
) STRICT;
|
||||
|
||||
-- race_config is observed configurations of races including the race type.
|
||||
CREATE TABLE IF NOT EXISTS race_config (
|
||||
-- id is the DB-internal ID of the race config.
|
||||
id INTEGER PRIMARY KEY,
|
||||
type INTEGER NOT NULL,
|
||||
course_set_id INTEGER NOT NULL REFERENCES race_course_set (id) ON DELETE RESTRICT,
|
||||
ground_condition INTEGER NOT NULL,
|
||||
weather INTEGER NOT NULL,
|
||||
season INTEGER NOT NULL,
|
||||
time INTEGER NOT NULL,
|
||||
num_horse INTEGER NOT NULL,
|
||||
|
||||
UNIQUE (type, course_set_id, ground_condition, weather, season, time, num_horse)
|
||||
) STRICT;
|
||||
CREATE INDEX IF NOT EXISTS race_config_by_type ON race_config (type);
|
||||
|
||||
-- race is all observed races.
|
||||
CREATE TABLE IF NOT EXISTS race (
|
||||
-- id is the DB-internal ID of the race.
|
||||
id INTEGER PRIMARY KEY,
|
||||
config_id INTEGER NOT NULL REFERENCES race_config (id),
|
||||
seed INTEGER NOT NULL,
|
||||
|
||||
distance_diff_max REAL NOT NULL,
|
||||
frame_count INTEGER NOT NULL,
|
||||
|
||||
-- There is no consistent ID for races, and even race seeds have been found
|
||||
-- to collide well above the expected rate for a uniform distribution.
|
||||
-- In order to prevent adding duplicates of races each time the tool is run,
|
||||
-- we include the list of all horse IDs present in the race.
|
||||
-- Analysis on the horses in the race should instead use the race_horse table.
|
||||
-- Horse entries here are 0 where there is actually no horse at that gate.
|
||||
horse_1 INTEGER NOT NULL REFERENCES trained_chara (id) ON DELETE CASCADE,
|
||||
horse_2 INTEGER NOT NULL REFERENCES trained_chara (id) ON DELETE CASCADE,
|
||||
horse_3 INTEGER NOT NULL REFERENCES trained_chara (id) ON DELETE CASCADE,
|
||||
horse_4 INTEGER NOT NULL REFERENCES trained_chara (id) ON DELETE CASCADE,
|
||||
horse_5 INTEGER NOT NULL REFERENCES trained_chara (id) ON DELETE CASCADE,
|
||||
horse_6 INTEGER NOT NULL REFERENCES trained_chara (id) ON DELETE CASCADE,
|
||||
horse_7 INTEGER NOT NULL REFERENCES trained_chara (id) ON DELETE CASCADE,
|
||||
horse_8 INTEGER NOT NULL REFERENCES trained_chara (id) ON DELETE CASCADE,
|
||||
horse_9 INTEGER NOT NULL REFERENCES trained_chara (id) ON DELETE CASCADE,
|
||||
horse_10 INTEGER NOT NULL REFERENCES trained_chara (id) ON DELETE CASCADE,
|
||||
horse_11 INTEGER NOT NULL REFERENCES trained_chara (id) ON DELETE CASCADE,
|
||||
horse_12 INTEGER NOT NULL REFERENCES trained_chara (id) ON DELETE CASCADE,
|
||||
horse_13 INTEGER NOT NULL REFERENCES trained_chara (id) ON DELETE CASCADE,
|
||||
horse_14 INTEGER NOT NULL REFERENCES trained_chara (id) ON DELETE CASCADE,
|
||||
horse_15 INTEGER NOT NULL REFERENCES trained_chara (id) ON DELETE CASCADE,
|
||||
horse_16 INTEGER NOT NULL REFERENCES trained_chara (id) ON DELETE CASCADE,
|
||||
horse_17 INTEGER NOT NULL REFERENCES trained_chara (id) ON DELETE CASCADE,
|
||||
horse_18 INTEGER NOT NULL REFERENCES trained_chara (id) ON DELETE CASCADE,
|
||||
|
||||
UNIQUE (
|
||||
config_id,
|
||||
seed,
|
||||
horse_1,
|
||||
horse_2,
|
||||
horse_3,
|
||||
horse_4,
|
||||
horse_5,
|
||||
horse_6,
|
||||
horse_7,
|
||||
horse_8,
|
||||
horse_9,
|
||||
horse_10,
|
||||
horse_11,
|
||||
horse_12,
|
||||
horse_13,
|
||||
horse_14,
|
||||
horse_15,
|
||||
horse_16,
|
||||
horse_17,
|
||||
horse_18
|
||||
)
|
||||
) STRICT;
|
||||
CREATE INDEX IF NOT EXISTS race_by_config ON race (config_id);
|
||||
|
||||
-- race_horse pairs each instance of a horse with its condition and performance
|
||||
-- in a given race.
|
||||
CREATE TABLE IF NOT EXISTS race_horse (
|
||||
race INTEGER NOT NULL REFERENCES race (id) ON DELETE CASCADE,
|
||||
horse INTEGER NOT NULL REFERENCES trained_chara (id) ON DELETE CASCADE,
|
||||
horse_index INTEGER NOT NULL,
|
||||
post INTEGER NOT NULL,
|
||||
finish_order INTEGER NOT NULL,
|
||||
finish_time_raw REAL NOT NULL,
|
||||
finish_time_scaled REAL NOT NULL,
|
||||
finish_diff_time_from_prev REAL NOT NULL,
|
||||
running_style INTEGER NOT NULL,
|
||||
running_style_ex INTEGER NOT NULL,
|
||||
active_distance_aptitude INTEGER NOT NULL,
|
||||
active_surface_aptitude INTEGER NOT NULL,
|
||||
mood INTEGER NOT NULL,
|
||||
mood_coef REAL NOT NULL,
|
||||
base_speed REAL NOT NULL,
|
||||
base_stamina REAL NOT NULL,
|
||||
base_power REAL NOT NULL,
|
||||
base_guts REAL NOT NULL,
|
||||
base_wit REAL NOT NULL,
|
||||
popularity INTEGER NOT NULL,
|
||||
popularity_rank_left INTEGER NOT NULL,
|
||||
popularity_rank_center INTEGER NOT NULL,
|
||||
popularity_rank_right INTEGER NOT NULL,
|
||||
start_delay REAL NOT NULL,
|
||||
spurt_start_distance REAL NOT NULL,
|
||||
|
||||
PRIMARY KEY (race, horse),
|
||||
UNIQUE (race, horse_index),
|
||||
CHECK (horse != 0)
|
||||
) STRICT;
|
||||
|
||||
-- race_frame holds the actual received frames.
|
||||
CREATE TABLE IF NOT EXISTS race_frame (
|
||||
race INTEGER NOT NULL REFERENCES race (id) ON DELETE CASCADE,
|
||||
frame_time REAL NOT NULL,
|
||||
horse_index INTEGER NOT NULL,
|
||||
distance REAL NOT NULL,
|
||||
lane INTEGER NOT NULL, -- scale -4
|
||||
speed INTEGER NOT NULL, -- scale -2
|
||||
hp INTEGER NOT NULL,
|
||||
temptation INTEGER NOT NULL,
|
||||
block_front_horse_index INTEGER NOT NULL,
|
||||
|
||||
PRIMARY KEY (race, frame_time, horse_index)
|
||||
) STRICT;
|
||||
|
||||
-- race_event holds raw received race events.
|
||||
-- Views such as race_event_skill_activate, race_event_spot_struggle_activate, &c.
|
||||
-- interpret events per type.
|
||||
CREATE TABLE IF NOT EXISTS race_event (
|
||||
race INTEGER NOT NULL REFERENCES race (id) ON DELETE CASCADE,
|
||||
frame_time REAL NOT NULL,
|
||||
type INTEGER NOT NULL,
|
||||
param_0 INTEGER,
|
||||
param_1 INTEGER,
|
||||
param_2 INTEGER,
|
||||
param_3 INTEGER,
|
||||
param_4 INTEGER,
|
||||
param_5 INTEGER
|
||||
) STRICT;
|
||||
CREATE INDEX IF NOT EXISTS race_event_by_race ON race_event (race);
|
||||
CREATE INDEX IF NOT EXISTS race_event_by_type ON race_event (type);
|
||||
@@ -1,36 +0,0 @@
|
||||
DROP VIEW IF EXISTS race_event_skill_activate;
|
||||
DROP VIEW IF EXISTS race_event_spot_struggle_activate;
|
||||
DROP VIEW IF EXISTS race_event_duel_activate;
|
||||
DROP VIEW IF EXISTS race_event_release_conserve_power;
|
||||
|
||||
CREATE VIEW race_event_skill_activate AS
|
||||
SELECT
|
||||
race,
|
||||
frame_time,
|
||||
param_0 AS user_index,
|
||||
param_1 AS skill,
|
||||
param_2 * 0.0001 AS duration,
|
||||
param_3 AS branch,
|
||||
param_4 AS hit_bitmask
|
||||
FROM race_event WHERE type = 3;
|
||||
|
||||
CREATE VIEW race_event_spot_struggle_activate AS
|
||||
SELECT
|
||||
race,
|
||||
frame_time,
|
||||
param_0 AS user_index
|
||||
FROM race_event WHERE type = 4;
|
||||
|
||||
CREATE VIEW race_event_duel_activate AS
|
||||
SELECT
|
||||
race,
|
||||
frame_time,
|
||||
param_0 AS user_index
|
||||
FROM race_event WHERE type = 5;
|
||||
|
||||
CREATE VIEW race_event_release_conserve_power AS
|
||||
SELECT
|
||||
race,
|
||||
frame_time,
|
||||
param_0 AS user_index
|
||||
FROM race_event WHERE type = 6;
|
||||
+4
-3
@@ -139,8 +139,9 @@ func spansslopes(id horse.RacecourseID, evts []courseEvent, length float32) ([]h
|
||||
}
|
||||
// Start of the straight.
|
||||
span = horse.RacecourseSpan{
|
||||
Start: evt.Distance,
|
||||
Straight: int8(evt.Values[1]),
|
||||
Start: evt.Distance,
|
||||
Straight: true,
|
||||
FarStraight: evt.Values[1] == 2,
|
||||
}
|
||||
// Continue out of the switch.
|
||||
}
|
||||
@@ -153,7 +154,7 @@ func spansslopes(id horse.RacecourseID, evts []courseEvent, length float32) ([]h
|
||||
// Longchamp 1000 (which doesn't appear to actually be used but w/e)
|
||||
// defines straights by their start positions only, so check for the
|
||||
// previous event being a straight with no end.
|
||||
if prev.Straight != 0 && prev.End == 0 {
|
||||
if prev.Straight && prev.End == 0 {
|
||||
spans[len(spans)-1].End = span.Start
|
||||
} else {
|
||||
// Otherwise, insert a new span which is neither straight nor corner.
|
||||
|
||||
+66
-20
@@ -35,7 +35,7 @@ func TestRacecourses(t *testing.T) {
|
||||
{
|
||||
Start: 0,
|
||||
End: 649.9000244140625,
|
||||
Straight: 1,
|
||||
Straight: true,
|
||||
},
|
||||
{
|
||||
Start: 649.9000244140625,
|
||||
@@ -44,7 +44,7 @@ func TestRacecourses(t *testing.T) {
|
||||
{
|
||||
Start: 650,
|
||||
End: 1000,
|
||||
Straight: 1,
|
||||
Straight: true,
|
||||
},
|
||||
},
|
||||
Slopes: []horse.Slope{},
|
||||
@@ -64,9 +64,10 @@ func TestRacecourses(t *testing.T) {
|
||||
LaneStart: [18]horse.TenThousandths{0, 555, 1111, 1666, 2222, 2777, 3333, 3888, 4444, 5333, 5888, 6444, 7000, 7555, 8111, 8666, 9222, 9777},
|
||||
Spans: []horse.RacecourseSpan{
|
||||
{
|
||||
Start: 0,
|
||||
End: 458,
|
||||
Straight: 2,
|
||||
Start: 0,
|
||||
End: 458,
|
||||
Straight: true,
|
||||
FarStraight: true,
|
||||
},
|
||||
{
|
||||
Start: 458,
|
||||
@@ -81,7 +82,7 @@ func TestRacecourses(t *testing.T) {
|
||||
{
|
||||
Start: 958,
|
||||
End: 1450,
|
||||
Straight: 1,
|
||||
Straight: true,
|
||||
},
|
||||
{
|
||||
Start: 1450,
|
||||
@@ -94,9 +95,10 @@ func TestRacecourses(t *testing.T) {
|
||||
Corner: 2,
|
||||
},
|
||||
{
|
||||
Start: 1850,
|
||||
End: 2300,
|
||||
Straight: 2,
|
||||
Start: 1850,
|
||||
End: 2300,
|
||||
Straight: true,
|
||||
FarStraight: true,
|
||||
},
|
||||
{
|
||||
Start: 2300,
|
||||
@@ -111,7 +113,7 @@ func TestRacecourses(t *testing.T) {
|
||||
{
|
||||
Start: 2797,
|
||||
End: 3200,
|
||||
Straight: 1,
|
||||
Straight: true,
|
||||
},
|
||||
},
|
||||
Slopes: []horse.Slope{
|
||||
@@ -164,7 +166,7 @@ func TestRacecourses(t *testing.T) {
|
||||
{
|
||||
Start: 0,
|
||||
End: 500,
|
||||
Straight: 1,
|
||||
Straight: true,
|
||||
},
|
||||
{
|
||||
Start: 500,
|
||||
@@ -177,9 +179,10 @@ func TestRacecourses(t *testing.T) {
|
||||
Corner: 2,
|
||||
},
|
||||
{
|
||||
Start: 800,
|
||||
End: 1300,
|
||||
Straight: 2,
|
||||
Start: 800,
|
||||
End: 1300,
|
||||
Straight: true,
|
||||
FarStraight: true,
|
||||
},
|
||||
{
|
||||
Start: 1300,
|
||||
@@ -194,12 +197,54 @@ func TestRacecourses(t *testing.T) {
|
||||
{
|
||||
Start: 1614,
|
||||
End: 2000,
|
||||
Straight: 1,
|
||||
Straight: true,
|
||||
},
|
||||
},
|
||||
Slopes: []horse.Slope{},
|
||||
MoveLanePoint: 500,
|
||||
},
|
||||
{
|
||||
ID: 11203,
|
||||
TrackID: 10201,
|
||||
TrackName: "Longchamp",
|
||||
Distance: 2400,
|
||||
Ground: 1,
|
||||
InOut: 1,
|
||||
Turn: 1,
|
||||
LaneMax: 12000,
|
||||
Threshold1: 3,
|
||||
Threshold2: 0,
|
||||
LaneStart: [18]horse.TenThousandths{0, 555, 1111, 1666, 2222, 2777, 3333, 3888, 4444, 5000, 5555, 6111, 6666, 7222, 8811, 9366, 9922, 10477},
|
||||
Spans: []horse.RacecourseSpan{
|
||||
{
|
||||
Start: 0,
|
||||
End: 798,
|
||||
Straight: true,
|
||||
FarStraight: true,
|
||||
},
|
||||
{
|
||||
Start: 798,
|
||||
End: 800,
|
||||
},
|
||||
{
|
||||
Start: 800,
|
||||
End: 1200,
|
||||
Corner: 3,
|
||||
},
|
||||
{
|
||||
Start: 1200,
|
||||
End: 1600,
|
||||
Corner: 4,
|
||||
},
|
||||
{
|
||||
Start: 1600,
|
||||
End: 2400,
|
||||
Straight: true,
|
||||
},
|
||||
},
|
||||
Slopes: []horse.Slope{},
|
||||
MoveLanePoint: 800,
|
||||
},
|
||||
{
|
||||
ID: 11504,
|
||||
TrackID: 10105,
|
||||
@@ -220,7 +265,7 @@ func TestRacecourses(t *testing.T) {
|
||||
{
|
||||
Start: 100,
|
||||
End: 500,
|
||||
Straight: 1,
|
||||
Straight: true,
|
||||
},
|
||||
{
|
||||
Start: 500,
|
||||
@@ -233,9 +278,10 @@ func TestRacecourses(t *testing.T) {
|
||||
Corner: 2,
|
||||
},
|
||||
{
|
||||
Start: 900,
|
||||
End: 1300,
|
||||
Straight: 2,
|
||||
Start: 900,
|
||||
End: 1300,
|
||||
Straight: true,
|
||||
FarStraight: true,
|
||||
},
|
||||
{
|
||||
Start: 1300,
|
||||
@@ -250,7 +296,7 @@ func TestRacecourses(t *testing.T) {
|
||||
{
|
||||
Start: 1700,
|
||||
End: 2000,
|
||||
Straight: 1,
|
||||
Straight: true,
|
||||
},
|
||||
},
|
||||
Slopes: []horse.Slope{
|
||||
|
||||
@@ -17,7 +17,6 @@ FROM race_course_set c
|
||||
JOIN track ON c.race_track_id = track.id
|
||||
JOIN race_track t ON c.race_track_id = t.id
|
||||
LEFT JOIN race_course_set_status ss ON c.course_set_status_id = ss.course_set_status_id
|
||||
-- Only include courses that can be selected for room matches.
|
||||
-- Some other courses exist in the game files but have busted data.
|
||||
WHERE c.id IN (SELECT course_set FROM race WHERE "group" = 12)
|
||||
-- Exclude unused Longchamp courses with busted data.
|
||||
WHERE c.id NOT IN (11201, 11202)
|
||||
ORDER BY c.id
|
||||
|
||||
+1
-1
@@ -6,7 +6,7 @@ WITH skill_names AS (
|
||||
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 DISTINCT
|
||||
SELECT
|
||||
group_id,
|
||||
name
|
||||
FROM skill_data d
|
||||
|
||||
Vendored
-61
@@ -40,64 +40,3 @@ INSERT INTO race_track (id, initial_lane_type, enable_half_gate, horse_num_gate_
|
||||
INSERT INTO race_track (id, initial_lane_type, enable_half_gate, horse_num_gate_variation, turf_vision_type, footsmoke_color_type, area, flag_type, gate_panel_type, gate_lamp_type, board_condition_type, result_board_type) VALUES (10101, 2, 0, 1, 2, 2, 2, 1, 1, 1, 3, 0);
|
||||
INSERT INTO race_track (id, initial_lane_type, enable_half_gate, horse_num_gate_variation, turf_vision_type, footsmoke_color_type, area, flag_type, gate_panel_type, gate_lamp_type, board_condition_type, result_board_type) VALUES (10105, 4, 1, 0, 2, 2, 1, 1, 1, 1, 4, 0);
|
||||
INSERT INTO race_track (id, initial_lane_type, enable_half_gate, horse_num_gate_variation, turf_vision_type, footsmoke_color_type, area, flag_type, gate_panel_type, gate_lamp_type, board_condition_type, result_board_type) VALUES (10201, 3, 1, 0, 1, 1, 5, 0, 2, 2, 1, 0);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS 'race' ('id' INTEGER NOT NULL, 'group' INTEGER NOT NULL, 'grade' INTEGER NOT NULL, 'course_set' INTEGER NOT NULL, 'thumbnail_id' INTEGER NOT NULL, 'ff_cue_name' TEXT NOT NULL, 'ff_cuesheet_name' TEXT NOT NULL, 'real_ff_cue_name' TEXT NOT NULL, 'real_ff_cuesheet_name' TEXT NOT NULL, 'ff_anim' INTEGER NOT NULL, 'ff_camera' INTEGER NOT NULL, 'ff_camera_sub' INTEGER NOT NULL, 'ff_sub' INTEGER NOT NULL, 'start_gate' INTEGER NOT NULL, 'start_gate_panel' INTEGER NOT NULL, 'goal_gate' INTEGER NOT NULL, 'goal_flower' INTEGER NOT NULL, 'result_podium' INTEGER NOT NULL, 'audience_group_id' INTEGER NOT NULL, 'paddock_bg_id' INTEGER NOT NULL, 'audience' INTEGER NOT NULL, 'entry_num' INTEGER NOT NULL, 'change_full_gate' INTEGER NOT NULL, 'is_dirtgrade' INTEGER NOT NULL, 'start_date' INTEGER NOT NULL, PRIMARY KEY('id'));
|
||||
-- SELECT * FROM race WHERE course_set IN (10301, 10811, 11103, 11203, 11504);
|
||||
INSERT INTO race VALUES(3042,1,300,10301,3042,'snd_mfx_1007_CL','snd_mfx_1007_CL','snd_mfx_r_1007_CL','snd_mfx_r_1007_CL',3042,207,0,0,0,0,0,0,-1,0,0,0,18,0,0,1498870800);
|
||||
INSERT INTO race VALUES(4055,1,400,10301,4055,'snd_mfx_1012_CL','snd_mfx_1012_CL','snd_mfx_r_1012_CL','snd_mfx_r_1012_CL',4055,312,0,0,0,0,0,0,-1,0,0,0,18,0,0,1498870800);
|
||||
INSERT INTO race VALUES(4101,1,400,10301,4101,'snd_mfx_1012_CL','snd_mfx_1012_CL','snd_mfx_r_1012_CL','snd_mfx_r_1012_CL',4101,312,0,0,0,0,0,0,-1,0,0,0,18,0,0,1498870800);
|
||||
INSERT INTO race VALUES(5032,6,900,10301,5002,'snd_mfx_2009_CL','snd_mfx_2009_CL','','',5002,312,0,0,0,0,0,0,-1,0,0,1,18,0,0,1636977600);
|
||||
INSERT INTO race VALUES(5226,12,999,10301,5200,'snd_mfx_2008_CL','snd_mfx_2008_CL','','',5200,121,0,0,0,0,0,0,-1,0,0,0,18,0,0,1627560000);
|
||||
INSERT INTO race VALUES(6136,9,100,10301,6100,'snd_mfx_2004_CL','snd_mfx_2004_CL','','',6100,121,0,0,0,0,0,0,-1,0,0,0,12,0,0,1498870800);
|
||||
INSERT INTO race VALUES(6137,9,100,10301,6101,'snd_mfx_2004_CL','snd_mfx_2004_CL','','',6101,121,0,0,0,0,0,0,-1,0,0,0,12,0,0,1498870800);
|
||||
INSERT INTO race VALUES(6138,9,100,10301,6102,'snd_mfx_2004_CL','snd_mfx_2004_CL','','',6102,121,0,0,0,0,0,0,-1,0,0,0,12,0,0,1498870800);
|
||||
INSERT INTO race VALUES(9307,7,800,10301,9005,'snd_mfx_1012_CL','snd_mfx_1012_CL','snd_mfx_r_1012_CL','snd_mfx_r_1012_CL',9005,312,0,0,0,0,0,0,-1,0,0,0,9,0,0,1498870800);
|
||||
INSERT INTO race VALUES(9323,7,800,10301,9003,'snd_mfx_1012_CL','snd_mfx_1012_CL','snd_mfx_r_1012_CL','snd_mfx_r_1012_CL',9003,312,0,0,0,0,0,0,-1,0,0,0,9,0,0,1498870800);
|
||||
INSERT INTO race VALUES(1006,1,100,10811,1006,'snd_mfx_1002_CL','snd_mfx_1002_CL','snd_mfx_r_1002_CL','snd_mfx_r_1002_CL',1006,102,0,0,0,0,1,1,-1,0,0,0,18,0,0,1498870800);
|
||||
INSERT INTO race VALUES(4905,61,100,10811,1006,'snd_mfx_1002_CL','snd_mfx_1002_CL','','',1006,102,0,0,0,0,1,1,-1,0,0,0,18,0,0,1498870800);
|
||||
INSERT INTO race VALUES(5070,6,900,10811,5002,'snd_mfx_2009_CL','snd_mfx_2009_CL','','',5002,313,0,0,0,0,0,0,-1,0,0,1,18,0,0,1636977600);
|
||||
INSERT INTO race VALUES(5113,6,100,10811,1006,'snd_mfx_2009_CL','snd_mfx_2009_CL','','',1006,313,0,0,0,0,1,1,-1,0,0,1,18,0,0,1636977600);
|
||||
INSERT INTO race VALUES(5257,12,999,10811,5200,'snd_mfx_2008_CL','snd_mfx_2008_CL','','',5200,121,0,0,0,0,0,0,-1,0,0,0,18,0,0,1627560000);
|
||||
INSERT INTO race VALUES(5930,8,100,10811,5930,'snd_mfx_2003_CL','snd_mfx_2003_CL','','',5930,102,0,0,0,0,1,1,-1,0,0,0,18,0,0,1623931200);
|
||||
INSERT INTO race VALUES(5931,8,100,10811,5930,'snd_mfx_2003_CL','snd_mfx_2003_CL','','',5930,102,0,0,0,0,1,1,-1,0,0,0,18,0,0,1623931200);
|
||||
INSERT INTO race VALUES(5932,8,100,10811,5930,'snd_mfx_2003_CL','snd_mfx_2003_CL','','',5930,102,0,0,0,0,1,1,-1,0,0,0,18,0,0,1623931200);
|
||||
INSERT INTO race VALUES(5933,8,100,10811,5930,'snd_mfx_2003_CL','snd_mfx_2003_CL','','',5930,102,0,0,0,0,1,1,-1,0,0,0,18,0,0,1623931200);
|
||||
INSERT INTO race VALUES(6006,11,100,10811,6006,'snd_mfx_2005_CL','snd_mfx_2005_CL','','',6006,122,0,0,0,0,0,0,-1,0,0,0,9,0,0,1623326400);
|
||||
INSERT INTO race VALUES(6018,11,100,10811,6006,'snd_mfx_2006_CL','snd_mfx_2006_CL','','',6006,123,0,0,0,0,0,0,-1,0,0,0,9,0,0,1623326400);
|
||||
INSERT INTO race VALUES(6283,9,100,10811,6100,'snd_mfx_2004_CL','snd_mfx_2004_CL','','',6100,121,0,0,0,0,0,0,-1,0,0,0,12,0,0,1498870800);
|
||||
INSERT INTO race VALUES(6284,9,100,10811,6101,'snd_mfx_2004_CL','snd_mfx_2004_CL','','',6101,121,0,0,0,0,0,0,-1,0,0,0,12,0,0,1498870800);
|
||||
INSERT INTO race VALUES(6285,9,100,10811,6102,'snd_mfx_2004_CL','snd_mfx_2004_CL','','',6102,121,0,0,0,0,0,0,-1,0,0,0,12,0,0,1498870800);
|
||||
INSERT INTO race VALUES(7004,4,0,10811,7004,'snd_mfx_1013_CL','snd_mfx_1013_CL','','',7004,313,0,0,0,0,0,0,-1,0,0,0,18,0,0,1498870800);
|
||||
INSERT INTO race VALUES(7018,4,0,10811,7018,'snd_mfx_1013_CL','snd_mfx_1013_CL','','',7018,313,0,0,0,0,0,0,-1,0,0,0,12,0,0,1647777600);
|
||||
INSERT INTO race VALUES(9530,7,100,10811,9020,'snd_mfx_3002_CL','snd_mfx_3002_CL','','',9020,208,0,0,0,0,302,0,-1,0,0,0,18,0,0,1498870800);
|
||||
INSERT INTO race VALUES(9630,7,100,10811,9010,'snd_mfx_3002_CL','snd_mfx_3002_CL','','',9010,208,0,0,0,0,303,0,-1,0,0,0,18,0,0,1498870800);
|
||||
INSERT INTO race VALUES(9730,7,100,10811,9001,'snd_mfx_3001_CL','snd_mfx_3001_CL','','',9001,119,0,0,0,0,301,0,-1,0,0,0,18,0,0,1498870800);
|
||||
INSERT INTO race VALUES(9826,7,100,10811,9031,'snd_mfx_3005_CL','snd_mfx_3005_CL','','',9030,119,0,0,0,0,304,0,-1,0,0,0,16,0,0,1645704000);
|
||||
INSERT INTO race VALUES(9867,7,100,10811,9032,'snd_mfx_3005_CL','snd_mfx_3005_CL','','',9030,119,0,0,0,0,304,0,-1,0,0,0,16,0,0,1645704000);
|
||||
INSERT INTO race VALUES(9908,7,100,10811,9033,'snd_mfx_3005_CL','snd_mfx_3005_CL','','',9030,119,0,0,0,0,304,0,-1,0,0,0,16,0,0,1645704000);
|
||||
INSERT INTO race VALUES(10165,12,100,10811,1006,'snd_mfx_1002_CL','snd_mfx_1002_CL','snd_mfx_r_1002_CL','snd_mfx_r_1002_CL',1006,102,0,0,0,0,1,1,-1,0,0,0,18,0,0,1623326400);
|
||||
INSERT INTO race VALUES(12000,8,100,10811,5930,'snd_mfx_2003_CL','snd_mfx_2003_CL','','',5930,102,0,0,0,0,1,1,-1,0,0,0,18,0,0,1652184000);
|
||||
INSERT INTO race VALUES(16305,10,1000,10811,6101,'snd_mfx_3003_CL','snd_mfx_3003_CL','','',6100,121,0,0,0,0,0,0,-1,0,0,3,12,0,0,1783029600);
|
||||
INSERT INTO race VALUES(1101,1,100,11103,1101,'snd_mfx_1015_CL','snd_mfx_1015_CL','snd_mfx_r_1015_CL','snd_mfx_r_1015_CL',1101,313,0,0,100,0,1,0,-1,0,0,0,16,0,1,1498870800);
|
||||
INSERT INTO race VALUES(1102,1,100,11103,1102,'snd_mfx_1015_CL','snd_mfx_1015_CL','snd_mfx_r_1015_CL','snd_mfx_r_1015_CL',1102,313,0,0,100,0,2,0,-1,0,0,0,16,0,1,1498870800);
|
||||
INSERT INTO race VALUES(1105,1,100,11103,1105,'snd_mfx_1015_CL','snd_mfx_1015_CL','snd_mfx_r_1015_CL','snd_mfx_r_1015_CL',1105,313,0,0,100,0,5,0,-1,0,0,0,16,0,1,1498870800);
|
||||
INSERT INTO race VALUES(1106,1,100,11103,1106,'snd_mfx_1014_CL','snd_mfx_1014_CL','snd_mfx_r_1014_CL','snd_mfx_r_1014_CL',1106,313,0,0,100,0,6,0,-1,0,0,0,16,0,1,1498870800);
|
||||
INSERT INTO race VALUES(4924,61,100,11103,1101,'snd_mfx_1015_CL','snd_mfx_1015_CL','','',1101,313,0,0,100,0,1,0,-1,0,0,0,16,0,1,1498870800);
|
||||
INSERT INTO race VALUES(4925,61,100,11103,1102,'snd_mfx_1015_CL','snd_mfx_1015_CL','','',1102,313,0,0,100,0,2,0,-1,0,0,0,16,0,1,1498870800);
|
||||
INSERT INTO race VALUES(4928,61,100,11103,1105,'snd_mfx_1015_CL','snd_mfx_1015_CL','','',1105,313,0,0,100,0,5,0,-1,0,0,0,16,0,1,1498870800);
|
||||
INSERT INTO race VALUES(4929,61,100,11103,1106,'snd_mfx_1014_CL','snd_mfx_1014_CL','','',1106,313,0,0,100,0,6,0,-1,0,0,0,16,0,1,1498870800);
|
||||
INSERT INTO race VALUES(5105,6,900,11103,5002,'snd_mfx_2009_CL','snd_mfx_2009_CL','','',5002,315,0,0,100,0,0,0,-1,0,0,1,16,0,1,1636977600);
|
||||
INSERT INTO race VALUES(5132,6,100,11103,1101,'snd_mfx_2009_CL','snd_mfx_2009_CL','','',1101,315,0,0,100,0,1,0,-1,0,0,1,16,0,1,1636977600);
|
||||
INSERT INTO race VALUES(5133,6,100,11103,1102,'snd_mfx_2009_CL','snd_mfx_2009_CL','','',1102,315,0,0,100,0,2,0,-1,0,0,1,16,0,1,1636977600);
|
||||
INSERT INTO race VALUES(5136,6,100,11103,1105,'snd_mfx_2009_CL','snd_mfx_2009_CL','','',1105,315,0,0,100,0,5,0,-1,0,0,1,16,0,1,1636977600);
|
||||
INSERT INTO race VALUES(5137,6,100,11103,1106,'snd_mfx_2009_CL','snd_mfx_2009_CL','','',1106,315,0,0,100,0,6,0,-1,0,0,1,16,0,1,1636977600);
|
||||
INSERT INTO race VALUES(5290,12,999,11103,5200,'snd_mfx_2008_CL','snd_mfx_2008_CL','','',5200,121,0,0,100,0,0,0,-1,0,0,0,16,0,1,1627560000);
|
||||
INSERT INTO race VALUES(10184,12,100,11103,1101,'snd_mfx_1015_CL','snd_mfx_1015_CL','snd_mfx_r_1015_CL','snd_mfx_r_1015_CL',1101,313,0,0,100,0,1,0,-1,0,0,0,16,0,1,1623326400);
|
||||
INSERT INTO race VALUES(10185,12,100,11103,1102,'snd_mfx_1015_CL','snd_mfx_1015_CL','snd_mfx_r_1015_CL','snd_mfx_r_1015_CL',1102,313,0,0,100,0,2,0,-1,0,0,0,16,0,1,1623326400);
|
||||
INSERT INTO race VALUES(10188,12,100,11103,1105,'snd_mfx_1015_CL','snd_mfx_1015_CL','snd_mfx_r_1015_CL','snd_mfx_r_1015_CL',1105,313,0,0,100,0,5,0,-1,0,0,0,16,0,1,1623326400);
|
||||
INSERT INTO race VALUES(10189,12,100,11103,1106,'snd_mfx_1014_CL','snd_mfx_1014_CL','snd_mfx_r_1014_CL','snd_mfx_r_1014_CL',1106,313,0,0,100,0,6,0,-1,0,0,0,16,0,1,1623326400);
|
||||
INSERT INTO race VALUES(1201,1,100,11203,1023,'snd_mfx_1001_CL','snd_mfx_1001_CL','','',1023,101,0,0,201,101,1,1,-1,0,0,0,18,0,0,1498870800);
|
||||
INSERT INTO race VALUES(1119,1,100,11504,1105,'snd_mfx_1018_CL','snd_mfx_1018_CL','snd_mfx_r_1018_CL','snd_mfx_r_1018_CL',1119,133,0,0,105,0,2,0,-1,0,0,0,16,0,1,1660910400);
|
||||
INSERT INTO race VALUES(3076,1,300,11504,3076,'snd_mfx_1019_CL','snd_mfx_1019_CL','snd_mfx_r_1019_CL','snd_mfx_r_1019_CL',3076,234,0,0,105,0,0,0,-1,0,0,0,14,0,1,1660910400);
|
||||
INSERT INTO race VALUES(5149,6,900,11504,5002,'snd_mfx_2009_CL','snd_mfx_2009_CL','','',5002,234,0,0,105,0,0,0,-1,0,0,1,16,0,1,1660910400);
|
||||
INSERT INTO race VALUES(5300,12,999,11504,5200,'snd_mfx_2008_CL','snd_mfx_2008_CL','','',5200,121,0,0,105,0,0,0,-1,0,0,0,16,0,1,1660910400);
|
||||
|
||||
@@ -46,9 +46,11 @@ type RacecourseSpan struct {
|
||||
Start float32 `json:"start"`
|
||||
// End is the distance in meters along the track at which the span ends.
|
||||
End float32 `json:"end"`
|
||||
// Straight is the straight type if the span is formally a straight.
|
||||
// 1 indicates the front stretch and 2 the backstretch.
|
||||
Straight int8 `json:"straight,omitzero"`
|
||||
// Straight is whether the span is formally a straight.
|
||||
Straight bool `json:"straight,omitzero"`
|
||||
// FarStraight is whether the span is the backstretch.
|
||||
// If true, then Straight is also true.
|
||||
FarStraight bool `json:"far_straight,omitzero"`
|
||||
// Corner is the corner number of the span, or zero if not a corner.
|
||||
Corner int8 `json:"corner,omitzero"`
|
||||
}
|
||||
|
||||
+4
-100
@@ -1,9 +1,6 @@
|
||||
package ism
|
||||
|
||||
import (
|
||||
"git.sunturtle.xyz/zephyr/horse"
|
||||
"git.sunturtle.xyz/zephyr/horse/umadump"
|
||||
)
|
||||
import "git.sunturtle.xyz/zephyr/horse"
|
||||
|
||||
type Skill struct {
|
||||
ID horse.SkillID `json:"skill_id"`
|
||||
@@ -228,106 +225,13 @@ type AddItem struct {
|
||||
Number int32 `json:"number"`
|
||||
}
|
||||
|
||||
type PieceData struct {
|
||||
PieceID int32 `json:"piece_id"`
|
||||
PieceNum int32 `json:"piece_num"`
|
||||
}
|
||||
|
||||
type UserCard struct {
|
||||
CardID horse.UmaID `json:"card_id"`
|
||||
Rarity int8 `json:"rarity"`
|
||||
TalentLevel int8 `json:"talent_level"`
|
||||
Skills []Skill `json:"skill_data_array"`
|
||||
CreateTime string `json:"create_time"`
|
||||
}
|
||||
|
||||
type UserSupportCard struct {
|
||||
ViewerID int64 `json:"viewer_id"`
|
||||
SupportCardID horse.SupportCardID `json:"support_card_id"`
|
||||
Exp int32 `json:"exp"`
|
||||
LimitBreak int32 `json:"limit_break_count"`
|
||||
FavoriteFlag int32 `json:"favorite_flag"`
|
||||
Stock int32 `json:"stock"`
|
||||
PossessTime string `json:"possess_time"`
|
||||
}
|
||||
|
||||
type RewardAddSupportCardNum struct {
|
||||
SupportCardID horse.SupportCardID `json:"support_card_id"`
|
||||
Number int32 `json:"number"`
|
||||
ItemType int32 `json:"item_type"`
|
||||
}
|
||||
|
||||
type HonorData struct {
|
||||
HonorID int32 `json:"honor_id"`
|
||||
Step int32 `json:"step"`
|
||||
CreateTime string `json:"create_time"`
|
||||
}
|
||||
|
||||
type UserChara struct {
|
||||
CharaID horse.CharacterID `json:"chara_id"`
|
||||
TrainingNum int32 `json:"training_num"`
|
||||
LovePoint int32 `json:"love_point"`
|
||||
LovePointPool int32 `json:"love_point_pool"`
|
||||
Fan uint64 `json:"fan"`
|
||||
}
|
||||
|
||||
type UserCloth struct {
|
||||
ClothID int32 `json:"cloth_id"`
|
||||
}
|
||||
|
||||
type UserMusic struct {
|
||||
MusicID int32 `json:"music_id"`
|
||||
AcquisitionTime string `json:"acquisition_time"`
|
||||
}
|
||||
|
||||
type CharaProfileData struct {
|
||||
CharaID horse.CharacterID `json:"chara_id"`
|
||||
DataID int32 `json:"data_id"`
|
||||
NewFlag int32 `json:"new_flag"`
|
||||
}
|
||||
|
||||
type RewardSummary struct {
|
||||
AddItem []AddItem `json:"add_item_list"`
|
||||
AddPiece []PieceData `json:"add_piece_list"`
|
||||
AddCard []UserCard `json:"add_card_list"`
|
||||
AddCardBonus *RewardSummary `json:"add_card_bonus_info"`
|
||||
AddSupportCard []UserSupportCard `json:"add_support_card_list"`
|
||||
AddSupportCardNum []RewardAddSupportCardNum `json:"add_support_card_num_array"`
|
||||
AddHonor []HonorData `json:"add_honor_list"`
|
||||
AddChara []UserChara `json:"add_chara_list"`
|
||||
AddCloth []UserCloth `json:"add_cloth_list"`
|
||||
AddMusic []UserMusic `json:"add_music_list"`
|
||||
AddStoryIDs []umadump.Never `json:"add_story_id_array"` // not in my disassembly
|
||||
AddFCoin int32 `json:"add_fcoin"` // carats
|
||||
AddPresents int32 `json:"add_present_num"`
|
||||
AddTotalFan int32 `json:"add_total_fan"`
|
||||
NewCharaProfiles []CharaProfileData `json:"new_chara_profile_array"`
|
||||
ForceUpdateHonorID int32 `json:"force_update_honor_id"`
|
||||
AddItem []AddItem `json:"add_item_list"`
|
||||
}
|
||||
|
||||
type End struct {
|
||||
Chara SingleModeChara `json:"chara_info"`
|
||||
RaceConditions []umadump.Never `json:"race_condition_array"`
|
||||
UncheckedEvents []umadump.Never `json:"unchecked_event_array"`
|
||||
HomeInfo *umadump.Never `json:"home_info"`
|
||||
WinSaddleIDs []umadump.Never `json:"win_saddle_id_array"`
|
||||
EffectedFactors []umadump.Never `json:"effected_factor_array"`
|
||||
RaceStartInfo *umadump.Never `json:"race_start_info"`
|
||||
RaceScenario *umadump.Never `json:"race_scenario"`
|
||||
AddTrophyInfo *umadump.Never `json:"add_trophy_info"`
|
||||
TrophyRewardInfo *umadump.Never `json:"trophy_reward_info"`
|
||||
PrevCharaGrade *umadump.Never `json:"prev_chara_grade"`
|
||||
RaceAddRewardInfo []umadump.Never `json:"race_add_reward_info"`
|
||||
Missions []umadump.Never `json:"mission_list"`
|
||||
StoryEventMissions []umadump.Never `json:"story_event_mission_list"`
|
||||
StoryEventCharaBonuses []umadump.Never `json:"story_event_chara_bonus_list"`
|
||||
StartDressInfo []umadump.Never `json:"start_dress_info"`
|
||||
ResumeFactorSelect *umadump.Never `json:"resume_factor_select"`
|
||||
RaceRandomPrograms []umadump.Never `json:"race_random_program_array"`
|
||||
RaceRewardLimitMore []umadump.Never `json:"race_reward_limit_more_list"`
|
||||
SkillFilterSettings []umadump.Never `json:"skill_filter_setting_array"`
|
||||
RewardSummary RewardSummary `json:"reward_summary_info"`
|
||||
IsUmaplan bool `json:"is_umaplan"`
|
||||
Chara SingleModeChara `json:"chara_info"`
|
||||
RewardSummary RewardSummary `json:"reward_summary_info"`
|
||||
}
|
||||
|
||||
type ProgressInfo struct {
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
package umadump
|
||||
|
||||
type Never struct{}
|
||||
|
||||
func (Never) AppendText(b []byte) ([]byte, error) {
|
||||
panic("umadump: attempt to encode value that should never exist")
|
||||
}
|
||||
func (Never) MarshalText() ([]byte, error) {
|
||||
panic("umadump: attempt to encode value that should never exist")
|
||||
}
|
||||
func (Never) UnmarshalText(b []byte) error {
|
||||
panic("umadump: attempt to decode value that should never exist")
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
@@ -1,29 +0,0 @@
|
||||
// Code generated by "stringer -type CardRarity -linecomment -trimprefix Card"; DO NOT EDIT.
|
||||
|
||||
package replay
|
||||
|
||||
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[CardNone-0]
|
||||
_ = x[CardRare1-1]
|
||||
_ = x[CardRare2-2]
|
||||
_ = x[CardRare3-3]
|
||||
_ = x[CardRare4-4]
|
||||
_ = x[CardRare5-5]
|
||||
}
|
||||
|
||||
const _CardRarity_name = "NONERare1Rare2Rare3Rare4Rare5"
|
||||
|
||||
var _CardRarity_index = [...]uint8{0, 4, 9, 14, 19, 24, 29}
|
||||
|
||||
func (i CardRarity) String() string {
|
||||
idx := int(i) - 0
|
||||
if i < 0 || idx >= len(_CardRarity_index)-1 {
|
||||
return "CardRarity(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||
}
|
||||
return _CardRarity_name[_CardRarity_index[idx]:_CardRarity_index[idx+1]]
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
// Code generated by "stringer -type CharaGradeType -linecomment -trimprefix CharaGrade"; DO NOT EDIT.
|
||||
|
||||
package replay
|
||||
|
||||
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[CharaGradeNone-0]
|
||||
_ = x[CharaGradeDebut-1]
|
||||
_ = x[CharaGradeNoWin-2]
|
||||
_ = x[CharaGradeOpen-3]
|
||||
_ = x[CharaGradeG3Silver-4]
|
||||
_ = x[CharaGradeG3Gold-5]
|
||||
_ = x[CharaGradeG2Silver-6]
|
||||
_ = x[CharaGradeG2Gold-7]
|
||||
_ = x[CharaGradeG1Bronze-8]
|
||||
_ = x[CharaGradeG1Silver-9]
|
||||
_ = x[CharaGradeG1Gold-10]
|
||||
_ = x[charaGradeMax-11]
|
||||
}
|
||||
|
||||
const _CharaGradeType_name = "NONEDebutNoWinOpenG3SilverG3GoldG2SilverG2GoldG1BronzeG1SilverG1GoldcharaGradeMax"
|
||||
|
||||
var _CharaGradeType_index = [...]uint8{0, 4, 9, 14, 18, 26, 32, 40, 46, 54, 62, 68, 81}
|
||||
|
||||
func (i CharaGradeType) String() string {
|
||||
idx := int(i) - 0
|
||||
if i < 0 || idx >= len(_CharaGradeType_index)-1 {
|
||||
return "CharaGradeType(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||
}
|
||||
return _CharaGradeType_name[_CharaGradeType_index[idx]:_CharaGradeType_index[idx+1]]
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
// Code generated by "stringer -type CourseDistanceType -linecomment -trimprefix Distance"; DO NOT EDIT.
|
||||
|
||||
package replay
|
||||
|
||||
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[DistanceSprint-1]
|
||||
_ = x[DistanceMile-2]
|
||||
_ = x[DistanceMedium-3]
|
||||
_ = x[DistanceLong-4]
|
||||
}
|
||||
|
||||
const _CourseDistanceType_name = "ShortMileMiddleLong"
|
||||
|
||||
var _CourseDistanceType_index = [...]uint8{0, 5, 9, 15, 19}
|
||||
|
||||
func (i CourseDistanceType) String() string {
|
||||
idx := int(i) - 1
|
||||
if i < 1 || idx >= len(_CourseDistanceType_index)-1 {
|
||||
return "CourseDistanceType(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||
}
|
||||
return _CourseDistanceType_name[_CourseDistanceType_index[idx]:_CourseDistanceType_index[idx+1]]
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
// Code generated by "stringer -type GroundCondition -linecomment"; DO NOT EDIT.
|
||||
|
||||
package replay
|
||||
|
||||
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[GroundFirm-1]
|
||||
_ = x[GroundGood-2]
|
||||
_ = x[GroundSoft-3]
|
||||
_ = x[GroundHeavy-4]
|
||||
}
|
||||
|
||||
const _GroundCondition_name = "GoodSoftHardBad"
|
||||
|
||||
var _GroundCondition_index = [...]uint8{0, 4, 8, 12, 15}
|
||||
|
||||
func (i GroundCondition) String() string {
|
||||
idx := int(i) - 1
|
||||
if i < 1 || idx >= len(_GroundCondition_index)-1 {
|
||||
return "GroundCondition(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||
}
|
||||
return _GroundCondition_name[_GroundCondition_index[idx]:_GroundCondition_index[idx+1]]
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
// Code generated by "stringer -type InitialLaneType -trimprefix InitialLane"; DO NOT EDIT.
|
||||
|
||||
package replay
|
||||
|
||||
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[InitialLaneExtraSpaceAfter9-1]
|
||||
_ = x[InitialLaneEquidistant-2]
|
||||
_ = x[InitialLaneExtraSpaceAfter14-3]
|
||||
_ = x[InitialLaneExtraSpaceAfter8-4]
|
||||
_ = x[initialLaneMax-5]
|
||||
}
|
||||
|
||||
const _InitialLaneType_name = "ExtraSpaceAfter9EquidistantExtraSpaceAfter14ExtraSpaceAfter8initialLaneMax"
|
||||
|
||||
var _InitialLaneType_index = [...]uint8{0, 16, 27, 44, 60, 74}
|
||||
|
||||
func (i InitialLaneType) String() string {
|
||||
idx := int(i) - 1
|
||||
if i < 1 || idx >= len(_InitialLaneType_index)-1 {
|
||||
return "InitialLaneType(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||
}
|
||||
return _InitialLaneType_name[_InitialLaneType_index[idx]:_InitialLaneType_index[idx+1]]
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
// Code generated by "stringer -type MainStoryRaceGimmickType -linecomment"; DO NOT EDIT.
|
||||
|
||||
package replay
|
||||
|
||||
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[GimmickNone-0]
|
||||
_ = x[GimmickSpecial00-1]
|
||||
_ = x[gimmickMax-2]
|
||||
}
|
||||
|
||||
const _MainStoryRaceGimmickType_name = "NONESpecial_00gimmickMax"
|
||||
|
||||
var _MainStoryRaceGimmickType_index = [...]uint8{0, 4, 14, 24}
|
||||
|
||||
func (i MainStoryRaceGimmickType) String() string {
|
||||
idx := int(i) - 0
|
||||
if i < 0 || idx >= len(_MainStoryRaceGimmickType_index)-1 {
|
||||
return "MainStoryRaceGimmickType(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||
}
|
||||
return _MainStoryRaceGimmickType_name[_MainStoryRaceGimmickType_index[idx]:_MainStoryRaceGimmickType_index[idx+1]]
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
// Code generated by "stringer -type Motivation -linecomment"; DO NOT EDIT.
|
||||
|
||||
package replay
|
||||
|
||||
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[MotivationNone-0]
|
||||
_ = x[MotivationAwful-1]
|
||||
_ = x[MotivationBad-2]
|
||||
_ = x[MotivationNormal-3]
|
||||
_ = x[MotivationGood-4]
|
||||
_ = x[MotivationGreat-5]
|
||||
}
|
||||
|
||||
const _Motivation_name = "NoneMinLowMiddleHighMax"
|
||||
|
||||
var _Motivation_index = [...]uint8{0, 4, 7, 10, 16, 20, 23}
|
||||
|
||||
func (i Motivation) String() string {
|
||||
idx := int(i) - 0
|
||||
if i < 0 || idx >= len(_Motivation_index)-1 {
|
||||
return "Motivation(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||
}
|
||||
return _Motivation_name[_Motivation_index[idx]:_Motivation_index[idx+1]]
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
// Code generated by "stringer -type ProperGrade -trimprefix Aptitude"; DO NOT EDIT.
|
||||
|
||||
package replay
|
||||
|
||||
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[AptitudeNull-0]
|
||||
_ = x[AptitudeG-1]
|
||||
_ = x[AptitudeF-2]
|
||||
_ = x[AptitudeE-3]
|
||||
_ = x[AptitudeD-4]
|
||||
_ = x[AptitudeC-5]
|
||||
_ = x[AptitudeB-6]
|
||||
_ = x[AptitudeA-7]
|
||||
_ = x[AptitudeS-8]
|
||||
}
|
||||
|
||||
const _ProperGrade_name = "NullGFEDCBAS"
|
||||
|
||||
var _ProperGrade_index = [...]uint8{0, 4, 5, 6, 7, 8, 9, 10, 11, 12}
|
||||
|
||||
func (i ProperGrade) String() string {
|
||||
idx := int(i) - 0
|
||||
if i < 0 || idx >= len(_ProperGrade_index)-1 {
|
||||
return "ProperGrade(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||
}
|
||||
return _ProperGrade_name[_ProperGrade_index[idx]:_ProperGrade_index[idx+1]]
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
// Code generated by "stringer -type RaceDifficulty -trimprefix Difficulty"; DO NOT EDIT.
|
||||
|
||||
package replay
|
||||
|
||||
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[DifficultyEasy-1]
|
||||
_ = x[DifficultyNormal-2]
|
||||
_ = x[DifficultyHard-3]
|
||||
_ = x[DifficultyVeryHard-4]
|
||||
_ = x[DifficultyExtreme-5]
|
||||
_ = x[difficultyMax-6]
|
||||
}
|
||||
|
||||
const _RaceDifficulty_name = "EasyNormalHardVeryHardExtremedifficultyMax"
|
||||
|
||||
var _RaceDifficulty_index = [...]uint8{0, 4, 10, 14, 22, 29, 42}
|
||||
|
||||
func (i RaceDifficulty) String() string {
|
||||
idx := int(i) - 1
|
||||
if i < 1 || idx >= len(_RaceDifficulty_index)-1 {
|
||||
return "RaceDifficulty(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||
}
|
||||
return _RaceDifficulty_name[_RaceDifficulty_index[idx]:_RaceDifficulty_index[idx+1]]
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
// Code generated by "stringer -type RaceTime -trimprefix Time"; DO NOT EDIT.
|
||||
|
||||
package replay
|
||||
|
||||
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[TimeMin-0]
|
||||
_ = x[TimeMorning-1]
|
||||
_ = x[TimeDaytime-2]
|
||||
_ = x[TimeEvening-3]
|
||||
_ = x[TimeNight-4]
|
||||
_ = x[TimeMax-5]
|
||||
}
|
||||
|
||||
const _RaceTime_name = "MinMorningDaytimeEveningNightMax"
|
||||
|
||||
var _RaceTime_index = [...]uint8{0, 3, 10, 17, 24, 29, 32}
|
||||
|
||||
func (i RaceTime) String() string {
|
||||
idx := int(i) - 0
|
||||
if i < 0 || idx >= len(_RaceTime_index)-1 {
|
||||
return "RaceTime(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||
}
|
||||
return _RaceTime_name[_RaceTime_index[idx]:_RaceTime_index[idx+1]]
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
// Code generated by "stringer -type RaceType -linecomment -trimprefix RaceType"; DO NOT EDIT.
|
||||
|
||||
package replay
|
||||
|
||||
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[RaceTypeNone-0]
|
||||
_ = x[RaceTypePvP-1]
|
||||
_ = x[RaceTypeTutorial-2]
|
||||
_ = x[RaceTypeStory-3]
|
||||
_ = x[RaceTypeStoryCondition-4]
|
||||
_ = x[RaceTypeChampions-5]
|
||||
_ = x[RaceTypeSingle-6]
|
||||
_ = x[RaceTypeSingleModeScenarioTeamRace-7]
|
||||
_ = x[RaceTypeRoomMatch-8]
|
||||
_ = x[RaceTypePractice-9]
|
||||
_ = x[RaceTypeDaily-10]
|
||||
_ = x[RaceTypeTeamBuilding-11]
|
||||
_ = x[RaceTypeLegend-12]
|
||||
_ = x[RaceTypeChallengeMatch-13]
|
||||
_ = x[RaceTypeTeamStadium-14]
|
||||
_ = x[RaceTypeHeroes-16]
|
||||
_ = x[raceTypeMaxInclusive-16]
|
||||
}
|
||||
|
||||
const (
|
||||
_RaceType_name_0 = "NONEPvPTutorialStoryStoryConditionChampionsSingleSingleModeScenarioTeamRaceRoomMatchPracticeDailyTeamBuildingLegendChallengeMatchTeamStadium"
|
||||
_RaceType_name_1 = "Heroes"
|
||||
)
|
||||
|
||||
var (
|
||||
_RaceType_index_0 = [...]uint8{0, 4, 7, 15, 20, 34, 43, 49, 75, 84, 92, 97, 109, 115, 129, 140}
|
||||
)
|
||||
|
||||
func (i RaceType) String() string {
|
||||
switch {
|
||||
case 0 <= i && i <= 14:
|
||||
return _RaceType_name_0[_RaceType_index_0[i]:_RaceType_index_0[i+1]]
|
||||
case i == 16:
|
||||
return _RaceType_name_1
|
||||
default:
|
||||
return "RaceType(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||
}
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
// Code generated by "stringer -type RaceWeather -trimprefix Weather"; DO NOT EDIT.
|
||||
|
||||
package replay
|
||||
|
||||
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[WeatherMin-0]
|
||||
_ = x[WeatherSunny-1]
|
||||
_ = x[WeatherCloudy-2]
|
||||
_ = x[WeatherRainy-3]
|
||||
_ = x[WeatherSnow-4]
|
||||
_ = x[WeatherMax-5]
|
||||
}
|
||||
|
||||
const _RaceWeather_name = "MinSunnyCloudyRainySnowMax"
|
||||
|
||||
var _RaceWeather_index = [...]uint8{0, 3, 8, 14, 19, 23, 26}
|
||||
|
||||
func (i RaceWeather) String() string {
|
||||
idx := int(i) - 0
|
||||
if i < 0 || idx >= len(_RaceWeather_index)-1 {
|
||||
return "RaceWeather(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||
}
|
||||
return _RaceWeather_name[_RaceWeather_index[idx]:_RaceWeather_index[idx+1]]
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,182 +0,0 @@
|
||||
package replay_test
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
"encoding/json"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"git.sunturtle.xyz/zephyr/horse/umadump/replay"
|
||||
)
|
||||
|
||||
//go:embed Mihono_Bourbon-74.2421s-20260829.json
|
||||
var testRace string
|
||||
|
||||
func TestRaceUnmarshal(t *testing.T) {
|
||||
// A replay is simply too large to bother converting by hand for a unit test.
|
||||
// Just test that we can unmarshal and certain important fields are populated.
|
||||
d := json.NewDecoder(strings.NewReader(testRace))
|
||||
d.DisallowUnknownFields()
|
||||
var got replay.Race
|
||||
if err := d.Decode(&got); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if got.RaceType != replay.RaceTypeChampions {
|
||||
t.Errorf("wrong race type: want %v, got %v", replay.RaceTypeChampions, got.RaceType)
|
||||
}
|
||||
if got.RandomSeed != 1667907263 {
|
||||
t.Errorf("wrong random seed: want 1667907263, got %d", got.RandomSeed)
|
||||
}
|
||||
if got.RaceCourseSet.ID != 10903 {
|
||||
t.Errorf("wrong race course: want 10903, got %d", got.RaceCourseSet.ID)
|
||||
}
|
||||
if got.GroundCondition != replay.GroundFirm {
|
||||
t.Errorf("wrong ground condition: want %v, got %v", replay.GroundFirm, got.GroundCondition)
|
||||
}
|
||||
if got.Weather != replay.WeatherCloudy {
|
||||
t.Errorf("wrong weather: want %v, got %v", replay.WeatherCloudy, got.Weather)
|
||||
}
|
||||
if got.NumRaceHorses != 9 {
|
||||
t.Errorf("wrong number of race horses: want 9, got %d", got.NumRaceHorses)
|
||||
}
|
||||
if len(got.PlayerTeamMemberArray) != 3 {
|
||||
t.Errorf("wrong number of team members: want 3, got %d", len(got.PlayerTeamMemberArray))
|
||||
}
|
||||
for _, m := range got.PlayerTeamMemberArray {
|
||||
if m.CharaID == 0 {
|
||||
t.Error("missing team member chara id")
|
||||
}
|
||||
if m.FinishTimeRaw == 0 {
|
||||
t.Error("missing team member finish time raw")
|
||||
}
|
||||
if m.RaceParam.RawSpeed == 0 {
|
||||
t.Error("missing team member race param raw speed")
|
||||
}
|
||||
if m.RaceParam.BaseSpeed == 0 {
|
||||
t.Error("missing team member race param base speed")
|
||||
}
|
||||
if m.ResponseHorseData.ViewerID == 0 {
|
||||
t.Error("missing team member response horse data viewer id")
|
||||
}
|
||||
if m.ResponseHorseData.TrainedCharaID == 0 {
|
||||
t.Error("missing team member response horse data trained chara id")
|
||||
}
|
||||
if m.ResponseHorseData.CardID == 0 {
|
||||
t.Error("missing team member response horse data card id")
|
||||
}
|
||||
if len(m.ResponseHorseData.SkillArray) == 0 {
|
||||
t.Error("missing team member response horse data skill array")
|
||||
}
|
||||
if m.ResponseHorseData.Speed == 0 {
|
||||
t.Error("missing team member response horse data speed")
|
||||
}
|
||||
if m.ResponseHorseData.RunningStyle == 0 {
|
||||
t.Error("missing team member response horse data running style")
|
||||
}
|
||||
if m.ActiveProperDistance == 0 {
|
||||
t.Error("missing team member active distance aptitude")
|
||||
}
|
||||
if m.ActiveProperGroundType == 0 {
|
||||
t.Error("missing team member active surface aptitude")
|
||||
}
|
||||
if m.TrainedCharaData.ID == 0 {
|
||||
t.Error("missing team member trained chara id")
|
||||
}
|
||||
if m.TrainedCharaData.CardID == 0 {
|
||||
t.Error("missing team member trained chara card id")
|
||||
}
|
||||
if m.TrainedCharaData.Speed == 0 {
|
||||
t.Error("missing team member trained chara speed")
|
||||
}
|
||||
if m.TrainedCharaData.RunningStyle == 0 {
|
||||
t.Error("missing team member trained chara running style")
|
||||
}
|
||||
if m.TrainedCharaData.CreateTime == "" {
|
||||
t.Error("missing team member trained chara create time")
|
||||
}
|
||||
if len(m.TrainedCharaData.AcquiredSkillArray) == 0 {
|
||||
t.Error("missing team member trained chara acquired skill array")
|
||||
}
|
||||
}
|
||||
if len(got.RaceHorse) != int(got.NumRaceHorses) {
|
||||
t.Errorf("wrong number of race horses: want %d, got %d", got.NumRaceHorses, len(got.RaceHorse))
|
||||
}
|
||||
zeroFinish, zeroDelta := 0, 0
|
||||
for _, m := range got.RaceHorse {
|
||||
if m.CharaID == 0 {
|
||||
t.Error("missing race horse chara id")
|
||||
}
|
||||
if m.FinishOrder == 0 {
|
||||
zeroFinish++
|
||||
}
|
||||
if m.FinishTimeRaw == 0 {
|
||||
t.Error("missing race horse finish time raw")
|
||||
}
|
||||
if m.FinishDiffTimeFromPrev == 0 {
|
||||
zeroDelta++
|
||||
}
|
||||
if m.RaceParam.RawSpeed == 0 {
|
||||
t.Error("missing race horse race param raw speed")
|
||||
}
|
||||
if m.RaceParam.BaseSpeed == 0 {
|
||||
t.Error("missing race horse race param base speed")
|
||||
}
|
||||
if m.ResponseHorseData.ViewerID == 0 {
|
||||
t.Error("missing race horse response horse data viewer id")
|
||||
}
|
||||
if m.ResponseHorseData.TrainedCharaID == 0 {
|
||||
t.Error("missing race horse response horse data trained chara id")
|
||||
}
|
||||
if m.ResponseHorseData.CardID == 0 {
|
||||
t.Error("missing race horse response horse data card id")
|
||||
}
|
||||
if len(m.ResponseHorseData.SkillArray) == 0 {
|
||||
t.Error("missing race horse response horse data skill array")
|
||||
}
|
||||
if m.ResponseHorseData.Speed == 0 {
|
||||
t.Error("missing race horse response horse data speed")
|
||||
}
|
||||
if m.ResponseHorseData.RunningStyle == 0 {
|
||||
t.Error("missing race horse response horse data running style")
|
||||
}
|
||||
if m.ActiveProperDistance == 0 {
|
||||
t.Error("missing race horse active distance aptitude")
|
||||
}
|
||||
if m.ActiveProperGroundType == 0 {
|
||||
t.Error("missing race horse active surface aptitude")
|
||||
}
|
||||
if m.TrainedCharaData.ID == 0 {
|
||||
t.Error("missing race horse trained chara id")
|
||||
}
|
||||
if m.TrainedCharaData.CardID == 0 {
|
||||
t.Error("missing race horse trained chara card id")
|
||||
}
|
||||
if m.TrainedCharaData.Speed == 0 {
|
||||
t.Error("missing race horse trained chara speed")
|
||||
}
|
||||
if m.TrainedCharaData.RunningStyle == 0 {
|
||||
t.Error("missing race horse trained chara running style")
|
||||
}
|
||||
if m.TrainedCharaData.CreateTime == "" {
|
||||
t.Error("missing race horse trained chara create time")
|
||||
}
|
||||
if len(m.TrainedCharaData.AcquiredSkillArray) == 0 {
|
||||
t.Error("missing race horse trained chara acquired skill array")
|
||||
}
|
||||
}
|
||||
if zeroFinish != 1 {
|
||||
t.Errorf("%d horses in index 0 finish", zeroFinish)
|
||||
}
|
||||
if zeroDelta != 1 {
|
||||
t.Errorf("%d horses with zero finish time delta", zeroDelta)
|
||||
}
|
||||
if got.SimDataBase64 != encodedSimdata {
|
||||
t.Errorf("wrong simdata:\nwant %s\n got %s", encodedSimdata, got.SimDataBase64)
|
||||
}
|
||||
if len(got.HorseIndexByFinishOrder) != len(got.RaceHorse) {
|
||||
t.Errorf("length of horseIndexByFinishOrder (%d) doesn't match length of raceHorse (%d)", len(got.HorseIndexByFinishOrder), len(got.RaceHorse))
|
||||
}
|
||||
if len(got.HorseIndexByPopularity) != len(got.RaceHorse) {
|
||||
t.Errorf("length of horseIndexByPopularity (%d) doesn't match length of raceHorse (%d)", len(got.HorseIndexByPopularity), len(got.RaceHorse))
|
||||
}
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
// Code generated by "stringer -type ResultBoardConditionType -linecomment"; DO NOT EDIT.
|
||||
|
||||
package replay
|
||||
|
||||
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[ResultBoardTurfNone-1]
|
||||
_ = x[ResultBoardTurfDirt-2]
|
||||
_ = x[ResultBoardDirtNone-3]
|
||||
_ = x[ResultBoardDirtTurf-4]
|
||||
}
|
||||
|
||||
const _ResultBoardConditionType_name = "Turf_NoneTurf_DirtDirt_NoneDirt_Turf"
|
||||
|
||||
var _ResultBoardConditionType_index = [...]uint8{0, 9, 18, 27, 36}
|
||||
|
||||
func (i ResultBoardConditionType) String() string {
|
||||
idx := int(i) - 1
|
||||
if i < 1 || idx >= len(_ResultBoardConditionType_index)-1 {
|
||||
return "ResultBoardConditionType(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||
}
|
||||
return _ResultBoardConditionType_name[_ResultBoardConditionType_index[idx]:_ResultBoardConditionType_index[idx+1]]
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
// Code generated by "stringer -type Rotation -trimprefix Rotation"; DO NOT EDIT.
|
||||
|
||||
package replay
|
||||
|
||||
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[RotationRight-1]
|
||||
_ = x[RotationLeft-2]
|
||||
_ = x[RotationStraightRight-3]
|
||||
_ = x[RotationStraightLeft-4]
|
||||
}
|
||||
|
||||
const _Rotation_name = "RightLeftStraightRightStraightLeft"
|
||||
|
||||
var _Rotation_index = [...]uint8{0, 5, 9, 22, 34}
|
||||
|
||||
func (i Rotation) String() string {
|
||||
idx := int(i) - 1
|
||||
if i < 1 || idx >= len(_Rotation_index)-1 {
|
||||
return "Rotation(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||
}
|
||||
return _Rotation_name[_Rotation_index[idx]:_Rotation_index[idx+1]]
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
// Code generated by "stringer -type RunningStyleEx -linecomment"; DO NOT EDIT.
|
||||
|
||||
package replay
|
||||
|
||||
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[StyleExNone-0]
|
||||
_ = x[StyleExRunaway-1]
|
||||
}
|
||||
|
||||
const _RunningStyleEx_name = "NONEOonige"
|
||||
|
||||
var _RunningStyleEx_index = [...]uint8{0, 4, 10}
|
||||
|
||||
func (i RunningStyleEx) String() string {
|
||||
idx := int(i) - 0
|
||||
if i < 0 || idx >= len(_RunningStyleEx_index)-1 {
|
||||
return "RunningStyleEx(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||
}
|
||||
return _RunningStyleEx_name[_RunningStyleEx_index[idx]:_RunningStyleEx_index[idx+1]]
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
// Code generated by "stringer -type Season -trimprefix Season"; DO NOT EDIT.
|
||||
|
||||
package replay
|
||||
|
||||
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[SeasonMin-0]
|
||||
_ = x[SeasonSpring-1]
|
||||
_ = x[SeasonSummer-2]
|
||||
_ = x[SeasonFall-3]
|
||||
_ = x[SeasonWinter-4]
|
||||
_ = x[SeasonCherryBlossom-5]
|
||||
_ = x[SeasonMax-6]
|
||||
}
|
||||
|
||||
const _Season_name = "MinSpringSummerFallWinterCherryBlossomMax"
|
||||
|
||||
var _Season_index = [...]uint8{0, 3, 9, 15, 19, 25, 38, 41}
|
||||
|
||||
func (i Season) String() string {
|
||||
idx := int(i) - 0
|
||||
if i < 0 || idx >= len(_Season_index)-1 {
|
||||
return "Season(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||
}
|
||||
return _Season_name[_Season_index[idx]:_Season_index[idx+1]]
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
@@ -1,27 +0,0 @@
|
||||
// Code generated by "stringer -type TurfVisionType -trimprefix TurfVision"; DO NOT EDIT.
|
||||
|
||||
package replay
|
||||
|
||||
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[TurfVisionURA-1]
|
||||
_ = x[TurfVisionNAU-2]
|
||||
_ = x[TurfVisionStand-3]
|
||||
_ = x[turfVisionMax-4]
|
||||
}
|
||||
|
||||
const _TurfVisionType_name = "URANAUStandturfVisionMax"
|
||||
|
||||
var _TurfVisionType_index = [...]uint8{0, 3, 6, 11, 24}
|
||||
|
||||
func (i TurfVisionType) String() string {
|
||||
idx := int(i) - 1
|
||||
if i < 1 || idx >= len(_TurfVisionType_index)-1 {
|
||||
return "TurfVisionType(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||
}
|
||||
return _TurfVisionType_name[_TurfVisionType_index[idx]:_TurfVisionType_index[idx+1]]
|
||||
}
|
||||
@@ -106,11 +106,6 @@ export const PAGES = {
|
||||
name: 'Tagged Skills Explorer',
|
||||
description: 'View skills matching the tags internal to the game.',
|
||||
},
|
||||
{
|
||||
route: resolve('/doc/fans'),
|
||||
name: 'Career Fan Gain Is Solved',
|
||||
description: 'The exact formula for fan gain from career races – nothing "random" about it.'
|
||||
},
|
||||
] satisfies Page[],
|
||||
} as const;
|
||||
|
||||
|
||||
+336
-24
File diff suppressed because it is too large
Load Diff
+23
-23
@@ -76,36 +76,36 @@ export interface SectionInfo {
|
||||
section: number;
|
||||
phase: Phase;
|
||||
endRate: number;
|
||||
posKeep?: boolean;
|
||||
rush?: boolean;
|
||||
spotStruggleEnter?: boolean;
|
||||
posKeep: boolean;
|
||||
rush: boolean;
|
||||
spotStruggleEnter: boolean;
|
||||
}
|
||||
|
||||
export const SECTIONS: ReadonlyArray<SectionInfo> = [
|
||||
{ section: 1, phase: Phase.EarlyRace, endRate: 1 / 24, posKeep: true },
|
||||
{ section: 1, phase: Phase.EarlyRace, endRate: 1 / 24, posKeep: true, rush: false, spotStruggleEnter: false },
|
||||
{ section: 2, phase: Phase.EarlyRace, endRate: 2 / 24, posKeep: true, rush: true, spotStruggleEnter: true },
|
||||
{ section: 3, phase: Phase.EarlyRace, endRate: 3 / 24, posKeep: true, rush: true, spotStruggleEnter: true },
|
||||
{ section: 4, phase: Phase.EarlyRace, endRate: 4 / 24, posKeep: true, rush: true, spotStruggleEnter: true },
|
||||
{ section: 5, phase: Phase.MidRace, endRate: 5 / 24, posKeep: true, rush: true, spotStruggleEnter: true },
|
||||
{ section: 6, phase: Phase.MidRace, endRate: 6 / 24, posKeep: true, rush: true },
|
||||
{ section: 7, phase: Phase.MidRace, endRate: 7 / 24, posKeep: true, rush: true },
|
||||
{ section: 8, phase: Phase.MidRace, endRate: 8 / 24, posKeep: true, rush: true },
|
||||
{ section: 9, phase: Phase.MidRace, endRate: 9 / 24, posKeep: true, rush: true },
|
||||
{ section: 10, phase: Phase.MidRace, endRate: 10 / 24, posKeep: true },
|
||||
{ section: 11, phase: Phase.MidRace, endRate: 11 / 24 },
|
||||
{ section: 12, phase: Phase.MidRace, endRate: 12 / 24 },
|
||||
{ section: 13, phase: Phase.MidRace, endRate: 13 / 24 },
|
||||
{ section: 14, phase: Phase.MidRace, endRate: 14 / 24 },
|
||||
{ section: 15, phase: Phase.MidRace, endRate: 15 / 24 },
|
||||
{ section: 16, phase: Phase.MidRace, endRate: 16 / 24 },
|
||||
{ section: 17, phase: Phase.LateRace, endRate: 17 / 24 },
|
||||
{ section: 18, phase: Phase.LateRace, endRate: 18 / 24 },
|
||||
{ section: 19, phase: Phase.LateRace, endRate: 19 / 24 },
|
||||
{ section: 20, phase: Phase.LateRace, endRate: 20 / 24 },
|
||||
{ section: 21, phase: Phase.LastSpurt, endRate: 21 / 24 },
|
||||
{ section: 22, phase: Phase.LastSpurt, endRate: 22 / 24 },
|
||||
{ section: 23, phase: Phase.LastSpurt, endRate: 23 / 24 },
|
||||
{ section: 24, phase: Phase.LastSpurt, endRate: 24 / 24 },
|
||||
{ section: 6, phase: Phase.MidRace, endRate: 6 / 24, posKeep: true, rush: true, spotStruggleEnter: false },
|
||||
{ section: 7, phase: Phase.MidRace, endRate: 7 / 24, posKeep: true, rush: true, spotStruggleEnter: false },
|
||||
{ section: 8, phase: Phase.MidRace, endRate: 8 / 24, posKeep: true, rush: true, spotStruggleEnter: false },
|
||||
{ section: 9, phase: Phase.MidRace, endRate: 9 / 24, posKeep: true, rush: true, spotStruggleEnter: false },
|
||||
{ section: 10, phase: Phase.MidRace, endRate: 10 / 24, posKeep: true, rush: false, spotStruggleEnter: false },
|
||||
{ section: 11, phase: Phase.MidRace, endRate: 11 / 24, posKeep: false, rush: false, spotStruggleEnter: false },
|
||||
{ section: 12, phase: Phase.MidRace, endRate: 12 / 24, posKeep: false, rush: false, spotStruggleEnter: false },
|
||||
{ section: 13, phase: Phase.MidRace, endRate: 13 / 24, posKeep: false, rush: false, spotStruggleEnter: false },
|
||||
{ section: 14, phase: Phase.MidRace, endRate: 14 / 24, posKeep: false, rush: false, spotStruggleEnter: false },
|
||||
{ section: 15, phase: Phase.MidRace, endRate: 15 / 24, posKeep: false, rush: false, spotStruggleEnter: false },
|
||||
{ section: 16, phase: Phase.MidRace, endRate: 16 / 24, posKeep: false, rush: false, spotStruggleEnter: false },
|
||||
{ section: 17, phase: Phase.LateRace, endRate: 17 / 24, posKeep: false, rush: false, spotStruggleEnter: false },
|
||||
{ section: 18, phase: Phase.LateRace, endRate: 18 / 24, posKeep: false, rush: false, spotStruggleEnter: false },
|
||||
{ section: 19, phase: Phase.LateRace, endRate: 19 / 24, posKeep: false, rush: false, spotStruggleEnter: false },
|
||||
{ section: 20, phase: Phase.LateRace, endRate: 20 / 24, posKeep: false, rush: false, spotStruggleEnter: false },
|
||||
{ section: 21, phase: Phase.LateRace, endRate: 21 / 24, posKeep: false, rush: false, spotStruggleEnter: false },
|
||||
{ section: 22, phase: Phase.LateRace, endRate: 22 / 24, posKeep: false, rush: false, spotStruggleEnter: false },
|
||||
{ section: 23, phase: Phase.LateRace, endRate: 23 / 24, posKeep: false, rush: false, spotStruggleEnter: false },
|
||||
{ section: 24, phase: Phase.LateRace, endRate: 24 / 24, posKeep: false, rush: false, spotStruggleEnter: false },
|
||||
];
|
||||
|
||||
export interface SectionSlopeSegment extends SectionInfo {
|
||||
|
||||
+38
-42
@@ -8,11 +8,11 @@ import { binomPMF } from './prob';
|
||||
* Fundamental stats of umas.
|
||||
*/
|
||||
export enum Stat {
|
||||
Speed = 1,
|
||||
Stamina = 2,
|
||||
Power = 3,
|
||||
Guts = 4,
|
||||
Wit = 5,
|
||||
Speed,
|
||||
Stamina,
|
||||
Power,
|
||||
Guts,
|
||||
Wit,
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -118,15 +118,14 @@ export function distance(raceLen: number): Distance {
|
||||
/**
|
||||
* Get the distances at which each phase of a race ends.
|
||||
* @param raceLen Length of the race in meters
|
||||
* @returns Ends of each phase in meters, including separate late and spurt phases
|
||||
* @returns Ends of each phase in meters
|
||||
*/
|
||||
export function phases(raceLen: number) {
|
||||
const sixth = raceLen / 6;
|
||||
return {
|
||||
[Phase.EarlyRace]: sixth,
|
||||
[Phase.MidRace]: 4 * sixth,
|
||||
[Phase.LateRace]: 5 * sixth,
|
||||
[Phase.LastSpurt]: raceLen,
|
||||
[Phase.LateRace]: raceLen,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -134,18 +133,18 @@ export function phases(raceLen: number) {
|
||||
* Race surfaces.
|
||||
*/
|
||||
export enum Surface {
|
||||
Turf = 1,
|
||||
Dirt = 2,
|
||||
Turf,
|
||||
Dirt,
|
||||
}
|
||||
|
||||
/**
|
||||
* Race ground condition types.
|
||||
*/
|
||||
export enum GroundConditions {
|
||||
Firm = 1,
|
||||
Good = 2,
|
||||
Soft = 3,
|
||||
Heavy = 4,
|
||||
Firm,
|
||||
Good,
|
||||
Soft,
|
||||
Heavy,
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -328,12 +327,12 @@ export const RUNNING_STYLES = [
|
||||
|
||||
/**
|
||||
* Race phases.
|
||||
* While last spurt phase is also a phase, it is not distinguished here.
|
||||
*/
|
||||
export enum Phase {
|
||||
EarlyRace = 0,
|
||||
MidRace = 1,
|
||||
LateRace = 2,
|
||||
LastSpurt = 3,
|
||||
EarlyRace,
|
||||
MidRace,
|
||||
LateRace,
|
||||
}
|
||||
|
||||
const hpStrategyCoeff = {
|
||||
@@ -408,7 +407,7 @@ export function hpPerSecond(
|
||||
const pdmMod = pdm ? 0.6 : 1;
|
||||
const damMod = dam ? 0.4 : 1;
|
||||
const groundMod = groundHPRateMod[surface][conditions];
|
||||
const gutsMod = phase >= Phase.LateRace ? 1 + 200 / (math.sqrt(600 * gutsStat) as number) : 1;
|
||||
const gutsMod = phase === Phase.LateRace ? 1 + 200 / (math.sqrt(600 * gutsStat) as number) : 1;
|
||||
const sp = currentSpeed - baseSpeed(raceLen) + 12;
|
||||
return (20 / 144) * sp * sp * rushedMod * pdmMod * damMod * groundMod * gutsMod;
|
||||
}
|
||||
@@ -448,11 +447,11 @@ export function baseSpeed(raceLen: number): number {
|
||||
}
|
||||
|
||||
const speedStrategyPhaseCoeff = [
|
||||
[1.0, 0.98, 0.962, 0.962],
|
||||
[0.978, 0.991, 0.975, 0.975],
|
||||
[0.938, 0.998, 0.994, 0.994],
|
||||
[0.931, 1.0, 1.0, 1.0],
|
||||
[1.063, 0.962, 0.95, 0.95],
|
||||
[1.0, 0.98, 0.962],
|
||||
[0.978, 0.991, 0.975],
|
||||
[0.938, 0.998, 0.994],
|
||||
[0.931, 1.0, 1.0],
|
||||
[1.063, 0.962, 0.95],
|
||||
] as const;
|
||||
|
||||
const distanceProficiencyMod: Readonly<Record<AptitudeLevel, number>> = {
|
||||
@@ -472,11 +471,7 @@ const distanceProficiencyMod: Readonly<Record<AptitudeLevel, number>> = {
|
||||
* @param style Horse's running style
|
||||
* @param phase Phase to calculate for
|
||||
*/
|
||||
export function baseTargetSpeed(
|
||||
raceLen: number,
|
||||
style: RunningStyle,
|
||||
phase: Exclude<Phase, Phase.LateRace | Phase.LastSpurt>,
|
||||
): number;
|
||||
export function baseTargetSpeed(raceLen: number, style: RunningStyle, phase: Exclude<Phase, Phase.LateRace>): number;
|
||||
/**
|
||||
* Get a horse's base target speed when not spurting during late race.
|
||||
* @param raceLen Length of the race in meters
|
||||
@@ -488,7 +483,7 @@ export function baseTargetSpeed(
|
||||
export function baseTargetSpeed(
|
||||
raceLen: number,
|
||||
style: RunningStyle,
|
||||
phase: Phase.LateRace | Phase.LastSpurt,
|
||||
phase: Phase.LateRace,
|
||||
speedStat: number,
|
||||
distanceApt: AptitudeLevel,
|
||||
): number;
|
||||
@@ -502,7 +497,7 @@ export function baseTargetSpeed(
|
||||
const base = baseSpeed(raceLen);
|
||||
const baseTarget = base * speedStrategyPhaseCoeff[style][phase];
|
||||
const late =
|
||||
phase >= Phase.LateRace ? (math.sqrt(500 * speedStat!) as number) * distanceProficiencyMod[distanceApt!] * 0.002 : 0;
|
||||
phase === Phase.LateRace ? (math.sqrt(500 * speedStat!) as number) * distanceProficiencyMod[distanceApt!] * 0.002 : 0;
|
||||
return baseTarget + late;
|
||||
}
|
||||
|
||||
@@ -517,7 +512,7 @@ export function sectionSpeed(
|
||||
raceLen: number,
|
||||
witStat: number,
|
||||
style: RunningStyle,
|
||||
phase: Exclude<Phase, Phase.LateRace | Phase.LastSpurt>,
|
||||
phase: Exclude<Phase, Phase.LateRace>,
|
||||
): [number, number];
|
||||
/**
|
||||
* Calculate the range of section speed values for a horse not spurting during late race.
|
||||
@@ -534,7 +529,7 @@ export function sectionSpeed(
|
||||
witStat: number,
|
||||
style: RunningStyle,
|
||||
distance: AptitudeLevel,
|
||||
phase: Phase.LateRace | Phase.LastSpurt,
|
||||
phase: Phase.LateRace,
|
||||
): [number, number];
|
||||
export function sectionSpeed(
|
||||
raceLen: number,
|
||||
@@ -542,7 +537,7 @@ export function sectionSpeed(
|
||||
witStatOrStyle: number | RunningStyle,
|
||||
styleOrPhase: RunningStyle | Phase,
|
||||
distance?: AptitudeLevel,
|
||||
lateRace?: Phase.LateRace | Phase.LastSpurt,
|
||||
lateRace?: Phase.LateRace,
|
||||
): [number, number] {
|
||||
const speedStat = lateRace !== undefined ? speedStatOrWitStat : 0;
|
||||
const witStat = lateRace !== undefined ? witStatOrStyle : speedStatOrWitStat;
|
||||
@@ -551,7 +546,7 @@ export function sectionSpeed(
|
||||
const base = baseSpeed(raceLen);
|
||||
const baseTarget = base * speedStrategyPhaseCoeff[style][phase];
|
||||
const late =
|
||||
phase >= Phase.LateRace
|
||||
phase === Phase.LateRace
|
||||
? (math.sqrt(500 * speedStat) as number) * distanceProficiencyMod[distance ?? AptitudeLevel.A] * 0.002
|
||||
: 0;
|
||||
const u = (witStat / 550000) * math.log10(witStat * 0.1);
|
||||
@@ -625,11 +620,11 @@ export const COURSE_WIDTH = 11.25;
|
||||
export const LANE_WIDTH = COURSE_WIDTH / 18;
|
||||
|
||||
const accelStrategyPhaseCoeff = {
|
||||
[RunningStyle.FrontRunner]: [1.0, 1.0, 0.996, 0.996],
|
||||
[RunningStyle.PaceChaser]: [0.985, 1.0, 0.996, 0.996],
|
||||
[RunningStyle.LateSurger]: [0.975, 1.0, 1.0, 1.0],
|
||||
[RunningStyle.EndCloser]: [0.945, 1.0, 0.997, 0.997],
|
||||
[RunningStyle.Runaway]: [1.17, 0.94, 0.956, 0.956],
|
||||
[RunningStyle.FrontRunner]: [1.0, 1.0, 0.996],
|
||||
[RunningStyle.PaceChaser]: [0.985, 1.0, 0.996],
|
||||
[RunningStyle.LateSurger]: [0.975, 1.0, 1.0],
|
||||
[RunningStyle.EndCloser]: [0.945, 1.0, 0.997],
|
||||
[RunningStyle.Runaway]: [1.17, 0.94, 0.956],
|
||||
} as const;
|
||||
|
||||
const surfaceProficiencyMod: Readonly<Record<AptitudeLevel, number>> = {
|
||||
@@ -686,7 +681,6 @@ const phaseDecel = {
|
||||
[Phase.EarlyRace]: -1.2,
|
||||
[Phase.MidRace]: -0.8,
|
||||
[Phase.LateRace]: -1.0,
|
||||
[Phase.LastSpurt]: -1.0,
|
||||
} as const;
|
||||
|
||||
/**
|
||||
@@ -701,7 +695,9 @@ export function deceleration(phase: Phase, pdm?: boolean, dead?: boolean): numbe
|
||||
return -1.2;
|
||||
}
|
||||
if (pdm) {
|
||||
return -0.5;
|
||||
// This isn't until 1.5anni.
|
||||
// return -0.5;
|
||||
return phaseDecel[phase];
|
||||
}
|
||||
return phaseDecel[phase];
|
||||
}
|
||||
|
||||
@@ -1,223 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { asset, resolve } from '$app/paths';
|
||||
import CalcInfo from '../../race/CalcInfo.svelte';
|
||||
import Article from '../Article.svelte';
|
||||
import Sec from '../Sec.svelte';
|
||||
import * as fans from './fans';
|
||||
|
||||
let baseFans = $state(700);
|
||||
let fanBonus = $state(0);
|
||||
let finishOrder = $state(1);
|
||||
let margin = $state(0);
|
||||
let skills = $state(0);
|
||||
let glowsticks = $state(false);
|
||||
|
||||
const marginFans = $derived(fans.marginFans(baseFans, finishOrder, margin));
|
||||
const skillFans = $derived(fans.skillFans(baseFans, skills));
|
||||
const glowstickBonus = $derived(glowsticks ? 50 : 0);
|
||||
const gainedFans = $derived(fans.gained(baseFans, marginFans, skillFans, fanBonus, glowstickBonus));
|
||||
|
||||
const marginType = $derived(finishOrder === 1 ? 'Win Margin' : 'Margin vs. Winner');
|
||||
|
||||
const orderPct = [
|
||||
[1, 100],
|
||||
[2, 40],
|
||||
[3, 25],
|
||||
[4, 15],
|
||||
[5, 10],
|
||||
[6, 9],
|
||||
[7, 8],
|
||||
[8, 7],
|
||||
[9, 6],
|
||||
[10, 5],
|
||||
[11, 4],
|
||||
[12, 3],
|
||||
[13, 2.5],
|
||||
[14, 2],
|
||||
[15, 1.5],
|
||||
[16, 1],
|
||||
[17, 1],
|
||||
[18, 1],
|
||||
] as const;
|
||||
|
||||
const marginBonus = [
|
||||
['Nose – 3/4 L', 0, 9],
|
||||
['1 L – 2.5 L', 2, 6],
|
||||
['3 L – 4 L', 4, 4],
|
||||
['5 L – 6 L', 6, 2],
|
||||
['7L – distance', 9, 0],
|
||||
] as const;
|
||||
|
||||
const skillBonus = [
|
||||
['0', 0],
|
||||
['1-3', 2],
|
||||
['4-6', 3],
|
||||
['7-9', 5],
|
||||
['10+', 8],
|
||||
] as const;
|
||||
</script>
|
||||
|
||||
<Article>
|
||||
{#snippet head()}
|
||||
<Sec h={1} id="top" class="text-center pointer-coarse:mb-8">Career Fan Gain Is Solved</Sec>
|
||||
<p>
|
||||
Thanks to work by Moxa and Werseter on the Hakuraku Discord, a precise formula for fans gained from career races has been
|
||||
found.
|
||||
</p>
|
||||
<p>
|
||||
There is no random component to fan gain, except insofar as the race itself is random. It is now known to be entirely
|
||||
determined by five factors:
|
||||
</p>
|
||||
<ul class="mb-4 list-disc pl-4">
|
||||
<li>Base fans, according to the race and finish placement.</li>
|
||||
<li>Fan bonus from support cards.</li>
|
||||
<li>Margin of the win, or margin behind the winner if the player's trainee did not win.</li>
|
||||
<li>Number of skills the trainee activated during the race.</li>
|
||||
<li>In Trackblazer/MANT, use of glowsticks.</li>
|
||||
</ul>
|
||||
<p>
|
||||
The identified formula perfectly fits hundreds of recorded career race gains, including losses. Some questions remain, but
|
||||
the results so far have important implications both for fan runs and for low fan bonus decks for which debut can fall short
|
||||
of fan requirements for early career goals.
|
||||
</p>
|
||||
{/snippet}
|
||||
|
||||
<Sec h={2} id="calc-fans">Calculate Fans</Sec>
|
||||
<p>This tool will calculate your exact fan gain from a race.</p>
|
||||
<form class="mx-auto mb-4 grid grid-cols-1 gap-2 rounded-md p-4 text-center shadow-md ring md:max-w-2xl md:grid-cols-3">
|
||||
<div>
|
||||
<label for="calc-base-fans">Base Fans</label>
|
||||
<input id="calc-base-fans" class="max-w-32" type="number" min="0" max="30000" step="100" bind:value={baseFans} />
|
||||
</div>
|
||||
<div>
|
||||
<label for="calc-fan-bonus">Fan Bonus</label>
|
||||
<input id="calc-fan-bonus" class="max-w-32" type="number" min="0" max="150" step="5" bind:value={fanBonus} />
|
||||
</div>
|
||||
<div>
|
||||
<label for="calc-finish-order">Finish Order</label>
|
||||
<input id="calc-finish-order" class="max-w-32" type="number" min="1" max="18" step="1" bind:value={finishOrder} />
|
||||
</div>
|
||||
<div>
|
||||
<label for="calc-margin">{marginType} (L)</label>
|
||||
<input id="calc-margin" class="max-w-32" type="number" min="0" step="0.25" bind:value={margin} />
|
||||
</div>
|
||||
<div>
|
||||
<label for="calc-skills">Activated Skills</label>
|
||||
<input id="calc-skills" class="max-w-32" type="number" min="0" step="1" bind:value={skills} />
|
||||
</div>
|
||||
<div class="my-auto">
|
||||
<input id="calc-glowsticks" class="min-h-6 min-w-6 align-middle" type="checkbox" bind:checked={glowsticks} />
|
||||
<label for="calc-glowsticks">Glowsticks</label>
|
||||
</div>
|
||||
</form>
|
||||
<div class="grid grid-cols-1 md:grid-cols-12">
|
||||
<CalcInfo title="Fans from Margin" class="md:col-span-4 md:col-start-3">{marginFans}</CalcInfo>
|
||||
<CalcInfo title="Fans from Skills" class="md:col-span-4">{skillFans}</CalcInfo>
|
||||
<CalcInfo title="Fans Gained" class="md:col-span-4 md:col-start-5">{gainedFans}</CalcInfo>
|
||||
</div>
|
||||
|
||||
<Sec h={2} id="base-fans">Base Fans</Sec>
|
||||
<p>
|
||||
Base fans is a parameter per race and finish placement, defined in <a href={resolve('/doc/mdb')}>master data</a>. The number
|
||||
shown in the career race selection menu is the base fans for first place. While it is defined separately for each race and
|
||||
finish order, every race currently follows the same curve for all placements.
|
||||
</p>
|
||||
<table class="mx-auto mb-4 border text-center">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col" class="px-2">Placement</th>
|
||||
<th scope="col" class="px-2">Fan Ratio</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{#each orderPct as [o, p] (o)}
|
||||
<tr class="even:bg-mist-300 dark:even:bg-mist-900">
|
||||
<td>{o}</td>
|
||||
<td>{p}%</td>
|
||||
</tr>
|
||||
{/each}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<Sec h={2} id="margin">Fans from Margin</Sec>
|
||||
<p>
|
||||
When the trainee places in the top three (top five?), she receives a bonus percentage of base fans according to the finish
|
||||
margin.
|
||||
</p>
|
||||
<p>
|
||||
The bonus depends on whether the trainee won the race. If so, it is calculated by her margin versus second place. Otherwise,
|
||||
her total margin behind the winner is used. If she did not place in the top three (top five?), then she gains no fans from
|
||||
margin.
|
||||
</p>
|
||||
<table class="mx-auto mb-4 border text-center">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col" class="px-2">Actual<br />Margin Within</th>
|
||||
<th scope="col" class="px-2">Bonus for Winner</th>
|
||||
<th scope="col" class="px-2">Bonus for Podium</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{#each marginBonus as [m, w, l] (m)}
|
||||
<tr class="even:bg-mist-300 dark:even:bg-mist-900">
|
||||
<td>{m}</td>
|
||||
<td>{w}%</td>
|
||||
<td>{l}%</td>
|
||||
</tr>
|
||||
{/each}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<Sec h={2} id="skills">Fans from Skill Activations</Sec>
|
||||
<p>The trainee receives a bonus percentage of base fans according to the number of skills she activated during the race.</p>
|
||||
<table class="mx-auto mb-4 border text-center">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col" class="px-2">Activated Skills</th>
|
||||
<th scope="col" class="px-2">Bonus</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{#each skillBonus as [n, b] (n)}
|
||||
<tr class="even:bg-mist-300 dark:even:bg-mist-900">
|
||||
<td>{n}</td>
|
||||
<td>{b}%</td>
|
||||
</tr>
|
||||
{/each}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<Sec h={2} id="rounding">Calculation Order and Rounding</Sec>
|
||||
<p>The calculation is perfomed as if in the following order.</p>
|
||||
<ol class="mb-4 list-decimal pl-4">
|
||||
<li>Calculate fans from win margin and round down.</li>
|
||||
<li>Calculate fans from skill activation count and round down.</li>
|
||||
<li>Calculate the sum of base fans, win margin fans, and skill activation fans.</li>
|
||||
<li>Apply fan bonus to the sum and round to the nearest integer with halfway cases away from zero.</li>
|
||||
<li>(Trackblazer/MANT) If glowsticks were used for the race, apply their multiplier and round down.</li>
|
||||
</ol>
|
||||
|
||||
<Sec h={2} id="data">Data and Limitations</Sec>
|
||||
<p>
|
||||
The data for this research comes from 193 races and fan results dumped using <a
|
||||
href="https://werseter.github.io/umadump/"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer">Umadump</a
|
||||
>. The races come from sixteen careers run between 19 Aug and 11 Oct 2026. These careers include five different values of race
|
||||
bonus ranging from 55 to 105. The identified formula predicts the actual fan gain perfectly for all of the races. You can
|
||||
<a
|
||||
href="https://docs.google.com/spreadsheets/d/1_jtOv6qk9dP4jJTagJqG53Pvj48IVd6gl2zKpyT6Kvs/edit?gid=463816108#gid=463816108"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer">view the data</a
|
||||
>.
|
||||
</p>
|
||||
<p>
|
||||
There are some gaps in the data. No collected race had a finish placement of 4th or 5th, nor lower than 6th, so the exact
|
||||
details of the win margin fan bonus remain open. Similarly, no race featured a trainee who activated more than ten skills, so
|
||||
it is unknown whether there are further tiers of skill activation count fan bonus.
|
||||
</p>
|
||||
<p>
|
||||
Once these edge cases are covered, this article will be updated. If you'd like to contribute, please contact me
|
||||
(@zephyrtronium) via Discord on the Hakuraku, uma.moe, GameTora, or EoG servers.
|
||||
</p>
|
||||
</Article>
|
||||
@@ -1,58 +0,0 @@
|
||||
const margins = [
|
||||
[0.75, 0.0, 0.09],
|
||||
[2.5, 0.02, 0.06],
|
||||
[4, 0.04, 0.04],
|
||||
[6, 0.06, 0.02],
|
||||
[Infinity, 0.09, 0.0],
|
||||
] as const;
|
||||
|
||||
/**
|
||||
* Calculate fans gained from a career race due to the margin of the finish.
|
||||
* @param baseFans Base fans from the race and finish order
|
||||
* @param finishOrder Placement of the trainee
|
||||
* @param margin Margin in lengths between the trainee and second if a win, or the winner and trainee if a loss
|
||||
* @returns Fans gained due to finish margin
|
||||
*/
|
||||
export function marginFans(baseFans: number, finishOrder: number, margin: number): number {
|
||||
if (finishOrder > 3) {
|
||||
return 0;
|
||||
}
|
||||
const [, wb, lb] = margins.find(([m]) => margin < m)!;
|
||||
const b = finishOrder === 1 ? wb : lb;
|
||||
return Math.floor(baseFans * b);
|
||||
}
|
||||
|
||||
const skills = [
|
||||
[0, 0.0],
|
||||
[3, 0.02],
|
||||
[6, 0.03],
|
||||
[9, 0.05],
|
||||
[Infinity, 0.08],
|
||||
] as const;
|
||||
|
||||
/**
|
||||
* Calculate the fans gained from a career race due to skills activated.
|
||||
* @param baseFans Base fans from the race and finish order
|
||||
* @param skillsActivated Number of skills the trainee activated during the race
|
||||
* @returns Fans gained due to skill activations
|
||||
*/
|
||||
export function skillFans(baseFans: number, skillsActivated: number): number {
|
||||
const [, b] = skills.find(([n]) => skillsActivated <= n)!;
|
||||
return Math.floor(baseFans * b);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate fan gain from a career race.
|
||||
* @param baseFans Base fans from the race and finish order
|
||||
* @param marginFans Fans gained due to finish margin
|
||||
* @param skillFans Fans gained due to skill activations
|
||||
* @param fanBonus Total fan bonus from support cards
|
||||
* @param itemBonus Fan bonus from items (MANT glowsticks)
|
||||
* @returns Total fans gained
|
||||
*/
|
||||
export function gained(baseFans: number, marginFans: number, skillFans: number, fanBonus: number, itemBonus?: number): number {
|
||||
const f = baseFans + marginFans + skillFans;
|
||||
const d = Math.round(f * (1 + fanBonus / 100));
|
||||
const b = itemBonus ?? 0;
|
||||
return Math.floor(d * (1 + b / 100));
|
||||
}
|
||||
@@ -99,8 +99,8 @@
|
||||
const distance = $derived([last(seggs[0]).end.x, last(seggs[1]).end.x] as const);
|
||||
const time = $derived([last(seggs[0]).end.t, last(seggs[1]).end.t] as const);
|
||||
const finishTime = $derived([
|
||||
time[0] + (raceLen - distance[0]) / spurtSpeed,
|
||||
time[1] + (raceLen - distance[1]) / spurtSpeed,
|
||||
time[0] + (phases[race.Phase.LateRace] - distance[0]) / spurtSpeed,
|
||||
time[1] + (phases[race.Phase.LateRace] - distance[1]) / spurtSpeed,
|
||||
] as const);
|
||||
function gainLengths(fast: accel.Instant, slow: accel.Instant): number {
|
||||
const m = fast.v * (slow.t - fast.t) + fast.x - slow.x;
|
||||
|
||||
@@ -103,10 +103,6 @@
|
||||
race.spurtSpeed(stats[race.Stat.Speed], stats[race.Stat.Guts], style, distanceApt, raceLen),
|
||||
race.spurtSpeed(stats[race.Stat.Speed], stats[race.Stat.Guts], style, distanceApt, raceLen),
|
||||
] as [number, number],
|
||||
[race.Phase.LastSpurt]: [
|
||||
race.spurtSpeed(stats[race.Stat.Speed], stats[race.Stat.Guts], style, distanceApt, raceLen),
|
||||
race.spurtSpeed(stats[race.Stat.Speed], stats[race.Stat.Guts], style, distanceApt, raceLen),
|
||||
] as [number, number],
|
||||
});
|
||||
|
||||
// const isFront = $derived(style === race.RunningStyle.FrontRunner || style === race.RunningStyle.Runaway);
|
||||
|
||||
@@ -1,194 +0,0 @@
|
||||
career,turn,scenario,character,race,program_id,place,base_fans,deck_bonus_pct,margin_lengths,skill_activations,margin_bonus_pct,skill_bonus_pct,item_bonus_pct,deck_only_fans,actual_fans,extra_fans,extra_pct,predicted_fans,residual_fans
|
||||
2026-08-19 20:37:27,12,3,[Ashen Miracle] Oguri Cap,Junior Make Debut,1070,1,700,55,4.4727,0,6,0,0,1085.0,1150,65.0,5.9908,1150,0
|
||||
2026-08-19 20:37:27,23,3,[Ashen Miracle] Oguri Cap,Hanshin Juvenile Fillies,623,6,585,55,1.2941,0,0,0,0,906.75,906,-0.75,-0.0827,906,0
|
||||
2026-08-19 20:37:27,24,3,[Ashen Miracle] Oguri Cap,Hopeful Stakes,625,1,7000,55,1.1495,0,2,0,0,10850.0,11067,217.0,2.0,11067,0
|
||||
2026-08-19 20:37:27,33,3,[Ashen Miracle] Oguri Cap,NHK Mile Cup,164,1,10500,55,1.6165,0,2,0,0,16275.0,16601,326.0,2.0031,16601,0
|
||||
2026-08-19 20:37:27,46,3,[Ashen Miracle] Oguri Cap,Mile Championship,78,1,11000,55,5.3966,5,6,3,0,17050.0,18585,1535.0,9.0029,18585,0
|
||||
2026-08-19 20:37:27,48,3,[Ashen Miracle] Oguri Cap,Arima Kinen,81,1,30000,55,8.8065,6,9,3,0,46500.0,52080,5580.0,12.0,52080,0
|
||||
2026-08-19 20:37:27,54,3,[Ashen Miracle] Oguri Cap,Osaka Hai,3,1,13500,55,3.3853,4,4,3,0,20925.0,22390,1465.0,7.0012,22390,0
|
||||
2026-08-19 20:37:27,57,3,[Ashen Miracle] Oguri Cap,Victoria Mile,5,1,10500,55,4.8613,2,6,2,0,16275.0,17577,1302.0,8.0,17577,0
|
||||
2026-08-19 20:37:27,68,3,[Ashen Miracle] Oguri Cap,Tenno Sho (Autumn),76,1,15000,55,14.0515,6,9,3,0,23250.0,26040,2790.0,12.0,26040,0
|
||||
2026-08-19 20:37:27,72,3,[Ashen Miracle] Oguri Cap,Arima Kinen,81,1,30000,55,10.5066,7,9,5,0,46500.0,53010,6510.0,14.0,53010,0
|
||||
2026-08-19 20:37:27,74,3,[Ashen Miracle] Oguri Cap,URA Finale Qualifier,2015,1,7000,55,6.1557,6,9,3,0,10850.0,12152,1302.0,12.0,12152,0
|
||||
2026-08-19 20:37:27,76,3,[Ashen Miracle] Oguri Cap,URA Finale Semifinal,2112,1,10000,55,6.906,7,9,5,0,15500.0,17670,2170.0,14.0,17670,0
|
||||
2026-08-19 20:37:27,78,3,[Ashen Miracle] Oguri Cap,URA Finale Finals,2213,1,30000,55,7.8077,8,9,5,0,46500.0,53010,6510.0,14.0,53010,0
|
||||
2026-09-08 21:05:53,12,3,[Ashen Miracle] Oguri Cap,Junior Make Debut,1070,1,700,70,2.4747,0,2,0,0,1190.0,1213,23.0,1.9328,1213,0
|
||||
2026-09-08 21:05:53,23,3,[Ashen Miracle] Oguri Cap,Hanshin Juvenile Fillies,623,3,1625,70,2.5458,0,4,0,0,2762.5,2873,110.5,4.0,2873,0
|
||||
2026-09-08 21:05:53,32,3,[Ashen Miracle] Oguri Cap,Flora Stakes,173,1,5200,70,0.166,0,0,0,0,8840.0,8840,0.0,0.0,8840,0
|
||||
2026-09-08 21:05:53,33,3,[Ashen Miracle] Oguri Cap,NHK Mile Cup,164,1,10500,70,0.6636,0,0,0,0,17850.0,17850,0.0,0.0,17850,0
|
||||
2026-09-08 21:05:53,46,3,[Ashen Miracle] Oguri Cap,Mile Championship,78,1,11000,70,0.3634,0,0,0,0,18700.0,18700,0.0,0.0,18700,0
|
||||
2026-09-08 21:05:53,48,3,[Ashen Miracle] Oguri Cap,Arima Kinen,81,1,30000,70,3.9924,0,4,0,0,51000.0,53040,2040.0,4.0,53040,0
|
||||
2026-09-08 21:05:53,54,3,[Ashen Miracle] Oguri Cap,Osaka Hai,3,1,13500,70,3.7863,0,4,0,0,22950.0,23868,918.0,4.0,23868,0
|
||||
2026-09-08 21:05:53,57,3,[Ashen Miracle] Oguri Cap,Victoria Mile,5,1,10500,70,1.4385,0,2,0,0,17850.0,18207,357.0,2.0,18207,0
|
||||
2026-09-08 21:05:53,68,3,[Ashen Miracle] Oguri Cap,Tenno Sho (Autumn),76,1,15000,70,3.9784,0,4,0,0,25500.0,26520,1020.0,4.0,26520,0
|
||||
2026-09-08 21:05:53,72,3,[Ashen Miracle] Oguri Cap,Arima Kinen,81,1,30000,70,7.8092,0,9,0,0,51000.0,55590,4590.0,9.0,55590,0
|
||||
2026-09-08 21:05:53,74,3,[Ashen Miracle] Oguri Cap,URA Finale Qualifier,2011,1,7000,70,4.2694,0,6,0,0,11900.0,12614,714.0,6.0,12614,0
|
||||
2026-09-08 21:05:53,76,3,[Ashen Miracle] Oguri Cap,URA Finale Semifinal,2108,1,10000,70,1.7312,0,2,0,0,17000.0,17340,340.0,2.0,17340,0
|
||||
2026-09-08 21:05:53,78,3,[Ashen Miracle] Oguri Cap,URA Finale Finals,2209,1,30000,70,2.9177,0,4,0,0,51000.0,53040,2040.0,4.0,53040,0
|
||||
2026-09-16 13:24:41,12,3,[Ashen Miracle] Oguri Cap,Junior Make Debut,1070,1,700,70,6.3564,0,9,0,0,1190.0,1297,107.0,8.9916,1297,0
|
||||
2026-09-16 13:24:41,23,3,[Ashen Miracle] Oguri Cap,Hanshin Juvenile Fillies,623,1,6500,70,0.5447,0,0,0,0,11050.0,11050,0.0,0.0,11050,0
|
||||
2026-09-16 13:24:41,33,3,[Ashen Miracle] Oguri Cap,NHK Mile Cup,164,1,10500,70,1.2208,0,2,0,0,17850.0,18207,357.0,2.0,18207,0
|
||||
2026-09-16 13:24:41,46,3,[Ashen Miracle] Oguri Cap,Mile Championship,78,1,11000,70,1.8764,0,2,0,0,18700.0,19074,374.0,2.0,19074,0
|
||||
2026-09-16 13:24:41,48,3,[Ashen Miracle] Oguri Cap,Arima Kinen,81,1,30000,70,7.2065,0,9,0,0,51000.0,55590,4590.0,9.0,55590,0
|
||||
2026-09-16 13:24:41,54,3,[Ashen Miracle] Oguri Cap,Osaka Hai,3,1,13500,70,9.1057,0,9,0,0,22950.0,25016,2066.0,9.0022,25016,0
|
||||
2026-09-16 13:24:41,59,3,[Ashen Miracle] Oguri Cap,Yasuda Kinen,73,1,13000,70,4.3705,0,6,0,0,22100.0,23426,1326.0,6.0,23426,0
|
||||
2026-09-16 13:24:41,68,3,[Ashen Miracle] Oguri Cap,Tenno Sho (Autumn),76,1,15000,70,7.933,0,9,0,0,25500.0,27795,2295.0,9.0,27795,0
|
||||
2026-09-16 13:24:41,72,3,[Ashen Miracle] Oguri Cap,Arima Kinen,81,1,30000,70,5.7931,0,6,0,0,51000.0,54060,3060.0,6.0,54060,0
|
||||
2026-09-16 13:24:41,74,3,[Ashen Miracle] Oguri Cap,URA Finale Qualifier,2010,1,7000,70,5.7824,0,6,0,0,11900.0,12614,714.0,6.0,12614,0
|
||||
2026-09-16 13:24:41,76,3,[Ashen Miracle] Oguri Cap,URA Finale Semifinal,2112,1,10000,70,2.7856,0,4,0,0,17000.0,17680,680.0,4.0,17680,0
|
||||
2026-09-16 13:24:41,78,3,[Ashen Miracle] Oguri Cap,URA Finale Finals,2208,1,30000,70,5.4409,0,6,0,0,51000.0,54060,3060.0,6.0,54060,0
|
||||
2026-09-17 20:23:11,12,3,[Ashen Miracle] Oguri Cap,Junior Make Debut,1070,1,700,70,5.3797,0,6,0,0,1190.0,1261,71.0,5.9664,1261,0
|
||||
2026-09-17 20:23:11,24,3,[Ashen Miracle] Oguri Cap,Hopeful Stakes,625,1,7000,70,4.8411,2,6,2,0,11900.0,12852,952.0,8.0,12852,0
|
||||
2026-09-17 20:23:11,33,3,[Ashen Miracle] Oguri Cap,NHK Mile Cup,164,1,10500,70,3.0921,1,4,2,0,17850.0,18921,1071.0,6.0,18921,0
|
||||
2026-09-17 20:23:11,46,3,[Ashen Miracle] Oguri Cap,Mile Championship,78,1,11000,70,3.4861,1,4,2,0,18700.0,19822,1122.0,6.0,19822,0
|
||||
2026-09-17 20:23:11,48,3,[Ashen Miracle] Oguri Cap,Arima Kinen,81,1,30000,70,8.0058,1,9,2,0,51000.0,56610,5610.0,11.0,56610,0
|
||||
2026-09-17 20:23:11,51,3,[Ashen Miracle] Oguri Cap,Kawasaki Kinen,1106,1,6000,70,5.6972,1,6,2,0,10200.0,11016,816.0,8.0,11016,0
|
||||
2026-09-17 20:23:11,52,3,[Ashen Miracle] Oguri Cap,February Stakes,1,1,10000,70,0.5589,1,0,2,0,17000.0,17340,340.0,2.0,17340,0
|
||||
2026-09-17 20:23:11,68,3,[Ashen Miracle] Oguri Cap,Tenno Sho (Autumn),76,1,15000,70,4.6895,2,6,2,0,25500.0,27540,2040.0,8.0,27540,0
|
||||
2026-09-17 20:23:11,72,3,[Ashen Miracle] Oguri Cap,Arima Kinen,81,1,30000,70,9.0895,1,9,2,0,51000.0,56610,5610.0,11.0,56610,0
|
||||
2026-09-17 20:23:11,74,3,[Ashen Miracle] Oguri Cap,URA Finale Qualifier,2014,1,7000,70,6.1828,1,9,2,0,11900.0,13209,1309.0,11.0,13209,0
|
||||
2026-09-17 20:23:11,76,3,[Ashen Miracle] Oguri Cap,URA Finale Semifinal,2111,1,10000,70,2.8136,1,4,2,0,17000.0,18020,1020.0,6.0,18020,0
|
||||
2026-09-17 20:23:11,78,3,[Ashen Miracle] Oguri Cap,URA Finale Finals,2212,1,30000,70,1.061,1,2,2,0,51000.0,53040,2040.0,4.0,53040,0
|
||||
2026-09-17 22:45:31,12,3,[Ashen Miracle] Oguri Cap,Junior Make Debut,1070,1,700,70,2.2776,1,2,2,0,1190.0,1237,47.0,3.9496,1237,0
|
||||
2026-09-17 22:45:31,23,3,[Ashen Miracle] Oguri Cap,Hanshin Juvenile Fillies,623,2,2600,70,0.0231,0,9,0,0,4420.0,4817,397.0,8.9819,4817,0
|
||||
2026-09-17 22:45:31,33,3,[Ashen Miracle] Oguri Cap,NHK Mile Cup,164,1,10500,70,2.7073,2,4,2,0,17850.0,18921,1071.0,6.0,18921,0
|
||||
2026-09-17 22:45:31,46,3,[Ashen Miracle] Oguri Cap,Mile Championship,78,1,11000,70,1.9154,2,2,2,0,18700.0,19448,748.0,4.0,19448,0
|
||||
2026-09-17 22:45:31,48,3,[Ashen Miracle] Oguri Cap,Arima Kinen,81,1,30000,70,4.8206,2,6,2,0,51000.0,55080,4080.0,8.0,55080,0
|
||||
2026-09-17 22:45:31,56,3,[Ashen Miracle] Oguri Cap,Tenno Sho (Spring),4,1,15000,70,14.398,5,9,3,0,25500.0,28560,3060.0,12.0,28560,0
|
||||
2026-09-17 22:45:31,57,3,[Ashen Miracle] Oguri Cap,Victoria Mile,5,1,10500,70,2.1273,3,2,2,0,17850.0,18564,714.0,4.0,18564,0
|
||||
2026-09-17 22:45:31,68,3,[Ashen Miracle] Oguri Cap,Tenno Sho (Autumn),76,1,15000,70,7.6906,5,9,3,0,25500.0,28560,3060.0,12.0,28560,0
|
||||
2026-09-17 22:45:31,72,3,[Ashen Miracle] Oguri Cap,Arima Kinen,81,1,30000,70,7.549,6,9,3,0,51000.0,57120,6120.0,12.0,57120,0
|
||||
2026-09-17 22:45:31,74,3,[Ashen Miracle] Oguri Cap,URA Finale Qualifier,2014,1,7000,70,6.4801,3,9,2,0,11900.0,13209,1309.0,11.0,13209,0
|
||||
2026-09-17 22:45:31,76,3,[Ashen Miracle] Oguri Cap,URA Finale Semifinal,2109,1,10000,70,5.9469,7,6,5,0,17000.0,18870,1870.0,11.0,18870,0
|
||||
2026-09-17 22:45:31,78,3,[Ashen Miracle] Oguri Cap,URA Finale Finals,2212,1,30000,70,7.2892,5,9,3,0,51000.0,57120,6120.0,12.0,57120,0
|
||||
2026-09-19 20:01:05,12,3,[Ashen Miracle] Oguri Cap,Junior Make Debut,1070,1,700,70,6.6525,0,9,0,0,1190.0,1297,107.0,8.9916,1297,0
|
||||
2026-09-19 20:01:05,23,3,[Ashen Miracle] Oguri Cap,Asahi Hai Futurity Stakes,624,3,1750,70,1.5022,0,6,0,0,2975.0,3153,178.0,5.9832,3153,0
|
||||
2026-09-19 20:01:05,24,3,[Ashen Miracle] Oguri Cap,Hopeful Stakes,625,1,7000,70,5.6931,0,6,0,0,11900.0,12614,714.0,6.0,12614,0
|
||||
2026-09-19 20:01:05,33,3,[Ashen Miracle] Oguri Cap,NHK Mile Cup,164,1,10500,70,1.488,0,2,0,0,17850.0,18207,357.0,2.0,18207,0
|
||||
2026-09-19 20:01:05,46,3,[Ashen Miracle] Oguri Cap,Mile Championship,78,1,11000,70,3.8134,0,4,0,0,18700.0,19448,748.0,4.0,19448,0
|
||||
2026-09-19 20:01:05,48,3,[Ashen Miracle] Oguri Cap,Arima Kinen,81,1,30000,70,2.6633,0,4,0,0,51000.0,53040,2040.0,4.0,53040,0
|
||||
2026-09-19 20:01:05,52,3,[Ashen Miracle] Oguri Cap,February Stakes,1,1,10000,70,0.6637,0,0,0,0,17000.0,17000,0.0,0.0,17000,0
|
||||
2026-09-19 20:01:05,59,3,[Ashen Miracle] Oguri Cap,Yasuda Kinen,73,1,13000,70,5.9364,0,6,0,0,22100.0,23426,1326.0,6.0,23426,0
|
||||
2026-09-19 20:01:05,68,3,[Ashen Miracle] Oguri Cap,Tenno Sho (Autumn),76,1,15000,70,6.0564,0,9,0,0,25500.0,27795,2295.0,9.0,27795,0
|
||||
2026-09-19 20:01:05,72,3,[Ashen Miracle] Oguri Cap,Arima Kinen,81,1,30000,70,7.8703,0,9,0,0,51000.0,55590,4590.0,9.0,55590,0
|
||||
2026-09-19 20:01:05,74,3,[Ashen Miracle] Oguri Cap,URA Finale Qualifier,2015,1,7000,70,5.8192,0,6,0,0,11900.0,12614,714.0,6.0,12614,0
|
||||
2026-09-19 20:01:05,76,3,[Ashen Miracle] Oguri Cap,URA Finale Semifinal,2111,1,10000,70,5.2726,0,6,0,0,17000.0,18020,1020.0,6.0,18020,0
|
||||
2026-09-19 20:01:05,78,3,[Ashen Miracle] Oguri Cap,URA Finale Finals,2213,1,30000,70,5.6513,0,6,0,0,51000.0,54060,3060.0,6.0,54060,0
|
||||
2026-09-19 21:45:59,12,2,[Blossom in Learning] Sakura Bakushin O,Junior Make Debut,676,1,700,65,1.7631,0,2,0,0,1155.0,1178,23.0,1.9913,1178,0
|
||||
2026-09-19 21:45:59,21,2,[Blossom in Learning] Sakura Bakushin O,Keio Hai Junior Stakes,626,1,3800,65,0.917,0,2,0,0,6270.0,6395,125.0,1.9936,6395,0
|
||||
2026-09-19 21:45:59,30,2,[Blossom in Learning] Sakura Bakushin O,Spring Stakes,171,2,2160,65,0.7907,1,6,2,0,3564.0,3847,283.0,7.9405,3847,0
|
||||
2026-09-19 21:45:59,34,2,[Blossom in Learning] Sakura Bakushin O,Aoi Stakes,210,2,1520,65,1.5851,0,6,0,0,2508.0,2658,150.0,5.9809,2658,0
|
||||
2026-09-19 21:45:59,35,2,[Blossom in Learning] Sakura Bakushin O,Epsom Cup,95,1,4100,65,1.3364,1,2,2,0,6765.0,7035,270.0,3.9911,7035,0
|
||||
2026-09-19 21:45:59,42,2,[Blossom in Learning] Sakura Bakushin O,Sprinters Stakes,75,3,3250,65,0.8705,0,6,0,0,5362.5,5684,321.5,5.9953,5684,0
|
||||
2026-09-19 21:45:59,54,2,[Blossom in Learning] Sakura Bakushin O,Takamatsunomiya Kinen,2,2,5200,65,0.451,0,9,0,0,8580.0,9352,772.0,8.9977,9352,0
|
||||
2026-09-19 21:45:59,60,2,[Blossom in Learning] Sakura Bakushin O,Hakodate Sprint Stakes,96,1,3900,65,0.9971,0,2,0,0,6435.0,6563,128.0,1.9891,6563,0
|
||||
2026-09-19 21:45:59,61,2,[Blossom in Learning] Sakura Bakushin O,CBC Sho,97,1,3900,65,2.3483,0,2,0,0,6435.0,6563,128.0,1.9891,6563,0
|
||||
2026-09-19 21:45:59,65,2,[Blossom in Learning] Sakura Bakushin O,Centaur Stakes,83,1,5900,65,1.5096,0,2,0,0,9735.0,9929,194.0,1.9928,9929,0
|
||||
2026-09-19 21:45:59,66,2,[Blossom in Learning] Sakura Bakushin O,Sprinters Stakes,75,6,1170,65,2.5098,1,0,2,0,1930.5,1968,37.5,1.9425,1968,0
|
||||
2026-09-23 23:41:07,12,1,[Blossom in Learning] Sakura Bakushin O,Junior Make Debut,676,1,700,65,2.7319,0,4,0,0,1155.0,1201,46.0,3.9827,1201,0
|
||||
2026-09-23 23:41:07,21,1,[Blossom in Learning] Sakura Bakushin O,Keio Hai Junior Stakes,626,1,3800,65,0.5164,0,0,0,0,6270.0,6270,0.0,0.0,6270,0
|
||||
2026-09-23 23:41:07,30,1,[Blossom in Learning] Sakura Bakushin O,Spring Stakes,171,1,5400,65,2.4521,1,2,2,0,8910.0,9266,356.0,3.9955,9266,0
|
||||
2026-09-23 23:41:07,34,1,[Blossom in Learning] Sakura Bakushin O,Aoi Stakes,210,1,3800,65,0.4297,0,0,0,0,6270.0,6270,0.0,0.0,6270,0
|
||||
2026-09-23 23:41:07,42,1,[Blossom in Learning] Sakura Bakushin O,Sprinters Stakes,75,6,1170,65,3.8513,0,0,0,0,1930.5,1930,-0.5,-0.0259,1930,0
|
||||
2026-09-28 14:22:08,12,1,[Hot☆Summer Night] Maruzensky,Junior Make Debut,676,1,700,65,0.2547,0,0,0,0,1155.0,1155,0.0,0.0,1155,0
|
||||
2026-10-01 09:39:24,12,2,[Wild Frontier] Taiki Shuttle,Junior Make Debut,846,1,700,80,2.9274,1,4,2,0,1260.0,1335,75.0,5.9524,1335,0
|
||||
2026-10-01 09:39:24,25,2,[Wild Frontier] Taiki Shuttle,Shinzan Kinen,178,1,3800,80,3.297,0,4,0,0,6840.0,7113,273.0,3.9912,7113,0
|
||||
2026-10-01 09:39:24,33,2,[Wild Frontier] Taiki Shuttle,NHK Mile Cup,164,1,10500,80,1.5041,0,2,0,0,18900.0,19278,378.0,2.0,19278,0
|
||||
2026-10-01 09:39:24,36,2,[Wild Frontier] Taiki Shuttle,Unicorn Stakes,189,1,3500,80,0.0829,0,0,0,0,6300.0,6300,0.0,0.0,6300,0
|
||||
2026-10-01 09:39:24,46,2,[Wild Frontier] Taiki Shuttle,Mile Championship,78,1,11000,80,1.3511,1,2,2,0,19800.0,20592,792.0,4.0,20592,0
|
||||
2026-10-01 09:39:24,59,2,[Wild Frontier] Taiki Shuttle,Yasuda Kinen,73,1,13000,80,4.4812,0,6,0,0,23400.0,24804,1404.0,6.0,24804,0
|
||||
2026-10-01 09:39:24,66,2,[Wild Frontier] Taiki Shuttle,Sprinters Stakes,75,1,13000,80,3.6966,1,4,2,0,23400.0,24804,1404.0,6.0,24804,0
|
||||
2026-10-01 09:39:24,70,2,[Wild Frontier] Taiki Shuttle,Mile Championship,78,1,11000,80,3.6774,0,4,0,0,19800.0,20592,792.0,4.0,20592,0
|
||||
2026-10-01 09:39:24,74,2,[Wild Frontier] Taiki Shuttle,URA Finale Qualifier,2013,1,7000,80,4.0671,0,6,0,0,12600.0,13356,756.0,6.0,13356,0
|
||||
2026-10-01 09:39:24,76,2,[Wild Frontier] Taiki Shuttle,URA Finale Semifinal,2109,1,10000,80,4.9632,1,6,2,0,18000.0,19440,1440.0,8.0,19440,0
|
||||
2026-10-01 09:39:24,78,2,[Wild Frontier] Taiki Shuttle,URA Finale Finals,2214,1,30000,80,1.1329,0,2,0,0,54000.0,55080,1080.0,2.0,55080,0
|
||||
2026-10-04 14:36:48,12,4,[Wild Frontier] Taiki Shuttle,Junior Make Debut,846,1,700,105,2.6127,1,4,2,0,1435.0,1521,86.0,5.993,1521,0
|
||||
2026-10-04 14:36:48,14,4,[Wild Frontier] Taiki Shuttle,Hakodate Junior Stakes,628,1,3100,105,4.3676,0,6,0,0,6355.0,6736,381.0,5.9953,6736,0
|
||||
2026-10-04 14:36:48,16,4,[Wild Frontier] Taiki Shuttle,Niigata Junior Stakes,629,1,3100,105,2.8947,0,4,0,0,6355.0,6609,254.0,3.9969,6609,0
|
||||
2026-10-04 14:36:48,17,4,[Wild Frontier] Taiki Shuttle,Sapporo Junior Stakes,630,1,3100,105,2.2675,1,2,2,0,6355.0,6609,254.0,3.9969,6609,0
|
||||
2026-10-04 14:36:48,21,4,[Wild Frontier] Taiki Shuttle,Keio Hai Junior Stakes,626,1,3800,105,1.8215,1,2,2,0,7790.0,8101,311.0,3.9923,8101,0
|
||||
2026-10-04 14:36:48,22,4,[Wild Frontier] Taiki Shuttle,Tokyo Sports Hai Junior Stakes,635,1,3300,105,4.1972,0,6,0,0,6765.0,7170,405.0,5.9867,7170,0
|
||||
2026-10-04 14:36:48,23,4,[Wild Frontier] Taiki Shuttle,Hanshin Juvenile Fillies,623,1,6500,105,4.4133,1,6,2,0,13325.0,14391,1066.0,8.0,14391,0
|
||||
2026-10-04 14:36:48,24,4,[Wild Frontier] Taiki Shuttle,Zen-Nippon Junior Yushun,1107,2,1680,105,0.6647,1,9,2,0,3444.0,3821,377.0,10.9466,3821,0
|
||||
2026-10-04 14:36:48,27,4,[Wild Frontier] Taiki Shuttle,Kyodo News Hai,183,1,3800,105,3.9819,1,4,2,0,7790.0,8257,467.0,5.9949,8257,0
|
||||
2026-10-04 14:36:48,29,4,[Wild Frontier] Taiki Shuttle,Fillies' Revue,170,2,2080,105,0.056,1,9,2,0,4264.0,4731,467.0,10.9522,4731,0
|
||||
2026-10-04 14:36:48,30,4,[Wild Frontier] Taiki Shuttle,Spring Stakes,171,1,5400,105,1.5715,1,2,2,0,11070.0,11513,443.0,4.0018,11513,0
|
||||
2026-10-04 14:36:48,31,4,[Wild Frontier] Taiki Shuttle,Oka Sho,162,1,10500,105,2.0067,0,2,0,0,21525.0,21956,431.0,2.0023,21956,0
|
||||
2026-10-04 14:36:48,33,4,[Wild Frontier] Taiki Shuttle,NHK Mile Cup,164,1,10500,105,1.3928,0,2,0,0,21525.0,21956,431.0,2.0023,21956,0
|
||||
2026-10-04 14:36:48,35,4,[Wild Frontier] Taiki Shuttle,Yasuda Kinen,73,1,13000,105,1.2662,1,2,2,0,26650.0,27716,1066.0,4.0,27716,0
|
||||
2026-10-04 14:36:48,39,4,[Wild Frontier] Taiki Shuttle,Sekiya Kinen,105,1,3900,105,3.8619,0,4,0,0,7995.0,8314,319.0,3.99,8314,0
|
||||
2026-10-04 14:36:48,41,4,[Wild Frontier] Taiki Shuttle,Rose Stakes,176,1,5200,105,3.3191,1,4,2,0,10660.0,11300,640.0,6.0038,11300,0
|
||||
2026-10-04 14:36:48,42,4,[Wild Frontier] Taiki Shuttle,Sprinters Stakes,75,1,13000,105,3.2007,1,4,2,0,26650.0,28249,1599.0,6.0,28249,0
|
||||
2026-10-04 14:36:48,43,4,[Wild Frontier] Taiki Shuttle,M.C. Nambu Hai,1109,1,6000,105,2.7546,0,4,0,0,12300.0,12792,492.0,4.0,12792,0
|
||||
2026-10-04 14:36:48,45,4,[Wild Frontier] Taiki Shuttle,JBC Sprint,1111,1,6000,105,1.5294,1,2,2,0,12300.0,12792,492.0,4.0,12792,0
|
||||
2026-10-04 14:36:48,46,4,[Wild Frontier] Taiki Shuttle,Mile Championship,78,1,11000,105,2.2484,0,2,0,0,22550.0,23001,451.0,2.0,23001,0
|
||||
2026-10-04 14:36:48,47,4,[Wild Frontier] Taiki Shuttle,Champions Cup,80,1,10000,105,2.5859,0,4,0,0,20500.0,21320,820.0,4.0,21320,0
|
||||
2026-10-04 14:36:48,50,4,[Wild Frontier] Taiki Shuttle,Negishi Stakes,22,2,1520,105,1.4117,0,6,0,0,3116.0,3302,186.0,5.9692,3302,0
|
||||
2026-10-04 14:36:48,52,4,[Wild Frontier] Taiki Shuttle,February Stakes,1,1,10000,105,0.0968,0,0,0,0,20500.0,20500,0.0,0.0,20500,0
|
||||
2026-10-04 14:36:48,54,4,[Wild Frontier] Taiki Shuttle,Takamatsunomiya Kinen,2,1,13000,105,3.2402,0,4,0,0,26650.0,27716,1066.0,4.0,27716,0
|
||||
2026-10-04 14:36:48,55,4,[Wild Frontier] Taiki Shuttle,Marine Cup,1128,1,2500,105,2.6992,0,4,0,0,5125.0,5330,205.0,4.0,5330,0
|
||||
2026-10-04 14:36:48,57,4,[Wild Frontier] Taiki Shuttle,Victoria Mile,5,1,10500,105,1.5024,0,2,0,0,21525.0,21956,431.0,2.0023,21956,0
|
||||
2026-10-04 14:36:48,58,4,[Wild Frontier] Taiki Shuttle,Keyaki Stakes,71,1,2200,105,3.8244,1,4,2,0,4510.0,4780,270.0,5.9867,4780,0
|
||||
2026-10-04 14:36:48,59,4,[Wild Frontier] Taiki Shuttle,Yasuda Kinen,73,1,13000,105,1.4361,1,2,2,0,26650.0,27716,1066.0,4.0,27716,0
|
||||
2026-10-04 14:36:48,62,4,[Wild Frontier] Taiki Shuttle,Queen Stakes,103,1,3600,105,5.0515,0,6,0,0,7380.0,7822,442.0,5.9892,7822,0
|
||||
2026-10-04 14:36:48,65,4,[Wild Frontier] Taiki Shuttle,Keisei Hai Autumn Handicap,110,1,3900,105,5.633,0,6,0,0,7995.0,8474,479.0,5.9912,8474,0
|
||||
2026-10-04 14:36:48,66,4,[Wild Frontier] Taiki Shuttle,Sprinters Stakes,75,1,13000,105,2.6328,0,4,0,0,26650.0,27716,1066.0,4.0,27716,0
|
||||
2026-10-04 14:36:48,67,4,[Wild Frontier] Taiki Shuttle,M.C. Nambu Hai,1109,1,6000,105,4.184,0,6,0,0,12300.0,13038,738.0,6.0,13038,0
|
||||
2026-10-04 14:36:48,69,4,[Wild Frontier] Taiki Shuttle,JBC Sprint,1025,1,6000,105,2.1263,1,2,2,0,12300.0,12792,492.0,4.0,12792,0
|
||||
2026-10-04 14:36:48,70,4,[Wild Frontier] Taiki Shuttle,Mile Championship,78,1,11000,105,2.1798,0,2,0,0,22550.0,23001,451.0,2.0,23001,0
|
||||
2026-10-04 14:36:48,71,4,[Wild Frontier] Taiki Shuttle,Champions Cup,80,1,10000,105,0.8076,1,2,2,0,20500.0,21320,820.0,4.0,21320,0
|
||||
2026-10-04 14:36:48,74,4,[Wild Frontier] Taiki Shuttle,Twinkle Star Climax Race 1,2313,1,7000,105,4.0466,1,6,2,0,14350.0,15498,1148.0,8.0,15498,0
|
||||
2026-10-04 14:36:48,76,4,[Wild Frontier] Taiki Shuttle,Twinkle Star Climax Race 2,2410,1,10000,105,1.8345,1,2,2,0,20500.0,21320,820.0,4.0,21320,0
|
||||
2026-10-04 14:36:48,78,4,[Wild Frontier] Taiki Shuttle,Twinkle Star Climax Race 3,2515,1,30000,105,5.1406,1,6,2,50,61500.0,99630,38130.0,62.0,99630,0
|
||||
2026-10-08 10:00:27,12,3,[CODE: ICING] Mihono Bourbon,Junior Make Debut,1075,1,700,70,3.126,1,4,2,0,1190.0,1261,71.0,5.9664,1261,0
|
||||
2026-10-08 10:00:27,23,3,[CODE: ICING] Mihono Bourbon,Asahi Hai Futurity Stakes,624,1,7000,70,3.0099,2,4,2,0,11900.0,12614,714.0,6.0,12614,0
|
||||
2026-10-08 10:00:27,30,3,[CODE: ICING] Mihono Bourbon,Spring Stakes,171,1,5400,70,1.4594,1,2,2,0,9180.0,9547,367.0,3.9978,9547,0
|
||||
2026-10-08 10:00:27,31,3,[CODE: ICING] Mihono Bourbon,Satsuki Sho,163,1,11000,70,6.1843,2,9,2,0,18700.0,20757,2057.0,11.0,20757,0
|
||||
2026-10-08 10:00:27,34,3,[CODE: ICING] Mihono Bourbon,Tokyo Yushun (Japanese Derby),166,1,20000,70,4.682,2,6,2,0,34000.0,36720,2720.0,8.0,36720,0
|
||||
2026-10-08 10:00:27,44,3,[CODE: ICING] Mihono Bourbon,Kikuka Sho,168,1,12000,70,3.5273,1,4,2,0,20400.0,21624,1224.0,6.0,21624,0
|
||||
2026-10-08 10:00:27,56,3,[CODE: ICING] Mihono Bourbon,Tenno Sho (Spring),4,1,15000,70,10.0374,2,9,2,0,25500.0,28305,2805.0,11.0,28305,0
|
||||
2026-10-08 10:00:27,70,3,[CODE: ICING] Mihono Bourbon,Japan Cup,79,1,30000,70,10.1623,2,9,2,0,51000.0,56610,5610.0,11.0,56610,0
|
||||
2026-10-08 10:00:27,72,3,[CODE: ICING] Mihono Bourbon,Arima Kinen,81,1,30000,70,10.4669,3,9,2,0,51000.0,56610,5610.0,11.0,56610,0
|
||||
2026-10-08 10:00:27,74,3,[CODE: ICING] Mihono Bourbon,URA Finale Qualifier,2010,1,7000,70,5.7274,3,6,2,0,11900.0,12852,952.0,8.0,12852,0
|
||||
2026-10-08 10:00:27,76,3,[CODE: ICING] Mihono Bourbon,URA Finale Semifinal,2113,1,10000,70,3.7223,3,4,2,0,17000.0,18020,1020.0,6.0,18020,0
|
||||
2026-10-08 10:00:27,78,3,[CODE: ICING] Mihono Bourbon,URA Finale Finals,2209,1,30000,70,3.873,3,4,2,0,51000.0,54060,3060.0,6.0,54060,0
|
||||
2026-10-10 03:12:29,12,3,[Soirée des Chatons] Seiun Sky,Junior Make Debut,689,1,700,70,3.0303,1,4,2,0,1190.0,1261,71.0,5.9664,1261,0
|
||||
2026-10-10 03:12:29,24,3,[Soirée des Chatons] Seiun Sky,Hopeful Stakes,625,1,7000,70,2.1576,1,2,2,0,11900.0,12376,476.0,4.0,12376,0
|
||||
2026-10-10 03:12:29,31,3,[Soirée des Chatons] Seiun Sky,Satsuki Sho,163,1,11000,70,2.5681,2,4,2,0,18700.0,19822,1122.0,6.0,19822,0
|
||||
2026-10-10 03:12:29,34,3,[Soirée des Chatons] Seiun Sky,Tokyo Yushun (Japanese Derby),166,1,20000,70,2.4678,2,2,2,0,34000.0,35360,1360.0,4.0,35360,0
|
||||
2026-10-10 03:12:29,44,3,[Soirée des Chatons] Seiun Sky,Kikuka Sho,168,1,12000,70,0.6938,2,0,2,0,20400.0,20808,408.0,2.0,20808,0
|
||||
2026-10-10 03:12:29,48,3,[Soirée des Chatons] Seiun Sky,Arima Kinen,81,1,30000,70,3.3828,2,4,2,0,51000.0,54060,3060.0,6.0,54060,0
|
||||
2026-10-10 03:12:29,56,3,[Soirée des Chatons] Seiun Sky,Tenno Sho (Spring),4,1,15000,70,20.5803,10,9,8,0,25500.0,29835,4335.0,17.0,29835,0
|
||||
2026-10-10 03:12:29,60,3,[Soirée des Chatons] Seiun Sky,Takarazuka Kinen,74,1,15000,70,6.4095,10,9,8,0,25500.0,29835,4335.0,17.0,29835,0
|
||||
2026-10-10 03:12:29,68,3,[Soirée des Chatons] Seiun Sky,Tenno Sho (Autumn),76,1,15000,70,9.3985,9,9,5,0,25500.0,29070,3570.0,14.0,29070,0
|
||||
2026-10-10 03:12:29,72,3,[Soirée des Chatons] Seiun Sky,Arima Kinen,81,1,30000,70,11.7606,10,9,8,0,51000.0,59670,8670.0,17.0,59670,0
|
||||
2026-10-10 03:12:29,74,3,[Soirée des Chatons] Seiun Sky,URA Finale Qualifier,2018,1,7000,70,11.2054,9,9,5,0,11900.0,13566,1666.0,14.0,13566,0
|
||||
2026-10-10 03:12:29,76,3,[Soirée des Chatons] Seiun Sky,URA Finale Semifinal,2123,1,10000,70,8.0601,7,9,5,0,17000.0,19380,2380.0,14.0,19380,0
|
||||
2026-10-10 03:12:29,78,3,[Soirée des Chatons] Seiun Sky,URA Finale Finals,2222,1,30000,70,5.7947,8,6,5,0,51000.0,56610,5610.0,11.0,56610,0
|
||||
2026-10-10 15:53:30,12,3,[Soirée des Chatons] Seiun Sky,Junior Make Debut,689,1,700,70,3.7946,2,4,2,0,1190.0,1261,71.0,5.9664,1261,0
|
||||
2026-10-10 15:53:30,24,3,[Soirée des Chatons] Seiun Sky,Hopeful Stakes,625,1,7000,70,5.3394,1,6,2,0,11900.0,12852,952.0,8.0,12852,0
|
||||
2026-10-10 15:53:30,31,3,[Soirée des Chatons] Seiun Sky,Satsuki Sho,163,1,11000,70,7.5947,2,9,2,0,18700.0,20757,2057.0,11.0,20757,0
|
||||
2026-10-10 15:53:30,34,3,[Soirée des Chatons] Seiun Sky,Tokyo Yushun (Japanese Derby),166,1,20000,70,2.1677,4,2,3,0,34000.0,35700,1700.0,5.0,35700,0
|
||||
2026-10-10 15:53:30,44,3,[Soirée des Chatons] Seiun Sky,Kikuka Sho,168,3,3000,70,3.8191,4,4,3,0,5100.0,5457,357.0,7.0,5457,0
|
||||
2026-10-10 15:53:30,48,3,[Soirée des Chatons] Seiun Sky,Arima Kinen,81,1,30000,70,12.6422,3,9,2,0,51000.0,56610,5610.0,11.0,56610,0
|
||||
2026-10-10 15:53:30,56,3,[Soirée des Chatons] Seiun Sky,Tenno Sho (Spring),4,1,15000,70,1.11,7,2,5,0,25500.0,27285,1785.0,7.0,27285,0
|
||||
2026-10-10 15:53:30,60,3,[Soirée des Chatons] Seiun Sky,Takarazuka Kinen,74,1,15000,70,13.7279,7,9,5,0,25500.0,29070,3570.0,14.0,29070,0
|
||||
2026-10-10 15:53:30,68,3,[Soirée des Chatons] Seiun Sky,Tenno Sho (Autumn),76,1,15000,70,3.6698,6,4,3,0,25500.0,27285,1785.0,7.0,27285,0
|
||||
2026-10-10 15:53:30,72,3,[Soirée des Chatons] Seiun Sky,Arima Kinen,81,1,30000,70,13.5919,8,9,5,0,51000.0,58140,7140.0,14.0,58140,0
|
||||
2026-10-10 15:53:30,74,3,[Soirée des Chatons] Seiun Sky,URA Finale Qualifier,2025,1,7000,70,13.045,7,9,5,0,11900.0,13566,1666.0,14.0,13566,0
|
||||
2026-10-10 15:53:30,76,3,[Soirée des Chatons] Seiun Sky,URA Finale Semifinal,2122,1,10000,70,12.4002,7,9,5,0,17000.0,19380,2380.0,14.0,19380,0
|
||||
2026-10-10 15:53:30,78,3,[Soirée des Chatons] Seiun Sky,URA Finale Finals,2217,1,30000,70,12.3247,7,9,5,0,51000.0,58140,7140.0,14.0,58140,0
|
||||
2026-10-11 18:47:03,12,3,[Soirée des Chatons] Seiun Sky,Junior Make Debut,689,1,700,80,3.2149,2,4,2,0,1260.0,1335,75.0,5.9524,1335,0
|
||||
2026-10-11 18:47:03,24,3,[Soirée des Chatons] Seiun Sky,Hopeful Stakes,625,1,7000,80,2.4816,2,2,2,0,12600.0,13104,504.0,4.0,13104,0
|
||||
2026-10-11 19:13:35,12,3,[Soirée des Chatons] Seiun Sky,Junior Make Debut,689,1,700,80,4.1512,1,6,2,0,1260.0,1360,100.0,7.9365,1360,0
|
||||
2026-10-11 19:13:35,24,3,[Soirée des Chatons] Seiun Sky,Hopeful Stakes,625,1,7000,80,2.8651,2,4,2,0,12600.0,13356,756.0,6.0,13356,0
|
||||
2026-10-11 19:13:35,31,3,[Soirée des Chatons] Seiun Sky,Satsuki Sho,163,1,11000,80,4.6012,2,6,2,0,19800.0,21384,1584.0,8.0,21384,0
|
||||
2026-10-11 19:13:35,34,3,[Soirée des Chatons] Seiun Sky,Tokyo Yushun (Japanese Derby),166,1,20000,80,4.1483,2,6,2,0,36000.0,38880,2880.0,8.0,38880,0
|
||||
2026-10-11 19:13:35,44,3,[Soirée des Chatons] Seiun Sky,Kikuka Sho,168,2,4800,80,4.218,2,2,2,0,8640.0,8985,345.0,3.9931,8985,0
|
||||
2026-10-11 19:13:35,48,3,[Soirée des Chatons] Seiun Sky,Arima Kinen,81,1,30000,80,10.0709,3,9,2,0,54000.0,59940,5940.0,11.0,59940,0
|
||||
2026-10-11 19:13:35,56,3,[Soirée des Chatons] Seiun Sky,Tenno Sho (Spring),4,1,15000,80,6.5113,8,9,5,0,27000.0,30780,3780.0,14.0,30780,0
|
||||
2026-10-11 19:13:35,60,3,[Soirée des Chatons] Seiun Sky,Takarazuka Kinen,74,1,15000,80,6.7601,6,9,3,0,27000.0,30240,3240.0,12.0,30240,0
|
||||
2026-10-11 19:13:35,72,3,[Soirée des Chatons] Seiun Sky,Arima Kinen,81,1,30000,80,14.0565,7,9,5,0,54000.0,61560,7560.0,14.0,61560,0
|
||||
2026-10-11 19:13:35,74,3,[Soirée des Chatons] Seiun Sky,URA Finale Qualifier,2018,1,7000,80,10.2344,7,9,5,0,12600.0,14364,1764.0,14.0,14364,0
|
||||
2026-10-11 19:13:35,76,3,[Soirée des Chatons] Seiun Sky,URA Finale Semifinal,2121,1,10000,80,5.3797,5,6,3,0,18000.0,19620,1620.0,9.0,19620,0
|
||||
2026-10-11 19:13:35,78,3,[Soirée des Chatons] Seiun Sky,URA Finale Finals,2222,1,30000,80,7.7281,7,9,5,0,54000.0,61560,7560.0,14.0,61560,0
|
||||
|
Reference in New Issue
Block a user