身を捨ててこそ浮かぶ瀬もあれ

野菜のストックがしおれかけて来たとき,水を掛けたり,水栽培したりして延命させることができるが,それでもしおれっ放しで戻らないときは,野菜が浸るまで水を注いでやるとよい.野菜が水に浮いてくるまでじゃぶじゃぶに水を入れてやると翌日には完全に復活している.

image

水をたっぷり張って野菜が謂わば無重力状態のように水中に浮いているようにするというのが肝心なところだ.捨身,身を捨てるということもときには必要なときがある.

SUPPORTSIMPLEGRAPHオフ,「基準ノードの配偶者の配偶者側で展開する@20201230」オフのバージョンで源氏物語全系譜6.1.ZELの全体図を#57 女三宮で開いてい,MARGBOX::GetUpperNodeで親ノード不正エラーが発生する.障害ノードはMARGBOX:#401:#1262 女三宮(1)+#1263 柏木(1)→で,親ノードの位置にNAMEBOX:#1267 小宰相の君(1)が現れている.これはかなりおかしい.

image

相互関係を確認するためにインストール済の別アプリで開いてみた.どう見てもこの結婚枠の上位に来るようなノードではない.TribeRelocationの【7.2】完全木検定:すべての系列を完全系列として正準化するステージでMakePairListClean中に起きている.NAMEBOX:GetGoldenCoupleでZTYW婚を削除するタイミングで発生している.削除対象のZTYW婚はMARGBOX:#2006:#1262 女三宮(1)+#1263 柏木(1)→#1265 按察の君(薫付)(1)だ.

削除対象のZTYW婚の本人ノードは按察の君ではなくて,薫 #1204だ.按察の君と小宰相の君はいずれも薫の配偶者で道連れでZTYW婚に連れ出されたものだ.手順の中では本人の薫の仮ノードは削除されているが,配偶者が取り残されて孤立している.⇒ZTYW婚を始末する完結したルーチンRestoreErasedYoungWifeがあるので,それと差し替えることにしよう.これで問題は解決したが,まだ不具合が残っている.

MARGBOX::RestoreErasedYoungWife→ NAMEBOX:RestoreExtractBox→ RetrieveGhost→ RestoreYoungWife→ RestoreExtractBoxで処理後の位置関係の検査で不一致が出ている.⇒検査の意味が不明.抽出枠を復元したとき,親の結婚枠の位置とサイズが不変であることを仮定しているようだが… この論理は廃止でよい.

▲#90 雲井の雁で出口検査で水平スプリットを検出.#91 左衛門督(寄生)でも発生.確かに#218 ※系列でスプリットが生じている.

image

検査ルーチンが実動作している可能性があるので,まず,それを見てみよう.検査ルーチンは動作にまったく影響していないようだ.このスプリットはAccidentalCollision→DetectHorizontalSplitでは検出されていない.2018-07-13の決定で「HORIZONTALORDERフェーズまではAccidentalCollisionを実行しない」となっている.InvestigateHSplitsはこのスプリットを検出できるが,この関数は描画ステージ(絶対座標系)でなければ動作しないため,適用できない.また,適用したとしても,系統並び替えの出口でHeapTribeBoxesが実行されるまでは,系統間の距離が定まっていないので検査しても意味がない.

▲#121 明石の尼君で系列優先実ノード不在が出た.#150 ※,#169 中務宮でも起きる.

系列接続問題を「人間関係の解析」から「図形の配置問題」に転換する

ZTシステムの作図法の基本は,系図を系列に分解しそれらの相互位置関係を定めることにある.系列とは先祖ノードの直系血族とその配偶者の集合であり,自ずと木を構成するから系列単体を描画するというのは初歩的な課題だが,入り組んだ系列の相互関係を定めるというのは必ずしも容易ではない.この部分に関してはこれまでにも何度も試行錯誤的な作り替えを実施してきた.このような試行錯誤に終止符を打つために提案されたEstablishMajorTribeChainが廃止されたのは2019/02/04だ.系列優先ノードの選択基準を緩和するという方向に動き始めたのはこの頃からと思われる.緩和の方向性は最終的には,系列の接続関係を「人間関係の解析」から「図形の配置問題」に転換するという方向を目指していたのではないかと思う.この方向性はまだ完結したとは言えないが,大幅に緩いものになってきていることは確かだ.

SUPPORTSIMPLEGRAPHオフのシステムで源氏物語全系譜6.1.ZELの全体図を#4 明石中宮で開いてTribeRelocationの【5.2】重婚同類グラフ検定の事後処理を行なうステージでLIST::nextで停止した.前にこのエラーが発生したときには,対象リストを取り違えているという論理ミスがあったが,今回はどうなっているのだろう?

今回は事前にplist->find(pbox)を試しているにも関わらず発生している.このエラーが発生する一つ前に,「ノード対世代不一致」というのが起きて,PairBoxGeneChangeが実行されている.この関数の中でノード対が削除される場合がある.いや,そもそもエラーは(!find(listnod))ではなく,(PHASE > INITIALSTATE)という理由だ.

明らかにPAIRLIST #1443のbottomlistの値が間違っている.⇒PairBoxGeneChangeの実行で不良が発生している.NAMEBOX:RetrieveGhostでノード対を破棄したときに起きているようだ.⇒plist->deleteElement(pairbox, true);で発生している.PAIRLISTはdeleteElementを持っていない.⇒PAIRLIST::dataCountDownでリスト終端ノードが端点共有である場合を見落としていた.

同上サンプルを開いて,TribeRelocationの【7.8】多重カードゼロを検査して変化なしなら検定打ち切りステージでTRIBEBOX:SetMinorTribeのエラーが発生した.DisConnectedGraphを強制モードで実行しているところだ.障害系列は#1120 先祖=#801 左大臣(梅枝)(0)で優先仮ノードはNAMEBOX:#1336 今上(2),関係は配偶者,優先実ノードは#415 明石中宮(0)で関係は養子,所属系列は#1056 先祖=#427 一院(0)の始系列だ.この接続関係は「逆婚姻関係」と呼ばれるものに相当する.DecideTribeTypeはPRIME_BTWLEFTを返している.確かに,今上(2)はDOUBLYBLESSED,明石中宮(0)はRIGHTHUSBANDだから,PRIME_BTWLEFTという判定は正しい.

ここの論理は,優先仮ノードが配偶者で系列種別が婚姻関係でも逆婚姻関係でもない場合で,この系列が始系列であった場合の代替ノードを探すというものになっているが,始系列でない場合は無動作なので当然配偶者のままになっているから,明らかにおかしい.また,始系列の場合は通常は基準ノードが最初から確定しているのだから,代替を探すということ自体かなり奇妙な感じがする.複数系統の場合にはこのようなことが起こり得るかもしれないが,いずれにしても,始系列以外でここで停止するというのは誤りだろう.

同上サンプルを#48 四宮(桐壷院の)で開いてTRIBEBOX:DecidePrimaryNodeで停止した.優先仮ノード不在エラーが起きている.障害が起きているのはTRIBEBOX: #1068 先祖=#811 大臣(橋姫)(0)で,優先ノードはCARDLINK:#546 @70浮舟[0].浮舟は仮ノードを4つ持っているが,うち3つは一院系列,他の一つは常陸介系列で,橋姫系列というのは存在しない.

浮舟の母は2つ結婚を持っている.相手は八宮と常陸介で,浮舟は両方に属しているがどちらも相手方の系列で展開されているのだろう.従って,大臣(橋姫)系列の優先ノードとなり得るのは浮舟の母だけだ.DecidePrimaryNodeではこの後,TRIBEBOX:GetAlternativePrimeNodeで代替候補の浮舟の母を見つけているので,問題はない.つまり,ここでは停止しなくてよい.

予想されていたことだが,非連結系列が出てしまった.

同上サンプルを#81 弘徽殿女御で開いて,TribeRelocation【11】系列包括矩形領域を集積して系統を並列配置するステージで非連結系列が発生した.「始系列参照パスが途切れている系列」が3件.

  1. #110566 先祖=#789 左大臣(真木柱)(0)[27] 優先=#653 左大臣の女御(0)→#425 冷泉院(0) →主系列#110502:摂政太政大臣 type=婚姻関係
  2. #110568 先祖=#853 宰相の娘(0)[28] 優先=#853 宰相の娘(0)→ #425 冷泉院(0) → 主系列#110502:摂政太政大臣 type=婚姻関係
  3. #110570 先祖=#857 中納言の娘(0)[29] 優先=#857 中納言の娘(0)→#425 冷泉院(0)→主系列#110502:摂政太政大臣 type=婚姻関係

少しおかしい.これら3系列はすべて摂政太政大臣系列を参照しているが,摂政太政大臣系列は始系列ではなかったのか?⇒Majortribeが設定されていないためのようだ.いや,設定されているが,TRIBEBOX:GetMajorTribeでNULLを返している.GetMajorTribeでは

  1. 始系列である場合
  2. 仮ノードがノード対を持っている場合
  3. 仮ノードがBTW右手本人の場合
  4. 仮ノードがBTW左手本人の場合
  5. 仮ノードがノード対参照先実ノードで系列種別が逆婚姻関係の場合

