The dictionary effectively defines four tables, the spaces, vendors, attributes/fields and values tables.
Each table entry contains a number, a name, and a few variables that define the entry's properties, except for those in the vendors and values tables, which are simple name/number mappings, nothing more.
A space is something that enables the packet decoder (and encoder, but decoding is easier to think about) to decode a block of data. In order to do so, a space has the following properties:
If a space's atr_size is 0, then the space has no numbered attributes/ fields/dictionary items (all used interchangably in the dictionary and its code) and provides no way to walk the block, other than to apply each field defined in that space one after another.
Once the decoder finds a dictionary item (an attribute/field) to apply, either by applying all defined in the space in sequence, or by looking one up based on the vendor and attribute numbers obtained from the block at the given places, it proceeds by decoding the block at the current offset using the dictionary item found.
A dictionary item has the following properties to that end:
If 'nodec' is not 0, then a new A/V pair is created on the request list. If 'subspace' is defined, then the value is decoded by applying the given space to the block given by the value.
Encoding goes the other way around, based on the same information. There are some subtleties involving catch all attributes and some other things, but if you grasp the above, you should be able to create working dictionaries for anything that even remotely resembles RADIUS attributes using the stuff that's already there.