138 lines
6.9 KiB
HTML
138 lines
6.9 KiB
HTML
|
|
<!DOCTYPE html>
|
|||
|
|
<html lang="en">
|
|||
|
|
<head>
|
|||
|
|
<meta charset="UTF-8">
|
|||
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|||
|
|
<title>Capacity Planner | stream.4ort.net</title>
|
|||
|
|
<style>
|
|||
|
|
:root { --bg:#050505; --fg:#e0e0e0; --accent:#7aa2f7; --dim:#404040; --border:#1a1a1a; }
|
|||
|
|
* { box-sizing:border-box; }
|
|||
|
|
body { background:var(--bg); color:var(--fg); font-family:system-ui,sans-serif; margin:0; padding:3rem; line-height:1.6; }
|
|||
|
|
.wrap { max-width:1100px; margin:0 auto; display:grid; grid-template-columns:1fr 400px; gap:3rem; }
|
|||
|
|
h1 { font-size:2.2rem; letter-spacing:-0.03em; margin:0 0 1rem 0; }
|
|||
|
|
h2 { font-size:0.75rem; opacity:0.5; font-family:monospace; margin:2rem 0 1rem 0; text-transform:uppercase; letter-spacing:0.12em; }
|
|||
|
|
a { color:var(--accent); text-decoration:none; border-bottom:1px solid var(--border); }
|
|||
|
|
.card { border:1px solid var(--border); padding:1.5rem; background:#080808; }
|
|||
|
|
.input-group { margin-bottom:1rem; }
|
|||
|
|
label { display:block; font-size:0.75rem; opacity:0.7; margin-bottom:0.3rem; font-family:monospace; }
|
|||
|
|
input[type="number"] { width:100%; background:#0a0a0a; border:1px solid var(--dim); color:var(--fg); padding:0.6rem; font-family:monospace; font-size:0.9rem; }
|
|||
|
|
input:focus { outline:none; border-color:var(--accent); }
|
|||
|
|
.output { background:#0a0a0a; border:1px solid var(--accent); padding:1rem; font-family:monospace; font-size:0.85rem; white-space:pre-wrap; }
|
|||
|
|
.metric { display:flex; justify-content:space-between; padding:0.4rem 0; border-bottom:1px dashed var(--dim); }
|
|||
|
|
.metric:last-child { border-bottom:none; }
|
|||
|
|
.metric-label { opacity:0.6; }
|
|||
|
|
.metric-val { font-weight:bold; color:var(--accent); }
|
|||
|
|
img { width:100%; height:auto; filter:contrast(1.1) brightness(0.9); }
|
|||
|
|
.note { font-size:0.75rem; opacity:0.5; margin-top:1rem; font-style:italic; }
|
|||
|
|
.formula { background:#0a0a0a; padding:1rem; font-family:monospace; font-size:0.8rem; border-left:3px solid var(--accent); margin:1.5rem 0; }
|
|||
|
|
</style>
|
|||
|
|
<script defer src="https://analytics.4ort.xyz/script.js" data-website-id="d3ed927c-888a-4a6c-ae5f-0b1c613ddf5b"></script>
|
|||
|
|
</head>
|
|||
|
|
<body>
|
|||
|
|
<div class="wrap">
|
|||
|
|
<main>
|
|||
|
|
<h1>Capacity Planner</h1>
|
|||
|
|
<p>FIFA 2026 requires 16 venues, 104 matches, 60 days of continuous operation. This planner computes the GPU-hours required to render all broadcast assets before kickoff.</p>
|
|||
|
|
|
|||
|
|
<div class="formula">
|
|||
|
|
CAPACITY = V × M × A × R ÷ S<br>
|
|||
|
|
where:<br>
|
|||
|
|
V = venues (16)<br>
|
|||
|
|
M = matches per venue (avg 6.5)<br>
|
|||
|
|
A = assets per match (3200 frames × 8 cameras = 25,600)<br>
|
|||
|
|
R = resolution factor (8K = 33.2M px vs 1080p baseline = 2.1M → ×15.8)<br>
|
|||
|
|
S = single-GPU render speed (frames/sec)
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<h2>Grounded Data</h2>
|
|||
|
|
<p>Source: <a href="https://4ort.xyz/entity/2026-fifa-world-cup" target="_blank">Wikidata Q5020214</a> — 2026 FIFA World Cup hosted across United States, Mexico, and Canada. Venue list includes Arrowhead Stadium, AT&T Stadium, BC Place, BMO Field, Estadio Akron, NRG Stadium, SoFi Stadium, Levi's Stadium, and 8 additional sites.</p>
|
|||
|
|
|
|||
|
|
<p>Pipeline precedent: <a href="render-farm-theory.html">Theory page</a> establishes throughput as T⁻¹. <a href="render-farm-calculator.html">Calculator</a> implements T = N × (F ÷ S) × 3600.</p>
|
|||
|
|
|
|||
|
|
<h2>Why This Matters</h2>
|
|||
|
|
<p>Broadcast deadlines are absolute. A frame late means a blackout. The planner below takes venue count, match distribution, asset complexity, and hardware spec to output:</p>
|
|||
|
|
<ul>
|
|||
|
|
<li>Total GPU-hours required</li>
|
|||
|
|
<li>Minimum cluster size for 60-day runway</li>
|
|||
|
|
<li>Critical path bottleneck</li>
|
|||
|
|
</ul>
|
|||
|
|
|
|||
|
|
<img src="https://images.pexels.com/photos/37730212/pexels-photo-37730212.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=650&w=940" alt="Data center server racks with active equipment">
|
|||
|
|
|
|||
|
|
<p class="note">Image: Pexels (CC0). Server density represents minimum cluster footprint.</p>
|
|||
|
|
</main>
|
|||
|
|
|
|||
|
|
<aside class="card">
|
|||
|
|
<h2>Inputs</h2>
|
|||
|
|
<div class="input-group">
|
|||
|
|
<label>Venues</label>
|
|||
|
|
<input type="number" id="venues" value="16" min="1">
|
|||
|
|
</div>
|
|||
|
|
<div class="input-group">
|
|||
|
|
<label>Matches per venue (avg)</label>
|
|||
|
|
<input type="number" id="matchesPerVenue" value="6.5" step="0.5">
|
|||
|
|
</div>
|
|||
|
|
<div class="input-group">
|
|||
|
|
<label>Assets per match (frames × cameras)</label>
|
|||
|
|
<input type="number" id="assetsPerMatch" value="25600">
|
|||
|
|
</div>
|
|||
|
|
<div class="input-group">
|
|||
|
|
<label>Resolution multiplier (8K vs 1080p)</label>
|
|||
|
|
<input type="number" id="resolutionMult" value="15.8" step="0.1">
|
|||
|
|
</div>
|
|||
|
|
<div class="input-group">
|
|||
|
|
<label>GPU render speed (frames/sec)</label>
|
|||
|
|
<input type="number" id="gpuSpeed" value="120" step="1">
|
|||
|
|
</div>
|
|||
|
|
<div class="input-group">
|
|||
|
|
<label>Available hours (60 days)</label>
|
|||
|
|
<input type="number" id="availableHours" value="1440">
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<button onclick="compute()" style="width:100%; padding:0.8rem; background:var(--accent); color:#000; border:none; font-weight:bold; cursor:pointer; margin-top:1rem;">COMPUTE</button>
|
|||
|
|
|
|||
|
|
<div class="output" id="results" style="margin-top:1.5rem;"></div>
|
|||
|
|
</aside>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<script>
|
|||
|
|
function compute() {
|
|||
|
|
const V = parseFloat(document.getElementById('venues').value) || 16;
|
|||
|
|
const M = parseFloat(document.getElementById('matchesPerVenue').value) || 6.5;
|
|||
|
|
const A = parseFloat(document.getElementById('assetsPerMatch').value) || 25600;
|
|||
|
|
const R = parseFloat(document.getElementById('resolutionMult').value) || 15.8;
|
|||
|
|
const S = parseFloat(document.getElementById('gpuSpeed').value) || 120;
|
|||
|
|
const H = parseFloat(document.getElementById('availableHours').value) || 1440;
|
|||
|
|
|
|||
|
|
const totalFrames = V * M * A * R;
|
|||
|
|
const gpuHoursRequired = totalFrames / (S * 3600);
|
|||
|
|
const gpusRequired = Math.ceil(gpuHoursRequired / H);
|
|||
|
|
const headroom = ((H * gpusRequired - gpuHoursRequired) / gpuHoursRequired * 100).toFixed(1);
|
|||
|
|
|
|||
|
|
const results = `CAPACITY PLAN — FIFA 2026
|
|||
|
|
━━━━━━━━━━━━━━━━━━━━━━
|
|||
|
|
Total Frames to Render: ${totalFrameString(totalFrames)}
|
|||
|
|
GPU-Hours Required: ${(gpuHoursRequired/1e6).toFixed(2)}M
|
|||
|
|
Minimum Cluster Size: ${gpusRequired} GPUs
|
|||
|
|
Headroom at 60 Days: ${headroom}%
|
|||
|
|
|
|||
|
|
CRITICAL PATH:
|
|||
|
|
• Asset generation: ${((V*M*A)/1e6).toFixed(2)}M base frames
|
|||
|
|
• Resolution upscale: ×${R}
|
|||
|
|
• Parallelization ceiling: ${gpusRequired} nodes`;
|
|||
|
|
|
|||
|
|
document.getElementById('results').textContent = results;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function totalFrameString(n) {
|
|||
|
|
if (n >= 1e9) return (n/1e9).toFixed(2) + 'B';
|
|||
|
|
if (n >= 1e6) return (n/1e6).toFixed(2) + 'M';
|
|||
|
|
if (n >= 1e3) return (n/1e3).toFixed(2) + 'K';
|
|||
|
|
return n.toString();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
compute();
|
|||
|
|
</script>
|
|||
|
|
</body>
|
|||
|
|
</html>
|