Skip to main content

FeatureAccessFact

A fact whose answer is another feature's verdict. This is how features compose.

-- "may enter a chapter" becomes one of the ways "may see the shop" is granted
local chaptersFact = maid:Add(FeatureAccessFact.new("allowedChapters", Chapters))

maid:GiveTask(accessDataService:RegisterFact(chaptersFact))
maid:GiveTask(shop:PushFactAllowsFeature(chaptersFact))

Features do not read other features directly, on purpose. Facts are already the one currency everything else is built on -- the layer merge, the reports, the console readout, AccessFeature.PushFactAllowsFeature -- so turning a feature into a fact means composition arrives with all of that already working, instead of needing a second resolution path and a second way of explaining itself.

Cycles resolve to unresolved

A feature reached from a fact it also grants does not recurse. Each fact layer's resolution is shared per player, and the share is connected before its upstream is, so the re-entrant read joins the in-flight one instead of starting another. It finds nothing buffered yet and the whole loop settles as unresolved.

That is a safe answer rather than a crash, but it is still a modelling mistake -- a feature that depends on itself can never say yes. If a feature is stuck unresolved for no visible reason, look for a cycle in access-facts.

The tri-state has to survive the conversion

allowed becomes true and a real refusal becomes false, but unresolved stays unresolved rather than collapsing to false. A feature that cannot answer yet must not read as a definite "no" to whatever is composing it, or one slow lookup turns into a confident denial three features away.

Functions

new

FeatureAccessFact.new(
factNamestring,
featureAccessFeature,
options{
prioritynumber?,
sourcestring?
}?
) → AccessFact
Show raw api
{
    "functions": [
        {
            "name": "new",
            "desc": "",
            "params": [
                {
                    "name": "factName",
                    "desc": "",
                    "lua_type": "string"
                },
                {
                    "name": "feature",
                    "desc": "",
                    "lua_type": "AccessFeature"
                },
                {
                    "name": "options",
                    "desc": "",
                    "lua_type": "{ priority: number?, source: string? }?"
                }
            ],
            "returns": [
                {
                    "desc": "",
                    "lua_type": "AccessFact"
                }
            ],
            "function_type": "static",
            "source": {
                "line": 55,
                "path": "src/access/src/Shared/Implementations/Facts/FeatureAccessFact.lua"
            }
        }
    ],
    "properties": [],
    "types": [],
    "name": "FeatureAccessFact",
    "desc": "A fact whose answer is another feature's verdict. This is how features compose.\n\n```lua\n-- \"may enter a chapter\" becomes one of the ways \"may see the shop\" is granted\nlocal chaptersFact = maid:Add(FeatureAccessFact.new(\"allowedChapters\", Chapters))\n\nmaid:GiveTask(accessDataService:RegisterFact(chaptersFact))\nmaid:GiveTask(shop:PushFactAllowsFeature(chaptersFact))\n```\n\nFeatures do not read other features directly, on purpose. Facts are already the one currency\neverything else is built on -- the layer merge, the reports, the console readout,\n[AccessFeature.PushFactAllowsFeature] -- so turning a feature into a fact means composition arrives\nwith all of that already working, instead of needing a second resolution path and a second way of\nexplaining itself.\n\n## Cycles resolve to unresolved\n\nA feature reached from a fact it also grants does **not** recurse. Each fact layer's resolution is\nshared per player, and the share is connected before its upstream is, so the re-entrant read joins the\nin-flight one instead of starting another. It finds nothing buffered yet and the whole loop settles as\nunresolved.\n\nThat is a safe answer rather than a crash, but it is still a modelling mistake -- a feature that\ndepends on itself can never say yes. If a feature is stuck unresolved for no visible reason, look for\na cycle in `access-facts`.\n\n## The tri-state has to survive the conversion\n\n`allowed` becomes true and a real refusal becomes false, but **unresolved stays unresolved** rather\nthan collapsing to false. A feature that cannot answer yet must not read as a definite \"no\" to\nwhatever is composing it, or one slow lookup turns into a confident denial three features away.",
    "source": {
        "line": 38,
        "path": "src/access/src/Shared/Implementations/Facts/FeatureAccessFact.lua"
    }
}