十字军之王3开发日志#92 | 4/6 游戏剖析:从报告问题到解决问题

牧游社 牧有汉化翻译
CK3 Dev Diary #92: Anatomy of a Game: From Report to Resolution
jakeowaty, Captain
Hello, everyone, and welcome back the Dev Diary. Let's have a little talk about the Anatomy of a Game (Development)! I'll be your host for today, @jakeowaty, current QA Lead on Crusader Kings 3, and I'm joined by Elisabeth, known as @PDXOxycoon in these here parts - a wonderful programmer, a gentlewoman and a scholar, who you may have seen a few times already! (Editor's note: Can I mention the raw onion story?)
大家好,欢迎回到开发日志。我们来稍微剖析一下游戏(开发)吧!我是今天的主持人@jakeowaty,CK3现任QA主管,另外还有伊丽莎白,也就是@PDXOxycoon,也一同参与,她是一位杰出的程序媛、优雅的淑女以及学者。你们可能已经见过她几次了!(编者注:我能说说那个生洋葱的故事吗?)
Today we are here to tell you about the process a bug goes through from being reported to being resolved and put into an update. Grab yourself the beverage of your choice, and enjoy this little expose on how we go about resolving bugs on Crusader Kings 3!
今天我们想跟大家介绍一下,一个bug从被报告上来到被解决再到更新修复的历程。各位拿好心仪的酒水饮料,听听我们讲在CK3中修bug的故事吧!

Bug report forum
Bug报告论坛

Whenever you launch the game, get yourself a your favorite gamer juice and steel your resolve for a few good hours of managing a medieval kingdom, there is a lot of stuff that happened behind the scenes to make it a memorable experience, free from issues. For a game as complex as Crusader Kings 3, this is even more important, and as the features get added to the game, it becomes more and more complicated, like a careful balancing act between economy, AI, warfare, interactions - all to ensure the experience you receive in the newest update is something worth waiting for!
每当你打开游戏、喝着快乐水、运营着中世纪王国的时候,在游戏的幕后都存在着大量的工作,只为了让你享受到难忘的、不存在问题的游戏体验。对一个像CK3这么复杂的游戏来说,这就显得尤为重要。例如,小心翼翼地平衡经济、AI、福利、交互——一切都是为了确保新版本的游戏体验是值得大家的等待的!
We are but a few humble people, whose job is to find out what works and what doesn’t and report any and all issues to the team before the game hits your platform of choice. But what happens when sometimes issues do remain hidden away and they will take spitting behind your shoulder 3 times on a 3rd fortnight of a 5th month on a leap year, during a full moon while dancing hokey-pokey to uncover?
我们几个庸人的职责是找出哪些运行得好、哪些运行得不好,然后在游戏登上平台发售之前把所有的问题报告给开发团队。但是有的时候仍有问题漏网,直到很久很久以后才被发现,这可怎么办呢?
This is where you can come in and help! We can get rid of serious, big issues, find them ahead of time before you even realize they existed in the first place, and try and deliver a polished product. For anything else - well, it's the economy of scale. With the variety of systems, languages, experiences, playstyles - YOU are the biggest help of all, in how a game can be shaped. And for that we have a special platform, called Bug Report Forum, where you can air out your grievances, provide feedback, reproduction steps and situations that irk you on the constant, so that our QA team can go through them much more efficiently and supply the team with a steady stream of new bugs to iron out for the next bug fix patch or release.
如果发生了这样的问题,你就可以来找我们帮忙了!我们可以解决严重的大问题,在你们还没意识到问题存在之前就把它们就出来,尝试着把产品打磨地更好。除此之外的话,就是规模效应了。这么多的系统、语言、经验、玩法——你们才是塑造游戏的过程中最大的帮手。为此目的,我们有一个特殊的平台,叫做Bug报告论坛,在其中,你们可以发泄不满、提供反馈、再现产生问题的情景。然后我们的质保团队就可以浏览大家的报告,更加高效地向开发团队稳定地汇报新bug,为下个补丁版本解决好问题。

Verification
验证