以外は有効な系列接続関係と認めていない.しかし,現行ではもっと多様な関係が許されている.これには,親子関係による接続が含まれていないし,配偶者→配偶者の参照も認められていない.これを以下のように整理した.

  1. 仮ノード:本人 x 実ノード:本人 → 親元関係
  2. 仮ノード:本人 x 実ノード:配偶者 → 婚姻関係
  3. 仮ノード:配偶者 x 実ノード:本人 → 逆婚姻関係
  4. 仮ノード:配偶者 x 実ノード:配偶者 → BTW左接続関係/BTW右接続関係

すべてこの区分で解決した.

▲同上サンプルを#94 明石の上で開いて,DecidePrimaryNodeを実行中,MARGBOX::GetUpperNodeで「親ノード不正」エラーが発生

抽象グラフ検証系を外したベーシックなシステムの動作を再確認する

抽象グラフ検証系を外したベーシックなシステムの動作を再確認しておこう.グラフ検証系はZTシステムの肝とも言うべき重婚同類循環検定で用いられている機構で,ZTシステムの必須コンポーネントではあるが,それなしでも通常の系図を描画することは可能であり,どのような系図もたとえそれが最適なものではなかったとしても少なくともエラーなしで描画できなくてはならない.グラフ検証系がベーシックシステム自体の不良をカバーするものであってはならない.

MakeUpTreeの中で系列優先ノードを決定するDecidePrimaryNodeを全面的に書き換えた.これはある意味で,系列接続関係の仕様を根底から修正するものになっている.従来仕様では,従系列の系列優先仮ノードとそれが参照する主系列の優先実ノードは大概の場合は異なるカードの人名枠であることが仮定されていたが,改訂DecidePrimaryNodeではこの制限を大幅に緩和して,仮ノードと実ノードが同一人名であることがむしろノーマルであるような仕様になっている.

要は2系列の接点となるノードが決定できれば,その接続関係は問わないという立ち位置にシフトした.実際,系列優先ノード決定の時点では接続関係が確定していない配偶者ポジションにある2つのノードを結合するということが行われている.この関係は最終的にはBTWによって解決されているが,従来論理ではこのような選択は不可能だった.つまり,接続関係は後付けてよいというポリシー変更がなされたと解釈してよい.

グラフ検証系を外したZTベーシックシステムで,源氏物語全系譜6.1.ZELの全体図を#1 光源氏で開いて,出口検査のCheckPerfectTreeで「結婚枠内兄弟ノードの衝突」が検出されている.実際,NAMEBOX #615 藤壷の宮(0)と#605 末摘花(0)の人名枠が交叉している.水平交叉量は6で計算誤差として許容できる範囲を超えている.しかし,エラーを無視して描画するとこの交叉は解消してノーマルな状態で描画されている.これは検査関数の中で使われているCheckBrotherOrderが検査モードで呼び出されているにも関わらず,実動作を伴うものになっているためと考えられる.これはそれ自体不良なので調べておこう.

MARGBOX::CheckBrotherOrderは実行関数としてMARGBOX:checkBrotherOrderを呼び出している.この関数から呼び出しているCheckBrotherCrushがモードに関わりなく移動を実行している.⇒対処した.次の問題はなぜ,このエラーがメインループで検出されなかったか?という点だ.⇒どうも,検査ルーチンの中に実動作を伴うものがあるように思われる.⇒原因はTRIBELIST::CheckMaximalSegmentにある.セグメント検定はTRIBEBOX::CheckMaximalSegmentによって系列単位に実行される.障害が起きているのは一院系列 #1056なので,ここだけ見ればよいだろう.いや,障害はどうも複合的なもののようだ.

TRIBELIST::CheckMaximalSegmentの単独実行では発現しない.それに加えてtribelist->IsTribeCompleteを実行する必要があるようだ.この2つは実行順序を問わず,どちらを先に実行した場合でも発現する※.TRIBEBOX::CheckMaximalSegmentから呼び出されるTRIBEBOX:CheckMaximalCompactionは検査モードというのを持っていないが,それから呼び出されるMaximalCompactionには検査モードがある.便宜的にCheckMaximalCompactionの引数が空の場合は実行モード,そうでないときは検査モードであるとしてみよう.⇒この修正は意味があるが,現在の障害には影響を与えない.

※藤壷の宮(0)と末摘花(0)の人名枠の交叉はTribeRelocationの出口ですでに存在している.ただし,交叉量は4で許容誤差の範囲内にある.下記のMoveNeutralによる移動は実際にはわずか1しか移動していないが,IsTribeCompleteの中でもCheckMaximalSegmentが実行されているため,移動量1の動作が複数回実行されることにより,傷口が6まで拡大して発現に至るという状況になっている.

MAXIMALGRAPH::mergeUpperSegmentでBobject::MoveNeutralが実行されていた.MaximalCompactionから呼び出されるCountUpLooseBeltは検査モードを持っていない.この関数は本来検査専用と考えられていたものと思われるが,CountUpLooseBeltからMoveNeutralまではかなりの段数がある.

CountUpLooseBelt→ TRIBEBOX::CountLooseBelt→ MAXIMALGRAPH:JoinUpperSegment→ MergeUpperSegment→ mergeUpperSegment→ Bobject::MoveNeutral

中間の各関数は本来検査専用で,検査/実行を区別していない.mergeUpperSegmentでは対象結婚枠がZTYW婚の場合に限り,隣接人名ノードを接触位置まで移動するという操作を行っている.この動作を廃止するというのも一つの考え方であるかもしれない.

この処理を停止したら,今度はMARGBOX::CheckMarchainで「結婚鎖拡張チェーン不正」というエラーが起きるようになってしまった.上記修正とは本来まったく無関係であるようなところだが… 上の修正が妥当であるか否かは別として,これはこれでまた別の障害と考えるべきだろう.まず,こちらを先に片付けることにする.

上記のような条件の下で,TribeRelocationの【9】先祖並び自動オフで系列枠を線形物理配置というステージの「ZTYW婚の適正配置を実施する」という段で,CheckZeroPositionを実行して「結婚鎖拡張チェーン不正」というエラーが発生している.結婚チェーン上に並んだ2つの結婚枠が同じ配偶者を持っているとき,後ろの結婚枠のsiblingsが前方結婚枠を指していないというエラーだ.実際には空が入っている.siblingsというのは,「前方にある同じ母の子ども枠への参照:チェーンを構成する」というものなので,確かにどこかで間違えているように思われる.

  1. MARGBOX:#1725:#409 光源氏(0)+#603 夕顔(0)→#419 玉鬘(0)
  2. MARGBOX:#343:#409 光源氏(0)+#603 夕顔(0)→

どちらも光源氏(0)+夕顔(0)の子ども枠で本人と配偶者が同じ結婚枠が子どもも1人しかいないのに2つあるという点が不審だが… 属性を見ると,前者はTOOYOUNGWIFE|ERASEDYOUNGWIFE,つまり,ZTYW婚,後者はERASEDZEROGHOST|GOODSIBLINGとなっている.つまり,「ZTYW本人を参照する仮ノードが存在する元の親枠」だ.

この2つの結婚枠は元々は一つのものだったのだから,「拡張子ども枠」のようなものでないことは明らかであり,siblingsを持っていないのも当然のように思われるが,このようなエラーはこれまで見たことがないような気がする.ZTYW婚というのはそれほどレアなものではないので,もっと頻発してもよさそうな気もするのだが…

最終的にはこのZTYW婚は消えてオリジナルのMARGBOX:#343だけが残り,玉鬘は夕顔の直下に配置されている.mergeUpperSegment→ Bobject::MoveNeutral の問題に戻ろう.下図で赤く塗りつぶしたところはZTYW婚で,この図面だけでもかなりの個数が使われている.

image

ZTYW婚の位置は不定なので,親ノードと同一セグメントにするのが難しいため,強制的に親ノードを隣接ノードの接触位置まで移動するという便法を取っているのだが,そのような強行手段(小細工)を取らなくても事実上問題は解決されているように思われる.この解法はひとまず保留として様子を見ることにしたい.しかし,これ以外でも検査モードで実動作が起きている可能性があるので,点検が必要だ.

予防措置としてTRIBEBOX::CheckMaximalSegment実行中はOnCheckMaximalSegmentが立っているので,このフラグがONのときは停止するようにしておこう.OnCheckMaximalSegmentを拡張してTRIBELIST::CheckMaximalSegmentの範囲まで広げておく.Bobject::MoveNeutral,Bobject::RelativeとBobject::originateでこのフラグをチェックするようにした.

すべてのオブジェクトの固有データ部を完全に殺菌(ゼロクリア)する

条件コンパイルをフィックスするときの手続きにミスがあったため,ファイルをクローズしたときのシステムのクリーンアップに失敗していたが,ようやく保健所の調理場検査にパスできる状態になった.理想的には存続するすべてのオブジェクトの固有データ部を完全に殺菌(ゼロクリア)してしまうことが望ましいが,一部環境パラメータを保持しているオブジェクトがあり,そのようなものに関しては別途措置が必要になる.環境パラメータと一般データを形式的に識別することはできないので個別のクラスで対応することになるが,保全の必要な環境パラメータであることを明示するためにこれらを一度ローカル変数に退避しておいて,処理的には全領域をゼロクリアするというのがわかり易いのではないかと思う.保全されるべき環境パラメータには以下がある.

  1. long BASETABLE::tablesize テーブルの物理サイズ
  2. HWND COUPLING::hwnd ウィンドウハンドル
  3. long DATALIST::datasize リスト要素のデータサイズ
  4. QUICKDB KAKEIZU::carddata, marriage
  5. CSize TREEVIEW::WindowSize 親ウィンドウサイズ,物理単位 CPoint WindowPost ノーマル画面で親フレームの左上点物理座標 CPoint PreviewPost プレビュー画面で親フレームの左上点物理座標

