const test=require('node:test'),assert=require('node:assert/strict'),fs=require('node:fs'),vm=require('node:vm'),ts=require('typescript'); test('continue prompt uses the shared artwork without a label or clock binding',()=>{ const a=JSON.parse(fs.readFileSync('assets/cloud_rise_home/prefab/CloudRise.prefab')); const panel=a.find(o=>o.pages&&o.buttons),node=a[panel.continueHint.__id__]; const spriteRef=node._components.find(r=>a[r.__id__].__type__==='cc.Sprite'),sprite=a[spriteRef.__id__]; const meta=JSON.parse(fs.readFileSync('assets/cloud_rise/art/images/continue.png.meta')); assert.equal(node._name,'ClickToContinue'); assert.equal(sprite._spriteFrame.__uuid__,meta.subMetas.continue.uuid); assert.equal(node._components.length,1); assert.equal(node._contentSize.width-4,224); assert.equal(node._contentSize.height-4,43); assert.equal(sprite._sizeMode,0,'custom size reserves the outline margin'); const material=JSON.parse(fs.readFileSync('assets/cloud_rise_home/images/continue_outline.mtl')); assert.equal(sprite._materials[0].__uuid__,JSON.parse(fs.readFileSync('assets/cloud_rise_home/images/continue_outline.mtl.meta')).uuid); assert.equal(material._techniqueData['0'].props.outlineWidth,2); assert.deepEqual(material._techniqueData['0'].props.outlineColor,{__type__:'cc.Vec4',x:0,y:0,z:0,w:0.5}); assert.equal(meta.packable,false,'outline UVs require an independent texture'); assert(!panel.clocks.some(r=>r&&r.__id__===spriteRef.__id__)); }); test('matching and all stages share the continue prompt without stale legacy text or clock rebinding',()=>{ for(const name of ['home','matching','stage1','stage2','stage3']){ const a=JSON.parse(fs.readFileSync((name==='home'?'assets/cloud_rise_home':'assets/cloud_rise/'+name)+'/prefab/CloudRise.prefab')); for(const label of a.filter(o=>o.__type__==='cc.Label')){ if(label._string!==undefined&&label['_N$string']!==undefined) assert.equal(label['_N$string'],label._string,name+'/'+a[label.node.__id__]._name); } const bindings=a.find(o=>o.nodeKeys); if(!bindings)continue; assert(!bindings.nodeKeys.includes('continueHint'),name+' must keep the shared prompt'); bindings.componentKeys.forEach((key,i)=>{ if(!key.startsWith('clocks:'))return; const label=a[bindings.components[i].__id__]; assert.equal(a[label.node.__id__]._name,'Clock',name+' must only bind its activity clock'); }); } }); test('image fractions reuse seven glyphs, preserve slash artwork, and fit the counter width',()=>{ const exports={},cc={Component:class{},Sprite:class{},SpriteFrame:class{},_decorator:{ccclass:c=>c,executeInEditMode:c=>c,property:(...args)=>args.length>1?undefined:()=>{}}}; cc.Sprite.SizeMode={CUSTOM:0}; vm.runInNewContext(ts.transpileModule(fs.readFileSync('assets/cloud_rise_home/scripts/CloudRiseCounter.ts','utf8'),{compilerOptions:{module:ts.ModuleKind.CommonJS,target:ts.ScriptTarget.ES2017,experimentalDecorators:true}}).outputText,{exports,cc}); const counter=new exports.default();counter.frames=Array.from({length:11},(_,i)=>({id:i,getRect:()=>({width:i===10?18:29,height:39})})); counter.node={width:300,children:Array.from({length:7},()=>({sprite:{},getComponent(){return this.sprite;},setContentSize(w,h){this.width=w;this.height=h;},setPosition(x,y){this.x=x;this.y=y;}}))}; for(const value of ['0/5','5/5','0/7','7/7','9/9','0/100','57/100','100/100']){ counter.string=value;const active=counter.node.children.filter(n=>n.active); assert.equal(active.length,value.length);assert.equal(active.map(n=>n.sprite.spriteFrame.id===10?'/':n.sprite.spriteFrame.id).join(''),value); assert(active.every(n=>Math.abs(n.x)+n.width/2<=150.0001)); assert(Math.abs(active[0].x-active[0].width/2+active.at(-1).x+active.at(-1).width/2)<.001); } counter.string='0/5';assert.equal(counter.node.children.length,7); }); test('all stages bind image counters and the supplied hint without cross-stage dependencies',()=>{ const frameId=name=>Object.values(JSON.parse(fs.readFileSync('assets/cloud_rise_home/images/'+name+'.png.meta')).subMetas)[0].uuid; const ids=Array.from({length:10},(_,i)=>frameId('stat_'+i)).concat(frameId('stat_slash')); for(let i=1;i<=3;i++){ const a=JSON.parse(fs.readFileSync('assets/cloud_rise/stage'+i+'/prefab/CloudRise.prefab')); const binding=a.find(o=>o.componentKeys),get=key=>a[binding.components[binding.componentKeys.indexOf(key)].__id__]; for(const key of ['progressCount','survivorCount']){ const c=get(key),n=a[c.node.__id__];assert.deepEqual(c.frames.map(f=>f.__uuid__),ids);assert.equal(n._children.length,7); assert(!n._components.some(r=>a[r.__id__].__type__==='cc.Label')); } const hint=a.find(o=>o._name==='HintText');assert.equal(hint._active,false); assert(a.some(o=>o._spriteFrame?.__uuid__===frameId('streak_hint'))); const button=a.find(o=>o._name==='ShowParticipants');assert.equal(button._active,false); assert.equal(a[button._components.find(r=>a[r.__id__].__type__==='cc.Button').__id__]._enabled,false); } const matching=JSON.parse(fs.readFileSync('assets/cloud_rise/matching/prefab/CloudRise.prefab')); assert(!matching.some(o=>o._name==='StreakHint'));assert(!matching.some(o=>o._spriteFrame?.__uuid__===frameId('streak_hint'))); }); test('authored counter layout survives changing survivor counts and showing slots again',()=>{ const exports={},cc={Component:class{},Sprite:class{},SpriteFrame:class{},_decorator:{ccclass:c=>c,executeInEditMode:c=>c,property:(...args)=>args.length>1?undefined:()=>{}}}; vm.runInNewContext(ts.transpileModule(fs.readFileSync('assets/cloud_rise_home/scripts/CloudRiseCounter.ts','utf8'),{compilerOptions:{module:ts.ModuleKind.CommonJS,target:ts.ScriptTarget.ES2017,experimentalDecorators:true}}).outputText,{exports,cc}); const a=JSON.parse(fs.readFileSync('assets/cloud_rise/stage1/prefab/CloudRise.prefab')); const node=a.find(n=>n._name==='SurvivorsValue'),component=a[node._components[0].__id__],counter=new exports.default(); assert.equal(component.usePrefabLayout,true);counter.usePrefabLayout=component.usePrefabLayout; counter.frames=Array.from({length:11},(_,id)=>({id})); counter.node={children:node._children.map(r=>{const n=a[r.__id__],s=a[n._components[0].__id__];return {x:n._trs.array[0],y:n._trs.array[1],width:n._contentSize.width,height:n._contentSize.height,sprite:{sizeMode:s._sizeMode},getComponent(){return this.sprite;}};})}; const style=()=>counter.node.children.map(n=>[n.x,n.y,n.width,n.height,n.sprite.sizeMode]);const before=style(); for(const value of ['100/100','57/100','9/100','0/100','100/100']){ counter.string=value;assert.deepEqual(style(),before); const active=counter.node.children.filter(n=>n.active);assert.equal(active.length,value.length); assert.equal(active.map(n=>n.sprite.spriteFrame.id===10?'/':n.sprite.spriteFrame.id).join(''),value); } }); test('matching count and all victory counts bind sprite counters with complete digit sets',()=>{ for(const name of ['matching','stage1','stage2','stage3']){ const a=JSON.parse(fs.readFileSync('assets/cloud_rise/'+name+'/prefab/CloudRise.prefab')); const b=a.find(o=>o.componentKeys),key=name==='matching'?'matchingCount':'victoryOthers'; const c=a[b.components[b.componentKeys.indexOf(key)].__id__],n=a[c.node.__id__]; assert.equal(c.frames.length,name==='matching'?11:10);assert.equal(n._children.length,name==='matching'?7:3); assert(!n._components.some(r=>a[r.__id__].__type__==='cc.Label'));assert.equal(c.usePrefabLayout,false); } }); test('stage hints share stage1 size without bubble parents and guides share the home-owned icon',()=>{ const home=JSON.parse(fs.readFileSync('assets/cloud_rise_home/prefab/CloudRise.prefab')),b=home.find(o=>o.pages&&o.buttons); const guide=home[b.activityGuide.__id__];assert.equal(guide._active,false); const help=home[b.overviewHelp.__id__],sprite=home[help._components[0].__id__]._spriteFrame.__uuid__; assert(home[b.guideShade.__id__]._components.some(r=>home[r.__id__].__type__==='cc.BlockInputEvents')); assert.equal(b.guideClose,undefined);assert(!home.some(n=>n._name==='GuideClose')); for(let i=1;i<=3;i++){ const a=JSON.parse(fs.readFileSync('assets/cloud_rise/stage'+i+'/prefab/CloudRise.prefab')); const hint=a.find(n=>n._name==='HintText'); assert(!a.some(n=>n._name==='RewardHint')); assert.equal(a[hint._parent.__id__]._name,'Progress'); assert.equal(hint._contentSize.width,328.4);assert.equal(hint._contentSize.height,39); assert.equal(hint._active,false); const binding=a.find(n=>n.nodeKeys),button=a[binding.nodes[binding.nodeKeys.indexOf('stageHelp')].__id__]; assert.equal(a[button._components[0].__id__]._spriteFrame.__uuid__,sprite); } });