[Comic on logging][关于记录日志的漫画]
Once you take your valuable time to report the issue that ruins your mojo when playing Crusader Kings, it lands on our Forum as a thread. We then take turns, employing QA power to go through the forums and reproduce them locally on our machines to ensure if the issue is serious, or perhaps we can offer support by providing advice on how to avoid it.
在大家花费宝贵的时间汇报了破坏游戏体验的bug时,就会在我们的论坛上形成一个串儿(帖子)。然后我们就轮流派遣QA人员来浏览论坛,在我们自己的机器上复现bug,看看这个问题是否严重;或者,我们也可以向大家提供如何避免问题的建议。
Once the ticket is verified internally, using your reproduction steps, your description, screenshots, crash logs and saves, we then take the time to translate it into a language that our internal team can understand - Jira.
一旦我们按照你们的描述、截图、崩溃日志和存档成功复现了报告的问题、形成内部ticket之后,就会将其翻译为一种只有我们团队内部能够理解的语言——Jira。
There's an art to this. Jira is an extremely helpful and smart tool to use, and QA by nature becomes naturally adept at using it skillfully. It's usually the QA who does project-wide database evaluations, scrubbing it from obsolete tickets and updating any and all lingering ones to make sure the project team can focus on what's important - making more fun stuff!
这也是有窍门的。Jira是一个非常好用的智能工具,而QA人员天生就擅长使用它。QA常常会进行全项目组范围内的数据库评估,把过时的ticket清理或者升级。这样,项目组才能集中在真正重要的东西之上——制造出更多有趣的东西!
Wait… Obsolete? You mean you just CLOSE old tickets?
等下……过时的?你是说你们会把旧的ticket直接关闭掉?
We do… sometimes. For example, if we had an issue lingering around in some hidden away system for a long time (and sometimes not even you, our community, are able to find it for years on end) - the issue becomes obsolete - either we just make the bug into a feature, or because we change a system entirely - it becomes changed so it's not even a problem anymore. Happens on occasion, and even we are quite surprised that some bugs that linger for long never get picked up by the players, but hey - bugs are features too! Right?
确实……有时。打个比方,假如某个犄角旮旯的机制里有个问题藏了很久(有时候甚至玩家社区的大家好几年都发现不了)——这个问题就成了过时的——要不我们直接把bug整合进特性里,要么我们完全把机制改掉了——这个问题也就不再是问题了。有时候我们自己都感到惊讶,有些bug一直存在,玩家却永远发现不了,那句话怎么说来着——没有bug,只有特性嘛!对吧?
Riiiiiiiiiight…?
对吧……?

Jira - or how I learned to stop worrying about my mental health and learned to embrace the madness
Jira——我学会了不再担忧心理健康,而是拥抱疯狂

[Excerpt from "How to JIRA" guide][“如何JIRA”指南中的节选]
Now that might look like a lot, but trust me - there's a way to learn this. First of all, it's pretty straight forward; complex, but not complicated. We have been campaigning for the team to use Jira tickets efficiently. For example, if we get a ticket that says "Fix the thing we talked about yesterday" and there is nothing else in it, how can we remember what we have done, what needs to be done and how do we go back to it to retest? And again - fast-forward time to a month later and believe me - nobody will remember what in the world such a ticket meant.
虽然看起来很难,但是相信我——学习的方法也是有的。首先,它很直接、复杂但不繁复。我们已经让整个团队熟练地使用Jira处理ticket。例如,如果我们收到一个ticket,上面除了一句“修复我们昨天提到的那个东西”就什么都没有了,那我们又怎么能记的得之前做了什么、还要做什么、再测试该怎么进行呢?再说了——快进到一个月之后,就再也没人能看得懂这个ticket到底说的是啥了。
As a result, making sure all the fields are appropriately filled out is paramount to an efficient use of the database, and to make our team's lives just a little bit easier when having to deal with a big pile of bugs that come from you or from our internal reporting.
因此,将所有的框框都正确地填满,对数据库的高效使用是至关重要的,也让我们团队处理来自大家和来自内部的大量bug的工作稍微简单了一点点。
If everything is sorted, and everyone is on the same page, it makes it a lot easier for the producers to schedule when and how to report. And the upvoting on the forum? That's an incredibly powerful tool to let us know which tickets need to be prioritized and fixed, no matter what. That's how we can influence what is important to be fixed first.
如果所有东西都分好类了,所有人说话在同一个频道上,会对处理者安排报告的时间和方式有很大帮助。那在论坛里点赞顶帖呢?这种方式可以很好地帮助我们知道哪些东西需要优先处理和修复。我们可以通过这种方式影响需要优先处理的事项。