NODULE::signatureという変数は使われていないので廃止する.

ZTシステム構成図7.BAD2.ZELを開いて,LIST::nextで停止する.(!find(listnod))が起きている.TribeRelocationでSIMPLEGRAPH:BuildTightHasseDiagramを実行しているところだ.障害ノードは#2408 NODELISTでSIMPLENODE #14742の次の要素を求めているが,#14742がリスト上に存在しないというエラーになっている.このリストはかなり複雑な構造になっているが,該当要素は存在していないようだ.重婚同類検定では枝グラフを3つ使っている.

SIMPLEGRAPH::TightenHasseDiagramは主語のグラフの他,引数でもう一つのグラフtajugraph3への参照が渡されている.問題の箇所にはこの2つのグラフの節点リストを取り違えるというミスがあった.重婚グラフ検定の論理は一度総点検する必要がある.もし,複数のグラフを併用する必要があるのであれば,2つのグラフを引数で渡して,関数自体はクラスの静的関数とした方がよいと思う.一応現状で正しい論理が実装されているという仮定で修正を入れてみよう.以下の関数を改造した.

  1. BuildTightHasseDiagram
  2. TightenHasseDiagram
  3. sortComponentList

「完全参照リスト管理をサポートする」というオプションをOPTIONSからSPECIFICATIONに格上げし,REFERENCELISTCONTROLとリネームした.⇒裏オプションでも動作することを確認しておこう.⇒参照リスト管理は現在,黒子としての存在に留まり表では活動していない.

SUPPORTSIMPLEGRAPH「SIMPLEGRAPH抽象グラフ検証系をサポートする」というオプションで抽象グラフ検証系を止めてみる.⇒実装した.グラフ検証系なしでも思ったより動作している.ZTシステム構成図7.ZELは難なく開けた.ただし,一部の検査論理は停止している.抽象グラフ検証系でサポートしている機能には以下がある.

  1. GENEBOX::CheckInverseCycle 危険対枝グラフの循環検定
  2. TOPOLOGY::TestInevitableMultiZero 重婚同類グラフの循環検定
  3. TRIBELIST::ShiftDirectAbsolute 絶対世代番号に基づきカードシフト
  4. TOPOLOGY::FindJikusenAncestor 血糖軸線図のサポート
  5. TOPOLOGY::MakeLinearEdgeList 系列枠の全順序を決定する

SUPPORTSIMPLEGRAPHオフのバージョンで渋沢一族8.ZELの全体図を#211 大川平兵衛の息子で開こうとして,TRIBEBOX:GetAlternativePrimeNodeで停止した.系列優先ノードが決定できない.この系列は#2501 先祖 #950 渋沢 長登(宗安)で,最初は#1038 尾高 平九郎を優先ノードとする親元関係で決定していたはずなのだが,#956 渋沢 やへの婚姻関係に切り替わっている.

最初のGetAlternativePrimeNodeで切り替わり,2回目で失敗しているという構図だ.何が変わったというのだろう?⇒いや,変化していないために停止している.これはこのカードがTRIBEBOX:DecidePrimaryNodeでリジェクトされていることを意味する.つまり,DecidePrimaryNodeとGetAlternativePrimeNodeが相反している.⇒DecidePrimaryNodeに逆婚姻関係を導入して解決した.

どうもDecidePrimaryNodeのできが悪過ぎる.今度は源氏:明石中宮で停止した.この関数は全面的に書き換えた方がよい.この関数では現在の優先ノードのチェックだけを行い,不可のときは自分で代替候補を探すのではなく,GetAlternativePrimeNodeに任せるようにした方がよい.優先ノードチェックの要点は,①GetRealnodeで優先実ノードを確保できるかどうか?②主系列と従系列の基準ノードに対する位置関係,③系列種別が合っているかどうか,だけでよい.

CARDLINK::selectflagオフという障害がぶり返している

BUG20-12-25 17-30-25.ZELの全体図を#5 pagesetupで開いて,TRIBEBOX::GetMajorTribeChainでASSERT_NEVER(upper == major) 「主系列チェーンがループしている」が起きる.このサンプルはZTシステム構成図7.ZELでカード削除を複数回実行→UNDO→REDOを繰り替えして発生したものだ.

障害が発生しているのはTRIBEBOX #7837,先祖#670 familytree,優先#2083 noduleだ.TribeRelocationのステージ【7.9】仮ノード消去と両手に花を適用して多重カードを削減の段で,TOPOLOGY:CheckAbsorbMarriage→TOPOLOGY::CheckAbsorbMarriageを実行しているところだ.この処理は「人名ノードpersonの右枝リストの内容をすべて本ノードの右枝リストに移転する」というもので,多重カード削減の一手段として実行されている.

この関数では系列優先実ノードの切り替えが常套的に起こっているようだが,好ましくない.多重カードの削減手段は複数あるので,あえて系列優先実ノードを対象とする必要はないと考えられる.⇒NAMEBOX:AbsorbMarriageの冒頭で対象ノードがIsPrimaryRealのときはゼロ復帰するようにした.⇒結果,多重カードも発生していないようなのでこれでよいのではないかと思う.

上記サンプルを開いてアプリ終了で,MARGBOX::MargPointOffsetのエラー(IsTooYoungWife())で(!upper || !person)が起きる.⇒フェーズがTOPOLOGICALSORT以下ではゼロ復帰するようにした.この後,NAMEBOX::RestoreExtractBoxで系列枠不在で停止した.この関数はINITIALSTATEではゼロ復帰するようになっているが,CHAOTICSTATE(INITIALIZED)でも同様とする.

ファイルを開き直したとき,CARDLINK::selectflagがオフになっているという障害がぶり返している.これは2020/12/09に確認された問題で,原因はファイルをクローズしたときselectcardlistがクリアされていないため,対策として仮想関数Cleanを導入して完全に解決しいていたはずだったのだが… しかし,現行版の実装は予定していたものより,かなり後退しているように見える.オプションをフィックスする手順で何か失敗していたのではないかと思われるが,ともかく修復を試みることにしよう.まず,現状を見てみることにしよう.

  1. NODULE::Clean 仮想関数で仮想関数cleanを呼び出している
  2. NODULE::clean フラグを2つクリアしているだけ
  3. COUPLING::CloseFamilyBase Clean関数の実行
  4. COUPLINGクラスの共通ヘッダ:COMMONHEADERSHORT
  5. KAKEIZUクラスの共通ヘッダ:COMMONHEADERSHORT
  6. NODULE::doClean 純粋仮想関数
  7. KAKEIZU::doClean cleanの呼び出し
  8. nodule::doClean 固有データ部を0クリア
  9. doCleanの呼び出し元:TRASHCAN::ReuseWaste

Clean仮想関数は「接続されたすべての下流オブジェクトをクリーンアップする」という目的で20201210に導入され,20201216にFIXしている.しかし,現状はまったくそのようなものにはなっていない.原因としては,「Disposeの設置を義務化@20201211」というオプションと交叉してしまったことが考えられる.このオプションは廃止されているが,そのあおりで必要なロジックが消滅してしまったのではないだろうか?⇒復元して動作するようになったが,おそらく以前のロジックは元々動作していなかったのではないかと思う.doCleanは固有データ部をまるごとゼロクリアする関数だが,その中で使っているdataptrとdatalenという2つの関数が仮想関数になっていなかった.

これでほぼ問題なく動作するようになったが,完璧であるとは言えない.doCleanは自クラスの固有データ部を完全にゼロクリアすることができるが,noduleクラスから派生したクラスからさらに派生したクラスの場合には,それぞれの固有データ部は縞模様で接続し,必ずしも連続なものにはなっていない.つまり,それぞれのクラスごとにdoCleanを実行する必要がある.ZTの骨格木を構成するクラスの中ではこのようなクラスには以下がある.

  1. CARDTABLE→ BASETABLE→ ARRAY
  2. GENELIST→ NLIST→ LIST→ DATALIST
  3. LIST→ DATALIST
  4. nlist→ LIST→ DATALIST
  5. TREEVIEW→ Bobject
  6. MARGTABLE→ BASETABLE→ ARRAY

doClean自体は仮想関数なので,nodule::doCleanを呼び出せばよいだけなのだが… ここまでやらなくても実行上はほとんど問題ないと思われるが,一応実装しておこう.上記にはテンプレートクラスなども含まれているが,このようなクラスでもdataptrとdatalenは正しく動作しているだろうか?⇒問題なさそうだ.

ZTシステム構成図7.ZELを開いて,「画面に合わせてズーム」を実行してTREEVIEW::GetAutoZoomRateで停止した.(!WindowSize.cx || !WindowSize.cy)が起きている.ただし,再現しない.⇒先に別ファイル(ZTシステム構成図7.BAD.ZEL)を開いた状態から,開く→ズームで再現するようだ.これは上記の修正に関係あるかもしれない.WindowSizeは「親ウィンドウサイズ,物理単位」なので,アプリ起動時に取得したものだろう.TREEVIEW::doCleanではTREEVIEW:Clearを呼んでおくことにしよう.

