diff --git a/.eslintignore b/.eslintignore
new file mode 100644
index 00000000..1f6a7e97
--- /dev/null
+++ b/.eslintignore
@@ -0,0 +1,3 @@
+# Auto-generated by Next.js — should not be linted or hand-edited
+next-env.d.ts
+.next/
diff --git a/.prettierrc.json b/.prettierrc.json
index bbe2eeb5..9eccf21e 100644
--- a/.prettierrc.json
+++ b/.prettierrc.json
@@ -2,5 +2,6 @@
"semi": false,
"singleQuote": true,
"tabWidth": 2,
+ "endOfLine": "auto",
"plugins": ["prettier-plugin-tailwindcss"]
}
diff --git a/next-env.d.ts b/next-env.d.ts
index 52e831b4..254b73c1 100644
--- a/next-env.d.ts
+++ b/next-env.d.ts
@@ -1,5 +1,6 @@
///
///
+///
// NOTE: This file should not be edited
// see https://nextjs.org/docs/pages/api-reference/config/typescript for more information.
diff --git a/public/assets/papers/maia3.jpg b/public/assets/papers/maia3.jpg
new file mode 100644
index 00000000..3115be0a
Binary files /dev/null and b/public/assets/papers/maia3.jpg differ
diff --git a/public/assets/team/daniel.png b/public/assets/team/daniel.png
new file mode 100644
index 00000000..ff3e0d08
Binary files /dev/null and b/public/assets/team/daniel.png differ
diff --git a/src/components/Common/Footer.tsx b/src/components/Common/Footer.tsx
index 05c6537f..80ffa025 100644
--- a/src/components/Common/Footer.tsx
+++ b/src/components/Common/Footer.tsx
@@ -18,13 +18,17 @@ export const Footer: React.FC = () => {
title: 'Research',
links: [
{
- href: 'https://arxiv.org/abs/2006.01855',
- text: "Maia Paper (KDD '20)",
+ href: 'https://arxiv.org/abs/2605.19091',
+ text: "Maia-3 Paper (ICLR '26)",
},
{
href: 'https://www.cs.toronto.edu/~ashton/pubs/maia2-neurips2024.pdf',
text: "Maia-2 Paper (NeurIPS '24)",
},
+ {
+ href: 'https://arxiv.org/abs/2006.01855',
+ text: "Maia Paper (KDD '20)",
+ },
{
href: 'https://csslab.cs.toronto.edu/research/',
text: 'CSSLab Research',
@@ -32,16 +36,20 @@ export const Footer: React.FC = () => {
],
},
{
- title: 'Opensource',
+ title: 'Open-Source',
links: [
{
- href: 'https://github.com/CSSLab/maia-chess',
- text: 'Maia Model',
+ href: 'https://github.com/CSSLab/maia3',
+ text: 'Maia-3 Model',
},
{
href: 'https://github.com/CSSLab/maia2',
text: 'Maia-2 Model',
},
+ {
+ href: 'https://github.com/CSSLab/maia-chess',
+ text: 'Maia Model',
+ },
{
href: 'https://github.com/csslab/maia-platform-frontend',
text: 'Maia Web Platform',
@@ -153,7 +161,7 @@ export const Footer: React.FC = () => {
- © 2025 Maia Chess. All rights reserved.
+ © 2026 Maia Chess. All rights reserved.
diff --git a/src/components/Home/AboutMaia.tsx b/src/components/Home/AboutMaia.tsx
index b1deaae2..8299e77b 100644
--- a/src/components/Home/AboutMaia.tsx
+++ b/src/components/Home/AboutMaia.tsx
@@ -42,6 +42,14 @@ const teamMembers = [
role: 'Model Developer',
github: 'lilv98',
},
+ {
+ image: '/assets/team/daniel.png',
+ name: 'Daniel Monroe',
+ website: 'https://daniel-monroe.github.io/',
+ institution: 'University of Toronto',
+ role: 'Model Developer',
+ github: 'daniel-monroe',
+ },
{
image: '/assets/team/kevin.jpg',
name: 'Kevin Thomas',
@@ -75,19 +83,44 @@ const teamMembers = [
},
]
-const researchPapers = {
+interface Paper {
+ title: string
+ link: string
+ description: string
+ image?: string
+ shortName?: string
+}
+
+const researchPapers: {
+ maia1: Paper
+ maia2: Paper
+ maia3: Paper
+ others: Paper[]
+} = {
maia1: {
title:
'Aligning Superhuman AI with Human Behavior: Chess as a Model System',
link: 'https://www.cs.toronto.edu/~ashton/pubs/maia-kdd2020.pdf',
description:
'This paper introduces Maia, a chess engine trained to imitate real human moves at different rating levels. Instead of always picking the best move, Maia predicts what a human player of a given skill would actually play. This makes it ideal for training, game analysis, and even coaching, as it helps players learn from realistic decisions rather than computer perfection. It was the first AI to prioritize human-likeness over engine strength, making it a powerful tool for improvement.',
+ image: 'maia1',
+ shortName: 'Maia 1',
},
maia2: {
title: 'Maia‑2: A Unified Model for Human‑AI Alignment in Chess',
link: 'https://www.cs.toronto.edu/~ashton/pubs/maia2-neurips2024.pdf',
description:
"Maia‑2 is the evolution of Maia into a single model that can simulate any skill level in chess. Instead of using separate models for different ratings, it understands and adapts to your level in real time. Whether you're a beginner or a master, Maia‑2 predicts the moves players like you would actually make. It's built to feel human, teach naturally, and support personalized analysis without needing to toggle between bots.",
+ image: 'maia2',
+ shortName: 'Maia 2',
+ },
+ maia3: {
+ title: 'Chessformer: A Unified Architecture for Chess Modeling',
+ link: 'https://arxiv.org/abs/2605.19091',
+ description:
+ 'Introduces Chessformer, a transformer-based architecture that unifies chess modeling to capture how human players make decisions across a wide range of skill levels.',
+ image: 'maia3',
+ shortName: 'Maia 3',
},
others: [
{
@@ -125,7 +158,7 @@ const PaperCard = ({
featured = false,
className = '',
}: {
- paper: typeof researchPapers.maia1
+ paper: Paper
featured?: boolean
className?: string
}) => (
@@ -146,7 +179,7 @@ const PaperCard = ({
{featured && (
)
@@ -172,7 +205,7 @@ const PaperCard = ({
rel="noreferrer"
className="inline-flex w-full items-center justify-center bg-human-4/80 px-5 py-3 font-medium text-primary transition duration-200 hover:bg-human-4"
>
- Read {paper.title.includes('Maia‑2') ? 'Maia 2' : 'Maia 1'} Paper
+ Read {paper.shortName} Paper
)}
@@ -248,46 +281,17 @@ export const AboutMaia = () => {
- {/* Layout for screens < 1280px */}
-
- {/* Featured papers in a row */}
-
- {/* Other papers in a row */}
-
- {researchPapers.others.map((paper, index) => (
-
- ))}
-
+ {/* Featured papers */}
+
-
- {/* Layout for screens >= 1280px (original layout) */}
-
-
-
-
- {researchPapers.others.map((paper, index) => (
-
- ))}
-
+ {/* Other papers in two columns */}
+
+ {researchPapers.others.map((paper, index) => (
+
+ ))}
diff --git a/src/constants/common.ts b/src/constants/common.ts
index b3259a20..aa32f5ba 100644
--- a/src/constants/common.ts
+++ b/src/constants/common.ts
@@ -1,6 +1,9 @@
+// The Maia model is only run at multiples of 200 Elo. The intermediate levels
+// (200n + 100, e.g. 700, 900, ...) are not selectable; they only appear in the
+// "Moves by Rating" graph, where they are linearly interpolated.
export const MAIA_MODELS = Array.from(
- { length: 21 },
- (_, i) => `maia_kdd_${600 + i * 100}`,
+ { length: 11 },
+ (_, i) => `maia_kdd_${600 + i * 200}`,
)
export const MAIA_RATINGS = MAIA_MODELS.map((m) =>
diff --git a/src/hooks/useAnalysisController/useMoveRecommendations.ts b/src/hooks/useAnalysisController/useMoveRecommendations.ts
index e38ae1c3..cda1d81f 100644
--- a/src/hooks/useAnalysisController/useMoveRecommendations.ts
+++ b/src/hooks/useAnalysisController/useMoveRecommendations.ts
@@ -1,6 +1,6 @@
import { useMemo } from 'react'
import { Chess } from 'chess.ts'
-import { MAIA_MODELS } from 'src/constants/common'
+import { MAIA_MODELS, MAIA_RATINGS } from 'src/constants/common'
import { GameNode, MaiaEvaluation, StockfishEvaluation } from 'src/types'
import { sortStockfishMoves } from './utils'
@@ -105,15 +105,27 @@ export const useMoveRecommendations = (
}
}
+ // Maia is only evaluated at multiples of 200 Elo (MAIA_MODELS). The graph is
+ // drawn at a finer 100-Elo resolution: evaluated levels use their real policy
+ // and the intermediate levels (200n + 100) are linearly interpolated from
+ // their two neighbours.
+ const probabilityAt = (rating: number, move: string): number =>
+ (maia[`maia_kdd_${rating}`]?.policy?.[move] || 0) * 100
+
+ const minRating = MAIA_RATINGS[0]
+ const maxRating = MAIA_RATINGS[MAIA_RATINGS.length - 1]
+
const data = []
- for (const rating of MAIA_MODELS) {
- const entry: { [key: string]: number } = {
- rating: parseInt(rating.replace('maia_kdd_', '')),
- }
+ for (let rating = minRating; rating <= maxRating; rating += 100) {
+ const entry: { [key: string]: number } = { rating }
+ const isEvaluated = rating % 200 === 0
for (const move of candidates) {
- const probability = (maia[rating]?.policy?.[move[0]] || 0) * 100
- entry[move[1]] = probability
+ entry[move[1]] = isEvaluated
+ ? probabilityAt(rating, move[0])
+ : (probabilityAt(rating - 100, move[0]) +
+ probabilityAt(rating + 100, move[0])) /
+ 2
}
data.push(entry)
diff --git a/src/pages/candidates.tsx b/src/pages/candidates.tsx
index d480972d..ca78a636 100644
--- a/src/pages/candidates.tsx
+++ b/src/pages/candidates.tsx
@@ -189,8 +189,8 @@ const PositionPill: React.FC<{
href={playHref}
className={`inline-flex items-center gap-2 rounded-full px-4 py-2 text-sm font-medium text-primary shadow-[0_8px_24px_rgba(0,0,0,0.2)] transition ${
completed
- ? 'bg-emerald-500/30 hover:bg-emerald-500/34 border border-emerald-100/70 hover:border-emerald-50/80'
- : 'bg-rose-500/30 hover:bg-rose-500/34 border border-rose-100/65 hover:border-rose-50/75'
+ ? 'hover:bg-emerald-500/34 border border-emerald-100/70 bg-emerald-500/30 hover:border-emerald-50/80'
+ : 'hover:bg-rose-500/34 border border-rose-100/65 bg-rose-500/30 hover:border-rose-50/75'
}`}
>