blog-RuinDig

短かったり長かったりする。Articles are my own.

開発日誌:ポータルヒストリー機能 (日本語訳)

2021年7月、IngressフォーラムでIngress Primeのポータルヒストリー機能の開発日誌が公開された。

community.ingress.com

ポータルヒストリー機能

訪問済み、キャプチャー済み、スカウトコントロール済み*1のポータルをレイヤーで表示するポータルヒストリー機能は2021年2月にIntelMapに実装されて、その後はIngress Prime ver.2.65.1からアプリにも実装された。

ingress.lycaeum.net

ingress.lycaeum.net

日本語訳

皆さん、2月に追加されたポータルヒストリー機能の見方について、共有できる事を嬉しく思います。

シニアソフトウェアエンジニアのofer2による開発日誌です。

========================

皆さん、こんにちは。私はポータルヒストリー機能を主導したOferです。今回の開発日誌では、この機能の開発プロセスについて皆さんに少しご紹介したいと思います。

競い合う、探索する、ソーシャルの3つがIngressの大きな側面です。バトルビーコンを担当していた頃は、競争的な側面に集中していました。しかし、新型コロナウイルス感染症の流行*2で家に閉じこもっていたため、これは後回しになっていました。長い間、家に閉じこもっていたので、私はゲームの探索の側面に重点を置きたいと考えていました。私は探索が好きなのですが、ここでの一番の問題は、目標が必要であり、その目標を達成するための進捗を確認する必要があるという事でした。ですから、皆さんと同じように、自分が行った事のあるポータルを見る事ができて、全てのポータルを制覇するまで新しいポータルに行く事ができるようにしたかったのです!

昨年、開発チーム全体のハッカソンでアイデアを募集した時に、この事を掘り下げる機会がありました。私達のアイデアは、サーバーにコードを追加して、キャプチャした全てのポータルを追跡し、アプリのマップ画面に表示するというものでした。このアイデアの検証では、以前にキャプチャしたポータルは含まれません。なぜなら、それは全く別の問題で、ハッカソンの範囲内では絶対にできないからです。

この限られた機能の最大の課題は、空間データとエージェント固有のデータを混在させる必要があった事です。通常、地図上のエンティティ*3に対するクエリを実行すると、全てのデータが誰にとっても同じものになります。そのため、クエリを実行するコードは、データを要求しているのがどのエージェントなのかを知る事ができません。次に、全てのデータに目を通す事なく、あるエリアにいるエージェントの特定のデータを求める方法が必要でした。

基本的なプランは、プレイヤーIDとS2セルIDをキーにしたエンティティ(データの保存容器)を作る事でした。S2セルとは、世界をチャンク(塊)に分割してラベル付けする方法です。興味のある方はこちらをご覧ください。S2セルを使ってプレイヤーのデータを検索し、それをポータルの情報に"ミックス"して出力します。

この計画を念頭に置いて、私はこの機能に興味を持っていたサーバーチームのもう1人のエンジニアであるアンドリューに参加してもらって、作業に取り掛かりました。ハッカソンの最後には、全員が自分のプロジェクトを発表し、私は自分のチームのものを発表する機会に恵まれました。

f:id:edgeknight:20210914094439j:plain
ユニークポータルキャプチャーのみを表示するIngress Primeの画面 by ofer2

ポータルの根元に赤い点をつけたり、上の画像ではユニークポータルキャプチャーのみを表示しましたが、プログラマーのアートはさておき、本当に素晴らしい機能を作ったと思いますし、製品化に向けて本格的なバージョンを作っていきたいと思いました。

ハッカソン終了後、私達は通常の仕事に戻りましたが、開発チームはその結果を見て、エージェントが何年も前から求めていた機能を実現する機会を得ました。ブライアン*4は、ゲーム開始時からのエージェントの履歴に加えて、ポータルの履歴も一緒に表示させ、更にはIntelMapにも表示させたいと考えていました。