[QA chasing the Programmers]
[QA追着程序员]

The (un)lucky Programmer/Designer
(不)幸运的程序员/设计师
Once the bug has been verified, put into Jira, assigned a version and prioritized, a Developer can pick the bug up for fixing. This is typically either a Designer or a Programmer, depending on where the bug originates. In some cases the bug is reassigned to another role if the bug is found to be caused in another part of the game. Now that we know what the issue is and how it's experienced, we need to investigate the how and why it happens.
当确认bug后,放进Jira,分配版本并优先处理,开发者会拿到bug并开始处理。有可能是设计师或者程序员来处理,取决于bug是怎么产生的。有时候,如果bug是其他部分原因产生的,bug会被重新分配给其他人。现在我们知道了问题是什么和其效果,就需要调查它怎么样和为什么发生。
As an example of this, let us look at the recently fixed issue where the Holy Orders would raise themselves and immediately disband if they were called into a war. This issue was a side effect of letting Holy Orders raise themselves for Great Holy Wars: whenever a Holy Order is at war as an ally (which they qualify as during the Great Holy Wars), they would raise their armies.
下面是一个例子,让我们瞧瞧最近修复的“如果骑士团被召入战争,会召集并立即解散自己”的问题。这个问题是骑士团为大圣战召集自己的副作用:当骑士团作为战争中盟友时(在大圣战中符合这一条件),会召集自己的军队。
We start by looking at why the Order is disbanding the armies. If they're raised, they must have something to fight against, right?
首先我们看看为什么骑士团会解散自己的军队。他们都集结起来了,肯定需要和什么东西打一仗,对不对?

