← Gorillaz results

Gorillaz

Tiel Coder 35B A3B GGUF MTP · full

Initial view of Gorillaz

Reviews & generation details

Reviews

No human review yet. Unreviewed does not mean unsuccessful.

Generation context

Imported historical artifact. Its exact original instructions and lineage are not known.

Generation prompt · exact text unavailable

See the current prompt for reference. It may differ from the instructions used to generate this artifact.

Inspect original source 25,390 bytes · SHA-256 2bf0c9e9c8a9
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Gorillaz — Artillery Duel</title>
<style>
  :root { --p1:#ff4455; --p2:#44aaff; --ink:#e9f0f0; }
  * { box-sizing: border-box; }
  html,body { margin:0; height:100%; background:#0b0e13; overflow:hidden; font-family: "Courier New", monospace; }
  #wrap { position:fixed; inset:0; display:flex; align-items:center; justify-content:center; }
  canvas { image-rendering: pixelated; image-rendering: crisp-edges; background:#0b0e13; box-shadow:0 0 0 3px #1e2733, 0 0 40px rgba(0,0,0,.7); max-width:100%; max-height:100%; }
  #hud { position:fixed; top:0; left:0; right:0; display:flex; gap:18px; align-items:center; padding:8px 16px; color:var(--ink); font-weight:bold; text-shadow:0 2px 0 #000; pointer-events:none; }
  .badge { background:#12181f; border:2px solid #2c3a4d; border-radius:6px; padding:5px 12px; letter-spacing:1px; }
  #turn { margin-left:auto; }
  #wind .arr { display:inline-block; min-width:34px; text-align:center; color:#7fe3c2; }
  #score { margin-left:14px; }
  #aim { position:fixed; bottom:10px; left:50%; transform:translateX(-50%); display:flex; gap:22px; color:var(--ink); background:#12181f; border:2px solid #2c3a4d; border-radius:8px; padding:8px 18px; font-weight:bold; text-shadow:0 2px 0 #000; pointer-events:none; }
  #aim span { color:#ffe14d; }
  #help { position:fixed; bottom:10px; left:14px; color:#8aa0b3; font-size:12px; text-shadow:0 1px 0 #000; pointer-events:none; }
  #overlay { position:fixed; inset:0; display:none; align-items:center; justify-content:center; background:rgba(6,9,13,.72); color:var(--ink); z-index:20; }
  #overlay .panel { background:#10161d; border:3px solid #2c3a4d; border-radius:12px; padding:22px 26px; max-width:92%; max-height:92%; overflow:auto; box-shadow:0 0 40px rgba(0,0,0,.7); }
  #overlay h1 { margin:0 0 4px; font-size:26px; letter-spacing:3px; }
  #overlay h2 { margin:18px 0 8px; font-size:15px; color:#7fe3c2; letter-spacing:1px; border-bottom:1px solid #2c3a4d; padding-bottom:4px; }
  table { border-collapse:collapse; width:100%; color:var(--ink); }
  td { padding:4px 6px; border-bottom:1px solid #1c2631; }
  td.num { width:70px; text-align:center; }
  td.val { text-align:center; font-weight:bold; }
  td.val.live { color:#7fe3c2; }
  button { font-family:inherit; font-weight:bold; cursor:pointer; background:#1c2836; color:var(--ink); border:2px solid #33455a; border-radius:6px; padding:6px 12px; }
  button:hover { background:#26384b; }
  .rowbtns { display:flex; gap:8px; align-items:center; }
  .ctrl { display:flex; align-items:center; gap:6px; }
  .legend { font-size:12px; color:#8aa0b3; margin-top:10px; line-height:1.5; }
  .livebadge { display:inline-block; background:#0e3a2e; color:#7fe3c2; border:1px solid #2c6b55; border-radius:4px; padding:1px 8px; font-size:11px; margin-left:8px; }
  #winbox { text-align:center; }
  #winbox .big { font-size:40px; letter-spacing:4px; margin:6px 0; }
  .p1c { color:var(--p1); } .p2c { color:var(--p2); }
  kbd { background:#22303f; border:1px solid #3a4c60; border-bottom-width:2px; border-radius:4px; padding:1px 6px; font-size:12px; }
</style>
</head>
<body>
<div id="wrap"><canvas id="c"></canvas></div>
<div id="hud">
  <div class="badge" id="wind">WIND <span class="arr" id="windArr">→</span> <span id="windVal">0.0</span></div>
  <div class="badge" id="score">P1 <span class="p1c">0</span> : <span class="p2c">0</span> P2</div>
  <div class="badge" id="turn">PLAYER 1 TURN</div>
</div>
<div id="aim"><span>ANG <b id="angVal">45</b>°</span><span>PWR <b id="pwrVal">50</b></span></div>
<div id="help"><kbd>←</kbd>/<kbd>→</kbd> angle · <kbd>↑</kbd>/<kbd>↓</kbd> power · <kbd>Space</kbd> fire · <kbd>T</kbd> settings · <kbd>Enter</kbd> start/rematch</div>
<div id="overlay"><div class="panel" id="panel"></div></div>
<script>
"use strict";
(function(){
const canvas = document.getElementById('c');
const ctx = canvas.getContext('2d');
const W = 960, H = 600, GROUND = 560;
canvas.width = W; canvas.height = H;

function fit(){
  const s = Math.min(window.innerWidth/W, window.innerHeight/H);
  canvas.style.width = (W*s)+'px';
  canvas.style.height = (H*s)+'px';
}
window.addEventListener('resize', fit); fit();

/* ---------- Live-tunable configuration ---------- */
const CONFIG = {
  blastRadius: 56,
  gravity: 0.7,
  windMin: 0.0,
  windMax: 1.6,
  windShifts: true,
  buildingCount: 10,
  buildingHeightMin: 110,
  buildingHeightMax: 340,
  powerMin: 6,
  powerMax: 22,
  damageMode: "radius",   // "radius" (must be inside) | "falloff" (partial ring damage)
  splashFalloff: 0.55     // falloff: damage factor at edge when damageMode=="falloff"
};
let tuningActive = false;
const DEFAULTS = Object.assign({}, CONFIG);

/* ---------- RNG (seeded, reproducible) ---------- */
let seed = (Math.random()*1e9)|0;
function rnd(){ seed = (seed*1103515245 + 12345) & 0x7fffffff; return seed/0x7fffffff; }
function ri(min,max){ return Math.floor(rnd()*(max-min+1))+min; }
function rf(min,max){ return rnd()*(max-min)+min; }

/* ---------- Game state ---------- */
let buildings = [];
let players = [];   // {side:'L'|'R', x, y (roof top), color, alive, reaction: timer}
let projectile = null; // {x,y,vx,vy,trail}
let state = 'idle';  // idle | aiming | flying | exploding | result
let currentPlayer = 0; // 0 or 1
let angle = 45, power = 50; // aim values (0..90 deg, 0..100 power)
let wind = 0;
let score = [0,0];
let particles = [];
let scorch = null; // {x,y,r}
let windTimer = 0;
let resultTimer = 0;

const el = id => document.getElementById(id);

/* ---------- City generation ---------- */
function generateCity(){
  buildings = [];
  const count = Math.max(4, Math.min(14, CONFIG.buildingCount));
  const gap = 14;
  let totalGap = gap*(count-1);
  let avail = W - totalGap;
  // scale widths so the skyline fits within the canvas
  let totalW = 0, widths = [];
  for(let i=0;i<count;i++){
    const w = Math.round(rf(44, 92));
    widths.push(w); totalW += w;
  }
  const scale = totalW>0 ? avail/totalW : 1;
  let x = 0;
  for(let i=0;i<count;i++){
    const w = Math.max(30, Math.round(widths[i]*scale));
    const h = Math.round(rf(CONFIG.buildingHeightMin, CONFIG.buildingHeightMax));
    const color = `hsl(${ri(190,260)} ${ri(28,45)}% ${ri(26,40)}%)`;
    const windows = ri(3,6);
    buildings.push({ x:x, y:GROUND-h, w:w, h:h, color, windows, windowColor:`hsl(${ri(30,55)} 90% ${ri(55,72)}%)` });
    x += w + gap;
  }
}

/* place two gorillas on left-side and right-side buildings, far apart */
function placeGorillas(){
  const n = buildings.length;
  let li = ri(0, Math.max(0, Math.floor(n*0.35)-1));
  let ri_ = Math.floor(n*0.65)+ri(0, Math.max(0, n-1-Math.floor(n*0.65)));
  if(ri_ <= li) ri_ = Math.min(n-1, li+2);
  const bL = buildings[li], bR = buildings[ri_];
  const gapPx = (bR.x) - (bL.x + bL.w);
  const minSep = CONFIG.blastRadius*3 + 120;
  // if too close, push to outer edges
  if(gapPx < minSep){
    li = 0; ri_ = n-1;
  }
  players = [
    { side:'L', x: buildings[li].x + buildings[li].w/2, y: buildings[li].y, building:li, color:'#ff4455', name:'P1', alive:true, reaction:0 },
    { side:'R', x: buildings[ri_].x + buildings[ri_].w/2, y: buildings[ri_].y, building:ri_, color:'#44aaff', name:'P2', alive:true, reaction:0 }
  ];
}

/* ---------- Wind ---------- */
function setWind(){
  const range = Math.max(0, CONFIG.windMax - CONFIG.windMin);
  const mag = rf(CONFIG.windMin, CONFIG.windMax);
  wind = (rnd()<0.5 ? -1 : 1) * mag;
  updateWindHUD();
}
function updateWindHUD(){
  const arr = el('windArr');
  if(Math.abs(wind) < 0.05){ arr.textContent='·'; arr.style.color='#556'; }
  else { arr.textContent = wind>0 ? '→' : '←'; arr.style.color = '#7fe3c2'; }
  el('windVal').textContent = Math.abs(wind).toFixed(2);
}

/* ---------- HUD ---------- */
function updateTurnHUD(){
  const t = el('turn');
  t.textContent = 'PLAYER '+(currentPlayer+1)+' TURN';
  t.style.color = players[currentPlayer].color;
  el('angVal').textContent = Math.round(angle);
  el('pwrVal').textContent = Math.round(power);
  el('score').innerHTML = 'P1 <span class="p1c">'+score[0]+'</span> : <span class="p2c">'+score[1]+'</span> P2';
}

/* ---------- Input ---------- */
const keys = {};
window.addEventListener('keydown', e=>{
  const k = e.key;
  if(k===' '|| k==='ArrowUp'||k==='ArrowDown'||k==='ArrowLeft'||k==='ArrowRight') e.preventDefault();
  if(k==='Enter'){
    if(state==='idle' || state==='result'){ startMatch(); }
    return;
  }
  if(k==='t' || k==='T'){ toggleSettings(); return; }
  if(state!=='aiming') return;
  if(k===' '){ fire(); return; }
  keys[k]=true;
});
window.addEventListener('keyup', e=>{ keys[e.key]=false; });

function clamp(v,a,b){ return Math.max(a,Math.min(b,v)); }

function aimTick(){
  if(state!=='aiming') return;
  if(keys['ArrowLeft'])  angle = clamp(angle-0.8, 0, 90);
  if(keys['ArrowRight']) angle = clamp(angle+0.8, 0, 90);
  if(keys['ArrowUp'])    power = clamp(power+0.6, CONFIG.powerMin, CONFIG.powerMax);
  if(keys['ArrowDown'])  power = clamp(power-0.6, CONFIG.powerMin, CONFIG.powerMax);
  el('angVal').textContent = Math.round(angle);
  el('pwrVal').textContent = Math.round(power);
}

/* ---------- Firing ---------- */
function fire(){
  if(state!=='aiming') return;
  const p = players[currentPlayer];
  const rad = angle*Math.PI/180;
  const speed = CONFIG.powerMin + (power-CONFIG.powerMin)/(100-CONFIG.powerMin)*(CONFIG.powerMax-CONFIG.powerMin);
  const dir = p.side==='L' ? 1 : -1;
  projectile = {
    x: p.x + dir*18, y: p.y - 34,
    vx: Math.cos(rad)*speed*dir,
    vy: -Math.sin(rad)*speed,
    trail: [], alive:true
  };
  state='flying';
  playLaunch();
}

/* ---------- Physics & collision ---------- */
function circleHitsRect(cx,cy,cr, r){
  const nx = Math.max(r.x, Math.min(cx, r.x+r.w));
  const ny = Math.max(r.y, Math.min(cy, r.y+r.h));
  const dx = cx-nx, dy = cy-ny;
  return dx*dx+dy*dy <= cr*cr;
}

function explode(x,y){
  scorch = { x:x, y:y, r:0, max: CONFIG.blastRadius*1.15, t:0 };
  // particles
  for(let i=0;i<30;i++){
    const a = rnd()*Math.PI*2, sp = rf(1,7);
    particles.push({ x:x, y:y, vx:Math.cos(a)*sp, vy:Math.sin(a)*sp - 1, life:1, c: rnd()<0.5?'#ffcc33':'#ff7722' });
  }
  playExplosion();
  // damage check
  const opp = players[1-currentPlayer];
  const dx = opp.x-x, dy = (opp.y-30)-y;
  const dist = Math.hypot(dx,dy);
  let hit = false;
  if(CONFIG.damageMode==='radius'){
    hit = dist <= CONFIG.blastRadius;
  } else {
    // falloff: full damage inside radius, linear down to splashFalloff at edge ring
    if(dist <= CONFIG.blastRadius*0.6) hit = true;
    else if(dist <= CONFIG.blastRadius) hit = (1 - (dist - CONFIG.blastRadius*0.6)/(CONFIG.blastRadius*0.4)) > CONFIG.splashFalloff;
  }
  if(hit){
    opp.alive = false; opp.reaction = 120;
    state='exploding'; resultTimer = 90;
  } else {
    state='exploding'; resultTimer = 45;
  }
}

function stepPhysics(){
  if(state!=='flying') return;
  const p = projectile;
  const steps = 2;
  for(let s=0;s<steps;s++){
    p.vy += CONFIG.gravity*100/100*1; // gravity
    p.vy += 0;
    p.vx += wind*0.02;
    p.x += p.vx;
    p.y += p.vy;
    p.trail.push({x:p.x,y:p.y});
    if(p.trail.length>18) p.trail.shift();
    const r = 6;
    // ground
    if(p.y >= GROUND - r){ explode(p.x, GROUND-r); return; }
    // buildings (skip own building? still stops)
    for(const b of buildings){
      if(circleHitsRect(p.x, p.y, r, b)){ explode(p.x, p.y); return; }
    }
    // out of bounds sides
    if(p.x < -20 || p.x > W+20){ explode(p.x, p.y); return; }
  }
}

/* ---------- Turn / match flow ---------- */
function startMatch(){
  seed = (Math.random()*1e9)|0;
  generateCity();
  placeGorillas();
  players.forEach(p=>{ p.alive=true; p.reaction=0; });
  projectile=null; particles=[]; scorch=null;
  angle=45; power=50;
  currentPlayer = Math.random()<0.5?0:1;
  state='aiming';
  setWind();
  updateTurnHUD();
  updateWindHUD();
  hideOverlay();
  playLaunch();
}

function endTurn(){
  // wind shift between turns
  if(CONFIG.windShifts) setWind();
  currentPlayer = 1-currentPlayer;
  angle=45; power=50;
  updateTurnHUD();
  state='aiming';
}

function onExplodeDone(){
  const opp = players[1-currentPlayer];
  if(!opp.alive){
    // winner is current player (the one who fired)
    const winner = currentPlayer;
    score[winner]++;
    updateTurnHUD();
    state='result'; resultTimer=0;
    showResult(winner);
    playVictory();
  } else {
    endTurn();
  }
}

/* ---------- Rendering ---------- */
function drawSky(){
  const g = ctx.createLinearGradient(0,0,0,H);
  g.addColorStop(0,'#1a2b4a'); g.addColorStop(0.55,'#2b3d5e'); g.addColorStop(1,'#3a2a44');
  ctx.fillStyle=g; ctx.fillRect(0,0,W,H);
  // sun/moon
  ctx.fillStyle='rgba(255,240,200,0.9)';
  ctx.beginPath(); ctx.arc(780,120,42,0,Math.PI*2); ctx.fill();
  ctx.fillStyle='rgba(255,240,200,0.12)';
  ctx.beginPath(); ctx.arc(780,120,70,0,Math.PI*2); ctx.fill();
  // clouds
  ctx.fillStyle='rgba(220,230,245,0.14)';
  for(let i=0;i<4;i++){
    const cx=(i*260+40)% (W+200), cy=70+i*40;
    ctx.beginPath(); ctx.ellipse(cx,cy,60,18,0,0,Math.PI*2); ctx.fill();
  }
}

function drawGround(){
  ctx.fillStyle='#243040'; ctx.fillRect(0,GROUND,W,H-GROUND);
  ctx.fillStyle='#35475c'; ctx.fillRect(0,GROUND,W,5);
}

function drawBuilding(b){
  ctx.fillStyle=b.color;
  ctx.fillRect(b.x, b.y, b.w, b.h);
  // roof edge
  ctx.fillStyle='rgba(255,255,255,0.08)';
  ctx.fillRect(b.x, b.y, b.w, 5);
  // windows
  const cols = b.windows, rows = Math.max(2, Math.floor(b.h/44));
  const pw = (b.w-14)/(cols);
  const ph = 12;
  ctx.fillStyle=b.windowColor;
  for(let c=0;c<cols;c++){
    for(let rr=0; rr<rows; rr++){
      const wx = b.x + 8 + c*pw + 2;
      const wy = b.y + 16 + rr*26;
      if(wy < GROUND-10) ctx.fillRect(wx, wy, Math.max(3,pw-6), ph);
    }
  }
}

function drawGorilla(p){
  if(!p.alive){
    // defeated: slumped on ground
    ctx.save();
    ctx.globalAlpha = p.reaction>0?1:0.55;
    ctx.fillStyle=p.color;
    ctx.translate(p.x, GROUND-14);
    ctx.fillRect(-14, -6, 28, 12);
    ctx.beginPath(); ctx.arc(-8,-6,7,0,Math.PI*2); ctx.fill();
    ctx.restore();
    return;
  }
  const x=p.x, y=p.y;
  ctx.save();
  ctx.translate(x,y);
  const facing = p.side==='L'?1:-1;
  // body
  ctx.fillStyle=p.color;
  ctx.fillRect(-11,-30,22,26);
  // head
  ctx.beginPath(); ctx.arc(0,-36,9,0,Math.PI*2); ctx.fill();
  // face
  ctx.fillStyle='rgba(0,0,0,0.35)';
  ctx.fillRect(-6,-40,12,8);
  // eyes
  ctx.fillStyle='#fff';
  ctx.fillRect(-5,-38,3,3); ctx.fillRect(2,-38,3,3);
  // arms
  ctx.fillStyle=p.color;
  ctx.fillRect(facing*10,-28,6,16);
  // legs
  ctx.fillRect(-9,-6,7,8); ctx.fillRect(2,-6,7,8);
  ctx.restore();
}

function drawProjectile(){
  if(!projectile) return;
  const p=projectile;
  // trail
  for(let i=0;i<p.trail.length;i++){
    const t=p.trail[i];
    ctx.globalAlpha = i/p.trail.length*0.6;
    ctx.fillStyle='#ffe14d';
    ctx.beginPath(); ctx.arc(t.x,t.y, 3+(i/p.trail.length)*2, 0, Math.PI*2); ctx.fill();
  }
  ctx.globalAlpha=1;
  // banana
  ctx.save();
  ctx.translate(p.x,p.y);
  ctx.rotate(Math.atan2(p.vy,p.vx));
  ctx.fillStyle='#ffe14d';
  ctx.strokeStyle='#c9a820'; ctx.lineWidth=1.5;
  ctx.beginPath();
  ctx.moveTo(-7,-3); ctx.quadraticCurveTo(4,-8,9,-1); ctx.quadraticCurveTo(3,-1, -7,3);
  ctx.closePath(); ctx.fill(); ctx.stroke();
  ctx.restore();
}

function drawBlast(){
  if(scorch){
    const s=scorch;
    // scorch marker
    ctx.fillStyle='rgba(30,15,10,0.5)';
    ctx.beginPath(); ctx.arc(s.x,s.y, s.max*0.9, 0, Math.PI*2); ctx.fill();
    // explosion ring
    const prog = 1 - (s.t/40);
    ctx.strokeStyle='rgba(255,180,60,'+(0.9*prog)+')';
    ctx.lineWidth = 6*prog+2;
    ctx.beginPath(); ctx.arc(s.x,s.y, s.max*(1-prog*0.4),0,Math.PI*2); ctx.stroke();
    ctx.fillStyle='rgba(255,230,120,'+(0.5*prog)+')';
    ctx.beginPath(); ctx.arc(s.x,s.y, s.max*(1-prog*0.4)*0.6,0,Math.PI*2); ctx.fill();
  }
  for(const pt of particles){
    ctx.globalAlpha = Math.max(0,pt.life);
    ctx.fillStyle=pt.c;
    ctx.fillRect(pt.x-2, pt.y-2, 4,4);
  }
  ctx.globalAlpha=1;
}

function drawAimGuide(){
  if(state!=='aiming') return;
  const p = players[currentPlayer];
  const rad = angle*Math.PI/180;
  const speed = CONFIG.powerMin + (power-CONFIG.powerMin)/(100-CONFIG.powerMin)*(CONFIG.powerMax-CONFIG.powerMin);
  const dir = p.side==='L'?1:-1;
  let vx=Math.cos(rad)*speed*dir, vy=-Math.sin(rad)*speed;
  let x=p.x+dir*18, y=p.y-34;
  ctx.strokeStyle='rgba(255,255,255,0.25)';
  ctx.setLineDash([4,6]); ctx.lineWidth=2;
  ctx.beginPath(); ctx.moveTo(x,y);
  for(let i=0;i<60;i++){
    vy += CONFIG.gravity*100/100*1; vx += wind*0.02;
    x+=vx; y+=vy;
    if(y>GROUND||x<0||x>W) break;
    ctx.lineTo(x,y);
  }
  ctx.stroke(); ctx.setLineDash([]);
}

function render(){
  drawSky();
  buildings.forEach(drawBuilding);
  drawGround();
  drawBlast();
  players.forEach(drawGorilla);
  drawProjectile();
  drawAimGuide();
}

/* ---------- Audio ---------- */
let actx=null;
function ac(){ if(!actx){ actx=new (window.AudioContext||window.webkitAudioContext)(); } return actx; }
function playLaunch(){
  try{ const a=ac(); const o=a.createOscillator(), g=a.createGain();
    o.type='square'; o.frequency.setValueAtTime(600,a.currentTime); o.frequency.exponentialRampToValueAtTime(180,a.currentTime+0.25);
    g.gain.setValueAtTime(0.12,a.currentTime); g.gain.exponentialRampToValueAtTime(0.001,a.currentTime+0.25);
    o.connect(g); g.connect(a.destination); o.start(); o.stop(a.currentTime+0.25); }catch(e){}
}
function playExplosion(){
  try{ const a=ac(); const o=a.createOscillator(), g=a.createGain();
    o.type='sawtooth'; o.frequency.setValueAtTime(160,a.currentTime); o.frequency.exponentialRampToValueAtTime(40,a.currentTime+0.5);
    g.gain.setValueAtTime(0.25,a.currentTime); g.gain.exponentialRampToValueAtTime(0.001,a.currentTime+0.5);
    o.connect(g); g.connect(a.destination); o.start(); o.stop(a.currentTime+0.5); }catch(e){}
}
function playVictory(){
  try{ const a=ac(); const notes=[523,659,784,1046]; let t=a.currentTime;
    notes.forEach((f,i)=>{ const o=a.createOscillator(), g=a.createGain(); o.type='square'; o.frequency.value=f;
      g.gain.setValueAtTime(0.14,t); g.gain.exponentialRampToValueAtTime(0.001,t+0.22); o.connect(g); g.connect(a.destination); o.start(t); o.stop(t+0.22); t+=0.16; }); }catch(e){}
}

/* ---------- Settings panel ---------- */
const SETTINGS = [
  { k:'blastRadius', label:'Blast Radius', min:20, max:120, step:1 },
  { k:'gravity', label:'Gravity Strength', min:0.02, max:2.0, step:0.005 },
  { k:'windMin', label:'Wind Min Speed', min:0, max:3, step:0.1 },
  { k:'windMax', label:'Wind Max Speed', min:0, max:5, step:0.1 },
  { k:'buildingCount', label:'Building Count', min:4, max:14, step:1 },
  { k:'buildingHeightMin', label:'Height Min', min:60, max:400, step:1 },
  { k:'buildingHeightMax', label:'Height Max', min:120, max:520, step:1 },
  { k:'powerMin', label:'Power Min', min:2, max:30, step:0.5 },
  { k:'powerMax', label:'Power Max', min:8, max:40, step:0.5 },
  { k:'splashFalloff', label:'Falloff Threshold', min:0, max:1, step:0.05 }
];
const BOOLS = [
  { k:'windShifts', label:'Wind Shifts Between Turns' },
  { k:'damageMode', label:'Damage Mode', type:'choice', options:['radius','falloff'] }
];

function toggleSettings(){
  if(state==='flying') return;
  if(el('overlay').style.display==='flex') { hideOverlay(); return; }
  showSettings();
}

function showSettings(){
  tuningActive = true;
  const panel = el('panel');
  let html = '<h1>⚙ SETTINGS — LIVE TUNING</h1><span class="livebadge">LIVE TUNING ACTIVE</span>';
  html += '<table>';
  SETTINGS.forEach(s=>{
    html += '<tr><td>'+s.label+'</td><td class="num"><button data-dec="'+s.k+'">−</button></td>'+
            '<td class="val" id="v_'+s.k+'">'+fmtNum(CONFIG[s.k])+'</td>'+
            '<td class="num"><button data-inc="'+s.k+'">+</button></td></tr>';
  });
  html += '</table>';
  BOOLS.forEach(s=>{
    let cur;
    if(s.type==='choice') cur = CONFIG[s.k];
    else cur = CONFIG[s.k] ? 'On' : 'Off';
    html += '<h2>'+s.label+'</h2>';
    if(s.type==='choice'){
      html += '<div class="rowbtns">';
      s.options.forEach(o=>{ html += '<button data-choice="'+s.k+'" data-val="'+o+'" class="'+(cur===o?'':'')+'">'+o+'</button>'; });
      html += '</div>';
    } else {
      html += '<div class="ctrl"><button data-toggle="'+s.k+'">'+cur+'</button></div>';
    }
  });
  html += '<h2>Match</h2><div class="rowbtns">';
  html += '<button id="regenBtn">Regenerate City</button>';
  html += '<button id="rematchBtn">New Match</button>';
  html += '<button id="resetBtn">Reset Defaults</button>';
  html += '<button id="closeBtn">Close (T)</button>';
  html += '</div>';
  html += '<div class="legend">Edits apply immediately (or next launch). Wind range uses windMin..windMax. "Regenerate City" reshuffles the skyline for the current match; "New Match" starts a fresh round with new buildings.</div>';
  panel.innerHTML = html;
  el('overlay').style.display='flex';

  panel.querySelectorAll('[data-inc]').forEach(b=> b.onclick=()=>{ changeSetting(b.getAttribute('data-inc'), +1, b); });
  panel.querySelectorAll('[data-dec]').forEach(b=> b.onclick=()=>{ changeSetting(b.getAttribute('data-dec'), -1, b); });
  panel.querySelectorAll('[data-toggle]').forEach(b=> b.onclick=()=>{ toggleBool(b.getAttribute('data-toggle'), b); });
  panel.querySelectorAll('[data-choice]').forEach(b=> b.onclick=()=>{
    CONFIG[b.getAttribute('data-choice')] = b.getAttribute('data-val');
    showSettings();
  });
  el('regenBtn').onclick = ()=>{ generateCity(); placeGorillas(); projectile=null; particles=[]; scorch=null; state='aiming'; angle=45; power=50; updateTurnHUD(); };
  el('rematchBtn').onclick = ()=>{ hideOverlay(); startMatch(); };
  el('resetBtn').onclick = ()=>{ Object.assign(CONFIG, DEFAULTS); showSettings(); };
  el('closeBtn').onclick = ()=>{ hideOverlay(); };
}

function fmtNum(v){ return Number(v).toFixed(v%1?2:0); }

function changeSetting(k, dir, btn){
  const s = SETTINGS.find(x=>x.k===k);
  const step = s.step;
  if(dir>0) CONFIG[k] = Math.min(s.max, CONFIG[k]+step);
  else CONFIG[k] = Math.max(s.min, CONFIG[k]-step);
  CONFIG[k] = Math.round(CONFIG[k]/step)*step;
  el('v_'+k).textContent = fmtNum(CONFIG[k]);
}

function toggleBool(k, btn){
  if(CONFIG[k]==='radius'||CONFIG[k]==='falloff'){ /* skip */ }
  CONFIG[k] = !CONFIG[k];
  btn.textContent = CONFIG[k]?'On':'Off';
  if(k==='damageMode'){ CONFIG.damageMode = CONFIG[k]?'radius':'falloff'; }
}

function showResult(winner){
  const panel = el('panel');
  const c = players[winner].color;
  panel.innerHTML = '<div id="winbox">'+
    '<h1>ROUND OVER</h1>'+
    '<div class="big" style="color:'+c+'">'+players[winner].name+' WINS</div>'+
    '<div>P1 '+score[0]+' : '+score[1]+' P2</div>'+
    '<div class="legend" style="text-align:center;margin-top:16px">Press <kbd>Enter</kbd> or click to play another match</div>'+
    '<div style="text-align:center;margin-top:12px"><button id="rematchWin">REMATCH</button> <button id="settingsWin">SETTINGS</button></div>'+
    '</div>';
  el('overlay').style.display='flex';
  el('rematchWin').onclick = ()=>{ hideOverlay(); startMatch(); };
  el('settingsWin').onclick = ()=>{ showSettings(); };
}

function hideOverlay(){ el('overlay').style.display='none'; }

/* ---------- Main loop ---------- */
let last=performance.now();
function loop(now){
  const dt = Math.min(50, now-last); last=now;
  aimTick();
  if(state==='flying') stepPhysics();
  else if(state==='exploding'){
    resultTimer--;
    if(scorch){ scorch.t++; }
    for(const pt of particles){ pt.x+=pt.vx; pt.y+=pt.vy; pt.vy+=0.15; pt.vx*=0.98; pt.life-=0.02; }
    particles = particles.filter(p=>p.life>0);
    if(resultTimer<=0) onExplodeDone();
  } else if(state==='result'){
    for(const pt of particles){ pt.x+=pt.vx; pt.y+=pt.vy; pt.vy+=0.15; pt.vx*=0.98; pt.life-=0.015; }
    particles = particles.filter(p=>p.life>0);
  }
  players.forEach(p=>{ if(p.reaction>0) p.reaction--; });
  render();
  requestAnimationFrame(loop);
}

/* ---------- Boot ---------- */
generateCity();
placeGorillas();
state='idle';
updateTurnHUD();
setWind();
render();
// Start screen overlay
(function startScreen(){
  const panel = el('panel');
  panel.innerHTML = '<div id="winbox">'+
    '<h1 style="letter-spacing:6px">🦍 GORILLAZ 🦍</h1>'+
    '<div class="big">ARTILLERY DUEL</div>'+
    '<div class="legend" style="text-align:center">Two gorillas on rooftops. Hurl an exploding banana.<br>'+
    '<kbd>←</kbd>/<kbd>→</kbd> angle · <kbd>↑</kbd>/<kbd>↓</kbd> power · <kbd>Space</kbd> fire · <kbd>T</kbd> settings</div>'+
    '<div style="text-align:center;margin-top:16px"><button id="startBtn">START (Enter)</button></div>'+
    '</div>';
  el('overlay').style.display='flex';
  el('startBtn').onclick = ()=>{ ac(); startMatch(); };
})();

requestAnimationFrame(loop);
})();
</script>
</body>
</html>
<!-- agent-meta {"model":"peculiar-ragdoll/Tiel-Coder-35B-A3B-GGUF-MTP","provider":"brigid","persona":"full","sessionId":"d4c255a7-2846-4633-87c2-8574c1b1fea1","tokensIn":5704460,"tokensOut":246240,"tokensTotal":5950700,"cost":0,"turns":114,"toolCalls":113,"failedToolCalls":1,"timestamp":"2026-09-04T23:33:54.997Z"} -->