ブライアンはエンジニアチームに、この機能を実現するように命じました。幸いにも、ハッカソンでのプロジェクトで強固な基盤が出来ていました。エージェント固有のデータをポータルごとに返すのは、ポータルに前回アクセスした時からデータが変わっていないため、不要な場合が多いのです。これを解決するために、プレイヤーのデータをポータルのデータとは全く別のデータとして返し、それをクライアント上でまとめています。残念ながら、IntelMapはクライアントのようにリアルタイムに更新されない(全てのデータを更新する)ため、やはりプレイヤーデータをポータルに添付する必要がありました。

2つ目の大きなポイントは、Ingressのゲームプレイの全履歴をデータにあらかじめ入力しておく事でした。最初にこれを調べた時、エージェントが実行したアクションを記録するためのエンティティのフィールドがあると分かりました。このフィールドはハッシュ化されているため、記録に対応する実際のエンティティを見つけるには、ハッシュを逆にする必要があります。そのために、ポータルIDを全て調べてハッシュ化し、そのハッシュをエージェントのものと比較できるようにするプロセスを作りました。次に、各エージェントの記録を調べて、そのエージェントが訪問したポータル(Visited)とキャプチャーしたポータル(Captured)を全て見つけて、そのデータをクライアントが照会する構造体に書き込みます。これを書き終えた後、開発環境で実行したところ、データストア*5に結果が出力されました。

出力された結果を確認したところ、予想していたポータルの情報とは一致しませんでした。それは、追跡記録を書き出すコードを調べた時に、間違いがあった事が判明しました。この関数は、IDと呼ばれる文字列を受け取り、それをハッシュ化して、そのフィールドに書き込んでいます。私達が見落としていたのは、ポータルを訪れてキャプチャした場合には、この関数が呼び出されていなかった事です。代わりに別の関数を最初に呼び出し、エージェントのIDとポータルのIDを組み合わせました。つまり、以前作った単一のハッシュのリストでは機能しないという事です。エージェントごとに最初からリストを作り直さなければなりませんでした。

1回の処理にかかるコストは、妥当とはいえ高いです。しかし、それを全てのエージェントに実施すると、エンド・ツー・エンドのプロセスは実現できません。開発チームは軌道を変えて、コスト削減のための作業に取り掛かりました。その結果、パイプラインの最適化を行い、コストを大幅に削減する事に成功しました。最適化の1つとして、フローを複数のマシーンに分散させるのではなく、プレイヤーごとに1台のマシーンで実行するようにしました。そのためには、結果を出すために各マシーンが必要とするデータのサイズを小さくするなどの工夫が必要です。そして、最適化して埋め戻したデータをエージェントレベル3以上で実行するように指定した事で、コストを妥当な範囲に収められました。このエンジニアチームのコスト削減により、この機能を有料機能にする事なく、レベル3以上の全てのエージェントに提供できました。

この時点で、クライアントとサーバーは動作していましたが、これらのデータを全て表示する明確な方法が必要でした。そこで私達は、"レイヤー"という巧みなコンセプトを思いつきました。これにより、エージェントが必要としている情報だけを表示する事ができます。アートチームはこれを受けて最初のコンセプトを作成し、皆でそれを試してみました。最初の試作段階のデザインは読みにくかったので、様々なデザインを試してみました。アートチームは、開発チームやヴァンガード*6からの意見をすぐに取り入れて、各ポータルのポータルヒストリーのリングのサイズを大きくし、ポータルに空白部分を加える事で、ズームレベルが変わってもリングが見やすくなりました。

この機能については、ハッカソンから実装前の開発、そして実装後の開発に至るまで、何度も繰り返してきました。エージェントが望んでいるであろう追加機能(IntelMapの反転など)もありましたが、それが無くてもエージェントがこの機能をとても楽しんでくれる事が分かっていたので、核となる機能を実装して、その後も繰り返し取り組む事にしました。このような反復的な設計過程によって、エージェントやチームメイトからのフィードバックを引き出すことができて、Ingressのロードマップの方向性を導くのに役立っています。

エージェントのポータルヒストリー機能は、エージェントからの要望が多かったため、常にロードマップに入れたいと考えていましたが、クリエイティブな方法でエージェントに提供できました。また、レイヤーボタンにドローンを追加し、エージェントが自分の周りで起こっている活動をより多く確認できるようにしました。今後も更なる機能強化を目指していきたいと思います。

