Cultist Simulator

Cultist Simulator

The Roost Machine
 This topic has been pinned, so it's probably important
Chelnoque  [developer] 5 Jun, 2022 @ 4:01am
THE TWINS - References and Expressions
-- Paths and Spheres 101 --

References allow to target specific areas of the game - "Spheres" in its own terms - and their content. Decks, verbs, slots, table - these all are the separate Spheres. Each Sphere has a unique "Fuсine Path" - not unlike the file system paths! - that defines its [hierarchical] position in the game's world.

Let's begin with several examples:

~/tabletop - self-explanatory! The Sphere that contains tokens on the table. The sphere that's checked by "tablereqs".
~/tabletop!work_0/SituationStorageSphere - the contents of the Work Verb during an execution of any recipe. The things that are displayed in the Verb window corner. By the way, did you know that a correct term for the Verb instance on the table is 'Situation Token'?
~/tabletop!work_0/OutputSphere - the contents of the Work Verb after a recipe chain was completed.
~/tabletop!work_0/work - the contents of the Work slot in the Work Verb (its initial slot).
~/acquaintances_draw - the current contents of the Acquaintances deck.
[/list]

This probably already gave you a good idea, but let's take a closer look on the paths' different parts.

~ - so-called "Root". All the paths (normally) start with it. A disk C: of the game's structure. The Root is where it all begins.
/ - a Sphere separator. Everything that has a '/' before its name in the path, is a Sphere.
! - a token separator. Everything that has a '!' before its name in the path, is a Token.

Root contains Spheres. Spheres may contain Tokens. Tokens, in turn, may contain other Spheres - like Verbs* do. These Spheres, in turn, may contain other Tokens, which, in turn, may contain other Spheres and so on and so on ad infinitum. In practice, the Token/Sphere nesting rarely goes too deep, and for most purposes usually ends somewhere in one the Situation Token spheres. Also Root actually may contain both Spheres and Tokens, what a rascal.
So, a Fuсine path can look like this
~/sphere!token/
or like this
~!token/sphere
or, to complicate things, like this
~/sphere_1!token_1/sphere_2!token_2
but it *can't* look like this
~!token_1!token_2
and it *can't* look like this
~/sphere_1/sphere_2

For more examples you can use /spheres console command in the game - this will log all the existing Spheres, their category and path. You can also toy with the Fuсine Path system by using either /sphere [path] - that'll return the info about the target sphere - or /tokens [path] - that'll return the contents of the target sphere.
Last edited by Chelnoque; 5 Jun, 2022 @ 4:16am
< >
Showing 1-4 of 4 comments
Chelnoque  [developer] 5 Jun, 2022 @ 4:13am 
-- Fuсine References --

