This commit is contained in:
33333-33333 2026-07-16 22:12:03 +09:00
commit 2cc2f003df
71 changed files with 3359 additions and 1027 deletions

View file

@ -25,7 +25,7 @@ function harness(storage = new Map()) {
location:{protocol:"file:",search:""},crypto:{randomUUID:()=>"11111111-1111-4111-8111-111111111111"},
localStorage:{getItem:k=>storage.has(k)?storage.get(k):null,setItem:(k,v)=>storage.set(k,String(v)),removeItem:k=>storage.delete(k)},
document:{hidden:false,getElementById:id=>elements.get(id)||null,createElement:()=>new Element(),addEventListener(){}},
TarinaiEvents:{emit(){}},showToast(){},TarinaiGameDialogs:{confirm:async()=>true},audio:{uiClick(){}},setTimeout(){return ++timer;},clearTimeout(){},setInterval(){return ++timer;},clearInterval(){},fetch:async()=>{throw new Error("offline");},TARINAI_VERSION:"39.16.60"};
TarinaiEvents:{emit(){}},showToast(){},TarinaiGameDialogs:{confirm:async()=>true},audio:{uiClick(){}},setTimeout(){return ++timer;},clearTimeout(){},setInterval(){return ++timer;},clearInterval(){},fetch:async()=>{throw new Error("offline");},TARINAI_VERSION:"39.16.65"};
c.window=c;c.globalThis=c;vm.createContext(c);vm.runInContext(source,c,{filename:"achievements.js"});
return {c,api:c.TarinaiAchievements,storage,elements};
}
@ -35,7 +35,7 @@ function findCard(h,id){const stack=[h.elements.get("achievementList")];while(st
(function run(){
let h=harness();
assert(h.api.definitions.length===54,"must have 54 achievements");
assert(h.api.definitions.length===64,"must have 64 achievements");
h.api.open();
const title=Object.fromEntries(h.api.definitions.map(d=>[d.id,d.title]));
assert(title.medicine_ledger_all==="おくすり手帳全埋め","medicine title");
@ -149,18 +149,24 @@ function findCard(h,id){const stack=[h.elements.get("achievementList")];while(st
h=harness();const noOil={};h.api.recordFightStarted({world:{time:0},a:noOil,b:{}});assert(off(h,"fuel_to_fire"),"fight without mochi unlocked at time zero");
h=harness();const oil2={};const fw2={time:0};h.api.recordConsumableUse({world:fw2,tarinai:oil2,type:"fight_mochi"});fw2.time=30.01;h.api.recordFightStarted({world:fw2,a:oil2,b:{}});assert(off(h,"fuel_to_fire"),"fight mochi over 30s");
// Enemy's enemy: 30 alive, no tarinai damage for 10 sec, danger source, field-local count.
h=harness(); const ew={time:20,tarinai:alive(30),achievementLastTarinaiDamageAt:0};
// Enemy's enemy: 30 alive, danger-item kills, and any dangerous tarinai hit resets the streak.
h=harness(); const ew={time:20,tarinai:alive(30)};
for(let i=0;i<9;i++){const ant={world:ew};h.api.recordAntDamageSource(ant,{type:"firecracker"},{world:ew});h.api.recordAntKilled({world:ew,ant});}
assert(off(h,"enemy_enemy_friend"),"enemy achievement early");const ant10={world:ew};h.api.recordAntDamageSource(ant10,{type:"firecracker"},{world:ew});h.api.recordAntKilled({world:ew,ant:ant10});assert(on(h,"enemy_enemy_friend"),"enemy achievement at 10");
h=harness();const ew2={time:20,tarinai:alive(30),achievementLastTarinaiDamageAt:11};for(let i=0;i<10;i++){const ant={world:ew2};h.api.recordAntDamageSource(ant,{type:"firecracker"},{world:ew2});h.api.recordAntKilled({world:ew2,ant});}assert(off(h,"enemy_enemy_friend"),"enemy counted within 10s of tarinai damage");
ew2.time=22;ew2.achievementLastTarinaiDamageAt=11;const ordinary={world:ew2};h.api.recordAntDamageSource(ordinary,{type:"food"},{world:ew2});h.api.recordAntKilled({world:ew2,ant:ordinary});assert(off(h,"enemy_enemy_friend"),"non-danger ant kill counted");
const noCollateral={time:50,tarinai:alive(30),achievementLastTarinaiDamageAt:-Infinity};
h.api.recordDamageSource(noCollateral.tarinai[0],{type:"food"},{world:noCollateral});
assert(noCollateral.achievementLastTarinaiDamageAt===-Infinity,"ordinary damage incorrectly blocked enemy achievement");
h.api.recordDamageSource(noCollateral.tarinai[0],{type:"firecracker"},{world:noCollateral});
assert(noCollateral.achievementLastTarinaiDamageAt===50,"danger collateral was not recorded");
h=harness();const ew3={time:20,tarinai:alive(30),achievementLastTarinaiDamageAt:0};const overwritten={world:ew3};h.api.recordAntDamageSource(overwritten,{type:"firecracker"},{world:ew3});h.api.recordAntDamageSource(overwritten,{id:"t0"},{world:ew3});h.api.recordAntKilled({world:ew3,ant:overwritten});assert((ew3.achievementEnemyAntKills||0)===0,"stale danger attribution survived later non-danger damage");
assert(off(h,"enemy_enemy_friend"),"enemy achievement early");
h.api.recordDamageSource(ew.tarinai[0],{type:"firecracker"},{world:ew});
assert((ew.achievementEnemyAntKills||0)===0,"dangerous tarinai damage did not reset enemy streak");
for(let i=0;i<10;i++){const ant={world:ew};h.api.recordAntDamageSource(ant,{type:"firecracker"},{world:ew});h.api.recordAntKilled({world:ew,ant});}
assert(on(h,"enemy_enemy_friend"),"enemy achievement did not unlock after 10 clean danger kills");
h=harness();const ew2={time:20,tarinai:alive(30)};
for(let i=0;i<5;i++){const ant={world:ew2};h.api.recordAntDamageSource(ant,{type:"firecracker"},{world:ew2});h.api.recordAntKilled({world:ew2,ant});}
ew2.tarinai[0].dead=true;const resetAnt={world:ew2};h.api.recordAntDamageSource(resetAnt,{type:"firecracker"},{world:ew2});h.api.recordAntKilled({world:ew2,ant:resetAnt});
assert((ew2.achievementEnemyAntKills||0)===0,"population below 30 did not reset enemy streak");
const ordinary={world:ew2};h.api.recordAntDamageSource(ordinary,{type:"food"},{world:ew2});h.api.recordAntKilled({world:ew2,ant:ordinary});assert((ew2.achievementEnemyAntKills||0)===0,"non-danger ant kill counted");
h=harness();const ew3={time:20,tarinai:alive(30)};const overwritten={world:ew3};h.api.recordAntDamageSource(overwritten,{type:"firecracker"},{world:ew3});h.api.recordAntDamageSource(overwritten,{id:"t0"},{world:ew3});h.api.recordAntKilled({world:ew3,ant:overwritten});assert((ew3.achievementEnemyAntKills||0)===0,"stale danger attribution survived later non-danger damage");
// New observation and sticky-bomb relay achievements.
h=harness();const antWorld={tarinai:[],ants:[],items:[{type:"ant_nest",dead:false}]};h.api.evaluateWorld(antWorld,{});assert(on(h,"ant_nest_without_tarinai"),"ant observation condition");
h=harness();const bomb={type:"sticky_bomb",dead:false};for(let i=0;i<14;i++)h.api.recordStickyBombPass(bomb,{});assert(off(h,"sticky_bomb_15_passes"),"sticky relay early");h.api.recordStickyBombPass(bomb,{});assert(on(h,"sticky_bomb_15_passes"),"sticky relay at 15");
// Static integration guards for all production hook points.
const checks={
@ -180,6 +186,9 @@ function findCard(h,id){const stack=[h.elements.get("achievementList")];while(st
"js/tarinai_local_environment_system.js":["enclosedShelterFor","nest_box","pipe"],
"js/tarinai_update_step_frame.js":["isSheltered?.(t.world, t.x, t.y, t)"],
"js/tarinai_disease_nest.js":["isSheltered?.(this.world, this.x, this.y, this)"],
"js/item_dynamic_tool_system.js":["recordStickyBombPass", "reason === \"transfer\""],
"js/snapshot_system.js":["stickyBombPassCount"],
"js/save_codec.js":["sticky_bomb", "[0, -1, 0], 3"],
};
for(const [file,needles] of Object.entries(checks)){const text=fs.readFileSync(path.join(root,file),"utf8");for(const needle of needles)assert(text.includes(needle),`${file} missing ${needle}`);}
console.log("[OK] playstyle achievements: boundaries, reset scope, persistence, inline disclosures, and hook coverage passed");