訳注

S2セルについての日本語での解説は以下を参照。

Qiita『S2 Geometry Libraryを利用し、ある地点の緯度経度よりその地点を含むS2セルを求める方法』
https://qiita.com/yoshii0110/items/7f96ed41488988ae5554

Google Maps Platform『Playable Locations API の基本概念』
https://developers.google.com/maps/documentation/gaming/concepts_playable_locations

ポケモンGO攻略 - みんポケ『【ポケモンGO】S2セルの使い道と表示方法まとめ』https://9db.jp/pokemongo/data/5227

英語原文

Hi everyone - I'm excited to share insight into the Player Portal History that was added back in February.

This entry of the Dev Diary was provided by @ofer2 , Senior Software Engineer.

===========================

Hi everyone, my name is Ofer and I was the Feature Lead for Portal History. With this Dev Diary entry, I wanted to give you all a bit of insight into the development process for this feature.

Competition, exploration, and social are three major aspects to Ingress. When I worked on Battle Beacons, I got into the competitive aspect. But with COVID and staying at home, this was put on the backburner. After being stuck indoors for so long, I was really looking to lean into the exploration aspect of the game. The main problem here, for me, was that I love exploration, but I need a goal and I need to see progress towards completing that goal. Therefore, like many of you, I wanted to be able to see the portals I had been to and go to new ones until I CAPTURED THEM ALL!

The opportunity to dig into this arose last year when there was a call for ideas for a team-wide hackathon. Our pitch was to add code to the server to track all the Portals you’ve captured and show it to you on the app’s Map Screen. In this proof of concept, this wouldn’t include previously-captured Portals because that was a whole other can of worms, which would definitely not be doable within the scope of a hackathon.

The main challenge with this limited feature was that we needed to mix spatial data with Agent-specific data. Normally, when you make a query for entities on the map, all of that data is the same for everyone. Therefore, the code responsible for doing that query has no idea which Agent is the one that is asking for the data. Secondly, we needed a way to ask for that Agent’s specific data in an area without having to look through all the data.

The basic plan was to create an entity (a storage container for data) that was keyed by player ID and S2 cell ID. An S2 cell is a way of dividing the world into chunks and labeling it. You can read more about it here if you are interested: https://s2geometry.io/devguide/s2cell_hierarchy.html. Then, when you query for entities on the map, we would route that through a new interface which also looks up the player data using the S2 cell and “mixes” it in to the Portal information on the way out.

With this plan in mind, I recruited Andrew, another engineer on the server team, who was interested in the feature and set off to work. At the end of the hackathon, we all presented our projects and I had the honor of presenting my team’s:

f:id:edgeknight:20210914094439j:plain
Showing unique Portal captures on Ingress Prime: Image by ofer2

We used red dots at the bases of Portals and only showed unique Portal captures here, but programmer art aside, I think we made a really awesome feature, and wanted to work towards building a real version for production.

After the hackathon concluded, we returned to our normally scheduled work, but the team saw the results and the opportunity to bring to life a feature that Agents have been asking for for years. Brian, however, wanted the whole enchilada: prior Portal History included together with all Agent history from the beginning of the game AND to top it all off, he wanted it on the Intel Map, too.

Brian tasked the Engineering team with getting this feature out. Fortunately, we had a strong foundation with our hackathon project. Alas, it turned out to have a few kinks: returning Agent-specific data for every Portal is often unnecessary as the data hasn't changed since the last time you interacted with the Portal. To tackle this, we return the player data as a completely different piece of data from Portal data, and then put it together on the client. Unfortunately, the Intel Map doesn’t have real-time updates like the client does (it refreshes all data), so it still had to have the player data attached to the Portal.

The second major part of this was pre-populating the data with the entire history of Ingress gameplay. When we initially looked into this, we saw that the records storing actions that Agents have taken have a field for the entity. This field is hashed, meaning that we would have to reverse the hash to find out the actual entity that the record corresponds to. In order to do this, we created a process that would go through every single Portal ID and hash it so that we could use that hash to compare against the Agents’. Then we would go through each Agent’s records and find all the Portals they’ve visited and captured, and write that data to the structures that the client will query. After we finished writing this, we ran it in the dev environment and it wrote out the results to the datastore.

