Skip to content

Releases: OnlyWorlds/unity-sdk

OnlyWorlds Unity SDK 0.9.0

Choose a tag to compare

@tmoosting tmoosting released this 20 Oct 21:24

Party links: one join link seats a table of players, each picking a Character from the link's list (or typing a name
at a table that lists none), walked live on onlyworlds.com through a game's door; the wire check now reads request
and response bodies and error codes; the schema pin moves to dist.35. A minor release because it adds public API
(below).

Added

  • Party links (one join link for a table of up to 50 players): OWJoinPreview reads MaxUses, UsesLeft,
    NameRequired and Characters (OWJoinPick: id, name, image url, taken), and IsTable tells a party link from a
    link for one, which sends none of them.
  • OWJoin.RedeemAsync takes character, the player's pick from the preview's list. It is the last parameter, so
    existing positional calls keep working.
  • OWApiError.IsCharacterTaken, IsCharacterInvalid and IsWorldFull, matching the npm SDK's helpers.

Changed

  • The schema pin moves to dist.35 (v0.32.2-dist.35; canonical 00.32.02 unchanged). The portal's example download,
    vendored for the envelope tests, now carries world.source and world.exported_as as keel writes them, and the
    reader and the file export keep both. The generated models changed only in their serial.
  • The wire check also compares request body keys, response keys and error codes with keel's OpenAPI document
    (tools/refresh_wire.py; the fixture refreshed from production). The SDK's join keys, the preview's and a join's
    answer keys, and every code an OWApiError helper names are checked against what keel declares.

OnlyWorlds Unity SDK 0.8.0

Choose a tag to compare

@tmoosting tmoosting released this 20 Oct 18:18

A world folder zipped as one .owworld file imports into Unity as a read-only world cache, measured in the Editor, a
desktop player, an Android IL2CPP player and a WebGL player; the calendar reader follows the new atlas_calendar
profile and its conformance fixture; the schema pin moves to dist.33. A minor release because it adds public API
(below).

Added

  • World archives, .owworld (a spec world zip, renamed: one world, as one top-level folder or with world.json
    at the root). Drop one into Assets/ and it imports as a read-only OWWorldCache; a zip with no world or two is an
    import error, never a guess. The cache is keyed by the world's id and the archive asset's GUID
    (OWWorldArchive.KeyFor(worldId, guid)), so the asset carries no machine's path and keeps its key through a move.
    The archive is read in memory, never extracted, and an entry name that is absolute or climbs out of the world
    refuses the archive. Assets/OnlyWorlds/Pack World Folder... packs a folder from anywhere
    on disk into the project and imports it; OWWorldArchive.Pack(folder, archive) is the same step from code. Pack
    writes only the archive, never into the folder; packs world.json, elements/, spatial/ and media/, leaving out
    dot-folders, dot-files and Unity .meta files, and records/ whole and byte for byte (folder spec §4.5: a producer's
    files are never dropped; the importer reads none of them); drops world.json's api block (it holds a key) unless asked to
    keep it; and is deterministic (ordinal entry order, a fixed timestamp). OWWorldArchive.Read(archive) gives a folder
    read's result. Media travels in the archive but is not imported yet.
  • OWFolderReader.ReadEntries(entries, includeLegacySpatial?, source?) reads a world held in memory, as
    (path inside the world, text) pairs, by the folder read's own rules and code; Read(folder) is unchanged.
  • OWFolderLoader.LoadInto(cache, read, key, readOnly?) fills a cache from a read already done, so it is not paid
    twice. LoadInto(cache, path) now calls it and behaves as before.
  • Measured on Tuvsh Ul (3,829 elements): an imported archive loads about 5-6x faster than the folder reader from
    cold in a release desktop player, and the asset and a runtime OWWorldArchive.LoadInto both work in an Android
    IL2CPP player and a WebGL player (Minimal stripping; higher stripping untested).

Changed

  • OWJoin.ParseLink names the link shape with the preview on, <site>/join/<world_id>#<code> (keel #422), and a
    test holds it. Its behaviour is unchanged: it always read the code after the last #.
  • The schema pin moves from dist.24 to v0.32.2-dist.33 (canonical 00.32.02): two descriptions in the generated
    models (an Event's characters are those who know of it; a Narrative's dates are when it is told), and
    ow-presentation.json gains each type's public list (the fields shown on a known-of element). The walk and the
    rulings are unchanged.