[Code controlling if a order's armies should be disbanded][控制骑士团解散军队的代码]
Through developer magic known as debug mode and source code, we can step through each individual function call to see what's going on. Stepping through this function leads us to finding that the war they are fighting in is not relevant to them, causing them to disband. Looking at the enemies in the war they're in, none of them are of a hostile Faith. We also see that they are hired by the Grandmaster of the Order, so the Order itself decided to raise their armies, not another character hiring them.
通过开发者神奇的调试模式和源代码,我们可以遍历所有函数,看看发生甚么事了。通过遍历,我们发现他们正在进行的战争和他们无关,所以他们解散了。看看他们交战的敌人,没有一个是敌对信仰的。我们同时发现他们被骑士团大团长雇佣,所以骑士团自己决定集结,而不是其他雇佣他们的人。
Holy Orders will not fight in wars where the opposing side does not have any participants of hostile Faith. Because we found the Holy Order disbanding due to being raised against an irrelevant enemy, we must look at the logic which controls how they are raised.
骑士团不会参加没有任何敌军是敌对信仰的战争。因为我们发现骑士团因为被召集对抗不相关敌军而解散,我们必须检查控制其被征召的逻辑。
I will spare you the initial dig down the AI code, but the short of it is: if a Holy Order can be raised, raise it. So we need to see why the Holy Order can be raised when it clearly should not be able to. We arrive at the following function.
我会挑出挖到的原始AI代码,但简要概括:如果骑士团可被召集,便进行召集。所以我们需要研究为什么骑士团会在显然不应该被召集的情况下可被召集。看以下函数。

[Code to check the Holy Order can be hired by a Character][检查骑士团可被角色雇佣的代码]
This logic checks if the Order can be hired by a character. This goes for any character looking to use the Order in question, even the Grandmaster of the Order. The Grandmaster of the Order is part of the war, so let us see where it leads us. Starting from the top, we check if the character can use the Order. Let us take a look at how that goes.
这是检查骑士团可被角色雇佣的逻辑。这会检查所有希望雇佣骑士团的人,甚至包括骑士团大团长。骑士团大团长是战争的一部分,所以让我们看看这会导致什么。从最上面开始,我们检查角色是否可使用骑士团。让我们看看这怎么进行。

[Code to check if a character can use the Holy Order][检查角色是否可使用骑士团的代码]
This is the code that checks if a character can use a Holy Order. When the Holy Order joins a war on its own, pCharacter is the Grandmaster, which is the Owner of the Holy Order. Makes perfect sense that the Grandmaster can make use of his own Holy Order. But, make notice further down: there is a check there, verifying if the character is in a relevant war. This is the same function that caused the army to disband earlier.
这段代码检查角色是否可使用骑士团。当骑士团自己加入战争时,pCharacter是骑士团的所有者大团长。理所当然大团长可以使用自己的骑士团。但是,继续向下看:这有个检查,确认角色是否在相关的战争中。这同样是导致军队提前解散的函数。
Returning back to the function which checks if a Holy Order can be hired by our Grandmaster.
返回到检查骑士团是否可以被我们的大团长雇佣的函数。

[Code to check the Holy Order can be hired by a Character 2][检查骑士团是否可以被大团长雇佣的代码 2]
To simplify things, I have collapsed all the irrelevant parts; contained within each "if" section is something which will cause the function to fail, and not allow the Grandmaster to hire their own Order. Let's go through these relevant parts. We don't run afoul with the hire limit, as we don't hire any other Holy Orders as the Grandmaster of one. We are not already hired by someone else, so we skip that part. We own this order, so we don't touch the third "if" statement. Finally, we can afford to hire our own Order. Look at that, this means we can hire the Holy Order!
为简便起见,我折叠了所有不相关的部分;每个“if”里包括了导致函数失效,不允许大团长雇佣自己骑士团的条件。让我们看看有关的部分。雇佣限制没有冲突,因为没有雇佣除了大团长的骑士团外的骑士团。我们没有被其他人雇佣,所以跳过这部分。我们拥有这个骑士团,所以不触发第三个“if”。最终,我们可以负担得起雇佣自己的骑士团。看看啊,这意味着我们可以雇佣这个骑士团!
But returning back to the finding that the Holy Order would immediately disband because we're not in a relevant war. This is where we find our solution: if we own our own Holy Order, we can always use it, but because of this we will not look if the war we are in is relevant at all. So we have a very simple solution to the problem.
但回到发现骑士团会因为处于不相关的战争而立即解散的问题。我们在这里发现了答案:如果我们拥有自己的骑士团,我们总是可以使用它,因此我们不会检查战争是否与我们相关。所以对这个问题,我们有一个非常简单的解决方案。

[Solution to preventing the Holy Order from being raised][防止骑士团被召集的解决方案]
We know that the check for relevant wars is only skipped if we are the Grandmaster of the Order, we don't need to check the faith of our own Grandmaster, so this is what we're left with: if the one who wants to hire the Holy Order is the Owner of the Order, check if they are in a relevant war or not.
我们知道跳过检查战争相关性仅会在我们是骑士团大团长的情况下跳过,我们不需要检查自己大团长的信仰,所以我们在这里增加了:如果希望雇佣骑士团的人是骑士团的所有者,检查他们是否在相关的战争中。

The merge request process - everyone judges you
合并请求的过程——所有人都要评判你
When the fix has been created, it is put into what is called the merge request; this is the step before the fix is put into the upcoming build. Before it gets merged, there are a bunch of steps. It starts with one or more members of the team of appropriate discipline reviewing the changes being applied. This is the first step to spotting any unintended side effects that may occur with any given fix. The scrutiny of these reviews increase drastically with several more steps if the fix will go into an upcoming release.
当bug修复被制作完成后,会将其放入一个叫合并请求的东西里。这是把修正加入未来补丁前的步骤。在合并前,有好几个步骤。刚开始是团队的一到多名相关专业成员审核改变了的内容。这是发现任何可能产生的无意的副作用的第一步。如果修正在即将发布的版本内,那么审核力度将大幅增加,并会多几个步骤。

[Merge request overview for Preventing Holy Orders from raising themselves in irrelevant wars][防止骑士团在无关战争中自行出征的合并请求概览]
Merge requests have a template that must be filled in when filed. This process gives both the fixer and reviewer the chance to review the implications of a fix and whether or not to actually commit it to a particular version. We make a serious consideration on how risky a fix is, can the fix have unintended knockon effects and the like, and how it impacts the overall performance of the game, does the fix contain a lot of computationally heavy code or script that will degrade performance.
提交合并请求时有必须要填写的模板。这个流程给修复担当和审阅担当的工作人员查询修复应用方法,以及是否按某一版解决方法执行。我们会针对修复的潜在风险进行严格把控,包括这样是否有计划之外的效果等问题,以及游戏整体运行会受什么影响、是否包括了会降低运行性能的大量硬编码或编程内容。
The golden rules of managing a merge request are:
处理整合请求时的基本法则如下:
1. Reviewer is right until proven wrong.
1、审核员被证明出错前,他说啥都是对的
2. Discipline leads are always right.
2、管理组长永远是对的。
Adding a fix to a build generally requires approval from the discipline lead, tech lead and another reviewer, and only the tech lead can actually merge the fix into the build when we’re getting close to release.
要给某版本添加修复通常需要管理组长、技术组长和另一位审核人员的同意,而且接近发布前能实际将修复内容添加进版本中的也只有技术组长。

[The commit fixing the issue][交付的问题修复]
The reviewer(s) give feedback on the merge request, and these must be addressed in one way or another. In the overview, you can see that the fix ended up being five commits in total, where just one of them was the fix itself. The rest were either addressing comments made in the review process or issues raised by the automated build and testing system, which we call pipelines. Speaking of pipelines.
审核员(们)会对合并请求提出反馈,并以某种形式进行处理。总体来说,你会发现最后共提交了5份内容,但真正的修复只有一个。其他的要么是回复审核流程中的评论或者是自动编制与测试系统提出的问题,我们称之为流水线。

[Pipeline for merge request][合并请求的流水线]
Pipelines verify the integrity and quality of the code, build the game and test the game while the review process is going. All of these must pass for the merge request to be allowed to merge, unless explicit permission is given from the programmers, and in the case of an upcoming release the pipelines must be green. No exceptions.
流水线会核实代码、游戏版本的完整性与质量,并在审核流程进行时测试游戏。所有流程通过后才能允许合并请求实际进行合并,除非程序员专门给了许可。而即将发布的内容则要求流水线全程达标,绝无例外。
Once the fix has been merged, we hand it back over to the sadistic wonderful QA team.
修复内容被合并后,便交付给高效而严酷的QA组。

The (sadistic) QA verification - where QA asks the fixer: “why are you running?!”
QA组(残酷的)核实流程——QA问修复员:你为啥要跑啊?

[Happy tears from QA after a 2 year old issue was resolved][历经两年的一个问题终于修复后,QA留下了幸福的泪水]
Once a ticket gets processed, churned through the complicated development machine, the issue is removed from the game, the correct code, script or asset gets merged into the game, the ticket is then marked as resolved.
在某个ticket获得处理、历经繁琐的开发流程后,便会移除游戏内的问题,让正确的代码、脚本或素材融入游戏,最后将这一单标为已解决。
This is when we get our hands on it one more time to verify if the fix has not only resolved the issue listed therein, but also that it has no other knock-on effects. Think of big fixes like a domino - you push one piece and the others will fall. Sometimes a different set of dominos, that we set up a long time ago, gets randomly pushed and some older issues reappear out of nowhere. It happens when you deal with a complex codebase, in which case you sometimes do see returning fan favorites.
在此我们又有一次机会来验证修复内容是否解决了相应问题,而且没有带来什么连锁效果。大型修复就像是一套多米诺骨牌,推倒一块必然会让后面的也倒下。有时候可能是很久以前码好的不同套牌,莫名受什么影响挨碰了,然后某些老问题又突然回来了。这种情况会在处理繁琐的代码库时出现,因此有时某些广为玩家喜爱的bug会唐突返场。
But all we have to do is revisit the older system, fix it (again), and away we go! Unless yet another bug is retriggered, in which case… Well, we have to revisit yet another system. Videogames are hard, m'kay?
但我们要做的也就是重查旧系统、(又双叒叕)修复,然后实装!除非又有其他bug出现,好吧……我们就得再重查另一个系统了。电子游戏开发很难的好吧。
This is why we sometimes need to go through the same ticket again, and again, and again, until we are very sure it fixes more things than it breaks. Sometimes bugs linger in our database for a long time. Trust us - we know that issues you mentioned on release still persist and we want to fix everything, but with every release we get a new batch of tickets we have to go through…
这也是有时要反复翻出旧帖的原因,直到能确定修复的内容多过改崩的部分。有时候某些bug会在数据库里存活很久。请理解,我们知道大家提到的发布时就有的问题还在,我们也想把所有内容都改好,但每一次发布都会新加一大堆要看的帖子……
So.
Many.
Times.
However, once the fix is confirmed, and the build is locked in - we are ready to press the large green button and release it for your scrutiny and repeat aforementioned hokey-pokey jig again!
猴
多
次!
不过在确认修复补订并保证版本稳定后,我们就会着手按下绿色按钮发布,并由各位玩家仔细审查,然后再照前文那套流程再搞一遍!

The Update - Are we done yet?
更新——做好了嘛?
Not really. On our end we close the bug as resolved, but there is one last thing that comes into play on resolving a bug. This is all of you, the community. The QA team catches most of the problems before a release is made, but sometimes for an issue to reoccur, you need the stars to align on a laptop manufactured on 29th of February 2020 while the player is doing the Macarena.
没完全好。我们这边还没确定bug都解决了,不过修复bug这套流程里还有一个要素,就是各位玩家——我们的社群。QA组会在发布前尽量查清所有问题,但有时候要查明某个问题,需要“行星与2020年2月29日产笔记本电脑成一线且玩家正玩着二人转”这种级别的天时地利人和。
Paraphrasing, of course, but there are cases where an issue can reemerge later. This can happen due to a myriad of factors that are hard to account for. Some issues can be related to hardware and specific circumstances we don't have the capacity to test for. This is when we rely on you all to be vigilant and report issues back to us. The more detailed and specified, the better. Once this is done we go back from the top of this expose and do the dance one more time.
例子举得有点歪,不过有时某些问题可能之后才会复现,可能是由一坨难以考量的因素所致。有些问题可以与硬件或其他我们无法满足测试条件的特定情况挂钩,所以这时我们才会依靠诸位玩家打起精神提供问题反馈,越详细具体越好。之后,我们会从头整理发现的问题并再来一遍整个流程。
If no one in the community experiences the issue again, then it stays resolved.
如果社群内没有其他人再遇到这个问题,那么它就归档为已解决。
I hope you all have enjoyed this little expose into the life of a game developer! Thank you for your time, and we wish you a very pleasant day.
希望大家中意本次游戏开发者的小揭底!谢谢大家来看日志,祝日安!
Until next time!
下周再见!
翻译:一个幽灵 毛里求斯大酋长 zzztotoso
校对:三等文官猹中堂
欢迎关注UP主和主播小牧Phenix!
欢迎关注牧游社微信公众号和知乎专栏!微信公众号改版为信息流,欢迎【置顶订阅】不迷路,即时获得推送消息!
B站在关注分组中设置为【特别关注】,将会在私信内及时收到视频和专栏投稿的推送!
欢迎加入牧有汉化, 致力于为玩家社群提供优质内容!组员急切募集中!测试群组822400145!
本作品英文原文著作权属Paradox interactive AB所有,中文译文著作权属牧有汉化所有。