When we checked our results, they didn’t match the Portals we expected. It turns out that we made a mistake when examining the code that wrote out the tracking records. The function takes in a string which it calls ID, hashes it, and writes it to that field. The thing we missed was that this function wasn’t called in the case of visiting and capturing Portals. We instead called a different function first, which combined the ID of the Agent with the ID of the Portal. This means that the single list of hashes that we made before wouldn't work. We had to recreate the list from scratch for each individual Agent.

The cost for doing that process once was high, though reasonable; but multiply that for every Agent and the end-to-end process wasn’t feasible. The team put our heads down and went to work trying to reduce the cost. After some diligent effort and time optimizing the pipeline, we succeeded in reducing the cost significantly. One of the optimizations that helped us to do this was by making the flow run on a single machine per player instead of spreading it out to multiple machines. This involved some creative ways of reducing the size of the data that each machine would need to have in order to generate the results. Then, by specifically choosing to run the optimized backfill for Agents Level 3 and higher, we managed to get the cost into a reasonable range. The cost reduction of the engineering team here allowed us to give this feature to all Level 3 Agents and higher without making it a paid feature.

At this point, we had a working client and server, but we needed a clear way of displaying all of this data. We came up with a clever concept of “Layers.” This enabled us to only show Agents the information that they care about. The Art team took this on and came up with an initial concept, which everyone was so excited to try out. The first iteration was hard to read, so we continued to test different designs. The Art team quickly took that feedback from the team and from Vanguards, and increased the size of each Portal History ring and added spacing to the Portal, which made the rings easier to see at different zoom levels.

We have iterated quite a bit on this feature from the hackathon to during development before the launch all the way to development after launch as well. There were some additions that we knew players would want (e.g. invert on the intel map) but even without that, we knew that players would really enjoy this feature, so we elected to ship the core feature and then continue to iterate after launch. This iterative design process enables us to draw feedback from Agents and our teammates, and help guide the direction of the Ingress roadmap.

Player portal history was always something that we wanted to put on the roadmap as it was one of the top requested features from players, so we found a creative way to get it out to players. We’ve since expanded the Layers button to include Drones so Agents can see more activity going on around them. We hope to continue to make further enhancements in the future!

出典:Dev Diary: Player Portal History - Ingress Community Forums https://community.ingress.com/en/discussion/15850/-/p1, posted on July 10th, 2021.

-end-

*1:スカウトコントロールについては以下を参照。

解説:スカウトコントローラーメダル #Ingress|RuinDig|note
https://note.com/ruindig/n/nef483052cb50

*2:新型コロナウイルス(COVID-19)感染症の対応について - 内閣官房新型コロナウイルス感染症対策推進室
https://corona.go.jp

*3:

エンティティ(実体)とは - IT用語辞典 e-Words
https://e-words.jp/w/%E3%82%A8%E3%83%B3%E3%83%86%E3%82%A3%E3%83%86%E3%82%A3.html

エンティティとは | 株式会社データ総研
http://www.drinet.co.jp/blog/kurosawa/2006/%E3%82%A8%E3%83%B3%E3%83%86%E3%82%A3%E3%83%86%E3%82%A3%E3%81%A8%E3%81%AF.html

*4:ブライアン・ローズさん:Niantic社でIngressのシニアプロデューサーを務める。

*5:

データストア(data store)とは - IT用語辞典 e-Words
https://e-words.jp/w/%E3%83%87%E3%83%BC%E3%82%BF%E3%82%B9%E3%83%88%E3%82%A2.html

*6:

ヴァンガードに関する参考資料はこちらの2つを参照:

IngressのヴァンガードプログラムのFAQの日本語訳 - blog-RuinDig
https://ruindig.hatenablog.jp/entry/ingress/vanguard-faq-japanese

ヴァンガード:新体制発表 - PROJECT LYCAEUM
https://ingress.lycaeum.net/2019/09/20190927-1259.html