居中标题并接入指定WAV与随机关卡音乐
This commit is contained in:
@@ -4,7 +4,6 @@ test('music loops independently, persists mute and cancels in background',()=>{c
|
||||
test('old arrows retain distance, rush to endpoint and fresh arrows move at base speed',()=>{const a=new HintArrows(),motion={total:300},events=[];let f=a.frame('a',0,events);a.heads(f,motion,0);f=a.frame('a',100,events);assert.equal(a.heads(f,motion,0)[0],6.5);events.push({sequence:1,kind:'move'});f=a.frame('a',116,events);const at=a.heads(f,motion,30);assert.ok(at[0]>6.5&&at[0]<10);assert.equal(at[1],30);f=a.frame('a',216,events);const next=a.heads(f,motion,30);assert.ok(next[0]>150);assert.equal(next[1],36.5);for(let t=316;t<=416;t+=100)a.heads(a.frame('a',t,events),motion,30);assert.ok(a.particles.every(p=>!p.rush));a.reset();assert.equal(a.key,null);});
|
||||
test('settings have exactly three independent preferences, and hint has its own cue',()=>{const ui=boot();ui.select(3);ui.button('设置');assert.deepEqual(Array.from(ui.api.buttons(),b=>b.label),['音乐:开','音效开','振动:开','完成']);ui.button('完成');ui.button('指路');assert.equal(ui.played.at(-1),'audio/hint.wav');ui.button('选关');assert.ok(ui.played.includes('music/select.wav'));ui.button('下一页');assert.equal(ui.played.at(-1),'audio/ui.wav');});
|
||||
test('actual fixed numeric node uses checkpoint pitch, final cell uses complete chord',()=>{const pack=JSON.parse(fs.readFileSync(new URL('./src/upstream/levels.json',import.meta.url)));const levels=pack.playOrder.map(id=>pack.levels.find(l=>l.id===id));const i=levels.findIndex(l=>l.cells.some(c=>c.clue>1&&c.clue<l.cells.length));const l=levels[i],ui=boot();ui.select(i);const route=solve(l)[0];for(const id of route){ui.advance(80);ui.cell(id);const e=ui.api.snapshot().feedback.events.at(-1);if(e.kind==='checkpoint')assert.equal(ui.played.at(-1),'audio/checkpoint-'+e.step+'.wav');}assert.equal(ui.played.at(-1),'audio/complete.wav');assert.ok(ui.api.snapshot().feedback.events.some(e=>e.kind==='checkpoint'));});
|
||||
test('two original music loops are quiet, nonempty and have a smooth loop seam',()=>{for(const name of ['select','play']){const b=fs.readFileSync(new URL('./music/'+name+'.wav',import.meta.url));assert.equal(b.readUInt32LE(24),22050);assert.ok(b.length>600000);assert.ok(Math.abs(b.readInt16LE(44)-b.readInt16LE(b.length-2))<100);}});
|
||||
|
||||
test('warm gameplay ambient is 32 seconds, softly leveled and free from percussive envelope jumps',()=>{const b=fs.readFileSync(new URL('./music/play.wav',import.meta.url)),n=(b.length-44)/2,frames=[];assert.equal(n/22050,32);let sum=0,peak=0;for(let i=0;i<n;i++){const v=b.readInt16LE(44+i*2)/32767;sum+=v*v;peak=Math.max(peak,Math.abs(v));}assert.ok(Math.sqrt(sum/n)>.03&&Math.sqrt(sum/n)<.04);assert.ok(peak<.17);for(let i=0;i<n;i+=2205){let energy=0,count=0;for(let j=i;j<Math.min(i+2205,n);j++){const v=b.readInt16LE(44+j*2)/32767;energy+=v*v;count++;}frames.push(Math.sqrt(energy/count));}assert.ok(frames.every((v,i)=>!i||Math.abs(v-frames[i-1])<.006));assert.ok(Math.abs(b.readInt16LE(44)-b.readInt16LE(b.length-2))<50);});
|
||||
test('gameplay ambient uses lower scene volume and keeps independent mute and resume behavior',()=>{const store=new Map(),api={getStorageSync:k=>store.get(k),setStorageSync:(k,v)=>store.set(k,v),createInnerAudioContext:()=>({stop(){},play(){this.plays=(this.plays||0)+1;},onError(){}})},m=new Music(api);m.scene('select');assert.equal(m.audio.volume,.16);m.scene('play');assert.equal(m.audio.volume,.14);assert.equal(m.audio.loop,true);const plays=m.audio.plays;m.scene('play');assert.equal(m.audio.plays,plays);m.suspend();m.resume();assert.equal(m.audio.src,'music/play.wav');assert.equal(m.audio.volume,.14);m.toggle();assert.equal(new Music(api).enabled,false);});
|
||||
test('selected menu and both level loops are stereo PCM WAV with complete musical durations',()=>{const meta=JSON.parse(fs.readFileSync(new URL('./.audio-sources/manifest.json',import.meta.url)));for(const name of ['select','play-a','play-b']){const b=fs.readFileSync(new URL('./'+meta[name].output,import.meta.url));assert.equal(b.readUInt32LE(24),22050);assert.equal(b.readUInt16LE(22),2);assert.ok(meta[name].seconds>29);assert.equal(b.length,meta[name].bytes);}});
|
||||
test('selected four runtime WAVs retain the original durations after sample-rate adaptation',()=>{const meta=JSON.parse(fs.readFileSync(new URL('./.audio-sources/manifest.json',import.meta.url)));for(const [name,item]of Object.entries(meta)){const b=fs.readFileSync(new URL('./.audio-sources/'+name+'.wav',import.meta.url)),seconds=(b.length-44)/(44100*4);assert.ok(Math.abs(seconds-item.seconds)<1/22050);}});
|
||||
test('gameplay ambient uses lower scene volume and keeps independent mute and resume behavior',()=>{const store=new Map(),api={getStorageSync:k=>store.get(k),setStorageSync:(k,v)=>store.set(k,v),createInnerAudioContext:()=>({stop(){},play(){this.plays=(this.plays||0)+1;},onError(){}})},m=new Music(api);m.scene('select');assert.equal(m.audio.volume,.16);m.scene('play');assert.equal(m.audio.volume,.14);assert.equal(m.audio.loop,true);const plays=m.audio.plays;m.scene('play');assert.equal(m.audio.plays,plays);m.suspend();m.resume();assert.ok(['music/play-a.wav','music/play-b.wav'].includes(m.audio.src));assert.equal(m.audio.volume,.14);m.toggle();assert.equal(new Music(api).enabled,false);});
|
||||
|
||||
Reference in New Issue
Block a user