100 Days After — complete playable survival strategy game

- Seeded procedural runs (survivors, locations, weather, events)
- 100-day campaign with 5 phases, scripted story beats, Day-100 finale
- 8 endings incl. hidden 'First Light Again'
- Data-driven events (people/world/story packs) with callback chains
- Relationships, memories, grief, karma/hope systems
- Tactical encounters (fight/hide/run/negotiate) with honest odds
- 8 upgradeable camp buildings, 13 location templates
- Canvas parallax scenes, procedural SVG portraits, weather FX
- Synthesized adaptive audio (WebAudio, no assets)
- Save slots + autosave (localStorage), shareable seeds
- Tests: headless multi-seed simulator + browser E2E (playwright)
This commit is contained in:
2026-08-23 06:58:47 +00:00
commit 486201655b
36 changed files with 9989 additions and 0 deletions
+54
View File
@@ -0,0 +1,54 @@
import { chromium } from 'playwright-core';
const browser = await chromium.launch({ executablePath: '/root/.cache/ms-playwright/chromium_headless_shell-1148/chrome-linux/headless_shell', args:['--no-sandbox','--disable-gpu','--disable-dev-shm-usage'] });
const page = await browser.newPage({ viewport:{width:1440,height:900} });
await page.goto('http://127.0.0.1:5199', { waitUntil:'domcontentloaded' });
await page.waitForSelector('.title-screen');
await page.waitForTimeout(1500);
function analyzeCanvas(sel){
return page.evaluate((sel)=>{
const c=document.querySelector(sel);
const g=c.getContext('2d');
const d=g.getImageData(0,0,c.width,c.height).data;
let sum=0,sum2=0,n=0; const buckets={};
for(let i=0;i<d.length;i+=4*97){
const v=(d[i]+d[i+1]+d[i+2])/3;
sum+=v;sum2+=v*v;n++;
const b=Math.floor(v/32); buckets[b]=(buckets[b]??0)+1;
}
const mean=sum/n, sd=Math.sqrt(Math.max(0,sum2/n-mean*mean));
return {w:c.width,h:c.height,mean:Math.round(mean),sd:Math.round(sd),buckets};
},sel);
}
console.log('TITLE canvas:', JSON.stringify(await analyzeCanvas('#title-canvas')));
// body font/bg sanity
console.log('body bg:', await page.evaluate(()=>getComputedStyle(document.body).backgroundColor));
console.log('logo font-size:', await page.evaluate(()=>getComputedStyle(document.querySelector('.logo')).fontSize));
await page.click('text=NEW RUN'); await page.fill('.seed-input','999'); await page.click('text=BEGIN DAY 1');
await page.waitForSelector('.game-layout');
await page.waitForTimeout(1500);
console.log('GAME canvas:', JSON.stringify(await analyzeCanvas('#scene')));
// layout geometry: no zero-size majors, actionbar buttons count
const geo = await page.evaluate(()=>{
const r=s=>{const e=document.querySelector(s); if(!e) return null; const b=e.getBoundingClientRect(); return [Math.round(b.width),Math.round(b.height)];};
return {
scene:r('#scene'), left:r('.panel.left'), right:r('.panel.right'),
actions:[...document.querySelectorAll('.action')].length,
survCards:document.querySelectorAll('.surv-card').length,
resChips:document.querySelectorAll('.res-chip').length,
apPips:document.querySelectorAll('.ap-pip').length,
svgPorts:document.querySelectorAll('.surv-card svg').length,
};
});
console.log('geometry:',JSON.stringify(geo));
// portrait svg content sanity
const svgInfo = await page.evaluate(()=>{
const s=document.querySelector('.surv-card svg');
return s?{paths:s.querySelectorAll('path,ellipse,circle,rect').length,len:s.innerHTML.length}:null;
});
console.log('portrait:',JSON.stringify(svgInfo));
await browser.close();
console.log('VISUAL CHECK DONE');