openworldformat 0.4.4__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (29) hide show
  1. openworldformat-0.4.4/PKG-INFO +278 -0
  2. openworldformat-0.4.4/README.md +254 -0
  3. openworldformat-0.4.4/openworldformat/__init__.py +1765 -0
  4. openworldformat-0.4.4/openworldformat/agent.py +142 -0
  5. openworldformat-0.4.4/openworldformat/cinematography.py +404 -0
  6. openworldformat-0.4.4/openworldformat/dataset.py +83 -0
  7. openworldformat-0.4.4/openworldformat/entity_refs.py +25 -0
  8. openworldformat-0.4.4/openworldformat/eval.py +226 -0
  9. openworldformat-0.4.4/openworldformat/physics.py +424 -0
  10. openworldformat-0.4.4/openworldformat/soundtrack.py +94 -0
  11. openworldformat-0.4.4/openworldformat.egg-info/PKG-INFO +278 -0
  12. openworldformat-0.4.4/openworldformat.egg-info/SOURCES.txt +27 -0
  13. openworldformat-0.4.4/openworldformat.egg-info/dependency_links.txt +1 -0
  14. openworldformat-0.4.4/openworldformat.egg-info/top_level.txt +1 -0
  15. openworldformat-0.4.4/pyproject.toml +41 -0
  16. openworldformat-0.4.4/setup.cfg +4 -0
  17. openworldformat-0.4.4/tests/test_agent.py +76 -0
  18. openworldformat-0.4.4/tests/test_alignment.py +890 -0
  19. openworldformat-0.4.4/tests/test_authoring_outcomes.py +100 -0
  20. openworldformat-0.4.4/tests/test_cinematography.py +179 -0
  21. openworldformat-0.4.4/tests/test_dataset.py +40 -0
  22. openworldformat-0.4.4/tests/test_entity_refs.py +179 -0
  23. openworldformat-0.4.4/tests/test_eval.py +121 -0
  24. openworldformat-0.4.4/tests/test_example.py +107 -0
  25. openworldformat-0.4.4/tests/test_fold.py +231 -0
  26. openworldformat-0.4.4/tests/test_head_first.py +171 -0
  27. openworldformat-0.4.4/tests/test_merge_corpus.py +63 -0
  28. openworldformat-0.4.4/tests/test_physics.py +124 -0
  29. openworldformat-0.4.4/tests/test_soundtrack.py +50 -0
