Master data views¶
A view answers, for every row of a master table, which objects of the catalog the game shows for it: the full
illustration of a member card and its sub-sprites, the icon of a stamp, the jacket of a song, the banner of a story
episode, the icon of a reward. The rules are data (src/nnnotes/pipeline/viewrules.json, package data) read by
nnnotes.pipeline.views; a view document lists object ids, so it depends on the master data and the catalog, never on how
the objects are encoded.
Rules¶
{"format": 1,
"resolvers": {"GameResourceType": {"1": {"name": "Item", "table": "MasterItem", "vars": {"path": "_imagePath"},
"address": "{path}", "expect": "Sprite", "names": {"name": "_nameTextId"}}}},
"views": {"cards": {"version": 1, "table": "MasterMemberCard",
"vars": {"asset": "_assetID", "rarity": "_rarity",
"band": ["_characterID", "MasterCharacter._bandID"]},
"names": {"name": "_nameTextID"},
"prefixes": ["MemberCard/"],
"roles": [{"role": "member_full", "address": "MemberCard/{asset}/member_full[member_full]",
"expect": "Sprite", "required": true},
{"role": "movie", "address": "MemberCard/{id}/movie/{part}",
"each": {"var": "part", "values": ["anime_part1", "anime_part2"]},
"when": {"field": "rarity", "op": "eq", "value": 4}}]}}}
format: the rules format this version reads (1). Every view has aversion; a change of a view's rule is a change of its version.table: the master table of the rows (the rows of a view are sorted byid, default_id). A view ofsourcesinstead has one row per distinct pair of a GameResourceType and an id that the listed tables name ({table, type, id}: the columns of the pair); its vars aretypeandid.vars: values of a row. A column name, or a chain[column, "Table.column", ...]: each step looks the previous value up as the_idof the next table.names: text ids (MasterText_id), given in all five languages (ja,en,zh-Hant,zh-Hans,ko); a text id without a MasterText row givesnull, an empty text id leaves the name out.prefixes,exclude: the catalog keys the view accounts for (reverse coverage);*stands for one path segment.roles:role(its name),address(a template:{var}or{var:spec}with a Python format spec, such as{id:05d}),expect(Sprite,Texture2D,GameObject, another class name, a list of class names in order of preference, orany, the default),required,when,each, orresolverinstead of an address (a resolver role has noeachorexpect: the resolver says what each code expects).when: a condition or a list of conditions that must all hold,{field, op, value}, wherefieldis a var, an enumerated var or a column. Operators:eq,ne,lt,le,gt,ge,in,notin,empty,nonempty. A role whose condition fails isnot-applicable.each: the role is a list, one entry per value ofvar: fixedvalues, the list in acolumn, or the distinct next path segments of the catalog keys undercatalogPrefix(a template).resolvers: per name, per code, how a polymorphic reference is shown: thetablewhose row has that id, itsvarsandnames, theaddressandexpect. A code with no table has a fixed address.
Resolving an address¶
An address is a catalog key, optionally followed by an Addressables sub-object name: key[sub]. The address index
maps each catalog key to the census objects of the container path it loads, in the order the bundle stores them
(object id, class, name, and the stable address of contract.stable_address when known);
views.AddressIndex.from_addresses builds it from the address table of the link.addresses stage.
A role resolves to the object the Addressables loader returns when the game loads the address as the expected class:
- With
[sub]: the first object namedsubof the expected class. - Without
[sub]: the first object of the expected class. A Sprite load of a sprite sheet (a texture with several sprites) returns its first sprite in stored order, whatever the sprites are named. - With no expected class (
any), the first object (for a texture, the texture itself). A list of classes tries them in order and takes the first class that has an object.
The entry of a role holds address, status and, when resolved, object, class, stable and, when several
objects fit, among (how many). Statuses:
| Status | Meaning | Also in the entry |
|---|---|---|
ok |
an object fits | object, class, stable, among |
missing-key |
not a catalog key, or a key whose container has no census object | detail |
missing-sub |
the key has objects, none fits | available: the key's objects |
not-applicable |
the condition of the role fails, or the resource type has no resolver | |
no-value |
a value the address needs is empty, or a chain or resolver finds no row | detail |
An entry of an each role also holds each ({var: value}); an entry of a resolver role holds resource (the
resolver's name).
View documents¶
nnnotes.view/1, canonical JSON (docs/contracts.md):
{"schema": "nnnotes.view/1", "view": "cards", "version": 1, "rules": "<digest>", "tables": ["MasterBand", "..."],
"rows": [{"id": 1, "vars": {"asset": 1, "rarity": 2},
"names": {"name": {"ja": "...", "en": "...", "zh-Hant": "...", "zh-Hans": "...", "ko": "..."}},
"roles": {"member_full": {"address": "MemberCard/1/member_full[member_full]", "status": "ok",
"object": "CAB-...:-4153...", "class": "Sprite"},
"movie": [{"each": {"part": "anime_part1"}, "status": "not-applicable"}]}}],
"coverage": {"rows": 1,
"roles": {"member_full": {"required": true, "counts": {"ok": 1}, "gaps": []}},
"reverse": {"prefixes": ["MemberCard/"], "exclude": [], "keys": 1, "unreferenced": []}}}
rules: the key hash of the rules format, the view's rule and the resolvers it uses (views.rules_digest);tables: every master table the view reads.- Rows of a view of sources also hold
from, the tables that name the resource. - Coverage both ways. Forward, per role: the number of entries per status, and
gaps, the rows where a required role ismissing-keyormissing-sub(no-valueandnot-applicableare the master data saying there is nothing to show). Reverse: the catalog keys under the prefixes that no row references.views.unreferenceddoes the same over several views, so views sharing a folder cover each other. Gaps are data: building a view never fails because of them. - A view reads the master tables it lists, its rules and part of the address index.
views.Recorderrecords that part (the keys looked up and the prefixes listed) as a document whose digest identifies it: the same tables, rules and recorded part give the same view. - Object ids change when a bundle's content changes;
views.diffcompares two documents of a view per row, role and enumerated value, and tells a new status or address from a new place (moved: the stable address changed) and from a new object id at the same place (object).
Stages¶
nnnotes.pipeline.views:stages gives one stage per view, view.<name> (docs/stages.md), run by nnnotes export assets
--views and planned by nnnotes store plan:
- Subjects: the catalog subjects of
link.addresses(the export command has one,main), when master data is set and the view is selected (the planning factsmasterandviews). - Inputs:
master:<Table>for every table the view reads (the decoded table file),rules(the rules document of the view alone: its rule, the resolvers it uses and the format) andaddresses(the part of the address table oflink.addresses:<subject>the view reads: the keys it looked up and the prefixes it listed, asviews.Recorderrecords it). No parameters, no atoms. So the key of a view changes when one of its tables, its rule or the addresses it reads change, and never with how objects are encoded or with tables and keys other views read. A table the master data lacks fails the task. - Version: the view's
version. - Artifact:
view.<name>:<subject>#view, the view document; its facts:rows,entries(entries per status),gaps(asviews.gaps: the (row, required role) pairs that aremissing-keyormissing-sub;export --strictexits 1 when a view has any) andunreferenced(reverse coverage). - Derived document of the
originallayout:views/<name>.json(views/<subject>/<name>.jsonfor a subject other thanmain), the view document with"layout": "original"and, in every entry naming an object,files(artifact role -> path of the artifacts the layout places for the object; for an object inside a prefab, the prefab's file) andpath(the file of its primary artifact;nullwhen the layout places none, for instance when the object's bundle was not exported).
Views¶
backgrounds¶
Profile backgrounds: the rows store full addresses. Table MasterBackground. Vars: path = _assetPath, thumbnail
= _thumbnailAssetPath. Names: name = _nameTextId, description = _descriptionTextId. Prefixes:
Image/Background/, thumbnail/Background/.
| Role | Address | Expect | Condition | Required |
|---|---|---|---|---|
image |
{path} |
any | yes | |
thumbnail |
{thumbnail} |
Sprite | yes |
band_items¶
Band items. Table MasterBandItem. Vars: id = _id, band = _bandId. Names: name = _nameTextId,
description = _descriptionTextId. Prefixes: Band/*/BandItem/.
| Role | Address | Expect | Condition | Required |
|---|---|---|---|---|
band_item |
Band/{band}/BandItem/{id}/band_item |
Sprite | yes |
bands¶
Bands. Table MasterBand. Vars: id = _id, r_background = _memberRarityRBackgroundAssetPath. Names: name =
_nameTextID. Prefixes: Band/ (except Band/*/BandItem/).
| Role | Address | Expect | Condition | Required |
|---|---|---|---|---|
band_small_icon |
Band/{id}/band_small_Icon |
Sprite | ||
band_small_icon_white |
Band/{id}/band_small_icon_white |
Sprite | ||
band_room_background |
Band/{id}/band_room_background |
Sprite | ||
band_board_self |
Band/{id}/Friendship/BandBoard_Self |
GameObject | ||
band_board_other |
Band/{id}/Friendship/BandBoard_Other |
GameObject | ||
band_logo |
Band/{id}/band_logo |
Sprite | ||
band_logo_white |
Band/{id}/band_logo_white |
Sprite | ||
band_menu_enabled_icon |
Band/{id}/band_menu_enabled_icon |
Sprite | ||
band_menu_disabled_icon |
Band/{id}/band_menu_disabled_icon |
Sprite | ||
live_stage |
Band/{id}/live_stage/live_stage |
any | ||
live_stage_lightweight_background |
Band/{id}/live_stage/lightweight_background |
Sprite | ||
live_start_timeline |
Band/{id}/timeline/live_start_playable_timeline |
any | ||
band_profile |
Band/{id}/BandProfile/BandProfile |
any | ||
band_fade |
Band/{id}/band_fade |
Sprite | ||
band_studio_background |
Band/{id}/band_studio_background |
Sprite | ||
band_stage_background |
Band/{id}/band_stage_background |
Sprite | ||
band_arena_background |
Band/{id}/band_arena_background |
Sprite | ||
login_bonus_bg |
Band/{id}/LoginBonus/LoginBonusBg |
any | ||
login_bonus_screen_bg |
Band/{id}/LoginBonus/LoginBonusScreenBg |
any | ||
login_bonus_screen_frame |
Band/{id}/LoginBonus/LoginBonusScreenFrame |
any | ||
member_rarity_r_background |
{r_background} |
Sprite |
cards¶
Member cards. asset formats most addresses; the movie parts use the card id. R cards (rarity 2) show the background
of their character's band; the movie parts are those of SSR cards (rarity 4). Table MasterMemberCard. Vars: id =
_id, asset = _assetID, rarity = _rarity, band = _characterID → MasterCharacter._bandID, r_background
= _characterID → MasterCharacter._bandID → MasterBand._memberRarityRBackgroundAssetPath. Names: name =
_nameTextID, subtitle = _subtitleTextID. Prefixes: MemberCard/.
| Role | Address | Expect | Condition | Required |
|---|---|---|---|---|
member_full |
MemberCard/{asset}/member_full[member_full] |
Sprite | yes | |
member_full_detail_pivot |
MemberCard/{asset}/member_full[detail_pivot] |
Sprite | ||
member_full_face_center_x |
MemberCard/{asset}/member_full[face_center_x] |
Sprite | ||
member_full_face_center |
MemberCard/{asset}/member_full[face_center] |
Sprite | ||
member_full_sprite_meta_data |
MemberCard/{asset}/member_full_sprite_meta_data |
any | ||
member_character |
MemberCard/{asset}/member_character[main] |
Sprite | yes | |
member_character_formation |
MemberCard/{asset}/member_character[formation] |
Sprite | ||
member_character_member_result |
MemberCard/{asset}/member_character[member_result] |
Sprite | ||
member_character_face_center_x |
MemberCard/{asset}/member_character[face_center_x] |
Sprite | ||
member_character_face_center |
MemberCard/{asset}/member_character[face_center] |
Sprite | ||
member_character_sprite_meta_data |
MemberCard/{asset}/member_character_sprite_meta_data |
any | ||
member_background |
MemberCard/{asset}/member_background[member_background] |
Sprite | rarity ne 2 |
|
member_background_formation |
MemberCard/{asset}/member_background[formation] |
Sprite | rarity ne 2 |
|
member_background_texture |
MemberCard/{asset}/member_background |
Texture2D | ||
member_background_r |
{r_background} |
Sprite | rarity eq 2 |
|
gacha_band_background |
MemberCard/MemberCommon/member_background{band}[member_background{band}_0] |
Sprite | ||
member_thumbnail |
MemberCard/{asset}/member_thumbnail[member_thumbnail] |
Sprite | yes | |
member_thumbnail_square |
MemberCard/{asset}/member_thumbnail[square] |
Sprite | yes | |
skill_sprite |
MemberCard/{asset}/skill_sprite |
Sprite | ||
member_preview_movie |
MemberCard/{asset}/member_preview_movie |
any | ||
movie |
MemberCard/{id}/movie/{part} for each part in anime_part1, anime_part2, live2d_in, live2d_loop |
any | rarity eq 4 |
|
member_gacha_animation_config |
MemberCard/{asset}/member_gacha_animation_config |
any |
characters¶
Characters. Table MasterCharacter. Vars: id = _id. Names: name = _nameTextID, short_name =
_shortNameTextID. Prefixes: Character/Image/.
| Role | Address | Expect | Condition | Required |
|---|---|---|---|---|
character_sprite |
Character/Image/{id}/character_sprite |
Sprite | ||
character_thumbnail |
Character/Image/{id}/character_thumbnail |
Sprite | ||
character_face_icon |
Character/Image/{id}/character_face_icon |
Sprite | ||
character_round_icon |
Character/Image/{id}/character_round_icon |
Sprite | ||
board_icon |
Character/Image/{id}/board_icon |
Sprite |
comics¶
Loading screen comics. Table MasterLoadingComics. Vars: image = _imageAsset. Prefixes: Image/Comic/.
| Role | Address | Expect | Condition | Required |
|---|---|---|---|---|
comic |
Image/Comic/{image} |
Sprite | yes |
degrees¶
Degrees (titles): the row stores the full address. Table MasterDegree. Vars: path = _imagePath. Names: name =
_nameTextId, description = _descriptionTextId. Prefixes: Image/Degree/.
| Role | Address | Expect | Condition | Required |
|---|---|---|---|---|
image |
{path} |
Sprite | yes |
events¶
Events. Table MasterEvent. Vars: image = _imageAsset, logo = _logoAsset, background = _backgroundAsset,
banner = _bannerAsset. Names: name = _nameTextId. Prefixes: Image/Event/.
| Role | Address | Expect | Condition | Required |
|---|---|---|---|---|
image |
Image/Event/{image} |
Sprite | yes | |
logo |
Image/Event/{logo} |
Sprite | yes | |
background |
Image/Event/{background} |
Sprite | yes | |
banner |
Image/Event/{banner} |
Sprite | yes |
exchange_categories¶
Exchange categories. Table MasterExchangeCategory. Vars: banner = _bannerAsset. Names: name = _nameTextId.
Prefixes: Exchange/Category/.
| Role | Address | Expect | Condition | Required |
|---|---|---|---|---|
banner |
Exchange/Category/{banner} |
Sprite | yes |
exchange_products¶
Exchange products. Table MasterExchangeProduct. Vars: thumbnail = _thumbnailAsset. Prefixes:
Exchange/ItemThumbnail/.
| Role | Address | Expect | Condition | Required |
|---|---|---|---|---|
thumbnail |
Exchange/ItemThumbnail/{thumbnail} |
Sprite | yes |
friendship_episodes¶
Friendship story episodes (their banners share a folder with the story episodes'). Table
MasterStoryFriendshipEpisode. Vars: banner = _banner. Prefixes: Story/Banner/Episode/.
| Role | Address | Expect | Condition | Required |
|---|---|---|---|---|
banner |
Story/Banner/Episode/{banner} |
Sprite | yes |
friendships¶
Character pairs of friendship stories. Table MasterCharacterFriendship. Vars: banner = _storyBanner. Prefixes:
Character/StoryBanner/.
| Role | Address | Expect | Condition | Required |
|---|---|---|---|---|
story_banner |
Character/StoryBanner/{banner} |
Sprite | yes |
gacha¶
Gacha banners and logos. The game formats the logo address from the gacha id; the row also stores a logo address.
Table MasterGacha. Vars: id = _id, banner = _bannerAssetName, logo_asset = _logoAssetName. Names: name
= _nameTextId. Prefixes: Gacha/Banner/, Gacha/Logo/.
| Role | Address | Expect | Condition | Required |
|---|---|---|---|---|
banner |
{banner} |
Sprite | yes | |
logo |
Gacha/Logo/gacha_logo_{id} |
Sprite | ||
logo_asset |
{logo_asset} |
Sprite |
home_banners¶
Home screen banners: the row stores the full address. Table MasterHomeBanner. Vars: image = _imageAsset.
| Role | Address | Expect | Condition | Required |
|---|---|---|---|---|
image |
{image} |
Sprite | yes |
items¶
Items: the row stores the full address. Table MasterItem. Vars: path = _imagePath. Names: name =
_nameTextId, description = _descriptionTextId. Prefixes: Item/.
| Role | Address | Expect | Condition | Required |
|---|---|---|---|---|
icon |
{path} |
Sprite | yes |
jackets¶
Song jackets. Table MasterLiveMusic. Vars: jacket = _jacketAssetName. Names: title = _titleTextID. Prefixes:
Image/Jacket/.
| Role | Address | Expect | Condition | Required |
|---|---|---|---|---|
jacket |
Image/Jacket/{jacket} |
Sprite | yes | |
jacket_small |
Image/Jacket/small/{jacket} |
Sprite | yes |
login_bonuses¶
Login bonus sheets: the rows store full addresses. Table MasterLoginBonus. Vars: sheet = _sheetImageAsset,
background = _backgroundImageAsset. Names: name = _nameTextID. Prefixes: LoginBonus/.
| Role | Address | Expect | Condition | Required |
|---|---|---|---|---|
sheet |
{sheet} |
Sprite | yes | |
background |
{background} |
Sprite | yes |
monthly_passes¶
Monthly passes (the id written with five digits). Table MasterMonthlyPass. Vars: id = _id. Names: name =
_nameTextId, description = _descriptionTextId. Prefixes: Shop/Pass/Banner/.
| Role | Address | Expect | Condition | Required |
|---|---|---|---|---|
banner |
Shop/Pass/Banner/{id:05d} |
Sprite | yes |
rewards¶
Every resource that a reward, prize, product or payment table names: one row per distinct (GameResourceType, id),
from lists the tables naming it, and the icon comes from the resolver of the type. Sources (type, id):
MasterArenaDailyReward, MasterArenaPromotionReward, MasterArenaRankingReward, MasterArenaSeasonReward,
MasterBattleLiveReward, MasterChallengeLiveEventReward, MasterCharacterFriendshipRankReward,
MasterCharacterRankReward, MasterCircleMissionReward, MasterExchange, MasterExchangeProduct,
MasterGachaBonusLot, MasterGachaPrize, MasterGekisouLiveRankReward, MasterInvitationReward,
MasterInvitationSuccessReward, MasterLiveArenaRankReward, MasterLiveEventReward, MasterLiveFreeReward,
MasterLiveMusicComboReward, MasterLiveMusicScoreReward, MasterLiveStamp, MasterLoginBonusSlot,
MasterMissionReward, MasterMonthlyPassContinuationReward, MasterMonthlyPassDailyReward,
MasterMonthlyPassFirstTimeReward, MasterMonthlyPassPurchaseReward, MasterOfflineBonusItemLot, MasterReward,
MasterSeasonPassReward, MasterShopProduct, MasterStoryReward, MasterVipDailyReward, MasterVipRankPack,
MasterVipRankUpReward.
| Role | Address | Expect | Condition | Required |
|---|---|---|---|---|
icon |
resolver GameResourceType |
per type | yes |
season_passes¶
Season passes. Table MasterSeasonPass. Vars: banner = _bannerAsset. Names: name = _nameTextId, description
= _descriptionTextId. Prefixes: SeasonPass/Banner/.
| Role | Address | Expect | Condition | Required |
|---|---|---|---|---|
banner |
SeasonPass/Banner/{banner} |
Sprite | yes |
shops¶
Shop products. Table MasterShop. Vars: thumbnail = _thumbnailAsset, dialog = _dialogAsset, tab =
_tabAsset. Names: name = _nameTextId, description = _descriptionTextId. Prefixes: Shop/ItemThumbnail/,
Shop/Dialog/, Shop/Tab/.
| Role | Address | Expect | Condition | Required |
|---|---|---|---|---|
thumbnail |
Shop/ItemThumbnail/{thumbnail} |
Sprite | yes | |
dialog |
Shop/Dialog/{dialog} |
Sprite | yes | |
tab |
Shop/Tab/{tab} |
Sprite | yes |
skill_icons¶
Skill icons (a skill names its icon by id). Table MasterSkillIcon. Vars: icon = _normalIconAssetName. Prefixes:
Character/Skill/.
| Role | Address | Expect | Condition | Required |
|---|---|---|---|---|
icon |
Character/Skill/{icon} |
Sprite | yes |
spots¶
Home spots: the rows store full addresses. Table MasterHomeSpot. Vars: thumbnail = _thumbnailAssetPath,
background = _backgroundAssetPath, situation = _situationAssetPath. Names: name = _nameTextId. Prefixes:
Image/Spot/.
| Role | Address | Expect | Condition | Required |
|---|---|---|---|---|
thumbnail |
{thumbnail} |
Sprite | yes | |
background |
{background} |
any | ||
situation |
{situation} |
any |
stamps¶
Stamps: the row stores the full address. Table MasterStamp. Vars: path = _stampAsset. Names: name =
_nameTextId. Prefixes: Stamp/.
| Role | Address | Expect | Condition | Required |
|---|---|---|---|---|
stamp |
{path} |
Sprite | yes |
story_chapters¶
Story chapters. Table MasterStoryChapter. Vars: banner = _banner, image = _image, icon = _icon. Names:
name = _nameTextId, description = _descriptionTextId. Prefixes: Story/Banner/Chapter/,
Story/Image/Chapter/, Story/Icon/.
| Role | Address | Expect | Condition | Required |
|---|---|---|---|---|
banner |
Story/Banner/Chapter/{banner} |
Sprite | yes | |
image |
Story/Image/Chapter/{image} |
Sprite | yes | |
icon |
Story/Icon/{icon} |
Sprite | yes |
story_episodes¶
Story episodes. Table MasterStoryEpisode. Vars: banner = _banner, image = _image. Names: description =
_descriptionTextId. Prefixes: Story/Banner/Episode/, Story/Image/Episode/.
| Role | Address | Expect | Condition | Required |
|---|---|---|---|---|
banner |
Story/Banner/Episode/{banner} |
Sprite | yes | |
image |
Story/Image/Episode/{image} |
Sprite | yes |
support_cards¶
Support cards. Table MasterSupportCard. Vars: asset = _assetID. Names: name = _nameTextID, description =
_descriptionTextID. Prefixes: SupportCard/.
| Role | Address | Expect | Condition | Required |
|---|---|---|---|---|
snap_full |
SupportCard/{asset}/snap_full[snap_full] |
Sprite | yes | |
snap_thumbnail |
SupportCard/{asset}/snap_thumbnail |
Sprite | yes | |
skill_sprite |
SupportCard/{asset}/skill_sprite |
Sprite |
Resolvers¶
GameResourceType (the icon of a resource, as the game's resource icon shows it; a type without a row here has no
icon: not-applicable):
| Code | Resource | Table | Address | Expect |
|---|---|---|---|---|
| 1 | Item | MasterItem |
{_imagePath} |
Sprite |
| 2 | MemberCard | MasterMemberCard |
MemberCard/{_assetID}/member_thumbnail[member_thumbnail] |
Sprite |
| 3 | SupportCard | MasterSupportCard |
SupportCard/{_assetID}/snap_thumbnail |
Sprite |
| 7 | GachaPoint | Item/common/item_icon_seal_member |
Sprite | |
| 8 | Music | MasterLiveMusic |
Image/Jacket/{_jacketAssetName} |
Sprite |
| 9 | Stamp | MasterStamp |
{_stampAsset} |
Sprite |
| 10 | PremiumPass | MasterSeasonPass |
SeasonPass/Banner/{_bannerAsset} |
Sprite |
| 17 | Degree | MasterDegree |
{_imagePath} |
Sprite |
| 18 | Background | MasterBackground |
{_thumbnailAssetPath} |
Sprite |
| 19 | Spot | MasterHomeSpot |
{_thumbnailAssetPath} |
Sprite |
| 1001 | BiliChatTheme | MasterBiliChatTheme |
{_iconAssetPath} |
Sprite |
| 1002 | BiliChatBubble | MasterBiliChatBubble |
{_iconAssetPath} |
Sprite |
| 1003 | BiliChatFrame | MasterBiliChatFrame |
{_iconAssetPath} |
Sprite |
Voices¶
view.voices (nnnotes.pipeline.voices) indexes the character voices the master data names and those of the story
episodes: per voice its source row, characters, category, text in the five languages and conditions, its cue sheet
and cue, and the decoded streams of the export that play it. It is a stage of its own rather than a view of the
rules above: besides master tables and the address table it reads the results of unity.export (the episodes) and
cri.audio, and the ACB of every decoded cue sheet. Its rules are the voices entry of viewrules.json
(nnnotes voices queries its document, docs/commands.md).
Sources¶
| Source | Table | Characters, sound, text | Category |
|---|---|---|---|
Talk |
MasterTalk |
_characterId, _voiceSoundId, _textId |
_category (TalkCategory) |
CharacterVoice |
MasterCharacterVoice |
_characterId, _soundId, _textId |
_type (CharacterVoiceType) |
MemberCard |
MasterMemberCard |
_characterID, _gachaVoiceSoundId, _gachaVoiceTextId |
|
LiveCharacter |
MasterLiveCharacter |
_characterID, _liveSkillVoiceSoundID, _liveSkillVoiceTextID |
|
LiveGekisouVoice |
MasterLiveGekisouVoice |
_characterID, _voiceID, _voiceTextID |
_gekisouVoiceType (GekisouVoiceType) |
LiveDialogueCommon |
MasterLiveDialogueCommon |
_characterID, _comboVoiceSoundID, _comboVoiceTextID |
|
LiveDialogueFixedPair |
MasterLiveDialogueFixedPair |
slot 1: _characterID01, _character01ComboVoiceSoundID, _character01ComboVoiceTextID; slot 2: the same with 02 |
|
LiveStartCharacterVoice |
MasterLiveStartCharacterVoice |
_characterId, _voiceSoundId, _voiceTextId |
|
HomeSpot |
MasterHomeSpot |
_characterIds, _introSoundId |
|
Title |
MasterTitle |
_voiceCharacterIds; cue names built per character (below) |
the cue's name |
Sound |
MasterSound |
the rows of category Voice that no other source names | |
Story |
MasterAdv |
per voice id of an episode command: the speaker (inferred characters), the episode's sound, the line (below) | the episode's kind |
- The first eight are the tables of the game's voice collection;
source.typegives their CharacterVoiceMasterType value. A row whose sound column is empty or 0 names no voice (counted asempty). Conditions are the listed columns with a value:_costumeId,_year,_seasonStartAt,_seasonEndAt,_startAt,_birthdayCharacterId,_unlockCharacterRank(Talk),_scoreRank,_startAt(CharacterVoice),_startAt(MemberCard, HomeSpot),_dialogueType,_dialogueCallType(LiveDialogueCommon),_dialogueType,_unlockFriendshipRank(LiveDialogueFixedPair),_unlockCharacterRank(LiveStartCharacterVoice). - A category without an enum is named after the source. TalkCategory: None 0, CharacterRankUp 1, Season 2,
Birthday 3, LoginBonus 4, Exchange 5, Live 6, OfflineBonus 7. CharacterVoiceType: LevelUp 0, Awaken 1,
SkillLevelUp 2, RankUp 3, LiveClear 4, LiveFullCombo 5, LiveAllPerfect 6, LiveResult 7, LiveBattleResultFirst 8,
LiveBattleResultHigh 9, LiveBattleResultLow 10. GekisouVoiceType: StartCombo 0, StartJustCount 1, StartLuck 2,
TopRank 3. MasterSound
_category: Bgm 0, Se 1, Voice 2, NotesSe 3. Title: every character in some row's_voiceCharacterIds(source.rows: those rows), with the cue names the client builds from the character id in cue sheetInitialVoice:Title_{character}_App_ON_01(TitleCall),Title_{character}_Company_BR_01(SplashCompanyBR),Title_{character}_Company_FT_01(SplashCompanyFT),Title_{character}_Company_bili_01(SplashCompanyBilibili).Soundrows have no characters, category or text in the master data. What their names suggest is ininferred, apart from the master facts:situation, the first of these patterns the cue name matches in full:
| Situation | Cue |
|---|---|
SpotTap |
spot_<n>_<n>_<n>_<name>_<n>[_<n>]_tap[_<n>] |
SpotAfter |
spot_<n>_<n>_<n>_<name>_<n>[_<n>]_after[_<n>] |
SpotIntro |
spot_<n>_<n>_<n>_<name>_<n>[_nasi] |
ResultPairTalk |
Result_<A>_Talk_CP_<B>_Call_<n>, ..._Res_<n> |
ResultTalk |
Result_<A>_Talk_Common_<n> |
TitleCall |
Title_<A>_App_<n> |
SplashCompany |
Title_<A>_Company_<X>_<n> |
Location |
vox_pt<n>_<a>[_<b>] |
characters, the characters whose name token (the last _ part of _nameTextID) is a word of the cue name, in
the order they appear; rows, the HomeSpot rows whose voice is in the same sheet.
Story episodes¶
Story reads every row of MasterAdv (the story rules). The row's _advEpisodeAsset names the episode: catalog
key Adv/Episode/<asset>/<asset>, an AdvEpisodeCollection MonoBehaviour whose Collection is the command list,
and three shards at the same key with a suffix, TextAssets of JSON rows: -Text (texts in the five languages, the
columns of MasterText), -Sound (sounds, the columns of MasterSound) and -SoundCueSheet (cue sheets, the columns
of MasterSoundCueSheet). A command with VoiceIDs gives one row per voice id:
source:rowthe adv id,commandthe command'sIndex,typeits AdvCommand value and name (Talk,ChatTalk,Voice, ...),slot(1,2, ...) when the command has several voice ids,ignoreDatawhen itsIgnoreDatais set.text:AdvTextIDwith its row of the episode's-Text(textsnull when that shard has no such row or is not read);speaker:TargetNameand each ofTargetTextIDswith its-Textrow.sound: the voice id with its-Soundrow's_categoryand_cueName, and the_cueSheetNameof the-SoundCueSheetrow of its_soundCueSheetID.category: the episode's kind;charactersis empty (the episode data names speakers, not characters).inferred, apart from these facts: rulespeaker-name, the segments ofTargetNamesplit at・that are, case ignored, a name part of one character: a_part of its_nameTextIDafter the leading parts every character's_nameTextIDshares (its name token, the last part, or another name the id gives it).charactersin speaker order andmatches({segment, character}); a part two characters have matches neither. A speaker without such a segment has noinferred.
The kind is that of the first table below (then by row id) with a row whose _advId is the episode; its fields and
names are listed on the episode. An episode none of them plays has kind null.
| Kind | Table | Fields | Names |
|---|---|---|---|
Main |
MasterStoryEpisode |
chapter _chapterId, episode _episodeNumber, character _characterId, another _isAnotherEpisode, extra _isExtraEpisode |
chapter: MasterStoryChapter _nameTextId of _chapterId |
Friendship |
MasterStoryFriendshipEpisode |
friendship _characterFriendshipId, episode _episodeNumber, characterA / characterB: MasterCharacterFriendship _masterCharacterIdA / _masterCharacterIdB |
|
HomeSpotTapTalk |
MasterStoryHomeSpotTapTalkEpisode |
spot _spotId, character _characterId |
|
LiveResult |
MasterStoryLiveResultEpisode |
characters _characterIds |
|
HomeSpot |
MasterHomeSpot |
spot _id |
spot _nameTextId |
The episodes are the document's episodes, by adv id: id, asset, title (_titleTextId), kind, table,
row, fields, names, status, detail and voices (its rows). An episode's status:
| Status | Meaning |
|---|---|
no-value |
the MasterAdv row has no asset |
missing-key |
the catalog has no key for the episode (a gap) |
not-exported |
the key exists; no unity.export result holds its MonoBehaviour |
unsupported |
the MonoBehaviour has no Collection, or a shard is not of the form above (detail) |
ok |
its voices are rows |
A voice of a home or after-live episode is often a cue that a row of another source also names; both rows are kept.
Cues and streams¶
The sound's _soundCueSheetID names the cue sheet (MasterSoundCueSheet _cueSheetName, catalog key
Cri/Sound/<sheet>) and _cueName the cue. The cue's streams come from the sheet's ACB tables (nnnotes.formats.acb): a
cue references a waveform, a synth, a sequence or a block sequence; synths reference waveforms, synths and
sequences; the tracks of sequences and blocks note on synths and sequences. A waveform of the memory AWB is the
stream at its position in the AWB, the numbering of the sheet's streams.json. A cue may play several streams (a
random or layered cue) and several cues one stream. The same cue name in two sheets is two cues. The waveform's
sample count, rate and channels are compared with the decoded stream; a difference is noted in detail.
Statuses¶
status is the first of these that holds; availability keeps the facts apart: master (the master data
resolves the sound to a sheet and a cue), catalog (the sheet's key is a catalog key; null when not looked up)
and exported (the cue's streams are decoded).
| Status | Meaning |
|---|---|
no-value |
no MasterSound row for the sound, or no MasterSoundCueSheet row for its sheet (detail); for Story, no -Sound row for the voice id or no -SoundCueSheet row for its sheet |
missing-key |
the catalog has no key Cri/Sound/<sheet>; for Story also: no key for the episode's -Sound or -SoundCueSheet (detail) |
not-exported |
the key exists; the export has no cri.audio result for its content (for Story also: the -Sound or -SoundCueSheet shard is not exported) |
unsupported |
the sheet's cri.audio task did not decode it (detail: the reason code) |
missing-cue |
the sheet is decoded; its ACB has no such cue, or the cue plays none of its streams |
ok |
the cue's streams are in audio |
Gaps are the rows that are missing-key or missing-cue, whatever their source, and the missing-key episodes.
Whether a site shows a voice is not recorded: sites are built separately.
Documents¶
nnnotes.voices/1, canonical JSON:
{"schema": "nnnotes.voices/1", "view": "voices", "version": 2, "rules": "<digest>", "tables": ["MasterAdv", "..."],
"characters": [{"id": 1, "token": "Alpha", "names": {"name": {"ja": "...", "en": "..."}, "short": {}, "en": {}}}],
"rows": [{"id": "MasterTalk:561",
"source": {"name": "Talk", "table": "MasterTalk", "row": 561, "column": "_voiceSoundId",
"type": {"enum": "CharacterVoiceMasterType", "value": 0, "name": "Talk"}},
"characters": [1], "category": {"name": "CharacterRankUp", "enum": "TalkCategory", "value": 1},
"sound": {"id": 100000001, "category": 2, "sheet": "VoiceSystem_01", "cue": "Growth_Alpha_RankUp_01"},
"text": {"id": "Talk_Text_561", "texts": {"ja": "...", "en": "...", "zh-Hant": "...", "zh-Hans": "...", "ko": "..."}},
"conditions": {"_costumeId": 1, "_unlockCharacterRank": 1},
"availability": {"master": true, "catalog": true, "exported": true}, "status": "ok",
"audio": {"cueId": 1, "lengthMs": 1000,
"streams": [{"stream": 0, "file": "Growth_Alpha_RankUp_01.flac",
"artifact": "cri.audio:<sha256>#Growth_Alpha_RankUp_01.flac",
"sha256": "...", "size": 1, "sampleRate": 48000, "channels": 1,
"samples": 48000, "seconds": 1.0}]}},
{"id": "MasterAdv:900001:3",
"source": {"name": "Story", "table": "MasterAdv", "row": 900001, "command": 3,
"type": {"enum": "AdvCommand", "value": 2, "name": "Talk"}},
"characters": [], "category": {"name": "Main"},
"sound": {"id": 5001, "category": 2, "sheet": "adv_voice_x_01", "cue": "adv_voice_x_01_001"},
"text": {"id": "x01_1", "texts": {"ja": "...", "en": "..."}},
"speaker": {"name": "alpha", "texts": [{"id": "adv_alpha", "texts": {"ja": "...", "en": "..."}}]},
"inferred": {"characters": [1], "rule": "speaker-name", "matches": [{"segment": "alpha", "character": 1}]},
"availability": {"master": true, "catalog": true, "exported": true}, "status": "ok", "audio": {}}],
"episodes": [{"id": 900001, "asset": "adv_script_x_01", "title": {"id": "...", "texts": {}}, "kind": "Main",
"table": "MasterStoryEpisode", "row": 101, "fields": {"chapter": 1, "episode": 1},
"names": {"chapter": {"ja": "..."}}, "status": "ok", "voices": 1}],
"snapshot": {"tables": {"MasterTalk": "<sha256>"}, "sheets": {"VoiceSystem_01": {"task": "cri.audio:<sha256>",
"acb": "<sha256>", "status": "exported"}},
"episodes": "<sha256>"},
"coverage": {"rows": 1, "sources": {"Talk": {"rows": 1, "empty": 0, "counts": {"ok": 1}, "gaps": []},
"Story": {"rows": 1, "empty": 0, "counts": {"ok": 1}, "gaps": [],
"episodes": {"count": 1, "counts": {"ok": 1}, "gaps": []}}},
"reverse": {"prefixes": ["Cri/Sound/Voice"], "keys": 1, "unreferencedKeys": [], "streams": 1,
"unreferencedStreams": {}}}}
- Row ids:
<Table>:<row id>(:<slot>for a row with two voices),Title:<character>:<category>,MasterSound:<id>,MasterAdv:<adv id>:<command index>(:<slot>for a command with several voice ids). Rows are in the order of the sources, then by id (Story: by adv id, then in the episode's command order); a row ofTitlehassource.rowsandsource.cue(the template) and no sound id; aHomeSpotrow hasnames.spot, aMemberCardrownames.card. snapshot: the content id of every master table read, per decoded sheet itscri.audiotask and ACB, and the content id of theepisodesinput (below).- Coverage. Forward, per source: rows, rows per status,
emptyandgaps; forStoryalsoepisodes(their number, number per status and themissing-keyones),emptycounting theokepisodes without voices. Reverse: the catalog keys under the prefixes (Cri/Sound/Voice,Cri/Sound/spot_,Cri/Sound/InitialVoice,Cri/Sound/adv_voice_), the ones whose sheet no row names (unreferencedKeys), and per decoded sheet the streams no row reaches (unreferencedStreams:[stream, name]).
Stage¶
- Subjects: the catalog subjects of
link.addresses, when master data is set andvoicesis selected. - Inputs:
master:<Table>for every table the rules read,rules(the format and thevoicesrules),addresses(the keys it looks up and the prefixes it lists, asviews.Recorderrecords them),sheets(each sheet thecri.audioresults are known by, ascristages.AudioStagenames them, that a row of the master sources names or the prefixes list: its task and ACB content id),acb:<sha256>for the ACB of every decoded one,episodes(per episode asset and part: the object its key names, the MonoBehaviour of the script or the TextAsset of a shard; theunity.exportartifact that holds it,jsonordata, found through the address table's serialized files; its content id) andadv:<sha256>for each of those contents; contextaudio, the keys of thecri.audioresults. It runs afterunity.exportandcri.audioand waits for their tasks; without them every present key and episode isnot-exported. A story voice is looked up in the sheets it reads. - Artifact
view.voices:<subject>#view; factsrows,entries(rows per status),gaps(export --strictexits 1 when there are any) andunreferenced(the streams of the decoded sheets that no row reaches). - Derived document of the
originallayout:views/voices.json, with"layout": "original"and thepathof every stream (nullwhen the layout places none).