Compare commits
23 Commits
fca43e64b5
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 48118efeeb | |||
| 79d4e76090 | |||
| ea39be0510 | |||
| 7bac55ca43 | |||
| 435da61405 | |||
| f3e7c7b43b | |||
| eded415df4 | |||
| 34029fe001 | |||
| ffb700545f | |||
| 70ad09a13e | |||
| 74c22c7313 | |||
| 2c1b4b9261 | |||
| e5128b6783 | |||
| b22c8307fd | |||
| cb54bbe414 | |||
| 096cff28b6 | |||
| 197df8f022 | |||
| 009dd1d695 | |||
| bdafc93315 | |||
| ef5b06d66e | |||
| 65b81612bc | |||
| 6269665e79 | |||
| cae7d5b8f4 |
@@ -2,6 +2,7 @@ when:
|
|||||||
- event: push
|
- event: push
|
||||||
branch: main
|
branch: main
|
||||||
- event: pull_request
|
- event: pull_request
|
||||||
|
- event: manual
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: test
|
- name: test
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
when:
|
when:
|
||||||
- event: push
|
- event: push
|
||||||
branch: main
|
branch: main
|
||||||
path: 'zenno/**'
|
|
||||||
- event: pull_request
|
- event: pull_request
|
||||||
path: 'zenno/**'
|
- event: manual
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: test
|
- name: test
|
||||||
|
|||||||
+12
-2
@@ -1,10 +1,20 @@
|
|||||||
# horsebot
|
# horsebot
|
||||||
|
|
||||||
Discord bot serving horse game data.
|
Website and Discord bot serving horse game data.
|
||||||
|
|
||||||
Production instance is named Zenno Rob Roy, because she has read all about Umamusume and is always happy to share her knowledge and give recommendations.
|
Production instance is named Zenno Rob Roy, because she has read all about Umamusume and is always happy to share her knowledge and give recommendations.
|
||||||
|
|
||||||
## Running
|
The `-mdb` flag is mandatory for both website and Discord bot modes.
|
||||||
|
It provides data for both the horsebot API and the discord bot.
|
||||||
|
|
||||||
|
## Running the Website
|
||||||
|
|
||||||
|
If the `-public` flag is provided, its value gives the directory to serve at `/`.
|
||||||
|
The `-img` flag gives a separate directory into which images are synced from GameTora; obtain permission from Gertas prior to using it.
|
||||||
|
The images are served on `/img/`.
|
||||||
|
|
||||||
|
## Running the Discord Bot
|
||||||
|
|
||||||
|
Provide the `-token` flag pointing to a file containing the token.
|
||||||
The bot always uses the Gateway API.
|
The bot always uses the Gateway API.
|
||||||
If the `-http` argument is provided, it will also use the HTTP API, and `-key` must also be provided.
|
If the `-http` argument is provided, it will also use the HTTP API, and `-key` must also be provided.
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ func main() {
|
|||||||
addr string
|
addr string
|
||||||
mdbf string
|
mdbf string
|
||||||
public string
|
public string
|
||||||
|
img string
|
||||||
// discord
|
// discord
|
||||||
tokenFile string
|
tokenFile string
|
||||||
apiRoute string
|
apiRoute string
|
||||||
@@ -47,6 +48,7 @@ func main() {
|
|||||||
flag.StringVar(&addr, "http", ":13669", "`address` to bind HTTP server")
|
flag.StringVar(&addr, "http", ":13669", "`address` to bind HTTP server")
|
||||||
flag.StringVar(&mdbf, "mdb", "", "`path` to master.mdb")
|
flag.StringVar(&mdbf, "mdb", "", "`path` to master.mdb")
|
||||||
flag.StringVar(&public, "public", "", "`dir`ectory containing the website to serve")
|
flag.StringVar(&public, "public", "", "`dir`ectory containing the website to serve")
|
||||||
|
flag.StringVar(&img, "img", "", "`dir`ectory to save and serve images")
|
||||||
flag.StringVar(&tokenFile, "token", "", "`file` containing the Discord bot token")
|
flag.StringVar(&tokenFile, "token", "", "`file` containing the Discord bot token")
|
||||||
flag.StringVar(&apiRoute, "route", "/interactions/callback", "`path` to serve Discord HTTP API calls")
|
flag.StringVar(&apiRoute, "route", "/interactions/callback", "`path` to serve Discord HTTP API calls")
|
||||||
flag.StringVar(&pubkey, "key", "", "Discord public key")
|
flag.StringVar(&pubkey, "key", "", "Discord public key")
|
||||||
@@ -86,6 +88,17 @@ func main() {
|
|||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if img != "" {
|
||||||
|
// Ensure the img directory is in fact a directory
|
||||||
|
// before we start trying to fill it out.
|
||||||
|
err := os.MkdirAll(img, 0777)
|
||||||
|
if err != nil {
|
||||||
|
slog.Error("img", slog.Any("err", err))
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
go saveImgs(ctx, db, img)
|
||||||
|
}
|
||||||
|
|
||||||
var client *bot.Client
|
var client *bot.Client
|
||||||
if tokenFile != "" {
|
if tokenFile != "" {
|
||||||
skills, err := mdb.Skills(ctx, db)
|
skills, err := mdb.Skills(ctx, db)
|
||||||
@@ -128,6 +141,9 @@ func main() {
|
|||||||
if public != "" {
|
if public != "" {
|
||||||
mux.Handle("GET /", httpmiddle.Compress(5)(http.FileServerFS(os.DirFS(public))))
|
mux.Handle("GET /", httpmiddle.Compress(5)(http.FileServerFS(os.DirFS(public))))
|
||||||
}
|
}
|
||||||
|
if img != "" {
|
||||||
|
mux.Handle("GET /img/", http.StripPrefix("/img", http.FileServerFS(os.DirFS(img))))
|
||||||
|
}
|
||||||
mux.Handle("GET /api/global/affinity", httpmiddle.Compress(5)(dataroute(ctx, db, mdb.AffinitySummary)))
|
mux.Handle("GET /api/global/affinity", httpmiddle.Compress(5)(dataroute(ctx, db, mdb.AffinitySummary)))
|
||||||
mux.Handle("GET /api/global/affinity-detail", httpmiddle.Compress(5)(dataroute(ctx, db, mdb.AffinityDetail)))
|
mux.Handle("GET /api/global/affinity-detail", httpmiddle.Compress(5)(dataroute(ctx, db, mdb.AffinityDetail)))
|
||||||
mux.Handle("GET /api/global/character", httpmiddle.Compress(5)(dataroute(ctx, db, mdb.Characters)))
|
mux.Handle("GET /api/global/character", httpmiddle.Compress(5)(dataroute(ctx, db, mdb.Characters)))
|
||||||
|
|||||||
@@ -0,0 +1,173 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"image"
|
||||||
|
"image/png"
|
||||||
|
"log/slog"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/gen2brain/avif"
|
||||||
|
"golang.org/x/image/draw"
|
||||||
|
"golang.org/x/sync/errgroup"
|
||||||
|
"zombiezen.com/go/sqlite"
|
||||||
|
"zombiezen.com/go/sqlite/sqlitex"
|
||||||
|
)
|
||||||
|
|
||||||
|
func saveImgs(ctx context.Context, db *sqlitex.Pool, out string) {
|
||||||
|
skill, err := skillIconIDs(ctx, db)
|
||||||
|
if err != nil {
|
||||||
|
slog.Error("skills", slog.Any("err", err))
|
||||||
|
}
|
||||||
|
chara, err := charaIDs(ctx, db)
|
||||||
|
if err != nil {
|
||||||
|
slog.Error("characters", slog.Any("err", err))
|
||||||
|
}
|
||||||
|
rank, err := rankIDs(ctx, db)
|
||||||
|
if err != nil {
|
||||||
|
slog.Error("ranks", slog.Any("err", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
g, ctx := errgroup.WithContext(ctx)
|
||||||
|
tick := time.NewTicker(250 * time.Millisecond)
|
||||||
|
idouts(ctx, g, tick, skill, filepath.Join(out, "skill"), "https://media.gametora.com/umamusume/skills/icon/%d.png", image.Pt(64, 64), image.Pt(32, 32), image.Pt(16, 16))
|
||||||
|
idouts(ctx, g, tick, chara, filepath.Join(out, "chara"), "https://gametora.com/images/umamusume/characters/icons/chr_icon_%d.png", image.Pt(256, 256), image.Pt(64, 64), image.Pt(32, 32), image.Pt(16, 16))
|
||||||
|
idouts(ctx, g, tick, rank, filepath.Join(out, "rank"), "https://media.gametora.com/umamusume/ui/rank/%02d.png", image.Pt(128, 128), image.Pt(64, 64), image.Pt(32, 32), image.Pt(16, 16))
|
||||||
|
idouts(ctx, g, tick, rank, filepath.Join(out, "rank", "simple"), "https://media.gametora.com/umamusume/ui/rank/simple/%02d.png", image.Pt(50, 50), image.Pt(32, 32), image.Pt(16, 16))
|
||||||
|
if err := g.Wait(); err != nil {
|
||||||
|
slog.Error("output", slog.Any("err", err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// load scans all results of sql and appends them to r.
|
||||||
|
func load[T any](ctx context.Context, db *sqlitex.Pool, r []T, sql string, row func(*sqlite.Stmt) T) ([]T, error) {
|
||||||
|
conn, err := db.Take(ctx)
|
||||||
|
defer db.Put(conn)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
stmt, err := conn.Prepare(sql)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for {
|
||||||
|
ok, err := stmt.Step()
|
||||||
|
if err != nil {
|
||||||
|
return r, err
|
||||||
|
}
|
||||||
|
if !ok {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
r = append(r, row(stmt))
|
||||||
|
}
|
||||||
|
return r, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func skillIconIDs(ctx context.Context, db *sqlitex.Pool) ([]int32, error) {
|
||||||
|
return load(ctx, db, nil, "SELECT DISTINCT icon_id FROM skill_data", func(s *sqlite.Stmt) int32 {
|
||||||
|
return s.ColumnInt32(0)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func charaIDs(ctx context.Context, db *sqlitex.Pool) ([]int32, error) {
|
||||||
|
return load(ctx, db, nil, "SELECT id FROM chara_data", func(s *sqlite.Stmt) int32 {
|
||||||
|
return s.ColumnInt32(0)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func rankIDs(ctx context.Context, db *sqlitex.Pool) ([]int32, error) {
|
||||||
|
return load(ctx, db, nil, "SELECT id-1 FROM single_mode_rank", func(s *sqlite.Stmt) int32 {
|
||||||
|
return s.ColumnInt32(0)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
var pngenc = &png.Encoder{CompressionLevel: png.BestCompression}
|
||||||
|
|
||||||
|
func idouts(ctx context.Context, g *errgroup.Group, tick *time.Ticker, ids []int32, p, urlf string, sizes ...image.Point) {
|
||||||
|
for _, id := range ids {
|
||||||
|
g.Go(func() error {
|
||||||
|
n := strconv.Itoa(int(id))
|
||||||
|
p := filepath.Join(p, n)
|
||||||
|
_, err := os.Stat(p)
|
||||||
|
if !errors.Is(err, os.ErrNotExist) {
|
||||||
|
slog.DebugContext(ctx, "exists", slog.String("path", p))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if err := os.MkdirAll(p, 0777); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
url := fmt.Sprintf(urlf, id)
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return ctx.Err()
|
||||||
|
case <-tick.C: // continue on
|
||||||
|
}
|
||||||
|
img, err := dlimg(ctx, url)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
slog.InfoContext(ctx, "resize", slog.String("url", url))
|
||||||
|
rs := resize(img, sizes...)
|
||||||
|
for _, r := range rs {
|
||||||
|
g.Go(func() error {
|
||||||
|
m := r.Bounds().Max
|
||||||
|
o := filepath.Join(p, fmt.Sprintf("%dx%d.png", m.X, m.Y))
|
||||||
|
slog.InfoContext(ctx, "write", slog.String("path", o))
|
||||||
|
f, err := os.Create(o)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
return pngenc.Encode(f, r)
|
||||||
|
})
|
||||||
|
g.Go(func() error {
|
||||||
|
m := r.Bounds().Max
|
||||||
|
o := filepath.Join(p, fmt.Sprintf("%dx%d.avif", m.X, m.Y))
|
||||||
|
slog.InfoContext(ctx, "write", slog.String("path", o))
|
||||||
|
f, err := os.Create(o)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
return avif.Encode(f, r)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func dlimg(ctx context.Context, url string) (image.Image, error) {
|
||||||
|
slog.InfoContext(ctx, "download", slog.String("url", url))
|
||||||
|
ctx, stop := context.WithTimeout(ctx, 5*time.Second)
|
||||||
|
defer stop()
|
||||||
|
req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
resp, err := http.DefaultClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
slog.InfoContext(ctx, "decode", slog.String("url", url))
|
||||||
|
img, _, err := image.Decode(resp.Body)
|
||||||
|
return img, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func resize(img image.Image, sizes ...image.Point) []image.Image {
|
||||||
|
rs := make([]image.Image, 0, len(sizes))
|
||||||
|
for _, sz := range sizes {
|
||||||
|
d := image.NewNRGBA(image.Rectangle{Min: image.Point{}, Max: sz})
|
||||||
|
draw.CatmullRom.Scale(d, d.Bounds(), img, img.Bounds(), draw.Src, nil)
|
||||||
|
rs = append(rs, d)
|
||||||
|
}
|
||||||
|
return rs
|
||||||
|
}
|
||||||
@@ -15,6 +15,9 @@ This file is my notes from exploring the database.
|
|||||||
- 119 is scenario full titles (e.g. The Beginning: URA Finale), 120 is scenario descriptions, 237 is scenario names (e.g. URA Finale)
|
- 119 is scenario full titles (e.g. The Beginning: URA Finale), 120 is scenario descriptions, 237 is scenario names (e.g. URA Finale)
|
||||||
- 130 is epithet names, 131 is epithet descriptions
|
- 130 is epithet names, 131 is epithet descriptions
|
||||||
|
|
||||||
|
in the decomp, text data categories are named in `public enum MasterString.Category`.
|
||||||
|
some categories seem to be missing, though.
|
||||||
|
|
||||||
# succession factor (sparks)
|
# succession factor (sparks)
|
||||||
|
|
||||||
tables are succession_factor and succession_factor_effect
|
tables are succession_factor and succession_factor_effect
|
||||||
@@ -317,6 +320,37 @@ so, it doesn't seem like there's a particular flag that identifies maiden races,
|
|||||||
- card_talent_hint_upgrade has costs to raise hint levels, but it's actually universal, only six rows
|
- card_talent_hint_upgrade has costs to raise hint levels, but it's actually universal, only six rows
|
||||||
- single_mode_route_race is career goals (not only races)
|
- single_mode_route_race is career goals (not only races)
|
||||||
- available_skill_set has starting skills including those unlocked by potential level given under need_rank (0 for pl1, 2 for pl2)
|
- available_skill_set has starting skills including those unlocked by potential level given under need_rank (0 for pl1, 2 for pl2)
|
||||||
|
- single_mode_special_chara is scenario links
|
||||||
|
|
||||||
|
# gl
|
||||||
|
|
||||||
|
single_mode_live_song_list has the song list for lives ha ha.
|
||||||
|
this is defined from the perspective of bonuses they grant when you have them, not the shop per se.
|
||||||
|
- text_data category for song names is 210, look up by live_id; for post-concert effect text, category 208 indexed by song list id
|
||||||
|
- command_id and live_id are always equal
|
||||||
|
- level is 1 for all songs except the two instances of girls' legend u
|
||||||
|
- master_bonus_content_text_id corresponds to text_data categories 207 for pickup effect text and 209 for song name
|
||||||
|
- live_bonus_type is 1 for specialty prio, 2 for chain frequency, 3 for fb
|
||||||
|
- girls' legend u is defined twice as ids 22 and 24
|
||||||
|
|
||||||
|
costs are in single_mode_live_square.
|
||||||
|
- square_type is 1 for stat up, 2 for skill up, 3 for energy up, 4 for live
|
||||||
|
- perf_type_n is token type: 1 for dance, 2 for passion, 3 for vocals, 4 for visuals, 5 for composure
|
||||||
|
- perf_value_n is number of tokens
|
||||||
|
|
||||||
|
immediate bonuses are in single_mode_live_master_bonus.
|
||||||
|
- master_bonus_type is 1 for stat up, 2 for skill hint, 3 for energy up, 4 for song
|
||||||
|
- master_bonus_type_value is 1 for type 1, live id for type 4, 0 otherwise
|
||||||
|
- master_bonus_gain_type_n is 1 for stat, 2 for skill hint, 3 for energy, 5 for permanent training bonus
|
||||||
|
+ 1 for stat: then master_bonus_gain_value_n_1 is 1-5 for stat id or 6 for sp, n_2 is 1 for fixed or 2 for random (not actually in the game), n_3 is lower bound on gain, n_4 is 0 for fixed or upper bound for random
|
||||||
|
+ 2 for skill hint: then gain_value_n_1 is 3(*), n_2 is skill tag, n_3 is hint level
|
||||||
|
+ 3 for energy: then gain_value_n_1 is amount
|
||||||
|
+ 5 for permanent training bonus: then gain_value_n_1 is stat id (6 for sp), n_2 is amount
|
||||||
|
(*) MasterSingleModeLiveMasterBonus.SingleModeLiveMasterBonus.GainSkillHintTypeEnum defines values of Random = 1 and SkillId = 2, but they are unused in the db.
|
||||||
|
|
||||||
|
there is some ostensibly unused data.
|
||||||
|
- master bonus ids 12001-12206 grant random amounts of stats
|
||||||
|
- id 20000 corresponds to "Group Lesson" "Skill hint appropriate for aptitude"
|
||||||
|
|
||||||
# lobby conversations!!!
|
# lobby conversations!!!
|
||||||
|
|
||||||
|
|||||||
@@ -4,32 +4,37 @@ go 1.25.5
|
|||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/disgoorg/disgo v0.19.6
|
github.com/disgoorg/disgo v0.19.6
|
||||||
github.com/go-chi/chi/v5 v5.3.0
|
github.com/gen2brain/avif v0.6.0
|
||||||
|
github.com/go-chi/chi/v5 v5.3.1
|
||||||
github.com/google/go-cmp v0.7.0
|
github.com/google/go-cmp v0.7.0
|
||||||
github.com/junegunn/fzf v0.73.1
|
github.com/junegunn/fzf v0.74.1
|
||||||
|
golang.org/x/image v0.44.0
|
||||||
|
golang.org/x/sync v0.22.0
|
||||||
zombiezen.com/go/sqlite v1.4.2
|
zombiezen.com/go/sqlite v1.4.2
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/disgoorg/godave v0.1.0 // indirect
|
github.com/disgoorg/godave v0.3.0 // indirect
|
||||||
github.com/disgoorg/json/v2 v2.0.0 // indirect
|
github.com/disgoorg/json/v2 v2.0.0 // indirect
|
||||||
github.com/disgoorg/omit v1.0.0 // indirect
|
github.com/disgoorg/omit v1.0.0 // indirect
|
||||||
github.com/disgoorg/snowflake/v2 v2.0.3 // indirect
|
github.com/disgoorg/snowflake/v2 v2.0.3 // indirect
|
||||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||||
|
github.com/ebitengine/purego v0.10.2 // indirect
|
||||||
github.com/google/uuid v1.6.0 // indirect
|
github.com/google/uuid v1.6.0 // indirect
|
||||||
github.com/gorilla/websocket v1.5.3 // indirect
|
github.com/gorilla/websocket v1.5.3 // indirect
|
||||||
github.com/junegunn/go-shellwords v0.0.0-20250127100254-2aa3b3277741 // indirect
|
github.com/junegunn/go-shellwords v0.0.0-20250127100254-2aa3b3277741 // indirect
|
||||||
github.com/klauspost/compress v1.18.6 // indirect
|
github.com/klauspost/compress v1.19.1 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.22 // indirect
|
github.com/mattn/go-isatty v0.0.24 // indirect
|
||||||
github.com/ncruces/go-strftime v1.0.0 // indirect
|
github.com/ncruces/go-strftime v1.0.0 // indirect
|
||||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||||
github.com/rivo/uniseg v0.4.7 // indirect
|
github.com/rivo/uniseg v0.4.7 // indirect
|
||||||
github.com/sasha-s/go-csync v0.0.0-20240107134140-fcbab37b09ad // indirect
|
github.com/sasha-s/go-csync v0.0.0-20240107134140-fcbab37b09ad // indirect
|
||||||
golang.org/x/crypto v0.53.0 // indirect
|
github.com/tetratelabs/wazero v1.12.0 // indirect
|
||||||
golang.org/x/sys v0.46.0 // indirect
|
golang.org/x/crypto v0.54.0 // indirect
|
||||||
golang.org/x/tools v0.46.0 // indirect
|
golang.org/x/sys v0.47.0 // indirect
|
||||||
modernc.org/libc v1.73.4 // indirect
|
golang.org/x/tools v0.48.0 // indirect
|
||||||
|
modernc.org/libc v1.74.3 // indirect
|
||||||
modernc.org/mathutil v1.7.1 // indirect
|
modernc.org/mathutil v1.7.1 // indirect
|
||||||
modernc.org/memory v1.11.0 // indirect
|
modernc.org/memory v1.11.0 // indirect
|
||||||
modernc.org/sqlite v1.52.0 // indirect
|
modernc.org/sqlite v1.54.0 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
|
|||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/disgoorg/disgo v0.19.6 h1:1xJQt6KZgiqvBOnjuziG1gE4Iqd0aJn8DNfE6eE+0qw=
|
github.com/disgoorg/disgo v0.19.6 h1:1xJQt6KZgiqvBOnjuziG1gE4Iqd0aJn8DNfE6eE+0qw=
|
||||||
github.com/disgoorg/disgo v0.19.6/go.mod h1:NnV63iw4lJdF1fnV0gX27XR43ZgRGqnL122svRMTgTE=
|
github.com/disgoorg/disgo v0.19.6/go.mod h1:NnV63iw4lJdF1fnV0gX27XR43ZgRGqnL122svRMTgTE=
|
||||||
github.com/disgoorg/godave v0.1.0 h1:3g0Zqzz+zNaxQTVLfCnl5eZKGqZk6cM/JLLbMKCtZQQ=
|
github.com/disgoorg/godave v0.3.0 h1:37F3ZuiMd8/EXeoCtTeE8iP2eT2nWsfEa4/ITwoKfe4=
|
||||||
github.com/disgoorg/godave v0.1.0/go.mod h1:OreAC3hpabr39bMVA+jwOVDq1EUPXH5A0XUBiZaDI1Y=
|
github.com/disgoorg/godave v0.3.0/go.mod h1:OreAC3hpabr39bMVA+jwOVDq1EUPXH5A0XUBiZaDI1Y=
|
||||||
github.com/disgoorg/json/v2 v2.0.0 h1:U16yy/ARK7/aEpzjjqK1b/KaqqGHozUdeVw/DViEzQI=
|
github.com/disgoorg/json/v2 v2.0.0 h1:U16yy/ARK7/aEpzjjqK1b/KaqqGHozUdeVw/DViEzQI=
|
||||||
github.com/disgoorg/json/v2 v2.0.0/go.mod h1:jZTBC0nIE1WeetSEI3/Dka8g+qglb4FPVmp5I5HpEfI=
|
github.com/disgoorg/json/v2 v2.0.0/go.mod h1:jZTBC0nIE1WeetSEI3/Dka8g+qglb4FPVmp5I5HpEfI=
|
||||||
github.com/disgoorg/omit v1.0.0 h1:y0LkVUOyUHT8ZlnhIAeOZEA22UYykeysK8bLJ0SfT78=
|
github.com/disgoorg/omit v1.0.0 h1:y0LkVUOyUHT8ZlnhIAeOZEA22UYykeysK8bLJ0SfT78=
|
||||||
@@ -12,8 +12,12 @@ github.com/disgoorg/snowflake/v2 v2.0.3 h1:3B+PpFjr7j4ad7oeJu4RlQ+nYOTadsKapJIzg
|
|||||||
github.com/disgoorg/snowflake/v2 v2.0.3/go.mod h1:W6r7NUA7DwfZLwr00km6G4UnZ0zcoLBRufhkFWgAc4c=
|
github.com/disgoorg/snowflake/v2 v2.0.3/go.mod h1:W6r7NUA7DwfZLwr00km6G4UnZ0zcoLBRufhkFWgAc4c=
|
||||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||||
github.com/go-chi/chi/v5 v5.3.0 h1:halUjDxhshgXHMrao5bB8eNBXo/rnzwr8m5m36glehM=
|
github.com/ebitengine/purego v0.10.2 h1:W809HbnvzAxgdm+aOvlSekrM16wGCdT/e76+9tS7gzE=
|
||||||
github.com/go-chi/chi/v5 v5.3.0/go.mod h1:R+tYY2hNuVUUjxoPtqUdgBqevM9s9njzkTLutVsOCto=
|
github.com/ebitengine/purego v0.10.2/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
|
||||||
|
github.com/gen2brain/avif v0.6.0 h1:/8WSgcU+IEF0jhKYsUZ/mzlziFuTeJFpIKBj2siTQps=
|
||||||
|
github.com/gen2brain/avif v0.6.0/go.mod h1:QgrYqdVE9y40PCfArK9VakcMIpYeDYpZmCSLkW6C1n8=
|
||||||
|
github.com/go-chi/chi/v5 v5.3.1 h1:3j4HZLGZQ3JpMCrPJF/Jl3mYJfWLKBfNJ6quurUGCf8=
|
||||||
|
github.com/go-chi/chi/v5 v5.3.1/go.mod h1:R+tYY2hNuVUUjxoPtqUdgBqevM9s9njzkTLutVsOCto=
|
||||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
@@ -22,14 +26,14 @@ github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aN
|
|||||||
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||||
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
|
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
|
||||||
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
|
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
|
||||||
github.com/junegunn/fzf v0.73.1 h1:Ni9jaVMxsehcx9IwJ4ZAL/sr2c4dOEg1DHIAwPUVlyo=
|
github.com/junegunn/fzf v0.74.1 h1:rCGVdCSoOkfcfTxtxCO/vG2yceDjRlKSlv7IfCGkc/Y=
|
||||||
github.com/junegunn/fzf v0.73.1/go.mod h1:hKDkO8orBWT5VLIZOL8oyhxn7mRLXdGyu+gz1Ss58pU=
|
github.com/junegunn/fzf v0.74.1/go.mod h1:hKDkO8orBWT5VLIZOL8oyhxn7mRLXdGyu+gz1Ss58pU=
|
||||||
github.com/junegunn/go-shellwords v0.0.0-20250127100254-2aa3b3277741 h1:7dYDtfMDfKzjT+DVfIS4iqknSEKtZpEcXtu6vuaasHs=
|
github.com/junegunn/go-shellwords v0.0.0-20250127100254-2aa3b3277741 h1:7dYDtfMDfKzjT+DVfIS4iqknSEKtZpEcXtu6vuaasHs=
|
||||||
github.com/junegunn/go-shellwords v0.0.0-20250127100254-2aa3b3277741/go.mod h1:6EILKtGpo5t+KLb85LNZLAF6P9LKp78hJI80PXMcn3c=
|
github.com/junegunn/go-shellwords v0.0.0-20250127100254-2aa3b3277741/go.mod h1:6EILKtGpo5t+KLb85LNZLAF6P9LKp78hJI80PXMcn3c=
|
||||||
github.com/klauspost/compress v1.18.6 h1:2jupLlAwFm95+YDR+NwD2MEfFO9d4z4Prjl1XXDjuao=
|
github.com/klauspost/compress v1.19.1 h1:VsB4HPswih7mmZ8WleSFQ75c/Ui1M4trX5oAsJnhSlk=
|
||||||
github.com/klauspost/compress v1.18.6/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ=
|
github.com/klauspost/compress v1.19.1/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ=
|
||||||
github.com/mattn/go-isatty v0.0.22 h1:j8l17JJ9i6VGPUFUYoTUKPSgKe/83EYU2zBC7YNKMw4=
|
github.com/mattn/go-isatty v0.0.24 h1:tGZZoVgT/KiqK1c8ocVLeDS8BSWMRd47J3Lbz7vsReI=
|
||||||
github.com/mattn/go-isatty v0.0.22/go.mod h1:ZXfXG4SQHsB/w3ZeOYbR0PrPwLy+n6xiMrJlRFqopa4=
|
github.com/mattn/go-isatty v0.0.24/go.mod h1:nMCL3Zebbrt45jsMDgnfIwz6ydEQApk5oEI3HqDio6A=
|
||||||
github.com/ncruces/go-strftime v1.0.0 h1:HMFp8mLCTPp341M/ZnA4qaf7ZlsbTc+miZjCLOFAw7w=
|
github.com/ncruces/go-strftime v1.0.0 h1:HMFp8mLCTPp341M/ZnA4qaf7ZlsbTc+miZjCLOFAw7w=
|
||||||
github.com/ncruces/go-strftime v1.0.0/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
|
github.com/ncruces/go-strftime v1.0.0/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
@@ -42,34 +46,38 @@ github.com/sasha-s/go-csync v0.0.0-20240107134140-fcbab37b09ad h1:qIQkSlF5vAUHxE
|
|||||||
github.com/sasha-s/go-csync v0.0.0-20240107134140-fcbab37b09ad/go.mod h1:/pA7k3zsXKdjjAiUhB5CjuKib9KJGCaLvZwtxGC8U0s=
|
github.com/sasha-s/go-csync v0.0.0-20240107134140-fcbab37b09ad/go.mod h1:/pA7k3zsXKdjjAiUhB5CjuKib9KJGCaLvZwtxGC8U0s=
|
||||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
golang.org/x/crypto v0.53.0 h1:QZ4Muo8THX6CizN2vPPd5fBGHyogrdK9fG4wLPFUsto=
|
github.com/tetratelabs/wazero v1.12.0 h1:DuWcpNu/FzgEXgGBDp8J1Spc+CWOvvtvVyjKlaZopYU=
|
||||||
golang.org/x/crypto v0.53.0/go.mod h1:DNLU434OwVakk9PzuwV8w62mAJpRJL3vsgcfp4Qnsio=
|
github.com/tetratelabs/wazero v1.12.0/go.mod h1:LvKtzl2RqO4gyF27BiXU+nKAjcV8f38U+kP/q2vgxh0=
|
||||||
golang.org/x/mod v0.37.0 h1:vF1DjpVEshcIqoEaauuHebaLk1O1forxjxBaVn884JQ=
|
golang.org/x/crypto v0.54.0 h1:YLIA59K4fiNzHzjnZt2tUJQjQtUWfWbeHBqKtk3eScw=
|
||||||
golang.org/x/mod v0.37.0/go.mod h1:m8S8VeM9r4dzDwjrKO0a1sZP3YjeMamRRlD+fmR2Q/0=
|
golang.org/x/crypto v0.54.0/go.mod h1:KWL8ny2AZdGR2cWmzeHrp2azQPGogOv+HeQaVEXC2dk=
|
||||||
golang.org/x/sync v0.21.0 h1:HLII4xRRTtCRkxYp4HNFF0Js/Og6q2i++KXbg0gHCwM=
|
golang.org/x/image v0.44.0 h1:+tDekMZED9+LrtB3G5xzRggpVh9CARjZqROla3R3R+I=
|
||||||
golang.org/x/sync v0.21.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
|
golang.org/x/image v0.44.0/go.mod h1:V8K3KE9KKKE+pLpQDOeN18w9oacNSvy1tDOirTu4xtY=
|
||||||
golang.org/x/sys v0.46.0 h1:noSf2Fq6F8DBgS+LysIkx7rIExoNHJsxOAtPp4rthXw=
|
golang.org/x/mod v0.38.0 h1:MECBjubtXD7yj4HrhIUcywNaGeNVUdfVnxmPajOk4yk=
|
||||||
golang.org/x/sys v0.46.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
golang.org/x/mod v0.38.0/go.mod h1:V6Xz0pq8TQ3dGqVQ1FVHuelZpAL0uNhSkk9ogYP3c40=
|
||||||
golang.org/x/text v0.38.0 h1:sXmwo9DwP3OK9EZ7PqAdaooSGozfl/3a6/xJcbzPRhE=
|
golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek=
|
||||||
golang.org/x/text v0.38.0/go.mod h1:YXZt3QhHUKYT53r2lLKFIVi6Ao1jdzrTR/KQ09qyxF4=
|
golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
|
||||||
golang.org/x/tools v0.46.0 h1:7jTurBkPZu4moS/Uy4OQT1M+QBlsj3wejyZwsT8Z7rk=
|
golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs=
|
||||||
golang.org/x/tools v0.46.0/go.mod h1:FrD85F8l+NWL+9XWBSyVSHO6Ne4jutsfIFba7AWQ5Ys=
|
golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||||
|
golang.org/x/text v0.40.0 h1:Ub2Z6/xjgF1WrYQz2nuITOEegKFtiIy+rieRJ5lHZKs=
|
||||||
|
golang.org/x/text v0.40.0/go.mod h1:hpnzDAfGV753zIKo+wk3u1bVKCGPbrnF7+7LBF/UHVY=
|
||||||
|
golang.org/x/tools v0.48.0 h1:3+hClM1aLL5mjMKm5ovokw9epgRXPuu2tILgismM6RE=
|
||||||
|
golang.org/x/tools v0.48.0/go.mod h1:08xX0orndb/F7jJxGDicx061tyd5pcMto75YMAXr6lk=
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
modernc.org/cc/v4 v4.28.4 h1:Hd/4Es+MBj+/7hSdZaisNyu6bv3V0Dp2MdllyfqaH+c=
|
modernc.org/cc/v4 v4.29.1 h1:MKgdCV3WykTSPqpVrnxdEDS0HEd2FHpKZDzxzU5LyeI=
|
||||||
modernc.org/cc/v4 v4.28.4/go.mod h1:OnovgIhbbMXMu1aISnJ0wvVD1KnW+cAUJkIrAWh+kVI=
|
modernc.org/cc/v4 v4.29.1/go.mod h1:OnovgIhbbMXMu1aISnJ0wvVD1KnW+cAUJkIrAWh+kVI=
|
||||||
modernc.org/ccgo/v4 v4.34.4 h1:OVnSOWQjVKOYkFxoHYB+qQmSHK5gqMqARM+K9DpR/Ws=
|
modernc.org/ccgo/v4 v4.34.6 h1:sBgfIwyN0TQ9C5hwIeuqyeAKyMWnbvj2fvpF4L11uzU=
|
||||||
modernc.org/ccgo/v4 v4.34.4/go.mod h1:qdKqE8FNIYyysougB1RX9MxCzp5oJOcQXSobANJ4TuE=
|
modernc.org/ccgo/v4 v4.34.6/go.mod h1:SZ8YcN9NG7XVsQYdm6jYBvi8PQP1qi+kqB6OhjqI3Fk=
|
||||||
modernc.org/fileutil v1.4.0 h1:j6ZzNTftVS054gi281TyLjHPp6CPHr2KCxEXjEbD6SM=
|
modernc.org/fileutil v1.4.0 h1:j6ZzNTftVS054gi281TyLjHPp6CPHr2KCxEXjEbD6SM=
|
||||||
modernc.org/fileutil v1.4.0/go.mod h1:EqdKFDxiByqxLk8ozOxObDSfcVOv/54xDs/DUHdvCUU=
|
modernc.org/fileutil v1.4.0/go.mod h1:EqdKFDxiByqxLk8ozOxObDSfcVOv/54xDs/DUHdvCUU=
|
||||||
modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI=
|
modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI=
|
||||||
modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito=
|
modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito=
|
||||||
modernc.org/gc/v3 v3.1.3 h1:6QAplYyVO+KdPW3pGnqmJDUxtkec8ooEWvks/hhU3lc=
|
modernc.org/gc/v3 v3.1.4 h1:2g65LGVSmFQrXeITAw97x7hCRvZFcyE1uDP+7Vng7JI=
|
||||||
modernc.org/gc/v3 v3.1.3/go.mod h1:HFK/6AGESC7Ex+EZJhJ2Gni6cTaYpSMmU/cT9RmlfYY=
|
modernc.org/gc/v3 v3.1.4/go.mod h1:HFK/6AGESC7Ex+EZJhJ2Gni6cTaYpSMmU/cT9RmlfYY=
|
||||||
modernc.org/goabi0 v0.2.0 h1:HvEowk7LxcPd0eq6mVOAEMai46V+i7Jrj13t4AzuNks=
|
modernc.org/goabi0 v0.2.0 h1:HvEowk7LxcPd0eq6mVOAEMai46V+i7Jrj13t4AzuNks=
|
||||||
modernc.org/goabi0 v0.2.0/go.mod h1:CEFRnnJhKvWT1c1JTI3Avm+tgOWbkOu5oPA8eH8LnMI=
|
modernc.org/goabi0 v0.2.0/go.mod h1:CEFRnnJhKvWT1c1JTI3Avm+tgOWbkOu5oPA8eH8LnMI=
|
||||||
modernc.org/libc v1.73.4 h1:+ra4Ui8ngyt8HDcO1FTDPWlkAh6yOdaO2yAoh8MddQA=
|
modernc.org/libc v1.74.3 h1:a4J+Z8aVaxPyjyxRAdJzw246PqpcFGvVPnfT/AuM5Ws=
|
||||||
modernc.org/libc v1.73.4/go.mod h1:DXZ3eO8qMCNn2SnmTNCiC71nJ9Rcq3PsnpU6Vc4rWK8=
|
modernc.org/libc v1.74.3/go.mod h1:4H7h/MJ8wnjL8RAbp9v3OXgnk22X7MouHIhDbvP3gj4=
|
||||||
modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
|
modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
|
||||||
modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
|
modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
|
||||||
modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI=
|
modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI=
|
||||||
@@ -78,8 +86,8 @@ modernc.org/opt v0.2.0 h1:tGyef5ApycA7FSEOMraay9SaTk5zmbx7Tu+cJs4QKZg=
|
|||||||
modernc.org/opt v0.2.0/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns=
|
modernc.org/opt v0.2.0/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns=
|
||||||
modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w=
|
modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w=
|
||||||
modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE=
|
modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE=
|
||||||
modernc.org/sqlite v1.52.0 h1:p4dhYh2tXZCiyaqHwRVJDjIGKWyXayiQpThxgDzJaxo=
|
modernc.org/sqlite v1.54.0 h1:JCxR4qwkJvOaqAoYcgDoO25Nc+ROg6EJ2LfBVzdrgog=
|
||||||
modernc.org/sqlite v1.52.0/go.mod h1:tcNzv5p84E0skkmJn038y+hWJbLQXQqEnQfeh5r2JLM=
|
modernc.org/sqlite v1.54.0/go.mod h1:4ntCLuNmnH8+GNqjka1wNg7KJd5/Hi5FYp8K+XQ7GZw=
|
||||||
modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0=
|
modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0=
|
||||||
modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A=
|
modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A=
|
||||||
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
|
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
|
||||||
|
|||||||
@@ -14,4 +14,4 @@ SELECT
|
|||||||
FROM single_mode_scenario sc
|
FROM single_mode_scenario sc
|
||||||
JOIN scenario_name n ON sc.id = n.id
|
JOIN scenario_name n ON sc.id = n.id
|
||||||
JOIN scenario_title t ON sc.id = t.id
|
JOIN scenario_title t ON sc.id = t.id
|
||||||
ORDER BY sc.id
|
ORDER BY sc.sort_id
|
||||||
|
|||||||
Generated
+621
-566
File diff suppressed because it is too large
Load Diff
+16
-13
@@ -20,32 +20,35 @@
|
|||||||
"@eslint/js": "^10.0.1",
|
"@eslint/js": "^10.0.1",
|
||||||
"@sveltejs/adapter-auto": "^7.0.1",
|
"@sveltejs/adapter-auto": "^7.0.1",
|
||||||
"@sveltejs/adapter-static": "^3.0.10",
|
"@sveltejs/adapter-static": "^3.0.10",
|
||||||
"@sveltejs/kit": "^2.60.1",
|
"@sveltejs/kit": "^2.70.1",
|
||||||
"@sveltejs/vite-plugin-svelte": "^6.2.4",
|
"@sveltejs/vite-plugin-svelte": "^6.2.4",
|
||||||
"@tailwindcss/vite": "^4.3.0",
|
"@tailwindcss/vite": "^4.3.3",
|
||||||
"@types/d3": "^7.4.3",
|
"@types/d3": "^7.4.3",
|
||||||
"@types/node": "^22.19.19",
|
"@types/node": "^22.20.1",
|
||||||
"@vitest/browser-playwright": "^4.1.0",
|
"@vitest/browser-playwright": "^4.1.0",
|
||||||
"d3": "^7.9.0",
|
"d3": "^7.9.0",
|
||||||
"eslint": "^10.4.0",
|
"eslint": "^10.7.0",
|
||||||
"eslint-config-prettier": "^10.1.8",
|
"eslint-config-prettier": "^10.1.8",
|
||||||
"eslint-plugin-svelte": "^3.17.1",
|
"eslint-plugin-svelte": "^3.22.0",
|
||||||
"globals": "^17.6.0",
|
"globals": "^17.7.0",
|
||||||
"playwright": "^1.60.0",
|
"playwright": "^1.61.1",
|
||||||
"prettier": "^3.8.3",
|
"prettier": "^3.9.6",
|
||||||
"prettier-plugin-svelte": "^3.5.2",
|
"prettier-plugin-svelte": "^3.5.2",
|
||||||
"prettier-plugin-tailwindcss": "^0.7.4",
|
"prettier-plugin-tailwindcss": "^0.7.4",
|
||||||
"svelte": "^5.55.7",
|
"svelte": "^5.56.7",
|
||||||
"svelte-check": "^4.4.8",
|
"svelte-check": "^4.7.3",
|
||||||
"tailwindcss": "^4.1.18",
|
"tailwindcss": "^4.1.18",
|
||||||
"typescript": "^5.9.3",
|
"typescript": "^5.9.3",
|
||||||
"typescript-eslint": "^8.59.3",
|
"typescript-eslint": "^8.65.0",
|
||||||
"vite": "^7.3.3",
|
"vite": "^7.3.6",
|
||||||
"vitest": "^4.1.0",
|
"vitest": "^4.1.0",
|
||||||
"vitest-browser-svelte": "^2.1.1"
|
"vitest-browser-svelte": "^2.2.1"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@observablehq/plot": "^0.6.17",
|
"@observablehq/plot": "^0.6.17",
|
||||||
"mathjs": "^15.2.0"
|
"mathjs": "^15.2.0"
|
||||||
|
},
|
||||||
|
"allowScripts": {
|
||||||
|
"esbuild@0.28.1": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,37 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import type { SvelteHTMLElements } from 'svelte/elements';
|
||||||
|
|
||||||
|
const sizes = [256, 64, 32, 16] as const;
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
chara_id: number | null | undefined;
|
||||||
|
size: (typeof sizes)[number];
|
||||||
|
}
|
||||||
|
|
||||||
|
let { chara_id, size, ...rest }: Props & SvelteHTMLElements['picture'] = $props();
|
||||||
|
|
||||||
|
const icon = $derived(chara_id ?? 0);
|
||||||
|
const avifs = $derived(
|
||||||
|
sizes
|
||||||
|
.filter((sz) => sz >= size)
|
||||||
|
.map((sz) => `/img/chara/${icon}/${sz}x${sz}.avif ${sz / size}x`)
|
||||||
|
.join(', '),
|
||||||
|
);
|
||||||
|
const pngs = $derived(
|
||||||
|
sizes
|
||||||
|
.filter((sz) => sz >= size)
|
||||||
|
.map((sz) => `/img/chara/${icon}/${sz}x${sz}.png ${sz / size}x`)
|
||||||
|
.join(', '),
|
||||||
|
);
|
||||||
|
const src = $derived(icon !== 0 ? `/img/chara/${icon}/${sizes[0]}x${sizes[0]}.png` : `/img/skill/10011`);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if icon !== 0}
|
||||||
|
<picture {...rest}>
|
||||||
|
<source srcset={avifs} type="image/avif" />
|
||||||
|
<source srcset={pngs} type="image/png" />
|
||||||
|
<img {src} alt={`Icon for character ID ${chara_id}`} class="inline" width={size} height={size} loading="lazy" />
|
||||||
|
</picture>
|
||||||
|
{:else}
|
||||||
|
<div class={`w-[${size}px] h-[${size}px]`}></div>
|
||||||
|
{/if}
|
||||||
@@ -1,23 +1,29 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { Character } from '$lib/data/character';
|
import type { Character } from '$lib/data/character';
|
||||||
import type { Snippet } from 'svelte';
|
|
||||||
import type { ClassValue } from 'svelte/elements';
|
import type { ClassValue } from 'svelte/elements';
|
||||||
import Pick from './Pick.svelte';
|
import Pick from './Pick.svelte';
|
||||||
|
import CharaIcon from './CharaIcon.svelte';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
id: string;
|
id: string;
|
||||||
characters: Character[] | null;
|
characters: Character[] | null;
|
||||||
value: Character | undefined;
|
value: Character | undefined;
|
||||||
class?: ClassValue | null;
|
class?: ClassValue | null;
|
||||||
option?: Snippet<[Character]>;
|
|
||||||
required?: boolean;
|
required?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
let { id, characters, value = $bindable(), class: className, option, required = false }: Props = $props();
|
let { id, characters, value = $bindable(), class: className, required = false }: Props = $props();
|
||||||
|
|
||||||
const charas = $derived(characters ?? []);
|
const charas = $derived(characters ?? []);
|
||||||
|
|
||||||
const key = (v: Character) => ({ id: v.chara_id, name: v.name });
|
const key = (v: Character) => ({ id: v.chara_id, name: v.name });
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Pick {id} items={charas} {key} bind:value {option} class={className} {required} />
|
<Pick {id} items={charas} {key} bind:value class={className} {required}>
|
||||||
|
{#snippet option(c: Character)}
|
||||||
|
<span>
|
||||||
|
<CharaIcon chara_id={c.chara_id} size={32} />
|
||||||
|
{c.name}
|
||||||
|
</span>
|
||||||
|
{/snippet}
|
||||||
|
</Pick>
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import * as all from '$lib/all';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
title?: string;
|
||||||
|
pages: all.Page[];
|
||||||
|
}
|
||||||
|
|
||||||
|
let { title, pages }: Props = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if title != null}
|
||||||
|
<h2 class="mt-8 mb-4 text-4xl">{title}</h2>
|
||||||
|
{/if}
|
||||||
|
<ul class="mb-4 list-disc pl-4">
|
||||||
|
{#each pages as page (page.route)}
|
||||||
|
<li>
|
||||||
|
<a href={page.route}>{page.name}</a> — {page.description}
|
||||||
|
</li>
|
||||||
|
{/each}
|
||||||
|
</ul>
|
||||||
@@ -6,7 +6,7 @@
|
|||||||
interface Props {
|
interface Props {
|
||||||
id: string;
|
id: string;
|
||||||
items: T[];
|
items: T[];
|
||||||
key: (t: T) => {id: number, name: string};
|
key: (t: T) => { id: number; name: string };
|
||||||
value: T | undefined;
|
value: T | undefined;
|
||||||
option?: Snippet<[T]>;
|
option?: Snippet<[T]>;
|
||||||
class?: ClassValue | null;
|
class?: ClassValue | null;
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
type Ability,
|
type Ability,
|
||||||
type Skill,
|
type Skill,
|
||||||
} from './data/skill';
|
} from './data/skill';
|
||||||
|
import SkillIcon from './SkillIcon.svelte';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
skill: Skill | null | undefined;
|
skill: Skill | null | undefined;
|
||||||
@@ -24,6 +25,7 @@
|
|||||||
group_rate: 1,
|
group_rate: 1,
|
||||||
wit_check: false,
|
wit_check: false,
|
||||||
activations: [],
|
activations: [],
|
||||||
|
tags: [],
|
||||||
icon_id: 0,
|
icon_id: 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -62,9 +64,13 @@
|
|||||||
|
|
||||||
<span class="group relative inline-block hyphens-none">
|
<span class="group relative inline-block hyphens-none">
|
||||||
<span
|
<span
|
||||||
class="skill-tip invisible absolute bottom-4 -left-1/2 flex w-80 flex-col rounded-lg border px-2 text-center opacity-0 shadow-lg transition-all group-hover:visible group-hover:bottom-6 group-hover:opacity-100"
|
class="skill-tip invisible absolute bottom-4 -left-1/2 flex min-w-80 flex-col rounded-lg border px-2 pt-2 text-center opacity-0 shadow-lg transition-all group-hover:visible group-hover:bottom-6 group-hover:opacity-100"
|
||||||
role="tooltip"
|
role="tooltip"
|
||||||
>
|
>
|
||||||
|
<span class="block text-xl">
|
||||||
|
<SkillIcon icon_id={skill?.icon_id} size={32} class="inline" />
|
||||||
|
{s.name}
|
||||||
|
</span>
|
||||||
<span class="block border-b py-1 hyphens-auto">
|
<span class="block border-b py-1 hyphens-auto">
|
||||||
{s.description}
|
{s.description}
|
||||||
</span>
|
</span>
|
||||||
@@ -91,5 +97,6 @@
|
|||||||
</span>
|
</span>
|
||||||
{/each}
|
{/each}
|
||||||
</span>
|
</span>
|
||||||
|
<SkillIcon icon_id={skill?.icon_id} size={16} class="inline max-h-lh align-text-bottom" />
|
||||||
<span class={[spanClass, 'cursor-pointer']}>{s.name}</span>
|
<span class={[spanClass, 'cursor-pointer']}>{s.name}</span>
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
@@ -0,0 +1,37 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import type { SvelteHTMLElements } from 'svelte/elements';
|
||||||
|
|
||||||
|
const sizes = [64, 32, 16] as const;
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
icon_id: number | null | undefined;
|
||||||
|
size: (typeof sizes)[number];
|
||||||
|
}
|
||||||
|
|
||||||
|
let { icon_id, size, ...rest }: Props & SvelteHTMLElements['picture'] = $props();
|
||||||
|
|
||||||
|
const icon = $derived(icon_id ?? 0);
|
||||||
|
const avifs = $derived(
|
||||||
|
sizes
|
||||||
|
.filter((sz) => sz >= size)
|
||||||
|
.map((sz) => `/img/skill/${icon}/${sz}x${sz}.avif ${sz / size}x`)
|
||||||
|
.join(', '),
|
||||||
|
);
|
||||||
|
const pngs = $derived(
|
||||||
|
sizes
|
||||||
|
.filter((sz) => sz >= size)
|
||||||
|
.map((sz) => `/img/skill/${icon}/${sz}x${sz}.png ${sz / size}x`)
|
||||||
|
.join(', '),
|
||||||
|
);
|
||||||
|
const src = $derived(icon !== 0 ? `/img/skill/${icon}/${sizes[0]}x${sizes[0]}.png` : `/img/skill/10011`);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if icon !== 0}
|
||||||
|
<picture {...rest}>
|
||||||
|
<source srcset={avifs} type="image/avif" />
|
||||||
|
<source srcset={pngs} type="image/png" />
|
||||||
|
<img {src} alt={`Skill icon ID ${icon_id}`} class="inline" width={size} height={size} loading="lazy" />
|
||||||
|
</picture>
|
||||||
|
{:else}
|
||||||
|
<div class={`w-[${size}px] h-[${size}px]`}></div>
|
||||||
|
{/if}
|
||||||
@@ -0,0 +1,103 @@
|
|||||||
|
import { resolve } from '$app/paths';
|
||||||
|
import type { ResolvedPathname } from '$app/types';
|
||||||
|
|
||||||
|
export interface Page {
|
||||||
|
route: ResolvedPathname;
|
||||||
|
name: string;
|
||||||
|
description: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* All tool and doc pages in the site.
|
||||||
|
*/
|
||||||
|
export const PAGES = {
|
||||||
|
index: [
|
||||||
|
{
|
||||||
|
route: resolve('/race'),
|
||||||
|
name: 'Race Mechanics Tools',
|
||||||
|
description: 'Tools for calculating Umamusume race characteristics.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
route: resolve('/chara'),
|
||||||
|
name: 'Character Tools',
|
||||||
|
description: 'Tools for understanding Umamusume characters.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
route: resolve('/doc'),
|
||||||
|
name: 'Documents',
|
||||||
|
description: 'Articles about Umamusume, including research and editorials.',
|
||||||
|
},
|
||||||
|
] satisfies Page[],
|
||||||
|
race: [
|
||||||
|
{
|
||||||
|
route: resolve('/race/spurt'),
|
||||||
|
name: 'Spurt Speed',
|
||||||
|
description: 'Calculate target speed in the last spurt and compare to other distance aptitudes and running styles.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
route: resolve('/race/mspeed'),
|
||||||
|
name: 'Front Runner Mechanical Speed Comparator',
|
||||||
|
description: 'Compare spot struggle and lane combo to the effects of individual skills.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
route: resolve('/race/gate'),
|
||||||
|
name: 'Gate Calculator',
|
||||||
|
description: 'Calculate the importance of acceleration effects at the starting gate.',
|
||||||
|
},
|
||||||
|
] satisfies Page[],
|
||||||
|
chara: [
|
||||||
|
{
|
||||||
|
route: resolve('/chara/affinity'),
|
||||||
|
name: 'Affinity Details',
|
||||||
|
description: 'Details of why characters have the base compatibility numbers they have.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
route: resolve('/chara/convo'),
|
||||||
|
name: 'Lobby Conversations',
|
||||||
|
description: 'Check participants in lobby conversations and get recommendations on unlocking them quickly.',
|
||||||
|
},
|
||||||
|
] satisfies Page[],
|
||||||
|
doc: [
|
||||||
|
{
|
||||||
|
route: resolve('/doc/frbm'),
|
||||||
|
name: 'Front Runner Black Magic',
|
||||||
|
description: 'Detailed analysis of how front runners interact with race mechanics.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
route: resolve('/doc/lanecalc'),
|
||||||
|
name: "Gaikokujin's Guide to Lanecalc",
|
||||||
|
description: 'English language manual for 危険回避シミュ, or Lanecalc, for precise lane combo simulation.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
route: resolve('/doc/race'),
|
||||||
|
name: 'Race Mechanics Charts',
|
||||||
|
description:
|
||||||
|
"Interactive adaptation of KuromiAK's race mechanics documentation for easier visualization of how mechanics scale.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
route: resolve('/doc/spark'),
|
||||||
|
name: 'Spark Explorer',
|
||||||
|
description: 'View all possible inheritance effects of any spark.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
route: resolve('/doc/tagged-skills'),
|
||||||
|
name: 'Tagged Skills Explorer',
|
||||||
|
description: 'View skills matching the tags internal to the game.',
|
||||||
|
},
|
||||||
|
] satisfies Page[],
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the page object for a route.
|
||||||
|
* @param route Route ID for the page
|
||||||
|
* @returns Matching page, or an empty object if none match
|
||||||
|
*/
|
||||||
|
export function page(route: string | null): Partial<Page> {
|
||||||
|
for (const p of Object.values(PAGES)) {
|
||||||
|
const r = p.find((v) => v.route === route);
|
||||||
|
if (r != null) {
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
@@ -18,7 +18,7 @@ const styleMap = {
|
|||||||
Sentou: RunningStyle.PaceChaser,
|
Sentou: RunningStyle.PaceChaser,
|
||||||
Sasi: RunningStyle.LateSurger,
|
Sasi: RunningStyle.LateSurger,
|
||||||
Oikomi: RunningStyle.EndCloser,
|
Oikomi: RunningStyle.EndCloser,
|
||||||
Oonige: RunningStyle.GreatEscape,
|
Oonige: RunningStyle.Runaway,
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
export interface ImportUma {
|
export interface ImportUma {
|
||||||
|
|||||||
@@ -48,6 +48,10 @@ export interface Skill {
|
|||||||
* SP cost to purchase the skill, if applicable.
|
* SP cost to purchase the skill, if applicable.
|
||||||
*/
|
*/
|
||||||
sp_cost?: number;
|
sp_cost?: number;
|
||||||
|
/**
|
||||||
|
* Numeric tags applied to the skill, for effects which select skill types.
|
||||||
|
*/
|
||||||
|
tags: number[];
|
||||||
/**
|
/**
|
||||||
* Skill icon ID.
|
* Skill icon ID.
|
||||||
*/
|
*/
|
||||||
@@ -133,10 +137,13 @@ export enum AbilityType {
|
|||||||
Frenzy = 13,
|
Frenzy = 13,
|
||||||
AddGateDelay = 14,
|
AddGateDelay = 14,
|
||||||
CurrentSpeed = 21,
|
CurrentSpeed = 21,
|
||||||
|
CurrentSpeedWithDecel = 22,
|
||||||
TargetSpeed = 27,
|
TargetSpeed = 27,
|
||||||
LaneSpeed = 28,
|
LaneSpeed = 28,
|
||||||
|
RushedChance = 29,
|
||||||
Accel = 31,
|
Accel = 31,
|
||||||
LaneChange = 35,
|
LaneChange = 35,
|
||||||
|
ActivateRare = 37,
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ABILITY_TYPE_FORMAT: Readonly<Record<AbilityType, (v: number) => string>> = {
|
export const ABILITY_TYPE_FORMAT: Readonly<Record<AbilityType, (v: number) => string>> = {
|
||||||
@@ -152,10 +159,13 @@ export const ABILITY_TYPE_FORMAT: Readonly<Record<AbilityType, (v: number) => st
|
|||||||
[AbilityType.Frenzy]: (v) => tenThousandths(v) + 's Frenzy',
|
[AbilityType.Frenzy]: (v) => tenThousandths(v) + 's Frenzy',
|
||||||
[AbilityType.AddGateDelay]: (v) => plusBonus(v) + 's Gate Delay',
|
[AbilityType.AddGateDelay]: (v) => plusBonus(v) + 's Gate Delay',
|
||||||
[AbilityType.CurrentSpeed]: (v) => plusBonus(v) + ' m/s Current Speed',
|
[AbilityType.CurrentSpeed]: (v) => plusBonus(v) + ' m/s Current Speed',
|
||||||
|
[AbilityType.CurrentSpeedWithDecel]: (v) => plusBonus(v) + ' m/s Current Speed',
|
||||||
[AbilityType.TargetSpeed]: (v) => plusBonus(v) + ' m/s Target Speed',
|
[AbilityType.TargetSpeed]: (v) => plusBonus(v) + ' m/s Target Speed',
|
||||||
[AbilityType.LaneSpeed]: (v) => plusBonus(v) + ' CW/s Lane Change Speed',
|
[AbilityType.LaneSpeed]: (v) => plusBonus(v) + ' CW/s Lane Change Speed',
|
||||||
|
[AbilityType.RushedChance]: (v) => plusBonus(v) + '% Rushed Chance',
|
||||||
[AbilityType.Accel]: (v) => plusBonus(v) + ' m/s² Acceleration',
|
[AbilityType.Accel]: (v) => plusBonus(v) + ' m/s² Acceleration',
|
||||||
[AbilityType.LaneChange]: (v) => 'Target Lane = ' + tenThousandths(v) + ' CW',
|
[AbilityType.LaneChange]: (v) => 'Target Lane = ' + tenThousandths(v) + ' CW',
|
||||||
|
[AbilityType.ActivateRare]: (v) => 'Activate Rare Skills ×' + tenThousandths(v),
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
export enum AbilityValueUsage {
|
export enum AbilityValueUsage {
|
||||||
@@ -329,3 +339,55 @@ export async function skillGroups(): Promise<SkillGroup[]> {
|
|||||||
const resp = await fetch('/api/global/skill-group');
|
const resp = await fetch('/api/global/skill-group');
|
||||||
return resp.json();
|
return resp.json();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const TAGS = [
|
||||||
|
{ name: 'Front Runner', tag: 101 },
|
||||||
|
{ name: 'Pace Chaser', tag: 102 },
|
||||||
|
{ name: 'Late Surger', tag: 103 },
|
||||||
|
{ name: 'End Closer', tag: 104 },
|
||||||
|
{ name: 'Sprint', tag: 201 },
|
||||||
|
{ name: 'Mile', tag: 202 },
|
||||||
|
{ name: 'Medium', tag: 203 },
|
||||||
|
{ name: 'Long', tag: 204 },
|
||||||
|
{ name: 'Early Race', tag: 301 },
|
||||||
|
{ name: 'Mid Race', tag: 302 },
|
||||||
|
{ name: 'Late Race', tag: 303 },
|
||||||
|
{ name: 'Speed Stat or Velocity', tag: 401 },
|
||||||
|
{ name: 'Stamina Stat or HP', tag: 402 },
|
||||||
|
{ name: 'Power Stat or Acceleration', tag: 403 },
|
||||||
|
{ name: 'Guts Stat', tag: 404 },
|
||||||
|
{ name: 'Wit Stat or Vision', tag: 405 },
|
||||||
|
{ name: 'Debuff', tag: 406 },
|
||||||
|
{ name: 'Gate Delay', tag: 407 },
|
||||||
|
{ name: 'Turf', tag: 501 },
|
||||||
|
{ name: 'Dirt', tag: 502 },
|
||||||
|
{ name: 'Ground Condition Passive', tag: 601 },
|
||||||
|
{ name: 'Weather Passive', tag: 602 },
|
||||||
|
{ name: 'Season Passive', tag: 603 },
|
||||||
|
{ name: 'Racecourse Passive', tag: 604 },
|
||||||
|
{ name: 'Time of Day Passive', tag: 605 },
|
||||||
|
{ name: 'Exchange Race Passive', tag: 606 },
|
||||||
|
{ name: 'Distance Passive', tag: 607 },
|
||||||
|
{ name: 'Corner-Based Passive', tag: 608 },
|
||||||
|
{ name: 'Gate Position Passive', tag: 609 },
|
||||||
|
{ name: 'Ground Type Passive', tag: 610 },
|
||||||
|
{ name: 'Popularity Passive', tag: 611 },
|
||||||
|
{ name: 'Running Style Passive', tag: 612 },
|
||||||
|
{ name: "Passive Depending on Other Horses' Styles or Skills", tag: 613 },
|
||||||
|
{ name: 'Base Stat Threshold Passive', tag: 614 },
|
||||||
|
{ name: 'Mood Passive', tag: 615 },
|
||||||
|
{ name: 'URA', tag: 801 },
|
||||||
|
{ name: 'Unity Cup', tag: 802 },
|
||||||
|
{ name: 'Trackblazer', tag: 804 },
|
||||||
|
// { name: 'Our Grand Concert', tag: ? },
|
||||||
|
// { name: 'Grand Masters', tag: ? },
|
||||||
|
// { name: "Project L'Arc", tag: ? },
|
||||||
|
// { name: 'U.A.F.', tag: ? },
|
||||||
|
// { name: 'Great Food Festival', tag: ? },
|
||||||
|
// { name: 'Run, Mecha Umamusume!', tag: ? },
|
||||||
|
// { name: 'The Twinkle Legends', tag: ? },
|
||||||
|
// { name: 'Design Your Island', tag: ? },
|
||||||
|
// { name: 'Yukoma Hot Springs', tag: ? },
|
||||||
|
// { name: 'Beyond Dreams', tag: ? },
|
||||||
|
// { name: 'Tracen-Ken', tag: ? },
|
||||||
|
] as const;
|
||||||
|
|||||||
@@ -260,7 +260,7 @@ export function finalStat(adjStat: number, skillPassive: number): number {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Running styles for strategy–phase coefficients.
|
* Running styles for strategy–phase coefficients.
|
||||||
* Great Escape is distinguished as a separate style even though it is
|
* Runaway is distinguished as a separate style even though it is
|
||||||
* mechanically identical to Front Runner.
|
* mechanically identical to Front Runner.
|
||||||
*/
|
*/
|
||||||
export enum RunningStyle {
|
export enum RunningStyle {
|
||||||
@@ -268,7 +268,7 @@ export enum RunningStyle {
|
|||||||
PaceChaser,
|
PaceChaser,
|
||||||
LateSurger,
|
LateSurger,
|
||||||
EndCloser,
|
EndCloser,
|
||||||
GreatEscape,
|
Runaway,
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -279,7 +279,7 @@ export const RUNNING_STYLES = [
|
|||||||
['Pace Chaser', RunningStyle.PaceChaser],
|
['Pace Chaser', RunningStyle.PaceChaser],
|
||||||
['Late Surger', RunningStyle.LateSurger],
|
['Late Surger', RunningStyle.LateSurger],
|
||||||
['End Closer', RunningStyle.EndCloser],
|
['End Closer', RunningStyle.EndCloser],
|
||||||
['Great Escape', RunningStyle.GreatEscape],
|
['Runaway', RunningStyle.Runaway],
|
||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -325,7 +325,7 @@ const hpStrategyCoeff = {
|
|||||||
[RunningStyle.PaceChaser]: 0.89,
|
[RunningStyle.PaceChaser]: 0.89,
|
||||||
[RunningStyle.LateSurger]: 1.0,
|
[RunningStyle.LateSurger]: 1.0,
|
||||||
[RunningStyle.EndCloser]: 0.995,
|
[RunningStyle.EndCloser]: 0.995,
|
||||||
[RunningStyle.GreatEscape]: 0.86,
|
[RunningStyle.Runaway]: 0.86,
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -595,7 +595,7 @@ const accelStrategyPhaseCoeff = {
|
|||||||
[RunningStyle.PaceChaser]: [0.985, 1.0, 0.996],
|
[RunningStyle.PaceChaser]: [0.985, 1.0, 0.996],
|
||||||
[RunningStyle.LateSurger]: [0.975, 1.0, 1.0],
|
[RunningStyle.LateSurger]: [0.975, 1.0, 1.0],
|
||||||
[RunningStyle.EndCloser]: [0.945, 1.0, 0.997],
|
[RunningStyle.EndCloser]: [0.945, 1.0, 0.997],
|
||||||
[RunningStyle.GreatEscape]: [1.17, 0.94, 0.956],
|
[RunningStyle.Runaway]: [1.17, 0.94, 0.956],
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
const surfaceProficiencyMod = [0.1, 0.3, 0.5, 0.7, 0.8, 0.9, 1.0, 1.05] as const;
|
const surfaceProficiencyMod = [0.1, 0.3, 0.5, 0.7, 0.8, 0.9, 1.0, 1.05] as const;
|
||||||
@@ -711,7 +711,7 @@ const conserveStratDistCoef = {
|
|||||||
[Distance.Medium]: 0.86,
|
[Distance.Medium]: 0.86,
|
||||||
[Distance.Long]: 0.9,
|
[Distance.Long]: 0.9,
|
||||||
},
|
},
|
||||||
[RunningStyle.GreatEscape]: {
|
[RunningStyle.Runaway]: {
|
||||||
[Distance.Sprint]: 1,
|
[Distance.Sprint]: 1,
|
||||||
[Distance.Mile]: 1,
|
[Distance.Mile]: 1,
|
||||||
[Distance.Medium]: 1,
|
[Distance.Medium]: 1,
|
||||||
|
|||||||
@@ -1,13 +1,31 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import './layout.css';
|
import './layout.css';
|
||||||
import favicon from '$lib/assets/favicon.png';
|
import favicon from '$lib/assets/favicon.png';
|
||||||
|
import * as all from '$lib/all';
|
||||||
import { resolve } from '$app/paths';
|
import { resolve } from '$app/paths';
|
||||||
|
import type { LayoutProps } from './$types';
|
||||||
|
import { page } from '$app/state';
|
||||||
|
|
||||||
let { children } = $props();
|
let { children }: LayoutProps = $props();
|
||||||
|
|
||||||
|
const cur = $derived(all.page(page.route.id));
|
||||||
|
const title = $derived(cur.name);
|
||||||
|
const desc = $derived(
|
||||||
|
cur.description ??
|
||||||
|
"She's read all about Umamusume, and she's always happy to share her knowledge and give recommendations! Tools and editorials for Umamusume optimization.",
|
||||||
|
);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svelte:head>
|
<svelte:head>
|
||||||
|
{#if title != null}
|
||||||
|
<title>Zenno Rob Roy – {title}</title>
|
||||||
|
{:else}
|
||||||
<title>Zenno Rob Roy</title>
|
<title>Zenno Rob Roy</title>
|
||||||
|
{/if}
|
||||||
|
<meta name="description" content={desc} />
|
||||||
|
<meta name="color-scheme" content="light dark" />
|
||||||
|
<meta name="referrer" content="strict-origin-when-cross-origin" />
|
||||||
|
<meta name="robots" content="noimageindex" />
|
||||||
<link rel="icon" href={favicon} />
|
<link rel="icon" href={favicon} />
|
||||||
</svelte:head>
|
</svelte:head>
|
||||||
|
|
||||||
@@ -23,9 +41,9 @@
|
|||||||
<a href={resolve('/doc')} class="mx-8 my-1 inline-block">Documents</a>
|
<a href={resolve('/doc')} class="mx-8 my-1 inline-block">Documents</a>
|
||||||
</span>
|
</span>
|
||||||
</nav>
|
</nav>
|
||||||
<div class="mx-4 grow lg:m-auto lg:max-w-7xl lg:min-w-7xl">
|
<main class="mx-4 grow lg:m-auto lg:max-w-7xl lg:min-w-7xl">
|
||||||
{@render children()}
|
{@render children()}
|
||||||
</div>
|
</main>
|
||||||
<footer
|
<footer
|
||||||
class="inset-x-0 bottom-0 mt-12 border-t bg-mist-300 p-4 text-center text-sm md:mt-20 dark:border-none dark:bg-mist-900"
|
class="inset-x-0 bottom-0 mt-12 border-t bg-mist-300 p-4 text-center text-sm md:mt-20 dark:border-none dark:bg-mist-900"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -1,59 +1,16 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { resolve } from '$app/paths';
|
import * as all from '$lib/all';
|
||||||
|
import IndexSection from '$lib/IndexSection.svelte';
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<h1 class="m-8 text-center text-7xl">Zenno Rob Roy</h1>
|
<h1 class="m-8 text-center text-7xl">Zenno Rob Roy</h1>
|
||||||
<p>She's read all about Umamusume, and she's always happy to share her knowledge and give recommendations!</p>
|
<p>She's read all about Umamusume, and she's always happy to share her knowledge and give recommendations!</p>
|
||||||
|
|
||||||
<h2 class="mt-8 mb-4 text-4xl">Race Mechanics Tools</h2>
|
<IndexSection title="Race Mechanics Tools" pages={all.PAGES.race} />
|
||||||
<ul class="mb-4 list-disc pl-4">
|
<IndexSection title="Character Tools" pages={all.PAGES.chara} />
|
||||||
<li>
|
<IndexSection title="Documents" pages={all.PAGES.doc} />
|
||||||
<a href={resolve('/race/spurt')}>Spurt Speed</a> — Calculate a horse's target speed in the last spurt and compare to other
|
|
||||||
distance aptitudes and running styles.
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href={resolve('/race/mspeed')}>Front Runner Mechanical Speed Comparator</a> — Compare spot struggle and lane combo to the
|
|
||||||
effects of individual skills.
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href={resolve('/race/gate')}>Gate Calculator</a> — Calculate the importance of acceleration effects at the starting gate.
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
<h2 class="mt-8 mb-4 text-4xl">Character Tools</h2>
|
|
||||||
<ul class="mb-4 list-disc pl-4">
|
|
||||||
<li>
|
|
||||||
<a href={resolve('/chara/affinity')}>Affinity Details</a> — Details of why characters have the base compatibility numbers
|
|
||||||
they have.
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href={resolve('/chara/convo')}>Lobby Conversations</a> — Check participants in lobby conversations and get recommendations
|
|
||||||
on unlocking them quickly.
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<h2 class="my-4 text-4xl">Documents</h2>
|
|
||||||
<ul class="mb-4 list-disc pl-4">
|
|
||||||
<li>
|
|
||||||
<a href={resolve('/doc/frbm')}>Front Runner Black Magic</a> — Detailed analysis of how front runners interact with race mechanics.
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href={resolve('/doc/lanecalc')}>Gaikokujin's Guide to Lanecalc</a> — English language manual for
|
|
||||||
<a href="https://lanecalc.hf-uma.net/" target="_blank" rel="noopener noreferrer">危険回避シミュ</a>, or Lanecalc, for precise
|
|
||||||
lane combo simulation.
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href={resolve('/doc/race')}>Race Mechanics Charts</a> — Interactive adaptation of
|
|
||||||
<a
|
|
||||||
href="https://docs.google.com/document/d/15VzW9W2tXBBTibBRbZ8IVpW6HaMX8H0RP03kq6Az7Xg/edit"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer">KuromiAK's race mechanics documentation</a
|
|
||||||
> for easier visualization of how mechanics scale.
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href={resolve('/doc/spark')}>Spark Explorer</a> — View all possible inheritance effects of any spark.
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
|
<!-- Not a real index section in, terms of types. -->
|
||||||
<h2 class="my-4 text-4xl">&c.</h2>
|
<h2 class="my-4 text-4xl">&c.</h2>
|
||||||
<ul class="mb-4 list-disc pl-4">
|
<ul class="mb-4 list-disc pl-4">
|
||||||
<li>
|
<li>
|
||||||
|
|||||||
@@ -1,17 +1,11 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { resolve } from '$app/paths';
|
import { page } from '$app/state';
|
||||||
|
import * as all from '$lib/all';
|
||||||
|
import IndexSection from '$lib/IndexSection.svelte';
|
||||||
|
|
||||||
|
const cur = $derived(all.page(page.route.id));
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<h1 class="m-8 text-center text-7xl">Zenno Rob Roy — Character Tools</h1>
|
<h1 class="m-8 text-center text-7xl">Zenno Rob Roy — {cur.name}</h1>
|
||||||
<p>Tools related to understanding Umamusume characters.</p>
|
<p>{cur.description}</p>
|
||||||
|
<IndexSection pages={all.PAGES.chara} />
|
||||||
<ul class="mb-4 list-disc pl-4">
|
|
||||||
<li>
|
|
||||||
<a href={resolve('/chara/affinity')}>Affinity Details</a> — Details of why characters have the base compatibility numbers
|
|
||||||
they have.
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href={resolve('/chara/convo')}>Lobby Conversations</a> — Check participants in lobby conversations and get recommendations
|
|
||||||
on unlocking them quickly.
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
|
|||||||
@@ -8,13 +8,12 @@
|
|||||||
const convoMap = $derived(byChara(conversations));
|
const convoMap = $derived(byChara(conversations));
|
||||||
const popular = $derived(groupPopulars(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 () => {
|
||||||
const [convos, charas] = await Promise.all([conversation(), character()]);
|
[conversations, characters] = await Promise.all([conversation(), character()]);
|
||||||
conversations = convos;
|
|
||||||
characters = charas.filter((c) => convoMap.has(c.chara_id));
|
|
||||||
});
|
});
|
||||||
|
|
||||||
let chara: Character | undefined = $state();
|
let chara: Character | undefined = $state();
|
||||||
@@ -43,7 +42,7 @@
|
|||||||
<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="mx-auto mt-8 flex flex-col rounded-md text-center shadow-md ring md:max-w-xl md:flex-row">
|
||||||
<div class="m-4 flex-1 md:mt-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} class="w-full" bind:value={chara} required />
|
<CharaPick id="chara" characters={selCharas} class="w-full" bind:value={chara} required />
|
||||||
</div>
|
</div>
|
||||||
<div class="m-4 flex-1 md:mt-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>
|
||||||
|
|||||||
@@ -1,28 +1,11 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { resolve } from '$app/paths';
|
import { page } from '$app/state';
|
||||||
|
import * as all from '$lib/all';
|
||||||
|
import IndexSection from '$lib/IndexSection.svelte';
|
||||||
|
|
||||||
|
const cur = $derived(all.page(page.route.id));
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<h1 class="m-8 text-center text-7xl">Zenno Rob Roy — Documents</h1>
|
<h1 class="m-8 text-center text-7xl">Zenno Rob Roy — {cur.name}</h1>
|
||||||
<p>Articles about Umamusume, including editorials and research.</p>
|
<p>{cur.description}</p>
|
||||||
|
<IndexSection pages={all.PAGES.doc} />
|
||||||
<ul class="mb-4 list-disc pl-4">
|
|
||||||
<li>
|
|
||||||
<a href={resolve('/doc/frbm')}>Front Runner Black Magic</a> — Detailed analysis of how front runners interact with race mechanics.
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href={resolve('/doc/lanecalc')}>Gaikokujin's Guide to Lanecalc</a> — English language manual for
|
|
||||||
<a href="https://lanecalc.hf-uma.net/" target="_blank" rel="noopener noreferrer">危険回避シミュ</a>, or Lanecalc, for precise
|
|
||||||
lane combo simulation.
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href={resolve('/doc/race')}>Race Mechanics Charts</a> — Interactive adaptation of
|
|
||||||
<a
|
|
||||||
href="https://docs.google.com/document/d/15VzW9W2tXBBTibBRbZ8IVpW6HaMX8H0RP03kq6Az7Xg/edit"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer">KuromiAK's race mechanics documentation</a
|
|
||||||
> for easier visualization of how mechanics scale.
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href={resolve('/doc/spark')}>Spark Explorer</a> — View all possible inheritance effects of any spark.
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
|
|||||||
@@ -7,12 +7,13 @@
|
|||||||
h?: 1 | 2 | 3 | 4 | 5 | 6;
|
h?: 1 | 2 | 3 | 4 | 5 | 6;
|
||||||
id: string;
|
id: string;
|
||||||
children: Snippet;
|
children: Snippet;
|
||||||
|
ref?: string;
|
||||||
top?: string;
|
top?: string;
|
||||||
toc?: boolean;
|
toc?: boolean;
|
||||||
class?: ClassValue | null;
|
class?: ClassValue | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
let { h = 1, id, children, top = '#top', toc = true, class: className }: Props = $props();
|
let { h = 1, id, children, ref, top = '#top', toc = true, class: className }: Props = $props();
|
||||||
|
|
||||||
const tag = $derived(`h${h}`);
|
const tag = $derived(`h${h}`);
|
||||||
const href = $derived(`#${id}`);
|
const href = $derived(`#${id}`);
|
||||||
@@ -39,9 +40,12 @@
|
|||||||
<svelte:element this={tag} {id} class={['relative w-full', className]}>
|
<svelte:element this={tag} {id} class={['relative w-full', className]}>
|
||||||
<!-- eslint-disable svelte/no-navigation-without-resolve -->
|
<!-- eslint-disable svelte/no-navigation-without-resolve -->
|
||||||
<a {href}><span class="hidden md:inline">{sign}</span> {@render children()}</a>
|
<a {href}><span class="hidden md:inline">{sign}</span> {@render children()}</a>
|
||||||
{#if h >= 2}
|
<div class="absolute right-0 bottom-0 inline-block text-end align-bottom text-sm whitespace-nowrap">
|
||||||
<a href={top} class="absolute right-0 bottom-0 text-end align-bottom text-sm whitespace-nowrap transition-all hover:bottom-px"
|
{#if ref != null}
|
||||||
>Top ▲</a
|
<a href={ref} target="_blank" rel="noopener noreferrer">Ref ↗</a>
|
||||||
>
|
|
||||||
{/if}
|
{/if}
|
||||||
|
{#if h >= 2}
|
||||||
|
<a href={top} class="ml-4">Top ▲</a>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
</svelte:element>
|
</svelte:element>
|
||||||
|
|||||||
@@ -179,7 +179,7 @@
|
|||||||
{ label: 'Style S', y: (x) => downhillAccelEnterChance(x * 1.1) * 100 },
|
{ label: 'Style S', y: (x) => downhillAccelEnterChance(x * 1.1) * 100 },
|
||||||
{ label: 'Style A', y: (x) => downhillAccelEnterChance(x) * 100 },
|
{ label: 'Style A', y: (x) => downhillAccelEnterChance(x) * 100 },
|
||||||
];
|
];
|
||||||
const secIsFront = $derived(secSpeedStyle === RunningStyle.FrontRunner || secSpeedStyle === RunningStyle.GreatEscape);
|
const secIsFront = $derived(secSpeedStyle === RunningStyle.FrontRunner || secSpeedStyle === RunningStyle.Runaway);
|
||||||
const secSpeedSeries: Array<ComputedAreas | null> = $derived([
|
const secSpeedSeries: Array<ComputedAreas | null> = $derived([
|
||||||
{
|
{
|
||||||
label: 'Early Race',
|
label: 'Early Race',
|
||||||
@@ -290,10 +290,16 @@
|
|||||||
>. Many of those interpretations are also informed by the exceptionally knowledgeable folks on the
|
>. Many of those interpretations are also informed by the exceptionally knowledgeable folks on the
|
||||||
<a href="https://discord.gg/SyAVkbBSkx" target="_blank" rel="noopener noreferrer">GameTora Discord server</a>. I may present
|
<a href="https://discord.gg/SyAVkbBSkx" target="_blank" rel="noopener noreferrer">GameTora Discord server</a>. I may present
|
||||||
some of the information from the race mechanics doc in chart form, but I will generally leave out exact mechanic numbers and
|
some of the information from the race mechanics doc in chart form, but I will generally leave out exact mechanic numbers and
|
||||||
conditions; the doc is already the place for that information.
|
conditions; the doc is already the place for that information. Some sections have "Ref ↗" links that will take
|
||||||
|
you to the corresponding section of the mechanics doc.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<Sec h={2} id="mechanics">Race Mechanics</Sec>
|
<Sec
|
||||||
|
h={2}
|
||||||
|
id="mechanics"
|
||||||
|
ref="https://docs.google.com/document/d/15VzW9W2tXBBTibBRbZ8IVpW6HaMX8H0RP03kq6Az7Xg/edit?tab=t.0#heading=h.3q7x98goz5cu"
|
||||||
|
>Race Mechanics</Sec
|
||||||
|
>
|
||||||
<p>
|
<p>
|
||||||
Very quick gloss of race fundamentals. Races are divided into four phases: early race, mid race, late race, and last spurt
|
Very quick gloss of race fundamentals. Races are divided into four phases: early race, mid race, late race, and last spurt
|
||||||
phase. They are also divided into twenty-four equal length sections. Early race is sections 1 to 4, mid race is sections 5 to
|
phase. They are also divided into twenty-four equal length sections. Early race is sections 1 to 4, mid race is sections 5 to
|
||||||
@@ -320,10 +326,7 @@
|
|||||||
|
|
||||||
<Sec h={3} id="runaway">Runaway</Sec>
|
<Sec h={3} id="runaway">Runaway</Sec>
|
||||||
<p>
|
<p>
|
||||||
The skill <Skill skill={skills.runaway} /> converts front runners into the <i>Great Escape</i> running style. However, no
|
The skill <Skill skill={skills.runaway} /> converts front runners into the Runaway running style.
|
||||||
player has ever uttered the words "Great Escape" when talking about Umamusume, presumably because Runaway is a much cooler
|
|
||||||
name. ("Great Escape" is a direct translation of Japanese 大逃げ <i>oonige</i>, whereas "Front Runner" is a more liberal
|
|
||||||
localization of 逃げ <i>nige</i> that technically just means "escape.")
|
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
Runaways are still front runners for all purposes. The difference is just different numbers for things like base speed and
|
Runaways are still front runners for all purposes. The difference is just different numbers for things like base speed and
|
||||||
@@ -331,7 +334,12 @@
|
|||||||
runners also apply to runaways.
|
runners also apply to runaways.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<Sec h={3} id="phase-speed">Phase Speed</Sec>
|
<Sec
|
||||||
|
h={3}
|
||||||
|
id="phase-speed"
|
||||||
|
ref="https://docs.google.com/document/d/15VzW9W2tXBBTibBRbZ8IVpW6HaMX8H0RP03kq6Az7Xg/edit?tab=t.0#heading=h.40iug3hm8429"
|
||||||
|
>Phase Speed</Sec
|
||||||
|
>
|
||||||
<p>
|
<p>
|
||||||
Race base speed is multiplied by the speed strategy–phase coefficient for each horse. As the name suggests, SSPC is
|
Race base speed is multiplied by the speed strategy–phase coefficient for each horse. As the name suggests, SSPC is
|
||||||
different per running style and per race phase. It's the thing that makes runaways take off in early race, and the thing that
|
different per running style and per race phase. It's the thing that makes runaways take off in early race, and the thing that
|
||||||
@@ -344,8 +352,8 @@
|
|||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
The late race speed difference means that, in a competitive setting, the speed stat (and, correspondingly, distance aptitude)
|
The late race speed difference means that, in a competitive setting, the speed stat (and, correspondingly, distance aptitude)
|
||||||
aren't what make front runners win most of the time. Capped speed with distance S is ideal, but 1100 speed with distance A
|
aren't what make front runners win most of the time. Capped speed with distance S is ideal, but being a hundred speed short
|
||||||
will likely lose only a couple races within the difference throughout a CM event.
|
with distance A will likely lose only a couple races within the difference throughout a CM event.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<Sec h={3} id="win-cons">Win Conditions</Sec>
|
<Sec h={3} id="win-cons">Win Conditions</Sec>
|
||||||
@@ -389,7 +397,12 @@
|
|||||||
mid race.
|
mid race.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<Sec h={3} id="front-modes">Speed-Up and Overtake Modes</Sec>
|
<Sec
|
||||||
|
h={3}
|
||||||
|
id="front-modes"
|
||||||
|
ref="https://docs.google.com/document/d/15VzW9W2tXBBTibBRbZ8IVpW6HaMX8H0RP03kq6Az7Xg/edit?tab=t.0#heading=h.tn8irhl5bjhv"
|
||||||
|
>Speed-Up and Overtake Modes</Sec
|
||||||
|
>
|
||||||
<p>
|
<p>
|
||||||
During the first 41.67% of the race, <i>position keep</i> is busy arranging each running style into their respective packs.
|
During the first 41.67% of the race, <i>position keep</i> is busy arranging each running style into their respective packs.
|
||||||
During position keep, all horses have access to <i>running modes</i> that influence how they run.
|
During position keep, all horses have access to <i>running modes</i> that influence how they run.
|
||||||
@@ -410,7 +423,12 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Sec h={3} id="pdm">Pace Down Mode</Sec>
|
<Sec
|
||||||
|
h={3}
|
||||||
|
id="pdm"
|
||||||
|
ref="https://docs.google.com/document/d/15VzW9W2tXBBTibBRbZ8IVpW6HaMX8H0RP03kq6Az7Xg/edit?tab=t.0#heading=h.5x2q33mxvumk"
|
||||||
|
>Pace Down Mode</Sec
|
||||||
|
>
|
||||||
<p>
|
<p>
|
||||||
The running modes for all other running styles are pace-up, which is similar to speed-up, and paced-down, which activates
|
The running modes for all other running styles are pace-up, which is similar to speed-up, and paced-down, which activates
|
||||||
whenever a horse gets what their style defines as too close to first place.
|
whenever a horse gets what their style defines as too close to first place.
|
||||||
@@ -427,7 +445,12 @@
|
|||||||
subject to PDM at all. Their mid race speed skills always gain distance.
|
subject to PDM at all. Their mid race speed skills always gain distance.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<Sec h={3} id="spot-struggle">Spot Struggle</Sec>
|
<Sec
|
||||||
|
h={3}
|
||||||
|
id="spot-struggle"
|
||||||
|
ref="https://docs.google.com/document/d/15VzW9W2tXBBTibBRbZ8IVpW6HaMX8H0RP03kq6Az7Xg/edit?tab=t.0#heading=h.5l523bk8k3vz"
|
||||||
|
>Spot Struggle</Sec
|
||||||
|
>
|
||||||
<p>
|
<p>
|
||||||
For each of runaways and non-runaways, there is at most one spot struggle per race. Runaways will not spot struggle with
|
For each of runaways and non-runaways, there is at most one spot struggle per race. Runaways will not spot struggle with
|
||||||
non-runaways, nor vice-versa. When a spot struggle triggers, all front runnners of that type within range participate; I've
|
non-runaways, nor vice-versa. When a spot struggle triggers, all front runnners of that type within range participate; I've
|
||||||
@@ -460,7 +483,12 @@
|
|||||||
struggle duration rises.
|
struggle duration rises.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<Sec h={3} id="full-charge">Conserve Power & Fully Charged</Sec>
|
<Sec
|
||||||
|
h={3}
|
||||||
|
id="full-charge"
|
||||||
|
ref="https://docs.google.com/document/d/15VzW9W2tXBBTibBRbZ8IVpW6HaMX8H0RP03kq6Az7Xg/edit?tab=t.0#heading=h.9edgwx6l733b"
|
||||||
|
>Conserve Power & Fully Charged</Sec
|
||||||
|
>
|
||||||
<p>
|
<p>
|
||||||
New in the 1 Jul balance patch is the conserve power mechanic. Horses with base power plus power passive skills greater than
|
New in the 1 Jul balance patch is the conserve power mechanic. Horses with base power plus power passive skills greater than
|
||||||
1200 effectively gain access to a free perfectly-timed accel skill, Fully Charged. The effectiveness of full charge varies
|
1200 effectively gain access to a free perfectly-timed accel skill, Fully Charged. The effectiveness of full charge varies
|
||||||
@@ -554,7 +582,12 @@
|
|||||||
<a href={resolve('/doc/lanecalc')}>my guide</a> for English instructions on how to use it.
|
<a href={resolve('/doc/lanecalc')}>my guide</a> for English instructions on how to use it.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<Sec h={3} id="uphills">Uphills</Sec>
|
<Sec
|
||||||
|
h={3}
|
||||||
|
id="uphills"
|
||||||
|
ref="https://docs.google.com/document/d/15VzW9W2tXBBTibBRbZ8IVpW6HaMX8H0RP03kq6Az7Xg/edit?tab=t.0#heading=h.wdj69udyo340"
|
||||||
|
>Uphills</Sec
|
||||||
|
>
|
||||||
<p>
|
<p>
|
||||||
Running uphill carries a penalty to target speed. This penalty scales negatively with the power stat; that is, higher power
|
Running uphill carries a penalty to target speed. This penalty scales negatively with the power stat; that is, higher power
|
||||||
means faster uphill running. It scales positively with slope angle. There is also a flat reduction in base acceleration for
|
means faster uphill running. It scales positively with slope angle. There is also a flat reduction in base acceleration for
|
||||||
@@ -579,7 +612,12 @@
|
|||||||
power on the Arima Kinen mid-race hills.
|
power on the Arima Kinen mid-race hills.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<Sec h={3} id="downhills">Downhills</Sec>
|
<Sec
|
||||||
|
h={3}
|
||||||
|
id="downhills"
|
||||||
|
ref="https://docs.google.com/document/d/15VzW9W2tXBBTibBRbZ8IVpW6HaMX8H0RP03kq6Az7Xg/edit?tab=t.0#heading=h.wdj69udyo340"
|
||||||
|
>Downhills</Sec
|
||||||
|
>
|
||||||
<p>
|
<p>
|
||||||
Running downhill allows horses to enter <i>downhill accel mode</i>. Contrary to its name, downhill accel mode does not affect
|
Running downhill allows horses to enter <i>downhill accel mode</i>. Contrary to its name, downhill accel mode does not affect
|
||||||
acceleration at all; it gives horses a target speed boost that scales with the slope angle, plus lowered HP consumption via a
|
acceleration at all; it gives horses a target speed boost that scales with the slope angle, plus lowered HP consumption via a
|
||||||
@@ -605,7 +643,12 @@
|
|||||||
Conversely, the HP savings on long downhills can be enough to drop a recovery skill or two on some tracks.
|
Conversely, the HP savings on long downhills can be enough to drop a recovery skill or two on some tracks.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<Sec h={3} id="section-speed">Section Speed</Sec>
|
<Sec
|
||||||
|
h={3}
|
||||||
|
id="section-speed"
|
||||||
|
ref="https://docs.google.com/document/d/15VzW9W2tXBBTibBRbZ8IVpW6HaMX8H0RP03kq6Az7Xg/edit?tab=t.0#heading=h.xdha40q2176g"
|
||||||
|
>Section Speed</Sec
|
||||||
|
>
|
||||||
<p>
|
<p>
|
||||||
Each section, each horse gets a random modifier to target speed. The modifier's range is determined by the wit stat.
|
Each section, each horse gets a random modifier to target speed. The modifier's range is determined by the wit stat.
|
||||||
(Curiously, the calculation uses both wit as modified by style proficiency and green skills as well as base wit.)
|
(Curiously, the calculation uses both wit as modified by style proficiency and green skills as well as base wit.)
|
||||||
@@ -649,7 +692,12 @@
|
|||||||
wit front A horse will pass in {secSpeedPassTime} seconds on average at mid race speeds.
|
wit front A horse will pass in {secSpeedPassTime} seconds on average at mid race speeds.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<Sec h={3} id="no-zone">No-Overtake Zone</Sec>
|
<Sec
|
||||||
|
h={3}
|
||||||
|
id="no-zone"
|
||||||
|
ref="https://docs.google.com/document/d/15VzW9W2tXBBTibBRbZ8IVpW6HaMX8H0RP03kq6Az7Xg/edit?tab=t.0#heading=h.7l02ti4pvzxp"
|
||||||
|
>No-Overtake Zone</Sec
|
||||||
|
>
|
||||||
<p>
|
<p>
|
||||||
The no-overtake zone is the 200m portion of the race prior to the first corner. For unclear reasons, while in the no-overtake
|
The no-overtake zone is the 200m portion of the race prior to the first corner. For unclear reasons, while in the no-overtake
|
||||||
zone, horses cannot enter overtake lane mode, which is what allows them to move away from the rail. (Overtake lane mode is
|
zone, horses cannot enter overtake lane mode, which is what allows them to move away from the rail. (Overtake lane mode is
|
||||||
@@ -1374,20 +1422,20 @@
|
|||||||
</p>
|
</p>
|
||||||
<ol class="mb-4 list-decimal pl-4">
|
<ol class="mb-4 list-decimal pl-4">
|
||||||
<li>Suzuka gets to be a runaway again!</li>
|
<li>Suzuka gets to be a runaway again!</li>
|
||||||
|
<li>Chocobon's Sprint C is easy to fix!</li>
|
||||||
<li>
|
<li>
|
||||||
Mejiro Palmer gets to have G sprint fixed and be a second runaway! Hopefully she will both stay in front of Taiki and not
|
Taiki Shuttle has a very-nearly-perfect 3-4 unique, which will be great with her as a front runner. Given her lack of
|
||||||
block Suzuka!
|
relevant built-in skills, I'm building her in MANT.
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
Taiki Shuttle has a very-nearly-perfect 3-4 unique, which will be great with her as a front runner. Given the importance of
|
|
||||||
skills, I'm building her in MANT.
|
|
||||||
</li>
|
</li>
|
||||||
</ol>
|
</ol>
|
||||||
<p>
|
<p>
|
||||||
This CM will be interesting as the first opportunity to use multiple runaways and not have them be vaporized by spot struggle.
|
This CM is interesting as the first opportunity to use multiple runaways and not have them be vaporized by spot struggle.
|
||||||
I'll actually want to click on power to make sure I break 1200, and I'll want to minimize guts to conserve more power, which
|
However, after getting a high roll with an S Sprint Palmer, I found that using two runaways was actually pretty bad. Even with
|
||||||
means I'll reasonably get the ca. 800 stamina required for them to survive. The only downside is that runaways, and front
|
1300 power, <Skill skill={skills.gw} mention />, and <Skill skill={skills.ttl} mention />, runaways spend over two thirds of
|
||||||
runners broadly, are not actually good here, because <Skill skill={skills.headon} mention /> is so wildly good in sprint now.
|
early race accelerating, so they lose the majority of their strongest race phase. Normal front runners can hardly compete with
|
||||||
|
the newly buffed <Skill skill={skills.headon} mention /> in late race, which means runaways who never got the lead they're known
|
||||||
|
for have virtually no chance. Suzuka can still fight because <Skill skill={skills.unrestrained} mention /> is also strong, but Palmer
|
||||||
|
doesn't make the cut.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<Sec h={3} id="cm15">CM15 – Cancer Cup (Takarazuka Kinen)</Sec>
|
<Sec h={3} id="cm15">CM15 – Cancer Cup (Takarazuka Kinen)</Sec>
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
let surfaceApt = $state(race.AptitudeLevel.A);
|
let surfaceApt = $state(race.AptitudeLevel.A);
|
||||||
let distanceApt = $state(race.AptitudeLevel.A);
|
let distanceApt = $state(race.AptitudeLevel.A);
|
||||||
let raceLen = $state(2000);
|
let raceLen = $state(2000);
|
||||||
const styleIsFront = $derived(style === race.RunningStyle.FrontRunner || style === race.RunningStyle.GreatEscape);
|
const styleIsFront = $derived(style === race.RunningStyle.FrontRunner || style === race.RunningStyle.Runaway);
|
||||||
const distanceType = $derived(race.distance(raceLen));
|
const distanceType = $derived(race.distance(raceLen));
|
||||||
const raceLenType = $derived(race.Distance[distanceType]);
|
const raceLenType = $derived(race.Distance[distanceType]);
|
||||||
|
|
||||||
@@ -39,7 +39,7 @@
|
|||||||
{ label: 'Pace Chaser', y: (x) => race.maxHP(raceLen, race.RunningStyle.PaceChaser, x) },
|
{ label: 'Pace Chaser', y: (x) => race.maxHP(raceLen, race.RunningStyle.PaceChaser, x) },
|
||||||
{ label: 'Late Surger', y: (x) => race.maxHP(raceLen, race.RunningStyle.LateSurger, x) },
|
{ label: 'Late Surger', y: (x) => race.maxHP(raceLen, race.RunningStyle.LateSurger, x) },
|
||||||
{ label: 'End Closer', y: (x) => race.maxHP(raceLen, race.RunningStyle.EndCloser, x) },
|
{ label: 'End Closer', y: (x) => race.maxHP(raceLen, race.RunningStyle.EndCloser, x) },
|
||||||
{ label: 'Runaway', y: (x) => race.maxHP(raceLen, race.RunningStyle.GreatEscape, x) },
|
{ label: 'Runaway', y: (x) => race.maxHP(raceLen, race.RunningStyle.Runaway, x) },
|
||||||
]);
|
]);
|
||||||
const moveLane: ComputedSeries[] = [{ label: 'Move Lane Modifier', y: (x) => race.moveLaneModifier(x) }];
|
const moveLane: ComputedSeries[] = [{ label: 'Move Lane Modifier', y: (x) => race.moveLaneModifier(x) }];
|
||||||
const uphill: ComputedSeries[] = [
|
const uphill: ComputedSeries[] = [
|
||||||
@@ -239,23 +239,43 @@
|
|||||||
|
|
||||||
<Sec h={2} id="speed">Speed</Sec>
|
<Sec h={2} id="speed">Speed</Sec>
|
||||||
|
|
||||||
<Sec h={3} id="spurt-speed">Spurt Speed</Sec>
|
<Sec
|
||||||
|
h={3}
|
||||||
|
id="spurt-speed"
|
||||||
|
ref="https://docs.google.com/document/d/15VzW9W2tXBBTibBRbZ8IVpW6HaMX8H0RP03kq6Az7Xg/edit?tab=t.0#heading=h.mpfjmgpzqalr"
|
||||||
|
>Spurt Speed</Sec
|
||||||
|
>
|
||||||
<p>Target speed during the Uma's last spurt. See also <a href="#spurt-speed-guts">the effect of Guts</a>.</p>
|
<p>Target speed during the Uma's last spurt. See also <a href="#spurt-speed-guts">the effect of Guts</a>.</p>
|
||||||
{@render statChart(race.Stat.Speed, spurtSpeed, 'Spurt Speed (m/s)', [20, 26.5], { len: true, style: true, dist: true })}
|
{@render statChart(race.Stat.Speed, spurtSpeed, 'Spurt Speed (m/s)', [20, 26.5], { len: true, style: true, dist: true })}
|
||||||
|
|
||||||
<Sec h={3} id="late-race-speed">Late Race Speed</Sec>
|
<Sec
|
||||||
|
h={3}
|
||||||
|
id="late-race-speed"
|
||||||
|
ref="https://docs.google.com/document/d/15VzW9W2tXBBTibBRbZ8IVpW6HaMX8H0RP03kq6Az7Xg/edit?tab=t.0#heading=h.43unjgrhmm7e"
|
||||||
|
>Late Race Speed</Sec
|
||||||
|
>
|
||||||
<p>Base target speed during late race when the Uma is not spurting (but not dead yet).</p>
|
<p>Base target speed during late race when the Uma is not spurting (but not dead yet).</p>
|
||||||
{@render statChart(race.Stat.Speed, nonSpurtSpeed, 'Base Target Speed (m/s)', [18, 23], { len: true, style: true, dist: true })}
|
{@render statChart(race.Stat.Speed, nonSpurtSpeed, 'Base Target Speed (m/s)', [18, 23], { len: true, style: true, dist: true })}
|
||||||
|
|
||||||
<Sec h={2} id="stamina">Stamina</Sec>
|
<Sec h={2} id="stamina">Stamina</Sec>
|
||||||
|
|
||||||
<Sec h={3} id="hp">HP</Sec>
|
<Sec
|
||||||
|
h={3}
|
||||||
|
id="hp"
|
||||||
|
ref="https://docs.google.com/document/d/15VzW9W2tXBBTibBRbZ8IVpW6HaMX8H0RP03kq6Az7Xg/edit?tab=t.0#heading=h.fnd75v9k5dx3"
|
||||||
|
>HP</Sec
|
||||||
|
>
|
||||||
<p>Max HP, i.e. starting HP.</p>
|
<p>Max HP, i.e. starting HP.</p>
|
||||||
{@render statChart(race.Stat.Stamina, hp, 'HP', [1000, 5000], { len: true })}
|
{@render statChart(race.Stat.Stamina, hp, 'HP', [1000, 5000], { len: true })}
|
||||||
|
|
||||||
<Sec h={2} id="power">Power</Sec>
|
<Sec h={2} id="power">Power</Sec>
|
||||||
|
|
||||||
<Sec h={3} id="move-lane-mod">Move Lane Modifier</Sec>
|
<Sec
|
||||||
|
h={3}
|
||||||
|
id="move-lane-mod"
|
||||||
|
ref="https://docs.google.com/document/d/15VzW9W2tXBBTibBRbZ8IVpW6HaMX8H0RP03kq6Az7Xg/edit?tab=t.0#heading=h.s5glz57t0wzd"
|
||||||
|
>Move Lane Modifier</Sec
|
||||||
|
>
|
||||||
<p>
|
<p>
|
||||||
Target speed bonus when changing lanes while under the effect of a lane change speed skill. See <a
|
Target speed bonus when changing lanes while under the effect of a lane change speed skill. See <a
|
||||||
href={resolve('/doc/frbm#lane-combo')}>lane combo</a
|
href={resolve('/doc/frbm#lane-combo')}>lane combo</a
|
||||||
@@ -263,15 +283,33 @@
|
|||||||
</p>
|
</p>
|
||||||
{@render statChart(race.Stat.Power, moveLane, 'Target Speed Modifier (m/s)', [0.2, 0.6])}
|
{@render statChart(race.Stat.Power, moveLane, 'Target Speed Modifier (m/s)', [0.2, 0.6])}
|
||||||
|
|
||||||
<Sec h={3} id="uphill">Uphill Target Speed Loss</Sec>
|
<Sec
|
||||||
|
h={3}
|
||||||
|
id="uphill"
|
||||||
|
ref="https://docs.google.com/document/d/15VzW9W2tXBBTibBRbZ8IVpW6HaMX8H0RP03kq6Az7Xg/edit?tab=t.0#heading=h.wdj69udyo340"
|
||||||
|
>Uphill Target Speed Loss</Sec
|
||||||
|
>
|
||||||
<p>Target speed modifier while running uphill.</p>
|
<p>Target speed modifier while running uphill.</p>
|
||||||
{@render statChart(race.Stat.Power, uphill, 'Target Speed Modifier (m/s)', [-2, 0])}
|
{@render statChart(race.Stat.Power, uphill, 'Target Speed Modifier (m/s)', [-2, 0])}
|
||||||
|
|
||||||
<Sec h={3} id="acceleration">Acceleration</Sec>
|
<Sec
|
||||||
<p>Acceleration.</p>
|
h={3}
|
||||||
|
id="acceleration"
|
||||||
|
ref="https://docs.google.com/document/d/15VzW9W2tXBBTibBRbZ8IVpW6HaMX8H0RP03kq6Az7Xg/edit?tab=t.0#heading=h.t9fv1oq5lsu9"
|
||||||
|
>Acceleration</Sec
|
||||||
|
>
|
||||||
|
<p>
|
||||||
|
Acceleration, i.e. the rate of change of forward velocity when target speed is greater than current speed. Stats do not affect
|
||||||
|
deceleration (acceleration when target speed is less than current speed).
|
||||||
|
</p>
|
||||||
{@render statChart(race.Stat.Power, accel, 'Acceleration (m/s²)', [0.1, 0.55], { style: true })}
|
{@render statChart(race.Stat.Power, accel, 'Acceleration (m/s²)', [0.1, 0.55], { style: true })}
|
||||||
|
|
||||||
<Sec h={3} id="lane-change-target-speed">Lane Change Target Speed</Sec>
|
<Sec
|
||||||
|
h={3}
|
||||||
|
id="lane-change-target-speed"
|
||||||
|
ref="https://docs.google.com/document/d/15VzW9W2tXBBTibBRbZ8IVpW6HaMX8H0RP03kq6Az7Xg/edit?tab=t.0#heading=h.8brkec5lv1fh"
|
||||||
|
>Lane Change Target Speed</Sec
|
||||||
|
>
|
||||||
<p>Horizontal (rather than forward) target speed of changing lanes.</p>
|
<p>Horizontal (rather than forward) target speed of changing lanes.</p>
|
||||||
<div class="mb-4 h-60 w-full md:h-96">
|
<div class="mb-4 h-60 w-full md:h-96">
|
||||||
<StatChart
|
<StatChart
|
||||||
@@ -283,8 +321,16 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Sec h={3} id="conserve-power">Conserve Power Acceleration</Sec>
|
<Sec
|
||||||
<p>Bonus acceleration at spurt start from the Conserve Power mechanic.</p>
|
h={3}
|
||||||
|
id="conserve-power"
|
||||||
|
ref="https://docs.google.com/document/d/15VzW9W2tXBBTibBRbZ8IVpW6HaMX8H0RP03kq6Az7Xg/edit?tab=t.0#heading=h.9edgwx6l733b"
|
||||||
|
>Fully Charged Acceleration</Sec
|
||||||
|
>
|
||||||
|
<p>
|
||||||
|
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.
|
||||||
|
</p>
|
||||||
<p>
|
<p>
|
||||||
This mechanic does not appear to be well understood, in particular how long the acceleration bonus lasts and how the rushed
|
This mechanic does not appear to be well understood, in particular how long the acceleration bonus lasts and how the rushed
|
||||||
and spot struggle multipliers apply.
|
and spot struggle multipliers apply.
|
||||||
@@ -293,26 +339,51 @@
|
|||||||
|
|
||||||
<Sec h={2} id="guts">Guts</Sec>
|
<Sec h={2} id="guts">Guts</Sec>
|
||||||
|
|
||||||
<Sec h={3} id="spurt-speed-guts">Spurt Speed</Sec>
|
<Sec
|
||||||
|
h={3}
|
||||||
|
id="spurt-speed-guts"
|
||||||
|
ref="https://docs.google.com/document/d/15VzW9W2tXBBTibBRbZ8IVpW6HaMX8H0RP03kq6Az7Xg/edit?tab=t.0#heading=h.mpfjmgpzqalr"
|
||||||
|
>Spurt Speed</Sec
|
||||||
|
>
|
||||||
<p>Target speed during the Uma's last spurt. See also <a href="#spurt-speed">the effect of Speed</a>.</p>
|
<p>Target speed during the Uma's last spurt. See also <a href="#spurt-speed">the effect of Speed</a>.</p>
|
||||||
{@render statChart(race.Stat.Guts, gutsSpurt, 'Spurt Speed (m/s)', [20, 26.5], { len: true, style: true, dist: true })}
|
{@render statChart(race.Stat.Guts, gutsSpurt, 'Spurt Speed (m/s)', [20, 26.5], { len: true, style: true, dist: true })}
|
||||||
|
|
||||||
<Sec h={3} id="min-speed">Minimum Speed</Sec>
|
<Sec
|
||||||
|
h={3}
|
||||||
|
id="min-speed"
|
||||||
|
ref="https://docs.google.com/document/d/15VzW9W2tXBBTibBRbZ8IVpW6HaMX8H0RP03kq6Az7Xg/edit?tab=t.0#heading=h.nn1kp5g8fdhu"
|
||||||
|
>Minimum Speed</Sec
|
||||||
|
>
|
||||||
<p>Forced minimum speed, as well as target speed when out of HP.</p>
|
<p>Forced minimum speed, as well as target speed when out of HP.</p>
|
||||||
{@render statChart(race.Stat.Guts, minSpeed, 'Target Speed (m/s)', [15.8, 18.5], { len: true })}
|
{@render statChart(race.Stat.Guts, minSpeed, 'Target Speed (m/s)', [15.8, 18.5], { len: true })}
|
||||||
|
|
||||||
<Sec h={3} id="hp-rate-mod">Late Race HP Consumption Rate Modifier</Sec>
|
<Sec
|
||||||
|
h={3}
|
||||||
|
id="hp-rate-mod"
|
||||||
|
ref="https://docs.google.com/document/d/15VzW9W2tXBBTibBRbZ8IVpW6HaMX8H0RP03kq6Az7Xg/edit?tab=t.0#heading=h.o0be6rj0myb1"
|
||||||
|
>Late Race HP Consumption Rate Modifier</Sec
|
||||||
|
>
|
||||||
<p>Multiplier on HP consumption rate during late race and last spurt phase.</p>
|
<p>Multiplier on HP consumption rate during late race and last spurt phase.</p>
|
||||||
{@render statChart(race.Stat.Guts, gutsHPRate, 'HP Consumption Multiplier', [1.2, 1.6])}
|
{@render statChart(race.Stat.Guts, gutsHPRate, 'HP Consumption Multiplier', [1.2, 1.6])}
|
||||||
|
|
||||||
<Sec h={3} id="spot-struggle">Spot Struggle</Sec>
|
<Sec
|
||||||
|
h={3}
|
||||||
|
id="spot-struggle"
|
||||||
|
ref="https://docs.google.com/document/d/15VzW9W2tXBBTibBRbZ8IVpW6HaMX8H0RP03kq6Az7Xg/edit?tab=t.0#heading=h.5l523bk8k3vz"
|
||||||
|
>Spot Struggle</Sec
|
||||||
|
>
|
||||||
<p>Speed boost and duration of spot struggle.</p>
|
<p>Speed boost and duration of spot struggle.</p>
|
||||||
<div class="mb-4 grid h-96 w-full md:grid-cols-2">
|
<div class="mb-4 grid h-96 w-full md:grid-cols-2">
|
||||||
<StatChart stat={race.Stat.Guts} y={ssBoost} yLabel="Speed Bonus (m/s)" range={[0, 0.4]} />
|
<StatChart stat={race.Stat.Guts} y={ssBoost} yLabel="Speed Bonus (m/s)" range={[0, 0.4]} />
|
||||||
<StatChart stat={race.Stat.Guts} y={ssDur} yLabel="Duration (s)" range={[0, 14]} />
|
<StatChart stat={race.Stat.Guts} y={ssDur} yLabel="Duration (s)" range={[0, 14]} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Sec h={3} id="dueling">Dueling</Sec>
|
<Sec
|
||||||
|
h={3}
|
||||||
|
id="dueling"
|
||||||
|
ref="https://docs.google.com/document/d/15VzW9W2tXBBTibBRbZ8IVpW6HaMX8H0RP03kq6Az7Xg/edit?tab=t.0#heading=h.98rgrb7ww1td"
|
||||||
|
>Dueling</Sec
|
||||||
|
>
|
||||||
<p>Speed boost and acceleration boost of dueling.</p>
|
<p>Speed boost and acceleration boost of dueling.</p>
|
||||||
<div class="mb-4 grid h-96 w-full md:grid-cols-2">
|
<div class="mb-4 grid h-96 w-full md:grid-cols-2">
|
||||||
<StatChart stat={race.Stat.Guts} y={duelSpeed} yLabel="Speed Bonus (m/s)" range={[0, 0.8]} />
|
<StatChart stat={race.Stat.Guts} y={duelSpeed} yLabel="Speed Bonus (m/s)" range={[0, 0.8]} />
|
||||||
@@ -321,7 +392,12 @@
|
|||||||
|
|
||||||
<Sec h={2} id="wit">Wit</Sec>
|
<Sec h={2} id="wit">Wit</Sec>
|
||||||
|
|
||||||
<Sec h={3} id="section-speed">Section Speed</Sec>
|
<Sec
|
||||||
|
h={3}
|
||||||
|
id="section-speed"
|
||||||
|
ref="https://docs.google.com/document/d/15VzW9W2tXBBTibBRbZ8IVpW6HaMX8H0RP03kq6Az7Xg/edit?tab=t.0#heading=h.xdha40q2176g"
|
||||||
|
>Section Speed</Sec
|
||||||
|
>
|
||||||
<p>Random variance in target speed per race section.</p>
|
<p>Random variance in target speed per race section.</p>
|
||||||
<div class="mb-24 h-60 w-full md:mb-20 md:h-96">
|
<div class="mb-24 h-60 w-full md:mb-20 md:h-96">
|
||||||
<StatChart
|
<StatChart
|
||||||
@@ -353,29 +429,54 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Sec h={3} id="downhill">Downhill Accel Mode Chance</Sec>
|
<Sec
|
||||||
|
h={3}
|
||||||
|
id="downhill"
|
||||||
|
ref="https://docs.google.com/document/d/15VzW9W2tXBBTibBRbZ8IVpW6HaMX8H0RP03kq6Az7Xg/edit?tab=t.0#heading=h.wdj69udyo340"
|
||||||
|
>Downhill Accel Mode Chance</Sec
|
||||||
|
>
|
||||||
<p>Chance each second to enter downhill accel mode when eligible.</p>
|
<p>Chance each second to enter downhill accel mode when eligible.</p>
|
||||||
{@render statChart(race.Stat.Wit, downhill, 'Entry Chance (% each second)', [0, 75])}
|
{@render statChart(race.Stat.Wit, downhill, 'Entry Chance (% each second)', [0, 75])}
|
||||||
|
|
||||||
<Sec h={3} id="spurt-accept">Reduced Spurt Accept Chance</Sec>
|
<Sec
|
||||||
|
h={3}
|
||||||
|
id="spurt-accept"
|
||||||
|
ref="https://docs.google.com/document/d/15VzW9W2tXBBTibBRbZ8IVpW6HaMX8H0RP03kq6Az7Xg/edit?tab=t.0#heading=h.mpfjmgpzqalr"
|
||||||
|
>Reduced Spurt Accept Chance</Sec
|
||||||
|
>
|
||||||
<p>Chance to accept each checked spurt delay and speed when not full spurting.</p>
|
<p>Chance to accept each checked spurt delay and speed when not full spurting.</p>
|
||||||
{@render statChart(race.Stat.Wit, reducedSpurt, 'Accept Chance (% each candidate)', [0, 100])}
|
{@render statChart(race.Stat.Wit, reducedSpurt, 'Accept Chance (% each candidate)', [0, 100])}
|
||||||
|
|
||||||
<Sec h={3} id="skill">Skill Activation Chance</Sec>
|
<Sec
|
||||||
|
h={3}
|
||||||
|
id="skill"
|
||||||
|
ref="https://docs.google.com/document/d/15VzW9W2tXBBTibBRbZ8IVpW6HaMX8H0RP03kq6Az7Xg/edit?tab=t.0#heading=h.23v0k7wyzcmb"
|
||||||
|
>Skill Activation Chance</Sec
|
||||||
|
>
|
||||||
<p>
|
<p>
|
||||||
Chance that each skill that has a wit check will be eligible to activate. This uses base wit, so style aptitude and passive
|
Chance that each skill that has a wit check will be eligible to activate. This uses base wit, so style aptitude and passive
|
||||||
wit skills do not affect the chance, although mood does.
|
wit skills do not affect the chance, although mood does.
|
||||||
</p>
|
</p>
|
||||||
{@render statChart(race.Stat.Wit, skillChance, 'Skill Activation Chance (%)', [0, 100])}
|
{@render statChart(race.Stat.Wit, skillChance, 'Skill Activation Chance (%)', [0, 100])}
|
||||||
|
|
||||||
<Sec h={3} id="poskeep">Position Keep Mode Chance</Sec>
|
<Sec
|
||||||
|
h={3}
|
||||||
|
id="poskeep"
|
||||||
|
ref="https://docs.google.com/document/d/15VzW9W2tXBBTibBRbZ8IVpW6HaMX8H0RP03kq6Az7Xg/edit?tab=t.0#heading=h.tulele26d1pm"
|
||||||
|
>Position Keep Mode Chance</Sec
|
||||||
|
>
|
||||||
<p>
|
<p>
|
||||||
Chance for front runners to enter speed-up or overtake mode, or for non-front runners to enter pace-up mode (but no effect on
|
Chance for front runners to enter speed-up or overtake mode, or for non-front runners to enter pace-up mode (but no effect on
|
||||||
pace-down mode), each 2 seconds when eligible.
|
pace-down mode), each 2 seconds when eligible.
|
||||||
</p>
|
</p>
|
||||||
{@render statChart(race.Stat.Wit, poskeep, 'Mode Entry Chance (%)', [0, 50])}
|
{@render statChart(race.Stat.Wit, poskeep, 'Mode Entry Chance (%)', [0, 50])}
|
||||||
|
|
||||||
<Sec h={3} id="rushed">Rushed Chance</Sec>
|
<Sec
|
||||||
|
h={3}
|
||||||
|
id="rushed"
|
||||||
|
ref="https://docs.google.com/document/d/15VzW9W2tXBBTibBRbZ8IVpW6HaMX8H0RP03kq6Az7Xg/edit?tab=t.0#heading=h.awbjufvej6ud"
|
||||||
|
>Rushed Chance</Sec
|
||||||
|
>
|
||||||
<p>Chance for runners to become rushed at some point during the race. Rushed chance is bracketed to integer percentages.</p>
|
<p>Chance for runners to become rushed at some point during the race. Rushed chance is bracketed to integer percentages.</p>
|
||||||
{@render statChart(race.Stat.Wit, rushed, 'Rushed Chance (%)', [0, 30])}
|
{@render statChart(race.Stat.Wit, rushed, 'Rushed Chance (%)', [0, 30])}
|
||||||
</Article>
|
</Article>
|
||||||
|
|||||||
@@ -52,14 +52,14 @@
|
|||||||
}
|
}
|
||||||
const curClass = $derived(sparkClass(group?.type));
|
const curClass = $derived(sparkClass(group?.type));
|
||||||
|
|
||||||
const key = (v: spark.SparkGroup) => ({id: v.spark_group, name: v.name})
|
const key = (v: spark.SparkGroup) => ({ id: v.spark_group, name: v.name });
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<h1 class="text-4xl">Spark Explorer</h1>
|
<h1 class="text-4xl">Spark Explorer</h1>
|
||||||
<form class="mx-auto mt-8 rounded-md p-4 text-center shadow-md ring md:max-w-2xl">
|
<form class="mx-auto mt-8 rounded-md p-4 text-center shadow-md ring md:max-w-2xl">
|
||||||
<Pick id="spark" items={groups} class="w-full" {key} bind:value={group} required>
|
<Pick id="spark" items={groups} class="w-full" {key} bind:value={group} required>
|
||||||
{#snippet option(g)}
|
{#snippet option(g)}
|
||||||
<span class={['spark px-2 py-1 rounded', sparkClass(g.type)]}>{g.name}</span>
|
<span class={['spark rounded px-2 py-1', sparkClass(g.type)]}>{g.name}</span>
|
||||||
{/snippet}
|
{/snippet}
|
||||||
</Pick>
|
</Pick>
|
||||||
</form>
|
</form>
|
||||||
@@ -83,9 +83,9 @@
|
|||||||
<tbody>
|
<tbody>
|
||||||
{#each curDescriptions as s (s)}
|
{#each curDescriptions as s (s)}
|
||||||
<tr>
|
<tr>
|
||||||
<td class="flex gap-2 p-1 text-center border border-mist-400 dark:border-mist-950">
|
<td class="flex gap-2 border border-mist-400 p-1 text-center dark:border-mist-950">
|
||||||
{#each s as n (n)}
|
{#each s as n (n)}
|
||||||
<span class="flex-1 my-auto">{n}</span>
|
<span class="my-auto flex-1">{n}</span>
|
||||||
{/each}
|
{/each}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|||||||
@@ -0,0 +1,158 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { onMount } from 'svelte';
|
||||||
|
import * as skill from '$lib/data/skill';
|
||||||
|
import SkillIcon from '$lib/SkillIcon.svelte';
|
||||||
|
|
||||||
|
let rare = $state(false);
|
||||||
|
let unique = $state(false);
|
||||||
|
|
||||||
|
let allSkillsList: skill.Skill[] = $state([]);
|
||||||
|
onMount(async () => {
|
||||||
|
allSkillsList = await skill.skills();
|
||||||
|
});
|
||||||
|
|
||||||
|
const tagTypes = $state([
|
||||||
|
{
|
||||||
|
name: 'Running Style',
|
||||||
|
includes: skill.TAGS.filter((t) => 101 <= t.tag && t.tag <= 199).map((t) => ({ ...t, on: false })),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Distance',
|
||||||
|
includes: skill.TAGS.filter((t) => 201 <= t.tag && t.tag <= 299).map((t) => ({ ...t, on: false })),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Surface',
|
||||||
|
includes: skill.TAGS.filter((t) => 501 <= t.tag && t.tag <= 599).map((t) => ({ ...t, on: false })),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Timing',
|
||||||
|
includes: skill.TAGS.filter((t) => 301 <= t.tag && t.tag <= 399).map((t) => ({ ...t, on: false })),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Effects',
|
||||||
|
includes: skill.TAGS.filter((t) => 401 <= t.tag && t.tag <= 499).map((t) => ({ ...t, on: false })),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Passive Types',
|
||||||
|
includes: skill.TAGS.filter((t) => 601 <= t.tag && t.tag <= 699).map((t) => ({ ...t, on: false })),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Scenario Skills',
|
||||||
|
includes: skill.TAGS.filter((t) => 801 <= t.tag && t.tag <= 899).map((t) => ({ ...t, on: false })),
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
const tags = $derived(tagTypes.flatMap((v) => v.includes.filter((t) => t.on).map((t) => t.tag)));
|
||||||
|
const skills = $derived(
|
||||||
|
allSkillsList.filter(
|
||||||
|
(s) =>
|
||||||
|
(rare || s.group_rate === 1) &&
|
||||||
|
(unique || (s.rarity < 3 && s.unique_owner == null)) &&
|
||||||
|
tags.some((t) => s.tags.includes(t)),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<h1 class="text-4xl">Tagged Skills Explorer</h1>
|
||||||
|
<form class="mx-auto my-8 rounded-md p-4 text-center shadow-md ring md:max-w-4xl">
|
||||||
|
<div class="flex w-full gap-4">
|
||||||
|
<div class="my-auto inline-block flex-1">
|
||||||
|
<input id="rare" type="checkbox" role="switch" class="min-h-6 min-w-6 align-middle" bind:checked={rare} />
|
||||||
|
<label for="rare">Include All Levels and Rarities</label>
|
||||||
|
</div>
|
||||||
|
<div class="my-auto inline-block flex-1">
|
||||||
|
<input id="unique" type="checkbox" role="switch" class="min-h-6 min-w-6 align-middle" bind:checked={unique} />
|
||||||
|
<label for="unique">Include Uniques</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{#each tagTypes as { name, includes } (name)}
|
||||||
|
<div class="mt-1 flex flex-row">
|
||||||
|
<span class="my-auto h-0 flex-1 border"></span>
|
||||||
|
<span class="m-2 flex-none text-sm italic">{name}</span>
|
||||||
|
<span class="my-auto h-0 flex-1 border"></span>
|
||||||
|
</div>
|
||||||
|
<div class="flex w-full flex-wrap place-content-center-safe gap-6 md:grid-cols-4">
|
||||||
|
{#each includes as t (t.tag)}
|
||||||
|
<div class="inline-block">
|
||||||
|
<input id={t.name} type="checkbox" class="min-h-6 min-w-6 align-middle" bind:checked={t.on} />
|
||||||
|
<label for={t.name} class="align-middle">{t.name}</label>
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
</form>
|
||||||
|
|
||||||
|
{#snippet condition(cond: string)}
|
||||||
|
{@const disj = cond.replaceAll('&', ' & ').split('@')}
|
||||||
|
{#each disj as conj, i (conj)}
|
||||||
|
<span class="block font-mono text-sm">{conj}</span>
|
||||||
|
{#if i !== disj.length - 1}
|
||||||
|
<span class="block text-xs italic">or</span>
|
||||||
|
{/if}
|
||||||
|
{/each}
|
||||||
|
{/snippet}
|
||||||
|
|
||||||
|
<span class="mb-4 block text-center text-2xl">{skills.length} skills selected</span>
|
||||||
|
<div class="grid max-w-7xl grid-cols-1 gap-2 md:grid-cols-3">
|
||||||
|
{#each skills as s (s.skill_id)}
|
||||||
|
<div class="rounded-md border px-2 pt-1 pb-2 text-center shadow-sm transition-shadow hover:shadow-md">
|
||||||
|
<span class="block text-xl">
|
||||||
|
<SkillIcon icon_id={s?.icon_id} size={32} />
|
||||||
|
{s.name}
|
||||||
|
</span>
|
||||||
|
<span class="block italic">{s.description}</span>
|
||||||
|
{#each s.activations as act, i (i)}
|
||||||
|
<span class="my-2 block rounded-md border">
|
||||||
|
{#if act.precondition}
|
||||||
|
<span class="block border-b py-1">
|
||||||
|
<span class="text-xs italic">Precondition</span>
|
||||||
|
{@render condition(act.precondition)}
|
||||||
|
</span>
|
||||||
|
{/if}
|
||||||
|
<span class="block border-b py-1">
|
||||||
|
{@render condition(act.condition)}
|
||||||
|
</span>
|
||||||
|
<span class="block py-1">
|
||||||
|
{#each act.abilities as a, i (i)}
|
||||||
|
<span class="block text-sm">
|
||||||
|
{console.log(a.type)}
|
||||||
|
{skill.ABILITY_TYPE_FORMAT[a.type](a.value)}
|
||||||
|
{skill.ABILITY_SCALE_NAME[a.value_usage]}
|
||||||
|
{#if a.target && a.target !== skill.Target.Self}
|
||||||
|
{skill.TARGET_FORMAT[a.target](a.target_value)}
|
||||||
|
{/if}
|
||||||
|
</span>
|
||||||
|
{/each}
|
||||||
|
{#if act.duration != null && act.duration > 0}
|
||||||
|
<span class="block text-sm">
|
||||||
|
for {skill.tenThousandths(act.duration)}s
|
||||||
|
{skill.DURATION_SCALE_NAME[act.dur_scale]}
|
||||||
|
</span>
|
||||||
|
{/if}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
{/each}
|
||||||
|
<div class="flex w-full text-xs">
|
||||||
|
<span class="flex-1">ID {s.skill_id}</span>
|
||||||
|
{#if s.unique_owner}
|
||||||
|
<span class="flex-1">{s.unique_owner}</span>
|
||||||
|
{/if}
|
||||||
|
{#if s.sp_cost}
|
||||||
|
<span class="flex-1">{s.sp_cost} SP</span>
|
||||||
|
{/if}
|
||||||
|
<span class="flex-1">Grade Value {s.grade_value}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mx-auto mt-12 w-full max-w-4xl border-t pt-4 md:mt-8">
|
||||||
|
<ul class="ml-4 list-disc">
|
||||||
|
<li>
|
||||||
|
To see skills that can be received from MANT/Trackblazer rivals, select the running style used and the distance and surface
|
||||||
|
of the race. Some skills require meeting multiple conditions to be eligible hints, e.g. both Pace and Long; they will
|
||||||
|
instead be shown in the tool when either condition is selected.
|
||||||
|
</li>
|
||||||
|
<li>Skills for each tag are auto-generated from the game's local database.</li>
|
||||||
|
<li>Tag names are inferred from the skills that have them.</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
@@ -1,20 +1,11 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { resolve } from '$app/paths';
|
import { page } from '$app/state';
|
||||||
|
import * as all from '$lib/all';
|
||||||
|
import IndexSection from '$lib/IndexSection.svelte';
|
||||||
|
|
||||||
|
const cur = $derived(all.page(page.route.id));
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<h1 class="m-8 text-center text-7xl">Zenno Rob Roy — Character Tools</h1>
|
<h1 class="m-8 text-center text-7xl">Zenno Rob Roy — {cur.name}</h1>
|
||||||
<p>Tools related to understanding Umamusume characters.</p>
|
<p>{cur.description}</p>
|
||||||
|
<IndexSection pages={all.PAGES.race} />
|
||||||
<ul class="mb-4 list-disc pl-4">
|
|
||||||
<li>
|
|
||||||
<a href={resolve('/race/spurt')}>Spurt Speed</a> — Calculate a horse's target speed in the last spurt and compare to other
|
|
||||||
distance aptitudes and running styles.
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href={resolve('/race/mspeed')}>Front Runner Mechanical Speed Comparator</a> — Compare spot struggle and lane combo to the
|
|
||||||
effects of individual skills.
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href={resolve('/race/gate')}>Gate Calculator</a> — Calculate the importance of acceleration effects at the starting gate.
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import type { Snippet } from 'svelte';
|
||||||
|
import type { ClassValue } from 'svelte/elements';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
title: Snippet | string;
|
||||||
|
children: Snippet;
|
||||||
|
additional?: Snippet;
|
||||||
|
class?: ClassValue | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
let { title, children, additional, class: className }: Props = $props();
|
||||||
|
|
||||||
|
const cl = $derived([
|
||||||
|
'm-2 flex flex-col rounded-md border p-2 text-center shadow-sm transition-shadow hover:shadow-md',
|
||||||
|
className,
|
||||||
|
]);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class={cl}>
|
||||||
|
<span class="block">
|
||||||
|
{#if typeof title === 'string'}
|
||||||
|
{title}
|
||||||
|
{:else}
|
||||||
|
{@render title()}
|
||||||
|
{/if}
|
||||||
|
</span>
|
||||||
|
<span class="block text-xl">
|
||||||
|
{@render children()}
|
||||||
|
</span>
|
||||||
|
{@render additional?.()}
|
||||||
|
</div>
|
||||||
@@ -3,6 +3,7 @@
|
|||||||
import * as race from '$lib/race';
|
import * as race from '$lib/race';
|
||||||
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';
|
||||||
|
|
||||||
let rawPower = $state(1200);
|
let rawPower = $state(1200);
|
||||||
let rawGuts = $state(1200);
|
let rawGuts = $state(1200);
|
||||||
@@ -284,14 +285,13 @@
|
|||||||
</form>
|
</form>
|
||||||
|
|
||||||
{#snippet info(name: string, unit: string, val: number, val2?: number)}
|
{#snippet info(name: string, unit: string, val: number, val2?: number)}
|
||||||
<div class="m-2 max-w-72 flex-1 flex-col rounded-md border p-2 text-center shadow-sm transition-shadow hover:shadow-md">
|
<CalcInfo title={name} class="max-w-72 flex-1">
|
||||||
<span class="block">{name}</span>
|
|
||||||
{#if val2 == null}
|
{#if val2 == null}
|
||||||
<span class="block text-xl">{val.toFixed(3)} {unit}</span>
|
<span class="block text-xl">{val.toFixed(3)} {unit}</span>
|
||||||
{:else}
|
{:else}
|
||||||
<span class="block text-xl">{val.toFixed(3)} – {val2.toFixed(3)} {unit}</span>
|
<span class="block text-xl">{val.toFixed(3)} – {val2.toFixed(3)} {unit}</span>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</CalcInfo>
|
||||||
{/snippet}
|
{/snippet}
|
||||||
|
|
||||||
<div class="mx-auto flex w-full flex-col place-items-center md:flex-row md:justify-center">
|
<div class="mx-auto flex w-full flex-col place-items-center md:flex-row md:justify-center">
|
||||||
|
|||||||
@@ -30,7 +30,7 @@
|
|||||||
const careerMod = $derived(isCareer ? 400 : 0);
|
const careerMod = $derived(isCareer ? 400 : 0);
|
||||||
const powerStat = $derived(rawPower + careerMod);
|
const powerStat = $derived(rawPower + careerMod);
|
||||||
const gutsStat = $derived(rawGuts + careerMod);
|
const gutsStat = $derived(rawGuts + careerMod);
|
||||||
const style = $derived(isRunaway ? RunningStyle.GreatEscape : RunningStyle.FrontRunner);
|
const style = $derived(isRunaway ? RunningStyle.Runaway : RunningStyle.FrontRunner);
|
||||||
|
|
||||||
const phases = [Phase.EarlyRace, Phase.MidRace, Phase.LateRace] as const;
|
const phases = [Phase.EarlyRace, Phase.MidRace, Phase.LateRace] as const;
|
||||||
const accel = $derived(phases.map((p) => acceleration(powerStat, style, surfApt, p)));
|
const accel = $derived(phases.map((p) => acceleration(powerStat, style, surfApt, p)));
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { HORSE_LENGTH, speedGain } from '$lib/race';
|
import { HORSE_LENGTH, speedGain } from '$lib/race';
|
||||||
import type { Snippet } from 'svelte';
|
import type { Snippet } from 'svelte';
|
||||||
|
import CalcInfo from '../CalcInfo.svelte';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
children: Snippet;
|
children: Snippet;
|
||||||
@@ -21,13 +22,12 @@
|
|||||||
const text = $derived(gain.map(fmtp).join(' – '));
|
const text = $derived(gain.map(fmtp).join(' – '));
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div
|
<CalcInfo title={children} class="max-w-80 flex-1">
|
||||||
class="m-2 flex h-full w-full max-w-80 flex-1 flex-col rounded-md border p-2 text-center shadow-sm transition-shadow hover:shadow-md"
|
{text} L
|
||||||
>
|
{#snippet additional()}
|
||||||
<div class="block">{@render children()}</div>
|
|
||||||
<span class="block text-xl">{text} L</span>
|
|
||||||
<div class="flex flex-row">
|
<div class="flex flex-row">
|
||||||
<span class="flex-1 text-xs">{fmtp(speed)} m/s</span>
|
<span class="flex-1 text-xs">{fmtp(speed)} m/s</span>
|
||||||
<span class="flex-1 text-xs">{dur.toFixed(3)} s</span>
|
<span class="flex-1 text-xs">{dur.toFixed(3)} s</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
{/snippet}
|
||||||
|
</CalcInfo>
|
||||||
|
|||||||
@@ -1,61 +1,74 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { ComputedSeries } from '$lib/chart';
|
import type { ComputedSeries } from '$lib/chart';
|
||||||
import { AptitudeLevel, inverseSpurtSpeed, RunningStyle, spurtSpeed, Stat } from '$lib/race';
|
import * as race from '$lib/race';
|
||||||
import StatChart from '$lib/StatChart.svelte';
|
import StatChart from '$lib/StatChart.svelte';
|
||||||
|
import CalcInfo from '../CalcInfo.svelte';
|
||||||
|
|
||||||
const aptsList = Object.entries(AptitudeLevel).filter(([, val]) => typeof val === 'number');
|
|
||||||
const stylesList = [
|
|
||||||
['Front Runner', RunningStyle.FrontRunner],
|
|
||||||
['Pace Chaser', RunningStyle.PaceChaser],
|
|
||||||
['Late Surger', RunningStyle.LateSurger],
|
|
||||||
['End Closer', RunningStyle.EndCloser],
|
|
||||||
['Great Escape', RunningStyle.GreatEscape],
|
|
||||||
] as const;
|
|
||||||
const skillSpeeds = [0.45, 0.35, 0.25, 0.15] as const;
|
const skillSpeeds = [0.45, 0.35, 0.25, 0.15] as const;
|
||||||
|
|
||||||
let rawSpeed: number = $state(1200);
|
let rawSpeed: number = $state(1200);
|
||||||
let rawGuts: number = $state(1200);
|
let rawGuts: number = $state(1200);
|
||||||
let style: RunningStyle = $state(RunningStyle.FrontRunner);
|
let style: race.RunningStyle = $state(race.RunningStyle.FrontRunner);
|
||||||
let opponentStyle: RunningStyle = $state(RunningStyle.PaceChaser);
|
let opponentStyle: race.RunningStyle = $state(race.RunningStyle.PaceChaser);
|
||||||
let distanceApt: AptitudeLevel = $state(AptitudeLevel.S);
|
let distanceApt: race.AptitudeLevel = $state(race.AptitudeLevel.S);
|
||||||
let raceLen: number = $state(2000);
|
let raceLen: number = $state(2000);
|
||||||
let isCareer: boolean = $state(false);
|
let isCareer: boolean = $state(false);
|
||||||
|
|
||||||
const careerMod = $derived(isCareer ? 400 : 0);
|
const careerMod = $derived(isCareer ? 400 : 0);
|
||||||
const speedStat = $derived(rawSpeed + careerMod);
|
const speedStat = $derived(rawSpeed + careerMod);
|
||||||
const gutsStat = $derived(rawGuts + careerMod);
|
const gutsStat = $derived(rawGuts + careerMod);
|
||||||
const speed = $derived(spurtSpeed(speedStat, gutsStat, style, distanceApt, raceLen));
|
const speed = $derived(race.spurtSpeed(speedStat, gutsStat, style, distanceApt, raceLen));
|
||||||
|
const duelBonus = $derived(race.duelSpeedMod(gutsStat));
|
||||||
|
|
||||||
const sProf = $derived([
|
const sProf = $derived([
|
||||||
inverseSpurtSpeed(speed, gutsStat, RunningStyle.FrontRunner, AptitudeLevel.S, raceLen) - careerMod,
|
race.inverseSpurtSpeed(speed, gutsStat, race.RunningStyle.FrontRunner, race.AptitudeLevel.S, raceLen) - careerMod,
|
||||||
inverseSpurtSpeed(speed, gutsStat, RunningStyle.PaceChaser, AptitudeLevel.S, raceLen) - careerMod,
|
race.inverseSpurtSpeed(speed, gutsStat, race.RunningStyle.PaceChaser, race.AptitudeLevel.S, raceLen) - careerMod,
|
||||||
inverseSpurtSpeed(speed, gutsStat, RunningStyle.LateSurger, AptitudeLevel.S, raceLen) - careerMod,
|
race.inverseSpurtSpeed(speed, gutsStat, race.RunningStyle.LateSurger, race.AptitudeLevel.S, raceLen) - careerMod,
|
||||||
inverseSpurtSpeed(speed, gutsStat, RunningStyle.EndCloser, AptitudeLevel.S, raceLen) - careerMod,
|
race.inverseSpurtSpeed(speed, gutsStat, race.RunningStyle.EndCloser, race.AptitudeLevel.S, raceLen) - careerMod,
|
||||||
inverseSpurtSpeed(speed, gutsStat, RunningStyle.GreatEscape, AptitudeLevel.S, raceLen) - careerMod,
|
race.inverseSpurtSpeed(speed, gutsStat, race.RunningStyle.Runaway, race.AptitudeLevel.S, raceLen) - careerMod,
|
||||||
]);
|
]);
|
||||||
const aProf = $derived([
|
const aProf = $derived([
|
||||||
inverseSpurtSpeed(speed, gutsStat, RunningStyle.FrontRunner, AptitudeLevel.A, raceLen) - careerMod,
|
race.inverseSpurtSpeed(speed, gutsStat, race.RunningStyle.FrontRunner, race.AptitudeLevel.A, raceLen) - careerMod,
|
||||||
inverseSpurtSpeed(speed, gutsStat, RunningStyle.PaceChaser, AptitudeLevel.A, raceLen) - careerMod,
|
race.inverseSpurtSpeed(speed, gutsStat, race.RunningStyle.PaceChaser, race.AptitudeLevel.A, raceLen) - careerMod,
|
||||||
inverseSpurtSpeed(speed, gutsStat, RunningStyle.LateSurger, AptitudeLevel.A, raceLen) - careerMod,
|
race.inverseSpurtSpeed(speed, gutsStat, race.RunningStyle.LateSurger, race.AptitudeLevel.A, raceLen) - careerMod,
|
||||||
inverseSpurtSpeed(speed, gutsStat, RunningStyle.EndCloser, AptitudeLevel.A, raceLen) - careerMod,
|
race.inverseSpurtSpeed(speed, gutsStat, race.RunningStyle.EndCloser, race.AptitudeLevel.A, raceLen) - careerMod,
|
||||||
inverseSpurtSpeed(speed, gutsStat, RunningStyle.GreatEscape, AptitudeLevel.A, raceLen) - careerMod,
|
race.inverseSpurtSpeed(speed, gutsStat, race.RunningStyle.Runaway, race.AptitudeLevel.A, raceLen) - careerMod,
|
||||||
]);
|
]);
|
||||||
const skillProf = $derived(
|
const skillProf = $derived(
|
||||||
skillSpeeds.map((v) => [v, inverseSpurtSpeed(speed + v, gutsStat, opponentStyle, AptitudeLevel.S, raceLen) - careerMod]),
|
skillSpeeds.map((v) => [
|
||||||
|
v,
|
||||||
|
race.inverseSpurtSpeed(speed + v, gutsStat, opponentStyle, race.AptitudeLevel.S, raceLen) - careerMod,
|
||||||
|
]),
|
||||||
);
|
);
|
||||||
|
|
||||||
const y: Array<ComputedSeries | null> = $derived([
|
const speedChart: Array<ComputedSeries | null> = $derived([
|
||||||
{ label: 'Aptitude S', y: (x) => spurtSpeed(x, gutsStat, style, AptitudeLevel.S, raceLen) },
|
{ label: 'Aptitude S', y: (x) => race.spurtSpeed(x, gutsStat, style, race.AptitudeLevel.S, raceLen) },
|
||||||
{ label: 'Aptitude A', y: (x) => spurtSpeed(x, gutsStat, style, AptitudeLevel.A, raceLen) },
|
{
|
||||||
distanceApt < AptitudeLevel.A
|
label: `Aptitude ${race.AptitudeLevel[distanceApt]} + Dueling`,
|
||||||
? { label: `Aptitude ${AptitudeLevel[distanceApt]}`, y: (x) => spurtSpeed(x, gutsStat, style, distanceApt, raceLen) }
|
y: (x) => race.spurtSpeed(x, gutsStat, style, distanceApt, raceLen) + duelBonus,
|
||||||
|
},
|
||||||
|
distanceApt !== race.AptitudeLevel.S
|
||||||
|
? {
|
||||||
|
label: `Aptitude ${race.AptitudeLevel[distanceApt]}`,
|
||||||
|
y: (x) => race.spurtSpeed(x, gutsStat, style, distanceApt, raceLen),
|
||||||
|
}
|
||||||
|
: null,
|
||||||
|
]);
|
||||||
|
const gutsChart: Array<ComputedSeries | null> = $derived([
|
||||||
|
{ label: 'Aptitude S', y: (x) => race.spurtSpeed(speedStat, x, style, race.AptitudeLevel.S, raceLen) },
|
||||||
|
{
|
||||||
|
label: `Aptitude ${race.AptitudeLevel[distanceApt]} + Dueling`,
|
||||||
|
y: (x) => race.spurtSpeed(speedStat, x, style, distanceApt, raceLen) + race.duelSpeedMod(x),
|
||||||
|
},
|
||||||
|
distanceApt !== race.AptitudeLevel.S
|
||||||
|
? {
|
||||||
|
label: `Aptitude ${race.AptitudeLevel[distanceApt]}`,
|
||||||
|
y: (x) => race.spurtSpeed(speedStat, x, style, distanceApt, raceLen),
|
||||||
|
}
|
||||||
: null,
|
: null,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const range: [number, number] = $derived([
|
const range: [number, number] = [20, 27.5];
|
||||||
spurtSpeed(200, gutsStat, RunningStyle.GreatEscape, AptitudeLevel.C, raceLen),
|
|
||||||
spurtSpeed(2000, gutsStat, RunningStyle.EndCloser, AptitudeLevel.S, raceLen),
|
|
||||||
]);
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<h1 class="text-4xl">Spurt Speed Calculator</h1>
|
<h1 class="text-4xl">Spurt Speed Calculator</h1>
|
||||||
@@ -71,16 +84,16 @@
|
|||||||
<div class="m-4">
|
<div class="m-4">
|
||||||
<label for="style">Style</label>
|
<label for="style">Style</label>
|
||||||
<select id="style" required bind:value={style} class="w-full">
|
<select id="style" required bind:value={style} class="w-full">
|
||||||
{#each stylesList as [name, style] (style)}
|
{#each race.RUNNING_STYLES as [name, style] (style)}
|
||||||
<option value={style}>{style === RunningStyle.GreatEscape ? 'Great Escape (Runaway)' : name}</option>
|
<option value={style}>{name}</option>
|
||||||
{/each}
|
{/each}
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div class="m-4">
|
<div class="m-4">
|
||||||
<label for="distanceApt">Distance Aptitude</label>
|
<label for="distanceApt">Distance Aptitude</label>
|
||||||
<select id="distanceApt" required bind:value={distanceApt} class="w-full">
|
<select id="distanceApt" required bind:value={distanceApt} class="w-full">
|
||||||
{#each aptsList.toReversed() as [name, val] (val)}
|
{#each race.APTITUDE_LEVELS as apt (apt)}
|
||||||
<option value={val}>{name}</option>
|
<option value={apt}>{race.AptitudeLevel[apt]}</option>
|
||||||
{/each}
|
{/each}
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
@@ -93,24 +106,18 @@
|
|||||||
<input type="checkbox" id="isCareer" role="switch" bind:checked={isCareer} class="min-h-6 min-w-6 align-middle" />
|
<input type="checkbox" id="isCareer" role="switch" bind:checked={isCareer} class="min-h-6 min-w-6 align-middle" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<span class="mt-8 block w-full text-center text-lg">
|
<div class="mx-auto mt-8 flex max-w-3xl flex-col md:flex-row">
|
||||||
Target spurt speed: {speed.toFixed(3)} m/s
|
<CalcInfo title="Target Spurt Speed" class="flex-1">{speed.toFixed(3)} m/s</CalcInfo>
|
||||||
</span>
|
<CalcInfo title="Dueling Bonus" class="flex-1">+{duelBonus.toFixed(3)} m/s</CalcInfo>
|
||||||
{#each [[AptitudeLevel.A, aProf] as const, [AptitudeLevel.S, sProf] as const] as [level, inv] (level)}
|
</div>
|
||||||
|
{#each [[race.AptitudeLevel.A, aProf] as const, [race.AptitudeLevel.S, sProf] as const] as [level, inv] (level)}
|
||||||
<div class="mx-auto max-w-3xl">
|
<div class="mx-auto max-w-3xl">
|
||||||
<span class="mt-8 block w-full">
|
<span class="mt-8 block w-full">
|
||||||
With {AptitudeLevel[level]} proficiency, the equivalent speed is
|
With {race.AptitudeLevel[level]} proficiency, the equivalent speed is
|
||||||
</span>
|
</span>
|
||||||
<div class="flex flex-col md:flex-row">
|
<div class="flex flex-col md:flex-row">
|
||||||
{#each stylesList as [styleName, s] (s)}
|
{#each race.RUNNING_STYLES as [styleName, s] (s)}
|
||||||
<div
|
<CalcInfo title={styleName} class={['flex-1', { 'border-2': s === style }]}>{inv[s]}</CalcInfo>
|
||||||
class={['m-2 flex-1 rounded-md border shadow-sm transition-shadow hover:shadow-md', s === style ? 'border-2' : null]}
|
|
||||||
>
|
|
||||||
<div class="h-full w-full flex-col text-center">
|
|
||||||
<span class="block text-lg">{styleName}</span>
|
|
||||||
<span class="block text-2xl">{inv[s]}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -119,23 +126,19 @@
|
|||||||
<span class="mt-8 block w-full">
|
<span class="mt-8 block w-full">
|
||||||
While a speed skill is active, the equivalent speed for a distance S
|
While a speed skill is active, the equivalent speed for a distance S
|
||||||
<select id="opponentStyle" required bind:value={opponentStyle}>
|
<select id="opponentStyle" required bind:value={opponentStyle}>
|
||||||
{#each stylesList as [name, style] (style)}
|
{#each race.RUNNING_STYLES as [name, style] (style)}
|
||||||
<option value={style}>{style === RunningStyle.GreatEscape ? 'Great Escape (Runaway)' : name}</option>
|
<option value={style}>{style === race.RunningStyle.Runaway ? 'Great Escape (Runaway)' : name}</option>
|
||||||
{/each}
|
{/each}
|
||||||
</select>
|
</select>
|
||||||
is
|
is
|
||||||
</span>
|
</span>
|
||||||
<div class="flex flex-col md:flex-row">
|
<div class="flex flex-col md:flex-row">
|
||||||
{#each skillProf as [v, inv] (v)}
|
{#each skillProf as [v, inv] (v)}
|
||||||
<div class="m-2 flex-1 rounded-md border shadow-sm transition-shadow hover:shadow-md">
|
<CalcInfo title={`+${v.toFixed(2)}`} class="flex-1">{inv}</CalcInfo>
|
||||||
<div class="h-full w-full flex-col text-center">
|
|
||||||
<span class="block text-lg">+{v.toFixed(2)}</span>
|
|
||||||
<span class="block text-2xl">{inv}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="mx-auto h-60 max-w-3xl place-content-center py-4 md:h-96">
|
<div class="mx-auto grid h-96 grid-cols-1 py-4 md:grid-cols-2">
|
||||||
<StatChart stat={Stat.Speed} {y} yLabel="Spurt Speed (m/s)" xRule={speedStat} {range} />
|
<StatChart stat={race.Stat.Speed} y={speedChart} yLabel="Spurt Speed (m/s)" xRule={speedStat} {range} />
|
||||||
|
<StatChart stat={race.Stat.Guts} y={gutsChart} yLabel="Spurt Speed (m/s)" xRule={gutsStat} {range} />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
+17
-14
@@ -1,6 +1,5 @@
|
|||||||
import tailwindcss from '@tailwindcss/vite';
|
import tailwindcss from '@tailwindcss/vite';
|
||||||
import { defineConfig } from 'vitest/config';
|
import { defineConfig } from 'vitest/config';
|
||||||
import { playwright } from '@vitest/browser-playwright';
|
|
||||||
import { sveltekit } from '@sveltejs/kit/vite';
|
import { sveltekit } from '@sveltejs/kit/vite';
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
@@ -9,19 +8,20 @@ export default defineConfig({
|
|||||||
test: {
|
test: {
|
||||||
expect: { requireAssertions: true },
|
expect: { requireAssertions: true },
|
||||||
projects: [
|
projects: [
|
||||||
{
|
// Playwright tests are disabled because I don't feel like setting it up in NixOS.
|
||||||
extends: './vite.config.ts',
|
// {
|
||||||
test: {
|
// extends: './vite.config.ts',
|
||||||
name: 'client',
|
// test: {
|
||||||
browser: {
|
// name: 'client',
|
||||||
enabled: true,
|
// browser: {
|
||||||
provider: playwright(),
|
// enabled: true,
|
||||||
instances: [{ browser: 'chromium', headless: true }],
|
// provider: playwright(),
|
||||||
},
|
// instances: [{ browser: 'chromium', headless: true }],
|
||||||
include: ['src/**/*.svelte.{test,spec}.{js,ts}'],
|
// },
|
||||||
exclude: ['src/lib/server/**'],
|
// include: ['src/**/*.svelte.{test,spec}.{js,ts}'],
|
||||||
},
|
// exclude: ['src/lib/server/**'],
|
||||||
},
|
// },
|
||||||
|
// },
|
||||||
|
|
||||||
{
|
{
|
||||||
extends: './vite.config.ts',
|
extends: './vite.config.ts',
|
||||||
@@ -39,6 +39,9 @@ export default defineConfig({
|
|||||||
'/api': {
|
'/api': {
|
||||||
target: 'http://localhost:13669',
|
target: 'http://localhost:13669',
|
||||||
},
|
},
|
||||||
|
'/img': {
|
||||||
|
target: 'http://localhost:13669',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user