Compare commits
4 Commits
d431d3382a
...
dc78d51def
| Author | SHA1 | Date | |
|---|---|---|---|
| dc78d51def | |||
| 5f0ad1b0e0 | |||
| 6db0ca5230 | |||
| 049617c22e |
+11
-3
@@ -11,13 +11,21 @@ func (c Character) String() string {
|
||||
return c.Name
|
||||
}
|
||||
|
||||
// AffinityRelation holds precomputed affinity values for a pair or trio.
|
||||
type AffinityRelation struct {
|
||||
IDA int `json:"chara_a"`
|
||||
IDB int `json:"chara_b"`
|
||||
IDC int `json:"chara_c,omitzero"`
|
||||
IDA CharacterID `json:"chara_a"`
|
||||
IDB CharacterID `json:"chara_b"`
|
||||
IDC CharacterID `json:"chara_c,omitzero"`
|
||||
Affinity int `json:"affinity"`
|
||||
}
|
||||
|
||||
// AffinityDetail holds a single affinity entry.
|
||||
type AffinityDetail struct {
|
||||
CharaID CharacterID `json:"chara_id"`
|
||||
Relation uint16 `json:"relation"`
|
||||
Affinity uint16 `json:"affinity"`
|
||||
}
|
||||
|
||||
// Conversation describes a lobby conversation.
|
||||
type Conversation struct {
|
||||
// CharacterID is the ID of the character who has the conversation as
|
||||
|
||||
@@ -129,6 +129,7 @@ func main() {
|
||||
mux.Handle("GET /", httpmiddle.Compress(5)(http.FileServerFS(os.DirFS(public))))
|
||||
}
|
||||
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/character", httpmiddle.Compress(5)(dataroute(ctx, db, mdb.Characters)))
|
||||
mux.Handle("GET /api/global/conversation", httpmiddle.Compress(5)(dataroute(ctx, db, mdb.Conversations)))
|
||||
mux.Handle("GET /api/global/race", httpmiddle.Compress(5)(dataroute(ctx, db, mdb.Races)))
|
||||
|
||||
+16
-3
@@ -15,6 +15,8 @@ var (
|
||||
characterSQL string
|
||||
//go:embed sql/affinity.sql
|
||||
affinitySQL string
|
||||
//go:embed sql/affinity-detail.sql
|
||||
affinityDetailSQL string
|
||||
//go:embed sql/uma.sql
|
||||
umaSQL string
|
||||
//go:embed sql/conversation.sql
|
||||
@@ -35,14 +37,25 @@ func Characters(ctx context.Context, db *sqlitex.Pool) ([]horse.Character, error
|
||||
func AffinitySummary(ctx context.Context, db *sqlitex.Pool) ([]horse.AffinityRelation, error) {
|
||||
return load(ctx, db, nil, affinitySQL, func(s *sqlite.Stmt) horse.AffinityRelation {
|
||||
return horse.AffinityRelation{
|
||||
IDA: s.ColumnInt(0),
|
||||
IDB: s.ColumnInt(1),
|
||||
IDC: s.ColumnInt(2),
|
||||
IDA: horse.CharacterID(s.ColumnInt(0)),
|
||||
IDB: horse.CharacterID(s.ColumnInt(1)),
|
||||
IDC: horse.CharacterID(s.ColumnInt(2)),
|
||||
Affinity: s.ColumnInt(3),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// AffinityDetail gets all individual affinity group entries.
|
||||
func AffinityDetail(ctx context.Context, db *sqlitex.Pool) ([]horse.AffinityDetail, error) {
|
||||
return load(ctx, db, nil, affinityDetailSQL, func(s *sqlite.Stmt) horse.AffinityDetail {
|
||||
return horse.AffinityDetail{
|
||||
CharaID: horse.CharacterID(s.ColumnInt(0)),
|
||||
Relation: uint16(s.ColumnInt(1)),
|
||||
Affinity: uint16(s.ColumnInt(2)),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Umas retrieves all trainable Uma variants.
|
||||
func Umas(ctx context.Context, db *sqlitex.Pool) ([]horse.Uma, error) {
|
||||
return load(ctx, db, make([]horse.Uma, 0, 128), umaSQL, func(s *sqlite.Stmt) horse.Uma {
|
||||
|
||||
@@ -44,6 +44,107 @@ func TestAffinitySummary(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestAffinityDetail(t *testing.T) {
|
||||
db := testdb(t.Context(), "file:TestAffinityDetail?mode=memory&cache=shared", characterSQL)
|
||||
got, err := mdb.AffinityDetail(t.Context(), db)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
want := []horse.AffinityDetail{
|
||||
{CharaID: 1001, Relation: 103, Affinity: 2},
|
||||
{CharaID: 1001, Relation: 202, Affinity: 2},
|
||||
{CharaID: 1001, Relation: 301, Affinity: 2},
|
||||
{CharaID: 1001, Relation: 401, Affinity: 2},
|
||||
{CharaID: 1001, Relation: 502, Affinity: 2},
|
||||
{CharaID: 1001, Relation: 510, Affinity: 2},
|
||||
{CharaID: 1001, Relation: 2001, Affinity: 1},
|
||||
{CharaID: 1001, Relation: 2102, Affinity: 1},
|
||||
{CharaID: 1001, Relation: 2301, Affinity: 1},
|
||||
{CharaID: 1001, Relation: 2406, Affinity: 1},
|
||||
{CharaID: 1001, Relation: 2506, Affinity: 1},
|
||||
{CharaID: 1001, Relation: 2510, Affinity: 1},
|
||||
{CharaID: 1001, Relation: 2516, Affinity: 1},
|
||||
{CharaID: 1001, Relation: 2519, Affinity: 1},
|
||||
{CharaID: 1001, Relation: 2601, Affinity: 1},
|
||||
{CharaID: 1001, Relation: 2603, Affinity: 1},
|
||||
{CharaID: 1001, Relation: 2811, Affinity: 1},
|
||||
{CharaID: 1001, Relation: 2903, Affinity: 7},
|
||||
{CharaID: 1001, Relation: 3003, Affinity: 7},
|
||||
{CharaID: 1001, Relation: 3004, Affinity: 7},
|
||||
{CharaID: 1001, Relation: 3101, Affinity: 7},
|
||||
{CharaID: 1001, Relation: 3205, Affinity: 1},
|
||||
{CharaID: 1001, Relation: 7003, Affinity: 1},
|
||||
{CharaID: 1001, Relation: 7004, Affinity: 1},
|
||||
{CharaID: 1001, Relation: 7005, Affinity: 1},
|
||||
{CharaID: 1001, Relation: 7006, Affinity: 1},
|
||||
{CharaID: 1001, Relation: 7007, Affinity: 1},
|
||||
{CharaID: 1001, Relation: 8001, Affinity: 1},
|
||||
{CharaID: 1001, Relation: 8002, Affinity: 1},
|
||||
{CharaID: 1001, Relation: 8003, Affinity: 1},
|
||||
{CharaID: 1001, Relation: 8004, Affinity: 1},
|
||||
{CharaID: 1001, Relation: 8005, Affinity: 1},
|
||||
{CharaID: 1001, Relation: 8006, Affinity: 1},
|
||||
{CharaID: 1001, Relation: 8007, Affinity: 1},
|
||||
{CharaID: 1001, Relation: 8008, Affinity: 1},
|
||||
{CharaID: 1001, Relation: 8009, Affinity: 1},
|
||||
{CharaID: 1001, Relation: 8010, Affinity: 1},
|
||||
{CharaID: 1001, Relation: 8011, Affinity: 1},
|
||||
{CharaID: 1001, Relation: 8012, Affinity: 1},
|
||||
{CharaID: 1001, Relation: 8013, Affinity: 1},
|
||||
{CharaID: 1001, Relation: 8014, Affinity: 1},
|
||||
{CharaID: 1001, Relation: 8015, Affinity: 1},
|
||||
{CharaID: 1001, Relation: 8016, Affinity: 1},
|
||||
{CharaID: 1001, Relation: 8443, Affinity: 1},
|
||||
{CharaID: 1001, Relation: 8479, Affinity: 1},
|
||||
{CharaID: 1001, Relation: 8511, Affinity: 1},
|
||||
{CharaID: 1001, Relation: 8536, Affinity: 1},
|
||||
{CharaID: 1001, Relation: 8640, Affinity: 1},
|
||||
{CharaID: 1002, Relation: 104, Affinity: 2},
|
||||
{CharaID: 1002, Relation: 202, Affinity: 2},
|
||||
{CharaID: 1002, Relation: 301, Affinity: 2},
|
||||
{CharaID: 1002, Relation: 402, Affinity: 2},
|
||||
{CharaID: 1002, Relation: 2001, Affinity: 1},
|
||||
{CharaID: 1002, Relation: 2406, Affinity: 1},
|
||||
{CharaID: 1002, Relation: 2512, Affinity: 1},
|
||||
{CharaID: 1002, Relation: 2601, Affinity: 1},
|
||||
{CharaID: 1002, Relation: 2603, Affinity: 1},
|
||||
{CharaID: 1002, Relation: 2810, Affinity: 1},
|
||||
{CharaID: 1002, Relation: 2901, Affinity: 7},
|
||||
{CharaID: 1002, Relation: 3003, Affinity: 7},
|
||||
{CharaID: 1002, Relation: 3101, Affinity: 7},
|
||||
{CharaID: 1002, Relation: 3205, Affinity: 1},
|
||||
{CharaID: 1002, Relation: 7000, Affinity: 1},
|
||||
{CharaID: 1002, Relation: 7001, Affinity: 1},
|
||||
{CharaID: 1002, Relation: 7002, Affinity: 1},
|
||||
{CharaID: 1002, Relation: 7013, Affinity: 1},
|
||||
{CharaID: 1002, Relation: 7015, Affinity: 1},
|
||||
{CharaID: 1002, Relation: 8017, Affinity: 1},
|
||||
{CharaID: 1002, Relation: 8018, Affinity: 1},
|
||||
{CharaID: 1002, Relation: 8019, Affinity: 1},
|
||||
{CharaID: 1002, Relation: 8020, Affinity: 1},
|
||||
{CharaID: 1002, Relation: 8021, Affinity: 1},
|
||||
{CharaID: 1002, Relation: 8022, Affinity: 1},
|
||||
{CharaID: 1002, Relation: 8023, Affinity: 1},
|
||||
{CharaID: 1002, Relation: 8024, Affinity: 1},
|
||||
{CharaID: 1002, Relation: 8025, Affinity: 1},
|
||||
{CharaID: 1002, Relation: 8026, Affinity: 1},
|
||||
{CharaID: 1002, Relation: 8027, Affinity: 1},
|
||||
{CharaID: 1002, Relation: 8028, Affinity: 1},
|
||||
{CharaID: 1002, Relation: 8029, Affinity: 1},
|
||||
{CharaID: 1002, Relation: 8030, Affinity: 1},
|
||||
{CharaID: 1002, Relation: 8031, Affinity: 1},
|
||||
{CharaID: 1002, Relation: 8032, Affinity: 1},
|
||||
{CharaID: 1002, Relation: 8444, Affinity: 1},
|
||||
{CharaID: 1002, Relation: 8480, Affinity: 1},
|
||||
{CharaID: 1002, Relation: 8512, Affinity: 1},
|
||||
{CharaID: 1002, Relation: 8537, Affinity: 1},
|
||||
{CharaID: 1002, Relation: 8641, Affinity: 1},
|
||||
}
|
||||
if diff := cmp.Diff(want, got); diff != "" {
|
||||
t.Errorf("wrong affinity details (+got/-want):\n%s", diff)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUmas(t *testing.T) {
|
||||
db := testdb(t.Context(), "file:TestUmas?mode=memory&cache=shared", characterSQL)
|
||||
got, err := mdb.Umas(t.Context(), db)
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
SELECT
|
||||
m.chara_id,
|
||||
m.relation_type,
|
||||
r.relation_point
|
||||
FROM succession_relation_member m
|
||||
JOIN succession_relation r ON m.relation_type = r.relation_type
|
||||
ORDER BY m.chara_id, m.relation_type
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
<select {id} class={className} bind:value {required}>
|
||||
{#if !required}
|
||||
<option value="0" class={optionClass}></option>
|
||||
<option value={0} class={optionClass}></option>
|
||||
{/if}
|
||||
{#each characters ?? [] as c (c.chara_id)}
|
||||
<option value={c.chara_id} class={optionClass}>{c.name}</option>
|
||||
|
||||
@@ -32,3 +32,352 @@ export function lookup(aff: Affinity[], chara_a: number, chara_b: number, chara_
|
||||
const r = aff.find((v) => v.chara_a === a && v.chara_b === b && (v.chara_c ?? Infinity) === c);
|
||||
return r?.affinity ?? 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Individual affinity entry.
|
||||
*/
|
||||
export interface AffinityDetail {
|
||||
/** Character to which the entry applies. */
|
||||
chara_id: number;
|
||||
/** Succession relation that the character has. */
|
||||
relation: number;
|
||||
/** Value of the succession relation. */
|
||||
affinity: number;
|
||||
}
|
||||
|
||||
export async function affinityDetail(): Promise<AffinityDetail[]> {
|
||||
const resp = await fetch('/api/global/affinity-detail');
|
||||
return resp.json();
|
||||
}
|
||||
|
||||
export interface AffinityRelationDescription {
|
||||
name: string;
|
||||
note?: string;
|
||||
}
|
||||
|
||||
export const AFFINITY_RELATION_DESCRIPTIONS = new Map<number, AffinityRelationDescription>([
|
||||
[2001, { name: 'Shared Parent', note: 'Sunday Silence' }],
|
||||
[2002, { name: 'Shared Parent', note: 'Tony Bin' }],
|
||||
[2003, { name: 'Shared Parent', note: 'Sakura Yutaka O' }],
|
||||
[2004, { name: 'Shared Parent', note: 'Mejiro Ryan' }],
|
||||
[2005, { name: 'Shared Parent' }],
|
||||
[2006, { name: 'Shared Parent' }],
|
||||
[2007, { name: 'Shared Parent', note: 'Symboli Rudolf' }],
|
||||
[2008, { name: 'Shared Parent', note: 'Tosho Boy' }],
|
||||
[2009, { name: 'Shared Parent', note: "Brian's Time" }],
|
||||
[2010, { name: 'Shared Parent', note: 'Gold Allure' }],
|
||||
[2011, { name: 'Shared Parent', note: 'Soccer Boy' }],
|
||||
[2101, { name: 'Shared Grandparent', note: 'Sunday Silence (grandsire)' }],
|
||||
[2102, { name: 'Shared Grandparent', note: 'Maruzensky (damsire)' }],
|
||||
[2103, { name: 'Shared Grandparent', note: 'Roberto (grandsire)' }],
|
||||
[2104, { name: 'Shared Grandparent', note: 'Blushing Groom (damsire)' }],
|
||||
[2105, { name: 'Shared Grandparent', note: 'Tesco Boy (damsire)' }],
|
||||
[2106, { name: 'Shared Grandparent', note: 'Northern Taste (damsire)' }],
|
||||
[2107, { name: 'Shared Grandparent', note: 'Royal Ski (damsire)' }],
|
||||
[2108, { name: 'Shared Grandparent', note: 'Never Beat (damsire)' }],
|
||||
[2109, { name: 'Shared Grandparent', note: 'Affirmed (damsire)' }],
|
||||
[2110, { name: 'Shared Grandparent', note: 'Sancy (damsire)' }],
|
||||
[2111, { name: 'Shared Grandparent', note: 'Last Tycoon (grandsire)' }],
|
||||
[2201, { name: 'Parent/Child' }],
|
||||
[2202, { name: 'Parent/Child' }],
|
||||
[2203, { name: 'Grandparent/Grandchild' }],
|
||||
[2204, { name: 'Parent/Child' }],
|
||||
[2205, { name: 'Grandparent/Grandchild' }],
|
||||
[2206, { name: 'Parent/Child' }],
|
||||
[2208, { name: 'Parent/Child' }],
|
||||
[2209, { name: 'Parent/Child' }],
|
||||
[2210, { name: 'Parent/Child' }],
|
||||
[2211, { name: 'Grandparent/Grandchild' }],
|
||||
[2212, { name: 'Parent/Child' }],
|
||||
[2213, { name: 'Parent/Child' }],
|
||||
[2214, { name: 'Grandparent/Grandchild' }],
|
||||
[2215, { name: 'Grandparent/Grandchild' }],
|
||||
[2216, { name: 'Grandparent/Grandchild' }],
|
||||
[2217, { name: 'Grandparent/Grandchild' }],
|
||||
[2218, { name: 'Parent/Child' }],
|
||||
[2301, { name: 'Grandparent/Grandchild' }],
|
||||
[2302, { name: 'Grandparent/Grandchild' }],
|
||||
[2303, { name: 'Grandparent/Grandchild' }],
|
||||
[2401, { name: 'Racing Generation', note: 'Pre-1980' }],
|
||||
[2402, { name: 'Racing Generation', note: '1983-86' }],
|
||||
[2403, { name: 'Racing Generation', note: '1987-90' }],
|
||||
[2404, { name: 'Racing Generation', note: '1990-93' }],
|
||||
[2405, { name: 'Racing Generation', note: '1995-97' }],
|
||||
[2406, { name: 'Racing Generation', note: '1997-99' }],
|
||||
[2407, { name: 'Racing Generation', note: '1998-2004' }],
|
||||
[2408, { name: 'Racing Generation', note: '2001-04' }],
|
||||
[2409, { name: 'Racing Generation', note: '2006-10' }],
|
||||
[2410, { name: 'Racing Generation' }],
|
||||
[2411, { name: 'Racing Generation' }],
|
||||
[2412, { name: 'Racing Generation' }],
|
||||
[2413, { name: 'Racing Generation' }],
|
||||
[2501, { name: 'G1 Winner', note: 'February Stakes' }],
|
||||
[2502, { name: 'G1 Winner', note: 'Takamatsunomiya Kinen' }],
|
||||
[2503, { name: 'G1 Winner', note: 'Osaka Hai' }],
|
||||
[2504, { name: 'G1 Winner', note: 'Oka Sho' }],
|
||||
[2505, { name: 'G1 Winner', note: 'Satsuki Sho' }],
|
||||
[2506, { name: 'G1 Winner', note: 'Tenno Sho (Spring)' }],
|
||||
[2507, { name: 'G1 Winner', note: 'NHK Mile Cup' }],
|
||||
[2508, { name: 'G1 Winner', note: 'Victoria Mile' }],
|
||||
[2509, { name: 'G1 Winner', note: 'Japanese Oaks' }],
|
||||
[2510, { name: 'G1 Winner', note: 'Tokyo Yushun (Japanese Derby)' }],
|
||||
[2511, { name: 'G1 Winner', note: 'Yasuda Kinen' }],
|
||||
[2512, { name: 'G1 Winner', note: 'Takarazuka Kinen' }],
|
||||
[2513, { name: 'G1 Winner', note: 'Sprinters Stakes' }],
|
||||
[2514, { name: 'G1 Winner', note: 'Shuka Sho' }],
|
||||
[2515, { name: 'G1 Winner', note: 'Kikuka Sho' }],
|
||||
[2516, { name: 'G1 Winner', note: 'Tenno Sho (Autumn)' }],
|
||||
[2517, { name: 'G1 Winner', note: 'Queen Elizabeth II Cup' }],
|
||||
[2518, { name: 'G1 Winner', note: 'Mile Championship' }],
|
||||
[2519, { name: 'G1 Winner', note: 'Japan Cup' }],
|
||||
[2520, { name: 'G1 Winner', note: 'Champions Cup' }],
|
||||
[2521, { name: 'G1 Winner', note: 'Hanshin Juvenile Fillies' }],
|
||||
[2522, { name: 'G1 Winner', note: 'Asahi Hai Futurity Stakes' }],
|
||||
[2523, { name: 'G1 Winner', note: 'Arima Kinen' }],
|
||||
[2524, { name: 'G1 Winner', note: 'Hopeful Stakes' }],
|
||||
[2525, { name: 'G1 Winner', note: 'Teio Sho' }],
|
||||
[2526, { name: 'G1 Winner', note: 'Japan Dirt Derby' }],
|
||||
[2527, { name: 'G1 Winner', note: 'JBC Ladies’ Classic' }],
|
||||
[2528, { name: 'G1 Winner', note: 'JBC Sprint' }],
|
||||
[2529, { name: 'G1 Winner', note: 'JBC Classic' }],
|
||||
[2530, { name: 'G1 Winner', note: 'Tokyo Daishoten' }],
|
||||
[2531, { name: 'G1 Winner', note: 'Kawasaki Kinen' }],
|
||||
[2532, { name: 'G1 Winner', note: 'Mile Championship Nambu Hai' }],
|
||||
[2533, { name: 'G1 Winner', note: 'Kashiwa Kinen' }],
|
||||
[2534, { name: 'G1 Winner', note: 'Zen-Nippon Junior Yushun' }],
|
||||
[2601, { name: 'Stallion' }],
|
||||
[2602, { name: 'Mare' }],
|
||||
[2702, { name: '7 G1 + Triple Crown winner' }],
|
||||
[2801, { name: 'Classic Year', note: '1986' }],
|
||||
[2802, { name: 'Classic Year', note: '1987' }],
|
||||
[2803, { name: 'Classic Year', note: '1990' }],
|
||||
[2804, { name: 'Classic Year', note: '1991' }],
|
||||
[2805, { name: 'Classic Year', note: '1992' }],
|
||||
[2806, { name: 'Classic Year', note: '1993' }],
|
||||
[2807, { name: 'Classic Year', note: '1994' }],
|
||||
[2808, { name: 'Classic Year', note: '1995' }],
|
||||
[2809, { name: 'Classic Year', note: '1996' }],
|
||||
[2810, { name: 'Classic Year', note: '1997' }],
|
||||
[2811, { name: 'Classic Year', note: '1998' }],
|
||||
[2812, { name: 'Classic Year', note: '1999' }],
|
||||
[2813, { name: 'Classic Year', note: '2000' }],
|
||||
[2814, { name: 'Classic Year', note: '2001' }],
|
||||
[2815, { name: 'Classic Year', note: '2007' }],
|
||||
[2816, { name: 'Classic Year', note: '2009' }],
|
||||
[2817, { name: 'Classic Year', note: '2010' }],
|
||||
[2818, { name: 'Classic Year' }],
|
||||
[2819, { name: 'Classic Year' }],
|
||||
[2820, { name: 'Classic Year' }],
|
||||
[2821, { name: 'Classic Year' }],
|
||||
[2822, { name: 'Classic Year', note: '2002' }],
|
||||
[2823, { name: 'Classic Year' }],
|
||||
[2824, { name: 'Classic Year' }],
|
||||
[2825, { name: 'Classic Year' }],
|
||||
[2826, { name: 'Classic Year' }],
|
||||
[2827, { name: 'Classic Year' }],
|
||||
[2828, { name: 'Classic Year' }],
|
||||
[2829, { name: 'Classic Year' }],
|
||||
[2830, { name: 'Classic Year' }],
|
||||
[2831, { name: 'Classic Year' }],
|
||||
[2832, { name: 'Classic Year' }],
|
||||
[2833, { name: 'Classic Year' }],
|
||||
[3201, { name: 'Birth Month', note: 'January' }],
|
||||
[3202, { name: 'Birth Month', note: 'February' }],
|
||||
[3203, { name: 'Birth Month', note: 'March' }],
|
||||
[3204, { name: 'Birth Month', note: 'April' }],
|
||||
[3205, { name: 'Birth Month', note: 'May' }],
|
||||
[3206, { name: 'Birth Month', note: 'June' }],
|
||||
[3301, { name: 'No G1 Wins' }],
|
||||
[3501, { name: 'Classic Triple Crown Runner' }],
|
||||
[3502, { name: 'Triple Tiara Runner' }],
|
||||
[3503, { name: 'Spring Triple Crown Runner' }],
|
||||
[3504, { name: 'Autumn Triple Crown Runner' }],
|
||||
[101, { name: 'Tracen Class' }],
|
||||
[102, { name: 'Tracen Class' }],
|
||||
[103, { name: 'Tracen Class' }],
|
||||
[104, { name: 'Tracen Class' }],
|
||||
[105, { name: 'Tracen Class' }],
|
||||
[106, { name: 'Tracen Class' }],
|
||||
[107, { name: 'Tracen Class' }],
|
||||
[201, { name: 'Residence', note: 'Miho Dorm' }],
|
||||
[202, { name: 'Residence', note: 'Ritto Dorm' }],
|
||||
[203, { name: 'Residence', note: 'Lives Alone' }],
|
||||
[204, { name: 'Residence' }],
|
||||
[205, { name: 'Residence', note: 'Not In Dorm' }],
|
||||
[301, { name: 'Roommate' }],
|
||||
[302, { name: 'Roommate' }],
|
||||
[303, { name: 'Roommate' }],
|
||||
[304, { name: 'Roommate' }],
|
||||
[305, { name: 'Roommate' }],
|
||||
[306, { name: 'Roommate' }],
|
||||
[307, { name: 'Roommate' }],
|
||||
[308, { name: 'Roommate' }],
|
||||
[309, { name: 'Roommate' }],
|
||||
[310, { name: 'Roommate' }],
|
||||
[311, { name: 'Roommate' }],
|
||||
[312, { name: 'Roommate' }],
|
||||
[313, { name: 'Roommate' }],
|
||||
[314, { name: 'Roommate' }],
|
||||
[315, { name: 'Roommate' }],
|
||||
[316, { name: 'Roommate' }],
|
||||
[317, { name: 'Roommate' }],
|
||||
[318, { name: 'Roommate' }],
|
||||
[319, { name: 'Roommate' }],
|
||||
[320, { name: 'Roommate' }],
|
||||
[321, { name: 'Roommate' }],
|
||||
[322, { name: 'Roommate' }],
|
||||
[323, { name: 'Roommate' }],
|
||||
[324, { name: 'Roommate' }],
|
||||
[325, { name: 'Roommate' }],
|
||||
[326, { name: 'Roommate' }],
|
||||
[327, { name: 'Roommate' }],
|
||||
[328, { name: 'Roommate' }],
|
||||
[329, { name: 'Roommate' }],
|
||||
[330, { name: 'Roommate' }],
|
||||
[331, { name: 'Roommate' }],
|
||||
[332, { name: 'Roommate' }],
|
||||
[333, { name: 'Roommate' }],
|
||||
[334, { name: 'Roommate' }],
|
||||
[335, { name: 'Roommate' }],
|
||||
[336, { name: 'Roommate' }],
|
||||
[337, { name: 'Roommate' }],
|
||||
[338, { name: 'Roommate' }],
|
||||
[339, { name: 'Roommate' }],
|
||||
[340, { name: 'Roommate' }],
|
||||
[341, { name: 'Roommate' }],
|
||||
[342, { name: 'Roommate' }],
|
||||
[343, { name: 'Roommate' }],
|
||||
[344, { name: 'Roommate' }],
|
||||
[345, { name: 'Roommate' }],
|
||||
[346, { name: 'Roommate' }],
|
||||
[347, { name: 'Roommate' }],
|
||||
[348, { name: 'Roommate' }],
|
||||
[349, { name: 'Roommate' }],
|
||||
[350, { name: 'Roommate' }],
|
||||
[351, { name: 'Roommate' }],
|
||||
[352, { name: 'Roommate' }],
|
||||
[353, { name: 'Roommate' }],
|
||||
[354, { name: 'Roommate' }],
|
||||
[355, { name: 'Roommate' }],
|
||||
[356, { name: 'Roommate' }],
|
||||
[357, { name: 'Roommate' }],
|
||||
[358, { name: 'Roommate' }],
|
||||
[359, { name: 'Roommate' }],
|
||||
[360, { name: 'Roommate' }],
|
||||
[361, { name: 'Roommate' }],
|
||||
[362, { name: 'Roommate' }],
|
||||
[363, { name: 'Roommate' }],
|
||||
[364, { name: 'Roommate' }],
|
||||
[365, { name: 'Roommate' }],
|
||||
[366, { name: 'Roommate' }],
|
||||
[367, { name: 'Roommate' }],
|
||||
[368, { name: 'Roommate' }],
|
||||
[369, { name: 'Roommate' }],
|
||||
[370, { name: 'Roommate' }],
|
||||
[371, { name: 'Roommate' }],
|
||||
[372, { name: 'Roommate' }],
|
||||
[373, { name: 'Roommate' }],
|
||||
[374, { name: 'Roommate' }],
|
||||
[375, { name: 'Roommate' }],
|
||||
[376, { name: 'Roommate' }],
|
||||
[377, { name: 'Roommate' }],
|
||||
[378, { name: 'Roommate' }],
|
||||
[379, { name: 'Roommate' }],
|
||||
[380, { name: 'Roommate' }],
|
||||
[381, { name: 'Roommate' }],
|
||||
[382, { name: 'Roommate' }],
|
||||
[383, { name: 'Roommate' }],
|
||||
[384, { name: 'Roommate' }],
|
||||
[385, { name: 'Roommate' }],
|
||||
[386, { name: 'Roommate' }],
|
||||
[387, { name: 'Roommate' }],
|
||||
[388, { name: 'Roommate' }],
|
||||
[401, { name: 'Clique', note: 'Golden Generation' }],
|
||||
[402, { name: 'Clique' }],
|
||||
[403, { name: 'Clique' }],
|
||||
[404, { name: 'Clique' }],
|
||||
[405, { name: 'Clique' }],
|
||||
[406, { name: 'Clique', note: 'Old People' }],
|
||||
[407, { name: 'Clique' }],
|
||||
[408, { name: 'Clique' }],
|
||||
[409, { name: 'Clique' }],
|
||||
[410, { name: 'Clique' }],
|
||||
[411, { name: 'Clique' }],
|
||||
[412, { name: 'Clique' }],
|
||||
[413, { name: 'Clique' }],
|
||||
[414, { name: 'Clique' }],
|
||||
[415, { name: 'Clique' }],
|
||||
[416, { name: 'Clique' }],
|
||||
[417, { name: 'Clique' }],
|
||||
[418, { name: 'Clique' }],
|
||||
[419, { name: 'Clique' }],
|
||||
[420, { name: 'Clique' }],
|
||||
[421, { name: 'Clique' }],
|
||||
[422, { name: 'Clique' }],
|
||||
[423, { name: 'Clique' }],
|
||||
[424, { name: 'Clique' }],
|
||||
[501, { name: 'Story Group', note: 'Student Council' }],
|
||||
[502, { name: 'Story Group', note: 'Golden Generation' }],
|
||||
[503, { name: 'Story Group', note: 'Mejiro Family' }],
|
||||
[504, { name: 'Story Group', note: 'Rich People' }],
|
||||
[505, { name: 'Story Group', note: 'Dorm Leaders' }],
|
||||
[506, { name: 'Story Group', note: 'Heisei Big Three' }],
|
||||
[507, { name: 'Story Group', note: 'BNW' }],
|
||||
[511, { name: 'Story Group', note: '"Sakura"' }],
|
||||
[512, { name: 'Story Group' }],
|
||||
[513, { name: 'Story Group', note: 'Team Canopus' }],
|
||||
[514, { name: 'Story Group', note: 'Runaway Bakas' }],
|
||||
[515, { name: 'Story Group', note: 'Kita and Dia' }],
|
||||
[516, { name: 'Story Group', note: 'McQueen and Dia' }],
|
||||
[517, { name: 'Story Group', note: 'Teio and Kitasan' }],
|
||||
[518, { name: 'Story Group', note: 'Notable Front Runners' }],
|
||||
[519, { name: 'Story Group' }],
|
||||
[520, { name: 'Story Group', note: 'Notable Dirt Runners' }],
|
||||
[521, { name: 'Story Group' }],
|
||||
[522, { name: 'Story Group', note: 'Satono Family' }],
|
||||
[523, { name: 'Story Group', note: 'Team Sirius' }],
|
||||
[524, { name: 'Story Group', note: 'Team Rigil' }],
|
||||
[525, { name: 'Story Group', note: 'Team Spica' }],
|
||||
[526, { name: 'Story Group', note: 'Notable Runaways' }],
|
||||
[3505, { name: 'Ran on Dirt' }],
|
||||
[3506, { name: 'Overseas' }],
|
||||
[3508, { name: 'Ran a Sprint' }],
|
||||
[2901, { name: 'Strategy', note: 'Front Runner' }],
|
||||
[2902, { name: 'Strategy', note: 'Pace Chaser' }],
|
||||
[2903, { name: 'Strategy', note: 'Late Surger' }],
|
||||
[2904, { name: 'Strategy', note: 'End Closer' }],
|
||||
[3001, { name: 'Distance', note: 'Sprint' }],
|
||||
[3002, { name: 'Distance', note: 'Mile' }],
|
||||
[3003, { name: 'Distance', note: 'Medium' }],
|
||||
[3004, { name: 'Distance', note: 'Long' }],
|
||||
[3101, { name: 'Surface', note: 'Turf' }],
|
||||
[3102, { name: 'Surface', note: 'Dirt' }],
|
||||
[508, { name: 'Haru Urara', note: 'Haru Urara' }],
|
||||
[509, { name: 'Haru Urara', note: 'Haru Urara' }],
|
||||
[510, { name: 'Haru Urara', note: 'Haru Urara' }],
|
||||
[527, { name: 'Haru Urara', note: 'Haru Urara' }],
|
||||
[528, { name: 'Haru Urara', note: 'Haru Urara' }],
|
||||
[529, { name: 'Haru Urara', note: 'Haru Urara' }],
|
||||
[530, { name: 'Haru Urara', note: 'Haru Urara' }],
|
||||
[531, { name: 'Haru Urara', note: 'Haru Urara' }],
|
||||
[532, { name: 'Haru Urara', note: 'Haru Urara' }],
|
||||
[533, { name: 'Haru Urara', note: 'Haru Urara' }],
|
||||
[534, { name: 'Haru Urara', note: 'Haru Urara' }],
|
||||
[535, { name: 'Haru Urara', note: 'Haru Urara' }],
|
||||
[536, { name: 'Haru Urara', note: 'Haru Urara' }],
|
||||
[537, { name: 'Haru Urara', note: 'Haru Urara' }],
|
||||
[538, { name: 'Haru Urara', note: 'Haru Urara' }],
|
||||
[539, { name: 'Haru Urara', note: 'Haru Urara' }],
|
||||
[540, { name: 'Haru Urara', note: 'Haru Urara' }],
|
||||
[541, { name: 'Haru Urara', note: 'Haru Urara' }],
|
||||
[542, { name: 'Haru Urara', note: 'Haru Urara' }],
|
||||
[543, { name: 'Haru Urara', note: 'Haru Urara' }],
|
||||
[544, { name: 'Haru Urara', note: 'Haru Urara' }],
|
||||
[545, { name: 'Haru Urara', note: 'Haru Urara' }],
|
||||
[546, { name: 'Haru Urara', note: 'Haru Urara' }],
|
||||
[547, { name: 'Haru Urara', note: 'Haru Urara' }],
|
||||
[548, { name: 'Haru Urara', note: 'Haru Urara' }],
|
||||
[549, { name: 'Haru Urara', note: 'Haru Urara' }],
|
||||
[550, { name: 'Haru Urara', note: 'Haru Urara' }],
|
||||
[551, { name: 'Haru Urara', note: 'Haru Urara' }],
|
||||
]);
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import './layout.css';
|
||||
import favicon from '$lib/assets/favicon.png';
|
||||
import { resolve } from '$app/paths';
|
||||
import { affinity } from '$lib/data/affinity';
|
||||
|
||||
let { children } = $props();
|
||||
</script>
|
||||
@@ -20,6 +21,7 @@
|
||||
<a href={resolve('/')} class="mx-8 my-1 block font-semibold md:hidden">Zenno Rob Roy</a>
|
||||
<a href={resolve('/spurt')} class="mx-8 my-1 inline-block">Spurt Speed</a>
|
||||
<a href={resolve('/mspeed')} class="mx-8 my-1 inline-block">Mechanical Speed</a>
|
||||
<a href={resolve('/affinity')} class="mx-8 my-1 inline-block">Affinity Details</a>
|
||||
<a href={resolve('/convo')} class="mx-8 my-1 inline-block">Lobby Conversations</a>
|
||||
<a href={resolve('/doc')} class="mx-8 my-1 inline-block">Documents</a>
|
||||
</span>
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
<h2 class="mt-8 mb-4 text-4xl">Tools</h2>
|
||||
<ul class="mb-4 list-disc pl-4">
|
||||
<li>
|
||||
<a href={resolve('/spurt')}>Spurt Speed</a> — Calculate a horse's target speed in the last spurt and compare to other distance aptitudes
|
||||
<a href={resolve('/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>
|
||||
@@ -16,13 +16,16 @@
|
||||
of individual skills.
|
||||
</li>
|
||||
<li>
|
||||
<a href={resolve('/convo')}>Lobby Conversations</a> — Check participants in lobby conversations and get recommendations on unlocking
|
||||
<a href={resolve('/affinity')}>Affinity Details</a> — Details of why characters have the base compatibility numbers they have.
|
||||
</li>
|
||||
<li>
|
||||
<a href={resolve('/convo')}>Lobby Conversations</a> — Check participants in lobby conversations and get recommendations on unlocking
|
||||
them quickly.
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://discord.com/oauth2/authorize?client_id=1461931240264568994" target="_blank" rel="noopener noreferrer"
|
||||
>Discord Bot</a
|
||||
> — Skill search by name or unique owner within Discord. Install to a server or user.
|
||||
> — Skill search by name or unique owner within Discord. Install to a server or user.
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
|
||||
@@ -0,0 +1,144 @@
|
||||
<script lang="ts">
|
||||
import CharaPick from '$lib/CharaPick.svelte';
|
||||
import { AFFINITY_RELATION_DESCRIPTIONS, affinityDetail, type AffinityDetail } from '$lib/data/affinity';
|
||||
import { character, charaNames, type Character } from '$lib/data/character';
|
||||
import { onMount } from 'svelte';
|
||||
import { SvelteSet } from 'svelte/reactivity';
|
||||
|
||||
let characters: Character[] = $state([]);
|
||||
let affs: AffinityDetail[] = $state([]);
|
||||
onMount(async () => {
|
||||
const [charas, affinities] = await Promise.all([character(), affinityDetail()]);
|
||||
affs = affinities;
|
||||
characters = charas.filter((c) => affs.some((d) => d.chara_id === c.chara_id));
|
||||
});
|
||||
const names = $derived(charaNames(characters));
|
||||
|
||||
let charA: number = $state(1001);
|
||||
let charB: number = $state(0);
|
||||
let charC: number = $state(0);
|
||||
const nameA = $derived(names.get(charA)?.en ?? `Character ${charA}`);
|
||||
const nameB = $derived(names.get(charB)?.en ?? `Character ${charB}`);
|
||||
const nameC = $derived(names.get(charC)?.en ?? `Character ${charC}`);
|
||||
|
||||
function groupsFor(affs: AffinityDetail[], chara: number): AffinityDetail[] {
|
||||
return chara !== 0 ? affs.filter((d) => d.chara_id === chara) : [];
|
||||
}
|
||||
const groupsA = $derived(groupsFor(affs, charA));
|
||||
const groupsB = $derived(groupsFor(affs, charB));
|
||||
const groupsC = $derived(groupsFor(affs, charC));
|
||||
const allGroups = $derived.by(() => {
|
||||
const seen = new SvelteSet<number>();
|
||||
const r = [];
|
||||
for (const g of [groupsA, groupsB, groupsC]) {
|
||||
for (const d of g) {
|
||||
if (seen.has(d.relation)) {
|
||||
continue;
|
||||
}
|
||||
seen.add(d.relation);
|
||||
r.push(d);
|
||||
}
|
||||
}
|
||||
return r;
|
||||
});
|
||||
const selCount = $derived(1 + (charB === 0 ? 0 : 1) + (charC === 0 ? 0 : 1));
|
||||
const duplicate = $derived(charA === charB || charA === charC || (charB !== 0 && charB === charC));
|
||||
const infos = $derived(
|
||||
allGroups
|
||||
.map((d) => ({
|
||||
...d,
|
||||
description: AFFINITY_RELATION_DESCRIPTIONS.get(d.relation),
|
||||
a: groupsA.some(({ relation }) => relation === d.relation),
|
||||
b: groupsB.some(({ relation }) => relation === d.relation),
|
||||
c: groupsC.some(({ relation }) => relation === d.relation),
|
||||
}))
|
||||
.map((d) => ({
|
||||
...d,
|
||||
counted: (d.a ? 1 : 0) + (d.b ? 1 : 0) + (d.c ? 1 : 0),
|
||||
}))
|
||||
.toSorted((a, b) => (a.counted !== b.counted ? b.counted - a.counted : a.relation - b.relation)),
|
||||
);
|
||||
const total = $derived(infos.filter((d) => d.counted === selCount).reduce((t, d) => t + d.affinity, 0));
|
||||
</script>
|
||||
|
||||
<h1 class="text-4xl">Affinity Details</h1>
|
||||
<div class="mx-auto mt-8 flex flex-col rounded-md text-center shadow-md ring md:max-w-2xl md:flex-row">
|
||||
<div class="m-4 flex-1 md:mt-3">
|
||||
<label for="charA" class="hidden md:inline">Character 1</label>
|
||||
<CharaPick id="charA" {characters} class="w-full" bind:value={charA} required />
|
||||
</div>
|
||||
<div class="m-4 flex-1 md:mt-3">
|
||||
<label for="charB" class="hidden md:inline">Character 2</label>
|
||||
<CharaPick id="charB" {characters} class="w-full" bind:value={charB} />
|
||||
</div>
|
||||
<div class="m-4 flex-1 md:mt-3">
|
||||
<label for="charC" class="hidden md:inline">Character 3</label>
|
||||
<CharaPick id="charC" {characters} class="w-full" bind:value={charC} />
|
||||
</div>
|
||||
</div>
|
||||
<svelte:boundary>
|
||||
{#if duplicate}
|
||||
<span class="mt-8 block w-full text-center text-lg">Duplicate characters always have zero affinity.</span>
|
||||
{:else if selCount > 1}
|
||||
<span class="mt-8 block w-full text-center text-lg">Total {selCount === 2 ? 'pair' : 'trio'} affinity: {total}</span>
|
||||
{/if}
|
||||
<table class="mx-auto mt-8 table-fixed">
|
||||
<thead>
|
||||
<tr class="py-2">
|
||||
<th class="w-32 px-2" scope="col">Group ID</th>
|
||||
<th class="w-96 px-2" scope="col">Description</th>
|
||||
{#if selCount > 1}
|
||||
<th class="w-56 px-2" scope="col">{nameA}</th>
|
||||
{#if charB !== 0}
|
||||
<th class="w-56 px-2" scope="col">{nameB}</th>
|
||||
{/if}
|
||||
{#if charC !== 0}
|
||||
<th class="w-56 px-2" scope="col">{nameC}</th>
|
||||
{/if}
|
||||
{/if}
|
||||
<th class="w-32 px-2" scope="col">Affinity</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{#each infos as { relation, affinity, description, a, b, c, counted } (relation)}
|
||||
<tr class="even:bg-mist-300 dark:even:bg-mist-900">
|
||||
<td class="px-4 text-center">{relation}</td>
|
||||
{#if description != null}
|
||||
<td class="px-4">{[description.name, description.note].filter((s) => s != null).join(' – ')}</td>
|
||||
{:else}
|
||||
<td class="px-4"></td>
|
||||
{/if}
|
||||
{#if selCount > 1}
|
||||
<td class="px-4 text-center">{a ? '✓' : ''}</td>
|
||||
{#if charB !== 0}
|
||||
<td class="px-4 text-center">{b ? '✓' : ''}</td>
|
||||
{/if}
|
||||
{#if charC !== 0}
|
||||
<td class="px-4 text-center">{c ? '✓' : ''}</td>
|
||||
{/if}
|
||||
{/if}
|
||||
<td class="px-4 text-center">{!duplicate && selCount > 1 && counted === selCount ? '+' : ''}{affinity}</td>
|
||||
</tr>
|
||||
{/each}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
{#snippet pending()}
|
||||
<div>Loading data...</div>
|
||||
{/snippet}
|
||||
</svelte:boundary>
|
||||
<div class="mx-auto mt-12 w-full max-w-4xl border-t pt-4 md:mt-8">
|
||||
<p class="text-center">
|
||||
Descriptions are inferred from the horses in the respective groups. They are not included in the game data.
|
||||
</p>
|
||||
<p class="text-center">
|
||||
Some characters are not in affinity groups that it seems like they should be in, e.g. parent/child pairs and G1 winners for
|
||||
various races. Other characters are in affinity groups that it seems like they should not be in, e.g. Mihono Bourbon in the
|
||||
sprint group. Inclusion in a group is automatically generated from the game data and hence is known to correspond to game
|
||||
calculations, even where they don't make sense.
|
||||
</p>
|
||||
<p class="text-center">
|
||||
Thanks to princess_fox, hell259, Werseter, thegenosys, Lofi (Class: Voyager), and Ordalca on the GameTora Discord server, and
|
||||
Damhain on the CirnoTV server, for figuring out most of the groups.
|
||||
</p>
|
||||
</div>
|
||||
@@ -1342,6 +1342,10 @@
|
||||
how good this no-overtake thing really is. <span class="inline-block">1200/662/1094/902/932 S/A/S</span> with lane combo.
|
||||
</li>
|
||||
</ol>
|
||||
<p>
|
||||
Win rates after 40: Falco 47.5%, Bourbon 27.5%, Suzuka 5%. While this is my best round 1 by a substantial margin, I'm pretty
|
||||
sure I'm going to get torn up in round 2; my stats aren't good enough to beat correctly built end closers.
|
||||
</p>
|
||||
|
||||
<Sec h={3} id="cm13">CM13 – Taurus Cup (Tokyo Derby)</Sec>
|
||||
<p>
|
||||
|
||||
Reference in New Issue
Block a user