1 Commits
Author SHA1 Message Date
zephyr 55aa03aa41 umadump/replay: start of race replay json decoding 2026-09-20 11:44:09 -04:00
23 changed files with 359 additions and 18100 deletions
-13
View File
@@ -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
-29
View File
@@ -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]]
}
-29
View File
@@ -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]]
}
-32
View File
@@ -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]]
}
+268 -355
View File
@@ -6,7 +6,6 @@ import (
"sync" "sync"
"git.sunturtle.xyz/zephyr/horse" "git.sunturtle.xyz/zephyr/horse"
"git.sunturtle.xyz/zephyr/horse/umadump"
) )
type Race struct { type Race struct {
@@ -47,10 +46,10 @@ type Race struct {
PostNumberMax int32 `json:"postNumberMax"` PostNumberMax int32 `json:"postNumberMax"`
PlayerHorseIndex int32 `json:"playerHorseIndex"` PlayerHorseIndex int32 `json:"playerHorseIndex"`
OverridePlayerHorseIndex int32 `json:"overridePlayerHorseIndex"` OverridePlayerHorseIndex int32 `json:"overridePlayerHorseIndex"`
PlayerTeamMemberArray []HorseData `json:"playerTeamMemberArray"` PlayerTeamMemberArray []RaceHorse `json:"playerTeamMemberArray"`
PlayerTeamTopFinishOrderHorse HorseData `json:"playerTeamTopFinishOrderHorse"` PlayerTeamTopFinishOrderHorse RaceHorse `json:"playerTeamTopFinishOrderHorse"`
IsGateInPopularityInitialized bool `json:"isGateInPopularityInitialized"` IsGateInPopularityInitialized bool `json:"isGateInPopularityInitialized"`
RaceHorse []HorseData `json:"raceHorse"` RaceHorse []RaceHorse `json:"raceHorse"`
RaceBibMaster struct{} `json:"raceBibMaster"` RaceBibMaster struct{} `json:"raceBibMaster"`
RaceMaster struct{} `json:"raceMaster"` RaceMaster struct{} `json:"raceMaster"`
RaceInstanceMaster struct{} `json:"raceInstanceMaster"` RaceInstanceMaster struct{} `json:"raceInstanceMaster"`
@@ -65,7 +64,7 @@ type Race struct {
ReplayCheckInfoLegend struct{} `json:"replayCheckInfoLegend"` ReplayCheckInfoLegend struct{} `json:"replayCheckInfoLegend"`
IsDailyLegendRace bool `json:"isDailyLegendRace"` IsDailyLegendRace bool `json:"isDailyLegendRace"`
ReplayCheckInfoChallengeMatch struct{} `json:"replayCheckInfoChallengeMatch"` ReplayCheckInfoChallengeMatch struct{} `json:"replayCheckInfoChallengeMatch"`
RaceRewardSingle struct{} `json:"raceRewardSingle"` RaceRewardSingle RaceRewardSingle `json:"raceRewardSingle"`
ResultHorseIndex int32 `json:"resultHorseIndex"` ResultHorseIndex int32 `json:"resultHorseIndex"`
PrevGradeType CharaGradeType `json:"prevGradeType"` PrevGradeType CharaGradeType `json:"prevGradeType"`
MainStoryRaceGimmickType MainStoryRaceGimmickType `json:"mainStoryRaceGimmickType"` MainStoryRaceGimmickType MainStoryRaceGimmickType `json:"mainStoryRaceGimmickType"`
@@ -538,334 +537,122 @@ func (r *RaceDifficulty) UnmarshalText(b []byte) error {
} }
type RaceParam struct { type RaceParam struct {
RawSpeed int `json:"rawSpeed"` RawSpeed int `json:"rawSpeed"`
RawStamina int `json:"rawStamina"` RawStamina int `json:"rawStamina"`
RawPow int `json:"rawPow"` RawPow int `json:"rawPow"`
RawGuts int `json:"rawGuts"` RawGuts int `json:"rawGuts"`
RawWiz int `json:"rawWiz"` RawWiz int `json:"rawWiz"`
BaseSpeed float32 `json:"baseSpeed"` BaseSpeed float32 `json:"baseSpeed"`
BaseStamina float32 `json:"baseStamina"` BaseStamina float32 `json:"baseStamina"`
BasePow float32 `json:"basePow"` BasePow float32 `json:"basePow"`
BaseGuts float32 `json:"baseGuts"` BaseGuts float32 `json:"baseGuts"`
BaseWiz float32 `json:"baseWiz"` BaseWiz float32 `json:"baseWiz"`
Motivation Motivation `json:"motivation"` Motivation string `json:"motivation"`
MotivationCoef float32 `json:"motivationCoef"` MotivationCoef float32 `json:"motivationCoef"`
}
type Motivation int32
//go:generate go run golang.org/x/tools/cmd/stringer@v0.41.0 -type Motivation -linecomment
const (
MotivationNone Motivation = iota // None
MotivationAwful // Min
MotivationBad // Low
MotivationNormal // Middle
MotivationGood // High
MotivationGreat // Max
)
func (r Motivation) AppendText(b []byte) ([]byte, error) {
return append(b, r.String()...), nil
}
func (r Motivation) MarshalText() ([]byte, error) {
return r.AppendText(nil)
}
func (r *Motivation) UnmarshalText(b []byte) error {
switch string(b) {
case "Min":
*r = MotivationAwful
case "Low":
*r = MotivationBad
case "Middle":
*r = MotivationNormal
case "High":
*r = MotivationGood
case "Max":
*r = MotivationGreat
case "None":
*r = MotivationNone
default:
*r = 0
return fmt.Errorf("unknown motivation %q", b)
}
return nil
} }
type SkillArray struct { type SkillArray struct {
SkillID horse.SkillID `json:"skill_id"` SkillID int `json:"skill_id"`
Level int32 `json:"level"` Level int `json:"level"`
} }
type RaceResult struct { type RaceResultArray struct {
Turn int32 `json:"turn"` Turn int `json:"turn"`
ProgramID int32 `json:"program_id"` ProgramID int `json:"program_id"`
Weather int32 `json:"weather"` Weather int `json:"weather"`
GroundCondition int32 `json:"ground_condition"` GroundCondition int `json:"ground_condition"`
RunningStyle int32 `json:"running_style"` RunningStyle int `json:"running_style"`
Popularity int32 `json:"popularity"` Popularity int `json:"popularity"`
ResultRank int32 `json:"result_rank"` ResultRank int `json:"result_rank"`
ResultTime int32 `json:"result_time"` ResultTime int `json:"result_time"`
PrizeMoney int32 `json:"prize_money"` PrizeMoney int `json:"prize_money"`
} }
type ResponseHorseData struct {
type RaceHorseData struct { FrameOrder int `json:"frame_order"`
FrameOrder int32 `json:"frame_order"` ViewerID int64 `json:"viewer_id"`
ViewerID int64 `json:"viewer_id"` TrainerName string `json:"trainer_name"`
TrainerName string `json:"trainer_name"` OwnerViewerID int `json:"owner_viewer_id"`
OwnerViewerID int64 `json:"owner_viewer_id"` OwnerTrainerName string `json:"owner_trainer_name"`
OwnerTrainerName string `json:"owner_trainer_name"` SingleModeCharaID int `json:"single_mode_chara_id"`
SingleModeCharaID int32 `json:"single_mode_chara_id"` TrainedCharaID int `json:"trained_chara_id"`
TrainedCharaID int32 `json:"trained_chara_id"` NicknameID int `json:"nickname_id"`
NicknameID int32 `json:"nickname_id"` CharaID int `json:"chara_id"`
CharaID int32 `json:"chara_id"` CardID int `json:"card_id"`
CardID int32 `json:"card_id"` MobID int `json:"mob_id"`
MobID int32 `json:"mob_id"` Rarity int `json:"rarity"`
Rarity int32 `json:"rarity"` TalentLevel int `json:"talent_level"`
TalentLevel int32 `json:"talent_level"` SkillArray []SkillArray `json:"skill_array"`
SkillArray []SkillArray `json:"skill_array"` Stamina int `json:"stamina"`
Stamina int32 `json:"stamina"` Speed int `json:"speed"`
Speed int32 `json:"speed"` Pow int `json:"pow"`
Pow int32 `json:"pow"` Guts int `json:"guts"`
Guts int32 `json:"guts"` Wiz int `json:"wiz"`
Wiz int32 `json:"wiz"` RunningStyle int `json:"running_style"`
RunningStyle int32 `json:"running_style"` RaceDressID int `json:"race_dress_id"`
RaceDressID int32 `json:"race_dress_id"` CharaColorType int `json:"chara_color_type"`
CharaColorType int32 `json:"chara_color_type"` NpcType int `json:"npc_type"`
NpcType int32 `json:"npc_type"` FinalGrade int `json:"final_grade"`
FinalGrade int32 `json:"final_grade"` Popularity int `json:"popularity"`
Popularity int32 `json:"popularity"` PopularityMarkRankArray []int `json:"popularity_mark_rank_array"`
PopularityMarkRankArray []int32 `json:"popularity_mark_rank_array"` ProperDistanceShort int `json:"proper_distance_short"`
ProperDistanceShort int32 `json:"proper_distance_short"` ProperDistanceMile int `json:"proper_distance_mile"`
ProperDistanceMile int32 `json:"proper_distance_mile"` ProperDistanceMiddle int `json:"proper_distance_middle"`
ProperDistanceMiddle int32 `json:"proper_distance_middle"` ProperDistanceLong int `json:"proper_distance_long"`
ProperDistanceLong int32 `json:"proper_distance_long"` ProperRunningStyleNige int `json:"proper_running_style_nige"`
ProperRunningStyleNige int32 `json:"proper_running_style_nige"` ProperRunningStyleSenko int `json:"proper_running_style_senko"`
ProperRunningStyleSenko int32 `json:"proper_running_style_senko"` ProperRunningStyleSashi int `json:"proper_running_style_sashi"`
ProperRunningStyleSashi int32 `json:"proper_running_style_sashi"` ProperRunningStyleOikomi int `json:"proper_running_style_oikomi"`
ProperRunningStyleOikomi int32 `json:"proper_running_style_oikomi"` ProperGroundTurf int `json:"proper_ground_turf"`
ProperGroundTurf int32 `json:"proper_ground_turf"` ProperGroundDirt int `json:"proper_ground_dirt"`
ProperGroundDirt int32 `json:"proper_ground_dirt"` Motivation int `json:"motivation"`
Motivation int32 `json:"motivation"` WinSaddleIDArray []int `json:"win_saddle_id_array"`
WinSaddleIDArray []int32 `json:"win_saddle_id_array"` RaceResultArray []RaceResultArray `json:"race_result_array"`
RaceResultArray []RaceResult `json:"race_result_array"` TeamID int `json:"team_id"`
TeamID int32 `json:"team_id"` TeamMemberID int `json:"team_member_id"`
TeamMemberID int32 `json:"team_member_id"` TeamRank int `json:"team_rank"`
TeamRank int32 `json:"team_rank"` SingleModeWinCount int `json:"single_mode_win_count"`
SingleModeWinCount int32 `json:"single_mode_win_count"` ItemIDArray []any `json:"item_id_array"`
ItemIDArray []int32 `json:"item_id_array"` MotivationChangeFlag int `json:"motivation_change_flag"`
MotivationChangeFlag int32 `json:"motivation_change_flag"` FrameOrderChangeFlag int `json:"frame_order_change_flag"`
FrameOrderChangeFlag int32 `json:"frame_order_change_flag"`
} }
type RaceRecord struct {
type HorseData struct {
HorseIndex int32 `json:"horseIndex"`
PostNumber int32 `json:"postNumber"`
CharaID horse.CharacterID `json:"charaId"`
CharaName string `json:"charaName"`
FinishOrder int32 `json:"finishOrder"`
FinishTimeRaw float32 `json:"finishTimeRaw"`
FinishTimeScaled float32 `json:"finishTimeScaled"`
FinishDiffTimeFromPrev float32 `json:"finishDiffTimeFromPrev"`
RaceParam RaceParam `json:"raceParam"`
ResponseHorseData RaceHorseData `json:"responseHorseData"`
Popularity int32 `json:"popularity"`
PopularityRankLeft int8 `json:"popularityRankLeft"`
PopularityRankCenter int8 `json:"popularityRankCenter"`
PopularityRankRight int8 `json:"popularityRankRight"`
GateInPopularity int8 `json:"gateInPopularity"`
Rarity CardRarity `json:"rarity"`
TrainerName string `json:"trainerName"`
IsGhost bool `json:"isGhost"`
IsRunningStyleExInitialized bool `json:"isRunningStyleExInitialized"`
RunningStyleEx RunningStyleEx `json:"runningStyleEx"`
Defeat string `json:"defeat"` // this is an enum, but not worth the effort
RaceDressID int32 `json:"raceDressId"`
RaceDressIDWithOption int32 `json:"raceDressIdWithOption"`
RunningType string `json:"runningType"` // enum not worth
ActiveProperDistance ProperGrade `json:"activeProperDistance"`
ActiveProperGroundType ProperGrade `json:"activeProperGroundType"`
MobID int32 `json:"mobId"`
RaceRecord struct{} `json:"raceRecord"`
FinishOrderRawScore int32 `json:"finishOrderRawScore"`
TrainedCharaData TrainedCharaData `json:"trainedCharaData"`
} }
type FactorDataArray struct {
type RunningStyleEx int8 FactorLv int `json:"factorLv"`
FactorID int `json:"factorId"`
//go:generate go run golang.org/x/tools/cmd/stringer@v0.41.0 -type RunningStyleEx -linecomment BaseFactorID int `json:"baseFactorId"`
const ( UpgradeHistoryList []any `json:"upgradeHistoryList"`
StyleExNone RunningStyleEx = iota // NONE
StyleExRunaway // Oonige
)
func (r RunningStyleEx) AppendText(b []byte) ([]byte, error) {
return append(b, r.String()...), nil
} }
type FavoriteData struct {
func (r RunningStyleEx) MarshalText() ([]byte, error) {
return r.AppendText(nil)
} }
func (r *RunningStyleEx) UnmarshalText(b []byte) error {
switch string(b) {
case "NONE":
*r = StyleExNone
case "Oonige":
*r = StyleExRunaway
default:
*r = 0
return fmt.Errorf("unknown extra running style %q", b)
}
return nil
}
type ProperGrade int8
//go:generate go run golang.org/x/tools/cmd/stringer@v0.41.0 -type ProperGrade -trimprefix Aptitude
const (
AptitudeNull ProperGrade = iota
AptitudeG
AptitudeF
AptitudeE
AptitudeD
AptitudeC
AptitudeB
AptitudeA
AptitudeS
)
func (r ProperGrade) AppendText(b []byte) ([]byte, error) {
return append(b, r.String()...), nil
}
func (r ProperGrade) MarshalText() ([]byte, error) {
return r.AppendText(nil)
}
func (r *ProperGrade) UnmarshalText(b []byte) error {
switch string(b) {
case "Null":
*r = AptitudeNull
case "G":
*r = AptitudeG
case "F":
*r = AptitudeF
case "E":
*r = AptitudeE
case "D":
*r = AptitudeD
case "C":
*r = AptitudeC
case "B":
*r = AptitudeB
case "A":
*r = AptitudeA
case "S":
*r = AptitudeS
default:
*r = 0
return fmt.Errorf("unknown motivation %q", b)
}
return nil
}
type TrainedCharaData struct {
ID int32 `json:"id"`
IsSaved bool `json:"isSaved"`
ViewerID int64 `json:"viewerId"`
OwnerViewerID int64 `json:"ownerViewerId"`
OwnerTrainedCharaID int32 `json:"ownerTrainedCharaId"`
UseType int32 `json:"useType"`
CardID horse.UmaID `json:"cardId"`
NickNameID int32 `json:"nickNameId"`
NickNameIDArray []int32 `json:"nickNameIdArray"`
Stamina int32 `json:"stamina"`
Speed int32 `json:"speed"`
Power int32 `json:"power"`
Guts int32 `json:"guts"`
Wiz int32 `json:"wiz"`
Fans int32 `json:"fans"`
Rank int32 `json:"rank"`
RankScore int32 `json:"rankScore"`
RunningStyle int32 `json:"runningStyle"`
ProperGroundTurf int32 `json:"properGroundTurf"`
ProperGroundDirt int32 `json:"properGroundDirt"`
ProperDistanceShort int32 `json:"properDistanceShort"`
ProperDistanceMile int32 `json:"properDistanceMile"`
ProperDistanceMiddle int32 `json:"properDistanceMiddle"`
ProperDistanceLong int32 `json:"properDistanceLong"`
ProperRunningStyleNige int32 `json:"properRunningStyleNige"`
ProperRunningStyleSenko int32 `json:"properRunningStyleSenko"`
ProperRunningStyleSashi int32 `json:"properRunningStyleSashi"`
ProperRunningStyleOikomi int32 `json:"properRunningStyleOikomi"`
SuccessionCount int32 `json:"successionCount"`
FactorDataArray []FactorData `json:"factorDataArray"`
CreateTime string `json:"createTime"`
ScenarioID horse.ScenarioID `json:"scenarioId"`
TalentLevel int32 `json:"talentLevel"`
CharaGrade int32 `json:"charaGrade"`
Rarity int32 `json:"rarity"`
IsLock bool `json:"isLock"`
FavoriteData struct{} `json:"favoriteData"`
CachedCreateTimeTimeStamp int64 `json:"cachedCreateTimeTimeStamp"`
SortedFactorList []umadump.Never `json:"sortedFactorList"`
SortedFactorProfileCardList []umadump.Never `json:"sortedFactorProfileCardList"`
FactorListIncludingSuccession []umadump.Never `json:"factorListIncludingSuccession"`
SuccessionCharaList []SuccessionCharaList `json:"successionCharaList"`
IsSuccessionHistoryInitialized bool `json:"isSuccessionHistoryInitialized"`
SuccessionHistoryList []umadump.Never `json:"successionHistoryList"`
AcquiredSkillArray []AcquiredSkill `json:"acquiredSkillArray"`
SupportCardArray []SupportCard `json:"supportCardArray"`
SingleModeRaceResultArray []SingleModeRaceResult `json:"singleModeRaceResultArray"`
WinSaddleArray []umadump.Never `json:"winSaddleArray"`
WinSaddleIDArray []horse.SaddleID `json:"winSaddleIdArray"`
CacheCharaID int32 `json:"cacheCharaId"`
MasterCardData struct{} `json:"masterCardData"`
MasterCharaData struct{} `json:"masterCharaData"`
MasterCardRarityData struct{} `json:"masterCardRarityData"`
SingleTotalRaceNum int32 `json:"singleTotalRaceNum"`
SingleWinNum int32 `json:"singleWinNum"`
TrainedCharaDataAccessor struct{} `json:"trainedCharaDataAccessor"`
}
type FactorData struct {
FactorLv int32 `json:"factorLv"`
FactorID horse.SparkID `json:"factorId"`
BaseFactorID horse.SparkID `json:"baseFactorId"`
UpgradeHistoryList []umadump.Never `json:"upgradeHistoryList"`
}
type SuccessionCharaList struct { type SuccessionCharaList struct {
PositionID int32 `json:"positionId"` PositionID int `json:"positionId"`
CardID int32 `json:"cardId"` CardID int `json:"cardId"`
Rarity int32 `json:"rarity"` Rarity int `json:"rarity"`
Level int32 `json:"level"` Level int `json:"level"`
Rank int32 `json:"rank"` Rank int `json:"rank"`
FactorDataArray []FactorData `json:"factorDataArray"` FactorDataArray []FactorDataArray `json:"factorDataArray"`
SortedFactorList []umadump.Never `json:"sortedFactorList"` SortedFactorList []any `json:"sortedFactorList"`
SortedFactorListForProfileCard []umadump.Never `json:"sortedFactorListForProfileCard"` SortedFactorListForProfileCard []any `json:"sortedFactorListForProfileCard"`
OwnerViewerID int64 `json:"ownerViewerId"` OwnerViewerID int `json:"ownerViewerId"`
IsPlayer bool `json:"isPlayer"` IsPlayer bool `json:"isPlayer"`
WinSaddleArray []umadump.Never `json:"winSaddleArray"` WinSaddleArray []any `json:"winSaddleArray"`
WinSaddleIDArray []int32 `json:"winSaddleIdArray"` WinSaddleIDArray []int `json:"winSaddleIdArray"`
} }
type Master struct {
type AcquiredSkill struct {
MasterID horse.SkillID `json:"masterId"`
Level int `json:"level"`
Master struct{} `json:"master"`
} }
type AcquiredSkillArray struct {
type SupportCard struct { MasterID int `json:"masterId"`
Position int32 `json:"position"` Level int `json:"level"`
SupportCardID horse.SupportCardID `json:"supportCardId"` Master Master `json:"master"`
LimitBreakCount int32 `json:"limitBreakCount"`
Exp int32 `json:"exp"`
} }
type SupportCardArray struct {
type SingleModeRaceResult struct { Position int `json:"position"`
SupportCardID int `json:"supportCardId"`
LimitBreakCount int `json:"limitBreakCount"`
Exp int `json:"exp"`
}
type SingleModeRaceResultArray struct {
Turn int `json:"turn"` Turn int `json:"turn"`
ProgramID int `json:"programId"` ProgramID int `json:"programId"`
RaceInstanceID int `json:"raceInstanceId"` RaceInstanceID int `json:"raceInstanceId"`
@@ -877,46 +664,172 @@ type SingleModeRaceResult struct {
ResultRank int `json:"resultRank"` ResultRank int `json:"resultRank"`
ScenarioID int `json:"scenarioId"` ScenarioID int `json:"scenarioId"`
} }
type MasterCardData struct {
type CardRarity int8 }
type MasterCharaData struct {
//go:generate go run golang.org/x/tools/cmd/stringer@v0.41.0 -type CardRarity -linecomment -trimprefix Card }
const ( type MasterCardRarityData struct {
CardNone CardRarity = iota // NONE }
CardRare1 type TrainedCharaDataAccessor struct {
CardRare2 }
CardRare3 type TrainedCharaData struct {
CardRare4 ID int `json:"id"`
CardRare5 IsSaved bool `json:"isSaved"`
) ViewerID int64 `json:"viewerId"`
OwnerViewerID int `json:"ownerViewerId"`
func (r CardRarity) AppendText(b []byte) ([]byte, error) { OwnerTrainedCharaID int `json:"ownerTrainedCharaId"`
return append(b, r.String()...), nil UseType int `json:"useType"`
CardID int `json:"cardId"`
NickNameID int `json:"nickNameId"`
NickNameIDArray []int `json:"nickNameIdArray"`
Stamina int `json:"stamina"`
Speed int `json:"speed"`
Power int `json:"power"`
Guts int `json:"guts"`
Wiz int `json:"wiz"`
Fans int `json:"fans"`
Rank int `json:"rank"`
RankScore int `json:"rankScore"`
RunningStyle int `json:"runningStyle"`
ProperGroundTurf int `json:"properGroundTurf"`
ProperGroundDirt int `json:"properGroundDirt"`
ProperDistanceShort int `json:"properDistanceShort"`
ProperDistanceMile int `json:"properDistanceMile"`
ProperDistanceMiddle int `json:"properDistanceMiddle"`
ProperDistanceLong int `json:"properDistanceLong"`
ProperRunningStyleNige int `json:"properRunningStyleNige"`
ProperRunningStyleSenko int `json:"properRunningStyleSenko"`
ProperRunningStyleSashi int `json:"properRunningStyleSashi"`
ProperRunningStyleOikomi int `json:"properRunningStyleOikomi"`
SuccessionCount int `json:"successionCount"`
FactorDataArray []FactorDataArray `json:"factorDataArray"`
CreateTime string `json:"createTime"`
ScenarioID int `json:"scenarioId"`
TalentLevel int `json:"talentLevel"`
CharaGrade int `json:"charaGrade"`
Rarity int `json:"rarity"`
IsLock bool `json:"isLock"`
FavoriteData FavoriteData `json:"favoriteData"`
CachedCreateTimeTimeStamp int `json:"cachedCreateTimeTimeStamp"`
SortedFactorList []any `json:"sortedFactorList"`
SortedFactorProfileCardList []any `json:"sortedFactorProfileCardList"`
FactorListIncludingSuccession []any `json:"factorListIncludingSuccession"`
SuccessionCharaList []SuccessionCharaList `json:"successionCharaList"`
IsSuccessionHistoryInitialized bool `json:"isSuccessionHistoryInitialized"`
SuccessionHistoryList []any `json:"successionHistoryList"`
AcquiredSkillArray []AcquiredSkillArray `json:"acquiredSkillArray"`
SupportCardArray []SupportCardArray `json:"supportCardArray"`
SingleModeRaceResultArray []SingleModeRaceResultArray `json:"singleModeRaceResultArray"`
WinSaddleArray []any `json:"winSaddleArray"`
WinSaddleIDArray []int `json:"winSaddleIdArray"`
CacheCharaID int `json:"cacheCharaId"`
MasterCardData MasterCardData `json:"masterCardData"`
MasterCharaData MasterCharaData `json:"masterCharaData"`
MasterCardRarityData MasterCardRarityData `json:"masterCardRarityData"`
SingleTotalRaceNum int `json:"singleTotalRaceNum"`
SingleWinNum int `json:"singleWinNum"`
TrainedCharaDataAccessor TrainedCharaDataAccessor `json:"trainedCharaDataAccessor"`
}
type PlayerTeamMember struct {
HorseIndex int `json:"horseIndex"`
PostNumber int `json:"postNumber"`
CharaID int `json:"charaId"`
CharaName string `json:"charaName"`
FinishOrder int `json:"finishOrder"`
FinishTimeRaw float32 `json:"finishTimeRaw"`
FinishTimeScaled float32 `json:"finishTimeScaled"`
FinishDiffTimeFromPrev float32 `json:"finishDiffTimeFromPrev"`
RaceParam RaceParam `json:"raceParam"`
ResponseHorseData ResponseHorseData `json:"responseHorseData"`
Popularity int `json:"popularity"`
PopularityRankLeft int `json:"popularityRankLeft"`
PopularityRankCenter int `json:"popularityRankCenter"`
PopularityRankRight int `json:"popularityRankRight"`
GateInPopularity int `json:"gateInPopularity"`
Rarity string `json:"rarity"`
TrainerName string `json:"trainerName"`
IsGhost bool `json:"isGhost"`
IsRunningStyleExInitialized bool `json:"isRunningStyleExInitialized"`
RunningStyleEx string `json:"runningStyleEx"`
Defeat string `json:"defeat"`
RaceDressID int `json:"raceDressId"`
RaceDressIDWithOption int `json:"raceDressIdWithOption"`
RunningType string `json:"runningType"`
ActiveProperDistance string `json:"activeProperDistance"`
ActiveProperGroundType string `json:"activeProperGroundType"`
MobID int `json:"mobId"`
RaceRecord RaceRecord `json:"raceRecord"`
FinishOrderRawScore int `json:"finishOrderRawScore"`
TrainedCharaData TrainedCharaData `json:"trainedCharaData"`
}
type PlayerTeamTopFinishOrderHorse struct {
HorseIndex int `json:"horseIndex"`
PostNumber int `json:"postNumber"`
CharaID int `json:"charaId"`
CharaName string `json:"charaName"`
FinishOrder int `json:"finishOrder"`
FinishTimeRaw float32 `json:"finishTimeRaw"`
FinishTimeScaled float32 `json:"finishTimeScaled"`
FinishDiffTimeFromPrev float32 `json:"finishDiffTimeFromPrev"`
RaceParam RaceParam `json:"raceParam"`
ResponseHorseData ResponseHorseData `json:"responseHorseData"`
Popularity int `json:"popularity"`
PopularityRankLeft int `json:"popularityRankLeft"`
PopularityRankCenter int `json:"popularityRankCenter"`
PopularityRankRight int `json:"popularityRankRight"`
GateInPopularity int `json:"gateInPopularity"`
Rarity string `json:"rarity"`
TrainerName string `json:"trainerName"`
IsGhost bool `json:"isGhost"`
IsRunningStyleExInitialized bool `json:"isRunningStyleExInitialized"`
RunningStyleEx string `json:"runningStyleEx"`
Defeat string `json:"defeat"`
RaceDressID int `json:"raceDressId"`
RaceDressIDWithOption int `json:"raceDressIdWithOption"`
RunningType string `json:"runningType"`
ActiveProperDistance string `json:"activeProperDistance"`
ActiveProperGroundType string `json:"activeProperGroundType"`
MobID int `json:"mobId"`
RaceRecord RaceRecord `json:"raceRecord"`
FinishOrderRawScore int `json:"finishOrderRawScore"`
TrainedCharaData TrainedCharaData `json:"trainedCharaData"`
} }
func (r CardRarity) MarshalText() ([]byte, error) { type RaceHorse struct {
return r.AppendText(nil) HorseIndex int `json:"horseIndex"`
PostNumber int `json:"postNumber"`
CharaID int `json:"charaId"`
CharaName string `json:"charaName"`
FinishOrder int `json:"finishOrder"`
FinishTimeRaw float32 `json:"finishTimeRaw"`
FinishTimeScaled float32 `json:"finishTimeScaled"`
FinishDiffTimeFromPrev float32 `json:"finishDiffTimeFromPrev"`
RaceParam RaceParam `json:"raceParam"`
ResponseHorseData ResponseHorseData `json:"responseHorseData"`
Popularity int `json:"popularity"`
PopularityRankLeft int `json:"popularityRankLeft"`
PopularityRankCenter int `json:"popularityRankCenter"`
PopularityRankRight int `json:"popularityRankRight"`
GateInPopularity int `json:"gateInPopularity"`
Rarity string `json:"rarity"`
TrainerName string `json:"trainerName"`
IsGhost bool `json:"isGhost"`
IsRunningStyleExInitialized bool `json:"isRunningStyleExInitialized"`
RunningStyleEx string `json:"runningStyleEx"`
Defeat string `json:"defeat"`
RaceDressID int `json:"raceDressId"`
RaceDressIDWithOption int `json:"raceDressIdWithOption"`
RunningType string `json:"runningType"`
ActiveProperDistance string `json:"activeProperDistance"`
ActiveProperGroundType string `json:"activeProperGroundType"`
MobID int `json:"mobId"`
RaceRecord RaceRecord `json:"raceRecord"`
FinishOrderRawScore int `json:"finishOrderRawScore"`
TrainedCharaData TrainedCharaData `json:"trainedCharaData,omitempty"`
} }
func (r *CardRarity) UnmarshalText(b []byte) error { type RaceRewardSingle struct {
switch string(b) { // TODO(zeph): this
case "NONE":
*r = CardNone
case "Rare1":
*r = CardRare1
case "Rare2":
*r = CardRare2
case "Rare3":
*r = CardRare3
case "Rare4":
*r = CardRare4
case "Rare5":
*r = CardRare5
default:
*r = 0
return fmt.Errorf("unknown card rarity %q", b)
}
return nil
} }
type CharaGradeType int8 type CharaGradeType int8
-182
View File
@@ -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))
}
}
-25
View File
@@ -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]]
}
File diff suppressed because one or more lines are too long
-20
View File
@@ -1,20 +0,0 @@
<script lang="ts">
interface Props {
value: number;
}
let { value = $bindable() }: Props = $props();
</script>
<div class="flex">
<input
class="my-auto w-full grow justify-self-center-safe"
type="range"
id="raceLen"
min="1000"
max="3600"
step="100"
bind:value
/>
<span class="my-auto flex-none pl-4 text-sm">{value}m</span>
</div>
-1
View File
@@ -20,7 +20,6 @@
skill_id: 0, skill_id: 0,
name: 'Skill data is loading...', name: 'Skill data is loading...',
description: 'Skill data is still loading, or else something went wrong.', description: 'Skill data is still loading, or else something went wrong.',
group_skills: [],
group: 0, group: 0,
rarity: 1, rarity: 1,
group_rate: 1, group_rate: 1,
-4
View File
@@ -4,10 +4,6 @@ import type { RegionalName } from '$lib/regional-name';
* Lobby conversation data. * Lobby conversation data.
*/ */
export interface Conversation { export interface Conversation {
/**
* Gallery ID.
*/
id: number;
/** /**
* Character who owns the conversation as a gallery entry. * Character who owns the conversation as a gallery entry.
*/ */
-16
View File
@@ -14,10 +14,6 @@ export interface Skill {
* Regional skil description. * Regional skil description.
*/ */
description: string; description: string;
/**
* Other skills in the skill group.
*/
group_skills: GroupSkill[];
/** /**
* Skill group ID. * Skill group ID.
*/ */
@@ -62,18 +58,6 @@ export interface Skill {
icon_id: number; icon_id: number;
} }
/**
* Skill reference in a skill group.
*/
export interface GroupSkill {
/** Skill ID. */
skill_id: number;
/** Group rate, as in `Skill`. */
group_rate: Skill['group_rate'];
/** Rarity, as in `Skill`. */
rarity: Skill['rarity'];
}
export enum DurationScale { export enum DurationScale {
Direct = 1, Direct = 1,
FrontDistance = 2, FrontDistance = 2,
+2 -3
View File
@@ -771,17 +771,16 @@ export function conservePowerAccel(
distanceType: Distance, distanceType: Distance,
style: RunningStyle, style: RunningStyle,
rawPower: number, rawPower: number,
basePower: number,
powerSkillBonus?: number, powerSkillBonus?: number,
spotStruggled?: boolean, spotStruggled?: boolean,
rushed?: boolean, rushed?: boolean,
): number { ): number {
const power = basePower + 0.5 * Math.max(rawPower - 1200, 0) + (powerSkillBonus ?? 0); const power = rawPower + (powerSkillBonus ?? 0);
if (power <= 1200) { if (power <= 1200) {
return 0; return 0;
} }
const sdc = conserveStratDistCoef[style][distanceType]; const sdc = conserveStratDistCoef[style][distanceType];
const activity = (rushed ? 0.8 : 1) * (spotStruggled ? 0.98 : 1); const activity = rushed ? 0.8 : spotStruggled ? 0.98 : 1;
return Math.sqrt((power - 1200) * 130) * 0.001 * sdc * activity; return Math.sqrt((power - 1200) * 130) * 0.001 * sdc * activity;
} }
-14
View File
@@ -1,14 +0,0 @@
import * as z from 'zod';
const Entry = z.strictObject({
home_story_trigger_id: z.int32(),
new_flag: z.literal(0).or(z.literal(1)),
});
export type Entry = z.infer<typeof Entry>;
export const Gallery = z.object({
talk_gallery_list: z.array(Entry),
});
export type Gallery = z.infer<typeof Gallery>;
+8 -57
View File
@@ -9,51 +9,25 @@
requiredCharas, requiredCharas,
convoCharas, convoCharas,
} from '$lib/data/convo'; } from '$lib/data/convo';
import * as talk from '$lib/umadump/talk';
import CharaPick from '$lib/CharaPick.svelte'; import CharaPick from '$lib/CharaPick.svelte';
import { onMount } from 'svelte'; import { onMount } from 'svelte';
import CharaIcon from '$lib/CharaIcon.svelte'; import CharaIcon from '$lib/CharaIcon.svelte';
import { SvelteSet } from 'svelte/reactivity';
let allConvos: Conversation[] = $state([]); let conversations: Conversation[] = $state([]);
const popular = $derived(groupPopulars(allConvos)); const convoMap = $derived(byChara(conversations));
const popular = $derived(groupPopulars(conversations));
let characters: Character[] = $state([]); let characters: Character[] = $state([]);
const selCharas = $derived(characters.filter((c) => convoMap.has(c.chara_id)));
const names = $derived(charaNames(characters)); const names = $derived(charaNames(characters));
const minSuggest = 8; const minSuggest = 8;
onMount(async () => { onMount(async () => {
[allConvos, characters] = await Promise.all([conversation(), character()]); [conversations, characters] = await Promise.all([conversation(), character()]);
}); });
let chara: Character | undefined = $state(); let chara: Character | undefined = $state();
let convo = $state(1); let convo = $state(1);
let talkFiles: FileList | undefined = $state();
let readIDs = new SvelteSet<number>();
let talkFileInvalid = $state(false);
async function setRead(f: FileList) {
talkFiles = f;
readIDs.clear();
talkFileInvalid = false;
if (talkFiles == null || talkFiles.length === 0) {
return;
}
const t = await talkFiles[0].text();
const d = JSON.parse(t);
const g = talk.Gallery.safeDecode(d);
if (!g.success) {
talkFileInvalid = true;
console.warn(g.error);
return;
}
for (const e of g.data.talk_gallery_list) {
readIDs.add(e.home_story_trigger_id);
}
}
const conversations = $derived(allConvos.filter((c) => !readIDs.has(c.id)));
const convoMap = $derived(byChara(conversations));
const selCharas = $derived(characters.filter((c) => convoMap.has(c.chara_id)));
const options = $derived(convoMap.get(chara?.chara_id ?? 0) ?? []); const options = $derived(convoMap.get(chara?.chara_id ?? 0) ?? []);
const cur = $derived(options.find((c) => c.number === convo)); const cur = $derived(options.find((c) => c.number === convo));
const charaIDs = $derived(cur != null ? convoCharas(cur) : []); const charaIDs = $derived(cur != null ? convoCharas(cur) : []);
@@ -73,12 +47,12 @@
</script> </script>
<h1 class="text-4xl">Lobby Conversations</h1> <h1 class="text-4xl">Lobby Conversations</h1>
<div class="mx-auto mt-8 grid grid-cols-1 gap-4 rounded-md p-4 text-center shadow-md ring md:max-w-xl md:grid-cols-6"> <div class="mx-auto mt-8 flex flex-col rounded-md text-center shadow-md ring md:max-w-xl md:flex-row">
<div class="md:col-span-3"> <div class="m-4 flex-1 md:mt-3">
<label for="chara" class="hidden md:inline">Character</label> <label for="chara" class="hidden md:inline">Character</label>
<CharaPick id="chara" characters={selCharas} class="w-full" bind:value={chara} required /> <CharaPick id="chara" characters={selCharas} class="w-full" bind:value={chara} required />
</div> </div>
<div class="md:col-span-3"> <div class="m-4 flex-1 md:mt-3">
<label for="convo" class="hidden md:inline">Conversation</label> <label for="convo" class="hidden md:inline">Conversation</label>
<select id="convo" bind:value={convo} class="w-full"> <select id="convo" bind:value={convo} class="w-full">
{#each options as opt (opt.number)} {#each options as opt (opt.number)}
@@ -86,29 +60,6 @@
{/each} {/each}
</select> </select>
</div> </div>
<div class="col-span-full">
<label for="talkgallery" class="block">
Select
<a href="https://werseter.github.io/umadump/" target="_blank" rel="noopener noreferrer">
<span class="inline-block font-mono">talk_gallery_data.json</span>
</a>
to hide read conversations.
</label>
<input
type="file"
class="rounded-full p-4"
accept="application/json"
id="talkgallery"
bind:files={() => talkFiles ?? new DataTransfer().files, setRead}
/>
<button
class="ml-4 rounded-full bg-mist-300 p-2 hover:cursor-pointer dark:bg-mist-900"
onclick={() => setRead(new DataTransfer().files)}>Reset</button
>
</div>
{#if talkFileInvalid}
<div class="col-span-full">Invalid File</div>
{/if}
</div> </div>
{#if cur} {#if cur}
<div class="shadow-sm transition-shadow hover:shadow-md"> <div class="shadow-sm transition-shadow hover:shadow-md">
+29 -11
View File
@@ -26,7 +26,6 @@
import Article from '../Article.svelte'; import Article from '../Article.svelte';
import Sec from '../Sec.svelte'; import Sec from '../Sec.svelte';
import * as skill from '$lib/data/skill'; import * as skill from '$lib/data/skill';
import RaceLenSlider from '$lib/RaceLenSlider.svelte';
let raceLen = $state(2000); let raceLen = $state(2000);
let secSpeedStyle = $state(RunningStyle.FrontRunner); let secSpeedStyle = $state(RunningStyle.FrontRunner);
@@ -225,10 +224,10 @@
}, },
]); ]);
const fcSeries: ComputedSeries[] = $derived([ const fcSeries: ComputedSeries[] = $derived([
{ label: 'Front Runner', y: (x, raw) => conservePowerAccel(distance(raceLen), RunningStyle.FrontRunner, raw, x) }, { label: 'Front Runner', y: (x) => conservePowerAccel(distance(raceLen), RunningStyle.FrontRunner, x) },
{ label: 'Pace Chaser', y: (x, raw) => conservePowerAccel(distance(raceLen), RunningStyle.PaceChaser, raw, x) }, { label: 'Pace Chaser', y: (x) => conservePowerAccel(distance(raceLen), RunningStyle.PaceChaser, x) },
{ label: 'Late Surger', y: (x, raw) => conservePowerAccel(distance(raceLen), RunningStyle.LateSurger, raw, x) }, { label: 'Late Surger', y: (x) => conservePowerAccel(distance(raceLen), RunningStyle.LateSurger, x) },
{ label: 'End Closer', y: (x, raw) => conservePowerAccel(distance(raceLen), RunningStyle.EndCloser, raw, x) }, { label: 'End Closer', y: (x) => conservePowerAccel(distance(raceLen), RunningStyle.EndCloser, x) },
]); ]);
</script> </script>
@@ -515,13 +514,23 @@
plotOptions={{ color: { legend: true } }} plotOptions={{ color: { legend: true } }}
/> />
<div class="mx-auto flex w-full md:max-w-2xl"> <div class="mx-auto flex w-full md:max-w-2xl">
<label class="my-auto mr-2 hidden flex-none pr-2 text-right text-sm md:inline" for="secSpeedRaceLen">Race Length</label> <label class="my-auto hidden flex-none pr-2 text-right text-sm md:inline" for="secSpeedRaceLen">Race Length</label>
<RaceLenSlider bind:value={raceLen} /> <input
class="my-auto max-w-40 flex-1 md:flex-2"
type="range"
id="secSpeedRaceLen"
min="1000"
max="3600"
step="100"
bind:value={raceLen}
/>
<span class="my-auto flex-none pl-2 text-sm">{raceLen}m</span>
</div> </div>
</div> </div>
<p> <p>
Rushed and <a href="#spot-struggle">spot struggle</a> reduce both the duration and effectiveness of full charge, potentially to The exact mechanics of conserving power are unclear at this time. What has been confirmed is that rushed and <a
zero. In practice, this appears to be a concern primarily on sprints, which already have a -55% penalty to full charge duration. href="#spot-struggle">spot struggle</a
> both reduce the chance for it to fire.
</p> </p>
<Sec h={3} id="lane-combo">Lane Combo</Sec> <Sec h={3} id="lane-combo">Lane Combo</Sec>
@@ -661,8 +670,17 @@
plotOptions={{ color: { legend: true } }} plotOptions={{ color: { legend: true } }}
/> />
<div class="mx-auto flex w-full md:max-w-2xl"> <div class="mx-auto flex w-full md:max-w-2xl">
<label class="my-auto mr-2 hidden flex-1 pr-2 text-right text-sm md:inline" for="secSpeedRaceLen">Race Length</label> <label class="my-auto hidden flex-1 pr-2 text-right text-sm md:inline" for="secSpeedRaceLen">Race Length</label>
<RaceLenSlider bind:value={raceLen} /> <input
class="my-auto max-w-40 flex-1 md:flex-2"
type="range"
id="secSpeedRaceLen"
min="1000"
max="3600"
step="100"
bind:value={raceLen}
/>
<span class="my-auto flex-1 pl-2 text-sm">{raceLen}m</span>
<label class="my-auto hidden flex-1 pr-2 text-right text-sm md:inline" for="secSpeedStyle">Style</label> <label class="my-auto hidden flex-1 pr-2 text-right text-sm md:inline" for="secSpeedStyle">Style</label>
<select class="my-auto flex-1 text-sm" id="secSpeedStyle" bind:value={secSpeedStyle}> <select class="my-auto flex-1 text-sm" id="secSpeedStyle" bind:value={secSpeedStyle}>
{#each RUNNING_STYLES as [name, style] (style)} {#each RUNNING_STYLES as [name, style] (style)}
+16 -15
View File
@@ -5,7 +5,6 @@
import * as race from '$lib/race'; import * as race from '$lib/race';
import StatChart from '$lib/StatChart.svelte'; import StatChart from '$lib/StatChart.svelte';
import { resolve } from '$app/paths'; import { resolve } from '$app/paths';
import RaceLenSlider from '$lib/RaceLenSlider.svelte';
function modemean(x: number, bonus: number, p: number): number { function modemean(x: number, bonus: number, p: number): number {
return x * (1 - p) + x * bonus * p; return x * (1 - p) + x * bonus * p;
@@ -59,10 +58,10 @@
]); ]);
const laneChange: ComputedSeries[] = [{ label: 'Lane Change Target Speed', y: (x) => race.laneChangeSpeed(x) }]; const laneChange: ComputedSeries[] = [{ label: 'Lane Change Target Speed', y: (x) => race.laneChangeSpeed(x) }];
const conservePower: Array<ComputedSeries | null> = $derived([ const conservePower: Array<ComputedSeries | null> = $derived([
{ label: 'Conserved Power', y: (x, raw) => race.conservePowerAccel(distanceType, style, raw, x, 0, false, false) }, { label: 'Conserved Power', y: (_raw, x) => race.conservePowerAccel(distanceType, style, x, 0, false, false) },
{ label: 'Rushed', y: (x, raw) => race.conservePowerAccel(distanceType, style, raw, x, 0, false, true) }, { label: 'Rushed', y: (_raw, x) => race.conservePowerAccel(distanceType, style, x, 0, false, true) },
styleIsFront styleIsFront
? { label: 'Spot Struggled', y: (x, raw) => race.conservePowerAccel(distanceType, style, raw, x, 0, true, false) } ? { label: 'Spot Struggled', y: (_raw, x) => race.conservePowerAccel(distanceType, style, x, 0, true, false) }
: null, : null,
]); ]);
const gutsSpurt: Array<ComputedSeries | null> = $derived([ const gutsSpurt: Array<ComputedSeries | null> = $derived([
@@ -175,9 +174,16 @@
/> />
<div class="mx-auto grid w-full grid-cols-3 gap-y-6 md:max-w-2xl md:grid-cols-7"> <div class="mx-auto grid w-full grid-cols-3 gap-y-6 md:max-w-2xl md:grid-cols-7">
{#if pick?.len} {#if pick?.len}
<div class="col-span-3 my-auto mr-4"> <input
<RaceLenSlider bind:value={raceLen} /> class="col-span-2 my-auto w-full justify-self-center-safe"
</div> type="range"
id="raceLen"
min="1000"
max="3600"
step="100"
bind:value={raceLen}
/>
<span class="col-span-1 my-auto pl-2 text-sm">{raceLen}m</span>
{/if} {/if}
{#if pick?.style} {#if pick?.style}
<select class="col-span-1 my-auto text-sm md:col-span-2" id="stylePick" bind:value={style}> <select class="col-span-1 my-auto text-sm md:col-span-2" id="stylePick" bind:value={style}>
@@ -323,16 +329,11 @@
Bonus acceleration at spurt start from the Charge Up/Fully Charged mechanic. Scales with base power plus passive skills, so Bonus acceleration at spurt start from the Charge Up/Fully Charged mechanic. Scales with base power plus passive skills, so
modifiers for ground conditions and career mode do not apply. modifiers for ground conditions and career mode do not apply.
</p> </p>
{@render statChart(race.Stat.Power, conservePower, 'Acceleration (m/s²)', [0, 0.35], { len: true, style: true })}
<p> <p>
The duration of Fully Charged does not depend directly on stats, but rather on a hidden <i>conserved power</i> value which This mechanic does not appear to be well understood, in particular how long the acceleration bonus lasts and how the rushed
increases per horse throughout the race. Conserved power does not increase while <a href="#spot-struggle">spot struggling</a>, and spot struggle multipliers apply.
so for front runners, the duration may scale negatively with guts. It also does not increase while running in any
<a href="#poskeep">position keep mode</a>
other than pace-down, so its duration may have a very slight negative correlation with wit.
<a href="#rushed">Rushed</a> additionally prevents accumulating conserved power, so full charge duration may scale positively with
wit amortized across races. Otherwise, power conservation rate depends on running style.
</p> </p>
{@render statChart(race.Stat.Power, conservePower, 'Acceleration (m/s²)', [0, 0.35], { len: true, style: true })}
<Sec h={2} id="guts">Guts</Sec> <Sec h={2} id="guts">Guts</Sec>
+10 -4
View File
@@ -4,7 +4,6 @@
import * as accel from '$lib/racelib/accel'; import * as accel from '$lib/racelib/accel';
import type { Attachment } from 'svelte/attachments'; import type { Attachment } from 'svelte/attachments';
import CalcInfo from '../CalcInfo.svelte'; import CalcInfo from '../CalcInfo.svelte';
import RaceLenSlider from '$lib/RaceLenSlider.svelte';
let rawSpeed = $state(1200); let rawSpeed = $state(1200);
let rawPower = $state(1200); let rawPower = $state(1200);
@@ -232,9 +231,16 @@
</div> </div>
<div class="mb-auto grid max-w-4xl grid-cols-6 rounded-md text-center shadow-md ring md:col-span-6"> <div class="mb-auto grid max-w-4xl grid-cols-6 rounded-md text-center shadow-md ring md:col-span-6">
<div class="col-span-full m-4"> <input
<RaceLenSlider bind:value={raceLen} /> class="col-span-4 m-4 w-full justify-self-center-safe"
</div> type="range"
id="raceLen"
min="1000"
max="3600"
step="100"
bind:value={raceLen}
/>
<span class="col-span-2 my-auto mb-4 pl-2 text-sm">{raceLen}m</span>
<select id="surface" bind:value={surface} class="col-span-2 mx-2 mb-4"> <select id="surface" bind:value={surface} class="col-span-2 mx-2 mb-4">
<option value={race.Surface.Turf}>Turf</option> <option value={race.Surface.Turf}>Turf</option>
<option value={race.Surface.Dirt}>Dirt</option> <option value={race.Surface.Dirt}>Dirt</option>
+10 -4
View File
@@ -4,7 +4,6 @@
import * as accel from '$lib/racelib/accel'; import * as accel from '$lib/racelib/accel';
import type { Attachment } from 'svelte/attachments'; import type { Attachment } from 'svelte/attachments';
import CalcInfo from '../CalcInfo.svelte'; import CalcInfo from '../CalcInfo.svelte';
import RaceLenSlider from '$lib/RaceLenSlider.svelte';
let rawPower = $state(1200); let rawPower = $state(1200);
let rawGuts = $state(1200); let rawGuts = $state(1200);
@@ -217,9 +216,16 @@
</div> </div>
</div> </div>
<div class="mb-auto grid max-w-4xl grid-cols-6 rounded-md text-center shadow-md ring md:col-span-3"> <div class="mb-auto grid max-w-4xl grid-cols-6 rounded-md text-center shadow-md ring md:col-span-3">
<div class="col-span-6 mx-4 mt-4"> <input
<RaceLenSlider bind:value={raceLen} /> class="col-span-4 m-4 w-full justify-self-center-safe"
</div> type="range"
id="raceLen"
min="1000"
max="3600"
step="100"
bind:value={raceLen}
/>
<span class="col-span-2 my-auto mb-4 pl-2 text-sm">{raceLen}m</span>
<select id="surface" bind:value={surface} class="col-span-2 m-4"> <select id="surface" bind:value={surface} class="col-span-2 m-4">
<option value={race.Surface.Turf}>Turf</option> <option value={race.Surface.Turf}>Turf</option>
<option value={race.Surface.Dirt}>Dirt</option> <option value={race.Surface.Dirt}>Dirt</option>
+2 -2
View File
@@ -18,7 +18,6 @@
import StatChart from '$lib/StatChart.svelte'; import StatChart from '$lib/StatChart.svelte';
import { resolve } from '$app/paths'; import { resolve } from '$app/paths';
import SpeedDur from './SpeedDur.svelte'; import SpeedDur from './SpeedDur.svelte';
import RaceLenSlider from '$lib/RaceLenSlider.svelte';
let rawPower = $state(1200); let rawPower = $state(1200);
let rawGuts = $state(1200); let rawGuts = $state(1200);
@@ -113,7 +112,8 @@
</select> </select>
</div> </div>
<div class="m-4 md:col-span-2 md:col-start-2"> <div class="m-4 md:col-span-2 md:col-start-2">
<RaceLenSlider bind:value={raceLen} /> <label for="raceLen">Race Distance</label>
<input type="number" id="raceLen" required min="1000" max="3600" step="100" bind:value={raceLen} class="w-full" />
</div> </div>
<div class="m-4 self-center md:col-span-2"> <div class="m-4 self-center md:col-span-2">
<label for="isRunaway" class="mr-1 align-middle">Runaway</label> <label for="isRunaway" class="mr-1 align-middle">Runaway</label>
+2 -2
View File
@@ -1,7 +1,6 @@
<script lang="ts"> <script lang="ts">
import type { ComputedSeries } from '$lib/chart'; import type { ComputedSeries } from '$lib/chart';
import * as race from '$lib/race'; import * as race from '$lib/race';
import RaceLenSlider from '$lib/RaceLenSlider.svelte';
import StatChart from '$lib/StatChart.svelte'; import StatChart from '$lib/StatChart.svelte';
import CalcInfo from '../CalcInfo.svelte'; import CalcInfo from '../CalcInfo.svelte';
@@ -99,7 +98,8 @@
</select> </select>
</div> </div>
<div class="m-4 md:col-start-2"> <div class="m-4 md:col-start-2">
<RaceLenSlider bind:value={raceLen} /> <label for="raceLen">Race Distance</label>
<input type="number" id="raceLen" required min="1000" max="3600" step="100" bind:value={raceLen} class="w-full" />
</div> </div>
<div class="m-4 self-center"> <div class="m-4 self-center">
<label for="isCareer" class="mr-1 align-middle">In Career</label> <label for="isCareer" class="mr-1 align-middle">In Career</label>
+10 -2
View File
@@ -1,7 +1,6 @@
<script lang="ts"> <script lang="ts">
import * as course from '$lib/course'; import * as course from '$lib/course';
import * as race from '$lib/race'; import * as race from '$lib/race';
import RaceLenSlider from '$lib/RaceLenSlider.svelte';
import * as recovery from '$lib/racelib/recovery'; import * as recovery from '$lib/racelib/recovery';
import HPChart from './HPChart.svelte'; import HPChart from './HPChart.svelte';
import InfoPanels from './InfoPanels.svelte'; import InfoPanels from './InfoPanels.svelte';
@@ -278,7 +277,16 @@
</div> </div>
<div class="mx-auto my-2 grid max-w-5xl grid-cols-1 gap-4 rounded-md text-center shadow-md ring md:grid-cols-6"> <div class="mx-auto my-2 grid max-w-5xl grid-cols-1 gap-4 rounded-md text-center shadow-md ring md:grid-cols-6">
<div class="m-4 flex md:col-span-2"> <div class="m-4 flex md:col-span-2">
<RaceLenSlider bind:value={raceLen} /> <input
class="col-span-2 my-auto w-full justify-self-center-safe"
type="range"
id="raceLen"
min="1000"
max="3600"
step="100"
bind:value={raceLen}
/>
<span class="my-auto pl-2 text-sm">{raceLen}m</span>
</div> </div>
<select id="surface" class="m-4" bind:value={surface}> <select id="surface" class="m-4" bind:value={surface}>
<option value={race.Surface.Turf}>Turf</option> <option value={race.Surface.Turf}>Turf</option>