@@ -0,0 +1,278 @@
1
+ Metadata-Version: 2.4
2
+ Name: openworldformat
3
+ Version: 0.4.4
4
+ Summary: The Open World Format research package: parse and fold a .world in pure Python, no engine required.
5
+ Author: The Open World Format contributors
6
+ License-Expression: Apache-2.0
7
+ Project-URL: Repository, https://github.com/openworldformat/openworldformat
8
+ Project-URL: Documentation, https://openworldformat.org
9
+ Project-URL: Homepage, https://openworldformat.org
10
+ Keywords: 3d,world,gltf,scene,format,session,crdt,replay,benchmark,dataset
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Intended Audience :: Science/Research
14
+ Classifier: Programming Language :: Python :: 3 :: Only
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: 3.13
19
+ Classifier: Programming Language :: Python :: 3.14
20
+ Classifier: Topic :: File Formats
21
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
22
+ Requires-Python: >=3.10
23
+ Description-Content-Type: text/markdown
24
+
25
+ # openworldformat (PyPI)
26
+
27
+ The Open World Format research package. The core — parse a `.world`
28
+ manifest, fold its session log — is pure standard library, no
29
+ dependencies, and deliberately **no renderer**: this is the format's
30
+ research surface, not a viewer. Embodied-agent benchmarks (tasks as
31
+ predicates over the state document, trajectories in the format's own
32
+ op vocabulary), dataset tooling over session logs (fold a thousand
33
+ recordings, compute one number), and notebooks — none of that wants
34
+ three.js or Bevy; all of it wants `pip install openworldformat` and
35
+ a fold.
36
+
37
+ It is also the fold contract's third independently-built
38
+ implementation, sharing no code or toolchain with the JS reference or
39
+ the Rust one — every conformance world and outcome assertion the `js`
40
+ CI job runs, this package runs too, and the two folds agree
41
+ byte-for-byte on the example packages.
42
+
43
+ ## Install
44
+
45
+ ```bash
46
+ pip install openworldformat
47
+ ```
48
+
49
+ ## Use
50
+
51
+ ```python
52
+ from pathlib import Path
53
+ from openworldformat import read_package, fold_log, to_manifest
54
+
55
+ # A head-first package: manifest.json is the world now (`head`); the log
56
+ # folds over snapshots/base.json back to it.
57
+ base, entries, head, package = read_package(Path("world"))
58
+
59
+ state = fold_log(base, entries)
60
+ state["entities"] # the world at head revision
61
+ state["applied_edits"] # how many edits the log held
62
+ to_manifest(state) # the whole manifest again — the fold is total
63
+ ```
64
+
65
+ The fold applies the spec's rules: only `edit` ops change the document;
66
+ `tool`, `input`, `state` and `clock` fold to nothing; a `Batch` applies
67
+ all-or-nothing; ids and names stay unique; deleting an entity deletes
68
+ its descendants; it stops at the first entry that no longer applies.
69
+
70
+ Branches are tips: `build_history` gives a log its shape, `fold_path`
71
+ folds any branch of it. Session `state` ops fold separately, over
72
+ `state.json` (`fold_state`). `openworldformat.physics` carries the
73
+ `ext-physics` extension's executable half — the deterministic reference
74
+ solver, trajectory write/fold, and the outcome runner:
75
+
76
+ ```python
77
+ from openworldformat.physics import simulate_physics, fold_trajectories, run_outcomes
78
+
79
+ sim = simulate_physics(manifest, {"until_s": 6}) # deterministic
80
+ sim["contacts"] # impacts: {t_s, body, other, position, normal_speed}
81
+ sim["resting"] # where each dynamic body ended up
82
+ track = fold_trajectories(entries) # scrub recorded motion, no solver
83
+ run_outcomes(manifest, outcomes_doc) # the conformance assertions
84
+ ```
85
+
86
+ The solver is deliberately minimal — spheres against floors and
87
+ axis-aligned statics, fixed 1/120 s semi-implicit Euler, sleep on rest —
88
+ so the conformance outcome assertions run anywhere and in CI.
89
+ Reference-grade, not production physics; deterministic within an
90
+ engine, semantic-only across engines, exactly as the core spec refuses
91
+ bit-exactness.
92
+
93
+ ## Live authoring: agents outside, ops in
94
+
95
+ What a host does with an agent that wants to change the world:
96
+ `ingest` a batch against the fold — names bind to ids (a string where
97
+ an entity id goes is a name), a spawn without an id gets the next one,
98
+ partial transforms and materials merge with what's there, keys the
99
+ format would drop are refused with a path to each, and any failure
100
+ refuses the whole batch. The state you pass in is never touched:
101
+
102
+ ```python
103
+ from openworldformat import fold_log, ingest
104
+
105
+ done = ingest(state, [{"SpawnEntity": {"entity": {"name": "lamp", "parent": "crate"}}}])
106
+ done["ok"] # True
107
+ done["spawned"] # {"lamp": 3} — the ids the batch's names got
108
+ done["ops"] # the committed ops, bound: serialize into an entry and append
109
+ done["state"] # the new world; the old one is unchanged
110
+ ```
111
+
112
+ `merge_patch` is the RFC 7396 helper the merges use; `manifest_text`
113
+ is the canonical pretty form an authority writes to `manifest.json` —
114
+ members sorted, nulls left out, entities by id, plain arrays inline —
115
+ so the same world is always the same bytes.
116
+
117
+ ## Tasks: the benchmark harness
118
+
119
+ A task is a world, a trajectory, and a goal — a list of predicates over
120
+ what the fold produces. Scoring is therefore deterministic and needs no
121
+ engine: fold the log, evaluate the predicates. `examples/tasks/` turns
122
+ the repository's four example packages into a benchmark dataset, each
123
+ package's own log its solution:
124
+
125
+ ```bash
126
+ python -m openworldformat.eval examples/tasks/
127
+ ```
128
+
129
+ ```
130
+ ok the-drop-test: the switch scores (1 edits, 6 ops, 1 authors, revision 1)
131
+ ok forked-exploration: the moat variant wins this branch (3 edits, 4 ops, 3 authors, revision 4)
132
+ ok hello-world: hang the lantern (1 edits, 5 ops, 3 authors, revision 1)
133
+ ok speedrun-fork: noor's run, on its own branch (2 edits, 9 ops, 2 authors, revision 2)
134
+ ```
135
+
136
+ A task file names the world and states the goal; a `tip` folds a branch
137
+ (a run, a variant) instead of the head; a budget caps the cost:
138
+
139
+ ```json
140
+ {
141
+ "task": "hello-world: hang the lantern",
142
+ "world": "../../../examples/hello-world",
143
+ "budget": { "max_edits": 1, "max_entries": 5 },
144
+ "goal": [
145
+ { "exists": { "entity": "lantern" } },
146
+ { "near": { "entity": "lantern", "position": [-12.0, 0.0, 3.0], "tolerance": 0.5 } },
147
+ { "field": { "name": "score.tour", "equals": 1 } }
148
+ ]
149
+ }
150
+ ```
151
+
152
+ | Predicate | Holds when |
153
+ |---|---|
154
+ | `{"exists": {"entity": name}}` | the fold holds an entity by that name |
155
+ | `{"gone": {"entity": name}}` | it doesn't |
156
+ | `{"near": {"entity", "position", "tolerance"=0.15}}` | the entity's folded position is within tolerance |
157
+ | `{"field": {"name", "equals"?, "at_least"?, "at_most"?}}` | the state document's value compares |
158
+
159
+ Budgets (`max_edits`, `max_ops`, `max_entries`, inclusive) are the cost
160
+ side of a task: solve it, but not by a million edits. Metrics report
161
+ the trajectory's shape — edits, ops, entries, authors (the audit
162
+ trail's model-vs-visitor filter is one field), span, revision.
163
+
164
+ To score an agent, don't write files at all — pass its ops to
165
+ `run_task` the moment it emits them:
166
+
167
+ ```python
168
+ from openworldformat.eval import run_task
169
+
170
+ result = run_task(manifest, agent_entries, task, state_doc)
171
+ result["ok"], result["failures"], result["metrics"]
172
+ ```
173
+
174
+ This task language is deliberately library-level, not a spec
175
+ extension; it graduates to `spec/extensions/` when a second implementer
176
+ wants it.
177
+
178
+ ## Agents: the benchmark's other half
179
+
180
+ Scoring a recorded trajectory is one loop; scoring a live agent is the
181
+ same loop with the recording still wet. `run_agent` keeps the entries,
182
+ folds between steps so the agent observes its own edits, and scores
183
+ with `run_task`:
184
+
185
+ ```python
186
+ from openworldformat.agent import run_agent
187
+
188
+ result = run_agent(manifest, task, state_doc) # template_agent inside
189
+ result["ok"], result["metrics"], result["entries"]
190
+ ```
191
+
192
+ An agent is `f(document, values, task, entries) -> entry | None`: the
193
+ folded world and state values as observation, one entry of ops as
194
+ action, `None` to stop. The built-in `template_agent` is the baseline —
195
+ a compiler from goal predicates to ops, not a brain; it exists so
196
+ benchmark authors have a floor to beat and a skeleton to copy. Its
197
+ entries serialize to `ops.jsonl` like any recording: an agent run is a
198
+ log, ready to join a dataset.
199
+
200
+ ## Dataset tooling: fold a corpus, compute one number
201
+
202
+ A folder of `.world` packages is a dataset — each `ops.jsonl` a
203
+ recording, each fold a row:
204
+
205
+ ```bash
206
+ python -m openworldformat.dataset examples/
207
+ ```
208
+
209
+ ```
210
+ world entities edits ops entries authors tips span_s
211
+ forked-exploration 5 4 5 5 host, llm, maya 2 0.4
212
+ hello-world 14 1 5 5 llm, maya, visitor-7 1 1.877
213
+ speedrun-fork 9 2 15 4 kai, maya, noor 2 20.0
214
+ the-drop-test 7 1 6 2 host 1 0.2
215
+ ```
216
+
217
+ The authors column is the audit trail's dimension — model, host and
218
+ visitor in one recording, separable by one field. The tips column
219
+ counts branches; fold each with `fold_path` and the counterfactuals
220
+ come with the corpus. `summarize(dir)` gives the row as a dict for
221
+ notebooks.
222
+
223
+ ## API
224
+
225
+ | Export | What it does |
226
+ |---|---|
227
+ | `parse_manifest(text, strict=False)` | parse and version-check a world document (strict also enforces the schema's key sets) |
228
+ | `parse_log_line(line, strict=False)` | parse one `ops.jsonl` line, ops classified |
229
+ | `classify_op(op)` | recognize an op by shape — edits first |
230
+ | `op_kind_shape_ok(kind)` | the shape collision rule: edits PascalCase, history lowercase |
231
+ | `edit_ops(entry)` | an entry's edits, in order |
232
+ | `canonical_json(value)` | the canonical form for hashing — sorted, whitespace-free |
233
+ | `compute_entry_id(entry)` | an entry's content id, `sha256:<hex>` |
234
+ | `manifest_text(manifest)` | the canonical pretty text an authority writes to `manifest.json` |
235
+ | `fold_log(manifest, entries)` | the document at the last entry (the linear fold; by-name references bind to ids here, at ingestion) |
236
+ | `build_history(entries)` | a log's ids, parents, children and tips |
237
+ | `fold_path(manifest, entries, tip=None)` | the document at any tip (a branch) |
238
+ | `fold_state(state_doc, entries)` | the state document's values at the last entry |
239
+ | `compute_inverse(op, state)` | the inverse of one edit against the state it's about to change — undo is appending it |
240
+ | `merge_branch(state, entries)` | a branch's entries rewritten onto a main fold, colliding ids reallocated |
241
+ | `snapshot_filename(entry_id, revision)` | where a snapshot goes: by entry id, else by revision |
242
+ | `compact_package(package_json, head_revision)` | the package.json of a compaction |
243
+ | `read_package(world_dir)` | a head-first package: `(base, entries, head, package_json)` |
244
+ | `to_manifest(state)` | the fold's state as a whole manifest — `to_manifest(fold_log(m, []))` is `m` again |
245
+ | `ingest(state, batch)` | live authoring: bind, validate and apply a batch whole-or-refused; the state passed in is never touched; `warnings` for what validation warns about in the new world (a behavior whose target the batch deleted) |
246
+ | `merge_patch(current, change)` | JSON merge patch, RFC 7396 — the rule partial transforms merge by |
247
+ | `compact(world_dir, head_revision=None)` | move the base up to the head (or a revision): `snapshots/base.json` rewritten, the entries it holds archived |
248
+ | `ext_provenance(manifest)` | the provenance extension's five lineage fields, or None |
249
+
250
+ `openworldformat.physics` mirrors the npm package's
251
+ `openworldformat/physics`: `collect_physics`, `simulate_physics`,
252
+ `fold_trajectories`, `trajectory_op`, `run_outcomes`.
253
+ `openworldformat.cinematography` mirrors
254
+ `openworldformat/cinematography`: `camera_of`, `frame_of`, `view_of`,
255
+ `project`, `world_origin`, `aim_point_of`, `focus_distance_of`,
256
+ `shot_list`, `run_outcomes`.
257
+ `openworldformat.soundtrack` holds the soundtrack curves — `curve_at`,
258
+ `beat_at`, `section_at`, `modulation_factor` — as plain functions over
259
+ plain numbers, for analysis rather than playback.
260
+ `openworldformat.eval` scores tasks (see above): `run_task`,
261
+ `load_task_file`, and the `python -m openworldformat.eval` CLI.
262
+ `openworldformat.agent` runs agents against them: `run_agent` and the
263
+ `template_agent` baseline. `openworldformat.dataset` summarizes
264
+ recordings — `summarize` and the `python -m openworldformat.dataset`
265
+ report.
266
+
267
+ ## Test
268
+
269
+ ```bash
270
+ python -m unittest discover -s tests -t .
271
+ ```
272
+
273
+ The suite mirrors the JS tests — it folds the repository's own example
274
+ packages, replays the drop-test recording through the solver, and runs
275
+ the conformance outcome assertions — so both references assert the same
276
+ facts.
277
+
278
+ Apache-2.0.
@@ -0,0 +1,254 @@
1
+ # openworldformat (PyPI)
2
+
3
+ The Open World Format research package. The core — parse a `.world`
4
+ manifest, fold its session log — is pure standard library, no
5
+ dependencies, and deliberately **no renderer**: this is the format's
6
+ research surface, not a viewer. Embodied-agent benchmarks (tasks as
7
+ predicates over the state document, trajectories in the format's own
8
+ op vocabulary), dataset tooling over session logs (fold a thousand
9
+ recordings, compute one number), and notebooks — none of that wants
10
+ three.js or Bevy; all of it wants `pip install openworldformat` and
11
+ a fold.
12
+
13
+ It is also the fold contract's third independently-built
14
+ implementation, sharing no code or toolchain with the JS reference or
15
+ the Rust one — every conformance world and outcome assertion the `js`
16
+ CI job runs, this package runs too, and the two folds agree
17
+ byte-for-byte on the example packages.
18
+
19
+ ## Install
20
+
21
+ ```bash
22
+ pip install openworldformat
23
+ ```
24
+
25
+ ## Use
26
+
27
+ ```python
28
+ from pathlib import Path
29
+ from openworldformat import read_package, fold_log, to_manifest
30
+
31
+ # A head-first package: manifest.json is the world now (`head`); the log
32
+ # folds over snapshots/base.json back to it.
33
+ base, entries, head, package = read_package(Path("world"))
34
+
35
+ state = fold_log(base, entries)
36
+ state["entities"] # the world at head revision
37
+ state["applied_edits"] # how many edits the log held
38
+ to_manifest(state) # the whole manifest again — the fold is total
39
+ ```
40
+
41
+ The fold applies the spec's rules: only `edit` ops change the document;
42
+ `tool`, `input`, `state` and `clock` fold to nothing; a `Batch` applies
43
+ all-or-nothing; ids and names stay unique; deleting an entity deletes
44
+ its descendants; it stops at the first entry that no longer applies.
45
+
46
+ Branches are tips: `build_history` gives a log its shape, `fold_path`
47
+ folds any branch of it. Session `state` ops fold separately, over
48
+ `state.json` (`fold_state`). `openworldformat.physics` carries the
49
+ `ext-physics` extension's executable half — the deterministic reference
50
+ solver, trajectory write/fold, and the outcome runner:
51
+
52
+ ```python
53
+ from openworldformat.physics import simulate_physics, fold_trajectories, run_outcomes
54
+
55
+ sim = simulate_physics(manifest, {"until_s": 6}) # deterministic
56
+ sim["contacts"] # impacts: {t_s, body, other, position, normal_speed}
57
+ sim["resting"] # where each dynamic body ended up
58
+ track = fold_trajectories(entries) # scrub recorded motion, no solver
59
+ run_outcomes(manifest, outcomes_doc) # the conformance assertions
60
+ ```
61
+
62
+ The solver is deliberately minimal — spheres against floors and
63
+ axis-aligned statics, fixed 1/120 s semi-implicit Euler, sleep on rest —
64
+ so the conformance outcome assertions run anywhere and in CI.
65
+ Reference-grade, not production physics; deterministic within an
66
+ engine, semantic-only across engines, exactly as the core spec refuses
67
+ bit-exactness.
68
+
69
+ ## Live authoring: agents outside, ops in
70
+
71
+ What a host does with an agent that wants to change the world:
72
+ `ingest` a batch against the fold — names bind to ids (a string where
73
+ an entity id goes is a name), a spawn without an id gets the next one,
74
+ partial transforms and materials merge with what's there, keys the
75
+ format would drop are refused with a path to each, and any failure
76
+ refuses the whole batch. The state you pass in is never touched:
77
+
78
+ ```python
79
+ from openworldformat import fold_log, ingest
80
+
81
+ done = ingest(state, [{"SpawnEntity": {"entity": {"name": "lamp", "parent": "crate"}}}])
82
+ done["ok"] # True
83
+ done["spawned"] # {"lamp": 3} — the ids the batch's names got
84
+ done["ops"] # the committed ops, bound: serialize into an entry and append
85
+ done["state"] # the new world; the old one is unchanged
86
+ ```
87
+
88
+ `merge_patch` is the RFC 7396 helper the merges use; `manifest_text`
89
+ is the canonical pretty form an authority writes to `manifest.json` —
90
+ members sorted, nulls left out, entities by id, plain arrays inline —
91
+ so the same world is always the same bytes.
92
+
93
+ ## Tasks: the benchmark harness
94
+
95
+ A task is a world, a trajectory, and a goal — a list of predicates over
96
+ what the fold produces. Scoring is therefore deterministic and needs no
97
+ engine: fold the log, evaluate the predicates. `examples/tasks/` turns
98
+ the repository's four example packages into a benchmark dataset, each
99
+ package's own log its solution:
100
+
101
+ ```bash
102
+ python -m openworldformat.eval examples/tasks/
103
+ ```
104
+
105
+ ```
106
+ ok the-drop-test: the switch scores (1 edits, 6 ops, 1 authors, revision 1)
107
+ ok forked-exploration: the moat variant wins this branch (3 edits, 4 ops, 3 authors, revision 4)
108
+ ok hello-world: hang the lantern (1 edits, 5 ops, 3 authors, revision 1)
109
+ ok speedrun-fork: noor's run, on its own branch (2 edits, 9 ops, 2 authors, revision 2)
110
+ ```
111
+
112
+ A task file names the world and states the goal; a `tip` folds a branch
113
+ (a run, a variant) instead of the head; a budget caps the cost:
114
+
115
+ ```json
116
+ {
117
+ "task": "hello-world: hang the lantern",
118
+ "world": "../../../examples/hello-world",
119
+ "budget": { "max_edits": 1, "max_entries": 5 },
120
+ "goal": [
121
+ { "exists": { "entity": "lantern" } },
122
+ { "near": { "entity": "lantern", "position": [-12.0, 0.0, 3.0], "tolerance": 0.5 } },
123
+ { "field": { "name": "score.tour", "equals": 1 } }
124
+ ]
125
+ }
126
+ ```
127
+
128
+ | Predicate | Holds when |
129
+ |---|---|
130
+ | `{"exists": {"entity": name}}` | the fold holds an entity by that name |
131
+ | `{"gone": {"entity": name}}` | it doesn't |
132
+ | `{"near": {"entity", "position", "tolerance"=0.15}}` | the entity's folded position is within tolerance |
133
+ | `{"field": {"name", "equals"?, "at_least"?, "at_most"?}}` | the state document's value compares |
134
+
135
+ Budgets (`max_edits`, `max_ops`, `max_entries`, inclusive) are the cost
136
+ side of a task: solve it, but not by a million edits. Metrics report
137
+ the trajectory's shape — edits, ops, entries, authors (the audit
138
+ trail's model-vs-visitor filter is one field), span, revision.
139
+
140
+ To score an agent, don't write files at all — pass its ops to
141
+ `run_task` the moment it emits them:
142
+
143
+ ```python
144
+ from openworldformat.eval import run_task
145
+
146
+ result = run_task(manifest, agent_entries, task, state_doc)
147
+ result["ok"], result["failures"], result["metrics"]
148
+ ```
149
+
150
+ This task language is deliberately library-level, not a spec
151
+ extension; it graduates to `spec/extensions/` when a second implementer
152
+ wants it.
153
+
154
+ ## Agents: the benchmark's other half
155
+
156
+ Scoring a recorded trajectory is one loop; scoring a live agent is the
157
+ same loop with the recording still wet. `run_agent` keeps the entries,
158
+ folds between steps so the agent observes its own edits, and scores
159
+ with `run_task`:
160
+
161
+ ```python
162
+ from openworldformat.agent import run_agent
163
+
164
+ result = run_agent(manifest, task, state_doc) # template_agent inside
165
+ result["ok"], result["metrics"], result["entries"]
166
+ ```
167
+
168
+ An agent is `f(document, values, task, entries) -> entry | None`: the
169
+ folded world and state values as observation, one entry of ops as
170
+ action, `None` to stop. The built-in `template_agent` is the baseline —
171
+ a compiler from goal predicates to ops, not a brain; it exists so
172
+ benchmark authors have a floor to beat and a skeleton to copy. Its
173
+ entries serialize to `ops.jsonl` like any recording: an agent run is a
174
+ log, ready to join a dataset.
175
+
176
+ ## Dataset tooling: fold a corpus, compute one number
177
+
178
+ A folder of `.world` packages is a dataset — each `ops.jsonl` a
179
+ recording, each fold a row:
180
+
181
+ ```bash
182
+ python -m openworldformat.dataset examples/
183
+ ```
184
+
185
+ ```
186
+ world entities edits ops entries authors tips span_s
187
+ forked-exploration 5 4 5 5 host, llm, maya 2 0.4
188
+ hello-world 14 1 5 5 llm, maya, visitor-7 1 1.877
189
+ speedrun-fork 9 2 15 4 kai, maya, noor 2 20.0
190
+ the-drop-test 7 1 6 2 host 1 0.2
191
+ ```
192
+
193
+ The authors column is the audit trail's dimension — model, host and
194
+ visitor in one recording, separable by one field. The tips column
195
+ counts branches; fold each with `fold_path` and the counterfactuals
196
+ come with the corpus. `summarize(dir)` gives the row as a dict for
197
+ notebooks.
198
+
199
+ ## API
200
+
201
+ | Export | What it does |
202
+ |---|---|
203
+ | `parse_manifest(text, strict=False)` | parse and version-check a world document (strict also enforces the schema's key sets) |
204
+ | `parse_log_line(line, strict=False)` | parse one `ops.jsonl` line, ops classified |
205
+ | `classify_op(op)` | recognize an op by shape — edits first |
206
+ | `op_kind_shape_ok(kind)` | the shape collision rule: edits PascalCase, history lowercase |
207
+ | `edit_ops(entry)` | an entry's edits, in order |
208
+ | `canonical_json(value)` | the canonical form for hashing — sorted, whitespace-free |
209
+ | `compute_entry_id(entry)` | an entry's content id, `sha256:<hex>` |
210
+ | `manifest_text(manifest)` | the canonical pretty text an authority writes to `manifest.json` |
211
+ | `fold_log(manifest, entries)` | the document at the last entry (the linear fold; by-name references bind to ids here, at ingestion) |
212
+ | `build_history(entries)` | a log's ids, parents, children and tips |
213
+ | `fold_path(manifest, entries, tip=None)` | the document at any tip (a branch) |
214
+ | `fold_state(state_doc, entries)` | the state document's values at the last entry |
215
+ | `compute_inverse(op, state)` | the inverse of one edit against the state it's about to change — undo is appending it |
216
+ | `merge_branch(state, entries)` | a branch's entries rewritten onto a main fold, colliding ids reallocated |
217
+ | `snapshot_filename(entry_id, revision)` | where a snapshot goes: by entry id, else by revision |
218
+ | `compact_package(package_json, head_revision)` | the package.json of a compaction |
219
+ | `read_package(world_dir)` | a head-first package: `(base, entries, head, package_json)` |
220
+ | `to_manifest(state)` | the fold's state as a whole manifest — `to_manifest(fold_log(m, []))` is `m` again |
221
+ | `ingest(state, batch)` | live authoring: bind, validate and apply a batch whole-or-refused; the state passed in is never touched; `warnings` for what validation warns about in the new world (a behavior whose target the batch deleted) |
222
+ | `merge_patch(current, change)` | JSON merge patch, RFC 7396 — the rule partial transforms merge by |
223
+ | `compact(world_dir, head_revision=None)` | move the base up to the head (or a revision): `snapshots/base.json` rewritten, the entries it holds archived |
224
+ | `ext_provenance(manifest)` | the provenance extension's five lineage fields, or None |
225
+
226
+ `openworldformat.physics` mirrors the npm package's
227
+ `openworldformat/physics`: `collect_physics`, `simulate_physics`,
228
+ `fold_trajectories`, `trajectory_op`, `run_outcomes`.
229
+ `openworldformat.cinematography` mirrors
230
+ `openworldformat/cinematography`: `camera_of`, `frame_of`, `view_of`,
231
+ `project`, `world_origin`, `aim_point_of`, `focus_distance_of`,
232
+ `shot_list`, `run_outcomes`.
233
+ `openworldformat.soundtrack` holds the soundtrack curves — `curve_at`,
234
+ `beat_at`, `section_at`, `modulation_factor` — as plain functions over
235
+ plain numbers, for analysis rather than playback.
236
+ `openworldformat.eval` scores tasks (see above): `run_task`,
237
+ `load_task_file`, and the `python -m openworldformat.eval` CLI.
238
+ `openworldformat.agent` runs agents against them: `run_agent` and the
239
+ `template_agent` baseline. `openworldformat.dataset` summarizes
240
+ recordings — `summarize` and the `python -m openworldformat.dataset`
241
+ report.
242
+
243
+ ## Test
244
+
245
+ ```bash
246
+ python -m unittest discover -s tests -t .
247
+ ```
248
+
249
+ The suite mirrors the JS tests — it folds the repository's own example
250
+ packages, replays the drop-test recording through the solver, and runs
251
+ the conformance outcome assertions — so both references assert the same
252
+ facts.
253
+
254
+ Apache-2.0.