diff --git a/CLAUDE.md b/CLAUDE.md index b6defe3..2f9941d 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -18,8 +18,8 @@ For work on the world package or the corpus (the disembodied simulation), read ` 6. `docs/specs/WORDFORM_PIPELINE.md` — the word-form pipeline: a Python program in `python/semantic_world/wordforms/` that makes spoken word forms, synthesizes them, and builds sound embeddings. 7. `docs/specs/CORPUS_GENERATOR.md` — the corpus generator: a Python program in `python/semantic_world/corpus/` that writes documents about the taxonomy's world in an artificial language, with a parse tree and a logical form for every sentence. Its numbered decisions, and the proposals they point to, are part of the specification. 8. `docs/specs/CONNECTED_SPEECH.md` — the plan for spoken sentences: whole utterances, alignment, pauses, speakers, and register. Planned, not built. -9. `docs/specs/WORLD_AND_LANGUAGE.md` — the world-and-language refactor: one world model of state and change for both simulation modes (fluents, event types with preconditions and effects, time steps), a new package `python/semantic_world/world/`, and the corpus rebuilt on that model. Built in phases and stages. Its decisions were reviewed by Jon on October 7, 2026, and are logged as WM.1 to WM.33 in `docs/DECISIONS.md`. Phase (a) is built (stages a1 to a8: the world package, the cut-over of the taxonomy and the corpus, the labels, states, changes, causal statements, descriptions, the documentation, and the regenerated datasets); the engineering choices of each stage are in `docs/proposals/` and logged as WM.E rows. Phase (b) is built: stage b1 (conditional effects), stage b2 (the world's comparisons and numeric fluents), stage b3a (the corpus's comparisons of scalars: relative gradable adjectives as comparisons, comparatives, and the comparison classes read from the world), and stage b3b (the numeric fluents in the corpus, `RAISES` and `LOWERS`, conditional causal statements, and the tense affix on the auxiliaries), each with a proposal file in `docs/proposals/`. Phase (c) is specified in `docs/specs/WORLD_PHASE_C.md` (item 10), and not yet built. -10. `docs/specs/WORLD_PHASE_C.md` — phase (c) of the world-and-language refactor: pair fluents (two-place fluents with profiles, `PAIRFL.`), places from a second run of the taxonomy generator, location as a pair fluent toward places, place event types (moves and acts at a place), emergent habitats, and the corpus's statements about them. Decided with Jon on October 15 and 16, 2026 (WM.36 to WM.59). Built in seven stages, c1 to c5b; read `WORLD_AND_LANGUAGE.md` first. +9. `docs/specs/WORLD_AND_LANGUAGE.md` — the world-and-language refactor: one world model of state and change for both simulation modes (fluents, event types with preconditions and effects, time steps), a new package `python/semantic_world/world/`, and the corpus rebuilt on that model. Built in phases and stages. Its decisions were reviewed by Jon on October 7, 2026, and are logged as WM.1 to WM.33 in `docs/DECISIONS.md`. Phase (a) is built (stages a1 to a8: the world package, the cut-over of the taxonomy and the corpus, the labels, states, changes, causal statements, descriptions, the documentation, and the regenerated datasets); the engineering choices of each stage are in `docs/proposals/` and logged as WM.E rows. Phase (b) is built: stage b1 (conditional effects), stage b2 (the world's comparisons and numeric fluents), stage b3a (the corpus's comparisons of scalars: relative gradable adjectives as comparisons, comparatives, and the comparison classes read from the world), and stage b3b (the numeric fluents in the corpus, `RAISES` and `LOWERS`, conditional causal statements, and the tense affix on the auxiliaries), each with a proposal file in `docs/proposals/`. Phase (c) is specified in `docs/specs/WORLD_PHASE_C.md` (item 10) and has begun: stage c1 (places, the static side) is built, after Jon's ruling WM.60 that every real the world draws is rounded to 4 decimals when drawn. +10. `docs/specs/WORLD_PHASE_C.md` — phase (c) of the world-and-language refactor: pair fluents (two-place fluents with profiles, `PAIRFL.`), places from a second run of the taxonomy generator, location as a pair fluent toward places, place event types (moves and acts at a place), emergent habitats, and the corpus's statements about them. Decided with Jon on October 15 and 16, 2026 (WM.36 to WM.59). Built in seven stages, c1 to c5b; stage c1 (places: the taxonomy's `labels.kind`, the second taxonomy run, place event types with requirements only, their capacities and statistics, and `view --places`) is built, and c2a is next; read `WORLD_AND_LANGUAGE.md` first. 11. `docs/DECISIONS.md` — the log of every design decision of the taxonomy, word-form, and corpus programs, and of the world model. Append new decisions to it. 12. `docs/guides/` — user guides for the world package (`WORLD.md`: what a world is, the commands, the outputs, the configuration, the default world's figures, the two-place share and its levers), the taxonomy generator (`TAXONOMY.md`), the word-form pipeline (`WORDFORMS.md`), and the corpus generator (`CORPUS.md`). `README.md` there has the full chain. 13. `docs/ENVIRONMENT_SURVEY.md` — background only: why the stack was chosen. @@ -88,6 +88,7 @@ Other commands: cargo run -p sw-schema -- schemas # regenerate schemas/*.json after changing the data-file types cargo run -p sw-schema -- check data/experiments/m1_smoke.yaml # load and resolve an experiment python -m semantic_world.taxonomy data/taxonomy/default.yaml [--seed N] [--out DIR] # run the taxonomy generator +python -m semantic_world.taxonomy data/taxonomy/places_default.yaml # a place taxonomy (labels.kind: place): the default world's second taxonomy run pytest tests/taxonomy # the taxonomy generator's tests alone python -m semantic_world.wordforms forms data/wordforms/default.yaml [--seed N] [--out DIR] # generate word forms python -m semantic_world.wordforms synth data/wordforms/tiny.yaml [--seed N] [--out DIR] # word forms and their audio @@ -106,6 +107,7 @@ python -m semantic_world.wordforms all data/wordforms/corpus_default.yaml pytest tests/corpus # the corpus generator's tests alone python -m semantic_world.world define data/world/default.yaml [--seed N] [--out DIR] # generate a world: the embedded taxonomy run, definition.json, entities.csv, derived values, statistics python -m semantic_world.world view runs/world/tiny_seed1 --preset classic [--include PROPERTY,PART] [--out FILE] # a table for a model: base, static, or classic +python -m semantic_world.world view runs/world/tiny_seed1 --places [--preset static] # a table of the places (stage c1): base, static (with the determined place features and CANBE capacities), or classic python -m semantic_world.world simulate runs/world/tiny_seed1 --episodes 100 [--seed N] [--legal] [--out FILE] [--config data/corpus/default.yaml] # run episodes of a world run and write episodes.jsonl; --config reads a corpus file's scene block python tests/taxonomy/make_golden_hashes.py # rewrite the taxonomy's golden hashes after a deliberate change of the generator python -m semantic_world.figures snapshot # rewrite tests/figures/snapshot.yaml (the figures of the default and tiny runs in runs/, and the baselines of the off settings) and print what moved diff --git a/README.md b/README.md index 7cea84d..64f0375 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ The disembodied simulation is a chain of three Python programs, which need no Ru 2. **The corpus generator** states propositions about the world's rules and events, and realizes the propositions as documents in an artificial language, with test sets. Sentences can use conceptual labels or spoken words (`docs/guides/CORPUS.md`). 3. **The word-form pipeline** makes a spoken word for every word of the lexicon, with audio from many voices and sound embeddings (`docs/guides/WORDFORMS.md`). -The commands for the full chain are in `docs/guides/README.md`, under "The full chain". The chain ran at default scale on October 15, 2026 (stage b3b of the world-and-language refactor, every step): 10,000 documents with 100,917 sentences, spoken words for 203 content lexemes and 18 function words with five sound embeddings, and 32,914 test items. Each guide states what its program does not do yet. +The commands for the full chain are in `docs/guides/README.md`, under "The full chain". The chain ran at default scale on October 15, 2026 (stage b3b of the world-and-language refactor, every step): 10,000 documents with 100,918 sentences, spoken words for 203 content lexemes and 18 function words with five sound embeddings, and 32,914 test items. Each guide states what its program does not do yet. ## Purpose diff --git a/data/taxonomy/default.yaml b/data/taxonomy/default.yaml index da69039..195b957 100644 --- a/data/taxonomy/default.yaml +++ b/data/taxonomy/default.yaml @@ -4,6 +4,8 @@ name: default seed: 1 # master seed, a 64-bit unsigned integer +labels: {kind: entity} # the labels the run writes: entity (CATEGORY, INSTANCE, PROPERTY, PART, SCALARDIM) or place (PLACECATEGORY, PLACE, PLACEPROPERTY, PLACEPART, PLACESCALARDIM; stage c1) + features: property: {count: 40, proportion_determined: 0.25, expected_true_free: 6} part: {count: 40, proportion_determined: 0.25, expected_true_free: 6} diff --git a/data/taxonomy/places_default.yaml b/data/taxonomy/places_default.yaml new file mode 100644 index 0000000..8a71a6c --- /dev/null +++ b/data/taxonomy/places_default.yaml @@ -0,0 +1,27 @@ +# The default place taxonomy (docs/specs/WORLD_PHASE_C.md, "Places"): 2 superordinates, depth 2, +# branching 3, and 2 places per kind, so 12 places in 8 categories; 12 place properties, a quarter +# of them determined by rules; no place parts; and 2 place scalars. The default world +# (data/world/default.yaml) runs it as its second taxonomy, with the seed of the stream +# world:places. With `labels.kind: place` the generator writes PLACECATEGORY, PLACE, +# ISA.PLACECATEGORY, PLACEPROPERTY, PLACEPART, and PLACESCALARDIM in place of the entity labels, +# and nothing else changes. Parameters not listed keep their defaults (see default.yaml). + +name: places_default +seed: 1 + +labels: {kind: place} + +features: + property: {count: 12, proportion_determined: 0.25, expected_true_free: 3} # 9 free, 3 determined; a free place property is true of a third of the places on average + part: {count: 0} # no place parts by default (WM.50) + +taxonomy: + superordinates: 2 + depth: 2 + branching: 3 + +instances: + per_leaf: 2 + +scalars: + count: 2 diff --git a/data/taxonomy/places_tiny.yaml b/data/taxonomy/places_tiny.yaml new file mode 100644 index 0000000..7f62d65 --- /dev/null +++ b/data/taxonomy/places_tiny.yaml @@ -0,0 +1,25 @@ +# The tiny place taxonomy: 2 superordinates at depth 1 (the superordinates are the leaves) with +# 2 places each, so 4 places; 4 place properties, 1 of them determined; no place parts; and 1 +# place scalar. The tiny world (data/world/tiny.yaml) runs it as its second taxonomy. Parameters +# not listed keep their defaults (see default.yaml). + +name: places_tiny +seed: 1 + +labels: {kind: place} + +features: + property: {count: 4, proportion_determined: 0.25, expected_true_free: 1} # 3 free, 1 determined + part: {count: 0} + base_rate_heterogeneity: null # equal base rates: with 3 free features, heterogeneous rates can leave a + # superordinate with no true feature, which the similarity bound cannot place + +taxonomy: + superordinates: 2 + depth: 1 + +instances: + per_leaf: 2 + +scalars: + count: 1 diff --git a/data/world/default.yaml b/data/world/default.yaml index badae36..9e86f29 100644 --- a/data/world/default.yaml +++ b/data/world/default.yaml @@ -57,3 +57,14 @@ event_types: numeric_share: 0.3 # the share of effects that are numeric (+= or -=, stage b2), when the world has numeric fluents numeric_amounts: [0.1, 0.5] # the interval a numeric effect's constant is drawn from event_file: null # a file of explicit event types + +places: # the places (stage c1): a second taxonomy run; null turns places off + taxonomy: {config: data/taxonomy/places_default.yaml, seed: null} # a taxonomy configuration with labels.kind: place; null: the seed of the stream world:places + habitat_strength: 1.0 # the weight of an entity's capacities at a place in its initial place (stage c3; 0: uniform) + +place_event_types: # the place event types (stage c1: requirements only; their moves and acts come in stage c3) + count: 4 + move_share: 0.5 # the share that are moves (stage c3) + constraints: {1: 0.6, 2: 0.4} # weights over the number of constraints of a requirement + constraint_families: {agent: 1, patient: 1, cross: 1, key_lock: 1} # the comparison family (an entity scalar against a place scalar) is off by default + rules: {} # overrides of the taxonomy's rules settings for the constraints diff --git a/data/world/tiny.yaml b/data/world/tiny.yaml index 314900f..7c4be34 100644 --- a/data/world/tiny.yaml +++ b/data/world/tiny.yaml @@ -1,10 +1,13 @@ # A world small enough to read by eye: the tiny taxonomy with 1 scalar dimension (12 instances), -# 4 one-place event types, 4 two-place event types in a tree of 2 superordinates, and 4 fluents. -# Parameters not listed keep their defaults (see default.yaml). +# 4 one-place event types, 4 two-place event types in a tree of 2 superordinates, 4 fluents, and, +# since stage c1, the tiny place taxonomy (4 places) with 2 place event types. Parameters not +# listed keep their defaults (see default.yaml). name: tiny seed: 1 taxonomy: {config: data/taxonomy/tiny_relations.yaml} +places: {taxonomy: {config: data/taxonomy/places_tiny.yaml}} +place_event_types: {count: 2} fluents: count: 4 diff --git a/docs/DECISIONS.md b/docs/DECISIONS.md index 26257b4..c79c11c 100644 --- a/docs/DECISIONS.md +++ b/docs/DECISIONS.md @@ -406,6 +406,7 @@ Spec: `docs/specs/WORLD_AND_LANGUAGE.md`. | WM.57 | 2026-10-16 | Phase (c) is built in seven stages: c1, c2a, c2b, c3, c4, c5a, and c5b, and is specified in a file of its own, `docs/specs/WORLD_PHASE_C.md`. | Claude (chat), Jon | Jon | Decided | `specs/WORLD_PHASE_C.md`, decision 22 | | WM.58 | 2026-10-16 | Where a preposition stands when its object is the gap of a relative clause is a setting, `grammar.word_order.stranding: strand | pied_pipe`, default `strand`. | Claude (chat) | Jon | Decided | `specs/WORLD_PHASE_C.md`, decision 23 | | WM.59 | 2026-10-16 | In phase (c), a conditional effect with a `pair` condition makes no causal statement; the grammar it needs joins stage b3b's second relative clause on the language to-do list. | Claude (chat) | Jon | Decided | `specs/WORLD_PHASE_C.md`, decision 24 | +| WM.60 | 2026-10-16 | Every real the world draws and stores at full precision is rounded to 4 decimals when drawn, as thresholds and comparison margins are (REL.9): a numeric fluent's drift, each entity's initial value of a numeric fluent (uniform or beta, when the world is generated and when an episode redraws its initial state), and a numeric effect's constant. Reason: on Linux (x86_64), with the same Python and package versions as the Mac, the tiny world's definition differed from the Mac's in the last digit of drawn reals (an amount of 0.3969666988843933 against 0.39696669888439323, a drift of 0.005553129001236876 against 0.005553129001236879), so the rule-set identity differed between machines and the snapshot's identity tests failed; the likely cause is that `rng.uniform` computes `low + (high - low) * u`, which ARM and x86 can round differently (a fused multiply-add). The rounding changes the tiny and default worlds once (stage c1, item 1). | Claude (chat) | Jon | Decided | Jon's ruling of October 16, 2026; stage c1 proposal, item 1 | ### World model: Claude Code's engineering choices @@ -652,6 +653,22 @@ Claude Code makes these while building, and records them in each stage's proposa | WM.E237 | 2026-10-15 | The one change to the word-form pipeline: an `inflect` entry may name function words (`InflectEntry.function_words`, read from a request file or the configuration, each gloss one of the request's function words); a function word so named is drawn among the forms that can take the entry's affixes (`can_take`: the join passes the phonotactic check with the repairs and is not a common English word; an English function word that cannot is an error), its inflected forms are made right after the function words (`function_word_pairs`, `FUNCWORD..AFFIX.`), and in a run without a request the affixes are drawn before the function words, from their own stream as before, so that the function words can be filtered. The content words, the assignment, and their audio and embeddings are the same with and without the entries. | Claude Code | Jon | Decided | Stage b3b proposal, choice 14 | | WM.E238 | 2026-10-15 | `stats.yaml`: `states` gains `numeric_state_sentences` and `numeric_change_sentences`; `causal` gains `raises_sentences`, `lowers_sentences`, `conditional_sentences`, and `conditional_effects` (the world's conditional effects of event types with events, the stated, and the unstated by reason); `comparisons` gains `numeric_comparative_sentences`. The snapshot's corpus figures carry the blocks. | Claude Code | Jon | Decided | Stage b3b proposal, choice 15 | | WM.E239 | 2026-10-15 | The oracle judges every numeric form on its own replay (the brute-force evaluator's numeric values) and the `range` of the fluent's symbol record: a numeric pole at the form's `cut`, a numeric change by the values at the two time points, a numeric comparative at the time point with the form's word, `RAISES` and `LOWERS` by the sign of the record's numeric effect, and a conditional statement by the effect whose condition literals equal the form's; its `observed` counts the events whose participants satisfied the condition, and a numeric statement's by the event's recorded change. | Claude Code | Jon | Decided | Stage b3b proposal, choice 16 | +| WM.E240 | 2026-10-16 | `DECIMALS = 4` and `drawn()` in `numeric.py` round a drawn real when it is drawn (WM.60): a numeric fluent's drift, a numeric effect's constant (`_numeric_effect` in `event_types.py`), and each entity's initial value of a numeric fluent, uniform or beta, then clamped to the range (a rounded value can cross a bound given to more than 4 decimals); a fixed initial value is the configuration's own, and the quantities already rounded (scalar thresholds, comparison margins, fluent-rule thresholds) and those drawn from lists (initial rates, threshold fractions) or by the taxonomy (scalar values, base rates) are left alone. The tiny world's fixtures were regenerated; the hand-written fixtures did not change. | Claude Code | Jon | Working design | Stage c1 proposal, choice 1 | +| WM.E241 | 2026-10-16 | `labels.kind: entity | place` (default `entity`) is read into a `Labels` record on the taxonomy configuration (the category, instance, property, part, and scalar prefixes), carried by `build_features` (the feature labels, `FeatureSet.scalar_prefix`), the tree settings (the category prefix), the instances, and `Threshold.prefix` (the scalar label of a threshold literal, drawn or explicit); the ISA labels follow the category labels. `resolved()` writes `labels: {kind}` after `seed`, `data/taxonomy/default.yaml` lists it, and nothing else changes: a place run equals the entity run of the same settings label for label (`test_taxonomy_labels.py`), and the golden hashes of the entity configurations did not move. | Claude Code | Jon | Working design | Stage c1 proposal, choice 2 | +| WM.E242 | 2026-10-16 | The place taxonomies: `data/taxonomy/places_default.yaml` (2 superordinates, depth 2, branching 3, 2 places per leaf, 12 place properties with a quarter determined and `expected_true_free: 3` of 9 free, no parts, 2 scalars) and `places_tiny.yaml` (2 superordinates at depth 1 with 2 places each, 4 place properties with 1 determined and `expected_true_free: 1` of 3 free, no parts, 1 scalar, `base_rate_heterogeneity: null` as WM.E108 set it for the tiny taxonomies). Depth 1 is valid (the superordinates are the leaves). Both carry `labels.kind: place` and are in the golden hashes. | Claude Code | Jon | Working design | Stage c1 proposal, choice 3 | +| WM.E243 | 2026-10-16 | The `places` block (`taxonomy: {config, seed}`, `habitat_strength`) is read into `PlacesConfig`, which validates the named taxonomy configuration at configuration time with its seed resolved: the given seed, or the low 64 bits of the seed of the stream `world:places` (the taxonomy's seeds are 64-bit). The named configuration must set `labels.kind: place`. A world without a `places` key gets `data/taxonomy/places_default.yaml` (the code's default follows `data/world/default.yaml`, WM.E41), and `places: null` turns places off. `place_event_types` (`count` 4, `move_share` 0.5, `constraints` `{1: 0.6, 2: 0.4}`, `constraint_families` `{agent: 1, patient: 1, cross: 1, key_lock: 1}`, `rules`) is `None` exactly when places are off, and giving it with `places: null` is an error; `move_share` and `habitat_strength` are validated and stored for stage c3. The key-lock and comparison settings of the place constraints are those of `event_types.binary` (the code's defaults when the world has no two-place event types). | Claude Code | Jon | Working design | Stage c1 proposal, choice 4 | +| WM.E244 | 2026-10-16 | `places.py` holds `Places` (the place taxonomy run, the place event types, each requirement over every binding as an `(entities, places)` matrix, the capacities) and `generate_places`; `WorldResult.places` carries it, `write` writes the run unmodified to `places/` and the places' base facts to `places.csv` (label, leaf, free place features, place scalars at full precision), and nothing of it enters `StaticWorld`, `entities.csv`, or the episodes. | Claude Code | Jon | Working design | Stage c1 proposal, choice 5 | +| WM.E245 | 2026-10-16 | The constraint sampler of the two-place event types takes a patient pool apart from the agent pool: for place event types the patient's literals come from the place features and scalars, a patient threshold carries the place scalar prefix (`Threshold.prefix`), and a comparison compares an entity scalar with a place scalar, always across dimensions, with the difference's model standard deviation `sqrt(entity_std^2 + place_std^2)` (`ScalarComparison.patient_prefix`, `agent_label`, `patient_label`). With one pool the sampler's draws are unchanged, so the two-place event types of a world are those of stage b3b. | Claude Code | Jon | Working design | Stage c1 proposal, choice 6 | +| WM.E246 | 2026-10-16 | A place event type `PLACEEVENTTYPE.` draws its number of constraints from `place_event_types.constraints`, labels them `CONSTRAINT.PLACEEVENTTYPE..`, and ANDs them as `REQUIREMENT.PLACEEVENTTYPE.`; a requirement that holds for no binding or for every binding of the world (the entities paired with the places, not the leaf pairs of the two-place check) is drawn again, up to 200 times, after which the last draw is kept with a warning in `world_stats.yaml`. The draws come from `world:place_event_types`, as do the samples of the capacities' local test. | Claude Code | Jon | Working design | Stage c1 proposal, choice 7 | +| WM.E247 | 2026-10-16 | `definition.json` gains, only with places: the symbol kinds `place_property`, `place_part`, `place_scalar`, and `place_event_type`; the rule and layer scopes `place` (the determined place features) and `place_binding` (the place constraints and requirements); place literals after the entity literals in the literal table (a place feature or place threshold of the entity scope, a place feature or threshold of the patient, a comparison with a place scalar, a place constraint's output), with the agent's literals shared with the binding scope; and a place event type's record with kind `place_event_type`, arity 2, roles agent and patient, no parent, level 1, no features, an empty precondition (`TRUE`), and no effects. With `places: null` nothing of this is written, so the definition and its identity are those of the world before places. | Claude Code | Jon | Working design | Stage c1 proposal, choice 8 | +| WM.E248 | 2026-10-16 | `RuntimeDefinition` loads `places.csv` beside `entities.csv` (required when the record has place symbols, refused otherwise) into the places' labels, leaves, free values, and scalar values, with the place rules and matrices of both place scopes; `EventTypeRecord.place` and `EventTypeRecord.performable` (kind `event_type`) tell the kinds apart, and the episodes, the fixtures, and the brute-force evaluator list the performable event types only. `able` and `legal` accept a place event type over bindings of an entity index and a place index, `legal_bindings(..., places=)` lists them by agent then place, `check_definition_agreement` covers the place scopes, and `apply` refuses an event of a place event type with a `WorldError`, because stage c1 gives them no dynamics. | Claude Code | Jon | Working design | Stage c1 proposal, choice 9 | +| WM.E249 | 2026-10-16 | The runtime's comparisons accept places: `EVERYPLACE` and the place categories are comparison classes on the place scalars (`comparison_classes`, `members`, `place_categories`), an argument on a place scalar is a place index or the mean of a class of places, and a comparison that mixes an entity scalar (or a numeric fluent) with a class of places, or a place scalar with a class of entities, is refused with a `WorldError`. `derived/comparison_classes.csv` holds the place rows after the entity rows: `EVERYPLACE` first, then the place tree in tree order, on every place scalar. | Claude Code | Jon | Working design | Stage c1 proposal, choice 10 | +| WM.E250 | 2026-10-16 | The capacities reuse `project` with the unknown side's rules apart from the known side's instances: `CAN.PLACEEVENTTYPE.` enumerates the place taxonomy's cones for the entities, `CANBE.PLACEEVENTTYPE.` the entity taxonomy's cones for the places, each with the approximate flag of the local test, then the extensional capacities over the world's bindings. In `capacities.csv` the entities' `CAN.PLACEEVENTTYPE` columns follow the two-place `CANBE` columns before `approximate` (which names approximate place columns too), and `ACTUAL_CAN.PLACEEVENTTYPE` columns come last; `derived/place_capacities.csv` holds one row per place with `CANBE.`, `approximate`, and `ACTUAL_CANBE.` columns. | Claude Code | Jon | Working design | Stage c1 proposal, choice 11 | +| WM.E251 | 2026-10-16 | `relation_proportions.csv` gains rows for the place event types: for each level of the entity tree, every entity category at that level against every place category at the same level, or at the place tree's last level when the entity tree is deeper (the default world's entity leaves at level 3 against the place leaves at level 2), with the share of their bindings for which the requirement holds; `event_type_stats.csv` gains one row per place event type with the share of bindings, the entities and places with a partner, the constraints and families, NaN for the symmetric and within-leaf shares, and the binding density under `leaf_pair_density` with its tries. Both files are written when the world has places even without two-place event types; `relation_pairs.csv` and `thematic.csv` are unchanged, and thematic relatedness stays a relation between entity leaves. | Claude Code | Jon | Working design | Stage c1 proposal, choice 12 | +| WM.E252 | 2026-10-16 | `world_stats.yaml` gains a `places` block (places, categories, place features by kind with free and determined counts, place event types, constraints by family, each requirement's density over the world's bindings and its tries, the share of approximate capacities, and warnings), null without places; `figures.py` reads it into a `places` block of the world figures, the `define` line and the quick-start figure name the places and place event types, and `places_off` (`places: null`) joins the off settings with its baseline identities. | Claude Code | Jon | Working design | Stage c1 proposal, choice 13 | +| WM.E253 | 2026-10-16 | The fixture format gains `places` (the rows of `places.csv`) after `entities`, written only for a world with places; `check_fixture` and the brute-force evaluator read it (`BruteForce(record, entities, places)`, with `_row` finding an entity or a place by label), a place event type's bindings in the evaluator are every entity paired with every place, and the regenerated tiny fixtures carry the places and the place scopes while listing the legal bindings of the performable event types only. The hand-written fixtures are unchanged. | Claude Code | Jon | Working design | Stage c1 proposal, choice 14 | +| WM.E254 | 2026-10-16 | `view --places` writes `RUN/views/places_.csv`: `base` (label, leaf, free place features, place scalars), `static` (with the determined place features and the `CANBE.PLACEEVENTTYPE` capacities), and `classic` (with the ISA columns of the place tree); the sidecar records `places: true`, and `--include` works over the place columns. | Claude Code | Jon | Working design | Stage c1 proposal, choice 15 | +| WM.E255 | 2026-10-16 | Checked, not changed: with places on, the tiny corpus's `documents.jsonl`, `lexicon.csv`, `stats.yaml`, and every rendering are byte-identical to the corpus over the same world with `places: null`, and `scenes.jsonl` differs in the rule-set identity alone; no corpus output names a place, a place feature, or a place event type. | Claude Code | Jon | Working design | Stage c1 proposal, choice 16 | ## Process diff --git a/docs/guides/CORPUS.md b/docs/guides/CORPUS.md index a1b5f95..e879676 100644 --- a/docs/guides/CORPUS.md +++ b/docs/guides/CORPUS.md @@ -32,7 +32,7 @@ The default corpus is 10,000 documents about the default world, `data/world/defa PYTHONPATH=python python -m semantic_world.corpus generate data/corpus/default.yaml ``` -It prints `10000 documents, 100917 sentences, 483329 tokens, 10082 scenes, 46 test sets with 16457 pairs`, and takes about 2.2 minutes (133 seconds) on a laptop (October 15, 2026, stage b3a; 158 seconds in stage b2, 157 seconds in stage b1, 158 seconds in stage a8). The world itself takes 6 seconds (`WORLD.md`). Every stage records these times, measured with nothing else running, and flags a growth of more than half in its proposal file; there is no hard time limit, and no step is made cheaper by changing what it produces by default (Jon's ruling 3 on stage a8). +It prints `10000 documents, 100918 sentences, 483322 tokens, 10082 scenes, 46 test sets with 16457 pairs`, and takes about 2.2 minutes (133 seconds) on a laptop (October 15, 2026, stage b3a; 158 seconds in stage b2, 157 seconds in stage b1, 158 seconds in stage a8). The world itself takes 6 seconds (`WORLD.md`). Every stage records these times, measured with nothing else running, and flags a growth of more than half in its proposal file; there is no hard time limit, and no step is made cheaper by changing what it produces by default (Jon's ruling 3 on stage a8). Two options change a run without editing the configuration: @@ -195,7 +195,7 @@ Every numeric fluent of the world gives the language two absolute gradable adjec | the owl was hungrier than the mouse | `HOLDS(SCENE.8, TIME.2, PAST, GREATER(NUMFL.1, REF.1, REF.2))` | | things that eat things become fuller | `NEC(ALL(EVENT(EVENTVAR.1, EVENTTYPE2.1.2(VAR.1, VAR.2)), LOWERS(EVENTVAR.1, NUMFL.1(VAR.1))))` | -A state ("was hungry") is said with the copula and the adjective, like a Boolean state, and joins the initial-state sentences of narratives. A change is `become` with the comparative form of the adjective whose direction it is: `INCREASE` says that the value at the next time point is greater than at the time point, whatever moved it (the drift included), and `DECREASE` that it is less; a result sentence states one only when the event's own effect moved the value in that direction and the step's net change has it too, and a change by drift alone is never a result sentence. A comparison of two participants at a time point joins the comparative descriptions of narratives, at the event's own time point, with the word the sentence used under `word` as for a comparative of scalars. `RAISES` and `LOWERS` are the causal statements about numeric effects (see "Causal statements"). A numeric threshold is never stated: no word of the language says "more than three quarters" (decision 23). In the default corpus, narratives hold 758 numeric states, 3,791 numeric changes, and 486 numeric comparisons. Every one is judged on the scene's replayed values, by the corpus's truth tests and by the independent oracle of the tests alike. The test sets gain numeric poles in the state sets and the set `state_comparison_role` (see "Test sets"). +A state ("was hungry") is said with the copula and the adjective, like a Boolean state, and joins the initial-state sentences of narratives. A change is `become` with the comparative form of the adjective whose direction it is: `INCREASE` says that the value at the next time point is greater than at the time point, whatever moved it (the drift included), and `DECREASE` that it is less; a result sentence states one only when the event's own effect moved the value in that direction and the step's net change has it too, and a change by drift alone is never a result sentence. A comparison of two participants at a time point joins the comparative descriptions of narratives, at the event's own time point, with the word the sentence used under `word` as for a comparative of scalars. `RAISES` and `LOWERS` are the causal statements about numeric effects (see "Causal statements"). A numeric threshold is never stated: no word of the language says "more than three quarters" (decision 23). In the default corpus, narratives hold 758 numeric states, 3,794 numeric changes, and 486 numeric comparisons. Every one is judged on the scene's replayed values, by the corpus's truth tests and by the independent oracle of the tests alike. The test sets gain numeric poles in the state sets and the set `state_comparison_role` (see "Test sets"). ### Descriptions and assertions diff --git a/docs/guides/README.md b/docs/guides/README.md index 3bc26b4..ba2b15d 100644 --- a/docs/guides/README.md +++ b/docs/guides/README.md @@ -8,8 +8,8 @@ The specifications in `docs/specs/` are a different kind of document. Specificat | Guide | Program | Status | | --- | --- | --- | -| [`WORLD.md`](WORLD.md) | The world package: entities, fluents (Boolean and numeric), event types with requirements, preconditions, and effects, comparisons, episodes, histories, and views | Phases (a) and (b) complete | -| [`TAXONOMY.md`](TAXONOMY.md) | The taxonomy generator: categories, instances, features, rules, and scalar dimensions (the part of a world that is fixed) | Complete | +| [`WORLD.md`](WORLD.md) | The world package: entities, fluents (Boolean and numeric), event types with requirements, preconditions, and effects, comparisons, places and place event types, episodes, histories, and views | Phases (a) and (b) complete; phase (c) begun (stage c1, places) | +| [`TAXONOMY.md`](TAXONOMY.md) | The taxonomy generator: categories, instances, features, rules, and scalar dimensions (the part of a world that is fixed), for entities or for places | Complete | | [`CORPUS.md`](CORPUS.md) | The corpus generator: documents in an artificial language about a world, their renderings, test sets, and spoken word forms | Complete | | [`WORDFORMS.md`](WORDFORMS.md) | The word-form pipeline: spoken word forms, synthesis, auditory front ends, and sound embeddings | Stages 1–7 complete | @@ -17,7 +17,7 @@ The specifications in `docs/specs/` are a different kind of document. Specificat Together, the programs make the disembodied simulation: a world, propositions about the world, and language about the world, without the 3D simulation. The chain works now, and its outputs are usable for training and testing models. -1. `define` builds the world: the taxonomy generator makes the categories, instances, and features in memory, and the world package adds fluents and event types, writes the definition, and runs the statistics episodes. +1. `define` builds the world: the taxonomy generator makes the categories, instances, and features in memory, and since stage c1 the places from a second taxonomy run, and the world package adds fluents, event types, and place event types, writes the definition, and runs the statistics episodes. 2. `generate` writes the corpus about the world: the documents, the four renderings, the test sets, the statistics, and a request for word forms. It defines the world in memory from the same configuration file, so step 1 is needed only when the world's files are wanted on their own. 3. The word-form pipeline reads the request and makes a spoken word for every lexeme. 4. `render` attaches the spoken words to the corpus. @@ -33,14 +33,14 @@ python -m semantic_world.corpus render runs/corpus/default_seed1 --wordforms run | Step | Output folder | What the step writes | Time at default scale | | --- | --- | --- | --- | -| `define` | `runs/world/default_seed1/` | The embedded taxonomy run, the definition, the entities, the derived values, and the world's statistics | 6 seconds (6.3 s in stage b3b, 6.1 s in stage b3a, 6.7 s in stage b2, 7.6 s in stage b1, 7.8 s in stage a8) | -| `generate` | `runs/corpus/default_seed1/` | Documents, the four renderings, test sets, statistics, and the word-form request | 2.4 minutes (141 s in stage b3b, 133 s in stage b3a, 158 s in stage b2, 157 s in stage b1, 158 s in stage a8) | +| `define` | `runs/world/default_seed1/` | The embedded taxonomy run, the place taxonomy run, the definition, the entities and the places, the derived values, and the world's statistics | 6 seconds (6.4 s in stage c1, 6.3 s in stage b3b, 6.1 s in stage b3a, 6.7 s in stage b2, 7.6 s in stage b1, 7.8 s in stage a8) | +| `generate` | `runs/corpus/default_seed1/` | Documents, the four renderings, test sets, statistics, and the word-form request | 2.2 minutes (134 s in stage c1, 141 s in stage b3b, 133 s in stage b3a, 158 s in stage b2, 157 s in stage b1, 158 s in stage a8) | | `wordforms all` | `runs/wordforms/corpus_default_seed1/` | Word forms, audio, auditory front ends, sound embeddings, and their evaluation | about 30 minutes | | `render` | `runs/corpus/default_seed1/` | The spelled rendering (`corpus.txt`) and the word columns of `lexicon.csv` | 3 seconds | Every stage records these times, measured with nothing else running, and its proposal file flags a growth of more than half, with its cause. There is no hard time limit, and no step is made cheaper by changing what it produces by default: a step that becomes too slow gets a setting that a study can turn on, with the full version as the default (Jon's ruling 3 on stage a8). -The default chain ran on October 15, 2026 (stage b3b, every step): a world of 284 entities, 8 fluents, 2 numeric fluents, and 40 event types with 10 conditional and 24 numeric effects; 10,000 documents with 100,917 sentences; spoken words for 203 content lexemes and 18 function words with five sound embeddings; and 16,457 pairs of test items in 46 sets. The tiny configurations (`data/world/tiny.yaml`, `data/corpus/tiny.yaml`, and `data/wordforms/corpus_tiny.yaml`) run the same chain in under a minute. With `forms` in place of `all`, the word-form step skips the audio, which `render` does not need. +The default chain ran on October 16, 2026 (stage c1: the world and the corpus regenerated, and the corpus rendered with the word forms of stage b3b, whose request did not change; the word forms themselves last ran on October 15, 2026, stage b3b): a world of 284 entities, 8 fluents, 2 numeric fluents, and 40 event types with 10 conditional and 24 numeric effects, and 12 places with 4 place event types; 10,000 documents with 100,918 sentences; spoken words for 203 content lexemes and 18 function words with five sound embeddings; and 16,457 pairs of test items in 46 sets. The tiny configurations (`data/world/tiny.yaml`, `data/corpus/tiny.yaml`, and `data/wordforms/corpus_tiny.yaml`) run the same chain in under a minute. With `forms` in place of `all`, the word-form step skips the audio, which `render` does not need. The taxonomy generator also runs on its own, when only the world's categories and feature vectors are needed (`TAXONOMY.md`), and the world package runs episodes of a world without a corpus (`WORLD.md`, "simulate"). diff --git a/docs/guides/TAXONOMY.md b/docs/guides/TAXONOMY.md index 01d873b..ad50314 100644 --- a/docs/guides/TAXONOMY.md +++ b/docs/guides/TAXONOMY.md @@ -4,7 +4,7 @@ The taxonomy generator builds an artificial world of categories and objects. The This guide covers running the generator, the ideas behind it, the configuration file, the output files, and the Python interface. The design is specified in `docs/specs/TAXONOMY_GENERATOR.md` and `docs/specs/TAXONOMY_RELATIONS.md`, with the changes of the world-and-language refactor (`docs/specs/WORLD_AND_LANGUAGE.md`, "Labels" and "Taxonomy outputs"). -**Status.** Complete: the base generator and scalar dimensions. Since stage a5b of the world-and-language refactor, the generator writes the labels of that specification (`CATEGORY.1.2`, `INSTANCE.1.2.3`, `PROPERTY.4`, `PART.5`, `SCALARDIM.1`), writes the base vector and the derived values apart (`base.csv` and `derived/`), and makes categories, instances, features, and scalars only. The actions (the old CAN features) and the verbs with their relations are made by the world package; see "Where CAN features and verbs went" below, and `WORLD.md` for the world package itself. +**Status.** Complete: the base generator and scalar dimensions. Since stage a5b of the world-and-language refactor, the generator writes the labels of that specification (`CATEGORY.1.2`, `INSTANCE.1.2.3`, `PROPERTY.4`, `PART.5`, `SCALARDIM.1`), writes the base vector and the derived values apart (`base.csv` and `derived/`), and makes categories, instances, features, and scalars only. The actions (the old CAN features) and the verbs with their relations are made by the world package; see "Where CAN features and verbs went" below, and `WORLD.md` for the world package itself. Since stage c1 of phase (c), a run can be a taxonomy of places (`labels.kind: place`): the same generator, writing the place labels, which the world package runs as its second taxonomy; see "Place taxonomies" below. ## Quick start @@ -36,6 +36,8 @@ The example configurations in `data/taxonomy/` are: | `rule_file.yaml` | The default world with rules drawn from a rule file (`rules/example.yaml`) instead of the automatic settings. | | `tiny_relations.yaml` | The tiny world with 1 scalar dimension. The tiny world of the world package (`data/world/tiny.yaml`) builds on it. | | `relations.yaml` | The default world with 2 scalar dimensions. The default world of the world package (`data/world/default.yaml`) builds on it. | +| `places_default.yaml` | The default place taxonomy (`labels.kind: place`): 2 superordinates, depth 2, branching 3, 2 places per kind (12 places), 12 place properties, no place parts, 2 place scalars. The default world runs it as its second taxonomy (stage c1). | +| `places_tiny.yaml` | The tiny place taxonomy: 2 superordinates at depth 1 with 2 places each (4 places), 4 place properties, 1 place scalar. The tiny world runs it. | ## Concepts @@ -55,6 +57,18 @@ Every label is a word a reader can read without a key, a period, and an index. I The old labels (`C1.2`, `I1.2.3`, `IS.4`, `HAS.12`, `SC.1`) appear in the two taxonomy specifications, which keep them as the record of what was built. The full table of labels, including the world's event types and the corpus's documents, is in `docs/specs/WORLD_AND_LANGUAGE.md`, "Labels". +With `labels: {kind: place}` (stage c1 of phase (c), `docs/specs/WORLD_PHASE_C.md`), a run writes the labels of a taxonomy of places in place of the entity labels, and nothing else changes: + +| Object | Entity label | Place label | +| --- | --- | --- | +| Category | `CATEGORY.1.2` | `PLACECATEGORY.1.2` | +| Instance (a place) | `INSTANCE.1.2.3` | `PLACE.1.2.3` | +| Membership feature | `ISA.CATEGORY.1.2` | `ISA.PLACECATEGORY.1.2` | +| Property, part | `PROPERTY.4`, `PART.12` | `PLACEPROPERTY.4`, `PLACEPART.12` | +| Scalar dimension | `SCALARDIM.1` | `PLACESCALARDIM.1` | + +The `type` column of `features.csv` keeps `property`, `part`, and `scalar`, and the configuration keys keep their names (`features.property`, `features.part`), whatever the kind. A place run of the same settings and seed as an entity run is the entity run relabeled, and the rule-set identity differs only because the labels are symbols of the hashed tables. + ### Feature types - **ISA** features record membership. An instance has `ISA.CATEGORY.1 = 1` and `ISA.CATEGORY.1.2 = 1` when it belongs to leaf `CATEGORY.1.2`. ISA features come from the tree alone. No rule reads them. @@ -242,6 +256,7 @@ A configuration file is YAML. Any parameter left out takes its default. An unkno | --- | --- | --- | | `name` | `default` | Names the output folder. | | `seed` | 1 | The master seed. | +| `labels.kind` | `entity` | The labels the run writes: `entity` (`CATEGORY`, `INSTANCE`, `PROPERTY`, `PART`, `SCALARDIM`) or `place` (`PLACECATEGORY`, `PLACE`, `PLACEPROPERTY`, `PLACEPART`, `PLACESCALARDIM`; stage c1). Nothing else changes with the kind. | | `features.property.count`, `features.part.count` | 40, 40 | Number of PROPERTY and PART features. | | `features..proportion_determined` | 0.25 | Proportion of PROPERTY or PART features computed by rules. | | `features..expected_true_free` | 6 | Expected number of true free features per object. Base rate = this number ÷ the number of free features. | @@ -306,6 +321,16 @@ explicit: The rule file's path is read relative to the configuration file's folder. A template applies to PROPERTY outputs (`property`), PART outputs (`part`), or both. The requirement of a one-place event type is given by hand in the world's event file, not in a rule file. +## Place taxonomies + +Since stage c1 of phase (c) (`docs/specs/WORLD_PHASE_C.md`), a world has places as well as entities: the places of a world are the instances of a second taxonomy run, made by this generator with `labels.kind: place`. Kinds of place have the status of kinds of entity: a tree of place categories with defining, characteristic, and undiagnostic place properties and parts, rules for the determined ones, and place scalars, from the same settings as an entity taxonomy. `data/taxonomy/places_default.yaml` is the default world's place taxonomy (12 places in 8 categories, 12 place properties of which a quarter are determined, no place parts, 2 place scalars), and `places_tiny.yaml` the tiny world's (4 places in 2 categories, 4 place properties, 1 place scalar). Both run on their own: + +``` +PYTHONPATH=python python -m semantic_world.taxonomy data/taxonomy/places_default.yaml +``` + +A world configuration names its place taxonomy under `places.taxonomy`, and the world package runs it in memory with the seed of its stream `world:places`, writes the run unmodified to `places/` in the world's folder, and draws the place event types whose requirements read the entities' and the places' features (`WORLD.md`, "Places"). Places are not entities: they have no fluents, nothing that reads the entities' tables sees a place, and their comparison classes have a root of their own, `EVERYPLACE`. + ## Where CAN features and verbs went The taxonomy once made actions (CAN features) and verbs with their relations. Both are made by the world package since stage a5b, from the taxonomy's features, with the same machinery under new names: CAN features are the **one-place event types** `EVENTTYPE1.`, and verbs are the **two-place event types** `EVENTTYPE2.`. The world package adds what the taxonomy never had: fluents, preconditions, effects, and time, so that an event type says what it needs and what it changes. `WORLD.md` is its guide; this section covers the part that came from the taxonomy. A world configuration (`data/world/default.yaml`) names a taxonomy configuration and adds the event types: diff --git a/docs/guides/WORLD.md b/docs/guides/WORLD.md index d96d9b4..0d18b89 100644 --- a/docs/guides/WORLD.md +++ b/docs/guides/WORLD.md @@ -2,9 +2,9 @@ The world package defines a world and runs it. A world is a set of entities with fixed features, a set of fluents that change over time, and a set of event types that say what can happen to the entities and what each event changes. The package generates a world from a taxonomy of the taxonomy generator, writes the world's definition as data files, runs episodes of the world, and writes their histories. The corpus generator reads worlds and writes documents about them. The 3D engine will later read the same definition files. -This guide covers running the package, what a world is, the commands, the output files, the configuration file, the default world's figures, the conformance fixtures, the two-place share, and the Python interface. The design is specified in `docs/specs/WORLD_AND_LANGUAGE.md`, and every design decision is listed in `docs/DECISIONS.md` (WM.1 to WM.34, and the engineering choices WM.E1 and following). +This guide covers running the package, what a world is, the commands, the output files, the configuration file, the default world's figures, the conformance fixtures, the two-place share, and the Python interface. The design is specified in `docs/specs/WORLD_AND_LANGUAGE.md` and, for phase (c), `docs/specs/WORLD_PHASE_C.md`, and every design decision is listed in `docs/DECISIONS.md` (WM.1 to WM.60, and the engineering choices WM.E1 and following). -**Status.** Phase (a) of the world-and-language refactor is complete (stages a1 to a8): Boolean fluents, one-place and two-place event types with requirements, preconditions, and effects, time steps with inertia, the Python runtime, episodes with selection policies, histories, views, world statistics, and the conformance fixtures. Phase (b) has begun: stage b1 adds conditional effects (an effect that applies only when a condition on the binding holds), and stage b2 adds the comparisons of scalars (`GREATER`, `ABOVE`, `BELOW`, with comparison classes and `ENTITY`) and numeric fluents (real-valued fluents with ranges, initial distributions, drift, numeric effects, numeric threshold literals in preconditions and conditions, and comparisons at a time point). Phase (b) is complete with stage b3 (b3a and b3b), which built the corpus's comparatives, numeric adjectives, and conditional causal statements (`CORPUS.md`); stored relations and places come in phase (c), sorts and parts in phase (d). The Rust runtime is not built yet: the fixtures are its tests, waiting. +**Status.** Phase (a) of the world-and-language refactor is complete (stages a1 to a8): Boolean fluents, one-place and two-place event types with requirements, preconditions, and effects, time steps with inertia, the Python runtime, episodes with selection policies, histories, views, world statistics, and the conformance fixtures. Phase (b) has begun: stage b1 adds conditional effects (an effect that applies only when a condition on the binding holds), and stage b2 adds the comparisons of scalars (`GREATER`, `ABOVE`, `BELOW`, with comparison classes and `ENTITY`) and numeric fluents (real-valued fluents with ranges, initial distributions, drift, numeric effects, numeric threshold literals in preconditions and conditions, and comparisons at a time point). Phase (b) is complete with stage b3 (b3a and b3b), which built the corpus's comparatives, numeric adjectives, and conditional causal statements (`CORPUS.md`). Phase (c) (`docs/specs/WORLD_PHASE_C.md`) has begun: stage c1 adds places, a second kind of object from a second taxonomy run, with place event types whose requirements read an entity's and a place's features, their capacities, comparison classes, and statistics; pair fluents (stages c2a and c2b), locations and the dynamics of place event types (c3), and the corpus's statements about them (c4 to c5b) are not built yet. Since stage c1 every real the world draws is rounded to 4 decimals when drawn (Jon's ruling WM.60), so the same seed gives the same world on every machine. Sorts and parts come in phase (d). The Rust runtime is not built yet: the fixtures are its tests, waiting. ## Quick start @@ -18,11 +18,11 @@ The program prints one line, and a note for each event type whose preconditions ``` -wrote runs/world/tiny_seed1: 12 entities, 4 fluents (1 derived), 2 numeric fluents, 4 one-place and 4 two-place event types, 12 constraints; rule set 912f1d371460 +wrote runs/world/tiny_seed1: 12 entities, 4 fluents (1 derived), 2 numeric fluents, 4 one-place and 4 two-place event types, 12 constraints, 4 places and 2 place event types; rule set 9659a419573f ``` -The tiny world builds on the tiny relations taxonomy (6 categories, 12 instances, 8 PROPERTY and 8 PART features, 1 scalar dimension) and adds 4 Boolean fluents, 2 numeric fluents, 4 one-place event types, and 4 two-place event types in a tree of 2 categories. The 12 constraints are the rules of the requirements: one for each one-place event type, and one for each constraint of the two-place event types. 2 of its 10 effects are conditional (`when NOT agent.BOOLFL.4 AND agent.PROPERTY.2 then agent.BOOLFL.2 := 1`, on `EVENTTYPE1.1`, and `when NOT patient.PROPERTY.4 then patient.BOOLFL.2 := 1`, on `EVENTTYPE2.2.2`), and 4 are numeric. The run is small enough to read every file by eye. The whole run, with its 1,000 statistics episodes, takes about 3 seconds. +The tiny world builds on the tiny relations taxonomy (6 categories, 12 instances, 8 PROPERTY and 8 PART features, 1 scalar dimension) and adds 4 Boolean fluents, 2 numeric fluents, 4 one-place event types, 4 two-place event types in a tree of 2 categories, and, since stage c1, the tiny place taxonomy (4 places in 2 categories, with 4 place properties and 1 place scalar) with 2 place event types. The 12 constraints are the rules of the requirements: one for each one-place event type, and one for each constraint of the two-place event types; the place event types' constraints are counted apart. 2 of its 10 effects are conditional (`when NOT agent.BOOLFL.4 AND agent.PROPERTY.2 then agent.BOOLFL.2 := 1`, on `EVENTTYPE1.1`, and `when NOT patient.PROPERTY.4 then patient.BOOLFL.2 := 1`, on `EVENTTYPE2.2.2`), and 4 are numeric. The run is small enough to read every file by eye. The whole run, with its 1,000 statistics episodes, takes about 3 seconds. Two options change a run without editing the configuration: @@ -34,7 +34,7 @@ The example configurations in `data/world/` are: | File | What it makes | | --- | --- | | `tiny.yaml` | The tiny world above. | -| `default.yaml` | The default world: the default relations taxonomy (4 superordinates, depth 3, 284 instances, 40 PROPERTY and 40 PART features, 2 scalar dimensions) with 8 Boolean fluents, 2 numeric fluents, 20 one-place event types, and 20 two-place event types in 5 categories. Every parameter appears with its value, so this file is also the reference for defaults; five settings of the event types depart from the code's defaults (see "The two-place share"). | +| `default.yaml` | The default world: the default relations taxonomy (4 superordinates, depth 3, 284 instances, 40 PROPERTY and 40 PART features, 2 scalar dimensions) with 8 Boolean fluents, 2 numeric fluents, 20 one-place event types, 20 two-place event types in 5 categories, and the default place taxonomy (12 places in 8 categories) with 4 place event types. Every parameter appears with its value, so this file is also the reference for defaults; five settings of the event types depart from the code's defaults (see "The two-place share"). | | `tiny_chain.yaml` | The tiny world with the event file `events/chain_example.yaml`, which defines two event types by hand so that they form a chain: catch, then eat. | ## Concepts @@ -45,7 +45,7 @@ A world definition has three parts: the entities' base facts, the feature rules, - **Entities** are the instances of the embedded taxonomy, `INSTANCE.1.3.2.5`. Each has a leaf category, free PROPERTY and PART features, and scalar values. These are its **base static facts**: stored, and fixed for the life of the world. Its **derived static facts**, the determined PROPERTY and PART features, are computed from them by the taxonomy's rules and never stored as inputs. - **Fluents** are Boolean facts that change over time, `BOOLFL.1` to `BOOLFL.n`. A **base fluent** is stored, starts at an initial value for each entity, and changes when an event's effect writes it. A **derived fluent** is computed by a rule that reads at least one fluent, and may read static features too, and, since stage b2, numeric threshold literals (`NUMFL.1 > 0.75`); it is recomputed at every time point and never written. So every fact is static or fluent, and base or derived. -- **Numeric fluents** (stage b2) are real-valued facts that change over time, `NUMFL.1` to `NUMFL.n`, as a need rises and is met. A numeric fluent is a base fluent with a **range** (`[0, 1]` by default) to which its values are clamped, an **initial distribution** from which each entity's starting value is drawn when the world is generated (`uniform` over the range, `fixed` at a value, or `beta` with a mean and a concentration), and a **drift**: a change at every step, by itself, which half of the numeric fluents get by default. A **numeric effect** adds a constant to a numeric fluent of a participant or subtracts one, `patient.NUMFL.1 += 0.3`. A **numeric threshold literal** compares a numeric fluent with a point of its range, `agent.NUMFL.1 > 0.75` or `agent.NUMFL.1 <= 0.75`, and can stand in a precondition, in a condition, or in a derived fluent's rule. Within a step each numeric value becomes `clamp(clamp(v + e) + d)`: the one effect that wrote it, then the drift, each clamped to the range, in float64. +- **Numeric fluents** (stage b2) are real-valued facts that change over time, `NUMFL.1` to `NUMFL.n`, as a need rises and is met. A numeric fluent is a base fluent with a **range** (`[0, 1]` by default) to which its values are clamped, an **initial distribution** from which each entity's starting value is drawn when the world is generated (`uniform` over the range, `fixed` at a value, or `beta` with a mean and a concentration), and a **drift**: a change at every step, by itself, which half of the numeric fluents get by default. A **numeric effect** adds a constant to a numeric fluent of a participant or subtracts one, `patient.NUMFL.1 += 0.3`. Every real the world draws and stores (a drift, an initial value, a numeric effect's constant) is rounded to 4 decimals when drawn, as the scalar thresholds and comparison margins are, so that the same seed gives the same world on an ARM and an x86 machine (stage c1, Jon's ruling WM.60); a fixed initial value is the configuration's own. A **numeric threshold literal** compares a numeric fluent with a point of its range, `agent.NUMFL.1 > 0.75` or `agent.NUMFL.1 <= 0.75`, and can stand in a precondition, in a condition, or in a derived fluent's rule. Within a step each numeric value becomes `clamp(clamp(v + e) + d)`: the one effect that wrote it, then the drift, each clamped to the range, in float64. - **Comparisons** (stage b2) are judged on demand, never stored: `GREATER(SCALARDIM.1, x, y)` (strict: two equal values give false both ways), `ABOVE(SCALARDIM.1, x, K, z)` and `BELOW` (at least `z` standard deviations above, or below, the mean of the comparison class `K`, a category or `ENTITY`, the symbol for every entity), with entities or category means (`MEAN(CATEGORY.1.4)`) as arguments. On a numeric fluent, at a time point, `ABOVE(NUMFL.1, x, RANGE, q)` says that the value is greater than the fraction `q` of the way through the range and `BELOW` that it is at most that point (`ABOVE` is strict and `BELOW` inclusive, so the two are the world's numeric literal `> t` and its negation `<= t`; Jon's ruling WM.35, October 15, 2026), and `INCREASE` and `DECREASE` that it rose or fell over a step. `derived/comparison_classes.csv` holds every category's count, mean, and standard deviation on every scalar. - **Event types** are the kinds of thing that can happen. A **one-place event type** (`EVENTTYPE1.4`) has one role, the agent. A **two-place event type** (`EVENTTYPE2.1.2`) has an agent and a patient; two-place event types form a tree, and a category of the tree (`EVENTTYPE2.1`) is a more general kind of event, as "hunt" is to "chase". A **binding** assigns entities to the roles, never the same entity twice. - **The requirement** of an event type is a rule over the static facts of a binding. When it holds, the entities are **able** to take part: the owl can eat the mouse. Capacities ("can") are requirements. A one-place requirement is a rule over the agent's features, sampled as the taxonomy's rules are. A two-place requirement is the conjunction of constraints over both roles, which can compare the two entities' scalars (the agent is bigger than the patient) or match their features (barbers cut hair). @@ -53,6 +53,7 @@ A world definition has three parts: the entities' base facts, the feature rules, - **The effects** set base fluents of the participants, `patient.BOOLFL.3 := 1`, or, since stage b2, add a constant to a numeric fluent or subtract one, `patient.NUMFL.1 += 0.3` (a share of the effects, `event_types.effects.numeric_share`, 0.3 by default). An event type never has two effects that write the same fluent of the same role, and never a Boolean effect that sets a fluent to the value its own precondition requires. Since stage b1 an effect may carry a **condition**, `when patient.PROPERTY.4 AND patient.BOOLFL.2 then patient.BOOLFL.3 := 1`: a conjunction of one or two literals over the binding, each a static feature (PROPERTY or PART, free or determined), a Boolean fluent (base or derived), or, since stage b2, a numeric threshold (`agent.NUMFL.1 <= 0.5`) of one role. The condition is judged in the state at the start of the step, before any effect of the step; when it is false, the effect does nothing. A condition never bears on whether the event is legal. A share of the effects (`event_types.effects.conditional_share`, 0.2 by default) gets a condition when the world is generated; with the share at 0 the world is the unconditional world, byte for byte. - **Time.** An episode has time points `TIME.1`, `TIME.2`, and so on. Step k takes the state at `TIME.k` to the state at `TIME.k+1`. Every event of a step must be legal at `TIME.k`, and the events of one step must not interfere: no two write the same fluent of the same entity (every effect counts as a write, whatever its condition; a numeric effect writes its numeric fluent), and none writes a fluent that another's precondition, or a condition of another's effect, reads (a condition on a derived fluent reads every base fluent the derived fluent depends on, numeric fluents read through thresholds included; a condition on a static feature reads nothing an event can write). The effects whose conditions hold are applied, every other base fluent keeps its value (inertia), every numeric fluent then moves by its drift, and the derived fluents are recomputed. Applying a step's events together gives the same state as applying them one at a time in any order, the drift added once after all of them. - **Episodes and histories.** An episode runs the world over a few participants for a few steps. Participants are drawn around a seed entity, weighted by thematic relatedness and taxonomic similarity. At each step a selection policy draws events among the legal ones; by default (`uniform_event`) it draws uniformly among all legal events, so an event type that is legal for many bindings happens more often, as in a world where some things simply happen more. An episode ends after its steps, or earlier when nothing is legal (quiescence). A **history** records the episode: its participants, its initial state (the numeric values among it), and each step's events with the changes they made, every numeric change with its cause (an event, or the drift). The corpus calls an episode a scene. +- **Places** (stage c1 of phase (c)) are a second kind of object, the world's grain of interaction: `PLACE.1.2.1`, the instances of a second taxonomy run with labels of its own (`PLACECATEGORY`, `PLACEPROPERTY`, `PLACEPART`, `PLACESCALARDIM`), which the world runs in memory on the configuration `places.taxonomy` names. Places are not entities: they have place features and place scalars, no fluents, and no location, and nothing that reads the entities' tables sees a place. A **place event type** (`PLACEEVENTTYPE.2`) has an entity as its agent and a place as its patient; its requirement is a conjunction of one or two constraints over the agent's features and the place's features (the same families as the two-place constraints, the comparison family comparing an entity scalar with a place scalar), drawn again when it holds for no binding or for every binding of the world. Its capacities are `CAN.PLACEEVENTTYPE.2` (the entity is able to be the agent at some possible place) and `CANBE.PLACEEVENTTYPE.2` (the place is able to be the patient of some possible entity). In this stage a place event type has no precondition and no effects, and no episode includes one; stage c3 makes them moves and acts at a place, with location as a pair fluent toward places. `EVERYPLACE` is the comparison class of every place, as `ENTITY` is of every entity, and the runtime's comparisons accept places and means of categories of places on place scalars, never mixed with entity scalars. - **Views** choose which columns of the base facts and the derived values a model sees. A view is an experiment setting, never part of the world. The event types attach their preconditions and effects to the tree as well: an event-type feature can carry a precondition literal or an effect, and every event type with that feature inherits it, so a branch of the tree shares what it needs and what it does, as it shares its constraints. @@ -71,12 +72,16 @@ Every label is a word a reader can read without a key, a period, and an index. T | Event-type feature | `EVENTFEAT.` | `EVENTFEAT.5` | | Constraint of an event-type feature, or an event type's own | `CONSTRAINT.` | `CONSTRAINT.EVENTFEAT.5` | | Capacity, as agent or as patient (derived) | `CAN.`, `CANBE.` | `CAN.EVENTTYPE1.4`, `CANBE.EVENTTYPE2.1.2` | +| Category of places, place, membership (stage c1) | `PLACECATEGORY.`, `PLACE..`, `ISA.PLACECATEGORY.` | `PLACECATEGORY.1.2`, `PLACE.1.2.1` | +| Place property, place part, place scalar | `PLACEPROPERTY.`, `PLACEPART.`, `PLACESCALARDIM.` | `PLACEPROPERTY.3` | +| Place event type, and its capacities | `PLACEEVENTTYPE.`, `CAN.PLACEEVENTTYPE.`, `CANBE.PLACEEVENTTYPE.` | `PLACEEVENTTYPE.2` | +| Every place, as a comparison class | `EVERYPLACE` | `ABOVE(PLACESCALARDIM.1, MEAN(PLACECATEGORY.1), EVERYPLACE, 1.0)` | | Role prefix in a rule | `agent.`, `patient.` | `patient.PROPERTY.7` | | Episode (a scene), event, time point | `SCENE.`, `SCENE..EVENTINSTANCE.`, `TIME.` | `SCENE.8.EVENTINSTANCE.5` | ### Where the world comes from -`define` runs the taxonomy generator first, in memory, and writes the run to `taxonomy/` inside the world's folder, byte-identical to the taxonomy run alone with the same configuration and seed. The taxonomy supplies the entities' static facts and the feature rules. The world package then draws, from streams of its own, the one-place requirements (`world:requirements`), the event-type tree and its constraints (`world:event_tree`, `world:constraints`, `world:pairs`), the fluents and their rules (`world:fluents`), the initial values (`world:initial`), the preconditions (`world:preconditions`), the effects (`world:effects`), the conditions (`world:conditions`), and, since stage b2, the numeric fluents' drift (`world:numeric`), their initial values (`world:numeric_initial`), and the numeric effects, precondition literals, and condition literals (`world:numeric_effects`, `world:numeric_preconditions`, `world:numeric_conditions`), and runs the statistics episodes (`world:stats`). Separate streams mean that changing the fluent, precondition, or effect settings never changes the taxonomy, the requirements, the capacities, or the relation statistics; and that with `numeric_fluents.count: 0` a world is the world of stage b1, byte for byte, and with the numeric shares at 0 its Boolean dynamics are those of the world without numeric fluents. `TAXONOMY.md`, "Where CAN features and verbs went", describes the requirements and the relation statistics in detail. +`define` runs the taxonomy generator first, in memory, and writes the run to `taxonomy/` inside the world's folder, byte-identical to the taxonomy run alone with the same configuration and seed. The taxonomy supplies the entities' static facts and the feature rules. The world package then draws, from streams of its own, the one-place requirements (`world:requirements`), the event-type tree and its constraints (`world:event_tree`, `world:constraints`, `world:pairs`), the fluents and their rules (`world:fluents`), the initial values (`world:initial`), the preconditions (`world:preconditions`), the effects (`world:effects`), the conditions (`world:conditions`), and, since stage b2, the numeric fluents' drift (`world:numeric`), their initial values (`world:numeric_initial`), and the numeric effects, precondition literals, and condition literals (`world:numeric_effects`, `world:numeric_preconditions`, `world:numeric_conditions`), and runs the statistics episodes (`world:stats`). Since stage c1 the place taxonomy runs with the seed of `world:places` (so it never repeats the entities' draws) and is written to `places/`, and the place event types' requirements, and the samples of their capacities' local test, come from `world:place_event_types`; both streams were created after every earlier one. Separate streams mean that changing the fluent, precondition, or effect settings never changes the taxonomy, the requirements, the capacities, or the relation statistics; that with `numeric_fluents.count: 0` a world is the world of stage b1, byte for byte, and with the numeric shares at 0 its Boolean dynamics are those of the world without numeric fluents; and that with `places: null` a world is the world before places, byte for byte. `TAXONOMY.md`, "Where CAN features and verbs went", describes the requirements and the relation statistics in detail. ### Rules as matrices, and the rule-set identity @@ -94,7 +99,7 @@ Every command runs from the root of the repository. With the repository's virtua PYTHONPATH=python python -m semantic_world.world define data/world/tiny.yaml [--seed N] [--out DIR] ``` -Generates a world and writes its folder (see "Reading the outputs"). The run embeds the taxonomy run, draws the fluents (Boolean and numeric) and the event types, builds the definition, runs the agreement test and the checks of the dynamics (no requirement reads a fluent, no effect writes a derived fluent, no contradiction, every precondition literal achievable, every numeric threshold inside its range), and runs 1,000 statistics episodes. An event type that is never legal in the statistics episodes has its own precondition literals drawn again, up to a few times; the command prints a note for each redraw, and a warning for each event type that is still never legal. The default world takes about 6 seconds (6.3 s on October 15, 2026, stage b3b; 6.1 s in stage b3a, 6.7 s in stage b2, 7.6 s in stage b1), of which the static side is about a second. Every stage records this time, measured with nothing else running, and flags a growth of more than half in its proposal file; there is no hard time limit, and no step is made cheaper by changing what it produces by default (Jon's ruling 3 on stage a8). +Generates a world and writes its folder (see "Reading the outputs"). The run embeds the taxonomy run and, since stage c1, the place taxonomy run, draws the fluents (Boolean and numeric), the event types, and the place event types, builds the definition, runs the agreement test (over every entity, every ordered pair of entities, every place, and every entity paired with every place) and the checks of the dynamics (no requirement reads a fluent, no effect writes a derived fluent, no contradiction, every precondition literal achievable, every numeric threshold inside its range), and runs 1,000 statistics episodes. An event type that is never legal in the statistics episodes has its own precondition literals drawn again, up to a few times; the command prints a note for each redraw, and a warning for each event type that is still never legal. The default world takes about 6 seconds (6.4 s on October 16, 2026, stage c1; 6.3 s in stage b3b, 6.1 s in stage b3a, 6.7 s in stage b2, 7.6 s in stage b1), of which the static side, the place taxonomy and the place event types included, is about a second. Every stage records this time, measured with nothing else running, and flags a growth of more than half in its proposal file; there is no hard time limit, and no step is made cheaper by changing what it produces by default (Jon's ruling 3 on stage a8). ### `simulate` @@ -124,6 +129,12 @@ Writes one table for a model, by default to `RUN/views/.csv`, with a sid `--include` takes label prefixes. A prefix that matches columns of the preset narrows the view to them; a prefix that matches none adds every available column with that prefix (ISA, base facts, derived static features, capacities, initial fluents, Boolean and numeric). For example, `--preset base --include PROPERTY` gives the free PROPERTY features alone, `--preset base --include BOOLFL` adds the initial Boolean fluents, and `--include NUMFL` the initial numeric values. +``` +PYTHONPATH=python python -m semantic_world.world view runs/world/tiny_seed1 --places [--preset static] [--include PLACEPROPERTY] [--out FILE] +``` + +With `--places` (stage c1) the table is of the places, by default `RUN/views/places_.csv`: `base` gives `label`, `leaf`, the free place features, and the place scalars; `static` adds the determined place features and the place capacities `CANBE.PLACEEVENTTYPE.`; `classic` adds the ISA columns of the place tree. The sidecar records `places: true`. The entities' views never show a place column. + ### `check-fixtures` and `make-fixtures` ``` @@ -154,8 +165,10 @@ A run writes one folder: | --- | --- | | `config.yaml` | The resolved world configuration, with the resolved taxonomy configuration under `taxonomy.resolved`, every stream seed, the git commit (flagged when the working tree had uncommitted changes), and the package version. Running `define` on a run's `config.yaml` reproduces the run exactly. | | `taxonomy/` | The embedded taxonomy run, in the taxonomy generator's own format (`TAXONOMY.md`, "Reading the outputs"). | -| `definition.json` | The definition: every symbol, every literal, every rule in Boolean form, the matrix form, and every event type. | -| `entities.csv` | One row per entity: `label`, `leaf`, the free PROPERTY and PART features, the scalars (full precision), the initial value of every base Boolean fluent, and the initial value of every numeric fluent (full precision). | +| `places/` | The embedded place taxonomy run (stage c1), in the same format, byte-identical to the place taxonomy run alone with the same configuration and seed; absent with `places: null`. | +| `definition.json` | The definition: every symbol, every literal, every rule in Boolean form, the matrix form, and every event type, the place event types included. | +| `entities.csv` | One row per entity: `label`, `leaf`, the free PROPERTY and PART features, the scalars (full precision), the initial value of every base Boolean fluent, and the initial value of every numeric fluent (4 decimals since stage c1). | +| `places.csv` | One row per place (stage c1): `label`, `leaf`, the free place features, and the place scalars (full precision); absent with `places: null`. | | `derived/` | The derived values, each file tagged in `manifest.yaml` with the rule-set identity that produced it. | | `world_stats.yaml` | The world's statistics (below). | | `views/` | The views written by `view`, each with its sidecar. | @@ -167,23 +180,24 @@ The language-neutral part of the definition, with no code in it. Its top-level k - `version`: the format's version, 1; - `rule_set_id`: the rule-set identity; -- `symbols`: one entry per feature, scalar, fluent, numeric fluent, event type, and event-type category: `label`, `kind` (`property`, `part`, `scalar`, `fluent`, `numeric_fluent`, `event_type`, `event_type_category`), `derived`, `fluent`, `arity`, for a base fluent its `initial_rate`, and for a numeric fluent its `range`, its `initial` distribution, and its `drift`; +- `symbols`: one entry per feature, scalar, fluent, numeric fluent, event type, and event-type category: `label`, `kind` (`property`, `part`, `scalar`, `fluent`, `numeric_fluent`, `event_type`, `event_type_category`, and, with places, `place_property`, `place_part`, `place_scalar`, `place_event_type`), `derived`, `fluent`, `arity`, for a base fluent its `initial_rate`, and for a numeric fluent its `range`, its `initial` distribution, and its `drift`; - `literals`: everything the rules read, each with its `index`, its `key`, its `kind` (`feature`, `threshold`, `fluent`, `numeric_threshold`, `comparison`, `constraint`), its `role` (null for the entity itself, `agent`, `patient`, or `binding`), and the fields of its kind; -- `rules`: every rule's `output`, `scope` (`entity` or `binding`), `family`, `inputs` (literal indices), `truth_table`, and `expression`. A derived feature and a derived fluent are entity rules; a constraint and a requirement are binding rules; +- `rules`: every rule's `output`, `scope` (`entity` or `binding`; with places also `place`, a determined place feature, and `place_binding`, a place event type's constraint or requirement over an entity and a place), `family`, `inputs` (literal indices), `truth_table`, and `expression`. A derived feature and a derived fluent are entity rules; a constraint and a requirement are binding rules; - `layers`: the matrix form, written sparsely: for each layer its `terms` (the literals of one term of the rule's minimal DNF, whether each is complemented, and the threshold) and its `outputs`; -- `event_types`: one entry per event type and category: `label`, `kind`, `arity`, `roles`, `parent`, `level`, `features`, `explicit`, the `requirement` (its constraints, the rule that is the requirement, and the expression), the `precondition` (its literals, each a `role`, `fluent`, and `value`, or, for a numeric threshold literal, a `role`, `fluent`, `operator` (`>` or `<=`), and `threshold`; and the expression), and the `effects` (each a `role`, `fluent`, `value`, and expression, or, for a numeric effect, a `role`, `fluent`, `operator` (`+=` or `-=`), `amount`, and expression; and, for a conditional effect, a `condition` with its literals, each a `role`, `kind` (`feature`, `fluent`, or `numeric`), `symbol`, and `value` or `operator` and `threshold`, and its expression; an unconditional effect has no `condition` key). +- `event_types`: one entry per event type and category, and per place event type (kind `place_event_type`, arity 2, its patient a place, with an empty precondition and no effects in stage c1): `label`, `kind`, `arity`, `roles`, `parent`, `level`, `features`, `explicit`, the `requirement` (its constraints, the rule that is the requirement, and the expression), the `precondition` (its literals, each a `role`, `fluent`, and `value`, or, for a numeric threshold literal, a `role`, `fluent`, `operator` (`>` or `<=`), and `threshold`; and the expression), and the `effects` (each a `role`, `fluent`, `value`, and expression, or, for a numeric effect, a `role`, `fluent`, `operator` (`+=` or `-=`), `amount`, and expression; and, for a conditional effect, a `condition` with its literals, each a `role`, `kind` (`feature`, `fluent`, or `numeric`), `symbol`, and `value` or `operator` and `threshold`, and its expression; an unconditional effect has no `condition` key). -Floating-point numbers are written with 17 significant digits, so both runtimes read the same values. `tests/fixtures/world/README.md` describes every field, and how the layers are evaluated. +Floating-point numbers are written with 17 significant digits, so both runtimes read the same values. With `places: null`, no place symbol, literal, rule, layer, or event type is written, so the definition and its identity are those of the world before places. `tests/fixtures/world/README.md` describes every field, and how the layers are evaluated. ### `derived/` | File | Contents | | --- | --- | | `static_features.csv` | One row per entity: every derived static feature. | -| `capacities.csv` | One row per entity: `CAN.EVENTTYPE1.` for every one-place event type, then `CAN.` and `CANBE.` for every two-place event type (whether some possible partner would make the requirement hold), an `approximate` flag, and the `ACTUAL_` columns (whether the entity has a partner among the world's entities). | +| `capacities.csv` | One row per entity: `CAN.EVENTTYPE1.` for every one-place event type, then `CAN.` and `CANBE.` for every two-place event type (whether some possible partner would make the requirement hold), then `CAN.PLACEEVENTTYPE.` for every place event type (whether some possible place would, stage c1), an `approximate` flag, and the `ACTUAL_` columns (whether the entity has a partner among the world's entities, or a place among the world's places). | +| `place_capacities.csv` | One row per place (stage c1): `CANBE.PLACEEVENTTYPE.` for every place event type (whether some possible entity would make the requirement hold), an `approximate` flag, and the `ACTUAL_CANBE.` columns (whether the place has an entity among the world's entities). | | `capacity_roles.csv` | For each category and one-place event type: whether the capacity is `fixed_1`, `fixed_0`, or `free` for the category's members, and whether the `exact` or the `local` test decided. The corpus's `NEC` statements rest on it. | -| `comparison_classes.csv` | For `ENTITY` (every entity) and each category of the tree, and each scalar: the number of `instances` below the category, their `mean`, and their standard deviation `sd` (the population standard deviation), the comparison classes of `ABOVE` and `BELOW` (stage b2). The corpus reads these figures from the runtime since stage b3a. | -| `relation_proportions.csv`, `relation_pairs.csv`, `event_type_stats.csv`, `thematic.csv` | The relation statistics of the two-place event types: for each event type and pair of categories at the same level, the share of pairs related; sampled true and false pairs; one row of statistics per event type (the proportion of pairs related, the agents and patients with a partner, the constraints and their families, the leaf-pair density); and, for every pair of leaves, the thematic relatedness score beside the taxonomic similarity. `TAXONOMY.md` has an example row. | +| `comparison_classes.csv` | For `ENTITY` (every entity) and each category of the tree, and each scalar: the number of `instances` below the category, their `mean`, and their standard deviation `sd` (the population standard deviation), the comparison classes of `ABOVE` and `BELOW` (stage b2); then, with places, `EVERYPLACE` and each category of places on each place scalar (stage c1). The corpus reads these figures from the runtime since stage b3a. | +| `relation_proportions.csv`, `relation_pairs.csv`, `event_type_stats.csv`, `thematic.csv` | The relation statistics of the two-place event types: for each event type and pair of categories at the same level, the share of pairs related; sampled true and false pairs; one row of statistics per event type (the proportion of pairs related, the agents and patients with a partner, the constraints and their families, the leaf-pair density); and, for every pair of leaves, the thematic relatedness score beside the taxonomic similarity. `TAXONOMY.md` has an example row. Since stage c1 the proportions and the event-type statistics gain the place event types after the two-place ones: for each level of the entity tree, every entity category at that level against every place category at the same level (or at the place tree's last level when the entity tree is deeper), with the share of their bindings for which the requirement holds; and one row per place event type with the share of all bindings, the entities and places with a partner, the constraints and families, NaN for the symmetric and within-leaf shares, and the binding density under `leaf_pair_density`. Thematic relatedness stays a relation between entity leaves. | A derived file is read through the package, which refuses a file whose identity differs from the rule set in use: @@ -205,6 +219,7 @@ Nothing in `derived/` is an input to anything, and nothing there is edited by ha - `absorbing_fluents`: the base Boolean fluents that some effect sets to one value and no effect sets back. - `numeric_fluents` (stage b2): the count, how many drift, each fluent's `range`, `initial` distribution, and `drift`; the numeric `effects` by operator (`add`, `subtract`), by kind of event type, by role, and from event-type features; the numeric `precondition_literals` by side (`above` for `>`, `at_or_below` for `<=`), by kind of event type, by role, and from features; the numeric `condition_literals` by side and role; and `rules_reading_a_threshold`, the derived Boolean fluents whose rule reads a numeric threshold. - `relations`: the event-type features, categories, event types, and constraints by family, and the density report. +- `places` (stage c1): the places and their categories, the place features by kind (`place_property` and `place_part`, free and determined, and `place_scalar`), the place event types, their constraints by family, each requirement's `density` over the world's bindings (an entity and a place) with its `tries`, the share of approximate capacities, and warnings; null with `places: null`. - `episodes`: the 1,000 statistics episodes of the default policy with the corpus's default scene settings, on the stream `world:stats`: for each event type the share of steps at which it had a legal binding (`legal_share`) and at which it occurred (`occurrence_share`), the mean number of Boolean changes per event, the mean number of numeric changes per event (`numeric_changes_per_event`) and of drift changes per step (`drift_changes_per_step`), the share of quiescent episodes, the two-place share (`two_place_share`: the share of two-place events among all events), the precondition redraws, and the event types that were never legal. ### Histories @@ -274,6 +289,12 @@ A configuration file is YAML. Any parameter left out takes its default, unknown | `event_types.effects.numeric_share` | 0.3 | The share of effects that are numeric (`+=` or `-=`, stage b2), when the world has numeric fluents: a Boolean effect is drawn, and then replaced by a numeric one at this share. | | `event_types.effects.numeric_amounts` | `[0.1, 0.5]` | The interval a numeric effect's constant is drawn from, uniformly. | | `event_types.event_file` | null | A file of explicit event types, read relative to the configuration file's folder. | +| `places` | `{taxonomy: {config: data/taxonomy/places_default.yaml, seed: null}, habitat_strength: 1.0}` | The places (stage c1): the place taxonomy configuration (which must set `labels.kind: place`) and its seed (null: the seed of the stream `world:places`), and the habitat strength that stage c3 uses (0: initial places uniform). `places: null` turns places off, and then the world is the world before places, byte for byte. | +| `place_event_types.count` | 4 | The number of place event types, `PLACEEVENTTYPE.1` and so on, a flat set. | +| `place_event_types.move_share` | 0.5 | The share that are moves (stage c3); read and validated in stage c1. | +| `place_event_types.constraints` | `{1: 0.6, 2: 0.4}` | Weights over the number of constraints of a requirement. | +| `place_event_types.constraint_families` | `{agent: 1, patient: 1, cross: 1, key_lock: 1}` | Weights over the families of a constraint; `comparison` may be given a weight, and then compares an entity scalar with a place scalar. The key-lock and comparison settings are those of `event_types.binary`. | +| `place_event_types.rules` | `{}` | Overrides of the taxonomy's `rules` settings for the place constraints. With `places: null` the block must be absent or null. | Every precondition literal must be achievable (a Boolean literal's value is some entity's initial value, or some effect produces it; a numeric literal `> t` holds of some entity's initial value, or its fluent drifts upward, or some effect adds to it, and `<= t` the other way round) and consistent (a precondition never holds two literals on one fluent of one role); the generator redraws or drops a literal that is not, and counts it under `fix_ups`. Every condition must be satisfiable with its event type's requirement and precondition: some able binding satisfies its static literals (for every event type that inherits the effect), each fluent literal is achievable, names no fluent of a role that the precondition names, and never names the Boolean fluent the effect sets (a numeric condition on the effect's own numeric fluent, "when low, add", is a real condition and allowed); the conditions are drawn from a stream of their own (`world:conditions`), so changing their settings changes nothing else, and a condition that breaks a rule is drawn again (`fix_ups.conditions_redrawn`). @@ -298,7 +319,7 @@ An entry that breaks a rule of the world (an effect on a derived fluent, a requi ## The default world -The figures of `data/world/default.yaml` with seed 1, from a run of October 15, 2026 (stage b3a); `define` took 6 seconds (6.1 s; 6.7 s in stage b2, 7.6 s in stage b1, 7.8 s in stage a8). +The figures of `data/world/default.yaml` with seed 1, from a run of October 16, 2026 (stage c1); `define` took 6 seconds (6.4 s; 6.3 s in stage b3b, 6.1 s in stage b3a, 6.7 s in stage b2, 7.6 s in stage b1, 7.8 s in stage a8). | What | Figure | | --- | --- | @@ -307,7 +328,7 @@ The figures of `data/world/default.yaml` with seed 1, from a run of October 15, | Event types | 20 one-place; 20 two-place in 5 categories of 4, with 12 event-type features and 12 constraints (4 patient, 3 agent, 2 cross, 2 comparison, 1 key-lock) | | Precondition literals | 62: 18 on one-place and 44 on two-place event types; 36 on the agent, 26 on the patient; 5 inherited from event-type features | | Effects | 81: 22 on one-place and 59 on two-place event types; 51 on the agent, 30 on the patient; 5 inherited from event-type features | -| Conditional effects | 10 of the 81 (3 on one-place and 7 on two-place event types): 4 own effects, and 1 effect of an event-type feature that 6 event types inherit (`when NOT patient.PART.1 AND agent.NUMFL.1 > 0.75 then patient.NUMFL.1 += 0.3640982468028209`); 16 condition literals, 9 on static features, 1 on Boolean fluents, and 6 numeric thresholds (stage b2), 10 on the agent and 6 on the patient; the share of the drawn effects that got a condition is 0.10, and the share of all effects 0.12 (25 of 81 in stage b1: the numeric kind of condition literal changes the draw, and an effect of an event-type feature that was conditional then is numeric now) | +| Conditional effects | 10 of the 81 (3 on one-place and 7 on two-place event types): 4 own effects, and 1 effect of an event-type feature that 6 event types inherit (`when NOT patient.PART.1 AND agent.NUMFL.1 > 0.75 then patient.NUMFL.1 += 0.3641`); 16 condition literals, 9 on static features, 1 on Boolean fluents, and 6 numeric thresholds (stage b2), 10 on the agent and 6 on the patient; the share of the drawn effects that got a condition is 0.10, and the share of all effects 0.12 (25 of 81 in stage b1: the numeric kind of condition literal changes the draw, and an effect of an event-type feature that was conditional then is numeric now) | | Fix-ups | 9 effects redrawn and 6 dropped as changing nothing; 5 literals redrawn and 17 dropped as unachievable; 3 conditions redrawn, 0 dropped | | Enabling graph | 296 edges; longest chain 1 | | Absorbing fluents | `BOOLFL.3` | @@ -316,8 +337,9 @@ The figures of `data/world/default.yaml` with seed 1, from a run of October 15, | Two-place requirements | hold for 17.1% of the ordered pairs on average (0.4% to 96.5% by event type) | | Statistics episodes | 1,000 episodes, 5,490 steps, 8,337 events; 0.50 Boolean changes per event (0.50 in stage b1 and 0.56 in stage a8: an effect whose condition fails changes nothing, and a numeric effect changes no Boolean fluent), 0.41 numeric changes per event, and 8.43 drift changes per step; quiescent share 0.000; never legal: none; precondition redraws: none | | Two-place share | 0.334 of the events of the statistics episodes (0.347 in stage b2, 0.313 in stage b1, 0.318 in stage a8) | +| Places (stage c1) | 12 places in 8 categories, with 12 place properties (3 determined), 0 place parts, and 2 place scalars; 4 place event types with 5 constraints (`agent` (2), `cross` (1), `key_lock` (2)), whose requirements hold for 53.4% of the bindings of an entity and a place on average (1.8% to 82.0% by place event type); the share of approximate place capacities is 0.00 | -Every category of event types keeps a word in the corpus: a category whose base relation holds for every pair, or for none, would get none, because it says nothing. The world's rule set is `f47334502194...` (`32356b8c6be9...` with `numeric_fluents: {count: 0}`, the world of stage b1: the numeric fluents and what reads and writes them are the only difference; `e529bce9af32...` with `conditional_share: 0` beside the numeric fluents, the world without conditions). +Every category of event types keeps a word in the corpus: a category whose base relation holds for every pair, or for none, would get none, because it says nothing. The world's rule set is `7677556dac8f...` (`cc1e27fac819...` with `places: null`, the world before places; `4522d2471f88...` with `numeric_fluents: {count: 0}`, the world of stage b1 with places: the numeric fluents and what reads and writes them are the only difference; `e9e38a67d89c...` with `conditional_share: 0` beside the numeric fluents, the world without conditions). ## The two-place share @@ -396,7 +418,7 @@ result = define(config) result.write("runs/world/tiny_seed7") ``` -`result` holds everything the run produced: `result.taxonomy` (the embedded taxonomy run), `result.statics` (the static side: the one-place requirements, the event-type tree, the relations, the capacities, and the relation statistics), `result.fluents`, `result.numeric` (the numeric fluents, with their ranges, distributions, drifts, and initial values), `result.event_types` (every event type with its requirement, precondition, and effects), `result.definition` (the definition, with `result.rule_set_id`), `result.stats` (the contents of `world_stats.yaml`), and `result.warnings`. `config_from_mapping` in the same module builds a configuration from a dictionary, for sweeping a setting in code. +`result` holds everything the run produced: `result.taxonomy` (the embedded taxonomy run), `result.statics` (the static side: the one-place requirements, the event-type tree, the relations, the capacities, and the relation statistics), `result.fluents`, `result.numeric` (the numeric fluents, with their ranges, distributions, drifts, and initial values), `result.event_types` (every event type with its requirement, precondition, and effects), `result.places` (the place taxonomy run, the place event types with their requirements over every binding, and their capacities; None with `places: null`), `result.definition` (the definition, with `result.rule_set_id`), `result.stats` (the contents of `world_stats.yaml`), and `result.warnings`. `config_from_mapping` in the same module builds a configuration from a dictionary, for sweeping a setting in code. The runtime works on a loaded definition: @@ -409,7 +431,7 @@ state = initial_state(definition) legal = legal_bindings(definition, state, "EVENTTYPE2.1.2") # every legal binding, as pairs of entity indices ``` -`derive(definition, state, entities)` gives the derived facts, `able(definition, event_type, bindings)` and `legal(definition, state, event_type, bindings)` judge bindings, `legal_bindings` lists every legal binding among given entities in a fixed order, and `apply(definition, state, events)` gives the state after one step, or raises an `IllegalEventError` or an `InterferenceError` that names the event and the reason; `apply_step` returns the numeric values after the effects and before the drift beside the state. A `State` holds `values` (the Boolean fluents) and `numeric` (the numeric fluents, float64). The comparisons are `greater(definition, dimension, x, y, state)`, `above(definition, dimension, x, standard, z, state)` and `below`, and `increase(definition, fluent, x, before, after)` and `decrease`, over entities by index or `Mean(category)`, with `ENTITY` and `RANGE` as the symbols; `comparison_classes(definition)` gives every class's count, mean, and standard deviation. The runtime draws no random number and chooses no event. Episodes are run by `semantic_world.world.episodes.simulate(folder, episodes, seed, settings)`, or by an `EpisodeGenerator` over a definition in memory; `semantic_world.world.history.replay(definition, history)` checks a history against the runtime, every recorded numeric value to the bit, and returns its states. +`derive(definition, state, entities)` gives the derived facts, `able(definition, event_type, bindings)` and `legal(definition, state, event_type, bindings)` judge bindings (for a place event type, bindings of an entity index and a place index, since stage c1), `legal_bindings` lists every legal binding among given entities in a fixed order (and among given places, `legal_bindings(definition, state, event_type, entities, places)`, for a place event type), and `apply(definition, state, events)` gives the state after one step, or raises an `IllegalEventError` or an `InterferenceError` that names the event and the reason (an event of a place event type is refused in stage c1); `apply_step` returns the numeric values after the effects and before the drift beside the state. A `State` holds `values` (the Boolean fluents) and `numeric` (the numeric fluents, float64). The comparisons are `greater(definition, dimension, x, y, state)`, `above(definition, dimension, x, standard, z, state)` and `below`, and `increase(definition, fluent, x, before, after)` and `decrease`, over entities by index or `Mean(category)`, with `ENTITY` and `RANGE` as the symbols; `comparison_classes(definition)` gives every class's count, mean, and standard deviation. The runtime draws no random number and chooses no event. Episodes are run by `semantic_world.world.episodes.simulate(folder, episodes, seed, settings)`, or by an `EpisodeGenerator` over a definition in memory; `semantic_world.world.history.replay(definition, history)` checks a history against the runtime, every recorded numeric value to the bit, and returns its states. ## Recipes @@ -418,6 +440,7 @@ legal = legal_bindings(definition, state, "EVENTTYPE2.1.2") # every legal bind - **A world without numeric fluents.** `numeric_fluents: {count: 0}` gives the world of stage b1, byte for byte. With numeric fluents kept but `event_types: {effects: {numeric_share: 0, condition_kinds: {feature: 1, fluent: 1}}, preconditions: {numeric_share: 0}}` and `numeric_fluents: {rule_weight: 0}`, the numeric fluents exist and drift but nothing reads or writes them, and every Boolean draw is as without them. - **A world of needs.** `numeric_fluents: {count: 1, fluents: {NUMFL.1: {initial: {kind: beta, mean: 0.3, concentration: 4}, drift: 0.05}}}` with `event_types: {effects: {numeric_share: 0.5}, preconditions: {numeric_share: 0.5}}`: one fluent that rises at every step, which half the effects add to or subtract from and half the literals read. - **A world without change.** `fluents: {count: 0}` and `numeric_fluents: {count: 0}`. Every able binding is legal at every time point, histories record no change, and `scene.policy: uniform_event_type` in the corpus then gives the scenes of the corpus's old scene generator. +- **A world without places.** `places: null` (and no `place_event_types` block): the world before stage c1, byte for byte, with no `places/`, `places.csv`, or `place_capacities.csv`. A study that wants other kinds of place names its own taxonomy configuration with `labels.kind: place` under `places.taxonomy`; `place_event_types.constraint_families: {comparison: 1}` makes every place requirement a comparison of an entity scalar with a place scalar. - **A known chain of events.** Write an event file (above), and name it under `event_types.event_file`. `simulate` on the world shows the chain's events in the order the preconditions force. - **More two-place events.** Lower `event_types.binary.features.expected_true`, or weigh the two-place event types in `scene.event_type_weights` of the corpus configuration; see "The two-place share" for every lever. - **A model's input.** `view --preset classic` for the columns of the old `instances.csv`; `view --preset base --include ISA` for the base vector with the categories. Join `derived/capacities.csv` on `label` for the two-place capacities. @@ -437,6 +460,7 @@ legal = legal_bindings(definition, state, "EVENTTYPE2.1.2") # every legal bind - `docs/specs/WORLD_AND_LANGUAGE.md`: the design, with "Terms", "Architecture", "The world definition", "Phase (a): the world", "Phase (a): determinism", and the build stages. - `tests/fixtures/world/README.md`: the fixture format, the definition record, the canonical JSON, and the semantics of the runtime, for the Rust runtime. -- `docs/proposals/2026-10-07-world-stage-a1-decisions.md` to `2026-10-09-world-stage-a8-decisions.md`: the engineering choices of each stage, and Jon's rulings on them. -- `docs/DECISIONS.md`, "World model": WM.1 to WM.34 and WM.E1 and following. +- `docs/proposals/2026-10-07-world-stage-a1-decisions.md` to `2026-10-16-world-stage-c1-decisions.md`: the engineering choices of each stage, and Jon's rulings on them. +- `docs/specs/WORLD_PHASE_C.md`: phase (c), with the as-built notes of stage c1. +- `docs/DECISIONS.md`, "World model": WM.1 to WM.60 and WM.E1 and following. - `docs/guides/TAXONOMY.md`: the taxonomy inside the world, and "Where CAN features and verbs went". `docs/guides/CORPUS.md`: the documents written about a world. diff --git a/docs/proposals/2026-10-16-world-stage-c1-decisions.md b/docs/proposals/2026-10-16-world-stage-c1-decisions.md new file mode 100644 index 0000000..b786a82 --- /dev/null +++ b/docs/proposals/2026-10-16-world-stage-c1-decisions.md @@ -0,0 +1,173 @@ +# Proposal: decisions for stage c1 of the world-and-language refactor + +October 16, 2026. Raised in the orientation for stage c1 of `docs/specs/WORLD_PHASE_C.md` ("c1. Places", the first stage of phase (c)), and while building the stage. Status: working design. Every choice below is Claude Code's unless Jon changes it. The choices are logged as WM.E240 and following in `docs/DECISIONS.md`; Jon's ruling on the rounding of drawn reals is WM.60. + +Stage c1 builds, in this order: the rounding of every real the world draws and stores (Jon's ruling of October 16, 2026); the taxonomy's `labels.kind`; the place taxonomies; places in the world (the second taxonomy run, `places/`, `places.csv`); the place comparison classes and `EVERYPLACE`; the place event types with their requirements, on the static side only; the capacities `CAN.PLACEEVENTTYPE` and `CANBE.PLACEEVENTTYPE`, the relation statistics, and the `places` block of the statistics; and `view --places`. Episodes and the corpus do not use places in this stage. + +## Orientation + +### What was read + +This is a fresh session. It read `CLAUDE.md` in full; `docs/specs/WORLD_PHASE_C.md` in full; `docs/specs/WORLD_AND_LANGUAGE.md` ("A principle for defaults", "Labels", "Files", "Rules: Boolean form and matrices", "Rule-set identity and derived values", "Views", phase (a)'s "Event types", phase (b)'s "The world" and "Numeric fluents"); `docs/DECISIONS.md` ("World model" WM.1 to WM.59, the engineering choices WM.E1 to WM.E55, WM.E95 to WM.E117, and WM.E190 to WM.E206, and "Process"); `docs/guides/WORLD.md` and `docs/guides/TAXONOMY.md`; the taxonomy package (`config.py` for the label prefixes, `generate.py`, `io.py`, `features.py`, `instances.py`, `tree.py`, the threshold rounding in `rules.py` and `expressions.py`); the world package (`generate.py`, `config.py`, `statics.py`, `event_tree.py`, `event_types.py`, `numeric.py`, `fluents.py`, `constraints.py`, `requirements.py`, `projections.py`, `capacities.py`, `relation_stats.py`, `definition.py`, `identity.py`, `derived.py`, `comparisons.py`, `runtime.py`, `views.py`, `stats.py`, `streams.py`, `fixtures.py`, `episodes.py`, `__main__.py`); `python/semantic_world/figures.py` and `tests/figures/snapshot.yaml`; the corpus's `world.py` and the tests' `truth_oracle.py` where they read a world; and `tests/world/` and `tests/taxonomy/` (the define, comparison, projection, relation-statistics, off-setting, identity, fixture, golden, and configuration tests). + +### How the pieces stand today + +Main holds the phase (c) specification (28bbfde, merged as PR #31) over the numeric cut (PR #30) and stage b3b (PR #29). Nothing of phase (c) is built. The world draws from sixteen streams, `world:requirements` to `world:episodes`; the last ones added were the five numeric streams of stage b2 (WM.E193). The world package draws four kinds of real number and stores them at full precision: a numeric effect's constant (`event_types.py`, `_numeric_effect`, from `world:numeric_effects`), a numeric fluent's drift (`numeric.py`, from `world:numeric`), and each entity's initial value of a numeric fluent, uniform or beta (`numeric.py`, `InitialDistribution.draw`, from `world:numeric_initial`, and again from an episode's part of `world:episodes` under `scene.initial: redraw`). Every scalar threshold and every comparison margin is already rounded to 4 decimals when drawn (`Threshold.__post_init__`, `model_quantile_threshold`, `Cmp.__post_init__`). + +The taxonomy writes its labels from four module constants of `taxonomy/config.py` (`LABEL_PREFIX`, `SCALAR_PREFIX`, `CATEGORY_PREFIX`, `INSTANCE_PREFIX`), read by `features.py`, `instances.py`, `rules.py` (`Threshold.label`, the explicit-rule parser), and `Config.tree_settings` (the category prefix); the ISA labels are built from the category labels in `fixed.py`. The world's definition has two rule scopes, `entity` and `binding`, one literal table, and symbol kinds `property`, `part`, `scalar`, `fluent`, `numeric_fluent`, `event_type`, and `event_type_category`; the runtime, the brute-force evaluator, the episodes, and the corpus tell event types apart by `kind` and `arity`. The constraint sampler of two-place event types draws both roles' literals from one pool (the entity features and scalars); the projections enumerate the unknown side's cones with the same taxonomy's rules as the known side's instances. + +Before the first change, a reference run of the tiny and default worlds and corpora was made from `main` at 28bbfde (`define` and `generate` of both configurations) into `/Users/jon/Documents/Projects/semantic-world-reference/c1/`, outside the repository, with the commit and the log beside it. + +### Plan for the stage + +In the order of the scope: + +1. **The rounding** (WM.60): every real the world draws and stores, rounded to 4 decimals when drawn; the fixtures regenerated; the snapshot, every baseline, and the guides' figures rewritten; the figures that moved recorded here as "item 1". +2. **`labels.kind`** in the taxonomy configuration, carried as a `Labels` record through the feature layout, the tree, the instances, the thresholds, and the explicit rules, with the entity labels as the defaults, so that the golden hashes do not move. +3. **The place taxonomies**, `data/taxonomy/places_default.yaml` and `places_tiny.yaml`. +4. **Places in the world**: the `places` block, the stream `world:places`, the second taxonomy run in memory, `places/` and `places.csv`, the place symbols in `definition.json` when places are on. +5. **Place comparison classes** in `derived/comparison_classes.csv` and in the runtime's `comparison_classes`, `greater`, `above`, and `below`. +6. **Place event types**, static side: the `place_event_types` block, the stream `world:place_event_types`, the constraint sampler over two pools, the redraw of degenerate requirements, the scopes `place` and `place_binding` in the definition with the agreement test over every entity and place binding, the runtime's `able` and `legal_bindings` over places, the brute-force evaluator over places, and the fixtures with a `places` table. +7. **Capacities and statistics**: the projections with the unknown side's rules apart from the known side's instances, `CAN.PLACEEVENTTYPE` in `capacities.csv`, `derived/place_capacities.csv`, the place rows of `relation_proportions.csv` and `event_type_stats.csv`, and the `places` block of `world_stats.yaml`. +8. **`view --places`**. +9. **The check that the corpus changes only through the world**, the runs, the snapshot with `places_off`, the guides, the documents, and the check list. + +### Reading of the scope + +- **"Every real the world draws and stores at full precision."** The taxonomy's scalar values (drawn from normal distributions by the taxonomy, written to `base.csv` at 6 decimals and copied into `entities.csv` at full precision by WM.E33) are the taxonomy's draws, not the world's, and the stage requires every taxonomy output to stay byte-identical; they are left alone and listed under "checked" below, with the remark that a normal draw could in principle show the same cross-machine difference. +- **"Places are not entities."** The place taxonomy run is a `TaxonomyResult` of its own on the `WorldResult`, never merged into the entity side: `ENTITY`, `entities.csv`, `capacities.csv`'s rows, the episodes, and the corpus's `World` see entities only; places get their own table, their own comparison-class root, and their own scopes in the definition. +- **"In this stage place event types have no preconditions or effects."** A place event type's record in `definition.json` carries an empty precondition and no effects; the runtime's `able` and `legal_bindings` accept it over (entity, place) bindings, `legal` equals `able`, and `apply` refuses an event of a place event type until stage c3 gives them dynamics. Episodes list only event types of kind `event_type` as performable, so no episode includes one. +- **"Over pairs of an entity category and a place category at the same tree depth."** The rule chosen is recorded in choice 13 below. +- **"The code's default follows `data/world/default.yaml`."** A world configuration without a `places` block gets the default place taxonomy, as a world without a `taxonomy` block gets `relations.yaml` (WM.E41); `places: null` turns places off. + +### Questions + +No question of the orientation changes the design or a file format beyond what `WORLD_PHASE_C.md` and the stage prompt say. The stage proceeds. Open questions found while building are listed at the end. + +## Item 1: the rounding of drawn reals (WM.60) + +Every real the world draws and stores was found by reading every `rng.uniform`, `rng.beta`, `rng.normal`, and `rng.random` call of the world package and following each drawn value to where it is stored. + +**Rounded when drawn, to 4 decimals** (`numeric.DECIMALS`, `numeric.drawn`): + +- A numeric fluent's drift (`numeric.py`, `generate_numeric_fluents`, from `world:numeric`): 0.072826 became 0.0728 and 0.005553 became 0.0056 in both worlds. +- Each entity's initial value of a numeric fluent, uniform or beta (`InitialDistribution.draw`, from `world:numeric_initial` when the world is generated, and from an episode's part of `world:episodes` under `scene.initial: redraw`), rounded and then clamped to the range, because a rounded value can cross a bound given to more than 4 decimals. A fixed initial value is the configuration's own number. +- A numeric effect's constant (`event_types.py`, `_numeric_effect`, from `world:numeric_effects`): `0.3640982468028209` became `0.3641` in the default world's inherited conditional effect. + +**Checked and left alone**, with the reason: + +- Scalar thresholds of the taxonomy's rules, the one-place requirements, the constraints, and the derived fluents' rules: already rounded to 4 decimals when drawn (`Threshold.__post_init__`, `model_quantile_threshold`, REL.9). +- Comparison margins: already rounded (`Cmp.__post_init__`, `_comparison`). +- Numeric threshold points of precondition, condition, and rule literals (`NumericFluent.point`): not drawn; computed from the configured range and a fraction drawn from a list (`numeric_thresholds`), exact under the default range `[0, 1]`, and the corpus computes the same expression for the language's cut, so rounding it here would break that equality for an odd range. Noted as a remark below. +- Initial rates of Boolean fluents, and the fractions of numeric thresholds: drawn from lists of configured numbers. +- The taxonomy's scalar values (normal draws, written to `base.csv` at 6 decimals and copied into `entities.csv` at full precision by WM.E33) and base rates (beta draws): the taxonomy's draws, which the stage requires to stay byte-identical (the golden hashes). Remark: a normal draw could in principle show the same cross-machine difference; nothing of the kind was reported. +- The local test's samples of the projections, the sampled pairs of the relation statistics, and the episodes' draws of participants and events: not stored as reals. + +The tiny world's generated fixtures were regenerated (`make-fixtures`); the hand-written fixtures use multiples of a quarter and did not change (`python tests/world/hand_world.py` rewrote them byte for byte). `tests/world/test_world_rounding.py` checks that every stored real of the tiny and default worlds, on seeds 1 to 5, equals itself rounded, in the objects and in the definition's record; that a redrawn initial state draws rounded values; and that a fixed value is the configuration's. + +**Figures moved by item 1 alone** (the b3b snapshot against the worlds and corpora with `places: null` after the rounding; the identities first): + +- `tiny.world.rule_set_id`: `912f1d371460...` became `6834c215f732...`; `default.world.rule_set_id`: `f47334502194...` became `cc1e27fac819...`. These are the `places_off` baselines of the new snapshot. +- `tiny.world.numeric.drifts` and `default.world.numeric.drifts`: `[0.072826, 0.005553]` became `[0.0728, 0.0056]` (the figures are rounded to six decimals; the stored values moved from 0.07282598... and 0.00555313... to the four-decimal values). +- `default.world.conditional.expressions`, `.inherited`, and `.own`: the numeric constants of the conditional effects, four decimals now (for example `patient.NUMFL.1 += 0.3641`). +- `default.world.numeric.changes_per_event`: 0.40818 became 0.4083; `default.world.numeric.drift_changes_per_step`: 8.427322 became 8.425501. +- The default corpus (the tiny corpus's figures did not move apart from the identity): `sentences` 100,917 became 100,918; `tokens` 483,329 became 483,322; `by_level.change` 9,608 became 9,610 and `by_level.event` 28,964 became 28,963; `comparisons.pole_modifiers` 2,972 became 2,969; `states.events_with_result_share` 0.331722 became 0.331803, `states.result_sentences` 9,608 became 9,610, `states.numeric_change_sentences` 3,791 became 3,794; `cooccurrence.words.situational.taxonomic` 0.206033 became 0.206018 and `.thematic` 0.632423 became 0.63219; `corpus.world` moved with the identity. +- Every off-setting baseline moved with the worlds (the conditional, fluent, two-place, and numeric baselines, and the hash of the tiny corpus without conditional effects), because every one of those worlds has rounded draws now. + +## Engineering choices made while building + +Numbered for `docs/DECISIONS.md` (WM.E240 to WM.E255). Each is Claude Code's choice inside the stage's scope. + +1. **The rounding** (above): `DECIMALS = 4` and `drawn()` in `numeric.py`, applied to the drift, the numeric effect constants, and the initial values (rounded, then clamped); the quantities checked and left alone are listed above. +2. **`labels.kind`.** A `Labels` record (the category, instance, property, part, and scalar prefixes) on the taxonomy configuration, read from `labels.kind: entity | place` (default `entity`) and carried by `build_features` (the feature labels and `FeatureSet.scalar_prefix`), the tree settings (the category prefix), the instances (the instance prefix), and `Threshold.prefix` (a threshold literal's scalar label, drawn or explicit); the ISA labels follow the category labels. `resolved()` writes `labels: {kind}` after `seed`, and `data/taxonomy/default.yaml` lists it. Nothing else changes: `tests/taxonomy/test_taxonomy_labels.py` checks that a place run equals the entity run of the same settings label for label (the YAML files compared as data, because the longer labels wrap lines elsewhere), that the ISA columns, thresholds, and explicit rules use the place labels, and the golden hashes of the entity configurations did not move. The golden hashes gained the two place configurations. +3. **The place taxonomies as built.** `places_default.yaml`: 2 superordinates, depth 2, branching 3, 2 places per leaf (12 places in 8 categories), 12 place properties with a quarter determined and `expected_true_free: 3` of the 9 free (a free place property is true of a third of the places on average; the entity default of 6 would be two thirds of 9), no parts (`part: {count: 0}`), 2 scalars, the taxonomy's defaults otherwise. `places_tiny.yaml`: 2 superordinates at depth 1 (the superordinates are the leaves, which the taxonomy allows) with 2 places each (4 places in 2 categories), 4 place properties with 1 determined and `expected_true_free: 1` of the 3 free, no parts, 1 scalar, and `base_rate_heterogeneity: null` as WM.E108 set it for the tiny entity taxonomies, so that the similarity bound can place the superordinates. Both carry `labels.kind: place`. +4. **The `places` block.** `PlacesConfig(taxonomy, habitat_strength, taxonomy_run)`: the named place taxonomy configuration is validated at configuration time with its seed resolved, the given seed or the low 64 bits of the seed of the stream `world:places` (the taxonomy's seeds are 64-bit integers, the stream's seed a 256-bit digest), and must set `labels.kind: place`. A world configuration without a `places` key gets `data/taxonomy/places_default.yaml` (the code's default follows `data/world/default.yaml`, as WM.E41 did for the entity taxonomy), and `places: null` turns places off. `place_event_types` (`count` 4, `move_share` 0.5, `constraints` `{1: 0.6, 2: 0.4}` with no weight on 0, `constraint_families` `{agent: 1, patient: 1, cross: 1, key_lock: 1}`, `rules` as overrides of the entity taxonomy's rule settings) is `None` exactly when places are off, and giving the block with `places: null` is an error; `move_share` and `habitat_strength` are validated and stored for stage c3. The comparison family needs scalars in both taxonomies. The key-lock pairs and the comparison settings of the place constraints are those of `event_types.binary` (the code's defaults when the world has no two-place event types), rather than new keys the specification does not list. The resolved configuration writes `places` (with the resolved place taxonomy under `places.taxonomy.resolved`) and `place_event_types` after `event_types`. +5. **`places.py` and `WorldResult.places`.** `Places` holds the place taxonomy run, the place event types, each requirement over every binding as an `(entities, places)` bool matrix, and the capacities; `generate_places` makes it after the static side (its streams are its own, so the order does not matter). `write` writes the run unmodified to `places/` and the places' base facts to `places.csv` (label, leaf, free place features, place scalars at full precision, as `entities.csv` holds the entities' base facts). Nothing of it enters `StaticWorld`, `entities.csv`, `capacities.csv`'s rows, the episodes, or the corpus's `World`. +6. **The constraint sampler over two pools.** `_ConstraintSampler` takes an optional patient pool (the place taxonomy's scalars and features): the patient's literals come from it, a patient threshold carries the place scalar prefix, and a comparison compares an entity scalar with a place scalar, always across dimensions, with the model standard deviation of the difference `sqrt(entity_std^2 + place_std^2)` (`ScalarComparison.patient_prefix`, `agent_label`, `patient_label`; `Constraint.matrix` takes the patients' tables). With one pool the sampler's draws are unchanged, so the two-place event types of a world are those of stage b3b (the `places_off` baseline checks it). +7. **Place event types and their redraw.** `PLACEEVENTTYPE.` draws the number of its constraints from `place_event_types.constraints`, labels them `CONSTRAINT.PLACEEVENTTYPE..`, and ANDs them as `REQUIREMENT.PLACEEVENTTYPE.`. A requirement that holds for no binding or for every binding of the world is drawn again, up to 200 times (the two-place `max_tries` default), after which the last draw is kept with a warning in `world_stats.yaml`. The check is over the world's bindings (every entity paired with every place), because the specification says "of the world"; the two-place check is over leaf pairs. The draws, and the samples of the capacities' local test, come from `world:place_event_types`. +8. **The definition.** With places, `definition.json` gains the symbol kinds `place_property`, `place_part`, `place_scalar`, and `place_event_type`; the rule and layer scopes `place` (the determined place features, evaluated over the places) and `place_binding` (the place constraints and requirements, evaluated over bindings of an entity and a place); place literals after the entity literals in the literal table, in the same group order (a place feature or threshold of the place scope, a place feature or threshold of the patient, a comparison with a place scalar, a place constraint's output), while the agent's literals (`agent.PROPERTY.3`) are shared with the binding scope; and a place event type's record with kind `place_event_type`, arity 2, the roles agent and patient, no parent, level 1, no features, `explicit: false`, an empty precondition (`TRUE`), and no effects. The agreement test (`check_definition`, `DefinitionReport.place` and `.place_binding`) covers every place rule on every place and every place constraint and requirement on every entity paired with every place, against the constraints' own evaluation. With `places: null` nothing of this is written, so the definition and its identity are those of the world before places. +9. **The runtime.** `RuntimeDefinition` loads `places.csv` beside `entities.csv` (required when the record has place symbols, refused otherwise, with the columns checked as the entities' are) into the places' labels, leaves, free values, and scalar values, with the rules and matrices of both place scopes. `EventTypeRecord.place` (kind `place_event_type`) and `EventTypeRecord.performable` (kind `event_type`) tell the kinds apart; the episodes, the fixtures, and the brute-force evaluator list the performable event types only. `able` and `legal` accept a place event type over bindings of an entity index and a place index (`place_able_table`, cached), `legal_bindings(definition, state, event_type, entities, places)` lists them by agent then place, `check_definition_agreement` covers the place scopes, and `apply` refuses an event of a place event type with a `WorldError`, because stage c1 gives them no dynamics (stage c3 lifts this). +10. **Comparisons over places.** `EVERYPLACE` and the place categories are comparison classes on the place scalars (`comparison_classes`, `members`, `place_categories`), an argument on a place scalar is a place index or `Mean` of a class of places, and a comparison that mixes an entity scalar or a numeric fluent with a class of places, or a place scalar with a class of entities, is refused with a `WorldError` (`_check_class_of_dimension`). `derived/comparison_classes.csv` holds the place rows after the entity rows: `EVERYPLACE` first, then the place tree in tree order, on every place scalar. +11. **Capacities.** `project` is reused with the unknown side's rules apart from the known side's instances: `CAN.PLACEEVENTTYPE.` enumerates the place taxonomy's cones for the entities, `CANBE.PLACEEVENTTYPE.` the entity taxonomy's cones for the places, each with the local test's approximate flag, then the extensional capacities over the world's bindings. In `capacities.csv` the `CAN.PLACEEVENTTYPE` columns follow the two-place `CANBE` columns before `approximate` (which names approximate place columns too), and the `ACTUAL_CAN.PLACEEVENTTYPE` columns come last, so every earlier column keeps its place; `derived/place_capacities.csv` holds one row per place with `CANBE.`, `approximate`, and `ACTUAL_CANBE.` columns. +12. **Relation statistics.** `relation_proportions.csv` gains rows for the place event types: for each level L of the entity tree, every entity category at level L against every place category at level L, or at the place tree's last level when the entity tree is deeper (the default world's entity leaves at level 3 against the place leaves at level 2), with the share of their bindings for which the requirement holds, rows with a share above 0. `event_type_stats.csv` gains one row per place event type with the share of all bindings, the entities and places with a partner, the constraints and families, NaN for the symmetric and within-leaf shares (a binding is never reversed, and an entity shares no leaf with a place), and the binding density under `leaf_pair_density` with its tries. Both files are written when the world has places even without two-place event types; `relation_pairs.csv` and `thematic.csv` are unchanged, and thematic relatedness stays a relation between entity leaves. +13. **Statistics and figures.** `world_stats.yaml` gains a `places` block (places, categories, place features by kind with free and determined counts, place event types, constraints by family, each requirement's density over the world's bindings and its tries, the share of approximate capacities, and warnings), null without places. `figures.py` reads it into a `places` block of the world figures, keeps the two-place requirement figure over the two-place rows of `event_type_stats.csv` alone, names the places and place event types in the `define` line and the quick-start figure, and adds `places_off` (`places: null`, `place_event_types: null`) to the off settings with its baseline identities. +14. **Fixtures.** A fixture of a world with places carries `places` (the rows of `places.csv`) after `entities`; `check_fixture` and the brute-force evaluator read it (`BruteForce(record, entities, places)`, with `_row` finding an entity or a place by label), a place event type's bindings in the evaluator are every entity paired with every place, and a step's `legal` lists the performable event types only. The regenerated tiny fixtures carry the places and the place scopes. `tests/fixtures/world/README.md` gains a "Places" section for the Rust runtime. +15. **`view --places`.** `RUN/views/places_.csv`: `base` (label, leaf, free place features, place scalars), `static` (with the determined place features and the `CANBE.PLACEEVENTTYPE` capacities), and `classic` (with the ISA columns of the place tree); the sidecar records `places: true`, `--include` works over the place columns, and the entities' views never show a place column. +16. **The corpus, checked.** With places on, the tiny corpus's `documents.jsonl`, `lexicon.csv`, `stats.yaml`, the four renderings, the test sets, and the word-form request are byte-identical to the corpus over the same world with `places: null`, and `scenes.jsonl` differs in the rule-set identity alone; no corpus output names a place, a place feature, or a place event type (`grep PLACE` finds nothing). The default corpus's figures moved only by item 1 (above): the snapshot's corpus figures are the same with and without places apart from the world's identity. The word forms were not remade, because both corpora's word-form requests are byte-identical to stage b3b's; `render` attached the b3b word-form runs. + +## The place taxonomies as built + +| Configuration | Places | Categories | Place properties (determined) | Place parts | Place scalars | Settings that depart from the taxonomy's defaults | +| --- | --- | --- | --- | --- | --- | --- | +| `places_default.yaml` | 12 | 8 (2 superordinates, depth 2, branching 3) | 12 (3) | 0 | 2 | `expected_true_free: 3`, `part: {count: 0}`, `instances.per_leaf: 2` | +| `places_tiny.yaml` | 4 | 2 (2 superordinates, depth 1) | 4 (1) | 0 | 1 | `expected_true_free: 1`, `part: {count: 0}`, `base_rate_heterogeneity: null`, `instances.per_leaf: 2` | + +The default world's place event types (seed 1): four requirements with 5 constraints (2 agent, 1 cross, 2 key-lock), holding for 64%, 65%, 82%, and 1.8% of the 3,408 bindings of an entity and a place; none approximate. The tiny world's: two agent-only requirements (3-literal and 4-literal rules over the agent's features), holding for 50% and 67% of the 48 bindings. In both worlds every requirement was accepted at the first draw. + +## Changed expectations + +**Item 1** rewrote the snapshot as listed above ("Figures moved by item 1 alone"), the generated tiny fixtures, and every off-setting baseline. + +**Places** then moved, beside the identities of both worlds and of every baseline (each world has places now): the new `places` block of both worlds' figures (`places.places`, `categories`, `features`, `place_event_types`, `constraints`, `constraint_families`, `density`, `approximate_capacities`, `on`), the `wrote_line` and quick-start figures (the places and place event types named), the new baseline `places_off` (tiny `6834c215f732...`, default `cc1e27fac819...`, the item-1 identities), and the corpora's `world` figure. No other figure of the worlds or the corpora moved with places; in particular the two-place requirement figures, which the first draft of the places figures had computed over the place rows too, are unchanged (0.171 mean on the default world). + +The guides' figures were rewritten by `python -m semantic_world.figures guides`: `README.md` and `docs/guides/README.md` (the default corpus's sentences), `WORLD.md` (the quick-start block, the inherited conditional effect's constant, the identities, and the new places row), and `CORPUS.md` (the default corpus's line and its numeric change sentences). + +## Documents changed + +- `docs/DECISIONS.md`: WM.60 (Jon's ruling on the rounding, after WM.59) and WM.E240 to WM.E255 (the engineering choices above, status "Working design", after WM.E239). Appended; no existing row edited. +- `docs/specs/WORLD_AND_LANGUAGE.md`: one "(As built, stage c1: ...)" note on the rounding of drawn reals, at the end of the "Drift" bullet of phase (b)'s "Numeric fluents". +- `docs/specs/WORLD_PHASE_C.md`: "(As built, stage c1: ...)" notes on "A second taxonomy run" (`places/`, `places.csv`, the seed's low 64 bits, `labels.kind: place` required, the default when the key is absent), "The default place taxonomy" (`expected_true_free`, the tiny settings, depth 1), "The runtime's comparisons" (place indices, the refusal of mixed comparisons), "How many, and their requirements" (the labels of constraints and requirements, the degenerate check over the world's bindings, the borrowed key-lock and comparison settings, the record's kind and scopes, no dynamics), "Capacities" (the column order), "Relation statistics" (the level rule, the NaN shares, the files without two-place event types), "Places in the runtime" (`places.csv`, `legal_bindings` with places, the agreement test), "Conformance fixtures" (the `places` table, the performable `legal` lists), "Determinism" (the b3b world read as the world after the rounding; the `places_off` baseline), and the stage c1 entry of "Build stages" (the proposal file and the WM.E rows). +- `docs/guides/WORLD.md`: the status, the quick start, the example configurations, the "Numeric fluents" and the new "Places" concepts, the labels, the streams, `define` and `view --places`, the outputs (`places/`, `places.csv`, the definition's kinds and scopes, `capacities.csv`, `place_capacities.csv`, `comparison_classes.csv`, the relation files, the `places` block), the configuration table, the default world's new row and the `places_off` identity (by the script), a recipe, the Python interface, the run times, and the references. +- `docs/guides/TAXONOMY.md`: the status, the example configurations, the place labels under "Labels", `labels.kind` in the configuration table, and a new section "Place taxonomies". +- `docs/guides/README.md`: the status table, the chain's first step, the run times, and the chain paragraph (the places' figures by the script). +- `README.md` and `docs/guides/CORPUS.md`: figures only, by the script. +- `CLAUDE.md`: the status of items 9 and 10 (phase (c) begun, stage c1 built), the `view --places` command, and the place taxonomy command. +- `tests/fixtures/world/README.md`: a "Places (stage c1)" section for the Rust runtime. +- `data/taxonomy/default.yaml`: `labels: {kind: entity}` listed with the other defaults. + +## Run times + +Measured on October 16, 2026, with nothing else running, after the full regeneration (the second run of the folder): + +| Step | Stage c1 | Stage b3b | Change | +| --- | --- | --- | --- | +| `define data/world/default.yaml` | 6.4 s | 6.3 s | +2%: the place taxonomy run, four place event types, their capacities, and the agreement test over 3,408 entity-and-place bindings add about a tenth of a second | +| `generate data/corpus/default.yaml` | 134 s | 141 s | −5%: the corpus does not read the places; the difference is noise of the machine | + +No step grew by more than half. The tiny chain (world and corpus) runs in about 5 seconds. + +## Test results + +The full check list of `CLAUDE.md`, run on October 16, 2026, at the end of the stage: + +| Check | Result | +| --- | --- | +| `cargo fmt --all --check`, `cargo clippy --workspace --all-targets --all-features -- -D warnings`, `cargo test --workspace --all-features` | passed (the Rust crates are untouched by the stage) | +| `ruff check python tests` | passed | +| `pytest -n auto` (the fast tier, 1,833 tests) | passed in 96 s, nothing skipped | +| `pytest --slow -n auto` (the slow tier, 285 tests) | passed in 374 s, nothing skipped | + +The new tests: `tests/world/test_world_rounding.py` (every stored real rounded, on seeds 1 to 5 of both worlds, the default seeds in the slow tier; the redraw; the fixed value), `tests/world/test_world_places.py` (the configuration, the byte identity of `places/` with the taxonomy run alone, `places.csv`, the place symbols and scopes present only with places, no degenerate requirement and the matrices against the truth tables and the runtime over every binding, the capacities against a brute-force enumeration and the written files, the comparison classes against a recomputation from `places.csv` and the runtime's classes, the mixed comparisons refused, the relation rows and the `places` block, the three place views, no place in any episode, and `apply` refusing a place event), and `tests/taxonomy/test_taxonomy_labels.py` (the kind read and validated, a place run as the entity run relabeled, the place configurations, a place rule file). The identity with `places: null` is in `test_world_off_settings.py` through the snapshot's `places_off` baseline, and the golden hashes of the entity configurations in `test_taxonomy_golden.py`. Every acceptance check of phases (a) and (b) still passes: the tests of stages a1 to b3b run unchanged in their meaning, with the edits listed in the commit (the performable event types, the places table in the loader calls, and the new columns in the expectations). + +## Open questions + +None of these changes the design; each is recorded for Jon's eye. + +1. **The taxonomy's scalar values at full precision.** `entities.csv` copies the entities' scalar values at full precision (WM.E33), and `places.csv` the places'. They are the taxonomy's normal draws, left alone because the taxonomy's outputs must stay byte-identical, and `base.csv` holds them at 6 decimals. If a normal draw ever differs between machines in the last bit, the comparison classes and the threshold literals' values would differ there too; nothing of the kind was reported. A rounding of the taxonomy's scalars would be a taxonomy change with new golden hashes, which this stage did not make. +2. **Numeric threshold points are not rounded.** `low + fraction * (high - low)` is computed, not drawn, and the corpus computes the same expression for the language's cut (WM.35); under the default range the points are exact. A world with a range such as `[0, 0.3]` would carry points like `0.22499999999999998` in its definition, and a fused multiply-add could in principle move the last bit between machines. Rounding the point would need the corpus's cut rounded the same way. +3. **The key-lock and comparison settings of the place constraints** are borrowed from `event_types.binary` (choice 4). If a study needs them apart for places, `place_event_types.key_lock_pairs` and `place_event_types.comparison` would be two more keys. +4. **The tiny world's place event types read the agent alone** (seed 1): both requirements drew the agent family, so no tiny requirement reads a place feature. The fast-tier tests cover a requirement over the place's features through the default world's place event types (seed 1 draws a cross constraint and two key-lock constraints) in the slow tier, and through the brute-force and agreement tests over every place literal of the tiny fixtures (the determined place feature's rule). A tiny configuration with `constraint_families: {patient: 1, cross: 1}` would guarantee a place literal in the tiny world; it was not changed, so that the tiny world stays the default settings' world. +5. **The level rule of the relation proportions** (choice 12) pairs the entity leaves with the place leaves when the entity tree is deeper. The alternative, rows only at levels both trees have, would leave the entity leaves without rows, which the corpus's capacity facts about places (stage c5b) may want. + +## Notes for stage c2a + +What the builder of the pair fluents (the definition, the runtime, and the fixtures) must know: + +1. **The definition has four scopes now**: `entity`, `binding`, `place`, and `place_binding`, each with its own `RuleMatrices` and its own literal-value builder on `Definition` (`entity_literal_values`, `binding_literal_values`, `place_literal_values`, `place_binding_literal_values`) and on the runtime (`static_facts`, `_binding_literals`, `place_facts`, `_place_binding_literals`). The literal table is shared; `_is_place_literal` tells a place literal from an entity literal by its label, and `_literal_sort_key` puts the place groups after the entity groups. Pair literals (`pair`, `pair_some`, `pair_someof`, `pair_same`) will join the literal table with new kinds; they are computed before the matrices, like thresholds, and their values depend on the state, like fluent literals, so they belong to the entity scope (a derived fluent's rule) and to the binding scopes (preconditions and conditions read them per binding, outside the matrices, as `_precondition_holds` reads fluent literals). +2. **Places are indexed apart from entities** in `RuntimeDefinition` (`place_labels`, `place_leaves`, `place_free_values`, `place_scalar_values`, `place_count`, `place_index`), and a place event type's binding is `(entity index, place index)`; `_bindings_array` validates the two ranges. A location fluent's pairs are entity-to-place, so a `State` will need a pair part whose second index ranges over places for a location fluent and over entities for a pair fluent between entities. +3. **`EventTypeRecord.performable`** is `kind == "event_type"`; place event types have `kind == "place_event_type"` and `place == True`. Stage c3 makes them performable (their preconditions and effects come from the `move`/`act` draws); until then `apply` raises a `WorldError` for them, `able_table` skips them, and `place_able_table` holds their requirements. The `performable` lists of `episodes.py`, `fixtures.py`, and the tests read the property, so widening it is one change. +4. **The fixture format**: `places` after `entities` (only with places), the brute-force evaluator's `_row` for either kind of object, and `fixture_places(definition)` for a built definition. The format's version stays 1. Stage c2a adds `pair_fluents` in the definition, `initial_pairs` beside `initial`, and `pairs` in each step; `initial_places` waits for c3. +5. **Event files** are untouched in c1: an explicit requirement for a place event type is not supported (the loader of `event_file.py` recognizes `EVENTTYPE1` and `EVENTTYPE2` labels only). Stage c2a's event files define pair fluents and pair literals for entity event types; `place_event_types` entries (`kind: move`, a requirement over `agent.` and `patient.PLACE...` literals) come with c3. +6. **The streams** `world:places` and `world:place_event_types` are the last two of `STREAM_NAMES`; c2a adds `world:pair_fluents` and `world:pair_initial` after them (the specification's order), and the identity test of `places_off` keeps holding as long as no new draw touches an earlier stream. +7. **The comparisons** take a place scalar's argument as a place index. `value_of` decides the kind of index by the dimension, so a pair fluent's literals must not be routed through it. +8. **The corpus** reads `result.event_types` (entity event types only), `statics`, and `definition.runtime()`; it ignores `result.places`. Its `World` and the tests' `truth_oracle` build `BruteForce(record, rows)` without places, which works because they never evaluate a place literal; c4 passes `places.csv` when places enter the corpus. diff --git a/docs/specs/WORLD_AND_LANGUAGE.md b/docs/specs/WORLD_AND_LANGUAGE.md index 1cfd1fa..59f004b 100644 --- a/docs/specs/WORLD_AND_LANGUAGE.md +++ b/docs/specs/WORLD_AND_LANGUAGE.md @@ -652,7 +652,7 @@ Scalars stay static in phase (b). Comparisons of scalars are derived static fact - **What they are.** A world has `numeric_fluents.count` numeric fluents, `NUMFL.1` to `NUMFL.n` (default 2). A numeric fluent is a base fluent with a real value for every entity. Each one has a range, `[0, 1]` by default, and settable per fluent. Values are clamped to the range. (As built, stage b2: a numeric fluent is a symbol of kind `numeric_fluent` in `definition.json`, which carries its `range`, its `initial` distribution, and its `drift`, all hashed into the rule-set identity; its initial values are columns of `entities.csv`. The range is `numeric_fluents.range` for every fluent, and `numeric_fluents.fluents.NUMFL..range` for one.) - **Initial values.** Each entity's initial value is drawn when the world is generated, from the fluent's initial distribution: `uniform` over the range (the default), `fixed` at a value, or `beta` with a mean and a concentration. The kind of distribution is set per fluent. Episodes start from these values, or redraw them with `scene.initial: redraw`, as Boolean fluents do. (As built, stage b2: `numeric_fluents.initial` sets the distribution of every fluent and `numeric_fluents.fluents.NUMFL..initial` of one, as `{kind: uniform}`, `{kind: fixed, value: v}`, or `{kind: beta, mean: m, concentration: c}`; the values are drawn from the stream `world:numeric_initial`; a redraw draws the Boolean fluents first and then the numeric ones, participant by participant.) -- **Drift.** A numeric fluent can change by itself at every step, as a need rises over time. A share `numeric_fluents.drift.share` of the numeric fluents (default 0.5) gets a drift per step, drawn uniformly from `numeric_fluents.drift.amounts` (default `[-0.1, 0.1]`). Drift applies to every participant of an episode, at every step. (As built, stage b2: the drift is drawn from the stream `world:numeric`, one draw against the share and one for the amount per fluent; `numeric_fluents.fluents.NUMFL..drift`, a number or null, fixes a fluent's drift in place of the draw, and so does the `drift` of a numeric fluent named in an event file. The runtime applies the drift to every entity of the state, of which the participants are the ones an episode reads.) +- **Drift.** A numeric fluent can change by itself at every step, as a need rises over time. A share `numeric_fluents.drift.share` of the numeric fluents (default 0.5) gets a drift per step, drawn uniformly from `numeric_fluents.drift.amounts` (default `[-0.1, 0.1]`). Drift applies to every participant of an episode, at every step. (As built, stage b2: the drift is drawn from the stream `world:numeric`, one draw against the share and one for the amount per fluent; `numeric_fluents.fluents.NUMFL..drift`, a number or null, fixes a fluent's drift in place of the draw, and so does the `drift` of a numeric fluent named in an event file. The runtime applies the drift to every entity of the state, of which the participants are the ones an episode reads.) (As built, stage c1: every real the world draws and stores at full precision is rounded to 4 decimals when drawn, as thresholds are: the drift, each entity's initial value of a numeric fluent (uniform or beta, in the world and in an episode's redraw, then clamped to the range), and a numeric effect's constant. On Linux the unrounded draws differed from the Mac's in the last digit (a fused multiply-add in `rng.uniform`), so the rule-set identity differed between machines. Jon's ruling of October 16, 2026, WM.60.) - **Effects.** An effect can add a constant to a numeric fluent of one role, or subtract one: `patient.NUMFL.1 += 0.3`. A share `event_types.effects.numeric_share` of effects (default 0.3) is numeric when numeric fluents exist. The constant is drawn uniformly from `event_types.effects.numeric_amounts` (default `[0.1, 0.5]`). Numeric effects attach to event-type features and to event types, as Boolean effects do. (As built, stage b2: each Boolean effect is drawn as before, from `world:effects`, and then replaced by a numeric one of the same role at the share, from the stream `world:numeric_effects`, so that with the share at 0 nothing is drawn from the numeric stream and the Boolean effects are those of the world before the stage. A numeric effect's record carries `operator` and `amount` in place of a `value`. Two effects on the same numeric fluent of the same role are a contradiction, as two Boolean effects on one fluent are; a numeric effect is never empty, because it can always add or subtract.) - **Order within a step.** For each entity and numeric fluent, the value after a step is `clamp(clamp(v + e) + d)`: first the effect `e` of the one event that wrote it (non-interference allows at most one), then the drift `d`, each clamped to the range in float64. Both runtimes use exactly this order. (As built, stage b2: `tests/fixtures/world/README.md`, "The order within a step", gives the arithmetic operation by operation for the Rust runtime, and the Python reference is `step_value` in `numeric.py`; `apply_step` returns the values after the effects and before the drift beside the state, which the histories record, and `apply(..., drift=False)` applies the effects alone, the device by which a step's events are applied one at a time in the order-independence tests.) - **Preconditions** can hold numeric threshold literals: `agent.NUMFL.1 > 0.75`, or its negation `agent.NUMFL.1 <= 0.75`. The threshold is a point of the range, drawn from `event_types.preconditions.numeric_thresholds` (default the quarter points, 0.25, 0.5, and 0.75). A share `event_types.preconditions.numeric_share` of precondition literals (default 0.3) is numeric when numeric fluents exist. A literal must be achievable, by the initial distribution, by drift, or by some effect. (As built, stage b2: as for effects, each Boolean literal is drawn as before and then replaced by a numeric one of the same role at the share, from `world:numeric_preconditions`, the numeric literal drawn again until it is achievable; `numeric_thresholds` are fractions of the range, strictly between 0 and 1. A literal `> t` is achievable when some entity's initial value is above `t`, the fluent drifts upward, or some effect adds to it; `<= t` when some initial value is at or below `t`, the fluent drifts downward, or some effect subtracts; a threshold at or outside the ends of the range is never a literal. A numeric literal's record carries `operator` and `threshold` in place of a `value`. A numeric literal reads its fluent for non-interference, and a numeric effect writes it. The redraw of a never-legal event type's preconditions draws its numeric replacements from the part `world:numeric_preconditions: