Apache License 2.0—see LICENSE for details.
Copyright (c) 2025 Woo Ker Yang (Patrick Woo) patrickwoo.1976@gmail.com
If you reference or adapt this framework, please credit Patrick Woo and this repository.
This documentation is still in development.
While every update aims for accuracy, some parts may still be incomplete or contain inaccuracies. I appreciate your understanding in this matter, and we apologize for any inconvenience this may cause.
| 中文版 | Chinese README |
A Podcast Style Audio Introduction
Here is a high-level podcast audio commentary of the following content.
🎧 Listen Now: FPS-R and the Nature of Random
FPS-R is Frame Persistent Stateless Randomisation.
FPS-R is a time-aware pseudo-random number generator that produces practically non-repeating patterns.
Given any series of incrementing input values (e.g., frame, training-epoch, uv, latent-space, x-axis), FPS-R produces a persistent sequence of deterministic random numbers.
These numbers hold their values for unpredictable, yet deterministic time steps before changing.
FPS-R is also stateless—it does not need to store state at each time-step, which avoids complex logic and state management.
The Grammar of FPS-R Across Domains The behaviour of held-randomness in FPS-R leads to the following emergent and observed properties:
At the core, FPS-R is a framework and a grammar that describes a behaviour and phenomenon that these phrases try to express:
In other words, FPS-R reduces entropy over time by adding temporal cohesion through irregular, non-uniform hold periods.
These are pervasive phenomena that can be observed across domains. Here are a few examples of non-linearity found in various aspects of our reality, including physical systems, socio-economic systems, and emotional states:
Natural Systems
Human Systems
Socio-Economic Systems
Geology/Astronomical Systems
Emotional Drifts and Shifts
fpsr_unifying_theory.md Read more about the thoughts and research behind the principle of move-hold and surprise.
The framework’s core design produces several distinct and powerful advantages:
Currently, there are limited methods to emulate this realistic and complex behaviour. Most of these methods are neither simple, convenient nor elegant.
rand()-based MethodsHigh-Level Intent:
rand(): To force structure onto a mechanism that produce actively jumping outputs from frame to frame, in an attempt to achieve naturalistic periods of holds.
As mentioned, rand() tends to jump values from one seed to the next. It cannot achieve a continuous output even when the input seeds are continuous.
A common workaround to make rand() hold its value across frames is to use a stateful logic scaffolding. This involves maintaining a counter variable that tracks how many frames have passed since the last random value was generated. The process typically looks like this:
if/else statement to check if the counter has reached a predefined threshold (the hold duration).
rand(), reset the counter, and hold this new value.Example pseudocode:
hold_counter = 0
hold_duration = random.randint(min_hold, max_hold)
held_value = rand()
def get_random_value():
global hold_counter, hold_duration, held_value
if hold_counter >= hold_duration:
held_value = rand()
hold_duration = random.randint(min_hold, max_hold)
hold_counter = 0
hold_counter += 1
return held_value
This approach requires storing state (such as counters and held values) and using logic to decide when to “toss the coin” for a new random value. While effective, it introduces complexity and demands ongoing state management, making the process difficult to trace and audit. The reliance on state means that each random stream must remember its history, which undermines transparency and predictability.
Furthermore, stateful methods cannot be easily used in parallel or distributed computing environments, including GPU operations, where statelessness is essential for scalability and reproducibility. As the number of these stateful streams increases, so does the burden of managing their individual states, leading to poor scalability and increased maintenance overhead.
High-Level Intent: Noise Functions: The goal is to force irregularity into noise functions that are fundamentally periodic and regularly repeating, in order to achieve more organic and natural randomness.
In the world of film, games and interactive media, using noise to generate random continuity is a common practice.
Simplex, Perlin, etc Noise functions like Perlin and Simplex generate smoothly varying, deterministic outputs across a domain, resulting in periodic and regular patterns. Even when these continuous gradients are quantised into discrete steps, the underlying regularity remains—cross-sections often resemble sine waves, and quantisation produces predictable, repeating transitions. This inherent evenness prevents noise functions from achieving truly random values with irregularly held periods, as their gradients distribute changes uniformly rather than unpredictably.
Worley or Cellular Noise Worley (cellular) noise generates discrete, stepped patterns by assigning each point a value based on its distance to the nearest feature in a grid. This produces regions of stability (holds) and abrupt transitions (jumps), but the underlying structure remains regular and predictable due to the grid and distance calculations. While Worley noise introduces complexity through spatial relationships, its output is fundamentally periodic and lacks the organic, irregularly held randomness needed for naturalistic temporal behaviours. Thus, it cannot emulate the unpredictable holding changing of random values.
A quick review of these methods:
The techniques described above try to achieve naturalistic “hold and jump” behaviour from opposite ends of the regularity-to-irregularity spectrum, but ultimately still fall short being able to express and control the natural “move and hold” phenomenon with intuitive ease.
Workarounds to Mitigate Noise Regularity
To disrupt regular patterns, programmers and artists often introduce layers of different, higher-frequency noise functions, sometimes with additional post-processing to extract “peaks and troughs” before layering and smoothing them out. They also blend in small amounts of true randomness, such as from rand(), to achieve more natural results.
However, these methods are essentially variations of the same techniques, inheriting the same inefficiencies. Each new layer of noise adds complexity, necessitating further tweaks and careful adjustments of parameters. Additionally, every layer of rand() requires its own stateful scaffolding to manage blending and transitions, leading to a complexity explosion.
These workarounds attempt to reduce artifacts by increasing complexity, forcing mechanisms originally not designed for this purpose to behave in a more natural and realistic way.
Given the complexity inherent in such structures, any attempts to modify or revise the setup necessitate a deep understanding of the intricate logic and relationships among the components. This complexity ultimately hinders expressive exploration.
Despite the variety of techniques discussed above, none fully capture the naturalistic “hold and jump” phenomenon observed in real-world systems. These approaches attempt to “force” naturalistic behavior from the outside, layering additional processes to compensate for their inherent limitations. FPS-R directly addresses this gap by working from the inside, designed to emulate natural “move and hold” behavior through intrinsic mathematical principles rather than relying on external mechanisms or scaffolding.
Designed from the ground up, FPS-R uses mathematics and logic based on simple and fundamental arithmetic operations and a stateless, deterministic foundation to emulate, shape, and sculpt the natural behavior of “move and hold.” By avoiding external scaffolding, FPS-R provides a clean, scalable, and reproducible solution for generating unpredictable yet repeatable sequences.
The Profile of rand()
Example output of a random number generator rand():
[ 0.8160,
0.9216,
0.0572,
0.5582,
0.3737,
0.4534,
0.6260,
0.1655,
0.3291,
0.5396]
Every value is different from neighbouring ones. This is the objective of rand().
The Profile of FPS-R
Example output of FPS-R: QS (one of the FPS-R algorithms):
[0.9947, # jump
0.9947,
0.9947,
0.4560, # jump
0.4560,
0.6687, # jump
0.6687,
0.6687,
0.2174, # jump
0.3755, # jump
0.3755]
The entries marked jump are changes in values. Subsequent entries after the jumps are held-values.
These numbers and timing are unpredictable but totally deterministic, repeatable and stateless. By looking up the frame number with the same parameter values, FPS-R will always return the same output.
The following are 2 of the pillars of FPS-R. They will be detailed in the Pillars of FPS-R section.
Predictable Surprise FPS-R is surpising in its values that hold and jump across time, but the value at each time step is perfectly deterministic. It only needs an incrementing or changing frame number as an input to achieve this move-and-hold behaviour, but the same input frame will always yield the same predictable output.
Remembering Without Memory Most current methods for achieving a move-and-hold behavior utilise stateful techniques, relying on “memory” (variables) to store and maintain previous values, using the previous value state to make decisions on the current frame. In contrast, FPS-R’s purely mathematical construct creates the illusion of memory without retaining variables over time. By inputting 100 frame numbers into FPS-R running on 100 separate threads and assembling the outputs sequentially, we observe a hold-and-jump behavior. The output values appear to hold and jump, giving the impression of remembering previous states, despite lacking any stored variables. This characteristic is highly advantageous in parallel processing environments.
FPS-R output is a single stream of values. This seemingly simplistic list of values are a richly encoded result expressing two streams of randomness:
[0.9947, 0.4560, 0.6687, 0.2174, 0.3755]. This looks like what a random number generator returns when we call it 5 times sequentially.Here is a sample line of Python code to generate 5 random numbers from 0 to 1:
# Generate a list of 5 random numbers, truncated to 2 decimal places
[float(f'{random.random():.4f}') for x in range(5)]
result: [0.9362, 0.007, 0.3853, 0.8503, 0.1027]
The output shows the same kind of random, discontinuous values like our sample output above.
[3, 2, 3, 1, 2].Here is a sample line of Python code to generate 5 random numbers from 1 to 5:
# Generate a list of 5 random integers in the range of 1 to 5.
[random.randint(1,5) for x in range(5)]
# result: [3, 5, 3, 4, 1]
This reflects the same patterns of randomness and value ranges reflected in our example output
The deterministic statelessness of FPS-R makes the framework repeatable and inspectable. Its output is laid out before us like an infinitely extending timeline. We can “time-travel” as far as we want to in either direction, forward and backwards without accumulating state like current stateful methods. This is an invaluable in areas of simulation and traceable audit. It makes FPS-R performant and reliable.
FPS-R’s output is a rich multi-axial stream that simultaneously carries 2 orthogonal axes of randomness, collapsed and folded into a single stream.
FPS-R consists of 3 algorithms. Each one achieves the move-and-hold behaviour in a slightly different way.
rand()Here is an HTML visualiser of the four FPS-R algorithms!
Interact with the parameters and see the output values update in real-time.
Check it out here:
FPS-R Algorithms Visualiser on GitHub Pages
FPS-R Algorithms Visualiser HTML code in this repository
The following graphs are sample output of each algorithm in the FPS-R framework, running for 400 time-steps.
fpsr_algoAnalysis.ipynb The following graphs exist inside a Jupyter notebook. Feel free to explore the codes and try them out!
FPS-R is the rich output of 2 folded and collapsed axes. It expresses randomness in both value and time.
This is the “value” axis in the collapsed randomness.
Blue Solid Lines - These are the output values across time, normalised between 0.0 to 1.0. Red Dotted Lines - These are binary 0 and 1 values. 1 indicates a new “jump”, 0 indicates a values that is held-over from the previous frame. Every spike is a jump.
This is “time” axis encoded in FPS-R.
Held Steps graphs are not synced to time. Every data point expresses the number of frames that a generated random value holds until it jumps.
portable_rand()By implementing our own pseudo random number generator, we ensure the results are deterministic across different languages and operating environments.
portable_rand Output Values
This is the output of the Portable_rand() included in FPS-R. It works very much like a rand(). Every frame input into portable_rand() yields a different output value from the previous one. Every frame is a jump with no holds.
portable_rand Held Steps
This graph is constantly showing 1.0 steps, indicating that the held frame length for each value to the next value that jumps is 1 frame exactly. Because every frame is a “jump”, every value constantly holds for 1 frame.
portable_rand Output Timing0.3 seconds for 100,000 frames.
The random output values are varied, and their hold durations for each stretch are different. By watching the spacing between the red spikes, we can see different and irregular hold times. Timing and spacing can be configured using parameters.
This graph shows the varying number frames (or steps) that each random value holds in SM. Each period is different from the last, expressing randomness in the time dimension.
This is the distribution graph of SM’s output after 100,000 time-steps. SM can achieve even distribution after many time-steps.
1.9 seconds for 100,000 frames.
Here we see another seemingly even distribution of random values. In TM, there appears to be an underlying rhythm, alternating between three types of spacings: a large spacing of about 30 frames, another spacing of approximately 15 frames, and a short, glitchy spacing of 3 to 5 frames. Timing and spacing are configurable through parameters.
This graph shows the varying number frames (or steps) that each random value holds in TM. Each period is different from the last, expressing randomness in the time dimension.
This is the distribution graph of TM’s output after 100,000 time-steps. TM can achieve even distribution after many time-steps.
1.6 seconds for 100,000 frames.
The QS algorithm is the second most expressive algorithm in the FPS-R framework; able to produce a wide range of phrasing patterns and the strongest contrast between short bursts of stuttering or glitching and long periods of holding. QS has the highest number of parameters.
This graph shows the varying number frames (or steps) that each random value holds in QS. Each period is different from the last, expressing randomness in the time dimension.
This is the distribution graph of QS’s output after 100,000 time-steps. The distribution of the output values from QS are not evenly distributed. This is the nature of the algorithm. The distribution shape (ie, which bands of values will be the prominent, resonant ones) will differ depending on the parameters.
3.2 seconds for 100,000 frames.
The BD algorithm is the most expressive FPS-R algorithm as a result of the combinatory possibilities of implemented parameters in the algorithm. It also has quirky jumps and twitches, perhaps more so than QS due to the bit-flipping nature of the algorithm.
This graph shows the varying number frames (or steps) that each random value holds in BD. Each period is different from the last, expressing randomness in the time dimension.
The distribution graph of BD’s output after 100,000 time-steps shows a relatively flat result. The evenness is comparable to SM and TM.
3.0 seconds for 100,000 frames with 1 stream.
3.8 seconds for 100,000 frames with 2 streams.
6.2 seconds for 100,000 frames with 5 streams.
The graphs shown above are example outputs. With different parameters, each algorithm can achieve a wide variety of holding patterns and random values. This is especially true of FPS-R QS and FPS-R BD.
FPS-R’s deterministic and stateless properties ensure that its decisions are easy to follow, trace, audit, and study.
FPS-R is designed to be used across a spectrum of operating environments, from the most computationally frugal to high-powered and capable systems.
Lightweight FPS-R was designed to be optimised and computationally light, to operate even on low-power and edge devices. But in the enhanced wrapper version, output can be scaled to unlock rich and analytically meaningful results in powerful high-end computing environments.
Precise The wrapper version achieves bit-for-bit accuracy. FPS-R primarily uses integer operations to ensure precision. In the wrapper version, floating-point decimal numbers are scaled up to large integers to maintain precision.
The enhanced wrapper version with rich output will be described in greater detail shortly below.
FPS-R is a glass-box framework due to its mathematical purity, determinism, and statelessness. Its logic and outputs are transparent, predictable, and free from side effects.
fpsr_manifesto.md Read here for more FPS-R’s manifesto.
Out of the 4 algorithms, SM and TM are “less complex” but are really efficient, delivering highly expressive output. They can each be expressed in a single-line expression. QS and BD have more complex logic than can be practically expressed in a single line, and thus do not have “single-line expression” versions.
fpsr_tech.md Go straight to the technical document of each algorithm here.
Portable_Rand as part of FPS-R
The portable_rand function is a critical component of FPS-R, generating a pseudo-random number based on a given integer seed in a mathematically pure, stateless, and deterministic manner.
Because portable_rand is stateless, deterministic, and mathematically pure, any FPS-R algorithm that uses it will also inherit these properties.
For the sake of brevity this article will only show the logic and expression for FPS-R SM.
Where:
frame).seedOuter).minHold, maxHold).portable_rand()).seedInner).reseedInterval).Let us take a look at FPS-R: SM in a single-line expression.
# The FPS-R:SM expression
frame = 100 # Is the current frame value
minHoldFrames = 16 # probable minimum held period
maxHoldFrames = 24 # maximum held period before cycling
reseedFrames = 9 # inner mod cycle timing
seedInner = -41 # offsets the inner frame
seedOuter = 23 # offsets the outer frame
fpsr_sm_expression = portable_rand(
(seedOuter + frame) - ((seedOuter + frame) % (
minHoldFrames + int(
portable_rand(
(seedInner + frame) - ((seedInner + frame) % reseedFrames)
) * (maxHoldFrames - minHoldFrames)
)
))
)
Each algorithm is also expressed as a function, enabling flexible interaction with parent systems and richer outputs.
There are “wrapper versions” of the algorithms with rich output structures that are able to generate output that is more insightful, meaningful, analytic, at an increased computation cost. These features are organised into level-of-details (LOD) from 0 (lowest computation cost) to 2 (highest computation cost). Setting a LOD will enable the respective set of rich outputs associated with the LOD.
This is significant wrapper-level feature is available across all LOD levels. A lot of work and thought was put into the design to achieve bit-for-bit determinism and accuracy in the output values.
The frame_multiplier feature is built on a “stretch-and-generate” model called Hierarchical Phrased Quantisation (HPQ). This model cleverly splits the timeline into two modes to handle time scaling:
frame_multiplier < 1.0), the algorithm will repeat the value from the original “Content Timeline” for the duration of the stretch. This is like slowing down a tape: the pitch/value is held, but it lasts longer.seg_block_length) that defines a threshold for this stretch. If the time-stretch is so extreme that it exceeds this runway, the algorithm switches modes. It stops stretching the original value and instead generates a new, unique phrase of random values to fill the gap.The optimised design also results in an almost negigible costs even at very low speeds where more frames need to be generated and inserted between the regular “real-time, master” frames.
This two-mode system ensures that slow-motion feels natural and “sticky” at first, but avoids becoming static and boring by introducing new, deterministically generated content during extreme time-stretches.
In broad terms these information include:
randValhas_changedrandVal_previoushold_progresslast_changed_framenext_changed_framerandVal_next_changed_framerandStreamsselected_stream_idxThe higher computational cost of LOD 2 is due to its robust, stateless search. The process of “searching through time” is to iteratively look ahead to find next_changed_frame and last_changed_frame.
To do this, the wrapper performs a two-phase search (an exponential probe followed by a binary search) by recursively calling the base algorithm at LOD 0. This stateless “discovery” method is what guarantees accuracy without relying on memory or state, but it requires re-calculating values for surrounding frames.
Obtaining next_changed_frame and last_changed_frame unlocks the following:
randVal_next_changed_frame the random value at the next jump.hold_progress can be evaluted, expressed as a normalised valueThese enhancements enable meaningful analysis of FPS-R outputs for transparency, traceability and auditable workflows.
fpsr_tech.md Read more about the technical detail of each algorithm here.
FPS-R describes a fundamental and observable phenomenon found across many fields. As such, it can be applied to almost any area to inject complexity and non-linearity.
fpsr_applications.md A more comprehensive document detailing potential areas of application.
n number of next closest matches.fpsr_applications.md A more comprehensive document detailing potential areas of application.
A Capsule is a self-contained, portable data structure that encapsulates a complete FPS-R “performance.” Think of it as a preset or a clip. Each Capsule stores:
Essentially, a Capsule is a single, reusable “block” of deterministic, non-linear behavior, storing “presets” and “performances” similar to audio or video clips.
This is where the power of Capsules becomes clear. If a single Capsule is a “word” or a musical “note,” then you can arrange them to create sophisticated compositions.
The Timeline: Capsules can be placed sequentially on a “timeline” or a “playlist.” By stringing together different Capsules—like cautious_hesitation.cap followed by erratic_burst.cap—you can build complex behavioral narratives for an AI, a robot, or an animation.
A True Grammar: This system transforms FPS-R from a raw generator into a true compositional language. You are no longer just sculpting a single, continuous stream; you are arranging discrete ideas into phrases, sentences, and stories. This unlocks nuanced complex and even layered behaviours. Now imagine a natural extension of a single timeline into simultaneous multi-track, multi-channel timelines with clips running in parallel, adding and subtracting from each other. This can create meaningful complexity that unlocks deeper expressivity.
capsule_A -> capsule_C -> capsule_B is a completely different “passphrase” from capsule_B -> capsule_A -> capsule_C.This creates a powerful, two-factor system. To unlock the content, an adversary would need not only the correct sequence of Capsules (the timeline) but also the correct secret offset. This provides multiple, independent layers of obfuscation behind a veil of deterministic complexity.
The applications of expressive phrases of auditable, traceable and controllable performances of stateless, deterministic random hold and jump are wide and diverse. And in the centre of it, FPS-R as the foundational grammar.
FPS-R is more than a pseudo-random number stream generator; it is a meticulously designed framework that fills a critical gap between chaotic randomness and rigid predictability. By providing a stateless, deterministic, and mathematically pure way to model the “hold-and-jump” phenomena found throughout nature, it offers a new, foundational primitive for a vast array of applications.
Beyond being a technical primitive, FPS-R is a new lens for observing, expressing and composing with the grammar of our non-linear world.
From building more believable AI and resilient systems to composing complex security protocols with Capsules, FPS-R provides the tools to move beyond simple emulation and toward true, expressive composition. It is a “glass-box” framework designed for exploration, precision, and scalability, inviting developers, researchers, and creators to harness the power of predictable unpredictability.
The potential for composability enables FPS-R to combine its time-phrased output in numerous ways. Moving forward, the vision for capsules allows for the encapsulation of performance clips that capture different “moods” across an infinite timeline, utilising various parameter combinations. This further unlocks richer and deeper “move and hold” phrases, enabling a new form of expressiveness that communicates with true non-linearity.
Join me in exploring the limitless possibilities of FPS-R. Together, we can shape the future of non-linear composition with creativity!
Supplementary documents referenced.
fpsr_tech.md Technical docs showing the math logic and code for all the algorithms.
fpsr_applications.md A more comprehensive document detailing potential areas of application.
fpsr_unifying_theory.md A philosophical exploration of surprise, examining nature’s complexity and non-linearity, and how FPS-R captures this essence to enhance simulations and emulations with natural complexity.
Origins, Journals and Reflections A record of my development journey, including inspirations and challenges encountered along the way.
fpsr_algoAnalysis A Jupyter notebook with data graph plots for interactive exploration and experimentation. Try it out yourself!
README-CH The Chinese version of this document.