@@ -28,6 +28,7 @@ type ProcessReport = {
2828 startedAt : Date
2929 finishedAt : Date | null
3030 errors : string [ ]
31+ godotLogs : string [ ]
3132 individualAssets : {
3233 total : number
3334 successful : number
@@ -54,7 +55,7 @@ export async function godotOptimizer(
5455 _msg : TaskQueueMessage ,
5556 components : Pick < AppComponents , 'logs' | 'config' | 'storage' | 'assetServer' | 'fetch' >
5657) : Promise < void > {
57- const { logs, assetServer } = components
58+ const { logs, assetServer, storage } = components
5859 const logger = logs . getLogger ( 'godot-optimizer' )
5960
6061 // Cast to extended type to access entityType
@@ -66,20 +67,36 @@ export async function godotOptimizer(
6667 entityType
6768 } )
6869
70+ const tempDir = path . join ( process . cwd ( ) , 'temp' )
71+ let report : ProcessReport | null = null
72+
6973 try {
7074 switch ( entityType ) {
7175 case 'scene' :
72- await processScene ( entity , components )
76+ report = await processScene ( entity , components )
7377 break
7478 case 'wearable' :
7579 case 'emote' :
76- await processWearableOrEmote ( entity , entityType , components )
80+ report = await processWearableOrEmote ( entity , entityType , components )
7781 break
7882 default :
7983 logger . warn ( 'Unknown entity type, defaulting to scene processing' , { entityType } )
80- await processScene ( entity , components )
84+ report = await processScene ( entity , components )
8185 }
8286 } finally {
87+ // Get Godot logs before restart (they will be cleared on restart)
88+ const godotProcessLogs = assetServer . getGodotLogs ( )
89+ if ( godotProcessLogs . length > 0 ) {
90+ logger . info ( 'Captured Godot process logs' , { lineCount : godotProcessLogs . length } )
91+ }
92+
93+ // Add Godot process logs to report
94+ if ( report ) {
95+ report . godotLogs = [ ...report . godotLogs , ...godotProcessLogs ]
96+ // Store the report with Godot logs included
97+ await storeReport ( report , tempDir , storage , logger )
98+ }
99+
83100 // Restart Godot after each entity to free memory
84101 logger . info ( 'Processing complete, restarting Godot to free memory' , {
85102 entityId : entity . entity . entityId
@@ -94,7 +111,7 @@ export async function godotOptimizer(
94111async function processScene (
95112 entity : DeploymentToSqs ,
96113 components : Pick < AppComponents , 'logs' | 'config' | 'storage' | 'assetServer' | 'fetch' >
97- ) : Promise < void > {
114+ ) : Promise < ProcessReport > {
98115 const { logs, storage, assetServer, config } = components
99116 const logger = logs . getLogger ( 'godot-optimizer:scene' )
100117
@@ -113,6 +130,7 @@ async function processScene(
113130 startedAt : new Date ( ) ,
114131 finishedAt : null ,
115132 errors : [ ] ,
133+ godotLogs : [ ] ,
116134 individualAssets : { total : 0 , successful : 0 , failed : 0 } ,
117135 result : null
118136 }
@@ -156,7 +174,7 @@ async function processScene(
156174 optimizedAssets : 0 ,
157175 individualZips : [ ]
158176 }
159- return
177+ return report
160178 }
161179 throw error
162180 }
@@ -170,7 +188,28 @@ async function processScene(
170188 // 3. Wait for metadata processing to complete
171189 const metadataResult = await assetServer . waitForCompletion ( metadataResponse . batch_id , timeoutMs )
172190
191+ // Collect job logs from metadata result
192+ for ( const job of metadataResult . jobs || [ ] ) {
193+ if ( job . error ) {
194+ report . godotLogs . push ( `[${ job . hash } ] ${ job . status } : ${ job . error } ` )
195+ } else {
196+ report . godotLogs . push ( `[${ job . hash } ] ${ job . status } (${ job . elapsed_secs } s)` )
197+ }
198+ }
199+
173200 if ( metadataResult . status === 'failed' ) {
201+ // Check if this is "No assets completed successfully" - treat as success with 0 assets
202+ const errorMsg = metadataResult . error || ''
203+ if ( errorMsg . includes ( 'No assets completed successfully' ) || errorMsg . includes ( 'No processable assets' ) ) {
204+ logger . info ( 'Scene has no processable assets (from metadata result)' , { entityId } )
205+ report . finishedAt = new Date ( )
206+ report . result = {
207+ success : true ,
208+ optimizedAssets : 0 ,
209+ individualZips : [ ]
210+ }
211+ return report
212+ }
174213 throw new Error ( metadataResult . error || 'Metadata processing failed' )
175214 }
176215
@@ -202,7 +241,7 @@ async function processScene(
202241 optimizedAssets : 0 ,
203242 individualZips : [ ]
204243 }
205- return
244+ return report
206245 }
207246
208247 // Parse error is a real failure
@@ -236,7 +275,7 @@ async function processScene(
236275 metadataZipPath : metadataS3Key ,
237276 individualZips : [ metadataS3Key ]
238277 }
239- return
278+ return report
240279 }
241280
242281 logger . info ( 'Found assets to bundle individually' , {
@@ -279,23 +318,34 @@ async function processScene(
279318
280319 const result = await assetServer . waitForCompletion ( response . batch_id , timeoutMs )
281320
321+ // Collect job logs
322+ for ( const job of result . jobs || [ ] ) {
323+ if ( job . error ) {
324+ report . godotLogs . push ( `[${ assetHash } ] ${ job . status } : ${ job . error } ` )
325+ }
326+ }
327+
282328 if ( result . status === 'completed' && result . zip_path ) {
283329 const s3Key = `${ assetHash } -mobile.zip`
284330 await storage . storeFile ( s3Key , result . zip_path )
285331 // Clean up temp file from asset-server
286332 await fs . rm ( result . zip_path , { force : true } ) . catch ( ( ) => { } )
287333 report . individualAssets . successful ++
288334 individualZips . push ( s3Key )
335+ report . godotLogs . push ( `[${ assetHash } ] completed successfully` )
289336 return { success : true , hash : assetHash , s3Key }
290337 } else {
291338 report . individualAssets . failed ++
292- report . errors . push ( `Asset ${ assetHash } failed: ${ result . error || 'Unknown error' } ` )
339+ const errorMsg = result . error || 'Unknown error'
340+ report . errors . push ( `Asset ${ assetHash } failed: ${ errorMsg } ` )
341+ report . godotLogs . push ( `[${ assetHash } ] failed: ${ errorMsg } ` )
293342 return { success : false , hash : assetHash , error : result . error }
294343 }
295344 } catch ( error ) {
296345 report . individualAssets . failed ++
297346 const errorMsg = error instanceof Error ? error . message : String ( error )
298347 report . errors . push ( `Asset ${ assetHash } failed: ${ errorMsg } ` )
348+ report . godotLogs . push ( `[${ assetHash } ] exception: ${ errorMsg } ` )
299349 return { success : false , hash : assetHash , error : errorMsg }
300350 }
301351 } )
@@ -328,16 +378,14 @@ async function processScene(
328378 }
329379
330380 report . finishedAt = new Date ( )
381+ return report
331382 } catch ( error ) {
332383 logger . error ( `Error processing scene ${ entityId } ` )
333384 logger . error ( error as any )
334385 report . errors . push ( error instanceof Error ? error . message : String ( error ) )
335386 report . finishedAt = new Date ( )
336387 report . result = { success : false }
337-
338- throw error
339- } finally {
340- await storeReport ( report , tempDir , storage , logger )
388+ return report
341389 }
342390}
343391
@@ -348,7 +396,7 @@ async function processWearableOrEmote(
348396 entity : DeploymentToSqs ,
349397 entityType : 'wearable' | 'emote' ,
350398 components : Pick < AppComponents , 'logs' | 'config' | 'storage' | 'assetServer' | 'fetch' >
351- ) : Promise < void > {
399+ ) : Promise < ProcessReport > {
352400 const { logs, storage, assetServer, config, fetch } = components
353401 const logger = logs . getLogger ( `godot-optimizer:${ entityType } ` )
354402
@@ -371,6 +419,7 @@ async function processWearableOrEmote(
371419 startedAt : new Date ( ) ,
372420 finishedAt : null ,
373421 errors : [ ] ,
422+ godotLogs : [ ] ,
374423 individualAssets : { total : 1 , successful : 0 , failed : 0 } ,
375424 result : null
376425 }
@@ -422,7 +471,7 @@ async function processWearableOrEmote(
422471 optimizedAssets : 0 ,
423472 individualZips : [ ]
424473 }
425- return
474+ return report
426475 }
427476
428477 // Use first GLTF (for standard mode, we process the whole entity)
@@ -470,6 +519,15 @@ async function processWearableOrEmote(
470519 // 4. Wait for completion
471520 const result = await assetServer . waitForCompletion ( response . batch_id , timeoutMs )
472521
522+ // Collect job logs
523+ for ( const job of result . jobs || [ ] ) {
524+ if ( job . error ) {
525+ report . godotLogs . push ( `[${ job . hash } ] ${ job . status } : ${ job . error } ` )
526+ } else {
527+ report . godotLogs . push ( `[${ job . hash } ] ${ job . status } (${ job . elapsed_secs } s)` )
528+ }
529+ }
530+
473531 if ( result . status === 'failed' ) {
474532 // Log detailed job status for debugging
475533 const jobErrors = result . jobs
@@ -482,6 +540,21 @@ async function processWearableOrEmote(
482540 error : result . error || 'unknown' ,
483541 jobErrors : jobErrors || 'none'
484542 } )
543+
544+ // Check if this is "No assets completed successfully" - treat as success with 0 assets
545+ const errorMsg = result . error || ''
546+ if ( errorMsg . includes ( 'No assets completed successfully' ) || errorMsg . includes ( 'No processable assets' ) ) {
547+ logger . info ( `${ entityType } has no processable assets` , { entityId, gltfHash } )
548+ report . finishedAt = new Date ( )
549+ report . individualAssets . total = 0
550+ report . result = {
551+ success : true ,
552+ optimizedAssets : 0 ,
553+ individualZips : [ ]
554+ }
555+ return report
556+ }
557+
485558 throw new Error ( result . error || 'Processing failed' )
486559 }
487560
@@ -497,6 +570,7 @@ async function processWearableOrEmote(
497570 logger . info ( 'Uploaded ZIP' , { gltfHash, entityType, s3Key } )
498571
499572 report . individualAssets . successful = 1
573+ report . godotLogs . push ( `[${ gltfHash } ] completed successfully` )
500574 report . result = {
501575 success : true ,
502576 batchId : response . batch_id ,
@@ -505,17 +579,17 @@ async function processWearableOrEmote(
505579 }
506580
507581 report . finishedAt = new Date ( )
582+ return report
508583 } catch ( error ) {
509584 logger . error ( `Error processing ${ entityType } ${ entityId } ` )
510585 logger . error ( error as any )
511- report . errors . push ( error instanceof Error ? error . message : String ( error ) )
586+ const errorMsg = error instanceof Error ? error . message : String ( error )
587+ report . errors . push ( errorMsg )
588+ report . godotLogs . push ( `[${ entityId } ] exception: ${ errorMsg } ` )
512589 report . finishedAt = new Date ( )
513590 report . result = { success : false }
514591 report . individualAssets . failed = 1
515-
516- throw error
517- } finally {
518- await storeReport ( report , tempDir , storage , logger )
592+ return report
519593 }
520594}
521595
0 commit comments