// TechBlog.jsx
// Stack: Next.js (React) + Tailwind CSS
// Font: JetBrains Mono — add to _document.js or layout.js:
//
const MONO = "'JetBrains Mono', monospace";
const rewardData = [
{ outcome: "SORT_CORRECT", reward: "+8.0", desc: "Fruit stored OR Waste crushed", pos: true, fatal: false },
{ outcome: "SORT_INCORRECT", reward: "-8.0", desc: "Waste stored OR Fruit crushed", pos: false, fatal: false },
{ outcome: "EPISODE_SUCCESS", reward: "+15.0", desc: "All 10 items processed successfully", pos: true, fatal: false },
{ outcome: "SYSTEM_FAILURE", reward: "-20.0", desc: "Storage overflow OR out of energy", pos: false, fatal: true },
];
const stateVars = [
{ key: "storage_count", range: "0 – 6", desc: "Items currently in the buffer" },
{ key: "energy_left", range: "0 – 4", desc: "Remaining power units for the batch" },
{ key: "item_type", range: "Fruit | Waste", desc: "Identity of the item on the belt" },
];
const actions = [
{ id: "0", name: "STORE", cost: "1 Energy", desc: "Move item into the storage buffer" },
{ id: "1", name: "CRUSH", cost: "1 Energy", desc: "Destroy the item — cannot be undone" },
];
const hyperparams = [
{ sym: "α", name: "Learning Rate", val: "0.10", desc: "How fast old Q-values are overwritten" },
{ sym: "γ", name: "Discount Factor", val: "0.95", desc: "Weight given to future rewards" },
{ sym: "ε", name: "Exploration", val: "0.30", desc: "Initial probability of a random action" },
];
/* ── Shared primitives ───────────────────────────────── */
function Tag({ children, variant = "default" }) {
const s = {
default: { bg: "#0f0f0f", border: "#1f1f1f", color: "#525252" },
blue: { bg: "#0c1528", border: "#1e3a8a", color: "#60a5fa" },
green: { bg: "#051407", border: "#14532d", color: "#4ade80" },
orange: { bg: "#130800", border: "#431407", color: "#fb923c" },
red: { bg: "#120404", border: "#450a0a", color: "#f87171" },
}[variant];
return (
{children}
);
}
function InlineCode({ children }) {
return (
{children}
);
}
function SectionLabel({ num, title }) {
return (
{String(num).padStart(2, "0")}
{title}
);
SectionLabel}
function CodeBlock({ filename, code }) {
return (
);
}
function FigureBlock({ num, title, src, alt, caption }) {
return (
FIG {num}
{title}
(e.currentTarget.style.borderColor = "#1e3a8a")}
onMouseLeave={(e) => (e.currentTarget.style.borderColor = "#181818")}
>
{caption}
);
}
/* ── Main component ──────────────────────────────────── */
export default function TechBlog() {
const bellmanCode = [
`def update_q (state, action, reward, next_state):`,
` # Bellman equation — core update rule `,
` best_next = max (Q[next_state].values())`,
` td_error = reward + γ * best_next - Q[state][action]`,
` Q[state][action] += α * td_error`,
``,
`def choose_action (state, epsilon):`,
` # ε-greedy: explore early, exploit when confident `,
` if random () < epsilon:`,
` return random_action () # explore `,
` return argmax (Q[state]) # exploit `,
``,
`# Hyperparameters `,
`α = 0.10 # learning rate `,
`γ = 0.95 # discount factor `,
`ε = 0.30 # initial exploration rate `,
].join("\n");
return (
{/* Dot grid overlay */}
{/* Top edge glow */}
{/* ── TERMINAL TOPBAR ── */}
~/sortiq/docs/rl-inference.md
{/* ── BREADCRUMB ── */}
research
/
reinforcement-learning
/
decision-kernels
{/* ── HERO ── */}
v1.0.0 — stable build
Sortiq
RL Inference
Tabular Q-Learning Under Constraints
A pedagogical environment for studying RL fundamentals. Unlike standard
classification, Sortiq forces an agent to learn the relationship between{" "}
state management and{" "}
long-term survival — physical
constraints make naive policies fail immediately.
{/* ── QUICK STATS ── */}
{[
{ val: "15K", label: "Training Episodes", accent: "#3b82f6" },
{ val: "10", label: "Items per Batch", accent: "#a855f7" },
{ val: "+8.0", label: "Max Reward / Sort", accent: "#22c55e" },
].map(({ val, label, accent }) => (
))}
{/* ── 01. PURPOSE ── */}
Sortiq was built as a pedagogical RL environment. Unlike standard classification —
where the only goal is accuracy — Sortiq introduces{" "}
physical constraints , forcing the agent to
manage a storage buffer and energy budget simultaneously. Naive "always store" policies
crash immediately. The agent must learn{" "}
survivability before it can learn accuracy .
{/* ── 02. ENVIRONMENT ── */}
{/* State space */}
State Space
{["variable", "range", "description"].map((h) => (
{h}
))}
{stateVars.map((s, i) => (
(e.currentTarget.style.background = "#080808")}
onMouseLeave={(e) => (e.currentTarget.style.background = "transparent")}
>
{s.key}
{s.range}
{s.desc}
))}
{/* Action space */}
Action Space
{actions.map((a) => (
(e.currentTarget.style.borderColor = "#1e3a8a")}
onMouseLeave={(e) => (e.currentTarget.style.borderColor = "#1a1a1a")}
>
Action {a.id}
{a.name}
{a.desc}
cost: {a.cost}
))}
{/* Constraints */}
⚠ Critical Constraints
Storage Bottleneck — Buffer is exactly 6 units . A 7th item triggers an immediate system crash with a −20.0 penalty.
Energy Budget — Agent starts with 4 units to process 10 items . It cannot simply store everything — every action must be deliberate.
{/* ── 03. REWARD ── */}
System death is penalized far more heavily than classification errors — teaching the agent that{" "}
survival is the prerequisite for performance .
{["outcome", "reward", "description"].map((h) => (
{h}
))}
{rewardData.map((r, i) => (
(e.currentTarget.style.background = "#080808")}
onMouseLeave={(e) => (e.currentTarget.style.background = "transparent")}
>
{r.outcome}
{r.reward}
{r.desc}
))}
// state = (storage_count, energy_left, item_type)
{/* ── 04. AGENT ARCHITECTURE ── */}
The agent uses Tabular Q-Learning — a 3D Q-Table where each cell holds the expected future reward of taking a specific action in a specific state. No neural networks, no approximation. Pure classical RL — every Q-value is looked up directly.
{/* Hyperparams */}
{hyperparams.map((h) => (
{h.name}
{h.val}
{h.desc}
{h.sym}
))}
{/* ── 05. DATASET ── */}
The "dataset" is a stochastic stream of items generated by the environment at runtime — not a static file. This ensures the agent encounters a diverse range of resource states such as high_storage / low_energy vs. low_storage / high_energy , testing the robustness of its policy across edge cases rather than memorising a fixed sequence.
{/* ── 06. PERFORMANCE ── */}
{/* Score comparison chips */}
{[
{ label: "Random Baseline", val: "~−12.0", color: "#f87171", sub: "avg reward — frequent crashes" },
{ label: "Trained Agent", val: "~+58.0", color: "#4ade80", sub: "avg reward — near-perfect policy" },
].map((s) => (
{s.label}
{s.val}
{s.sub}
))}
{/* ── FOOTER ── */}
);
}