ZTシステム構成図7.ZELを開いて,次にZTシステム構成図7.BAD.ZELを開こうとして,Bobject::getdrawsizeでエラーになった.(coordinate() == ABSOLUTE)で停止している.PHASEはINITIALSTATEだ.TREEVIEW::Clearでgetdrawsizeしている.getdrawsizeはBobjectの関数で,相対座標系であることを前提としているので,TREEVIEW::Clearでリセットするのが筋だろう.⇒ダメだ.まだ同じエラー(!WindowSize.cx || !WindowSize.cy)が起きる.⇒doCleanが思ったような動作になっていない.この関数を仮想関数化するというのが間違いなのかもしれない.いや,むしろ,dataptrとdatalenを仮想関数化したのが間違いなのではないか?

固有データ部というのは,そのクラスに固有の位置とサイズを持っているのだから,仮想関数化するというのはまったく意味がない.逆に言えば,個別クラスごとに実装する必要がある.共通ヘッダ部でdataclearという関数を用意することはできるだろう.doCleanは共通ヘッダ部には関数宣言だけを置いて,個別クラスに実装を義務付けるというのがわかり易いのではないか?たとえば,こんな感じだ.

void dataclear(void){ memset(dataptr(), 0, datalen()); }

この定義は現行ではnodule::doCleanの定義とまったく同一だ.doCleanの実装例を示すと以下のようになる.

void MYCLASS::doClean(void){
  dataclear();
  BASECLASS::doClean();
}

既存の仮想関数にcleanというのがあるので,dataclearではなく,datacleanとしておこう.ほとんどの場合は,cleanはdatacleanを呼び出すだけで済むのではないかと思う.方式的にはこれで正しいとは思われるが,数あるnoduleの派生クラスでdoCleanが未定義になってしまう.仮想関数としてのdataPtr, dataLen, dataCleanがあってもよいのではないか?その上で,

nodule::doClean(void){ dataClean(); }

とすればよい.このようにすれば,noduleから直接派生したクラスでは何もしなくて済む.cleanという関数はdoCleanを呼び出すのが正しいのではないだろうか?もちろん,コンストラクタから呼び出される場合は段階的に処理されるので,そのクラスの固有処理をやればよいのだが… いや,これだけではまだ不十分だ.TREEIEW→Bobject→noduleの場合, Bobjectにも固有のdoCleanを実装しないと,dataCleanが実行されてしまう.BASETABLEも固有データ部にtablesizeという不変パラメータを持っている.また,longtableというクラスはかなり融通の利かないクラスでサイズは固定でそのサイズも持っていない…

売春処女プアプアが家庭的アイウエオを行う

完全参照リスト管理とUNDOシステムが共存する体勢を確立することは,参照リスト管理を恒常的なシステムの一部として仕様化するための第一歩だ.「UNDOで参照リスト管理と参照カウントの不一致が発生する」という問題が起きているが,いよいよ大詰めの段階に差し掛かった.これを解決できればZTは「超クリーンなシステム」に変容するための準備が整ったことになる.「超クリーンなシステム」とは「グラスクリーンな世界」であり,ZTが追求する誰にも踏まれていない純白な処女雪に覆われた「スノーホワイトの世界」である.

プログラミングの世界には「オリジナルバージョンに戻る」ことを意味するvirginize(処女化/童貞化)という用語が存在する(わたしも知らなかった).virginizeとは具体的にはバックアップに戻ること,ないし条件コンパイルマクロをオフにして修正前の状態に戻すことに相当するが,基本的には「修正が取り返しが付かない程度に間違った方向に進んでいるとき,それまでの経緯をすべて捨てて振り出しに戻ること」と解釈するのが妥当である.実際,プログラミングでは「これを修正してすべてのバグを取り除くより一から作り直した方が早い」という局面があることは広く認知されている(最初にそれを言い出したのはIBMだ).

いま出ている障害の一因としてTRASHCAN::ReuseWasteで廃棄オブジェクトをリサイクルするときの初期化が不十分というより,まったく実行されていないという問題が出てきた.これを解決するために,新規メモリブロックを使い始めるときと同様に固有データ部を完全にゼロクリアし,すべてのスロットを空にすることにした.これは言ってみればrevirginize(処女膜再生)の施術に相当する.プログラマというのは言ってみれば白雪姫に仕える七人のこびとであったのかもしれない…

ZTシステム構成図7.ZELの全体図を#1 couplingで開き,この基準カードを削除すると,UNDONODE #115359の持っているNODEREFLIST #115361がMARGLINK #311に乗り移るという事象が起きている.これは,カード削除コマンド実行後のUNDOBASE::CommandEndで起きている.何が起きているのか?実に興味深いところだ.

障害はUNDOBASE::SetUndoList→ UNDOBASE::SetUndoList→ ReferenceControlで起きている.UNDONODE #115245の枝2にUNDONODE #115359への参照を設定しようとしているところだ.#115359は参照リストを持っていないので,新たに生成された参照リストがNODEREFLIST #11536だ.このオブジェクトは再生品だ.⇒しかし,NODEREFLISTがリサイクルに回っているということ自体おかしい.NODEREFLISTはENDOFAPPLICATIONフェーズ以外では削除されないことになっているからだ.この時点ではMARGLINK #311はPROLONG状態でNring上に存在する.参照リストは持っていない.

いや,ちょっと間違えていた.SWO(SearchWrongObject)の条件設定でANDとすべきところがORになっていた.⇒障害はやはり,UNDOBASE::UndoCopyで起きている.ここではShadowから実ノードにオブジェクトイメージをコピーしているので,確かにそのようなことは起こり得る.実ノードが参照リストを持たないとしても,イメージにはそれが残っている可能性はある.というか,実際そのような動作になっている.しかし,上記のようにNODEREFLIST #11536はリサイクルされているので,ダブリが生じたということだろう.

問題はNODEREFLISTがゴミ箱に入っているという点だ.#11536はリサイクル時に付番されているので,それ以前の通番は上書きされてしまっている.⇒#2726だったようだ.⇒nodule::~noduleの末尾に「countゼロの参照リストを削除する@20201214」というのが入っている.countゼロの参照リストは保持していても意味がないので始末するという趣旨と思われるが,PROLONGされたノードの場合は除外されなくてはならない.⇒対処した.これですべてのエラーは解消した.つまり,完全参照リスト管理とUNDOシステムの共存は実現された.

ただし,UNDO/REDOを実行したとき,~noduleで参照リストカウントが残っているという現象がある.~noduleの段階ではすべての参照はクリアされていることになっていたはずだ… ⇒現状ではCLEARTABLEフェーズでは参照管理を放棄した状態になっている.CLEARTABLEを含むすべてのフェーズで完全な参照管理を実現するのはまだ先の話だ.

UNDOでは参照カウント不一致のエラーはまったく表示されないのに,REDOではかなりのエラーが出ている.UNDO/REDOには直接関係しない描画要素(NAMEBOX, MARBOXなど)なので放置でも実害はないが… カウント差はほとんど1なので押さえることも可能ではないか?

REDOではMARGLINK:#311とCARDLINK:#689が削除されている.これらからの参照が処理されていないのではないか?⇒どうもそういうことのようだ.⇒この問題を解決するには,やはり参照管理を徹底する以外ない.まず,~noduleで参照リストカウントゼロの場合に(UNDO保全オブジェクト以外では)参照リストを削除するとしていたのを廃止し,参照リストは原則として「死んでも付いて回る」ものとした.

ただし,ゴミ箱に入っている参照リストの親オブジェクトがリサイクルされる場合には削除される.~NODEREFLISTでは参照リストカウントが残っている場合には停止するようにした.この結果,CARDLINK #689[6]→ NAMEBOX #690の参照が残っていることが判明した.

しかし,CARDLINK #689[6]にはNAMEBOX:#243582が入っている.NAMEBOX #690はすでにゴミ箱に入っているが,CARDLINK #689は生きている.というか,UNDOで復活したものと思われる.これは何が悪いのか?CARDLINK #689が削除されたとき,NAMEBOX #690が参照解除されていなかったものと思われる.いや,かなりおかしい.CARDLINK[6]というのは参照ではなく,接続のはずだ.

image

!解けた!一日一個の禁令を破ってここぞとばかり,全量投下の勝負を賭けてみた.有り金を賭ける賭博師の心境だ.チョコレートパワーの威力だね.いや,最初の一個で解けたよ!エラーは全部きれいに消えた!

UNDONODE::UndoCopyでは参照カウントと参照リストカウントの差分を調整するため,復元されたオブジェクトの全参照スロットを対象にReferenceControlで参照リスト登録を実施している.問題はこのとき,「接続」と「参照」を切り分けなくてはならないという点だ.

通常はリンクされているオブジェクトを見れば判定できるのだが,いまの場合,NAMEBOX #690はすでに死亡しているため,接続モードを確認することができない.このような場合にはそのノードの存否をチェックするようにして解決した.このスロットはこの後,UNDOBASE:RestoreShadowでアクティブな現物ノードによって置き換えられている.漏れを防ぐために~noduleでNODEREFLISTの参照リストカウントがゼロになっていることを確認するようにしておこう.