Fixed

  • The calendar reader follows the atlas_calendar profile (schema-dist profiles/calendar.md, dist.33) and the
    TypeScript kernel 4.42.2, checked by the profile's conformance fixture, now vendored (OWCalendarFixtureTests: 52
    cases, 113 steps). A down era counts its years from the one holding the step before its pivot, so a mid-year pivot
    has no year 0, and ParseDate is its inverse again; at equal starts an up era wins over a down one, then the later
    in the list; an eras or intercalary that is present but not a list (false, 0, "") is not a calendar; a
    calendar written as a JSON string is read only when it is strict JSON.

OnlyWorlds Unity SDK 0.7.0

Choose a tag to compare

@tmoosting tmoosting released this 19 Oct 22:07

Join a world by link, read the one-file export and write it as a folder, and an export's world.json says where it
came from and which slice it is (folder format v0.3.9). A minor release because it adds public API (below).

Added

  • Joining a world by link (keel #212, #353): OWJoin.ParseLink(text) finds the ow_j_ code in a pasted link or
    a bare code; OWJoin.PreviewAsync(code, transport) (POST /join/preview) says who invites, to which world, for
    which reader, without spending the code; OWJoin.RedeemAsync(code, transport, name?, agentName?) (POST /join)
    spends it. A player's code answers with a read key (ow_r_, no PIN) and the Character it reads as; an agent's with
    a seat's write key and secret. Both send no credential, so they sit beside OWClient, which cannot be built
    without a key; npm's previewJoin and redeemJoin are the same wire. Every bad code is keel's one 404
    not_found (OWApiError.IsNotFound). The wire check records both calls (12 now).
  • The one-file world export (format: onlyworlds-world-export, spec §8c), the file an owner downloads from a
    world's page: OWEnvelopeReader.Read(path) / Parse(json) give a folder read's result (OWEnvelopeReadResult,
    with the file's Format, SchemaVersion and ExportedAt), and OWEnvelopeLoader.LoadInto(cache, path) fills a
    read-only cache. The reader keys on format alone; schema_version (the standard's version the elements follow)
    is kept as information, never checked. An element with no id is skipped and reported, as in a folder. The test
    fixture is keel's own emitter output, published in schema-dist (example/world-export.json, serial 25) and vendored
    byte for byte; a test holds its hash to the dist's MANIFEST.
  • The one-file export as a world folder: OWFolderExport.ExportFileAsync(path, folder, options?, progress?, ct?)
    writes the downloaded file as the same folder ExportAsync writes from the API (bodies as the file holds them, every
    key of its world carried into world.json, each Map's image fetched from its image_url), born whole or not at all.
    What a folder cannot keep whole is refused, not skipped: an element with no id, a type that is not the standard's,
    one id under two types.
  • The provenance lines (folder format v0.3.9, §4.2): an API export's world.json now carries source
    ({server, world, at}: the API's origin, the world's id, when the export began, as toISOString writes it) and,
    when GET /me says which slice the key reads, exported_as ({kind, role, character}), after the wire's keys, as
    the Python package's export_world writes them (0.7.0). OWFolderExport.ExportedAs(me, characterName) is the
    spec owner's table in one place; a refused /me, or a guest whose Character cannot be read, writes none. New:
    OWClient.GetMeAsync (GET /me/, in the wire check: 13 calls), OWFolderExportOptions.Clock.

Changed

  • An export declares folder format 0.3.9 (OWFolderExport.FormatVersion), as the package's 0.7.0 does.

OnlyWorlds Unity SDK 0.6.0

Choose a tag to compare

@tmoosting tmoosting released this 17 Oct 09:08

A world folder that Atlas wrote now opens whole: its pins' and markers' links, and the older spatial/ layout.
A minor release because it adds public API (below).

Fixed

  • A world folder's pins and markers keep their links when Atlas spelled them map_id and zone_id. The spec
    says a reader accepts both spellings, and Atlas writes the _id forms. OWFolderLoader.LoadInto gave the cache the
    body as written, so a pin from an Atlas folder had no Map and a marker no Map or Zone. The cache now gets
    OWFolderReader.WithSchemaLinkNames(body): the schema's name, in the old one's place, when the schema's name is not
    already there. The read result's bodies and the files on disk are unchanged.
  • One id, one file (folder spec v0.3.8 §4). Where an older Atlas folder holds one id in both elements/ and
    spatial/, OWFolderReader.Read serves elements/ and logs the spatial/ copy in Skipped (it used to be loaded
    last, so the older copy won in the cache). OWFolderWriter.WriteElement writes a map, pin, zone or marker that
    lives under spatial/<type>/ there, in its own file, so an edit never leaves a second copy; a new element goes to
    elements/. OWFolderExport stamps format_version "0.3.8", as the Python package does since its 0.5.0.

Added

  • OWFolderReader.WithSchemaLinkNames(body): a body's links under the schema's names (map_id as map, zone_id
    as zone), a copy when anything differs; what the cache is given.
  • OWFolderReadResult.TimeCurrent: time_current, else the wire's time_range_current (both accepted, as the
    Python package reads them). OWFolderReadResult.ServerWorldId: a linked folder's api.world_id, else its id.
    WorldId stays the folder's own id, which keys the cache.
  • The reader's notes name the folder spec it follows: v0.3.8's reading rules (they said v0.3.5).

OnlyWorlds Unity SDK 0.5.0

Choose a tag to compare

@tmoosting tmoosting released this 16 Oct 13:52

Added

  • World calendars: OWCalendar reads a world's calendar (the atlas_calendar field Atlas writes on a calendar
    Construct) and formats, breaks down and parses dates on the world's integer time line: eras, months, weeks, leap and
    intercalary days. It is a port of the npm SDK's calendar kernel (@onlyworlds/sdk 4.23.0), checked against the same
    test vectors, so a date reads the same in Unity as in Atlas and Lantern. OWCalendarLens.WorldCalendars and
    ResolveLens find the calendars a world holds. A missing or malformed calendar reads as null, never an error.
  • The door of every write (OnlyWorlds keel #299; the npm SDK's 4.26.0 has the same). Agent = true on
    OWClientConfig sends X-OW-Agent on every element write (create, patch, delete, link edit, bulk), never on a read,
    and the server records those writes as agent-made. It is off by default and belongs only in a client an agent
    drives.
  • Every element reads the server-managed ChangedBy (the membership whose write last changed it) and ChangedVia (that
    write's door: seat, mcp, declared, api, v1, sync or server). A /changes delete reads DeletedBy and
    DeletedVia. OWWriteDoor.IsAgent(door) is true for seat, mcp and declared.
  • OWApiError.RetryAfter: the seconds to wait from the response's Retry-After header (a delay, or an HTTP date
    turned into one), or null. The server sends it on 503 server_busy, 429 and 409 idempotency_in_flight. It is read
    as the npm SDK reads it, and the client still never retries on its own.
  • Export an account world to a world folder: OWFolderExport.ExportAsync(client, folder) reads the world and all 22
    element types (GETs only) and writes the folder the onlyworlds Python package's export_world writes, a port of its
    rules: element files byte for byte, the collision rule's id-ascending names, time_current on disk,
    format_version "0.3.7". On Moppetopia's 310 elements it matched the package's output on the same wire bodies
    with no file different. Each Map's image is kept as Atlas keeps local media (media/<map id>/base-image/, as served,
    with its .meta.json sidecar); an image that will not fetch is a line in the report, never a failure. The folder
    is built beside its destination and moved into place, so a cancel or an error leaves no half-written world, and it
    must not exist or be empty. OWFolderExport.WorldForDisk is the world.json step on its own. The key and PIN go
    into no file and no message. On Windows a folder too deep for the files a world needs is refused before the first
    write: Unity's Mono does not take long paths.

Changed

  • Schema pin moved to v0.32.0-dist.24 (from dist.20): canonical unchanged. The element id is described as a
    UUID, new ids minted as v7 and earlier v4 ids still valid. The generated models changed only in their stamps.
  • New ids are UUIDv7, as the standard now says and as the server, the npm SDK (4.2.0 on) and the Python package
    mint them: CreateAsync and BulkAsync mint one with OWId.NewId() when an element has no id and no key is
    given, where they used a v4 Guid. Ids this client mints a millisecond or more apart sort by creation; a world still
    holds v4 ids, which stay valid, so order elements by created_at, never by id. A caller-supplied id is still sent as
    given.
  • Faster folder reads: OWFolderReader.Read (and so OWFolderLoader.LoadInto) reads a world's files on up to 8
    threads, then parses them one at a time in the same order as before. On a 20,570-element world it took about 2.0 s
    instead of 3.1 s, with the same elements, order, types and skipped files. Parsing stays on one thread on purpose: under
    Unity's Mono GC a parallel parse measured slower. WebGL players read one file at a time.

Fixed

  • The folder writer escapes strings as JSON.stringify does. OWFolderWriter.Serialize wrote U+2028, U+2029 and
    U+0085 as escapes, in values and in keys, where the reference implementation and the Python package write them
    raw, so a name holding one was a byte difference in every folder. It now matches the package's bytes on a body
    holding them, the short escapes, the other controls and a surrogate pair. A lone surrogate is written as its escape.
  • OWFolderWriter.Write settles a collision id-ascending, as the spec's writer rule requires: a batch is written
    lowest id first, so of two elements whose short filenames match (compared case-insensitively, on every filesystem)
    the lower id keeps the short form whatever order they came in. Into an empty folder it now names files exactly as
    the Python package does. Every body of a batch is checked before the first is written, and a refused batch writes
    nothing. WriteElement still never renames an element already in the folder.
  • ChangesAsync pages the feed. It sent its cursor as cursor=, and the changes feed takes since, refusing any
    other parameter with a 422. So OWSync.IncrementalAsync failed whenever a world had changed since its baseline. It
    now sends since.
  • A wire check against the API's own document. OWWireContractTests records every request OWClient can make and
    checks each against a snapshot of the server's published OpenAPI document: a path it serves, a method it takes
    there, and only query parameters and headers it declares. It catches both bugs above, which had passed their own
    tests since 0.1.0. tools/refresh_wire.py refreshes the snapshot (--check says whether the server's document has
    moved).
  • EditLinksAsync reaches the server. Since 0.1.0 it posted to /{type}/{id}/editLinks/, a route the API never
    served, so every call failed with 404. It now posts add and remove to /{type}/{id}/links/{field}. Its test had
    asserted the wrong URL; it now asserts the server's.
  • The folder writer's round-trip tests run in any host: they find their fixture through the package's own resolved
    path, so a project that lists the SDK in testables and references it by file: from outside its own folder runs
    all three instead of skipping them.
  • CreateAsync sends Idempotency-Key only when you pass one, as the npm SDK does. Since 0.1.0 it sent the
    element's id as the key on every create. The server keeps a keyed create's first answer for 24 hours and answers an
    identical repeat with it, writing nothing, so re-creating a deleted element under its old id (an undo) got the old
    201 back and the element stayed deleted. A retry without a key is safe when the element carries its own id: it
    answers 409 id_conflict, and the element is there. An element without one gets a fresh id on every call, so an
    unkeyed retry of it creates a second element, as in the npm SDK and the Python package; a caller who retries passes
    idempotencyKey, and the stored answer is replayed.
  • A keyed retry replays. With an idempotencyKey and no id, CreateAsync and BulkAsync minted a fresh id on
    every call, so a retry sent a different body and the server answered 409 idempotency_error instead of its stored
    answer. The id is now derived from the key and the element's position (OWId.Keyed, a UUIDv5 in the Python
    package's namespace), so a retry sends the same body. The Python package and the npm SDK (4.27.1) derive the same
    id, so a key mints one id in all three clients. Without a key, ids are minted as before.
  • OWSync.IncrementalAsync resumes from the server's own cursor, kept as is in the cache's new CursorToken,
    where it built one from the last change_seq. The server answers a guest key's bare number with 409
    resync_required, so a guest key's incremental sync failed on its first real change. A cache synced by an earlier
    version has no token and resumes from its change_seq, as before. A 409 resync_required now rebuilds the cache
    from the start, with a RebaselineReason saying why: a guest is never sent deletes, so only a rebuild drops what it
    can no longer see.
  • The sync reads the feed's epoch (OWChangesPage.Epoch, kept as OWWorldCache.Epoch). A guest's view can
    change with the head unmoved, so a cache at the head is current only when the epoch is unchanged too, and
    HasChangesAsync says so.
  • Each upsert is applied from the element it carries (OWChange.Element), where the sync fetched every changed
    element again: one request per page instead of one per change. If the feed reports more pages but gives no cursor,
    the cache keeps its change_seq and the last cursor the server gave, where it recorded the highest change_seq it
    had read; a bulk import gives many changes one change_seq, so that number could claim changes never read. An
    empty world's cache no longer rebuilds on every sync.
  • ChangesAsync sends limit: the client's PageSize, at most 1000, above which the feed answers 422. The
    server's default page was 500; the npm SDK asks for 100.
  • OWApiError.IsRetryable reads the error code, where it was any 429 or 5xx. A 409 idempotency_in_flight (the
    same key still running: send it again and it replays the first answer) now reads as retryable. A 429
    quota_exceeded (a daily allowance, used up until midnight UTC) and a 503 ask_unavailable or media_unavailable
    (a feature switched off) no longer do.
  • OWApiError.IsAuthError reads the error code, as the npm SDK does: invalid_credentials, key_revoked (and
    the reserved world_gone), where it was any 401 or 403. A 403 such as not_author or owner_only comes with a
    valid key, and the World Browser no longer tells you to check the key and PIN for one: it says the element is
    someone else's, that only the owner can do this, or that the key's scope does not allow it. IsNotFound is a 404
    with the code not_found, so a proxy's 404 is not mistaken for a missing element. New: IsNotAuthor,
    IsOwnerOnly, and IsResyncRequired.
  • **`OWKey.CanWrite(OWKeyK...
Read more

OnlyWorlds Unity SDK 0.4.0

Choose a tag to compare

@tmoosting tmoosting released this 11 Oct 12:31

Added

  • List filters: OWListParams.Name, NameContains, Supertype and Subtype send the API's name,
    name__icontains, supertype and subtype filters. ListAllAsync now carries every filter onto every page.

Fixed

  • The World Browser no longer demands a PIN to connect. Prefixed keys read without one, and a legacy key needs one
    only for writes or a private world, so the demo keys that worked in code were locked out of the browser. The
    settings window now says when a PIN is needed for each kind of key.
  • The editor's key, PIN and base URL are kept per project. They lived in one machine-wide EditorPrefs entry,
    so every project on the machine shared one key, and a key rotated for one world went stale in all the others.
    They are now scoped by the project's PlayerSettings.productGUID (clones and worktrees of one project share
    them), still in EditorPrefs, still never in the project. A project with no key of its own offers the
    machine-wide key an earlier version saved ("Use it here" in the settings window); it is never read silently.
  • The World Browser loads the whole world on connect, in the background, so every type's count and every
    link's name show before a type is clicked; before, a link read as a raw id and a count stayed blank until its
    type was opened. A type clicked during the load still opens at once. Unnamed markers read as their zone and
    order (" · 3") instead of "(unnamed)", and type names are capitalised.
  • The World Browser shows a folder world or a cache with no key. A key is needed to reach the API, not to
    read what is already on disk; with none set, the browser showed only the setup prompt, even over an open
    folder world, which needs no account at all.

Changed

  • Schema pin moved to v0.32.0-dist.20 (from dist.15, by way of dist.18 and dist.19): canonical
    00.32.00. The World gains length_unit, mass_unit and distance_unit (the world units) and created_at,
    updated_at and public_read, and loses api_key, user and version from its definition; GetWorldAsync
    returns the World as a JObject, so all of it arrives with no code change (world["length_unit"]). The element models changed only in
    their doc comments: the unit fields now name the World field they are measured in. In presentation.json,
    Zone's icon is now pentagon and Marker's polyline, and the World Browser draws both.
  • The World Browser reads like an editor list: the types in alphabetical order, each with its icon in its
    family's colour and its count; selectable rows with a hover state in place of button columns; the element's
    name, type, supertype and subtype as a header; and a link field shows the linked element's name, which opens
    it. A link resolves to any element the window has loaded, which, with the world loaded on connect, is every
    element; an id outside the world stays an id. The family legend is gone: the colours stay, the grouping is not shown. The 22 icons are Google's Material
    Symbols (Apache 2.0, licence in Editor/Icons/), rendered by tools/render_type_icons.py from the names in
    ow-presentation.json.
  • The READMEs open with a first run that needs no account: the demo key 0000000001 reads Moppetopia. Agent seats
    lead for writes. The account token is no longer offered to the client.

OnlyWorlds Unity SDK 0.3.0

Choose a tag to compare

@tmoosting tmoosting released this 08 Oct 21:02

All 22 OnlyWorlds element types as typed C# models, generated from the schema distribution.

  • Models for every element type, generated by codegen/generate_models.py; codegen/check_drift.py fails when a committed model no longer matches a fresh generation.
  • OWFolderWriter: writes a world folder to disk in the bytes the folder format specifies (LF, UTF-8 without a BOM, two-space indent, keys in their original order), so the same world writes the same files on every machine. OWFolderWriter.Parse reads JSON without rewriting timestamps.
  • OWElement.CreatedBy: who created an element on a shared world.
  • Writes now send "" for an unset string field, matching the API.

Install from the Package Manager with + → Add package from git URL:

https://github.com/OnlyWorlds/unity-sdk.git?path=/Packages/com.onlyworlds.sdk#v0.3.0

Full list: CHANGELOG.

OnlyWorlds Unity SDK 0.2.0

Choose a tag to compare

@tmoosting tmoosting released this 26 Aug 16:27

Install via Package Manager -> Add package from git URL:

https://github.com/OnlyWorlds/unity-sdk.git?path=/Packages/com.onlyworlds.sdk

Reading worlds from disk, writing them back safely, and a guard on the vendored schema. Verified
against a 142-test EditMode suite and, for the first time, a real stripped IL2CPP build.

Added

  • OWFolderReader / OWFolderLoader — read an OnlyWorlds world folder from disk (spec v0.3.5)
    and load it into a cache keyed (Folder, worldId, folderPath). Reading is not editing: no
    directories, dot-folders or marker files are created, and no file is ever rewritten — including to
    normalise a key spelling. Files that cannot be used are reported, never deleted; one malformed
    file never costs you the rest of the world. Verified against a second implementation's
    conformance fixture.
  • OWBrowserWindow → Open Folder… — the reader, reachable.
  • OWEdit — typed edits that PATCH only what changed. Diffs a baseline snapshot, so it needs
    nothing from the model and works for generated types that do not exist yet. Sends nothing when
    nothing changed, because a no-op PATCH still bumps updated_at and shows up as a phantom edit in
    everyone else's change feed.
  • OWClient.BulkAsync with OWBulkResult — bulk writes whose partial failures are hard to
    ignore. A bulk request returns HTTP 200 even when individual slots failed, so the per-slot
    status is the only place the truth lives.
  • OWSchemaPin + drift guard — the vendored presentation sidecar is hash-checked against the
    pinned distribution on every test run. The pin is (tag, manifest hash), never the tag alone: a
    tag is mutable, and moving one regenerates a self-consistent manifest that cannot detect its own
    tag having moved.

Fixed

  • Extension fields survived JSON but not Unity's own serializer. OWElement did not implement
    ISerializationCallbackReceiver, so the methods carrying the bag across a Unity serialization
    pass were never called. An element held in a MonoBehaviour or ScriptableObject field lost
    every x_* field.
  • Every field was serialized twice — name and Name. Newtonsoft defaults to OptOut, so
    public properties went to the wire beside their attributed fields. The server ignores the
    PascalCase copies, which is why it survived a day of green tests.
  • Incremental sync could advance its cursor past changes it never applied, if the changes feed
    reported more pages but gave no cursor to reach them — silently losing them forever.
  • The rewind diagnostic reported the wrong cursor, rendering "Cursor 100 was ahead of server
    head 100". The value it existed to report was overwritten before the message was built.
  • OWMainThread.Run queued work into silence when no pump existed: the task never completed and
    never faulted, hanging the caller with an empty console. It throws now, and says what to do.
  • OWWorldCache.Upsert left an element in its old type bucket when its type changed — reachable
    by the folder reader on the first file whose body contradicts its directory.
  • HasChangesAsync's two-directional truth is documented rather than surprising.

Validated

  • The nullable converter survives IL2CPP stripping. Against a real stripped Android build: five
    closed SerializableNullable<T> instantiations, three closed converter instantiations, the
    factory reflectable in global-metadata.dat, and ReadJson/WriteJson as executable methods.
    Runtime behaviour on device is still unproven — the probe's verdict only prints on hardware.