Okay, knowing how to tell the game what Sphere you want to check, how do you actually access the tokens inside it and their spects? With Fuсine References, of course!
(adding "Fuсine" to everything is a tradition that takes an inspiration in Batman's equipment)

They can come in many various forms:
  • lantern
  • aspect/lantern
  • ~/tabletop:aspectmax/lantern
  • ~/tabletop!work_0/work:lantern
  • ~/tabletop!work_0/work:lore:aspectrand/lantern
  • ~/tabletop:verb/work
  • root/II

Maybe you're already seeing a pattern?
References have three different parts, most of them being optional:

[SpherePath]:[Filter]:[ValueArea][Operation]/[TargetId]

[SpherePath] is what is says - a Fuсine Path that defines a target sphere. If omitted, it uses the "local scope", which roughly means "here, and as close as possible" (more on that below, in "Local Spheres").

[Filter] - SpherePath returns you all Tokens in a Sphere, Filter passes along only these that satisfy it. It's a boolean expression, and wholly conforms the expression syntax rules - more on that later. When not specified, it doesn't filter anything.

[ValueArea] - determines what kind of value you want; by default it's 'Aspect', meaning you want a value of a certain Aspects from the tokens in the sphere. Areas include:
  • Verb - retuns amount of Situation Tokens that are of specified Verb. Ex. 'verb/dream'
  • Recipe - return amount of Situation Tokens that currently run a specified Recipe, *or a Recipe with a specified aspect*. Ex. 'recipe/workintrojob'.
  • Aspect or Aspects - returns aspect amount from the tokens. Ex. 'aspect/lantern'
  • Root - returns Fuсine Root mutation amount of the specified target aspect. Fuсine Root is like a global scope of the game. There's a new recipe effect that allows you to store values there, and this here operation allows you to return it. Ex. 'root/myGlobalValue'.
  • Executions - returns how many times the specified recipe was executed during this run. Ex. 'executions/studypassion'.

[Operation] - now that you get your values from all the target tokens, what shall you do with them?
  • Sum - an overall amount of the values. Generally, you want that, and that's a default.
  • Num - like sum, but the final amount isn't multiplied by the stack's quantity. To illustrate, say, you have a stack of four 'fragmentedge'. 'aspectsum/edge' will return number 8 (stack of 4 * 2 edge each). 'aspectnum/edge' will return only 2, without multiplying it by 4. But it *will* return 8 if you split the stack into four separate tokens. Not sure it's useful, but who knows.
  • Max - single maximal value among all the tokens.
  • Min - single minimal non-zero value among all the tokens.
  • Rand - single random value from all the tokens.

[TargetId] - self-explanatory again. An id of element/verb/recipe/whatever that you want. You can use a keyword '$count' instead - it simply returns you a number of Tokens. Ex. '~/tabletop:$count' - amount of tokens on the table. Used together with filter, that can be pretty useful - ex. '~/tabletop:lore:$count' - amount of Tokens with the lore aspect on the table.

Note that, in a reference, Value Area and Operation are specified together without any kind of separation - like aspectsum, aspectmax, aspectrand. If only one is specified, the other is set to default - aspect for area, sum for operation. If only target is specified, both are set to default. Ex. 'max/heart' equals 'aspectmax/heart', 'aspect/grail' equals 'aspectsum/grail'. 'recipe/studyreason' technically equals 'recipesum/studyreason', but the value of 'recipe' will almost always be either 1 or 0, so in most cases there'll be no difference between the operations.

Also note that Root and Executions areas don't make use of operations at all - since they always return a single value, there's nothing to sum/max/min/rand. They also don't require a sphere path.

As I've already wrote, all values except of the targetId are optional. Default sphere path is "here", default filter is "pass everything", default area is "aspect", default operation is "sum".
Reference 'lantern' equals to '~/local:true:aspectsum/lantern".

You can toy around with references using /ref [reference] console command. That'll return you the value defined by your reference.
Last edited by Chelnoque; 6 Jun, 2022 @ 1:50am
Chelnoque  [developer] 5 Jun, 2022 @ 4:14am 
-- Expressions --

That's actually the easiest part! Expression is a combination of references that allows you to perform mathematical/logical operations between them. Expressions can make use of all usual math/logic operations, use the same conventions (eg. the order of operations), and even have a set of built-in functions like Max, Pow, Sqrt and such. The full manual can be found here: https://github.com/ncalc/ncalc

References in expressions act as variables and are evaluated when an expression value is accessed. Each reference has to be enclosed in [] or {} - [lantern]+1, {forge}*2, *unless the expression consists only of a single reference and no operations* - like '[~/tabletop:lantern]' can be simplified into just '~/tabletop:lantern', but it can't be '~/tabletop:lantern+1', it needs to be '[~/tabletop:lantern]+1'

[lantern]*[forge] - multiply the local amount of 'lantern' on the local amount of 'forge'.

Oh, and you can't mix booleans and numbers in a single expression. But you can use boolean if you expect a number as a result: '[lantern]>[forge]' returns 1 if there's more lantern than forge, 0 otherwise. But '([lantern]>[forge])+1' won't work.

Filter expressions support expression nesting of any depth - i.e. you can filter tokens with an expression that itself filters tokens and itself uses an expression that filters tokens, but this kind of syntax is hard to parse by human brain, like
{{{{forge}:heart}:lantern}:moth}:influence
What the hell does that even mean? Well, it means "give me an influence amount from a card that has at least one of forge, heart, lantern and moth" (that's Restlessness), but, again, it's really hard to read and write, and easier to replace by just
{[forge] && [heart] && [lantern] && [moth]}:influence
But maybe it'll find its use!


As usual, you can toy with expressions using /exp [expression] console command.
Last edited by Chelnoque; 6 Jun, 2022 @ 1:51am
Chelnoque  [developer] 5 Jun, 2022 @ 5:37am 
-- Advanced Sphere Referencing --

It may sometimes come to this that the normal Fuсine Paths won't cut the deal; and there are several tricks that expand their useability.

The first one is "multi-sphere paths". They allow to target several spheres at once - that's basically obligatory when you're doing a recipe requirements check, since the target values can be spread across verb slots and storage. The syntax is effectively the same:
~/tabletop!work+
But note a plus at the end. It says - "find me every sphere which path starts with '~/tabletop!work'" - i.e. every sphere inside this token. You can even specify a limit, like work+4, which means "return me no more than 4 spheres" - useful in case you want to target several tokens at once - or '+0' / just '+' for an unlimited amount.
Please note that it'll return all the nested spheres - i.e. '~/tabletop+' will return *all the spheres inside all of the tabletop tokens* - an equivalent of core game's "extant".

The second one is category filtering. All spheres belong to one of the following categories:
  • SituationStorage - copiously explained already;
  • Output - situation output;
  • Threshold - slots, wherever they are;
  • World - tabletop and dropzone spheres
  • Dormant - decks (and something else, check '/spheres' command)
  • Notes - a Situation spheres for... text? idk
  • Meta
  • Null

(generally you don't want to touch these last three. also, FuсinePaths by default will try to avoid finding you anything of these categories!)
In square brackets after the path, you can list the categories you want to be included, separated by commas. You can use minus sign to specify that you want these categories to be *excluded*. So, no sign means "all specified categories", minus sign means "any category except for that one". You can even mix categories that have and don't have minus sign, but this doesn't make sense, so don't do that.

By default, a path with a category filter returns only one sphere. You can combine it with the previous technique to return several. And examples, as usual:

~/tabletop!work[storage,output] will return either Storage or Output sphere from the Work verb (they can't coexist, so this is fine).
~/[dormant]+ will find all deck spheres there are in the game (not recommended!)
~/tabletop!work[-notes]+ will return everything that Work token has, except from the Notes sphere.
~[-notes,-dormant,-meta]+ will return every sphere in the game that's neither Notes, nor Dormant, nor Meta.

Finally, there are "Wild Paths". They start with an asterisk instead of "~" and mean "find me a sphere/token with this ID, wherever it is!"
*!work_0 will find you the Work Situation token even if it's not in the tabletop sphere (however unlikely that is! well, I suppose it can be in the debug slot).
Again, this can be combined with the previous techniques:
*!work+ - find all spheres of the token that starts with "work" (that's also shorter than using '~/tabletop!work+').
*/acquaintances[dormant]+77 - ??? I don't know even! Honestly, I'm out of examples, figure this one on your own.

A final sad note:
Given the examples, you'd probably expect that Situation Token naming convention will work like that: work_0 for the first instance of the Work Verb, work_1 for the second (if it happens to appear), dream_0 for the first instance of the Dream verb, etc, etc - i.e. that the number here means "how many times the token with that id manifested". Well, nope.
The number here increments every time a token is created - any kind of token, Situation, Element, anything. So while the first Work verb will always be the 'work_0', the dream token will already be something like 'dream_1536' (and won't be consistent across the runs).
So there's no way you can target Situation tokens by direct paths - you'll always have to use either multi-sphere path, or sphere category filter. Still, in theory, that should suffice.
Last edited by Chelnoque; 5 Jun, 2022 @ 10:58am
Chelnoque  [developer] 5 Jun, 2022 @ 6:53am 
-- Local Spheres --

Now then, when I was explaining what happens if you omit the "Fuсine Path" part of a reference, I said that it'll use the "local scope" by default. What exactly is meant by that?
There are several "convenience paths", which don't point to a specific sphere, but change depending on the current execution context. These paths are:
  • ~/situation - all the Spheres of a Situation where the current event takes place. When the Recipe execution starts, or when Recipe requirements are evaluated, this sphere is set. It resets after that, so no sense accessing it outside of these cases.
  • ~/sphere - the sphere in which the current effect takes place. It's a SituationStorage when you execute the normal recipe effects, it's something else when you're using DistantEffects, and it's tabletop in all other cases.
  • ~/token - the current token. It's set only when evaluating token-scope effects - filters of any kind, and xtriggers.
  • ~/local - one of these three, whichever was set the last. For example, you start evaluating some recipe's requirements - it sets '~/situation', and sets '~/local' to be equal '~/situation'. Then, for example, one of the expressions inside of the requirements start evaluating a filter, and each token that is being evaluated sets itself as the current local token, and during the proccess of that evaluation, '~/local' equals '~/token'. That's it.

The "~/local" is exactly that default scope, which is used when no path are defined. You can use it explicitly if you like to type more, and you can use other convenience paths, for example, if you need to access the situation spheres during the Token filtering.
Last edited by Chelnoque; 5 Jun, 2022 @ 10:59am
< >
Showing 1-4 of 4 comments
Per page: 1530 50