Programming open-form genart
Practicable notes on seeding open-form generative artworks and crafting interesting projects
Last updated
Was this helpful?
Practicable notes on seeding open-form generative artworks and crafting interesting projects
Last updated
Was this helpful?
Building the deterministic randomness of an open-form generative artwork is different from the traditional manner of building a long-form generative artwork. Before tackling this technical resource, learn more open-form genart here.
Similarly to long-form projects, open-form generative artworks still need to be deterministically random: the art generating script should always produce the same output for the same input seed/hash that it derives its randomness off of.
The difference with open-form generative art, is that the art generating script now receives an array of hash strings as input, rather than a single input hash.
This array is an ordered list of all the elapsed hash strings associated with the individual tokens that form an evolution lineage.
New editions collected at the root of a project behave like editions minted from a regular long-form project, they have a simple input hash.
Editions that are evolved from other editions, receive their own proper new input hash, as well as the hash of their parent, as well as the parent's parent, and so on. In form of an array.
The last entry in this array is the current edition's assigned hash string.
As an artist this means that you are now designing a new kind of generative algorithm that leverages multiple randomness generators—each seeded by one of the hash strings in the input array.
Developing an open-form genart project introduces a number of new fields in the API:
$fx.lineage
string[]
An array of hash strings. One for each of the parents in an evolved edition's lineage of parent tokens.
$fx.depth
number
Simply a number that represents the number of parents a minted edition has.
$fx.randAt: (depth: number)
ResettableRandFunction
A utility function that lets you access different PRNGs tied to the individual lineage hashes.
Open-form genart introduces a new URL schema where an evolved edition receives all of its parent hashes {url}?fxhash={hash}#lineage={parent-hash-0},{parent-hash-1},{parent-hash-2}
, in addition to its own hash. In the API you can access the individual parent hashes through the $fx.lineage
array:
Note that in this array, the last hash in the array is the current edition's hash.
The depth of the current iteration. It encodes the number of parents an iteration has.
It is always equal to $fx.lineage.length - 1
In regular long-form projects $fx.rand()
provides a deterministc PRNG for all randomness purposes. Through $fx.randAt()
you now have access to multiple PRNGs—one for each of the hashes in a token's lineage, accessible by passing the hash's index into the function.
$fx.randAt will throw an error ("Invalid depth") if you try to access a PRNG that doesn't exist.
$fx.randAt($fx.depth)
and $fx.rand()
point to the same PRNG. Additionally for $fx.depth == 0
we have $fx.rand()
, $fx.randAt(0)
, and $fx.randAt($fx.depth)
all pointing to the same PRNG.
The individual randomness generators provided through $fx.randAt()
are independent of each other. Their invocation of one does not advance the random state of another. For example, the two following examples are identical—the ordering of $fx.randAt()
only matters for each individual depth index:
is identical to:
Each PRNG can also be reset in the following manner:
Besides the new open-form specific API fields there is now also a new $fx.createFxRandom
function that lets you instantiate your own PRNGs.
All other API functions are also still available to use in your project's code. But you should be cautious about mixing and matching the randomness of the artwork with the other PRNGs that the API exposes. For example, the $fx.randminter function that is seeded through the minter's wallet address could lead to unpredictable behavior.
If you've previously developed a long-form project for fxhash and are familiar with the CLI and fx(lens), getting started on developing an open-form project is as simple as running npx fxhash create
from your terminal. A new open-form template option will be available.
Then navigate into the new directory cd <your_project_folder>
and run npx fxhash dev
, which will boot up fx(lens) in your browser.
The structure of an open-form artwork has not changed, it is an index HTML file, an optional CSS style sheet, and a script file that now needs to be configured using the new open-form API. Your project can still not make networked requests or load in packages from URLs.
In case you plan to develop and release a regular long-form project in the coming days prior to open-form genart going live on fxhash, you need to prevent the SDK from being updated when running the fxhash dev
command that launches fxlens from the terminal.
You can also revert to the previous version of the CLI/SDK with the update command and specifying the correct version:
The local dev environment fx(lens) got a new tab that lets you change between the existing “long form” and the new “open form” modes. The UI will help you to explore the lineages of your generative art project. You will see a list of all the hashes. A visual representation of each edition in the lineage. Aswell as a graph visualization of the current tree of generations.
In case you experience problems with captures—please reach out on discord. We are still improving the local capture pipeline.
By selecting one edition from the tree or graph you will enter the already known live mode of the edition. The graph will highlight the lineage of the node. You can now experiment with different hashes of the edition. In case the edition already has children, these updates will then be propagated accordingly.
In what follows we provide a couple of examples on how to configure randomness in your open-form project, showcasing how $fx.randAt
in combination with $fx.depth
can be used for practical purposes. There are a couple of major ways to design open-form mechanics:
Depth-based Traits: aspects of the artwork that are directly tied to an minted/evolved edition's depth.
Root Traits: a common characteristic based off of the hash string of an edition minted at the root of an open-form project (first in a lineage).
Inherited Traits (Re-generating Parent Randomness): evolved editions inherit traits by recreating/reproducing the random steps parent editions have previously computed.
Random Mutations: random traits that appear in evolved editions after a certain depth is reached.
Completely Random Traits: using the seed of the current edition (final seed in the seed list).
The simplest new mechanic in open-form genart to create an interesting, evolving artwork is by tying visual, or other parameters, of the artwork to the corresponding depth that it's minted at.
For example, in the open-form simulator, the resolution of the pixel grid (its width and height) can increase as the depth of the longer a token's lineage is.
And in your code you'd simply use the $fx.depth
parameter that's exposed by the API to make this happen.
One idea here could be tying an obvious visual characteristic of the artwork to the depth, so that it's immediately evident what depth the artwork belongs to.
Editions minted at the root of a project (that aren't evolved from another edition and don't have a parent) behave like editions collected from a regular long-form generative artwork. In root mints $fx.rand()
, $fx.randAt(0)
, and $fx.randAt($fx.depth)
will all point to the same PRNG. From an open-form design point-of-view, this root randomness can be used to set lineage defining characteristics.
For example, let's use this root randomness to select a color palette to be used in whatever graphics we draw next:
Keep in mind that the hash string at index 0 of the hash list will be the same for all evolutions/child tokens derived from this root edition. This means that using $fx.randAt(0)
in this manner will use same randomness to make a palette selection, whether it's the root edition, or the tenth edition down the lineage—the same palette will be picked.
In other terms, the root randomness (randomness derived from the index 0 hash) can be used to select lineage defining characteristics. As a simple visual example, this could be the color palette, giving each lineage of the open-form project a unique characteristic:
This same concept applies to using $fx.randAt()
at any depth, in case you want a certain behavior to emerge at a specific depth.
There is no direct way to pass values from parent editions to child evolutions. However, this is likely a common type of behavior that most projects will aim to implement; where editions pass on traits, characteristics, and variables to their evolutions.
To achieve this kind of inheritance the open-form script should be designed in such a manner that the evolved editions reproduce the randomness (random selections and randomly generated numbers) that parent tokens have previously computed.
In practice, this simply boils down to looping over the lineage PRNGs, recomputing the previously made random decisions, and then adding to it with the current edition's randomness. More concretely, we would simply call $fx.randAt
in a loop, from 0
up until the current $fx.depth
.
In the following example we are simply constructing a sentence, such that we add a new randomly selected word with each evolution.
Assuming that our artwork revolves around this constructed sentence in some manner, evolutions will inherit the first part of the sentence that was chained together throughout the preceding lineage, and add their own individual word:
A more complicated setting to exemplify this would be an L-system, in which the string is mutated over the course of the collected and evolved editions. Let's assume a simple alphabet and grammar like the following:
In this grammar each letter has a couple of replacement options—for an evolved child token we want to choose a replacement option at random and construct a new string that will be the base for the collected edition. The evolution logic would be as follows:
This behavior would look as follows:
While the API might seem minimal, open-form genart comes with its own complexities; your code always needs to account for the existence—or non-existence—of hashes depending on its mint depth.
As an example, one scenario that comes to mind are conditional blocks that assume the existence of a specific depth. What's the problem with the following block of code?
Inside of the for loop we're assuming a certain depth/length of the hash list that might not exist yet when the code runs. For the first three minted/evolved editions the loop will simply not do anything, and the phrase string will be remain empty—whereas on the fourth evolution it will do a sudden jump and catch up with the suddenly filled hash list, likely resulting in a random artwork that does not follow the intended open-form progression.
In addition to having root traits for individual lineages, as well as traits that are passed on between evolutions, you'll likely also want to spawn new random traits sometimes.
This involves adding a small algorithmic chance to make a modification appear at random, at a certain depth. In the open-form simulator there's a small chance for new colors to appear after depth 4 has been reached.
Let's revisit our color palette example from earlier—assuming that we're selecting a base palette with the root randomness, we could randomly append a new color to the selected palette:
This random chance could also be made relative to the depth, such that as the depth increases, the more likely it becomes for these random traits to appear—as an additional incentive for collectors to explore the open-form tree in a depth-first manner.
In case you would like to have completely random deterministic aspects for your artwork, you can simply use the last hash in the lineage array—essentially the native hash of the current edition.
Keep in mind, you'll want to design your randomness in such a way that collectors are incentivized to collect your open-form project horizontally and vertically—exploring its parametric space in full, and participating in shaping and curating an interesting collection.
Make sure to also read through the other sections provided in the open-form docs.
Before diving into the technical details, check out the open-form simulator below to get a feel for how this works—and try it out for yourself :
If it's your first time developing a project for fxhash, .
The individual artworks displayed in the open-form UI are captures and not live running artworks. This is done to avoid potential performance problems in case of the generative artwork requiring heavier computations. When evolving lineages in quick succession through the UI captures are queued are queued in the background, meaning that there's always only one live instance of your artwork running at a given time. is used for creating the capture—triggered just as normally when you call $fx.preview()
in your code.
Besides the regular determinism pitfalls that you should avoid — — there likely are many scenarios that could cause the deterministic evolutionary sequence of your open-form projects to break. Make sure to carefully design your open-form randomness and test your project plenty ahead of release.