上記と同様の操作(#1削除→UNDO→REDO)でNODEREFLISTのデストラクタで参照リストカウントの残留により停止した.親オブジェクトはすでに死亡しているNAMEBOX #322244でCARDLINK #689からの参照が残っている.#689[6]は空になっている.

CARDLINK[6]は接続スロットだから,参照リストに入っていることがそもそもの間違いだ.⇒どうもかなり難しい話になってきた.NAMEBOX #322244はリサイクルされた再生品という以外は特に問題のないオブジェクトだ.DELETEDはEXISTINGになっているので,弾くこともできない.いや,確かにおかしいところはある.このノードの親はNAMEBOXになっている.確かに,NAMEBOXはリスト構造で保持されているので,親がNAMEBOXというのは当たり前だが,CARDLINK[6]にリンクするためには接続しなくてはならないはずだ.

UndoCopyではそうなっていないのは仕方ないとしても,事後の調整というのがあるはずだ.本当は,その事後調整を実施したあとで,ReferenceControlの調整を行うべきなのではないだろうか?多分それしかないような気はするが,問題はUNDONODE::UndoCopyを呼び出しているUndoRestoreはUNDOSYSTEMの関数だという点だ.つまり,アプリ依存コードなので,UNDOBASEでできる範囲がより狭まってしまうということになる.⇒UNDONODE::SetNodeRefListという関数を作ってUndoRestoreから呼び出すようにしてみよう.

いや,もっといい場所がある.CountupReferenceという関数がある.ここでは参照カウントのインクリメントを実施している.それと同時にReferenceControlをやればよい.というか,ここではSansyoの取り直しを実施するというのが一番適切なのではないか?⇒完璧だ!傷跡も残らないくらいの完璧なサージェリィ,これ以上付け加えるものも引き去るものもないという感じになってきた.バックアップを取っておこう.

以下のブロックを選択して最初のカードを削除してエラーが発生した

image

@4 treeviewを削除で再現できる.PAIRBOX::CalcPairBoxでエラーが起きている.「NOCOMMONPAIRノード対は端点共有束で唯一でなくてはならない@20180915」というエラーだ.NOCOMMONPAIRは「終点がgoodsonのノード対は最大区間を除き端点共有不可@2018-09-04」とされている.このトラブルは既出だが,もう少し整理する必要がある.エラーを無視して描画は可能.出口検査はパスしているので,ここでは停止しないようにしておこう.

▲NOCOMMONPAIRノード対の論理を整理する.端点共有ノード対の場合は,NOCOMMONPAIRをつねにリスト先頭に配置するというのがわかり易いのではないか?

複数回のカード削除の後,UNDOで「フェーズのイレギュラーな遷移」が起きた.TOPOLOGICALSORTからINITIALIZEDに遷移しようとしている.⇒COUPLING::TopologicalSortの冒頭でSetPhase(TOPOLOGICALSORT)の後にTREEVIEW::SetDispParmを実行している.これはAUTOCHANNELのときの既定チャンネル数を設定するためだが,SetDispParmの中でフェーズの切り替えが発生している.これを避けるために,この操作はRESTOREDCIRCULATIONの前に移動する.

同様の動作テスト中,Bobject::setparentでエラーを表示しようとして例外が発生した.bprintfの引数に修正ミスがあった.

仮修正をフィックスしておこう.3件ある.

  1. UndoCopyでSansyo動作を実行@20201222 → 廃止
  2. オブジェクト削除で参照カウントゼロを確認@20201223 2箇所
  3. Bobject:initializeでCleanSlotする@20201223 → 廃止

仮修正17箇所,#ifdef ,#ifndefも一掃した.

UNDOで参照リスト管理と参照カウントの不一致が発生する

UNDOで参照リスト管理と参照カウントの不一致が発生する.かなり難しい.これが解けないと参照リスト管理を導入する意味も半減してしまう,正念場だ.参照リストのバックアップが必要なことは明らかだ.しかし,参照リストを単独でバックアップしても意味がない.リスト全体が保全されなくてはならない.だとすれば,むしろ参照リスト自体を「凍結」してしまうのが早いのではないか?

しかし,そのようなことが可能だろうか?⇒UNDONODE::UndoCopyでは,最初にそのオブジェクトの参照をすべて参照解除しているが,Shadowイメージをコピーした後で,記載された参照リンクを参照リストに登録するという動作を追加してみた.

これにより,参照リストと参照カウントの不一致は当初の34件から6件まで減少した.これら不一致のうち,4件はUndoProcess中に発生し,残り2件はその後に実施されるCOUPLING::TopologicalSortで発生している.後の2件はNAMEBOXとMARGBOXが関わるもので,これらはUNDO動作とは直接関わりがないのではないかと思われる.従って,UNDOに関わる不一致は最初の4件だけと言ってよいと思う.

障害が起きているのは,MARGLINK:#311,CARDLINK #689,UNDONODE #115324,UNDONODE #115359だが,UNDONODEの2件はかなり疑問がある.UNDONODE #115324の参照リストは72, 参照カウント2となっているが,UNDONODEが外部から72も参照されるということは考え難い.というか,UNDONODEは参照フリーだったのではなかったか?⇒UNDONODE自体はチェーンを構成するためのスロットを2つ持っているが,実ノードやShadowへのリンクはべた参照だ.もちろん,外部から72も参照されるということはあり得ない.

TRASHCAN::ReuseWasteはオブジェクトを再利用するとき,まったくクレンジングということをやっていないようだ.2020/12/10に実装したはずのClean関数の仕掛けが消えてしまっている.どこかで修正のフィックスに失敗しているのではないだろうか?再利用可能なオブジェクトがない場合にはfreeblock::getmemでメモリブロックを確保した後,ゼロクリアしているので問題ないが… doCleanは使えるので,まずこれで固有データ部をクリアし,CleanSlotでスロットを全クリアしておこう.

CleanSlotはそのオブジェクトのクラスの分しかクリアしていない.CleanSlotは主にデストラクタで使われるのでこの方式で問題ないが,スロットゼロや拡張スロットまで含めた完全なCleanSlotが必要だ.⇒~NODULEではそれと同等のことをやっている.⇒void NODULE:CleanSlot(int topslot)というのを作った.いや,この名前ではvoid CleanSlot(bool)とかち合ってしまう.⇒CleanAllSlotとしておこう.⇒完璧にrevirginizeできた!ただし,NODULE::doCleanは純粋仮想関数としているので,nodule::doCleanを明示的に呼ぶ必要がある.

この修正でUNDONODEの参照リストが変化するものと思っていたが,予定外のNAMEBOXとMARGBOXが消えて,UNDONODEの2件が残ってしまった.これはかなりおかしいので,何かしらのバグと思われる.UNDONODEが72も参照を持っているということはおよそ考えられない.何かの間違いだと思う.⇒UNDONODEと別のオブジェクトが同じ参照リストを持っている.⇒ここまでは想定通りだが,偽の参照リストを持っているのは,MARGLINK:#311とCARDLINK #689の方だった.なんでこんなことが起きるのだろう?考え難い.

どこかですり替えが起きているのだと思うが… MARGLINK:#311とCARDLINK #689はどちらも<再生>されている.つまり,一度削除されたオブジェクトを復活させたものだ.従って,<再生>の手順に誤りがあることになる.これをやっているのは,UNDOBASE:RestoreShadowだ.⇒いや,呼ばれているのはUNDOSYSTEM:RestoreShadowだ.RestoreShadowに入るときにはMARGLINKは参照リストを持っていない.まだUNDONODEの参照リストカウントも2しかない.どうもUNDOでデータを保全した時点で誤りが発生しているようだ.カード削除コマンド出口のUNDOSYSTEM::CommandEndだ.

Nringの残留オブジェクトが9件発生

BUG20-12-20 00-37-44.ZELの全体図を#229 式部卿宮の北の方で開いてアプリ終了して,Nringの残留オブジェクトが9件発生している.いずれもfreeblockだ.⇒freeblock::ReleaseFreeBlockがどこでも実行されていない.EraseFamilyTreeで実行していたはずなのだが… 条件コンパイルをフィックスする時点で消してしまったのだろうか?2020-12-15のバックアップを見ると,#ifdef 廃止@20201213 となっている.しかし,これは間違っている.

freeblock自体はnoduleオブジェクトの一種だから,ゴミ箱に入っていればゴミ箱の廃棄で削除されるが,アクティブなオブジェクトはCOULPINGとは別建てで管理されている.freeblockはリスト管理されていて,リスト先頭はグローバル変数のtopblockにある.この管理方式はあまりよくないと思う.ゴミ箱がCANというオブジェクトで管理されているように,freeblock管理の代表オブジェクトを定めて,CANと同格の位置に設置するようにした方がよい.しかし,その前にまず,Nringの残留の問題を片付けておこう.

freeblockは固有データ部にフリーメモリブロックを持つオブジェクトで,削除された場合は通常のオブジェクトと同様にゴミ箱に入るのでリサイクルは可能だが… Nringにどんなオブジェクトが残っているのか調べてみよう.以下のfreeblockが残留している.#879から連番で#887までの9個で,すべて個人記録ページだ.このアドレスはCARDLINK:cardbase.notepageに格納されている.CARDBASEに格納されている以下のリンクはすべてfreeblockで取得されたものと思われる.

CBitmap *cBitmap;            // MFCビットマップ
BITMAPINFO *bitmapinfo;        // カード画像DIビットマップ
char *notepage;                 // ノートページデータへのポインタ

これらのリンクはベタ参照であり,noduleオブジェクトとして管理されている訳ではないので,オブジェクトが削除されても自動的には削除されないから,どこかで明示的に削除する必要がある.画像イメージや記録ページデータなどはファイルに属するものだから,ファイルクローズで廃棄でよいのではないだろうか?

いや,少し違うのではないか?CARDBASEに載っているものは,CARDBASEのデストラクタで始末するべきだ.この意味では,@20201213 の決定の方が正しいような気がする.⇒現行では~CARDBASEでは何もしていない… ⇒ここでcBitmap,bitmapinfo,notepageを削除するようにしてみよう.

どうもこのCARDBASEのnotepage管理はあまり整っていないように思われる.notesizeがCARDBASEの下のCARDATAに入っている.しかも,ここにはもう一つ別のCBitmap*cbitmapというリンクまである.どうなっているのだろう?⇒CARDATAは外部とのインタフェース用,CARDBASEはデータ管理用という差があるのかもしれないが… メモリ管理ということは念頭になかったように思われる.

freeblock::delmem_((void**)&notepage, carddata.notesize);   

上記で一応メモリからの解放はできているようだが,「カウント不整合」がまた起きている.上の文が正しいかどうかも検証を要する.notepageというのはCARDLINK上のメンバー変数であり,メモリブロックを管理するfreeblockのアドレスが正しく引けているかどうかはチェックする必要がある.⇒関数の使い方を間違っている.bool freeblock:_delmem_(void **mptr)を使う必要がある.この関数を使ってカウント不整合も消えた.この関数ではサイズを指定する必要がないので,CARDBASEでサイズを押さえていないことも説明できる.

Nringの残留も解消した.CBitmap *cBitmapも同じ方法で削除しようとしたが,エラーになった.freeblock::ReleaseFreeBlockではカード写真イメージを解放するのに,DeleteBitmapImageという関数を使っている.CBitmapでは「ビットマップを破棄」するという操作が必要だ.

cBitmap->DeleteObject(); // ビットマップを破棄する
delete cBitmap;

しかし,delete cBitmapで削除できるということは,このオブジェクトはfreeblockを使っていないのだろうか?⇒確かに,new CBitmap; で生成されているようだ.CARDBASEが保持するオブジェクトに関してはこれでクリアできた.RTFを使っているところはカードの記録ページの他にもあったはずだ.タイトル履歴とか… ⇒調べてみよう.freeblockのMMTYPEには以下がある.

  1. MM_NODULE,        未使用
  2. MM_NOTEPAGE,    カード記録ページ CARDLINK::cardbase.notepage
  3. MM_CARDIMAGE,    カード写真イメージ CARDLINK::cardbase.bitmapinfo
  4. MM_CHUNKFILE,    記録ページ読み込み用バッファ FAMILYTREE::chunk.FILEBUFF
  5. MM_BUNSFILE,    データ読み込み用バッファ BUNSFILE::bunsbuff
  6. MM_QUICKDB,     カードテーブル項目名 QUICKDB::itemtable[maxitem].itemname
  7. MM_TEMPLATE,  廃止
  8. MM_TITLEINFO    タイトル履歴 TITLELINK::TitleInfo.Rtfbuf

タイトル履歴はTITLELINK::Disposeで削除しているので問題ない.CHUNKFILE chunkを持っているのはFAMILYTREEではなくCOUPLINGだ.chunk.FILEBUFFの解放は~CHUNKで実施すべきだろう.⇒いや,やっている.~BUNSFILEでもbunsbuffの解放は実施されている.~QUICKDBにも同様に措置されている.一応これですべて押さえたということになるだろう.MM_TEMPLATEは完全にソースからパージしてよいと思う.⇒いや,REVISIONの古いデータを読み込む場合に必要になるので残しておくしかない.

バックアップも取ったので,最近の修正をフィックスしておこう.以下の3件がある.

  1. Nodule:numberを廃止する@20201218 24箇所
  2. COUPLING:TemplateBuffを廃止する@20201219 13箇所
  3. 優先実ノードは本人・配偶者を問わない@20201221 2箇所

▲ZTシステム構成図7.ZELの全体図を#5 pagesetupで開いて,基準ノードを削除→UNDOで参照リストと参照カウントの不一致が発生する.⇒MARGLINK:#570のスロット20からの参照が参照リストに登録されていない.つまり,Sansyoを呼び出さない参照の書き換えが実行されているように思われる.

UNDONODE:UndoCopyでは冒頭ですべての参照をリセットしているが,その後の動作は単純な上書きコピーになっている.コピーではなくSansyoの実動作で書き込まなくてはならない.⇒一応実装してみたが… これはかなり難しそうだ.

昨日の仮修正を破棄してバックアップに戻る

昨日の仮修正は一旦破棄してバックアップに戻ろう.何が問題なのか?一番の問題は先帝系列で明石中宮が展開されていないという点だろう.明石中宮は※3系列で外部との接点を持つ唯一のノードなので,このノード以外に系列優先ノードとなり得るカードは存在しない.このノードがリジェクトされているのは,対向する有効な実ノードが存在しないためだ.対向する有効な実ノードとは,すなわち先帝系列で展開されるべき明石中宮の人名枠に他ならない.なぜそういう流れになっているのか,追跡してみよう.

明石中宮の義母である紫の上の父式部卿宮は結婚を3つ持っている.単身婚,式部卿宮の北の方,紫の上の母だ.この式部卿宮+紫の上の母の結婚が展開されていない.この結婚枠の所属系列が未定であるためだ.これはMARGBOX::IsPrimeboxOrNoに「基準ノードの配偶者が外部に有配偶者婚を持っている場合には,その結婚は基準ノードの配偶者の配偶者側で展開される@20190128」というルールがあるためだ.このサンプルの基準ノードは#229 式部卿宮の北の方で,式部卿宮はその配偶者に当たるため,弾かれている.この規則を暫定的に止めて描画できた!

image

確かにかなり難しい図面であるかもしれない… しかし,このような反例があるために,このルールを破棄ないし緩和するというのはよい方向であるとは思えない.IsPrimeboxOrNoという関数はある結婚を夫と妻のどちらのポジションで展開するか?という疑問に応えるための関数で,基本にはどちらを選択しても描画は可能となっているという前提がある.この前提が崩れたのは,「基準ノードの配偶者が外部に有配偶者婚を持っている場合には,その結婚は基準ノードの配偶者の配偶者側で展開される」というルールが厳し過ぎるためというよりは,むしろ「もっと優先度の高いルールが存在する」ということだろう.

つまり,その結婚がある位置で展開されないと描画不能となるようなものが存在するということではないのか?しかし,それをルール化するのは容易くはない.系列枠リストの最初の7系列を表示してみた.

  1. #915 先祖=#701 式部卿宮の北の方(0) 優先=#701 式部卿宮の北の方(0) 始系列 type=始系列
  2. #917 先祖=#509 先帝(0) 優先=#1093 式部卿宮(1)→#701 式部卿宮の北の方(0) →主系列#915:式部卿宮の北の方 type=BTW左接続関係
  3. #919 先祖=#557 ※6(0) 優先=#1101 先帝の后宮(1)→#511 先帝の后宮(0) →主系列#917:先帝 type=婚姻関係
  4. #921 先祖=#405 ※3(0) 優先=#1102 明石中宮(1)→#339 明石中宮(0) →主系列#917:先帝 type=親元関係
  5. #923 先祖=#603 中務宮(0) 優先=#1104 明石の尼君(1)→#521 明石の尼君(0) →主系列#921:※3 type=婚姻関係
  6. #925 先祖=#637 右大臣(明石)(0) 優先=#1110 今上(1)→#339 明石中宮(0) →主系列#917:先帝 type=BTW左接続関係
  7. #927 先祖=#589 按察の大納言(若紫)(0) 優先=#1112 紫の上の母(1)→#535 紫の上の母(0) →主系列#917:先帝 type=婚姻関係

1番目の式部卿宮の北の方系列は始系列だから,無条件で描画される.2番目の先帝系列の優先仮ノードは式部卿宮であり,実ノードは基準ノードの配偶者だから,この系列自体には何の問題もない.もちろん,式部卿宮+紫の上の母の結婚を他系列に譲ったとしても,問題なく描画できる.問題は※3系列の優先ノード候補が明石中宮しかないという点にある.ここで躓くのは,式部卿宮+紫の上の母の結婚の移転先が,※3系列より系列順位的に後方にあるという点にある.このようなことは予想されていなかったので,この場で対処することは不可能だ.

解決策としては,上記で実行したように何らかの条件を提示してこの結婚が紫の上の母側ではなく,式部卿宮側で展開する必要があるということを示す必要がある.これを決定するためには少なくとも次のことが言えなくてはならない.

  1. 系列優先ノード候補を一つしか持たない系列Kが存在する
  2. この優先ノードの実ノードを(子どもとして)含む結婚はこの系列Kに先行して展開されなくてはならない
  3. この結婚を系列Kより後方の系列に移籍してはならない

判定1.もあまり簡単ではないが,2., 3. も分かり易いものではない.通常始系列に含まれる結婚は最初に展開され,基準ノードの配偶者の系列はそれに準ずると考えられるから,通常の手順であれば,この結婚が「後方」に移動することは避けられない.仮にこの手順に入るまでに,すでに系列枠リストは生成され,系列順位が決定しているとしよう.また,系列優先ノードも選定済みであるとする.実際,現行論理ではそうなっていると言ってよいと思われる.とすれば,上の条件文は以下のように書き換えることが可能かもしれない.

「IsPrimeboxOrNoの検査対象の結婚に系列優先ノード(候補)が(子どもとして)含まれる場合は本人位置で展開する」

例外としては,その配偶者の系列が「系列優先ノードの系列」より先行する系列である場合は移動を認めるとしてもよい.以上をまとめると,

「IsPrimeboxOrNoの検査対象の結婚に系列優先ノード候補が子どもとして含まれる場合は本人位置で展開する ただし,配偶者系列が系列優先ノード候補系列より上位にあるときはその限りではない」

となる.しかし,これは絵に描いた餅で終わる可能性もある.まだ,この時点では結婚枠はおろか,人名枠でさえどの系列に属するかは確定していないと考えられるからだ.系列枠はすでに生成済みであるとすれば,系列枠のPrimaryには系列優先ノードが入っているはずだから,系列枠リストを走査すれば確認することは可能だが,ややコスト高になる.CARDLINK自体にその情報(優先ノード候補であるという情報)が入っていれば探索コストはかなり削減できるのだが…

CARDLINKには先祖ノードへのリンクを格納するancestryというスロットがあるが,これは必ずしも所属系列とは一致しない.CARDLINKはNAMEBOXのリストを持っているので,このリストをチェックすることはできる.しかし,たとえば,式部卿宮+紫の上の母の結婚を検査するタイミングでは明石中宮のNAMEBOXはまだ一つも展開されていないのではないだろうか?実装してみよう.

いや,問題はもう一段難しい.IsPrimeboxOrNoの対象結婚枠は式部卿宮+紫の上の母だが,明石中宮はこの結婚枠には入っていない.

式部卿宮+紫の上の母→紫の上→明石中宮

という関係だ.そこまでここで追求するのは無理なのではないか?というより,もし,それをやるとすれば系列優先ノードから先祖ノードまでの経路上に存在するすべての結婚が検査対象となってしまう.実際,そこまでやらないと解決にはならないのではないかと思う.かなり厄介な話に発展してしまった.つまり,あるノードを系列優先ノードとして予約するためにはそのノードから先祖ノードまでのすべての結婚枠を予約する必要があるということになる.⇒この方向はかなり険しいが,もしそれしか道がないのだとしたらそうするしかない.

もう一つ別のより簡便な方式も考えられる.現行の論理で一度GoDownStreamを通した後,MakeUpTreeで再挑戦の機会を与えるという方式だ.これなら,確定できる系列はすべて連結完了しているはずだから,系列順位とは無関係に接続点を探すことができる.

現行方式でまさにそれをやっているのではないだろうか?TRIBEBOX:GetAlternativePrimeNodeという関数はそれを行うために用意されたのではないのか?であるとすれば,この関数をより洗練させればよいというだけの話になる.それは実現可能な話ではないかと思う.大分回り道してしまったが,ようやくスタート地点まで戻ってきた.

一応GetAlternativePrimeNodeで(不十分ながら)接続先を見つけることはできるようになったが,TRIBEBOX::DecidePrimaryNodeが通らない.この関数は自分自身を再帰的に呼び出しているので無限ループに陥ってしまう.結局まだ問題は解決していないということのようだ.GetRealnodeでは「空でない可視の人名枠オブジェクト」を求めているため,按察の大納言系列の#1112 明石中宮(1)が弾かれてしまっている.しかし,所属系列枠が決まっているのに不可視というのはどういうことだろう?⇒GoDownStreamで所属系列を設定するタイミングで可視化してみたが,状況は変わらない.

どこかで落とされているようだ.CARDLINK::NameBoxを実行すると初期化される.しかし,この動作はかなりおかしい.対象となっているのは明石中宮ではなく,北山の尼君だ.

CARDLINK::NameBox→ NAMEBOX::makeProxy→ initializeで生成された北山の尼君の仮ノードを隠蔽リストにつなぎ込んでいる.この措置は「挿入」として実施されるため,挿入位置のノードが操作されて対象ノードの後ろに移動し,このとき不可視設定されている.隠蔽リストは原則として不可視となっているため,このような動作になっているのだろう.GoDownStreamでは人名枠などの描画要素は生成されてはいるが,まだ可視のYリストには登録されず,MAKEUPTREEで初めて繋ぎ込まれるという動作になっている.このため,MAKEUPTREEで実施しているGetAlternativePrimeNodeは再チャレンジになっていない.

見てきたように,従来手順では優先ノードが決定できない系列が発生することは避けられない.これを予防するための措置を導入することは不可能ではないが,高度に難解なものになることは避けられず,時間・空間効率に及ぼす影響も無視できない.この意味では系列木の生成を段階化して,後段で最終解決するというのは現実的であると思われる.中間段階に当たるGODOWNSTREAMでは描画要素はすべて隠蔽リスト上にあり,かつ隠蔽リスト上にあるノードはつねに不可視とするというルールになっており,これを直ちに変更するというのも現実的ではない.

TRIBEBOX::GetRealnodeはMAKEUPTREEに入ってから適用される関数だが,MAKEUPTREEが完了するまでは描画要素がすべてYリストに繋がって可視化した状態にならないため,この関数で認める系列優先実ノードの条件を緩和するしかない.実際問題として,人名リンクが有効であるような人名枠は基本的に合法と考えられるから,ここでは可視/不可視を問わないことにする.これで描画まで進むことができた.ただし,一つだけ問題がある.

TRIBEBOX::GetAlternativePrimeNodeの中段,(T2 == major)のとき(marglink)で停止する.TRIBEBOX::GetAlternativePrimeNodeで系列枠#927 先祖:#588 按察の大納言 (若紫)の優先ノードを探すメインループを抜けたところで誤動作している.婚姻関係では見つけられず,逆婚姻関係を探して見つけているが,相手方が空であるために間違った分岐に進んでいる.相手方を見つける論理は以下のように単純なものだが,単身婚の場合があるということが見落とされていた.

if (wife == marg2->OTTO) partner = marg2->TUMA;
else partner = marg2->OTTO;

逆婚姻関係の場合には従系列で配偶者となっているのだから,主系列では本人となる可能性が高いと考えられるが,実際にどちらに割り当てられるかは必ずしも分明ではない.ある結婚の夫と妻のどちらを本人としどちらを配偶者とするかということは,その結婚枠をどの系列ないし,どの「家」に配置するかを決定することに等しい.その最終決定を行っているのがMAKEUPTREEフェーズなので,このプロセスが完了するまでは確定的なことは言えない.しかし,系列優先ノードの決定はそれらが確定する以前になされなくてはならないという矛盾がある.

系列優先仮ノード→実ノードという関係はある意味でかなりルーズな関係であり,必要なことは,①2つの系列関係が接点を持つこと,②同世代の2つの人名ノードの配置によって系列枠の垂直位置関係を決定すること,の2つが満たされれば十分であると考えられる.従って,その関係を決定するためにはそのノードが本人ノードであるか配偶者であるかまでは問う必要がないと(基本的には)考えられる.この立場からすれば,婚姻関係の実ノードはつねに(従系列における)本人ノード,逆婚姻関係の場合は(従系列における)配偶者ノードとしてよいのではないか?このルールなら単身婚の場合も問題なく処理できる(はずだ).

image

一応これでできたようだ.最初の図と今回の図では微妙に細部が異なる.最初の図では紫の上の母が夫の式部卿宮との接触距離まで接近しているのに対し,後の図ではかなり開いたものになっている.これは「基準ノードの配偶者が外部に有配偶者婚を持っている場合には,その結婚は基準ノードの配偶者の配偶者側で展開される」というルールを文字通り実現したもので,基準ノードの式部卿宮の北の方の眼からすれば,この方がずっと好ましいことは間違いないだろう.明石中宮も2番目の図ではかなり遠いところに配置されているが,式部卿宮の北の方の距離感からすれば,こんな感じなのではないだろうか?

このサンプルではTRIBEBOX::GetAlternativePrimeNodeの出動が必要となるケースは1件しか起きていない.それも,※3系列の明石中宮ではなく,按察の大納言(若紫)系列の優先ノードが紫の上の母から式部卿宮に切り替わるという動きだ.これはかなり予想外の結末だが,この障害の真因が,MARGBOX::IsPrimeboxOrNoで式部卿宮+紫の上の母の結婚を紫の上の母系列で展開するように決定したことにあることからすれば,当然の成り行きとも言える.この修正は,結婚枠展開の自由度を残したという点では「※3系列の明石中宮」にこだわるというアプローチより優れていると言ってよいと思う.

※3系列はどうなったかと言えば,明石中宮を優先ノードとして,按察の大納言系列の従系列になっている.しかし,この方式が「完璧」であると言えるかと言えば,多少の不安は残る.系列の従属関係が当初の線形順序を崩しているため,場合によっては,本来連結でなければならない系統図が複数のブロックに分離してしまう可能性はゼロではない.系列優先ノードの選択というのは,かなり難易度の高い部分だったような気もするが,ようやくある水準に達したのではないかという気もする.

TRIBEBOX::decidePrimaryNodeで系列優先ノード決定不能が起きる

▲BUG20-12-20 00-37-44.ZELを開いて,TRIBEBOX:decidePrimaryNodeで系列優先ノード決定不能が起きる.障害が起きているのは[系列枠]: #921 先祖=#405 ※3で優先ノード候補は#338 @4明石中宮だが,TRIBEBOX::GetRealnodeで有効な実ノードが選択できないため,リジェクトされている.

かなり難しい問題だ.ある意味で現行方式が破綻していると言っても過言ではない.ZTは当初実親子関係しか扱っていなかったが,養親子関係が導入された後でも基本的な構成規則である「すべての人名はいずれかの系列に所属するという」という原則が踏襲されてきた.養親子関係をサポートするためにはこの原則を緩和するしかないのだが,原則それ自体の見直しが行われることがなかったため,不良が潜伏していつか露見するという宿命を負っていたのではないだろうか?

現行ではたとえば,CARDLINK::ancetryには所属系列の先祖ノードへの参照が格納されるようになっている.これは,つまり,ある系列に所属する人名は他の系列に所属することはできないことを暗に示している.実際には,養親子関係を含む系図を扱っている以上,運用的にそれをカバーしてきたものと思われるが,原理的に整合していないのでどこかで破綻することは避けられない.どこをどう改訂ないし改善すればよいのか?TOPOLOGY::topologicalSortをフェーズで区分すると,

  1. TOPOLOGICALSORT 系統並び替え開始
  2. CLEARTABL テーブル初期化
  3. EXTRACTPARTIAL 部分図ノードの抽出
  4. SETBASECARD 基準ノード設定
  5. KINSHIPDEGREE 予備検定(親等計算)
  6. DECOMPOSITION 本検定(系列分解)
  7. PHYLETICTREE 系列枠の生成と世代計算
  8. GODOWNSTREAM 系図木生成と展開:グループ゚を設定
  9. MAKEUPTREE 系図木構築:Yリストへの繋ぎ込み
  10. SETSIBLINGS 拡張子ども枠の設定
  11. SETRELATIVE 系図木相対領域計算中
  12. BUILDGENELIST 基本/系列世代枠リストの生成
  13. SOLVECOLLISION 系列内同世代結婚枠の衝突解消処理
  14. BUILDCENTERLINE 血統軸線図の構築
  15. TRIBERELOCATION 系列枠再配置
  16. SAMEGENEMARRIAGE 重婚同類グラフを生成
  17. AFTERMARGSAMEGENE 重婚同類グラフの後処理
  18. SYMMETRICGEOMETRY シンメトリ婚を展開する
  19. MAINEXPERIMENT 系列間衝突回避処理
  20. HORIZONTALORDER 先祖並び自動オフ時の系列水平配置
  21. FOLDINGCHANNELS チャンネル数自動のときのチャンネル整理
  22. HEAPTRIBEBOX 系列枠包括領域集積
  23. MAKEABSOLUTE 絶対座標系変換
  24. SETTITLEBOX 系図外枠矩形領域を最終決定
  25. DRAWSTAGE 系図木描画準備完了

のようになる.ただし,最後のDRAWSTAGEはCOUPLING:TopologicalSortで設定される.いま問題になっているところは(6)DECOMPOSITION から(9)MAKEUPTREEまでの処理と考えられるので,そこだけを抽出すると,

  1. DECOMPOSITION 本検定(系列分解)
  2. PHYLETICTREE 系列枠の生成と世代計算
  3. GODOWNSTREAM 系図木生成と展開:グループ゚を設定
  4. MAKEUPTREE 系図木構築:Yリストへの繋ぎ込み

のようになる.(1)DECOMPOSITIONではすべてのノード(人名リンク)に先祖ノードを割り当てて,排他的な系列に分解する.(2)では系列枠オブジェクトを生成して,系列枠リストを構成する.(3)GODOWNSTREAMでは各系列の先祖ノードからの下流検定を実施して,人名枠オブジェクト(仮ノード)を生成し,系列の最小・最大世代番号を決定する,(4)MAKEUPTREEでは系図木(描画リスト)を生成し,すべての描画要素の初期化を行う.エラーが出ているのは(4)のMAKEUPTREEだ.

フェーズの整理を行っていて,エラーが出た.上のリストで18. SYMMETRICGEOMETRY シンメトリ婚を展開するを実行している場所は実際には,21. FOLDINGCHANNELS チャンネル数自動のときのチャンネル整理の直前に移動しているので,SYMMETRICGEOMETRYの値を変更したところ,フローはまったく変化していないも関わらず,エラーが出るようになった.COUPLING::TopologicalSortの出口検査で(SymmetryList && SymmetryList->len() != SYM) で停止している.

SYMMETRICGEOMETRYの値が変化したことより,TOPOLOGY:BuildSymmetryListの直前でPHASEにSYMMETRICGEOMETRYをセットしていることが障害の原因となっている.BuildSymmetryListの中でフェーズによって処理が分岐するようになっているのではないか?⇒いや,BuildSymmetryListの後でCheckMultiCardsを実行してもエラーにはならない.BuildSymmetryListでフェーズをセットしなければエラーは回避できるが,なぜこのようなエラーが出るのかについては追求する必要がある.⇒原因は大体つかめた.

3つパターンがある.①オリジナルパターン→エラーなし,②BuildSymmetryList値を変更のみ→エラーなし③SYMMETRICGEOMETRY値を変更,BuildSymmetryList前でフェーズ設定→エラー発生.エラーはTOPOLOGY::SymmetryListのカウントと系列枠にセットされたカウントの合計が一致しないというものだが,実際にはTOPOLOGY::SymmetryListのカウントゼロで系列枠のシンメトリ婚カウント1という状況だ.

①のオリジナルコードの場合には,BuildSymmetryListの実行時のフェーズがSYMMETRICGEOMETRYと異なるため,MARGBOX:getSymmetricLeftBoxで厳格な条件が要求されるためシンメトリ婚不成立となっている.また,②の場合は,BuildSymmetryList実行時のフェーズ番号がSYMMETRICGEOMETRYよりも小さくて無動作で抜けているのでやはりシンメトリ婚不成立となり,不一致は生じない.③の場合はBuildSymmetryListの実行時フェーズがSYMMETRICGEOMETRYと一致するため条件が緩和されて,シンメトリ婚が成立,おそらく後に破棄されたためカウント不一致になるのだろう.

構成規則が時と場合によって厳しくなったり緩和されたりするという仕様はかなり問題があるので統一した方がよいと考えるが,カウント不一致というのはまた別の問題なので,まず先にこちらをクリアしておこう.⇒対処した.⇒今度は系列枠のSymmetryCountの方が少なくなってしまった.Ancesry.zelを開いて不一致が発生する.SymmetryListというのはMARGBOXを要素とするリストだ.シンメトリ婚に設定されると,その結婚枠はSYMMETRICMARGという属性を持つようになる.

SYMMETRICMARGがセットされるのはBuildSymmetryListの中だけで,このときは系列枠のSymmetryCountも同時にインクリメントされている.また,この値がリセットされるのはMARGBOX:AbandonSymmetricの中だけで,今回の修正はこの関数に関わるものだ.SymmetryCountのデクリメントはresetghostbits(SYMMETRICMARG)で実行される.resetghostbitsを呼ばずに直接リストからパージして,SymmetryCountをデクリメントしている箇所もあったが,すべてresetghostbitsで操作するように修正した.

ただし,resetghostbitsの中の論理にもミスがあった.SymmetryList->Removeの戻り値を見てカウントダウンしていた.LIST::Removeは現在リスト長を返すような仕様になっている.Ancestry.zelでシンメトリが崩れているという現象は大分前から出ているが,落ち着いてから調べることにする.最初の問題に戻ろう.

▲MARGBOX:getSymmetricLeftBoxでSYMMETRICGEOMETRYのときに限ってシンメトリ婚成立の条件を緩和している

▲Ancestry.zelでシンメトリが崩れている

DECOMPOSITIONではまだ描画オブジェクトは生成されていないので,人名リンクベースで人名をグループ分けしたものを系列と称しているに過ぎない.PHYLETICTREEでは先祖ノードを頂点とする系列枠オブジェクトが具体的に生成される.次のGODOWNSTREAMでは先祖ノードの下流系を展開してメンバーとその配偶者の人名枠を生成し,その所属を決定する.最後のMAKEUPTREEでは系列優先ノードを(最終)決定し,系列の相互位置関係を確定する.

見た限りではどうも三番目のGODOWNSTREAMに問題があるように思われる.ここでは,所属系列の移動ということがかなり頻繁に起こっているため,どこかに盲点が発生しそうな感触がある.

NAMEBOX #339 明石中宮(0)はPHYLETICTREEフェーズで[系列枠]: #917 先祖=#509 先帝(0)に振り分けられているが,GODOWNSTREAMに入って,※3系列に移動している.先帝系列は系列枠リスト上では※3系列より先にあるのに,明石中宮の人名リンクが先帝系列で展開されないという理由が分からない.このサンプルには系列が89個含まれているが,最初の7つまで表示すると,

  1. #915 先祖=#701 式部卿宮の北の方(0) 優先=式部卿宮の北の方 始系列 type=始系列
  2. #917 先祖=#509 先帝(0) 優先=式部卿宮  type=婚姻関係
  3. #919 先祖=#557 ※6(0) 優先=先帝の后宮  type=婚姻関係
  4. #921 先祖=#405 ※3(0) 優先=明石中宮  type=親元関係
  5. #923 先祖=#603 中務宮(0) 優先=明石の尼君  type=婚姻関係
  6. #925 先祖=#637 右大臣(明石)(0) 優先=髭黒  type=婚姻関係
  7. #927 先祖=#589 按察の大納言(若紫)(0) 優先=紫の上の母  type=婚姻関係

のようになっている.※3系列は順位からいくと4番目だ.