Other
- AC-3 sync-frame header decoder (syncinfo + bsi prefix)
- decode HLI_GI btn_md into typed ButtonMode (PCI highlight button groups)
- PCI NSML_AGLI non-seamless angle jump table
- add First-Play PGC reader (DvdDisc::parse_fp_pgc)
- menu C_ADT + VOBU_ADMAP reader helpers on DvdDisc
- decode VMGM_PGCI_UT + VTSM_PGCI_UT (menu PGCI Unit Table)
- decode VMG_VTS_ATRT + VMG_PTL_MAIT on the VMG side
- typed accessors for the remaining language / sentinel SPRMs
- drop release-plz.toml — use release-plz defaults across the workspace
- typed-instruction iterators on PgcCommandTable + decode_instruction bridge
- typed HighlightStatus enum on PCI_GI hli_ss
- typed cell-elapsed-time accessor on DsiGi + PgcTime::to_nanoseconds
- VTSI_MAT / VMGI_MAT stream-attribute extension decoders
- VOBU_ADMAP + VTS_TMAPTI typed decoders for time-based seek
- typed UOP-prohibition decoder + three-level OR-merge
- decode DVD-Video LPCM private_stream_1 audio-pack header
- SPRM bitfield accessors + named SPRM indices (Phase 3c next-item)
- execute Type 4..6 compound CMP/SET/LNK families (Phase 3c completion)
- Phase 3c interpreter — SPRM/GPRM register file + Link/Jump/Call execution
Added
-
AC-3 sync-frame header decode (
ac3module).Ac3Header::parse
decodes thesyncinfo()(sync word0x0B77,crc1,fscod
sampling-rate code,frmsizecodframe-size code) and the
deterministically-positioned prefix ofbsi()(bsid,bsmod
bitstream mode,acmodaudio-coding mode, and thecmixlev/
surmixlev/dsurmodconditional fields whose presence is a pure
function ofacmod, pluslfeon) off the start of an AC-3
elementary stream routed fromDvdSubstream::Ac3. Accessors:
sample_rate_hz,nominal_bitrate_kbpsandframe_size_words/
frame_size_bytes(driven by the 38-entryfrmsizecodtable with
per-sample-rate columns),total_channel_count(nfchans + lfeon),
and theAc3AudioCodingModechannel-layout / conditional-field
classifiers. Reservedfscod/frmsizecodcodes are preserved and
surface asNonefrom the rate/size accessors. Header-only: fields
pastlfeon(variable-lengthbsi()tail) and the audio blocks stay
with a downstream AC-3 decoder. Clean-room per
docs/container/dvd/application/stnsoft-ac3hdr.html+
mpucoder-dvdmpeg.html. -
HLI_GI
btn_mdtyped decode.HighlightInfo::button_mode()
now returns aButtonMode { group_count, group_types: [u8; 3] }
decoded from the rawbtn_mdword per thebtn_md wordsub-table
ofdocs/container/dvd/application/mpucoder-pci_pkt.html:
btngr_ns(number of button groups, u16 bits 13..12) and the three
3-bitbtngrN_tygroup-type codes (bits 10..8 / 6..4 / 2..0), with
the reserved bits (15..14, 11, 7, 3) masked out.ButtonMode
also providesfrom_btn_md/to_btn_md(reserved-bit-dropping
round-trip). The reference labels the type codes "normal / lb /
p/s" (normal / letterbox / pan-scan) but gives no numeric
value-to-name mapping, so the codes are surfaced raw rather than as
a named enum; the field had previously been kept as an opaqueu16. -
PCI NSML_AGLI non-seamless angle jump table.
PciPacketnow
decodes the 36-byte NSML_AGLI block at PCI packet offset
0x3C..0x60into a typedNsmlAgli { cells: [NsmlAngleCell; 9] }
perdocs/container/dvd/application/mpucoder-pci_pkt.html. Each
nsml_agl_cN_dstacell carries the relative sector offset to the
current ILVU for that angle, with bit 31 as the direction
(0 = forward, 1 = backward) and the0x0000_0000(angle absent) /
0x7FFF_FFFF(no more video) sentinels.NsmlAngleCellexposes
is_absent/is_no_more_video/is_backward/offset_sectors;
NsmlAgliexposesis_empty,active_angle_count, and a 1-based
angle(n)accessor that pairs with SPRM 3 (current angle). This is
the PCI counterpart to the existing DSISmlAgliseamless-angle
table, completing the multi-angle navigation surface a player needs
to switch angles on a non-seamless interleaved block. -
First-Play PGC reader —
DvdDisc::parse_fp_pgc. The VMGI_MAT
word at0x0084is the start byte address ofFP_PGC, the
program chain a player enters at disc insertion before any title or
menu domain is active — per
docs/container/dvd/application/mpucoder-ifo.htmlit is the only
VMGI structure addressed in bytes rather than sectors (same unit as
the0x0080"end byte address of VMGI_MAT" word), and its body is
an ordinary PGC permpucoder-pgc.html(the MAT row links straight
to the PGC page), soPgc::parsedecodes it unchanged. The new
helper reads the MAT, follows the byte address, and parses the PGC;
it returnsOk(None)whenfp_pgc_addris zero (no First-Play PGC
authored). The read is bounded at the first non-zero sector-aligned
VMG table so a malformed address can't pull bytes from an unrelated
table — an address at/past that boundary is rejected with an error
rather than mis-parsed. This closes the navigation bootstrap gap:
the Phase 3c VM could already execute startup routing
(JumpSs(FirstPlay)/JumpTTactions) but nothing could fetch
the FP_PGC those commands live in. Three new tests: the populated
path drives the disc-insertion sequence end-to-end (synthetic
cell-less FP_PGC at byte0x0400→parse_fp_pgc→
commands.pre→Vm::run_list→VmAction::JumpTitle { ttn: 1 }),
plus the zero-pointerNonepath and the past-first-table
rejection. 311 lib tests (was 308) under default features;
321 lib tests (was 318) under--all-features. -
Menu
C_ADT+VOBU_ADMAPreader helpers onDvdDisc. The
VMGI / VTSI MATs carry sector pointers to the menu-side cell-address
tables (vmgm_c_adt_sector/vtsm_c_adt_sector) and menu VOBU
address maps (vmgm_vobu_admap_sector/vtsm_vobu_admap_sector),
but no high-level reader followed them. The body decoders already
existed —docs/container/dvd/application/mpucoder-ifo.htmldocuments
VMGM_C_ADT/VTSM_C_ADT/VTS_C_ADTunder one shared#c_adt
heading (and the three VOBU_ADMAP variants under#vam) because all
share the wire format, soVtsCAdt::parse/VobuAdmap::parse
decode the menu copies unchanged. This round wires the four
high-level reader helpers that read the appropriate MAT, follow the
sector pointer, and parse the body:DvdDisc::parse_vmgm_c_adt(reader)— VMG menu cell-address table
(VIDEO_TS.VOBcells).DvdDisc::parse_vmgm_vobu_admap(reader)— VMG menu VOBU sector
list.DvdDisc::parse_vtsm_c_adt(reader, ts_index)— per-title-set menu
cell-address table (VTS_xx_0.VOBcells).DvdDisc::parse_vtsm_vobu_admap(reader, ts_index)— per-title-set
menu VOBU sector list.
Each returnsOk(None)when the corresponding MAT sector pointer is
zero (no menu VOB authored). The reads are bounded at the next
non-zero table sector in the MAT so a malformedend_addresslength
field can't pull bytes from an unrelated table — the same
bounded-read discipline theparse_vmgm_pgci_ut/parse_vtsm_pgci_ut
helpers use. Five new in-module tests cover the populated happy path
for all four helpers (synthetic VMGI/VTSI disc image → cell lookup +
VOBU sector-count/start round-trip) and the four zero-pointerNone
paths. 308 lib tests (was 303) under default features; 318 lib
tests (was 313) under--all-features.
-
VMGM_PGCI_UT+VTSM_PGCI_UTdecoders (menu PGCI Unit Table).
The MAT records the sector pointersvmgm_pgci_ut_sectorand
vtsm_pgci_ut_sectorfor the menu PGC tables on both the VMG and
VTS sides, but no body parser existed. This round materialises both
perdocs/container/dvd/application/mpucoder-ifo_vmg.html§VMGM_PGCI_UT
andmpucoder-ifo_vts.html§VTSM_PGCI_UT — the wire format is
identical between the two sides:PgciUt— the outer search-pointer list keyed by ISO 639 language
code (each entry: 16-bit language code + 1-byte language-code
extension + 1-bytemenu_existenceflag + 32-bit offset to LU).
Thelanguage_unit(lang_code)lookup round-trips a packed
b"en"-style code to its parsed Language Unit; the per-entry
has_root_menu/has_subpicture_menu/has_audio_menu/
has_angle_menu/has_ptt_menuaccessors decode each
menu-existence flag bit per the table atmpucoder-ifo_vts.html
(bit0x80= root/title,0x40= sub-picture,0x20= audio,
0x10= angle,0x08= PTT — the constants live in the public
menu_existencesub-module).PgciLu— one Language Unit body: a per-PGC search-pointer list
(PgciLuSrp: 32-bit PGC category dword + 32-bit offset to the
PGC body) plus the parsedPgcbodies themselves (via
Pgc::parse). ThePgciLuSrp::is_entry_pgc/
menu_type/parental_maskaccessors decompose the category
dword permpucoder-ifo_vts.html(PGC category breakdown).MenuTypeenum — decodes the low nibble of the PGC category
byte 0 (2= title /3= root /4= sub-picture /5=
audio /6= angle /7= PTT, plusUnknown(_)for the
reserved nibble values).DvdDisc::parse_vmgm_pgci_ut(reader)/
parse_vtsm_pgci_ut(reader, ts_index)— high-level reader helpers
that read the appropriate MAT, follow the sector pointer, and
parse the body. Both returnOk(None)when the corresponding
MAT sector pointer is zero (table absent on this disc / title
set). The reads are bounded at the next non-zero table sector
so a malformed length field can't pull bytes from an unrelated
table.
Nine new unit tests cover the happy path (two-language walkthrough
with entry-PGC + menu-type round-trip), the boundary cases (zero
language units, parental-mask extraction from the category dword),
and the four malformed-input rejection paths (short header /
SRP list past buffer / LU offset zero / LU offset past buffer /
inner PGC offset past buffer). -
VMG_VTS_ATRT+VMG_PTL_MAITdecoders on the VMG side. The
VMG IFO's MAT carries two table pointers we'd previously parsed
(vts_atrt_sector,ptl_mait_sector) without surfacing the
table bodies. This round materialises both per
docs/container/dvd/application/mpucoder-ifo_vmg.html:VmgVtsAtrt— per-VTS attribute copies that mirror each VTS
IFO's attribute block (the buffer at VTS IFO offset0x0100,
typically0x300bytes long) onto the VMG side. Each
VmgVtsAtrtEntryexposes the entry'svts_categoryfield
(0= unspecified,1= Karaoke), a 1-basedvts_number,
and the raw attribute blob.entry(vts_number)looks up an
entry; bound checks reject malformed EAs that would overlap
the next entry.VmgPtlMait— the country-keyed parental management table.
EachPtlMaitbody carries the eight parental-level mask
arrays (Nts + 116-bit masks per level — index 0 is the
VMG-side mask,1..=ntsare the title sets). The on-disc
storage order is descending (level 8 first), but the typed
masksarray is surfaced ascending (masks[0]= level 1) so
a caller can index withparental_level - 1directly.
country(code)looks up a country sub-table;mask(level, title_set)returns the 16-bit allow-mask for the
(parental_level, title_set)pair.DvdDisc::parse_vmg_vts_atrt(reader)/
parse_vmg_ptl_mait(reader)— high-level reader helpers that
read the MAT, follow the sector pointer, and parse the body.
Both returnOk(None)when the corresponding MAT sector
pointer is zero (table absent on this disc). The PTL_MAIT
reader bounds its sector read at the next non-zero table
pointer in the MAT so a malformed length field can't pull
bytes from an unrelated table.
Nine new tests cover the happy path (two-country / two-VTS
walkthroughs with mask + blob round-trip), boundary cases (zero
countries, partial header), and the four malformed-input
rejection paths (short header / offset list past buffer /
body offset past buffer / per-entry EA overlapping the next
entry).
-
Typed accessors for the remaining language / sentinel SPRMs.
Round 3c's first SPRM accessor sweep covered the six bit-packed
slots (SPRM 2 / 8 / 11 / 14 / 15 / 20); the rest of
docs/container/dvd/application/mpucoder-sprm.htmldocuments
nine more SPRMs that aren't plain integers either — the four
two-byte ASCII slots (SPRM 0 menu language, SPRM 12 parental
country, SPRM 16 / 18 preferred audio / sub-picture language,
ISO 639 / ISO 3166 alpha-2) and the sentinel-typed integer
slots (SPRM 1 audio stream0..=7+15-none, SPRM 3 angle
1..=9, SPRM 13 parental level1..=8+15-none, SPRM 17
audio language extension five-value enum, SPRM 19 sub-picture
language extension eleven-value enum). New surface on
RegisterFile:menu_language()/parental_country()/
preferred_audio_language()/
preferred_subpicture_language()return aLanguageCode
that exposes the raw word, anis_not_specified()predicate
(the0xFFFFSPRM 16 / 18 default), anascii_bytes()→
Option<[u8; 2]>accessor that only succeeds when both bytes
are printable ASCII letters, and anas_string()lower-cased
alpha-2 form for downstream tooling.audio_stream()returns anAudioStreamSelectorenum that
distinguishes the15-none sentinel from real stream indices
Stream(0..=7)and preserves out-of-range raws asInvalid.angle_number()collapses the SPRM 3 word to
Option<u8>with the1..=9range enforced.parental_level()returns aParentalLevelenum with
Level(1..=8)/None(= 15) /Invalidshapes.preferred_audio_language_ext()/
preferred_subpicture_language_ext()return
AudioLanguageExt/SubpictureLanguageExtenums covering
every spec-table value; unmapped values collapse to
Reserved(raw)for round-trip.
Twelve new tests cover the defaults, the in-range values, and
the out-of-range / sentinel collapse for each accessor.
-
Typed-instruction iterators on
PgcCommandTable. The PGC
command table carries three lists of raw 8-byte
[NavCommand] words (pre / post / cell) per
docs/container/dvd/application/mpucoder-pgc.html; the Phase 3c
disassembler in thenavmodule turns one word into a typed
[NavInstruction]. Previously the bridge between the two was
manual — callers had to walkcommands.pre/commands.post/
commands.cell, then callnav::NavCommand::decode()on each
entry themselves. New surface:NavCommand::decode_instruction()— convenience that
delegates to the Phase 3c precursor disassembler so the IFO
side can reach a typed instruction without re-importing the
navmodule's surface.PgcCommandTable::pre_instructions()/
post_instructions()/cell_instructions()— borrowing
iterators ofNavInstructionthat walk each list in storage
order.PgcCommandTable::cell_instruction(index_1based: u16)—
1-based indexed lookup matching the on-wire encoding
CellPlaybackInfo::cell_commandcarries; passes0for
"no cell command", out-of-range indices returnNonerather
than panicking. Permpucoder-pgc.htmlthe cell-command
table is 1-based, so 1 →cell[0], 2 →cell[1], etc.
Round-trip checked: a
NavCommandconstructed by hand with a
Type 1 jumpcall +cmd_nibble = 1payload decodes through both
decode()anddecode_instruction()to the sameExit
variant. Four new unit tests insrc/ifo.rs(synth command
table → typed walk; 1-based indexing;0and out-of-range
returnNone; round-trip with explicitnav::decode()). -
HighlightStatustyped enum on PCI_GIhli_ss. The PCI
packet'sHLI_GI 00field carries a 16-bit word whose lower two
bits encode how a player should treat the menu-button overlay
for the VOBU. Previously the field was surfaced only as the raw
u16(PciPacket::hli_ss), forcing every consumer to repeat
the& 0b11masking and four-waymatchdocumented in
docs/container/dvd/application/mpucoder-pci_pkt.html.
New typed surface:HighlightStatusenum with four exhaustive variants —
None(00),AllNew(01),UsePrevious(10),
UsePreviousExceptCommands(11).HighlightStatus::from_hli_ss(u16)infallible constructor
that ignores the 14 reserved upper bits.HighlightStatus::to_bits()round-trip back to the 2-bit
code.- Four classifier predicates —
is_none(),
declares_new_geometry(),reuses_previous_geometry(),
supplies_own_commands()— that match the four-row spec
table directly so call sites no longer have to re-derive
"AllNew + UsePreviousExceptCommands ⇒ commands come from
this VOBU" from scratch. PciPacket::highlight_status()accessor wrapping the
constructor; the rawhli_ssword stays exposed so callers
that need the reserved bits still have them.
The
HighlightInfogeometry struct is still populated only
when the VOBU actually declares buttons (btn_ns > 0); the
typed status accessor is now the documented way to detect a
"re-use previous geometry" VOBU whose ownBTN_ITis empty. -
DsiGicell-elapsed-time typed accessor. The DSI_GI block
on every Nav-Pack carries a 4-byte BCDc_eltmfield describing
the elapsed playback time inside the current cell, layered out
identically to thePGC_GIplayback-time field (hh:mm:ss:ff- 2-bit frame-rate code per
mpucoder-dsi_pkt.html). Previously
surfaced only as the rawu32. New methods:
DsiGi::cell_elapsed_time() -> PgcTimedecodes the four BE
bytes through the existingPgcTime::from_bytesdecoder, so
the samehours / minutes / seconds / frames / frame_rate
fields the PGC playback-time accessor returns become available
on the DSI side without the caller re-implementing the BCD
nibble split.DsiGi::cell_elapsed_ns() -> u64collapses the typed view to
absolute nanoseconds via the newPgcTime::to_nanoseconds
method below.DsiPacket::cell_elapsed_time()/cell_elapsed_ns()
convenience getters mirror the existing flatvobu_ea()/
vobu_vob_idn()shape.
- 2-bit frame-rate code per
-
PgcTime::to_nanoseconds()method. Previously the
nanosecond conversion lived only inside themkv-output
feature gate as a free function on the MKV-writer (because the
chapter timeline was the only consumer). Promoted to a regular
method onPgcTimeso default-feature builds get the rational
(frames × 1e9) / fpsconversion (30 fps → 33,333,333 ns/frame,
25 fps → 40,000,000 ns/frame, illegal / reserved rates drop the
frame fraction and keep only the integer-second portion). The
mkv_writer::pgc_time_to_nsfree function is preserved as a
thin wrapper for callers that imported it directly. -
VTSI_MAT / VMGI_MAT stream-attribute extension blocks.
The two MAT structures previously stopped at sector-pointer
offset 0x00E4 — the audio / sub-picture / multichannel
attribute extension that occupies 0x0100..0x015C (menu) and
0x0200..0x03D8 (VTS title content + karaoke multichannel) was
ignored. This round adds typed decoders for every field in
those blocks and surfaces them onVtsiMat::menu_attributes/
VtsiMat::title_attributes/VmgIfo::menu_attributes.
Clean-room perdocs/container/dvd/application/mpucoder-ifo.html
(thevidatt,audatt,spatt, andmcextfield layouts);
no external implementation source consulted.VideoAttributes— coding mode (MPEG-1 / MPEG-2),
NTSC / PAL standard, 4:3 / 16:9 aspect, pan-scan and
letterbox display-mode disallow flags, line-21 CC-field
flags, and aVideoResolution::dimensions(standard)helper
that resolves the 3-bit resolution code to absolute pixel
dimensions (Full-D1 / ¾-D1 / Half-D1 / SIF).AudioAttributes— coding mode (AC-3 / MPEG-1 / MPEG-2-
ext / LPCM / DTS), language type + two-letter ISO-639 code +
code-extension byte (per the SPRM-17 alternate-
director-comment scheme), application mode (unspecified /
karaoke / surround), channel count, sample-rate selector
(only 48 kHz defined), and dual-interpretation
quantization / DRC field (16/20/24 bps for LPCM versus
DRC-on/off for MPEG). Helpers:sample_rate_hz(),
dolby_surround_suitable(), and the four karaoke decoders
(karaoke_channel_assignment,karaoke_version,
karaoke_mc_intro_present,karaoke_duet) for the
application-info byte at offset 7.SubpictureAttributes— 2-bit-RLE coding mode (the only
one defined), language type, ISO-639 code, and code-extension
byte (per the SPRM-19 scheme).McExtensionEntry— 24-entry karaoke multichannel
extension table; each 8-byte entry decodes the 14 ACH
guide-melody / guide-vocal / sound-effect flag bits across
channels 0..=4.- Backwards-compatible parse.
VtsiMat::parsestill accepts
a 0x200-byte buffer; the menu block fits within that range
and is populated, the title block stays empty and the
multichannel-extension vec stays empty. RealVTS_xx_0.IFO
files run to 0x03D8 and now populate fully.
-
VobuAdmap+VtsTmapti/VtsTmap— time-based seek tables.
The two title-set sector pointersVTSI_MAT::vts_vobu_admap_sector
andVTSI_MAT::vts_tmapti_sectorpreviously surfaced only as raw
u32fields; this round materialises both tables into typed
parsers and wires them ontoVtsIfoso a player can answer
"where is playback at second N?" without re-walking the IFO byte
buffer. Clean-room per
docs/container/dvd/application/mpucoder-ifo.html(VOBU_ADMAP
layout) anddocs/container/dvd/application/mpucoder-ifo_vts.html
(VTS_TMAPTI / VTS_TMAP layout); no external implementation
source consulted.VobuAdmap—{ end_address, entries: Vec<u32> }decoder
for the per-VOBU sector list shared byVMGM_VOBU_ADMAP,
VTSM_VOBU_ADMAP, andVTS_VOBU_ADMAP(all three share the
same wire format permpucoder-ifo.html). Entry count is
implicit in theend_addressfield; the parser carves
(end_address + 1 - 4) / 4four-byte VOB-relative sector
words.vobu_count,vobu_start_sector(vobu_number)(1-based
lookup), andvobu_containing(sector)(binary-partition
inverse lookup that returns the 1-based VOBU number whose
range covers the requested sector) round out the surface.VtsTmap+TmapEntry— per-PGC time map. The 4-byte
header is{ time_unit: u8, reserved: u8, number_of_entries: u16 }; each entry is a 4-byte big-endian word with bit 31 set
when the previous entry was time-discontinuous (a VOBU
boundary that crosses an STC reset) and the low 31 bits
carrying the VOB-relative sector.sector_at(seconds)
translates a PGC-relative wall-clock time into the VOBU
sector whose[(i - 1) * time_unit, i * time_unit)bracket
contains it; the result clamps to the last entry once
secondsruns past the map. Empty maps andtime_unit == 0
both yieldNonerather than panic, per spec language that
declares an empty map legal but unindexable.
TmapEntry::DISCONTINUITY_BIT+SECTOR_MASKconstants make
the bit-31 split explicit.VtsTmapti—{ number_of_pgcs, end_address, maps: Vec<VtsTmap> }. The spec mandates "each PGC MUST have a time
map, even if it is empty" somaps.len() == number_of_program_chainsis invariant.get(pgcn)returns
the per-PGC map for a 1-based program-chain number.- Wired onto
VtsIfo::parseas the two new
Option<VobuAdmap>+Option<VtsTmapti>fields
(vobu_admap,time_map). Both stayNonewhen the
correspondingVTSI_MATsector pointer is zero — the spec
listsVTS_VOBU_ADMAPas mandatory but some authoring tools
elide it on title sets that hold only menu VOBs, and
VTS_TMAPTIis the explicitly-optional one. The new
VtsIfo::vobu_sector_at_pgc_time(pgcn, seconds)wrapper
composestime_map.get(pgcn)withVtsTmap::sector_at, the
expected entry point a playback engine uses when the user
requests a wall-clock seek; combine with
VtsiMat::title_vob_sectorfor the absolute disc LBA. - 15 new in-module tests (round-trip + partition lookup +
pre-sector / past-end edges + non-multiple-of-4 / truncated /
empty-map rejections forVobuAdmap; entry decode +
discontinuity-bit isolation + time-bracket sweep + empty +
zero-time_unit/ truncated rejections forVtsTmap;
two-PGC walk + empty-PGC invariant + short-offset rejection
forVtsTmapti; end-to-end VOBU-map + time-map composite
that walks a six-sector synthetic IFO throughVtsIfo::parse
and assertsvobu_sector_at_pgc_timeon three sample
timestamps). 244 lib tests (was 229) under default
features; 254 lib tests (was 239) under--all-features.
-
uopsmodule — DVD-Video User Operation flag decoder.
Three on-disc fields carry a UOP-prohibition bitmask: the
TT_SRPT entry (bits 0+1 packed intotitle_type), the PGC
header (offset0x0008), and the PCI packet (PCI_GI 08). The
newuopsmodule surfaces them as typed values, clean-room per
docs/container/dvd/application/mpucoder-uops.html(25-row bit
table + per-level applicability columns + the "set bit in any
mask inhibits the associated control" three-level OR-merge
rule).UserOpenum — 25 variants (TimePlayOrSearch,
PttPlayOrSearch,TitlePlay,Stop,GoUp,
TimeOrPttSearch,TopPgOrPrevPgSearch,NextPgSearch,
ForwardScan,BackwardScan, the sixMenuCall*variants,
Resume,ButtonSelectOrActivate,StillOff,PauseOn,
AudioStreamChange,SubpictureStreamChange,AngleChange,
KaraokeAudioMixChange,VideoPresentationModeChange) with
bit(),mask(),from_bit(), andALLaccessors.UopMask—u32newtype withcontains/is_allowed
/with/without/set/clear/is_empty/count
/iteraccessors plusmerge_or(a, b, c)for the three-
level OR.defined_bits()masks the raw word to bits 0..=24
so reserved bits don't pollute the comparison.fits_level
validates that a mask carries only bits the spec table marks
present at the given level — useful for an IFO sanity check.UopLevelenum (TitleSearchPointer/ProgramChain
/Vobu) with acover()accessor reporting which bits the
spec table's PGC and VOBU columns mark check-marked. PGC
cover excludes bit 4 (GoUp) per the spec table's row 4
PGC-column blank; VOBU cover excludes bits 0/1/2/17 per the
same table.title_type_uop_mask(title_type) -> UopMask— extracts
the 2-bit TT_SRPT subset from aDvdTitleEntry::title_type
byte (low two bits only; remaining bits are jump/link/call
permission flags permpucoder-ifo_vmg.htmland stay out of
the UOP surface).- Typed accessors wired into existing parsers:
Pgc::uop_mask()/Pgc::is_user_op_allowed(UserOp)
aroundPgc::prohibited_user_ops.PciPacket::uop_mask()/PciPacket::is_user_op_allowed
aroundPciPacket::vobu_uop_ctl.DvdTitleEntry::uop_mask()/
DvdTitleEntry::is_user_op_allowedaround
DvdTitleEntry::title_type(low 2 bits).
- Constants —
UOP_TIME_PLAY_OR_SEARCHthrough
UOP_VIDEO_PRESENTATION_MODE_CHANGE(25 named bit-number
constants),UOP_BIT_COUNT = 25, andUOP_DEFINED_BITS = 0x01FF_FFFF. - 21 new in-module tests (bit-number / mask round-trip; spec-
table column reproduction including the GoUp/PGC-blank row;
title_type byte sweep; merge-or commutativity / associativity
/ identity; iter ordering; reserved-bit skip;fits_level
cross-products) plus 7 cross-module integration tests in
tests/uops_integration.rsvalidating the typed accessors
against a hand-builtPgc::parse/PciPacket::parse/ raw
DvdTitleEntryplus the three-level merge end-to-end.
229 lib tests (was 208) + 7 integration tests.
-
lpcmmodule — DVD-Video LPCM 7-byte audio-pack header decoder.
Theprivate_stream_1LPCM substream (0xA0..=0xA7) carries a
fixed 7-byte audio-pack header ahead of the raw PCM sample bytes
that pins the sample format, the seamless-playback frame counter,
and the X/Y dynamic-range coefficients. The newlpcmmodule
decodes that header into a typedLpcmHeader, clean-room per
docs/container/dvd/application/mpucoder-lpcm.html
(field layout +linear_gain = 2^(4 - (X + Y/30))/
gain_db = 24.082 - 6.0206 X - 0.2007 Yformulas) and
docs/container/dvd/application/stnsoft-LimPcmAud.html(the
per-(sample_rate × quantisation × channels)bitrate table and
the 6144 kbps DVD-Video LPCM ceiling). Clean-room from those two
spec pages only.LpcmHeader—{ sub_stream_id, number_of_frame_headers, first_access_unit_pointer, audio_emphasis_flag, audio_mute_flag, audio_frame_number, quantisation, sample_frequency, channel_count, dynamic_range_x, dynamic_range_y }decoded view.LpcmQuantisationenum —Bits16/Bits20/Bits24/
Reserved, withbits_per_sample()accessor.LpcmSampleFrequencyenum —Hz48000/Hz96000/
Reserved, withhz()accessor.LpcmHeader::bitrate_kbps()computeschannels × sample_rate × bits_per_sample / 1000and returnsNonewhen either of the
two reserved codes is present;
LpcmHeader::is_within_dvd_video_limit()checks the result
against thestnsoft-LimPcmAud.html6144 kbps ceiling (the red-
highlighted combinations such as 96 kHz × 24-bit × 8-channel
returnfalse).LpcmHeader::linear_gain()+gain_db()evaluate the
two parameterisations of the dynamic-range coefficient table.
X = 0, Y = 0gives the unity-gain reference(2^4, +24.082 dB);
X = 7, Y = 30gives the-24 dBpole. Applying the gain to the
decoded samples stays with the audio decoder.peel_lpcm_payload(&[u8]) -> Result<(LpcmHeader, &[u8])>—
splits the substream-ID-prefixed PES payload into the typed
header and the raw PCM tail in one zero-copy call.- Constants —
LPCM_HEADER_LEN = 7and
DVD_LPCM_MAX_BITRATE_KBPS = 6144. - 14 new unit tests including a full reproduction of the
stnsoft-LimPcmAud.htmlbitrate table (48 combinations across
{48k, 96k} × {16, 20, 24} × {1..=8 ch}, every cell pinning both
the decoded kbps and the green/redis_within_dvd_video_limit
verdict). Parse-reject cases for the truncated buffer and the
non-LPCM substream selector; isolated decode of every quantisation,
sample-rate, and channel-count code; bit-by-bit decoding of the
emphasis / mute / frame-number byte, the first-access-unit
pointer, and the X/Y dynamic-range split; the unity-gain identity
and the-24 dBattenuation pole; andpeel_lpcm_payloadround-
trip + short-buffer rejection. 208 lib tests (was 192).
-
mkv_writerstrips the LPCM audio-pack header before forwarding
PCM samples to the MKV muxer, so thepcm_s16betrack now receives
the clean big-endian sample bytesA_PCM/INT/BIGexpects (the
previous comment had punted the stripping to "Phase 3c"; this round
closes that gap by re-using the newlpcm::LPCM_HEADER_LEN
constant). -
SPRM bitfield-aware accessors + named indices for SPRMs 0/12/14..20.
Thevmmodule now exposes typed views for the six packed SPRMs
whose contents are documented as bit-packed payloads on
mpucoder-sprm.html:RegisterFile::subpicture_stream()→SubpictureStreamView—
decodes SPRM 2 into the 6-bit stream index, the bit-6
displayflag, plusis_none_sentinel/is_forced_sentinel
helpers for the spec's62/63special values.RegisterFile::highlight_button()→u8— decodes SPRM 8's
1..=36button number from bits 10..=15; out-of-range fields
surface as0so a malformed disc cannot crash a player.RegisterFile::audio_mix_mode()→AudioMixMode— decodes
SPRM 11's six per-channel mix bits (bits 2/3/4 → front,
bits 10/11/12 → rear).RegisterFile::video_preference()→VideoPreferencewith
AspectRatio(4:3 / NotSpecified / Reserved / 16:9) and
DisplayMode(Normal / PanScan / Letterbox / Reserved) decoded
from SPRM 14 bits 10..=11 and 8..=9 respectively.RegisterFile::audio_capabilities()→AudioCapabilities—
decodes SPRM 15's nine documented capability bits (SDDS / DTS /
MPEG / Dolby / PCM, each with optional karaoke variant);
cannot_play()returnstruewhen the register is zero per the
spec page's "0 = cannot play" semantic.RegisterFile::region_allowed(region)/region_mask()—
decode SPRM 20's 8-bit region mask (biti⇒ regioni + 1).
Named index constants added for the missing SPRMs:SPRM_MENU_LANG
(0),SPRM_CC_PLT(12),SPRM_VIDEO_PREF(14),SPRM_AUDIO_CAPS
(15),SPRM_PREF_AUDIO_LANG(16),SPRM_PREF_AUDIO_LANG_EXT(17),
SPRM_PREF_SUBP_LANG(18),SPRM_PREF_SUBP_LANG_EXT(19),
SPRM_REGION_MASK(20). Default-vector documentation table
re-rendered with one row per SPRM index, the spec value, and the
spec-page source. SPRMs 17 and 19 now hold an explicit0("not
specified") rather than an implicit zero-fill, matching the spec's
language-extension enumeration. Clean-room per
docs/container/dvd/application/mpucoder-sprm.html. 14 new tests
cover each accessor's default value and bit-by-bit decode.
-
Compound CMP/SET/LNK execution (Type 4..6) — Phase 3c completion.
Thenavmodule'sSetCLnk/CSetCLnk/CmpSetLnkvariants now
carry the full operand triple (SET source, CMP RHS, shared selector,
Type 5's independent CMP-LHS,hl_bnbutton override, Link subset)
pulled out of the 8-byte word per the per-row layouts on
mpucoder-vmi.html(table 2, rows 88..101). Thevminterpreter
executes each compound in spec order permpucoder-vmi-sum.html:SetCLnk— SET first, then CMP against post-SET selector,
then Link ontrue;falsecollapses toContinueso the
outer command list keeps walking.CSetCLnk— CMP first; SET and Link only ontrue.CmpSetLnk— CMP first; SET only ontrue; Link
unconditional (the distinguishing semantic fromCSetCLnk).
Compound Link subsetsNopcollapse toContinueeven when the
enclosing compound ran;Rsmpops the same RSM stack as a bare
Type-1 link;Invalid(_)subsets degrade toContinueso a
malformed disc cannot crash the interpreter. The two "Illegal"
red rows (SET-dir=1 AND CMP-dir=1 for Types 5 and 6, where the
operand bytes would overlap) surface asNavInstruction::Invalid
per the spec page's explicit rejection. 14 new tests (the four
full-operand decode forms across register / immediate mixes for
Types 4, 5, 6, the two Invalid-row encodings, plus 10 VM-exec
cases covering SET-then-LINK truth + false-branch behaviour for
all three families, the Link-subsetNop/Rsm/
Invalidcollapse paths, and theSetOp::None"skip SET phase"
short-circuit). 177 lib tests (was 163 after Phase 3c VM
landed). Clean-room per the spec pages cited above; no external
implementation source consulted.
-
vmmodule — DVD-Video VM interpreter (Phase 3c). Wraps the
navmodule's typedNavInstructiondisassembler with a stateful
executor. Clean-room perdocs/container/dvd/application/mpucoder-{vmi,vmi-sum,vmi-jmp,sprm,uops}.html;
no external implementation source consulted.RegisterFile— 16 GPRMs (writable, persists across PGCs)- 24 SPRMs initialised to the spec-defined defaults (
ASTN = 15,
SPSTN = 62,AGLN/TTN/VTS_TTN/PTTN = 1,HL_BTNN = 1 << 10,
preferred-language slots =0xFFFF) + a per-GPRM counter-mode
bit-mask theSetGPRMMD mfflag toggles.tick_counters(delta)
advances every counter-mode GPRM bydeltaseconds (saturating)
so a playback engine that owns a wall clock can drive the
1 Hz semantic without owning the register file. Out-of-range
index reads return0and writes are silently dropped — matches
the spec's "invalid register reads as 0" fallback observed in
malformed PGC command tables.
- 24 SPRMs initialised to the spec-defined defaults (
Vm— owns aRegisterFile, the call/return stack
(ResumePointframes bounded byMAX_RSM_DEPTH = 8to detect
runaway nesting without restricting commercial-disc 1–2-deep
Menu Call → sub-menu use cases), and the per-list program
counter.Vm::step(NavInstruction) -> VmActionadvances one
decoded instruction.Vm::run_list(&[NavCommand])walks a
pre/post/cell command list end-to-end, honours intra-list
Goto(1-based line numbers per the spec page; out-of-range
target falls through to the end of the list) +Break+Exit
control flow, and terminates a pathologicalGotoself-loop
via alen * 16step budget so a malformed disc can never hang
the interpreter.VmAction— the playback-engine-visible effect of one step:
Continue/Break/Exit/Link(LinkAction)/JumpTitle
/JumpVtsTitle/JumpVtsPtt/JumpSs(JumpSSTarget)/
CallSs(CallSSTarget)/Resume(ResumePoint)/SetNavTimer { seconds, pgcn }/NoOpRaw(NavCommand). The interpreter
applies any register / counter / SPRM mutations the instruction
implied before returning, so the engine sees the post-state.LinkAction+ResumePoint— typed Link-family
descriptors.LinkAction::Subset { subset, hl_bn }covers the
13 enum-style forms (LinkTopCell…LinkTailPGC); the four
numbered forms (Pgcn,Pttn,Pgn,Cn) each get a dedicated
variant.ResumePoint { resume_cell, hl_btn }carries the
CallSSrsm_cellbyte through to a matchingRSMso a player
can resume to a different cell than the one active at call time.Vm::evaluate+Vm::apply_set— pure helpers exposing
the CMP and SET sub-op tables.evaluatecovers all 7 named
comparison predicates plus theNone"unconditional" sentinel;
apply_setcovers all 12 named SET ops (mov,swp,add,
sub,mul,div,mod,rnd,and,or,xor) using
wrapping arithmetic for overflow,checked_div/checked_rem
for the zero-divisor case (returns the destination unchanged
rather than panic), and a deterministic0placeholder for
rnduntil a caller wraps the VM with an entropy source.Vm::push_resume/Vm::pop_resume/Vm::resume_depth
— public RSM stack manipulators for tests + tooling. Push is
capacity-bounded atMAX_RSM_DEPTH(drops the new frame and
returnsfalserather than overflow).- 12 SPRM index constants re-exported at crate root
(SPRM_AUDIO_STREAM,SPRM_SUBPICTURE_STREAM,SPRM_ANGLE,
SPRM_TITLE,SPRM_VTS_TITLE,SPRM_PGCN,SPRM_PTT,
SPRM_HL_BTNN,SPRM_NV_TIMER,SPRM_NV_PGCN,SPRM_AMXMD,
SPRM_PARENTAL_LEVEL) so callers don't carry magic numbers. - 37 new unit tests covering register-file defaults + out-of-range
indexing + counter-mode toggle + tick saturation, the full CMP
sub-op truth table, every named SET op including overflow-wrap- zero-divisor +
Invalidno-op,step()dispatch for every
NavInstructionfamily (Set arithmetic / Swap exchange /
SetStn per-flag application / SetNvtmr action + SPRM 9-10 load /
SetGprmMd counter-mode toggle / SetHlBtnn / SetTmpPml / every
Link/Jump/Call surface / CallSs push + RSM pop with hl_btn
propagation / RSM-with-empty-stack falls through to Continue /
push bounded to MAX_RSM_DEPTH / Unknown + Invalid → NoOpRaw
without mutation), andrun_list()PC handling (clean
Nop chain / Break-mid-list / Exit-mid-list / Goto 1-based
addressing / out-of-range Goto falls off the end / runaway
Goto-self-loop terminates under budget / PC resets between
invocations / defaultNavCommandruns as single NOP).
163 lib tests (was 126 after the SPU compositor landed).
- zero-divisor +
Changed
- Phase 3c precursor → Phase 3c proper: the
navmodule's
NavInstructiondisassembler is now consumed by the newvm
module's interpreter, and Type 4..6 compounds carry their full
operand triple instead of just the classifier sub-ops. Existing
NavInstructiondecode + the disc / IFO / VOB / SPU / MKV
surfaces are unchanged. - Breaking —
NavInstruction::{SetCLnk, CSetCLnk, CmpSetLnk}
field layout extended with the per-row operand fields documented
inmpucoder-vmi.htmltable 2; the previous classifier-only
shape (set_op,cmp_op, and Type 4'sscronly) no longer
compiles. Pre-0.0.3 release — no published consumer to break. - Scrubbed an attributive external-implementation mention in
disc.rs'sDvdFileKinddoc comment and an enumerated-denial
paragraph at the bottom ofREADME.md; both are now spec-only
wording per the project's clean-room provenance discipline.