The Endfield Talos Wiki is a collaborative, community-maintained wiki. If you notice an issue, please consider making an edit, starting a discussion on the article's talk page, or reaching out via the Endfield Talos Wiki Discord.
+
In Arknights: Endfield, events, act as additional gameplay to the game, where the player go through different objectives to obtain rewards. Some of them may not require combat while some may, but with a well built team, one should obtain everything without much sweat.
+
There are currently no upcoming limited-time events.
There are currently no upcoming limited-time events.
+
By year
+
For a list of events released in 2026, see Event/2026.
+
Beginner Events
+
New Horizons Giveaway EXPO Panel
+
Exclusive to new players, the Panel functions as a summary of all Oroberyls, Permits or any additional reward that players can obtain, excluding those that comes from exploration such as Crates or Aurelynes.
+
After all rewards are obtained, the Panel will be removed from the list.
+
Unlike most Headhunting banners, New Horizons Headhunting use New Horizons HH-10×Permit and do not have the rate increase corresponding to pulls; However, the Operator pool is equivalent to Basic Headhunting, and a 6★ is guaranteed at 40th pull.
+
After 40 pulls, a New Horizons Weapon Supply will be given and the banner will be removed from the list.
+
+
Awakening Sign-In
+
A special Sign-In only available to new players, Awakening Sign-In is available for 14 days since the Event Center was unlocked:
+
+
+
+
+
Day 1
+
+
1
5
+
+
+
Day 2
+
+
1
30
+
+
+
Day 3
+
+
1
16
+
+
+
Day 4
+
+
1
12
+
+
+
Day 5
+
+
1
24K
+
+
+
Day 6
+
+
1
10
+
+
+
Day 7
+
+
1
5
+
+
+
Day 8
+
+
1
15
+
+
+
Day 9
+
+
1
24K
+
+
+
Day 10
+
+
1
10
+
+
+
Day 11
+
+
1
10
+
+
+
Day 12
+
+
1
5
+
+
+
Day 13
+
+
1
15
+
+
+
Day 14
+
+
1
24K
+
+
Authority Level Rewards
+
When new players reach a specific Authority Level, extra rewards will be given on top of the default rewards. This event will be removed from the list after all rewards are obtained.
+
Cookies help us deliver our services. By using our services, you agree to our use of cookies.
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/scripts/build-feed.ts b/scripts/build-feed.ts
index 90d1036..8f750d2 100644
--- a/scripts/build-feed.ts
+++ b/scripts/build-feed.ts
@@ -14,13 +14,20 @@ import type { GachaEvent, GameId } from "../src/shared/schema.ts";
const OUT = "public/data/events.v1.json";
-/** Newest fixture per adapter. */
+/**
+ * Newest fixture for one *source*, not one game.
+ *
+ * A game can have several sources, and fixtures are named `-events-`
+ * against adapter ids of `--events`. Globbing by game alone hands
+ * one site's page to another site's parser.
+ */
async function latestFixture(adapterId: string, game: GameId) {
- const glob = new Bun.Glob(`fixtures/${game}/*.html`);
- const files = [...glob.scanSync(".")].sort();
+ const site = adapterId.replace(`${game}-`, "").replace(/-events$/, "");
+ const pattern = `fixtures/${game}/${site}-*.html`;
+ const files = [...new Bun.Glob(pattern).scanSync(".")].sort();
const file = files.at(-1);
if (file === undefined) {
- throw new Error(`no fixture found for ${adapterId} (fixtures/${game}/*.html)`);
+ throw new Error(`no fixture found for ${adapterId} (${pattern})`);
}
return { file, html: await Bun.file(file).text() };
}
diff --git a/src/ingest/adapters/index.ts b/src/ingest/adapters/index.ts
index b96df67..6055f15 100644
--- a/src/ingest/adapters/index.ts
+++ b/src/ingest/adapters/index.ts
@@ -50,6 +50,15 @@ const SOURCES: SourceSpec[] = [
url: "https://game8.co/games/Arknights-Endfield/archives/535443",
parserId: "game8",
},
+ {
+ id: "endfield-wikigg-events",
+ game: "endfield",
+ url: "https://endfield.wiki.gg/wiki/Event",
+ parserId: "wikigg",
+ // Exact timestamps and per-region ends beat Game8's day-precision prose,
+ // so this source wins when the two disagree.
+ priority: 10,
+ },
{
id: "nte-game8-events",
game: "nte",
diff --git a/src/ingest/merge.ts b/src/ingest/merge.ts
index 69a29a6..6792153 100644
--- a/src/ingest/merge.ts
+++ b/src/ingest/merge.ts
@@ -123,12 +123,36 @@ function isSameEvent(
toleranceHours: number,
): boolean {
if (a.id === b.id) return true;
- if (titleSimilarity(a.title, b.title) < titleThreshold) return false;
+
+ // Overlap alone misses a source that appends a qualifier: "Bedazzling
+ // Dawnstar" vs "Bedazzling Dawnstar Sign-In" scores 0.67, well under any
+ // safe threshold, yet is plainly one event.
+ const similar =
+ titleSimilarity(a.title, b.title) >= titleThreshold ||
+ titleExtends(a.title, b.title);
+ if (!similar) return false;
+
// Similar titles are not enough — reruns reuse names. Require the start dates
// to be close before treating two entries as one event.
return hoursBetween(a.startsAt, b.startsAt) <= toleranceHours;
}
+/**
+ * True when one title is the other with words appended — the shape a source
+ * qualifier actually takes ("Bedazzling Dawnstar" → "… Sign-In").
+ *
+ * Deliberately a prefix test, not a subset test. Subset matching would also
+ * fuse "Gold Clash" with "Gold Rush Clash Royale", which are different events.
+ * Two words is the floor: a one-word title would swallow half the calendar.
+ */
+export function titleExtends(a: string, b: string): boolean {
+ const ta = tokenList(a);
+ const tb = tokenList(b);
+ const [small, large] = ta.length <= tb.length ? [ta, tb] : [tb, ta];
+ if (small.length < 2 || small.length === large.length) return false;
+ return small.every((word, i) => large[i] === word);
+}
+
function findConflict(
a: GachaEvent,
b: GachaEvent,
@@ -163,12 +187,14 @@ export function titleSimilarity(a: string, b: string): number {
return (2 * shared) / (ta.size + tb.size);
}
+function tokenList(title: string): string[] {
+ return title
+ .toLowerCase()
+ .replace(/[^a-z0-9\s]/g, " ")
+ .split(/\s+/)
+ .filter((w) => w.length > 0);
+}
+
function tokens(title: string): Set {
- return new Set(
- title
- .toLowerCase()
- .replace(/[^a-z0-9\s]/g, " ")
- .split(/\s+/)
- .filter((w) => w.length > 0),
- );
+ return new Set(tokenList(title));
}
diff --git a/src/ingest/parsers/index.ts b/src/ingest/parsers/index.ts
index 7a5e7f3..43037aa 100644
--- a/src/ingest/parsers/index.ts
+++ b/src/ingest/parsers/index.ts
@@ -1,4 +1,5 @@
import { game8Parser } from "./game8.ts";
+import { wikiGgParser } from "./wikigg.ts";
import type { SourceParser } from "./types.ts";
/**
@@ -6,7 +7,7 @@ import type { SourceParser } from "./types.ts";
* an entry in `adapters/index.ts`; adding a new *site* means a parser module
* here and one line below.
*/
-export const PARSERS: SourceParser[] = [game8Parser];
+export const PARSERS: SourceParser[] = [game8Parser, wikiGgParser];
export function parserById(id: string): SourceParser | undefined {
return PARSERS.find((p) => p.id === id);
@@ -14,3 +15,4 @@ export function parserById(id: string): SourceParser | undefined {
export type { SourceParser } from "./types.ts";
export { game8Parser } from "./game8.ts";
+export { wikiGgParser } from "./wikigg.ts";
diff --git a/src/ingest/parsers/wikigg.ts b/src/ingest/parsers/wikigg.ts
new file mode 100644
index 0000000..e24bfaa
--- /dev/null
+++ b/src/ingest/parsers/wikigg.ts
@@ -0,0 +1,142 @@
+import {
+ eventId,
+ Region,
+ type GachaEvent,
+ type Precision,
+} from "../../shared/schema.ts";
+import { text } from "../html.ts";
+import type { ParseContext } from "../adapters/types.ts";
+import { inferType } from "./game8.ts";
+import type { SourceParser } from "./types.ts";
+
+/**
+ * wiki.gg event pages (MediaWiki with the `mp-event` template).
+ *
+ * A markedly better source than a prose wiki: each event carries machine
+ * readable ISO timestamps, one timer per server region:
+ *
+ *
+ *
[TITLE]
+ * Challenge Event
+ *
+ *
+ *
Asia:…
+ *
+ * This is the first source that states region-scoped ends, which is what
+ * `regionScoped` / `regionEnds` exist for — the difference is up to 13 hours.
+ */
+
+const EVENT_BLOCK = /