@@ -318,6 +318,8 @@ export function initGlobalStrandsAPI(p5, fn, strandsContext) {
318318 // Add noise function with backend-agnostic implementation
319319 const originalNoise = fn . noise ;
320320 const originalNoiseDetail = fn . noiseDetail ;
321+ const originalRandom = fn . random ;
322+ const originalRandomSeed = fn . randomSeed ;
321323 const originalMillis = fn . millis ;
322324
323325 strandsContext . _noiseOctaves = null ;
@@ -382,6 +384,84 @@ export function initGlobalStrandsAPI(p5, fn, strandsContext) {
382384 return createStrandsNode ( id , dimension , strandsContext ) ;
383385 } ) ;
384386
387+ strandsContext . _randomSeed = null ;
388+
389+ augmentFn ( fn , p5 , 'randomSeed' , function ( seed ) {
390+ if ( ! strandsContext . active ) {
391+ return originalRandomSeed . apply ( this , arguments ) ;
392+ }
393+ strandsContext . _randomSeed = seed ;
394+ } ) ;
395+
396+ augmentFn ( fn , p5 , 'random' , function ( ...args ) {
397+ if ( ! strandsContext . active ) {
398+ return originalRandom . apply ( this , args ) ;
399+ }
400+
401+ const randomVertSnippet = strandsContext . backend . getRandomVertexShaderSnippet ( ) ;
402+ const randomFragSnippet = strandsContext . backend . getRandomFragmentShaderSnippet ( ) ;
403+
404+ strandsContext . vertexDeclarations . add ( randomVertSnippet ) ;
405+ strandsContext . fragmentDeclarations . add ( randomFragSnippet ) ;
406+
407+ if ( strandsContext . backend . getRandomComputeShaderSnippet ) {
408+ const randomComputeSnippet = strandsContext . backend . getRandomComputeShaderSnippet ( ) ;
409+ strandsContext . computeDeclarations . add ( randomComputeSnippet ) ;
410+ }
411+
412+ let seedNode ;
413+ if ( strandsContext . _randomSeed !== null && strandsContext . _randomSeed . isStrandsNode ) {
414+ seedNode = strandsContext . _randomSeed ;
415+ } else {
416+ const userSeed = strandsContext . _randomSeed ;
417+ seedNode = getOrCreateUniformNode (
418+ strandsContext ,
419+ '_p5_randomSeed' ,
420+ DataType . float1 ,
421+ userSeed !== null
422+ ? ( ) => userSeed
423+ : ( ) => performance . now ( ) ,
424+ ) ;
425+ }
426+
427+ // The shader-side random() owns a private per-invocation counter, so a
428+ // single AST node still produces distinct values across runtime loop
429+ // iterations. We just pass the seed.
430+ const nodeArgs = [ seedNode ] ;
431+ const randomOverloads = [ {
432+ params : [ DataType . float1 ] ,
433+ returnType : DataType . float1 ,
434+ } ] ;
435+
436+ if ( args . length === 0 ) {
437+ const { id, dimension } = build . functionCallNode ( strandsContext , 'random' , nodeArgs , {
438+ overloads : randomOverloads ,
439+ } ) ;
440+ return createStrandsNode ( id , dimension , strandsContext ) ;
441+ } else if ( args . length === 1 ) {
442+ // random(max) → [0, max)
443+ const rawNode = build . functionCallNode ( strandsContext , 'random' , nodeArgs , {
444+ overloads : randomOverloads ,
445+ } ) ;
446+ const rawStrandsNode = createStrandsNode ( rawNode . id , rawNode . dimension , strandsContext ) ;
447+ return rawStrandsNode . mult ( p5 . strandsNode ( args [ 0 ] ) ) ;
448+ } else if ( args . length === 2 ) {
449+ // random(min, max) → [min, max)
450+ const rawNode = build . functionCallNode ( strandsContext , 'random' , nodeArgs , {
451+ overloads : randomOverloads ,
452+ } ) ;
453+ const rawStrandsNode = createStrandsNode ( rawNode . id , rawNode . dimension , strandsContext ) ;
454+ const minNode = p5 . strandsNode ( args [ 0 ] ) ;
455+ const maxNode = p5 . strandsNode ( args [ 1 ] ) ;
456+ // min + raw * (max - min)
457+ return rawStrandsNode . mult ( maxNode . sub ( minNode ) ) . add ( minNode ) ;
458+ } else {
459+ p5 . _friendlyError (
460+ `It looks like you've called random() with ${ args . length } arguments. In strands, random() supports 0, 1, or 2 numeric arguments.`
461+ ) ;
462+ }
463+ } ) ;
464+
385465 augmentFn ( fn , p5 , 'millis' , function ( ...args ) {
386466 if ( ! strandsContext . active ) {
387467 return originalMillis . apply ( this , args ) ;
0 commit comments