Store PC protocols as deltas from the previous version - #1231
Conversation
JSON object key order carries no meaning for ProtoDef compilation, but until now it depended on the source YAML order, so any change to how a protocol is assembled rewrote large parts of protocol.json. Rendering keys in sorted order makes the generated files byte-stable. The top-level section order is preserved because protodef-validator registers the global types namespace before the per-state namespaces. This commit is a pure reordering of all existing protocol.json files (verified content-identical for all 109 files).
A proto.yml can now start with !base: <version> to be a delta on an earlier protocol instead of a full copy. Per section, a type present in both is replaced wholesale by the delta (no deep merge — mapper values and packet IDs are positional), types only in the base are inherited, and TypeName: !delete removes an inherited type. npm run build resolves !base chains transparently; full protos are unaffected. All 51 changed PC protocols are converted to deltas (net -70k lines). Each delta was verified to compile to a protocol deep-equal to the previously committed one; thanks to sorted rendering the regenerated protocol.json files are identical except for the position of the configuration section (new in 1.20.2, now appended at the end of the top level) in the 12 versions that have it. Versions with unchanged protocols keep sharing data dirs as before.
- Replace Ajv's uniqueItems (O(n^2) pairwise deep-equal) with a linear Set check on the canonical sorted-key JSON form; for JSON-parsed data this is exactly equivalent to deep equality. - Share one protodef Validator across all versions instead of building it per test.
|
We discussed this many times in the past and always concluded full protos are better. What changed now ? |
|
Can you share with me the conversations or reasons from these conversations? I don’t believe I’ve ever heard of this being discussed. |
|
Having the full proto have these benefits
What does diffing brings besides saving a few bytes ? |
Why would you want this? All protocol versions are version bumps upon a previous version and almost always build on eachother.
You still have the opportunity to render the files locally every time you make a change.
you can still do this by looking at the rendered version.
The advantages of this change are mostly in lowering the barrier for contribution that requires backporting protocol changes that have been wrong across many versions, and easily scanning the protocol as an implementer to know where to expect to make changes in the protocol implementations that is powered by these files. No part of the benefit is saving bytes, because we still have the rendered versions, just not in the repo. |
|
Are you sure in practice the "propagate to all later versions" property work as expected? How can we check? How do we make it so all non js dependency don't break with this change? |
Why can't we already do that with full files if that property of the diff works ? This would be just a script to run My guess is actually the protocols diff do not compose that well |
The PR shows that the changes properly work as described, it is the proof. After running the updated compilation step, we have generated the same json as before, but with the protocol-delta yaml, rather than the full protocol yaml.
We will leave the .json files in the repo that they depend on. Other than that, I'm not sure what we can do. If you have a list of who depended on the yaml versions, we can open an issue that they should now depend on the json version, but otherwise I'm not sure what to do there. We could also generate yaml files from the deltas, but I think it's a step backward if we just add a new format, unless we add a |
Look, you can totally make a script, formats materially change the viewing experience.
the last 5 pc protocol fixes: #1230 - changes 10 files before, changes 1 after You can be sure no files will be missed by adding in CI a bot that says what the range for a change is, and any missing in between, and when the protocol next diverges from that change, but this PR will make it easier to make those changes for both the person making the change and the person reviewing it. I don't mind if we close this PR though, we can also just make a ci workflow that shows the range of versions effectred by the protocol change made, any forgotten files, any inbetween versions missed, and where the changes end (and whether that's before latest). This format just makes that process slightly easier by only needing to know at what version the change you just made stops applying to newer versions. |
|
I'm going to try to fix all the issues one by one. |
|
Ok I took another look at the content of the PR and now I get it, you store the diff in the yml file but keep the whole content per version in the json file. This is actually better than what I had in mind and does compose relatively nicely with the overall process. |
|
@extremeheat you made that proto yml, what do you think about this change? |
|
I commented on the discord, but it can be pretty hard to follow packets when the relevant types (packets have types which can have their own subtypes) are dispersed throughout several versions. For example if SlotComponent changes (which is used in multiple packets), that type itself has many subtypes inside of it. If you want to follow the logic and fix an issue in it, that means a manual process of figuring out where the last change to the data type was and then from there looking at a subtype, checking if it's in the current file, it was updated in a newer version (yes a newer version, as old -> new -> old can happen when digging through subtypes), an older version, etc and then building a model of how the final result is supposed to look There is a search all feature yes but that gets quite messy as you have to walk back in reverse order every time you update the latest protocol. Comparatively back porting a correction (uncommon) is quite easy as you just apply same patch to n previous versions |
|
Closing based in @extremeheat ’s thoughts. If we decide to, we can reopen later. |
What
Protocols are now stored as deltas from the previous version instead of full copies.
A version's
proto.ymlcan start with!base: <version>(resolved throughdataPaths.json). Semantics, per section:TypeName: !deleteremoves an inherited type (hard error if it doesn't exist);^section: !deleteremoves a whole sectionVersions whose protocol didn't change keep sharing data dirs via
dataPaths.jsonexactly as before.npm run buildresolves!basechains transparently — full protos compile exactly as they did.Commits
typesnamespace before the per-state namespaces, sotypesmust stay first.configurationsection (new in 1.20.2) in the 12 versions that have it.uniqueItemsinstead of Ajv's O(n²) pairwise deep-equal, and one shared protodefValidatorinstead of one per version.Safety
Each converted delta was verified to compile to a protocol deep-equal to the previously committed
protocol.json. The backfill tool (tools/js/backfillDeltas.js) bakes this golden check in, so it can be re-run for future versions. Full suite passes with the currently published protodef-validator (1820 tests).Bedrock keeps full protos for now (its generated
packet_map.ymlimport needs handling first); the resolver is edition-agnostic.